From a49531193cff81faddf4674e742de3f8e055a47e Mon Sep 17 00:00:00 2001 From: Jing Hua Date: Thu, 12 Dec 2024 12:46:35 +0000 Subject: [PATCH] add i18n scalfold --- package-lock.json | 135 ++++++++++++++++++++++++++++- package.json | 4 + public/locales/en/translation.json | 10 +++ public/locales/zh/translation.json | 10 +++ src/Components/App.tsx | 18 +++- src/Components/Main.tsx | 11 ++- src/i18n.ts | 22 +++++ src/index.tsx | 2 + src/options.tsx | 12 ++- 9 files changed, 213 insertions(+), 11 deletions(-) create mode 100644 public/locales/en/translation.json create mode 100644 public/locales/zh/translation.json create mode 100644 src/i18n.ts diff --git a/package-lock.json b/package-lock.json index cdf1bcd..fc885f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,10 +18,14 @@ "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", "@monaco-editor/react": "^4.6.0", + "i18next": "^24.1.0", + "i18next-browser-languagedetector": "^8.0.2", + "i18next-http-backend": "^3.0.1", "monaco-editor": "^0.52.0", "purecss": "^3.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-i18next": "^15.1.4", "sweetalert2": "<=11.4.8", "sweetalert2-react-content": "^5.0.7", "tshet-uinh": "^0.15.0", @@ -2279,6 +2283,14 @@ "node": ">=10" } }, + "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", @@ -3742,6 +3754,60 @@ "react-is": "^16.7.0" } }, + "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/i18next": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.1.0.tgz", + "integrity": "sha512-suKlX82AlptkMUO5YRfaAeH4FQyyKvR66jNaubTMiyPPMx7INU6PXAiy3PGULc0q6K+t9nxmDf/TRj9KjAivmw==", + "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" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.2.tgz", + "integrity": "sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-3.0.1.tgz", + "integrity": "sha512-XT2lYSkbAtDE55c6m7CtKxxrsfuRQO3rUfHzj8ZyRtY9CkIX3aRGwXGTkUhpGWce+J8n7sfu3J0f2wTzo7Lw0A==", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -4558,6 +4624,25 @@ "dev": true, "license": "MIT" }, + "node_modules/node-fetch": { + "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==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-releases": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", @@ -5134,6 +5219,27 @@ "react": "^18.3.1" } }, + "node_modules/react-i18next": { + "version": "15.1.4", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.1.4.tgz", + "integrity": "sha512-2tai71gmehbvl9ZIqPMqlCCkm/cbeV1G4STpmM3C8Uzo6T2l8jDvZxEVSsQKt8blP9X34iRFP/k1ROqG2296MQ==", + "dependencies": { + "@babel/runtime": "^7.25.0", + "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", @@ -5718,6 +5824,11 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -5880,7 +5991,7 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6022,6 +6133,28 @@ } } }, + "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/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==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 3d39aad..a8d6c29 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,14 @@ "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", "@monaco-editor/react": "^4.6.0", + "i18next": "^24.1.0", + "i18next-browser-languagedetector": "^8.0.2", + "i18next-http-backend": "^3.0.1", "monaco-editor": "^0.52.0", "purecss": "^3.0.0", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-i18next": "^15.1.4", "sweetalert2": "<=11.4.8", "sweetalert2-react-content": "^5.0.7", "tshet-uinh": "^0.15.0", diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json new file mode 100644 index 0000000..a754b1b --- /dev/null +++ b/public/locales/en/translation.json @@ -0,0 +1,10 @@ +{ + "schemaCompareDifferent": "Found {{count}} different items.", + "phonologicalPosition": "Phonological Position", + "representativeCharacter": "Representative Character", + "schemaCompareSame": "The results of the program derivation are the same.", + "copyToClipboard": "Copy to clipboard", + "copySuccess": "Successfully copied to clipboard", + "copyFailed": "Unable to copy to clipboard", + "tshetUinhAutoderiver": "Tshet-uinh Autoderiver" +} diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json new file mode 100644 index 0000000..35b0ca8 --- /dev/null +++ b/public/locales/zh/translation.json @@ -0,0 +1,10 @@ +{ + "schemaCompareDifferent": "找到 {{count}} 個相異項目。", + "phonologicalPosition": "音韻地位", + "representativeCharacter": "代表字", + "schemaCompareSame": "方案推導結果相同。", + "copyToClipboard": "複製到剪貼簿", + "copySuccess": "成功複製到剪貼簿", + "copyFailed": "無法複製到剪貼簿", + "tshetUinhAutoderiver": "切韻音系自動推導器" +} diff --git a/src/Components/App.tsx b/src/Components/App.tsx index 4538d9b..24409f0 100644 --- a/src/Components/App.tsx +++ b/src/Components/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useRef } from "react"; +import { useCallback, useEffect, useRef } from "react"; import "purecss/build/pure.css"; // NOTE sweetalert2's ESM export does not setup styles properly, manually importing @@ -12,6 +12,9 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Main from "./Main"; import Swal from "../Classes/SwalReact"; import { codeFontFamily, noop } from "../consts"; +import { useTranslation } from "react-i18next"; + +import i18n from "../i18n"; injectGlobal` html, @@ -565,14 +568,25 @@ const FontPreload = styled.span` `; export default function App() { + const { t } = useTranslation(); const evaluateHandlerRef = useRef(noop); + + useEffect(() => { + document.documentElement.lang = i18n.language; + i18n.on("languageChanged", lng => { + document.documentElement.lang = lng; + }); + + document.title = t("tshetUinhAutoderiver"); + }, []); + return (