From f2d3f32b892266add8b2b49a04ee2884d0906086 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Fri, 19 Apr 2024 14:54:05 -0400 Subject: [PATCH] Allow properly changing language --- src/battle-scene.ts | 15 ++++++-- src/data/move.ts | 15 ++++++-- src/loading-scene.ts | 2 + src/plugins/i18n.ts | 71 ++++++++++++++++++++--------------- src/system/settings.ts | 46 +++++++++++++++++++---- src/ui/settings-ui-handler.ts | 9 ++++- 6 files changed, 111 insertions(+), 47 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 5f27b8e0ceb..8a3aecd6a28 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -15,7 +15,7 @@ import { GameData, PlayerGender } from './system/game-data'; import StarterSelectUiHandler from './ui/starter-select-ui-handler'; import { TextStyle, addTextObject } from './ui/text'; import { Moves } from "./data/enums/moves"; -import { } from "./data/move"; +import { allMoves } from "./data/move"; import { initMoves } from './data/move'; import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type'; import AbilityBar from './ui/ability-bar'; @@ -57,6 +57,7 @@ import { initTouchControls } from './touch-controls'; import { UiTheme } from './enums/ui-theme'; import { SceneBase } from './scene-base'; import CandyBar from './ui/candy-bar'; +import { Localizable } from './plugins/i18n'; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -458,7 +459,7 @@ export default class BattleScene extends SceneBase { hideOnComplete: true }); - this.reset(); + this.reset(false, false, true); const ui = new UI(this); this.uiContainer.add(ui); @@ -727,7 +728,7 @@ export default class BattleScene extends SceneBase { return this.currentBattle.randSeedInt(this, range, min); } - reset(clearScene: boolean = false, clearData: boolean = false): void { + reset(clearScene: boolean = false, clearData: boolean = false, reloadI18n: boolean = false): void { if (clearData) this.gameData = new GameData(this); @@ -780,7 +781,13 @@ export default class BattleScene extends SceneBase { this.trainer.setTexture(`trainer_${this.gameData.gender === PlayerGender.FEMALE ? 'f' : 'm'}_back`); this.trainer.setPosition(406, 186); - this.trainer.setVisible(true) + this.trainer.setVisible(true); + + if (reloadI18n) { + const localizable: Localizable[] = [ ...allMoves ]; + for (let item of localizable) + item.localize(); + } if (clearScene) { this.fadeOutBgm(250, false); diff --git a/src/data/move.ts b/src/data/move.ts index eaeb169654c..48758b5a8ac 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -24,7 +24,7 @@ import { Species } from "./enums/species"; import { ModifierPoolType } from "#app/modifier/modifier-type"; import { Command } from "../ui/command-ui-handler"; import { Biome } from "./enums/biome"; -import i18next from '../plugins/i18n'; +import i18next, { Localizable } from '../plugins/i18n'; export enum MoveCategory { PHYSICAL, @@ -75,7 +75,7 @@ export enum MoveFlags { type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; -export default class Move { +export default class Move implements Localizable { public id: Moves; public name: string; public type: Type; @@ -97,14 +97,14 @@ export default class Move { const i18nKey = Moves[id].split('_').filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join('') as unknown as string; - this.name = id ? i18next.t(`move:${i18nKey}.name`) as string : ''; + this.name = id ? i18next.t(`move:${i18nKey}.name`).toString() : ''; this.type = type; this.category = category; this.moveTarget = defaultMoveTarget; this.power = power; this.accuracy = accuracy; this.pp = pp; - this.effect = id ? i18next.t(`move:${i18nKey}.effect`) as string : ''; + this.effect = id ? i18next.t(`move:${i18nKey}.effect`).toString() : ''; this.chance = chance; this.priority = priority; this.generation = generation; @@ -119,6 +119,13 @@ export default class Move { this.setFlag(MoveFlags.MAKES_CONTACT, true); } + localize() { + const i18nKey = Moves[this.id].split('_').filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join('') as unknown as string; + + this.name = this.id ? i18next.t(`move:${i18nKey}.name`).toString() : ''; + this.effect = this.id ? i18next.t(`move:${i18nKey}.effect`).toString() : ''; + } + getAttrs(attrType: { new(...args: any[]): MoveAttr }): MoveAttr[] { return this.attrs.filter(a => a instanceof attrType); } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 53933294491..e4c20739c9d 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -8,12 +8,14 @@ import { SceneBase } from "./scene-base"; import { WindowVariant, getWindowVariantSuffix } from "./ui/ui-theme"; import { isMobile } from "./touch-controls"; import * as Utils from "./utils"; +import { initI18n } from "./plugins/i18n"; export class LoadingScene extends SceneBase { constructor() { super('loading'); Phaser.Plugins.PluginCache.register('Loader', CacheBustedLoaderPlugin, 'load'); + initI18n(); } preload() { diff --git a/src/plugins/i18n.ts b/src/plugins/i18n.ts index 8e4997f5d8d..9a72ff55f9d 100644 --- a/src/plugins/i18n.ts +++ b/src/plugins/i18n.ts @@ -15,41 +15,52 @@ export interface MoveTranslations { [key: string]: MoveTranslationEntry } +export interface Localizable { + localize(): void; +} + 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. - */ +export function initI18n(): void { + let lang = 'en'; -i18next.init({ - lng: DEFAULT_LANGUAGE_OVERRIDE ? DEFAULT_LANGUAGE_OVERRIDE : 'en', - fallbackLng: 'en', - debug: true, - interpolation: { - escapeValue: false, - }, - resources: { - en: { - menu: enMenu, - move: enMove, + if (localStorage.getItem('prLang')) + lang = localStorage.getItem('prLang'); + + /** + * 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 : lang, + fallbackLng: 'en', + debug: true, + interpolation: { + escapeValue: false, }, - it: { - menu: itMenu, + resources: { + en: { + menu: enMenu, + move: enMove, + }, + it: { + menu: itMenu, + }, + fr: { + menu: frMenu, + move: frMove, + } }, - fr: { - menu: frMenu, - move: frMove, - } - }, -}); + }); +} // Module declared to make referencing keys in the localization files type-safe. declare module 'i18next' { diff --git a/src/system/settings.ts b/src/system/settings.ts index 9231c32410d..bc302543a13 100644 --- a/src/system/settings.ts +++ b/src/system/settings.ts @@ -3,12 +3,15 @@ import BattleScene from "../battle-scene"; import { hasTouchscreen } from "../touch-controls"; import { updateWindowType } from "../ui/ui-theme"; import { PlayerGender } from "./game-data"; +import { Mode } from "#app/ui/ui"; +import SettingsUiHandler from "#app/ui/settings-ui-handler"; export enum Setting { Game_Speed = "GAME_SPEED", Master_Volume = "MASTER_VOLUME", BGM_Volume = "BGM_VOLUME", SE_Volume = "SE_VOLUME", + Language = "LANGUAGE", Damage_Numbers = "DAMAGE_NUMBERS", UI_Theme = "UI_THEME", Window_Type = "WINDOW_TYPE", @@ -23,8 +26,7 @@ export enum Setting { Player_Gender = "PLAYER_GENDER", Gamepad_Support = "GAMEPAD_SUPPORT", Touch_Controls = "TOUCH_CONTROLS", - Vibration = "VIBRATION", - Language = "LANGUAGE" + Vibration = "VIBRATION" } export interface SettingOptions { @@ -40,6 +42,7 @@ export const settingOptions: SettingOptions = { [Setting.Master_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'), [Setting.BGM_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'), [Setting.SE_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'), + [Setting.Language]: [ 'English', 'Change' ], [Setting.Damage_Numbers]: [ 'Off', 'Simple', 'Fancy' ], [Setting.UI_Theme]: [ 'Default', 'Legacy' ], [Setting.Window_Type]: new Array(5).fill(null).map((_, i) => (i + 1).toString()), @@ -54,8 +57,7 @@ export const settingOptions: SettingOptions = { [Setting.Player_Gender]: [ 'Boy', 'Girl' ], [Setting.Gamepad_Support]: [ 'Auto', 'Disabled' ], [Setting.Touch_Controls]: [ 'Auto', 'Disabled' ], - [Setting.Vibration]: [ 'Auto', 'Disabled' ], - [Setting.Language]: [ 'en', 'fr' ] + [Setting.Vibration]: [ 'Auto', 'Disabled' ] }; export const settingDefaults: SettingDefaults = { @@ -63,6 +65,7 @@ export const settingDefaults: SettingDefaults = { [Setting.Master_Volume]: 5, [Setting.BGM_Volume]: 10, [Setting.SE_Volume]: 10, + [Setting.Language]: 0, [Setting.Damage_Numbers]: 0, [Setting.UI_Theme]: 0, [Setting.Window_Type]: 0, @@ -77,8 +80,7 @@ export const settingDefaults: SettingDefaults = { [Setting.Player_Gender]: 0, [Setting.Gamepad_Support]: 0, [Setting.Touch_Controls]: 0, - [Setting.Vibration]: 0, - [Setting.Language]: 0 + [Setting.Vibration]: 0 }; export const reloadSettings: Setting[] = [ Setting.UI_Theme, Setting.Language ]; @@ -156,7 +158,37 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer) scene.enableVibration = settingOptions[setting][value] !== 'Disabled' && hasTouchscreen(); break; case Setting.Language: - i18next.changeLanguage(settingOptions[setting][value]); + if (value) { + if (scene.ui) { + const cancelHandler = () => { + scene.ui.revertMode(); + (scene.ui.getHandler() as SettingsUiHandler).setOptionCursor(Object.values(Setting).indexOf(Setting.Language), 0, true); + }; + const changeLocaleHandler = (locale: string) => { + i18next.changeLanguage(locale); + localStorage.setItem('prLang', locale); + cancelHandler(); + scene.reset(true, false, true); + }; + scene.ui.setOverlayMode(Mode.OPTION_SELECT, { + options: [ + { + label: 'English', + handler: () => changeLocaleHandler('en') + }, + { + label: 'French', + handler: () => changeLocaleHandler('fr') + }, + { + label: 'Cancel', + handler: () => cancelHandler() + } + ] + }); + return false; + } + } break; } diff --git a/src/ui/settings-ui-handler.ts b/src/ui/settings-ui-handler.ts index ce8a7542d89..8f43b377d21 100644 --- a/src/ui/settings-ui-handler.ts +++ b/src/ui/settings-ui-handler.ts @@ -22,11 +22,13 @@ export default class SettingsUiHandler extends UiHandler { private cursorObj: Phaser.GameObjects.NineSlice; private reloadRequired: boolean; + private reloadI18n: boolean; constructor(scene: BattleScene, mode?: Mode) { super(scene, mode); this.reloadRequired = false; + this.reloadI18n = false; } setup() { @@ -197,8 +199,11 @@ export default class SettingsUiHandler extends UiHandler { if (save) { this.scene.gameData.saveSetting(setting, cursor) - if (reloadSettings.includes(setting)) + if (reloadSettings.includes(setting)) { this.reloadRequired = true; + if (setting === Setting.Language) + this.reloadI18n = true; + } } return true; @@ -234,7 +239,7 @@ export default class SettingsUiHandler extends UiHandler { this.eraseCursor(); if (this.reloadRequired) { this.reloadRequired = false; - this.scene.reset(true); + this.scene.reset(true, false, true); } }