diff --git a/package-lock.json b/package-lock.json index c9201fd3..3c7c4e88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,11 @@ "license": "MIT", "dependencies": { "@etchteam/diamond-ui": "^1.0.3", + "i18next": "^23.7.18", + "i18next-http-backend": "^2.4.2", "lit": "^3.1.1", "preact": "^10.19.3", + "react-i18next": "^14.0.1", "react-router-dom": "^6.21.3" }, "devDependencies": { @@ -1978,7 +1981,6 @@ "version": "7.23.8", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.8.tgz", "integrity": "sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==", - "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -9981,6 +9983,14 @@ "typescript": ">=4" } }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -13443,6 +13453,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -13508,6 +13526,36 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "23.7.18", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.7.18.tgz", + "integrity": "sha512-b9N2KjRCYQNlUvE1Kc83g8knyUkL5NiZQOp9BsTR/v/LXk6Fzz+doOzTg2/826XK28mCgBkYLNAtixjE58qpCw==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.4.2.tgz", + "integrity": "sha512-wKrgGcaFQ4EPjfzBTjzMU0rbFTYpa0S5gv9N/d8WBmWS64+IgJb7cHddMvV+tUkse7vUfco3eVs2lB+nJhPo3w==", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -15812,7 +15860,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -17175,6 +17222,27 @@ "react": "^18.2.0" } }, + "node_modules/react-i18next": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.0.1.tgz", + "integrity": "sha512-TMV8hFismBmpMdIehoFHin/okfvgjFhp723RYgIqB4XyhDobVMyukyM3Z8wtTRmajyFMZrBl/OaaXF2P6WjUAw==", + "dependencies": { + "@babel/runtime": "^7.22.5", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -17439,8 +17507,7 @@ "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "dev": true + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -19185,8 +19252,7 @@ "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/trim-newlines": { "version": "4.1.1", @@ -19773,6 +19839,14 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/vscode-json-languageservice": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-4.2.1.tgz", @@ -19844,8 +19918,7 @@ "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack-sources": { "version": "3.2.3", @@ -19866,7 +19939,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" diff --git a/package.json b/package.json index a0b6dd40..402dd513 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,11 @@ }, "dependencies": { "@etchteam/diamond-ui": "^1.0.3", + "i18next": "^23.7.18", + "i18next-http-backend": "^2.4.2", "lit": "^3.1.1", "preact": "^10.19.3", + "react-i18next": "^14.0.1", "react-router-dom": "^6.21.3" }, "devDependencies": { diff --git a/public/translations/cy.json b/public/translations/cy.json new file mode 100644 index 00000000..49954406 --- /dev/null +++ b/public/translations/cy.json @@ -0,0 +1,5 @@ +{ + "start": { + "title": "Dod o hyd i leoedd i ailgylchu." + } +} diff --git a/public/translations/en.json b/public/translations/en.json new file mode 100644 index 00000000..f1c48427 --- /dev/null +++ b/public/translations/en.json @@ -0,0 +1,5 @@ +{ + "start": { + "title": "Find places to recycle." + } +} diff --git a/src/index.tsx b/src/index.tsx index 7209f488..bf7bc49f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,7 @@ import { LitElement, css } from 'lit'; import { customElement } from 'lit/decorators.js'; import { render as preactRender } from 'preact'; -import Entrypoint from './app/Entrypoint'; +import Entrypoint from './pages/Entrypoint'; import { diamondUi } from './styles/diamond-ui'; import { variables } from './styles/variables'; diff --git a/src/lib/i18n.ts b/src/lib/i18n.ts new file mode 100644 index 00000000..c9771c96 --- /dev/null +++ b/src/lib/i18n.ts @@ -0,0 +1,24 @@ +import i18n from 'i18next'; +import HttpBackend, { HttpBackendOptions } from 'i18next-http-backend'; +import { initReactI18next } from 'react-i18next'; + +i18n + // load translation using http -> see /public/translations + // https://github.com/i18next/i18next-http-backend + .use(HttpBackend) + // pass the i18n instance to react-i18next. + .use(initReactI18next) + // init i18next + // for all options read: https://www.i18next.com/overview/configuration-options + .init({ + fallbackLng: 'en', + debug: true, + backend: { + loadPath: '/translations/{{lng}}.json', + }, + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, + }); + +export default i18n; diff --git a/src/app/404.tsx b/src/pages/404.tsx similarity index 100% rename from src/app/404.tsx rename to src/pages/404.tsx diff --git a/src/app/Entrypoint.tsx b/src/pages/Entrypoint.tsx similarity index 66% rename from src/app/Entrypoint.tsx rename to src/pages/Entrypoint.tsx index aae2011e..da778db4 100644 --- a/src/app/Entrypoint.tsx +++ b/src/pages/Entrypoint.tsx @@ -1,3 +1,4 @@ +import { Suspense } from 'preact/compat'; import { createMemoryRouter, createRoutesFromElements, @@ -5,6 +6,8 @@ import { RouterProvider, } from 'react-router-dom'; +import '../lib/i18n'; + import NotFound from './404.js'; import StartPage from './Start.js'; @@ -19,7 +22,12 @@ const router = createMemoryRouter( * - Load up the router * - Setup the start page routes * - Lazily register sub routes + * - Initialise i18n (using suspense to wait for them to load in) */ export default function Entrypoint() { - return ; + return ( + loading...}> + + + ); } diff --git a/src/app/Start.tsx b/src/pages/Start.tsx similarity index 85% rename from src/app/Start.tsx rename to src/pages/Start.tsx index 88e71974..514f995c 100644 --- a/src/app/Start.tsx +++ b/src/pages/Start.tsx @@ -1,11 +1,15 @@ +import { useTranslation } from 'react-i18next'; + export default function StartPage() { + const { t } = useTranslation(); + return (
Recycling Locator
-

Find places to recycle

+

{t('start.title')}