diff --git a/client/package-lock.json b/client/package-lock.json index ca679c7..8156720 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -19,11 +19,11 @@ "@types/react-redux": "^7.1.22", "axios": "^0.26.0", "history": "^5.3.0", - "i18next": "^22.4.9", + "i18next": "^23.6.0", "i18next-browser-languagedetector": "^7.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-i18next": "^12.1.5", + "react-i18next": "^13.3.1", "react-markdown": "^8.0.2", "react-redux": "^7.2.6", "react-router": "^6.2.2", @@ -51,11 +51,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.16.7" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -160,13 +161,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", - "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" @@ -282,12 +284,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dependencies": { - "@babel/types": "^7.16.7" - }, + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } @@ -304,35 +303,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dependencies": { - "@babel/types": "^7.16.7" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -448,20 +435,28 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -502,12 +497,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -515,9 +510,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.3.tgz", - "integrity": "sha512-7yJPvPV+ESz2IUTPbOL+YkIGyCqOyNIzdguKQuJGnH7bg1WTIifuM21YqokFt/THWh1AkCRn9IgoykTRCBVpzA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1800,11 +1795,11 @@ } }, "node_modules/@babel/runtime": { - "version": "7.17.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", - "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { - "regenerator-runtime": "^0.13.4" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -1822,32 +1817,37 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1856,11 +1856,12 @@ } }, "node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -8949,9 +8950,9 @@ } }, "node_modules/i18next": { - "version": "22.4.9", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.4.9.tgz", - "integrity": "sha512-8gWMmUz460KJDQp/ob3MNUX84cVuDRY9PLFPnV8d+Qezz/6dkjxwOaH70xjrCNDO+JrUL25iXfAIN9wUkInNZw==", + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.6.0.tgz", + "integrity": "sha512-z0Cxr0MGkt+kli306WS4nNNM++9cgt2b2VCMprY92j+AIab/oclgPxdwtTZVLP1zn5t5uo8M6uLsZmYrcjr3HA==", "funding": [ { "type": "individual", @@ -8967,7 +8968,7 @@ } ], "dependencies": { - "@babel/runtime": "^7.20.6" + "@babel/runtime": "^7.22.5" } }, "node_modules/i18next-browser-languagedetector": { @@ -8978,38 +8979,6 @@ "@babel/runtime": "^7.19.4" } }, - "node_modules/i18next-browser-languagedetector/node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/i18next-browser-languagedetector/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/i18next/node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/i18next/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -12498,9 +12467,15 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=" }, "node_modules/nanoid": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.1.tgz", - "integrity": "sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13162,9 +13137,9 @@ } }, "node_modules/postcss": { - "version": "8.4.12", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.12.tgz", - "integrity": "sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==", + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", "funding": [ { "type": "opencollective", @@ -13173,10 +13148,14 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "nanoid": "^3.3.1", + "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -14622,15 +14601,15 @@ } }, "node_modules/react-i18next": { - "version": "12.1.5", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-12.1.5.tgz", - "integrity": "sha512-7PQAv6DA0TcStG96fle+8RfTwxVbHVlZZJPoEszwUNvDuWpGldJmNWa3ZPesEsZQZGF6GkzwvEh6p57qpFD2gQ==", + "version": "13.3.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.3.1.tgz", + "integrity": "sha512-JAtYREK879JXaN9GdzfBI4yJeo/XyLeXWUsRABvYXiFUakhZJ40l+kaTo+i+A/3cKIED41kS/HAbZ5BzFtq/Og==", "dependencies": { - "@babel/runtime": "^7.20.6", + "@babel/runtime": "^7.22.5", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { - "i18next": ">= 19.0.0", + "i18next": ">= 23.2.3", "react": ">= 16.8.0" }, "peerDependenciesMeta": { @@ -14642,22 +14621,6 @@ } } }, - "node_modules/react-i18next/node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/react-i18next/node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -15652,14 +15615,6 @@ "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", diff --git a/client/package.json b/client/package.json index 8d0e8b1..e469952 100644 --- a/client/package.json +++ b/client/package.json @@ -15,11 +15,11 @@ "@types/react-redux": "^7.1.22", "axios": "^0.26.0", "history": "^5.3.0", - "i18next": "^22.4.9", + "i18next": "^23.6.0", "i18next-browser-languagedetector": "^7.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-i18next": "^12.1.5", + "react-i18next": "^13.3.1", "react-markdown": "^8.0.2", "react-redux": "^7.2.6", "react-router": "^6.2.2", diff --git a/client/public/index.html b/client/public/index.html index 3b3fcef..0f446e5 100644 --- a/client/public/index.html +++ b/client/public/index.html @@ -1,5 +1,5 @@ - + @@ -25,6 +25,8 @@ Learn how to configure a non-root public URL by running `npm run build`. --> + + Agon Dice Roller diff --git a/client/public/themes/agon/de.json b/client/public/themes/agon/de.json new file mode 100644 index 0000000..e5dd0e6 --- /dev/null +++ b/client/public/themes/agon/de.json @@ -0,0 +1,105 @@ +{ + "Agon Dice Roller": "Agon Dice Roller", + "Agon Roller": "Agon Roller", + "About this App": "Über diese App", + "Remove": "Entfernen", + "Add": "Hinzufügen", + "Tie Breaker": "Entscheidungswurf", + "This is the tie breaking roll": "Dies ist der Entscheidungswurf", + "This dice contributed to the score": "Dieser Würfel trug zum Ergebnis bei", + "This dice was dropped and did not contribute to the final score": "Dieser Würfel wurde fallen gelassen und hat nicht zum Endergebnis beigetragen.", + "This was the result of your roll": "Das war das Ergebnis deines Würfelwurfs", + "This is the target number to beat in this contest": "Dies ist die Zielzahl, die es bei dieser Aufgabe zu schlagen gilt", + "There is a problem with your connection, attempting to reconnect. Please wait...": "Es gibt ein Problem mit deiner Verbindung, es wird versucht die Verbindung wiederherzustellen. Bitte warten...", + "Copied to clipboard!": "In die Zwischenablage kopiert!", + "Copy Invite Link": "Einladungslink kopieren", + "Name Dice": "Namenswürfel", + "Select Name Dice": "Wähle Namenswürfel", + "Cancel Tie Break Roll": "Entscheidungswurf abbrechen", + "Cancel": "Abbrechen", + "Rolling Name Die...": "Namenswürfel werfen...", + "Roll Again": "Nochmal würfeln", + "Break Tie": "Über Gleichstand entscheiden", + "Enter note here": "Hier Aufzeichnungen notieren", + "Enter note here, in #markdown if you like...": "Hier kannst du Aufzeichnungen mit #markdown notieren, wenn du möchtest...", + "Add Note": "Aufzeichnungen hinzufügen", + "Notes": "Aufzeichnungen", + "There are currently no notes": "Es existieren zur Zeit keine Aufzeichnungen", + "Delete note": "Aufzeichnung entfernen", + "Save": "Speichern", + "Join as": "Teilnehmen als", + "Strife Player": "Unfrieden", + "Sacred": "Geweiht", + "Perilous": "Lebensgefährlich", + "Mythic": "Mytisch", + "Epic": "Episch", + "ContestIsEpic": "Die Aufgabe ist episch - Held:innen, die sich der Aufgabe stellen, büßen Impuls ein", + "ContestIsMythic": "Die Aufgabe ist mytisch - Held:innen, die sich der Aufgabe stellen, büßen eine göttliche Gunst ein", + "ContestIsPerilous": "Die Aufgabe ist lebensgefährlich - Held:innen, die scheitern, büßen Impuls ein", + "ContestIsSacred": "Die Aufgabe ist geweiht - Held:innen, die scheitern, büßen eine göttliche Gunst ein", + "Harms": "Unheil", + "AboutCreatedBy": "Diese App wurde von @gareth und @sporgory mit Hilfe der AGON-Fan-Discord-Community erstellt und von [Waldemar Müller] anhand der deutschen Version von System Matters übersetzt.", + "AboutFeedback": "Wenn du Feedback geben oder dich beteiligen möchtest, besuche das GitHub Repo.", + "AboutAgon": "Dies ist eine von Fans entwickelte Würfel-App für AGON. AGON ist ein actiongeladenes Rollenspiel über epische Held:innen, die sich in einer alten Welt der Mythen und Legenden den Prüfungen der Gottheiten stellen. Erfahre mehr über AGON und über das Paragon-System unter agon-rpg.com und über AGON auf Deutsch unter https://www.system-matters.de/shop/agon.", + "AboutRules": "Mindestens eine spielende Person sollte mit dem AGON-Regelwerk vertraut sein.", + "AboutApp": "Diese App ist eine leichtgewichtige Würfel-App, die sich auf das Lösen und Erzählen von AGON Aufgaben konzentriert. Dies ist kein Kampagnen- oder Charakterarchiv - das müsst ihr immer noch selbst verwalten, z.B. mit dem [Add Link of the Agon Archiver].", + "Close Contest": "Aufgabe beenden", + "Contest Results": "Ergebnisse der Aufgabe", + "Join the Contest": "Der Aufgabe stellen", + "A record of how the Heroes fared in the Contest. Heroes should narrate their results in ascending order - the Strife player should respond with the Opponents reactions.": "Eine Aufzeichnung darüber, wie die Held:innen in der Aufgabe abgeschnitten haben. Die Held:innen sollten ihr Abschneiden in aufsteigender Reihenfolge schildern - der Unfrieden sollte im Laufe des Vortrags mit den Reaktionen der Widersacher:innen antworten.", + "This is the challenge that the Heroes must strive to overcome.": "Dies ist die Herausforderung, der sich die Held:innen stellen müssen.", + "The Challenge": "Die Herausforderung", + "Prevails": "Ist erfolgreich", + "Suffers": "Scheitert", + "New Contest": "Neue Aufgabe", + "Between Contests": "Zwischen den Aufgaben", + "You are a glorious Hero - strive to take on worthy opponents. If a challenge isn't glorious, you can accomplish it without need for a Contest.": "Du bist ein:e glorreiche:r Held:in - strebe danach, es mit würdigen Widersacher:innen aufzunehmen. Wenn eine Herausforderung nicht glorreich ist, kannst du sie auch ohne Aufgabe bewältigen.", + "Waiting for the next Glorious Contest": "Warten auf die nächste glorreiche Aufgabe", + "Create a Contest": "Erschaffe eine Aufgabe", + "Create a contest whenever the heroes come into conflict with a worthy opponent.": "Erschaffe eine Aufgabe, wenn die Held:innen mit würdigen Widersacher:innen in Konflikt geraten.", + "Set the Strife Level": "Ausmaß des Unfriedens", + "Adjust the strife level for permanent or temporary advantages or disadvantages.": "Passe das Ausmaß des Unfriedens für dauerhafte oder vorübergehende Vorteile oder Nachteile an.", + "Build the Opponent's Dice Pool": "Würfel der Widersacher:innen", + "Add dice for the Opponent's Name, Epithets, and any Bonus dice.": "Füge Würfel für den Namen der Widersacher:innen, relevante Beinamen und eventuell zusätzliche Würfel der Würfelhand des Unfriedens hinzu.", + "Choose Harm Tags": "Unheil der Aufgabe", + "ChooseSacredHarm": "Geweiht - Held:innen büßen eine göttliche Gunst ein, wenn sie in der Aufgabe scheitern.", + "ChoosePerilousHarm": "Lebensgefährlich - Held:innen büßen Impuls ein, wenn sie in der Aufgabe scheitern.", + "ChooseMythicHarm": "Mythisch - Held:innen büßen eine göttliche Gunst ein, wenn sie sich der Aufgabe stellen.", + "ChooseEpicHarm": "Episch - Held:innen büßen Impuls ein, wenn sie sich der Aufgabe stellen.", + "Roll the Contest": "Die Aufgabe würfeln", + "Each Hero Player indicates their participation in the contest by speaking their name. Start with the leader and go around the table. On your turn, recite your hero's name and add dice as you go: If your Epithet applies to the contest, add that die. Add your Name die and the Domain die for the contest when you say your Name and lineage.": "Die Held:innen zeigen ihre Teilnahme an der Aufgabe an, indem sie nacheinander ihre Namen nennen, beginnend mit der Person, die die Gruppe gerade anführt. Wer an der Reihe ist, trägt die gesamte Held:innenindentität vor und stellt die entsprechende Würfelhand zusammen: Name, falls relevant Beiname, Abstammung und abschließend Aspekt.", + "Join the Contest!": "Der Aufgabe stellen!", + "Ready": "Bereit", + "Leave": "Verlassen", + "Who Will Join the Contest?": "Wer stellt sich der Aufgabe?", + "Heroes join the contest by building their dice pool. Make sure everyone is ready before rolling the final results!": "Held:innen stellen sich mit dem Bau ihrer Würfelhand der Aufgabe. Vergewissere dich, dass alle bereit sind, bevor die Endergebnisse gewürfelt werden!", + "No one has joined": "Niemand stellt sich", + "Roll Results": "Endergebnisse würfeln", + "Strife Level": "Ausmass des Unfriedens", + "Are you Strife or a Hero?": "Unfrieden oder Held:in?", + "Every game of AGON needs one Strife player and at least two Hero Players.": "Jede Spielsitzung von AGON benötigt mindestens drei spielende Personen, eine spielt den Unfrieden und der Rest die Held:innen.", + "The Strife player challenges the Heroes with worthy Contests.": "Der Unfrieden fordert die Held:innen mit würdigen Aufgaben heraus.", + "Join as Strife Player": "Als Unfrieden spielen", + "Hero Player": "Held:in", + "Hero players contend against the Contests presented by the Strife player.": "Held:innen stellen sich der Aufgabe, die vom Unfrieden präsentiert werden.", + "Enter your Hero's Name": "Bennen deine:n Held:in", + "Join as new Hero": "Als Held:in spielen", + "AboutAgonDiceRoller": "Dies ist eine von Fans entwickelte Würfel-App für AGON. AGON ist ein actiongeladenes Rollenspiel über epische Held:innen, die sich in einer alten Welt der Mythen und Legenden den Prüfungen der Gottheiten stellen. Erfahre mehr über AGON und über das Paragon-System unter agon-rpg.com und über AGON auf Deutsch unter https://www.system-matters.de/shop/agon.", + "Start a New Session": "Starte ein neues Spiel", + "Start a new session. You'll be able to invite other players with a unique URL.": "Starte ein neues Spiel. Du wirst weitere Spieler:innen einladen können.", + "Start New Game": "Starte ein neues Spiel", + "Have an Invite Link?": "Hast du eine Einladung?", + "Paste it below or in your browser and click Join Game.": "Füge den Einladungslink unten oder in deinen Browser ein und klicke auf \"Am Spiel teilnehmen\".", + "Existing game id": "Vorhandene Spiel-ID", + "Join Game": "Am Spiel teilnehmen", + "link1": "https://www.reddit.com/user/gareththegeek/", + "link2": "https://twitter.com/sporgory", + "link3": "https://discord.gg/2kWxhJywGq", + "link4": "https://github.com/gareththegeek/rollagon", + "link5": "https://agon-rpg.com", + "link6": "https://www.system-matters.de/shop/agon", + "link7": "", + "link8": "", + "link9": "", + "link10": "" +} diff --git a/client/public/themes/agon/en.json b/client/public/themes/agon/en.json new file mode 100644 index 0000000..2f42fd9 --- /dev/null +++ b/client/public/themes/agon/en.json @@ -0,0 +1,107 @@ +{ + "Agon Dice Roller": "Agon Dice Roller", + "Agon Roller": "Agon Roller", + "About this App": "About this App", + "Remove": "Remove", + "Add": "Add", + "Tie Breaker": "Tie Breaker", + "This is the tie breaking roll": "This is the tie breaking roll", + "This dice contributed to the score": "This dice contributed to the score", + "This dice was dropped and did not contribute to the final score": "This dice was dropped and did not contribute to the final score", + "This was the result of your roll": "This was the result of your roll", + "This is the target number to beat in this contest": "This is the target number to beat in this contest", + "There is a problem with your connection, attempting to reconnect. Please wait...": "There is a problem with your connection, attempting to reconnect. Please wait...", + "Copied to clipboard!": "Copied to clipboard!", + "Copy Invite Link": "Copy Invite Link", + "Name Dice": "Name Dice", + "Select Name Dice": "Select Name Dice", + "Cancel Tie Break Roll": "Cancel Tie Break Roll", + "Cancel": "Cancel", + "Rolling Name Die...": "Rolling Name Die...", + "Roll Again": "Roll Again", + "Break Tie": "Break Tie", + "Enter note here": "Enter note here", + "Enter note here, in #markdown if you like...": "Enter note here, in #markdown if you like...", + "Add Note": "Add Note", + "Notes": "Notes", + "There are currently no notes": "There are currently no notes", + "Delete note": "Delete note", + "Save": "Save", + "Join as": "Join as", + "Strife Player": "Strife Player", + "Sacred": "Sacred", + "Perilous": "Perilous", + "Mythic": "Mythic", + "Epic": "Epic", + "ContestIsEpic": "The Contest is Epic- Heroes who enter the contest must mark Pathos", + "ContestIsMythic": "The Contest is Mythic - Heroes who enter the contest must spend Divine Favour", + "ContestIsPerilous": "The Contest is Perilous - Heroes who suffer must mark Pathos", + "ContestIsSacred": "The Contest is Sacred - Heroes who suffer must spend Divine Favour", + "Harms": "Harms", + "AboutCreatedBy": "This app was created by @gareth and @sporgory with the help of the AGON fan Discord community.", + "AboutFeedback": "If you'd like to give feedback or get involved, check out the GitHub repo.", + "AboutAgon": "This is a fan-made dice-rolling app for AGON. AGON is an action-packed roleplaying game about epic Heroes who face trials from the Gods in an ancient world of myth and legend. Learn more about it, and the Paragon system, at agon-rpg.com", + "AboutRules": "At least one person playing should be familiar with the AGON rulebook.", + "AboutApp": "This app is a lightweight dicerolling app, focused on resolving and narrating AGON Contests. It is not a campaign or character tracker - you'll still need to manage that on your own.", + "Close Contest": "Close Contest", + "Contest Results": "Contest Results", + "Join the Contest": "Join the Contest", + "A record of how the Heroes fared in the Contest. Heroes should narrate their results in ascending order - the Strife player should respond with the Opponents reactions.": "A record of how the Heroes fared in the Contest. Heroes should narrate their results in ascending order - the Strife player should respond with the Opponents reactions.", + "This is the challenge that the Heroes must strive to overcome.": "This is the challenge that the Heroes must strive to overcome.", + "The Challenge": "The Challenge", + "Prevails": "Prevails", + "Suffers": "Suffers", + "New Contest": "New Contest", + "Between Contests": "Between Contests", + "You are a glorious Hero - strive to take on worthy opponents. If a challenge isn't glorious, you can accomplish it without need for a Contest.": "You are a glorious Hero - strive to take on worthy opponents. If a challenge isn't glorious, you can accomplish it without need for a Contest.", + "Waiting for the next Glorious Contest": "Waiting for the next Glorious Contest", + "Create a Contest": "Create a Contest", + "Create a contest whenever the heroes come into conflict with a worthy opponent.": "Create a contest whenever the heroes come into conflict with a worthy opponent.", + "Set the Strife Level": "Set the Strife Level", + "Adjust the strife level for permanent or temporary advantages or disadvantages.": "Adjust the strife level for permanent or temporary advantages or disadvantages.", + "Build the Opponent's Dice Pool": "Build the Opponent's Dice Pool", + "Add dice for the Opponent's Name, Epithets, and any Bonus dice.": "Add dice for the Opponent's Name, Epithets, and any Bonus dice.", + "Choose Harm Tags": "Choose Harm Tags", + "ChooseSacredHarm": "Sacred - Heroes spend Divine Favor if they suffer in the Contest.", + "ChoosePerilousHarm": "Perilous - Heroes mark Pathos if they suffer in the Contest.", + "ChooseMythicHarm": "Mythic - Heroes spend Divine Favor to enter the Contest.", + "ChooseEpicHarm": "Epic - Heroes mark Pathos to enter the Contest.", + "Roll the Contest": "Roll the Contest", + "Each Hero Player indicates their participation in the contest by speaking their name. Start with the leader and go around the table. On your turn, recite your hero's name and add dice as you go: If your Epithet applies to the contest, add that die. Add your Name die and the Domain die for the contest when you say your Name and lineage.": "Each Hero Player indicates their participation in the contest by speaking their name. Start with the leader and go around the table. On your turn, recite your hero's name and add dice as you go: If your Epithet applies to the contest, add that die. Add your Name die and the Domain die for the contest when you say your Name and lineage.", + "Join the Contest!": "Join the Contest!", + "Ready": "Ready", + "Leave": "Leave", + "Who Will Join the Contest?": "Who Will Join the Contest?", + "Heroes join the contest by building their dice pool. Make sure everyone is ready before rolling the final results!": "Heroes join the contest by building their dice pool. Make sure everyone is ready before rolling the final results!", + "No one has joined": "No one has joined", + "Roll Results": "Roll Results", + "Strife Level": "Strife Level", + "Are you Strife or a Hero?": "Are you Strife or a Hero?", + "Every game of AGON needs one Strife player and at least two Hero Players.": "Every game of AGON needs one Strife player and at least two Hero Players.", + "The Strife player challenges the Heroes with worthy Contests.": "The Strife player challenges the Heroes with worthy Contests.", + "Join as Strife Player": "Join as Strife Player", + "Hero Player": "Hero Player", + "Hero players contend against the Contests presented by the Strife player.": "Hero players contend against the Contests presented by the Strife player.", + "Enter your Hero's Name": "Enter your Hero's Name", + "Join as new Hero": "Join as new Hero", + "AboutAgonDiceRoller": "This is a fan-made dice-rolling app for AGON. AGON is an action-packed roleplaying game about epic Heroes who face trials from the Gods in an ancient world of myth and legend. Learn more about it, and the Paragon system, at agon-rpg.com", + "Start a New Session": "Start a New Session", + "Start a new session. You'll be able to invite other players with a unique URL.": "Start a new session. You'll be able to invite other players with a unique URL.", + "Start New Game": "Start New Game", + "Have an Invite Link?": "Have an Invite Link?", + "Paste it below or in your browser and click Join Game.": "Paste it below or in your browser and click Join Game.", + "Existing game id": "Existing game id", + "Join Game": "Join Game", + "Cannot set ready unless at least two dice (d6-d12) are included in the dice pool": "Cannot set ready unless at least two dice (d6-d12) are included in the dice pool", + "All players must be ready before contest can be rolled": "All players must be ready before contest can be rolled", + "link1": "https://www.reddit.com/user/gareththegeek/", + "link2": "https://twitter.com/sporgory", + "link3": "https://discord.gg/2kWxhJywGq", + "link4": "https://github.com/gareththegeek/rollagon", + "link5": "https://agon-rpg.com", + "link6": "", + "link7": "", + "link8": "", + "link9": "", + "link10": "" +} diff --git a/client/public/themes/agon/images.json b/client/public/themes/agon/images.json new file mode 100644 index 0000000..d93464b --- /dev/null +++ b/client/public/themes/agon/images.json @@ -0,0 +1,4 @@ +{ + "logoSmall": "logo-sm.svg", + "logoLarge": "logo-lg.png" +} \ No newline at end of file diff --git a/client/public/images/agon-big.png b/client/public/themes/agon/logo-lg.png similarity index 100% rename from client/public/images/agon-big.png rename to client/public/themes/agon/logo-lg.png diff --git a/client/public/images/agon.svg b/client/public/themes/agon/logo-sm.svg similarity index 99% rename from client/public/images/agon.svg rename to client/public/themes/agon/logo-sm.svg index 58d4039..949ea3a 100644 --- a/client/public/images/agon.svg +++ b/client/public/themes/agon/logo-sm.svg @@ -1,3 +1,3 @@ - - - + + + diff --git a/client/public/themes/agon/theme.css b/client/public/themes/agon/theme.css new file mode 100644 index 0000000..49569ec --- /dev/null +++ b/client/public/themes/agon/theme.css @@ -0,0 +1,44 @@ +.theme { + --colour-text: #001819; + --colour-accent: #004c48; + --colour-background: #f5f7f7; + --colour-button: #cad8dc; + --colour-button-hover: #dbe5e5; + --colour-heading-background: #cad8dc; + --colour-disabled: #8eb2b2; + --colour-button-select: #cad8dc; + + --font-body: calluna; + --font-heading: trajan-pro-3, serif; + + --font-size: 20px; + --line-height: 30px; + + --font-size-xs: 0.8rem; + --font-size-sm: 0.9rem; + --font-size-base: 1rem; + --font-size-xl: 1.6rem; + --font-size-2xl: 2rem; + --font-size-4xl: 3.2rem; + + --letter-spacing-tight: -0.02em; + --letter-spacing-normal: 0em; + + --line-height-none: 1; + --line-height-tight: 1.2; + --line-height-normal: 1.5; + + --h1-border-colour: var(--colour-text); + --h2-border-colour: var(--colour-text); + --h3-border-colour: var(--colour-accent); + --h4-border-colour: var(--colour-accent); + + --h1-font-weight: 600; + --h1-font-weight-md: 600; + --h2-font-weight: 600; + --h2-font-weight-md: 400; + --h3-font-weight: 700; + --h3-font-weight-md: 600; + --h4-font-weight: 700; + --h4-font-weight-md: 700; +} diff --git a/client/public/themes/dmi/en.json b/client/public/themes/dmi/en.json new file mode 100644 index 0000000..857cd16 --- /dev/null +++ b/client/public/themes/dmi/en.json @@ -0,0 +1,107 @@ +{ + "Agon Dice Roller": "Deathmatch Island Dice Roller", + "Agon Roller": "Deathmatch Island Roller", + "About this App": "About this App", + "Remove": "Remove", + "Add": "Add", + "Tie Breaker": "Tie Breaker", + "This is the tie breaking roll": "This is the tie breaking roll", + "This dice contributed to the score": "This dice contributed to the score", + "This dice was dropped and did not contribute to the final score": "This dice was dropped and did not contribute to the final score", + "This was the result of your roll": "This was the result of your roll", + "This is the target number to beat in this contest": "This is the target number to beat in this contest", + "There is a problem with your connection, attempting to reconnect. Please wait...": "There is a problem with your connection, attempting to reconnect. Please wait...", + "Copied to clipboard!": "Copied to clipboard!", + "Copy Invite Link": "Copy Invite Link", + "Name Dice": "Name Dice", + "Select Name Dice": "Select Name Dice", + "Cancel Tie Break Roll": "Cancel Tie Break Roll", + "Cancel": "Cancel", + "Rolling Name Die...": "Rolling Name Die...", + "Roll Again": "Roll Again", + "Break Tie": "Break Tie", + "Enter note here": "Enter note here", + "Enter note here, in #markdown if you like...": "Enter note here, in #markdown if you like...", + "Add Note": "Add Note", + "Notes": "Notes", + "There are currently no notes": "There are currently no notes", + "Delete note": "Delete note", + "Save": "Save", + "Join as": "Join as", + "Strife Player": "Production Player", + "Sacred": "Risky", + "Perilous": "Dangerous", + "Mythic": "Restricted", + "Epic": "Exhausting", + "ContestIsEpic": "The Contest is Exhausting - costs 1 Fatigue to enter.", + "ContestIsMythic": "The Contest is Restricted - you may not use any Acquisitions except for REDACTED Acquisitions", + "ContestIsPerilous": "The Contest is Dangerous - mark 1 Injury when you fail (instead of 1 Fatigue).", + "ContestIsSacred": "The Contest is Risky - for each PC who fails, the team receives one less Acquisition for any rewards in that node (if any).", + "Harms": "Harms", + "AboutCreatedBy": "This app was created by @gareth and @sporgory with the help of the AGON fan Discord community.", + "AboutFeedback": "If you'd like to give feedback or get involved, check out the GitHub repo.", + "AboutAgon": "This is a dice-rolling app for Deathmatch Island. Deathmatch Island is a fast-paced roleplaying game about a deadly game show set on a chain of mysterious islands. Learn more about it at deathmatchis.land", + "AboutRules": "At least one person playing should be familiar with the Deathmatch Island rulebook.", + "AboutApp": "This app is a lightweight dicerolling app, focused on resolving and narrating Deathmatch Island contests. It is not a campaign or character tracker - you'll still need to manage that on your own.", + "Close Contest": "Close Contest", + "Contest Results": "Contest Results", + "Join the Contest": "Join the Contest", + "A record of how the Heroes fared in the Contest. Heroes should narrate their results in ascending order - the Strife player should respond with the Opponents reactions.": "A record of how the Competitors fared in the Contest. Competitor Players should narrate their results in ascending order - the Production Player should respond with the opponents' reactions.", + "This is the challenge that the Heroes must strive to overcome.": "This is the challenge that the competitors must strive to overcome.", + "The Challenge": "The Challenge", + "Prevails": "Succeeds", + "Suffers": "Fails", + "New Contest": "New Contest", + "Between Contests": "Between Contests", + "You are a glorious Hero - strive to take on worthy opponents. If a challenge isn't glorious, you can accomplish it without need for a Contest.": "Each node on the map that the competitors travel to will involve one contest, usually rolling against a different team of competitors. The contest resolves which side is succesful, and they get the rewards on offer.", + "Waiting for the next Glorious Contest": "Waiting for the next contest", + "Create a Contest": "Create a Contest", + "Create a contest whenever the heroes come into conflict with a worthy opponent.": "Create a contest when the competitors arrive at a new node, or to resolve any other obstacle that might come up, from a raging storm to the frantic action of the battle royale.", + "Set the Strife Level": "Set the Danger Level", + "Adjust the strife level for permanent or temporary advantages or disadvantages.": "Adjust the Danger Level for permanent or temporary advantages or disadvantages.", + "Build the Opponent's Dice Pool": "Build the Opponent's Dice Pool", + "Add dice for the Opponent's Name, Epithets, and any Bonus dice.": "Add dice for the opponent's traits, and any bonus dice.", + "Choose Harm Tags": "Choose Harm Tags", + "ChooseSacredHarm": "Risky - for each PC who fails, the team receives one less Acquisition for any rewards in that node (if any).", + "ChoosePerilousHarm": "Dangerous - mark 1 Injury when you fail (instead of 1 Fatigue).", + "ChooseMythicHarm": "Restricted - you may not use any Acquisitions except for REDACTED Acquisitions", + "ChooseEpicHarm": "Exhausting - costs 1 Fatigue to enter.", + "Roll the Contest": "Roll the Contest", + "Each Hero Player indicates their participation in the contest by speaking their name. Start with the leader and go around the table. On your turn, recite your hero's name and add dice as you go: If your Epithet applies to the contest, add that die. Add your Name die and the Domain die for the contest when you say your Name and lineage.": "Each player dictates an announcement of their competitor's entrance into the contest. This could take the form of a written chyron or lower-third caption (as it would appear on screen over footage of their competitor), or it could be the bombastic voice-over of the Host.A nnounce your competitor's identity and grab dice as you go. If your Occupation means you have skills or experiences relevant to the contest, grab that die. Then add your Name die and the Capability die for the contest when you say your Name.", + "Join the Contest!": "Join the Contest!", + "Ready": "Ready", + "Leave": "Leave", + "Who Will Join the Contest?": "Who Will Join the Contest?", + "Heroes join the contest by building their dice pool. Make sure everyone is ready before rolling the final results!": "Competitors join the contest by building their dice pool. Make sure everyone is ready before rolling the final results!", + "No one has joined": "No one has joined", + "Roll Results": "Roll Results", + "Strife Level": "Danger Level", + "Are you Strife or a Hero?": "Are you Production or a Competitor?", + "Every game of AGON needs one Strife player and at least two Hero Players.": "Every game of Deathmatch Island needs one Production Player and at least two Competitor Players.", + "The Strife player challenges the Heroes with worthy Contests.": "The Production Player presents contests that the competitors contend with. ", + "Join as Strife Player": "Join as Production Player", + "Hero Player": "Competitor Player", + "Hero players contend against the Contests presented by the Strife player.": "Competitor Players contend against the contests presented by the Production Player.", + "Enter your Hero's Name": "Enter your Competitor's Name", + "Join as new Hero": "Join as new Competitor", + "AboutAgonDiceRoller": "This is a dice-rolling app for Deathmatch Island. Deathmatch Island is a fast-paced roleplaying game about a deadly game show set on a chain of mysterious islands. Learn more about it at deathmatchis.land", + "Start a New Session": "Start a New Session", + "Start a new session. You'll be able to invite other players with a unique URL.": "Start a new session. You'll be able to invite other players with a unique URL.", + "Start New Game": "Start New Game", + "Have an Invite Link?": "Have an Invite Link?", + "Paste it below or in your browser and click Join Game.": "Paste it below or in your browser and click Join Game.", + "Existing game id": "Existing game id", + "Join Game": "Join Game", + "Cannot set ready unless at least two dice (d6-d12) are included in the dice pool": "Cannot set ready unless at least two dice (d6-d12) are included in the dice pool", + "All players must be ready before contest can be rolled": "All players must be ready before contest can be rolled", + "link1": "https://www.reddit.com/user/gareththegeek/", + "link2": "https://twitter.com/sporgory", + "link3": "https://discord.gg/2kWxhJywGq", + "link4": "https://github.com/gareththegeek/rollagon", + "link5": "https://deathmatchis.land", + "link6": "", + "link7": "", + "link8": "", + "link9": "", + "link10": "" +} \ No newline at end of file diff --git a/client/public/themes/dmi/face.png b/client/public/themes/dmi/face.png new file mode 100644 index 0000000..fda21ea Binary files /dev/null and b/client/public/themes/dmi/face.png differ diff --git a/client/public/themes/dmi/images.json b/client/public/themes/dmi/images.json new file mode 100644 index 0000000..021c685 --- /dev/null +++ b/client/public/themes/dmi/images.json @@ -0,0 +1,4 @@ +{ + "logoSmall": "face.png", + "logoLarge": "logo-lg.png" +} \ No newline at end of file diff --git a/client/public/themes/dmi/logo-lg.png b/client/public/themes/dmi/logo-lg.png new file mode 100644 index 0000000..6d6511e Binary files /dev/null and b/client/public/themes/dmi/logo-lg.png differ diff --git a/client/public/themes/dmi/theme.css b/client/public/themes/dmi/theme.css new file mode 100644 index 0000000..74fcdb2 --- /dev/null +++ b/client/public/themes/dmi/theme.css @@ -0,0 +1,44 @@ +.theme { + --colour-text: #001919; + --colour-accent: #f64816 ; + --colour-background: #f5f7f7; + --colour-button: #f5f7f7; + --colour-button-hover: #f64816; + --colour-heading-background: #001919; + --colour-disabled: #FF997B; + --colour-button-select: #f64816; + + --font-body: 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'; + --font-heading: 'Helvetica Neue', 'Helvetica', 'Arial', 'sans-serif'; + + --font-size: 20px; + --line-height: 30px; + + --font-size-xs: 0.8rem; + --font-size-sm: 0.9rem; + --font-size-base: 1rem; + --font-size-xl: 1.6rem; + --font-size-2xl: 2rem; + --font-size-4xl: 3.2rem; + + --letter-spacing-tight: -0.02em; + --letter-spacing-normal: 0em; + + --line-height-none: 1; + --line-height-tight: 1.2; + --line-height-normal: 1.5; + + --h1-border-colour: var(--colour-text); + --h2-border-colour: var(--colour-text); + --h3-border-colour: var(--colour-accent); + --h4-border-colour: var(--colour-accent); + + --h1-font-weight: 600; + --h1-font-weight-md: 600; + --h2-font-weight: 600; + --h2-font-weight-md: 600; + --h3-font-weight: 700; + --h3-font-weight-md: 500; + --h4-font-weight: 700; + --h4-font-weight-md: 700; +} \ No newline at end of file diff --git a/client/public/themes/index.json b/client/public/themes/index.json new file mode 100644 index 0000000..708e5e8 --- /dev/null +++ b/client/public/themes/index.json @@ -0,0 +1,6 @@ +{ + "themes": [ + { "name": "Agon", "folder": "agon", "default": true }, + { "name": "Deathmatch Island", "folder": "dmi" } + ] +} diff --git a/client/src/App.tsx b/client/src/App.tsx index 0f657ee..38a3326 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect, useReducer } from 'react' import { Route, Routes } from 'react-router' import { BrowserRouter } from 'react-router-dom' import { Connection } from './components/header/Connection' @@ -11,14 +11,20 @@ import LanguageDetector from 'i18next-browser-languagedetector' import { initReactI18next } from 'react-i18next' import * as de from './translations/de.json' import * as en from './translations/en.json' +import './theme.css' +import { useSelector } from 'react-redux' +import { selectCurrentTheme } from './slices/themeSlice' i18n.use(LanguageDetector) .use(initReactI18next) .init({ resources: { - de, - en + de: { default: de }, + en: { default: en } }, + ns: ['default'], + defaultNS: 'default', + fallbackNS: 'default', detection: { order: ['querystring', 'navigator'], lookupQuerystring: 'lng' @@ -31,6 +37,10 @@ i18n.use(LanguageDetector) }) function App() { + const theme = useSelector(selectCurrentTheme) + const [, forceUpdate] = useReducer((x) => x + 1, 0) + useEffect(forceUpdate, [forceUpdate, theme]) + return (
diff --git a/client/src/api/players.ts b/client/src/api/players.ts index a75895e..3c3a6bc 100644 --- a/client/src/api/players.ts +++ b/client/src/api/players.ts @@ -27,7 +27,8 @@ const getAbsolute = (function () { a.href = url return a.href - }; -})(); + } +})() -export const generateInviteLink = (gameId: string): string => getAbsolute(`/join/${gameId}`) +export const generateInviteLink = (gameId: string, theme?: string): string => + getAbsolute(`/join/${gameId}${!!theme ? `?theme=${theme}` : ''}`) diff --git a/client/src/app/store.ts b/client/src/app/store.ts index 2e31e24..ff9522b 100644 --- a/client/src/app/store.ts +++ b/client/src/app/store.ts @@ -6,6 +6,7 @@ import noteReducer from '../slices/notesSlice' import playerReducer from '../slices/playerSlice' import statusReducer from '../slices/statusSlice' import strifeReducer from '../slices/strifeSlice' +import themeReducer from '../slices/themeSlice' export const store = configureStore({ reducer: { @@ -15,7 +16,8 @@ export const store = configureStore({ note: noteReducer, player: playerReducer, status: statusReducer, - strife: strifeReducer + strife: strifeReducer, + theme: themeReducer } }) diff --git a/client/src/app/useCustomNavigate.ts b/client/src/app/useCustomNavigate.ts new file mode 100644 index 0000000..ab6ed6d --- /dev/null +++ b/client/src/app/useCustomNavigate.ts @@ -0,0 +1,13 @@ +import { useSelector } from 'react-redux' +import { NavigateFunction, NavigateOptions, To, useNavigate as useWrappedNavigate } from 'react-router' +import { selectCurrentTheme } from '../slices/themeSlice' + +export const useNavigate = (): NavigateFunction => { + const wrappedNavigate = useWrappedNavigate() + const theme = useSelector(selectCurrentTheme) + + return ((to: To, options?: NavigateOptions) => { + const themedTo = `${to}${!!theme ? `?theme=${theme}` : ''}` + wrappedNavigate(themedTo, options) + }) as NavigateFunction +} diff --git a/client/src/app/useCustomTranslation.tsx b/client/src/app/useCustomTranslation.tsx new file mode 100644 index 0000000..0d743a0 --- /dev/null +++ b/client/src/app/useCustomTranslation.tsx @@ -0,0 +1,21 @@ +import { Trans as WrappedTrans, useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' +import { selectCurrentTheme } from '../slices/themeSlice' + +export const useCustomTranslation = () => { + const { t: wrappedT } = useTranslation() + const theme = useSelector(selectCurrentTheme) ?? 'default' + + const t = (key: string, options?: any) => { + return wrappedT(key, { ns: theme, ...options }) as unknown as string + } + + return { t } +} + +export const Trans = ({ i18nKey, ...props }: any) => { + const theme = useSelector(selectCurrentTheme) ?? 'default' + + const namespacedKey = `${theme}:${i18nKey}` + return +} diff --git a/client/src/app/useTheme.ts b/client/src/app/useTheme.ts new file mode 100644 index 0000000..972bb23 --- /dev/null +++ b/client/src/app/useTheme.ts @@ -0,0 +1,14 @@ +import { useSelector } from 'react-redux' +import { useLocation } from 'react-router' +import { selectCurrentTheme, setCurrentThemeAsync } from '../slices/themeSlice' +import { useAppDispatch } from './hooks' + +export const useTheme = () => { + const current = useSelector(selectCurrentTheme) + const dispatch = useAppDispatch() + const search = useLocation().search + const theme = new URLSearchParams(search).get('theme') ?? undefined + if (!!theme && theme !== current) { + dispatch(setCurrentThemeAsync(theme)) + } +} diff --git a/client/src/components/Divider.tsx b/client/src/components/Divider.tsx index fc835ad..4147c35 100644 --- a/client/src/components/Divider.tsx +++ b/client/src/components/Divider.tsx @@ -1,9 +1,12 @@ import React, { FC } from 'react' export interface DividerProps { - fullWidth?: boolean | undefined + fullWidth?: boolean + down?: boolean } -export const Divider: FC = ({ fullWidth }) => ( -
+export const Divider: FC = ({ fullWidth, down }) => ( +
+
+
) diff --git a/client/src/components/LinkTrans.tsx b/client/src/components/LinkTrans.tsx new file mode 100644 index 0000000..880e4b3 --- /dev/null +++ b/client/src/components/LinkTrans.tsx @@ -0,0 +1,23 @@ +import { Trans, useCustomTranslation } from '../app/useCustomTranslation' +import { A } from './A' + +export const LinkTrans = (props: any) => { + const { t } = useCustomTranslation() + return ( + , + link2: , + link3: , + link4: , + link5: , + link6: , + link7: , + link8: , + link9: , + link10: + }} + /> + ) +} diff --git a/client/src/components/SmallButton.tsx b/client/src/components/SmallButton.tsx index d1ffcc6..eec76df 100644 --- a/client/src/components/SmallButton.tsx +++ b/client/src/components/SmallButton.tsx @@ -1,14 +1,13 @@ import React from 'react' export const SmallButton = ({ children, extraSmall, className, selected, disabled, ...rest }: any) => { - const bg = selected ? 'bg-grey-300' : '' + const bg = selected ? 'bg-button-selected' : '' const border = selected ? '' : disabled ? 'border-grey-500 text-grey-500' : '' const hover = selected ? 'hover:bg-grey-200' : 'hover:bg-grey-300' - //const text = selected ? 'text-white' : '' return (