From 32363c9d7b381386c08f32c8203c03627796adbc Mon Sep 17 00:00:00 2001 From: Richard Wong Date: Mon, 1 Jul 2024 19:21:15 +0900 Subject: [PATCH] Feat: implement i18n support --- package.json | 4 + pnpm-lock.yaml | 109 + public/locales/en/translation.json | 22 + public/locales/kn/translation.json | 22 + src/VerseSampler.jsx | 80 +- src/VerseValidator.jsx | 12 +- src/assets/droplet.svg | 4 + src/assets/verse.json | 4509 +++++++++++++++++++--------- src/i18n.js | 27 + src/main.jsx | 3 + 10 files changed, 3275 insertions(+), 1517 deletions(-) create mode 100644 public/locales/en/translation.json create mode 100644 public/locales/kn/translation.json create mode 100644 src/assets/droplet.svg create mode 100644 src/i18n.js diff --git a/package.json b/package.json index 9d31659..bee5a93 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,13 @@ }, "dependencies": { "@fontsource/montserrat": "^4.5.14", + "i18next": "^23.11.5", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^2.5.2", "react": "^18.2.0", "react-checkbox-tree": "^1.8.0", "react-dom": "^18.2.0", + "react-i18next": "^14.1.2", "react-string-diff": "^0.2.0", "underscore": "^1.13.6" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c39e44..14d770b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ dependencies: '@fontsource/montserrat': specifier: ^4.5.14 version: 4.5.14 + i18next: + specifier: ^23.11.5 + version: 23.11.5 + i18next-browser-languagedetector: + specifier: ^8.0.0 + version: 8.0.0 + i18next-http-backend: + specifier: ^2.5.2 + version: 2.5.2 react: specifier: ^18.2.0 version: 18.2.0 @@ -17,6 +26,9 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-i18next: + specifier: ^14.1.2 + version: 14.1.2(i18next@23.11.5)(react-dom@18.2.0)(react@18.2.0) react-string-diff: specifier: ^0.2.0 version: 0.2.0(react@18.2.0) @@ -244,6 +256,13 @@ packages: '@babel/helper-plugin-utils': 7.21.5 dev: true + /@babel/runtime@7.24.7: + resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.1 + dev: false + /@babel/template@7.20.7: resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} @@ -808,6 +827,14 @@ packages: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: true + /cross-fetch@4.0.0: + resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==} + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1306,6 +1333,32 @@ packages: function-bind: 1.1.1 dev: true + /html-parse-stringify@3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + dependencies: + void-elements: 3.1.0 + dev: false + + /i18next-browser-languagedetector@8.0.0: + resolution: {integrity: sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==} + dependencies: + '@babel/runtime': 7.24.7 + dev: false + + /i18next-http-backend@2.5.2: + resolution: {integrity: sha512-+K8HbDfrvc1/2X8jpb7RLhI9ZxBDpx3xogYkQwGKlWAUXLSEGXzgdt3EcUjLlBCdMwdQY+K+EUF6oh8oB6rwHw==} + dependencies: + cross-fetch: 4.0.0 + transitivePeerDependencies: + - encoding + dev: false + + /i18next@23.11.5: + resolution: {integrity: sha512-41pvpVbW9rhZPk5xjCX2TPJi2861LEig/YRhUkY+1FQ2IQPS0bKUDYnEqY8XPPbB48h1uIwLnP9iiEfuSl20CA==} + dependencies: + '@babel/runtime': 7.24.7 + dev: false + /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} engines: {node: '>= 4'} @@ -1558,6 +1611,18 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + /node-releases@2.0.10: resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} dev: true @@ -1733,6 +1798,26 @@ packages: scheduler: 0.23.0 dev: false + /react-i18next@14.1.2(i18next@23.11.5)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==} + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@babel/runtime': 7.24.7 + html-parse-stringify: 3.0.1 + i18next: 23.11.5 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -1758,6 +1843,10 @@ packages: loose-envify: 1.4.0 dev: false + /regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + dev: false + /regexp.prototype.flags@1.5.0: resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} engines: {node: '>= 0.4'} @@ -1929,6 +2018,10 @@ packages: engines: {node: '>=4'} dev: true + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2011,6 +2104,22 @@ packages: fsevents: 2.3.2 dev: true + /void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + dev: false + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json new file mode 100644 index 0000000..276b8f6 --- /dev/null +++ b/public/locales/en/translation.json @@ -0,0 +1,22 @@ +{ + "main": { + "title": "Scripture Memory Tester", + "pick_lang": "Pick Language", + "pick_num_verses": "Pick Number of Verses:", + "num_verses_tested": "Number of Verses Tested:", + "note_num_verses": "(It will only give you as many verses as there are in selected packs)", + "set_shuffle": "Set Shuffle:", + "note_set_shuffle": "(Otherwise cards will appear in sequential order)", + "hide_reference": "Set Hide Reference:", + "note_hide_reference": "(If you also want to test the verse reference)", + "pick_pack": "Pick Your Packs:", + "shuffle_card": "Shuffle Cards:", + "verses": "Verses:" + }, + "verse_validator": { + "input_reference": "Input Verse Reference:", + "input_chapter_title": "Input Chapter Title:", + "input_title": "Input Title:", + "input_verse": "Input Verse:" + } +} \ No newline at end of file diff --git a/public/locales/kn/translation.json b/public/locales/kn/translation.json new file mode 100644 index 0000000..d29cc39 --- /dev/null +++ b/public/locales/kn/translation.json @@ -0,0 +1,22 @@ +{ + "main": { + "title": "Scripture Memory Tester (kr)", + "pick_lang": "Pick Language (kr)", + "pick_num_verses": "Pick Number of Verses: (kr)", + "num_verses_tested": "Number of Verses Tested:", + "note_num_verses": "(It will only give you as many verses as there are in selected packs)", + "set_shuffle": "Set Shuffle:", + "note_set_shuffle": "(Otherwise cards will appear in sequential order)", + "hide_reference": "Set Hide Reference:", + "note_hide_reference": "(If you also want to test the verse reference)", + "pick_pack": "Pick Your Packs:", + "shuffle_card": "Shuffle Cards:", + "verses": "Verses:" + }, + "verse_validator": { + "input_reference": "Input Verse Reference: (KR)", + "input_chapter_title": "Input Chapter Title: (KR)", + "input_title": "Input Title: (KR)", + "input_verse": "Input Verse: (KR)" + } +} \ No newline at end of file diff --git a/src/VerseSampler.jsx b/src/VerseSampler.jsx index 53c3d32..681cf13 100644 --- a/src/VerseSampler.jsx +++ b/src/VerseSampler.jsx @@ -3,15 +3,18 @@ Implemented features: - read keys from json - create checklist from keys */ -import VerseData from "./assets/verse.json" +import fullVerseData from "./assets/verse.json" // the actual verse json data file import { useState } from "react"; import CheckboxTree from 'react-checkbox-tree'; import 'react-checkbox-tree/lib/react-checkbox-tree.css'; import _ from 'underscore'; import './VerseSampler.css' import VerseValidator from "./VerseValidator"; +import { useTranslation } from 'react-i18next'; +import logo from './assets/droplet.svg'; +import { Suspense } from "react"; -const GenerateTestList = ({ packs, testCount, toShuffle, toHideReference}) => { +const GenerateTestList = ({ VerseData, packs, testCount, toShuffle, toHideReference}) => { let testList = packs.reduce( // grab all elements included checked in "packs" (accumulator, currentValue) => accumulator.concat(VerseData[currentValue]), @@ -110,10 +113,41 @@ const CheckboxWidget = ({checked, expanded, setChecked, setExpanded}) => { ); } +// loadCustomData +const loadCustomData = (language) => { + let data; + console.log(language) + switch (language) { + case 'kn': + data = fullVerseData.kn; + break; + case 'en': + default: + data = fullVerseData.en; + break; + } + return data; +}; + + + + +function Page() { + // setup i18 for function + const { t, i18n } = useTranslation(); + + // load VerseData json data file + const [VerseData, setVerseData] = useState(loadCustomData(i18n.language)); + + // function hook to change language + // updates both i18n language and also the VerseData state variable + const changeLanguage = (lng) => { + i18n.changeLanguage(lng); + setVerseData(loadCustomData(i18n.language)); + }; -function App() { // create checklist array for pack selection const packList = Object.keys(VerseData); // return a list of packObj's @@ -178,10 +212,13 @@ function App() { return (
-

Scripture Memory Tester

-

Pick Number of Verses:

+

{t('main.title')}

+

{t('main.pick_lang')}

+ + +

{t('main.pick_num_verses')}

-

(It will only give you as many verses as there are in selected packs)

+

{t('main.note_num_verses')}

Set Shuffle: @@ -202,26 +239,26 @@ function App() { onChange={handleShuffleCheckboxChange} />

-

(Otherwise cards will appear in sequential order)

+

{t('main.note_set_shuffle')}

{!toShuffle ? <>

- Set Hide Reference: + {t('main.hide_reference')}

-

(If you also want to test the verse reference)

+

{t('main.note_hide_reference')}

:

}
-

Pick Your Packs:

+

{t('main.pick_pack')}

{toShuffle ? <> -

Shuffle Cards:

+

{t('main.shuffle_card')}

:

}
-

Verses:

+

{t('main.verses')}

( +
+ logo +
loading...
+
+); + +export default function App() { + return ( + }> + + + ); +} diff --git a/src/VerseValidator.jsx b/src/VerseValidator.jsx index 9170ffb..433787e 100644 --- a/src/VerseValidator.jsx +++ b/src/VerseValidator.jsx @@ -1,11 +1,15 @@ import { useState } from "react"; import "./VerseValidator.css"; import { StringDiff } from "react-string-diff"; +import { useTranslation } from 'react-i18next'; // function to render and handle logic of each of the cells const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse } , toHideReference}) => { // useful use of destructuring here + // setup i18 for function + const { t } = useTranslation(); + const [inputReference, setReference] = useState('') const [referenceBool, setReferenceBool] = useState(false) const [inputChapterTitle, setChapterTitle] = useState('') @@ -117,7 +121,7 @@ const VerseValidator = ({ element: { pack, title, chapterTitle, reference, verse {toHideReference ? (