From 73c9625df11a53a74753db6c88e618ffdf93d33e Mon Sep 17 00:00:00 2001 From: James Lin Date: Thu, 11 Apr 2024 20:47:03 -0700 Subject: [PATCH] Add i18next framework to enable further contributions (#96) * prototype * Update with comments and type safety --- package-lock.json | 31 +++++++++++++++++++++++++++---- package.json | 1 + src/locales/en/menu.ts | 13 +++++++++++++ src/locales/it/menu.ts | 8 ++++++++ src/phases.ts | 13 +++++++------ src/plugins/i18n.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 src/locales/en/menu.ts create mode 100644 src/locales/it/menu.ts create mode 100644 src/plugins/i18n.ts diff --git a/package-lock.json b/package-lock.json index b21f00438f0..b93e2b2c6a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@material/material-color-utilities": "^0.2.7", "crypto-js": "^4.2.0", + "i18next": "^23.11.1", "json-stable-stringify": "^1.1.0", "phaser": "^3.70.0", "phaser3-rex-plugins": "^1.1.84" @@ -2750,9 +2751,9 @@ } }, "node_modules/i18next": { - "version": "22.5.1", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.1.tgz", - "integrity": "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==", + "version": "23.11.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.11.1.tgz", + "integrity": "sha512-mXw4A24BiPZKRsbb9ewgSvjYd6fxFCNwJyfK6nYfSTIAX2GkCWcb598m3DFkDZmqADatvuASrKo6qwORz3VwTQ==", "funding": [ { "type": "individual", @@ -2768,7 +2769,7 @@ } ], "dependencies": { - "@babel/runtime": "^7.20.6" + "@babel/runtime": "^7.23.2" } }, "node_modules/i18next-http-backend": { @@ -3819,6 +3820,28 @@ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==" }, + "node_modules/phaser3-rex-plugins/node_modules/i18next": { + "version": "22.5.1", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-22.5.1.tgz", + "integrity": "sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==", + "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.20.6" + } + }, "node_modules/phaser3spectorjs": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/phaser3spectorjs/-/phaser3spectorjs-0.0.8.tgz", diff --git a/package.json b/package.json index 9065bc17a55..fd189fd4d89 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "@material/material-color-utilities": "^0.2.7", "crypto-js": "^4.2.0", + "i18next": "^23.11.1", "json-stable-stringify": "^1.1.0", "phaser": "^3.70.0", "phaser3-rex-plugins": "^1.1.84" diff --git a/src/locales/en/menu.ts b/src/locales/en/menu.ts new file mode 100644 index 00000000000..1cdafac5d71 --- /dev/null +++ b/src/locales/en/menu.ts @@ -0,0 +1,13 @@ +/** + * The menu namespace holds most miscellaneous text that isn't directly part of the game's + * contents or directly related to Pokemon. This includes menu navigation, settings, + * account interactions, etc. + */ +export const menu = { + "cancel": "Cancel", + "continue": "Continue", + "dailyRun": "Daily Run (Beta)", + "loadGame": "Load Game", + "newGame": "New Game", + "selectGameMode": "Select a game mode." +} as const; \ No newline at end of file diff --git a/src/locales/it/menu.ts b/src/locales/it/menu.ts new file mode 100644 index 00000000000..6c6b6ba468c --- /dev/null +++ b/src/locales/it/menu.ts @@ -0,0 +1,8 @@ +export const menu = { + "cancel": "Annulla", + "continue": "Continua", + "newGame": "Nuova Partita", + "loadGame": "Carica Partita", + "dailyRun": "Corsa Giornaliera (Beta)", + "selectGameMode": "Seleziona una modalità di gioco." +} as const; \ No newline at end of file diff --git a/src/phases.ts b/src/phases.ts index 480c6c9a3f9..09d0de7f686 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -57,6 +57,7 @@ import { SaveSlotUiMode } from "./ui/save-slot-select-ui-handler"; import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run"; import { GameModes, gameModes } from "./game-mode"; import { getPokemonSpecies, speciesStarters } from "./data/pokemon-species"; +import i18next from './plugins/i18n'; export class LoginPhase extends Phase { private showText: boolean; @@ -173,12 +174,12 @@ export class TitlePhase extends Phase { const options: OptionSelectItem[] = []; if (loggedInUser.lastSessionSlot > -1) { options.push({ - label: 'Continue', + label: i18next.t('menu:continue'), handler: () => this.loadSaveSlot(this.lastSessionData ? -1 : loggedInUser.lastSessionSlot) }); } options.push({ - label: 'New Game', + label: i18next.t('menu:newGame'), handler: () => { const setModeAndEnd = (gameMode: GameModes) => { this.gameMode = gameMode; @@ -204,14 +205,14 @@ export class TitlePhase extends Phase { }); } options.push({ - label: 'Cancel', + label: i18next.t('menu:cancel'), handler: () => { this.scene.clearPhaseQueue(); this.scene.pushPhase(new TitlePhase(this.scene)); super.end(); } }); - this.scene.ui.showText('Select a game mode.', null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + this.scene.ui.showText(i18next.t("menu:selectGameMode"), null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); } else { this.gameMode = GameModes.CLASSIC; this.scene.ui.setMode(Mode.MESSAGE); @@ -221,7 +222,7 @@ export class TitlePhase extends Phase { } }, { - label: 'Load Game', + label: i18next.t('menu:loadGame'), handler: () => this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, (slotId: integer) => { if (slotId === -1) @@ -231,7 +232,7 @@ export class TitlePhase extends Phase { ) }, { - label: 'Daily Run (Beta)', + label: i18next.t('menu:dailyRun'), handler: () => this.initDailyRun(), keepOpen: true }); diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts new file mode 100644 index 00000000000..95ba54007e1 --- /dev/null +++ b/src/plugins/i18n.ts @@ -0,0 +1,42 @@ +import i18next from 'i18next'; +import { menu as enMenu } from '../locales/en/menu'; +import { menu as itMenu } from '../locales/it/menu'; + +const DEFAULT_LANGUAGE_OVERRIDE = ''; + +/** + * i18next is a localization library for maintaining and using translation resources. + * + * Q: How do I add a new language? + * A: To add a new language, create a new folder in the locales directory with the language code. + * Each language folder should contain a file for each namespace (ex. menu.ts) with the translations. + * + * Q: How do I add a new namespace? + * A: To add a new namespace, create a new file in each language folder with the translations. + * Then update the `resources` field in the init() call and the CustomTypeOptions interface. + */ + +i18next.init({ + lng: DEFAULT_LANGUAGE_OVERRIDE ? DEFAULT_LANGUAGE_OVERRIDE : 'en', + fallbackLng: 'en', + debug: true, + resources: { + en: { + menu: enMenu, + }, + it: { + menu: itMenu, + } + }, +}); + +// Module declared to make referencing keys in the localization files type-safe. +declare module 'i18next' { + interface CustomTypeOptions { + resources: { + menu: typeof enMenu; + }; + } +} + +export default i18next;