Added language setting (#185)

* Added language setting

* Allow properly changing language

---------

Co-authored-by: Flashfyre <flashfireex@gmail.com>
This commit is contained in:
Miguel S 2024-04-19 20:55:01 +02:00 committed by GitHub
parent 86da18943d
commit 95d2ad2fb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 112 additions and 41 deletions

View File

@ -15,7 +15,7 @@ import { GameData, PlayerGender } from './system/game-data';
import StarterSelectUiHandler from './ui/starter-select-ui-handler'; import StarterSelectUiHandler from './ui/starter-select-ui-handler';
import { TextStyle, addTextObject } from './ui/text'; import { TextStyle, addTextObject } from './ui/text';
import { Moves } from "./data/enums/moves"; import { Moves } from "./data/enums/moves";
import { } from "./data/move"; import { allMoves } from "./data/move";
import { initMoves } from './data/move'; import { initMoves } from './data/move';
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type'; import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type';
import AbilityBar from './ui/ability-bar'; import AbilityBar from './ui/ability-bar';
@ -58,6 +58,7 @@ import { UiTheme } from './enums/ui-theme';
import { SceneBase } from './scene-base'; import { SceneBase } from './scene-base';
import CandyBar from './ui/candy-bar'; import CandyBar from './ui/candy-bar';
import { Variant, variantData } from './data/variant'; import { Variant, variantData } from './data/variant';
import { Localizable } from './plugins/i18n';
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -454,7 +455,7 @@ export default class BattleScene extends SceneBase {
hideOnComplete: true hideOnComplete: true
}); });
this.reset(); this.reset(false, false, true);
const ui = new UI(this); const ui = new UI(this);
this.uiContainer.add(ui); this.uiContainer.add(ui);
@ -738,7 +739,7 @@ export default class BattleScene extends SceneBase {
return this.currentBattle.randSeedInt(this, range, min); 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) if (clearData)
this.gameData = new GameData(this); this.gameData = new GameData(this);
@ -791,7 +792,13 @@ export default class BattleScene extends SceneBase {
this.trainer.setTexture(`trainer_${this.gameData.gender === PlayerGender.FEMALE ? 'f' : 'm'}_back`); this.trainer.setTexture(`trainer_${this.gameData.gender === PlayerGender.FEMALE ? 'f' : 'm'}_back`);
this.trainer.setPosition(406, 186); 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) { if (clearScene) {
this.fadeOutBgm(250, false); this.fadeOutBgm(250, false);

View File

@ -24,7 +24,7 @@ import { Species } from "./enums/species";
import { ModifierPoolType } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#app/modifier/modifier-type";
import { Command } from "../ui/command-ui-handler"; import { Command } from "../ui/command-ui-handler";
import { Biome } from "./enums/biome"; import { Biome } from "./enums/biome";
import i18next from '../plugins/i18n'; import i18next, { Localizable } from '../plugins/i18n';
export enum MoveCategory { export enum MoveCategory {
PHYSICAL, PHYSICAL,
@ -75,7 +75,7 @@ export enum MoveFlags {
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: 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 id: Moves;
public name: string; public name: string;
public type: Type; 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; 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.type = type;
this.category = category; this.category = category;
this.moveTarget = defaultMoveTarget; this.moveTarget = defaultMoveTarget;
this.power = power; this.power = power;
this.accuracy = accuracy; this.accuracy = accuracy;
this.pp = pp; 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.chance = chance;
this.priority = priority; this.priority = priority;
this.generation = generation; this.generation = generation;
@ -119,6 +119,13 @@ export default class Move {
this.setFlag(MoveFlags.MAKES_CONTACT, true); 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[] { getAttrs(attrType: { new(...args: any[]): MoveAttr }): MoveAttr[] {
return this.attrs.filter(a => a instanceof attrType); return this.attrs.filter(a => a instanceof attrType);
} }

View File

@ -8,12 +8,14 @@ import { SceneBase } from "./scene-base";
import { WindowVariant, getWindowVariantSuffix } from "./ui/ui-theme"; import { WindowVariant, getWindowVariantSuffix } from "./ui/ui-theme";
import { isMobile } from "./touch-controls"; import { isMobile } from "./touch-controls";
import * as Utils from "./utils"; import * as Utils from "./utils";
import { initI18n } from "./plugins/i18n";
export class LoadingScene extends SceneBase { export class LoadingScene extends SceneBase {
constructor() { constructor() {
super('loading'); super('loading');
Phaser.Plugins.PluginCache.register('Loader', CacheBustedLoaderPlugin, 'load'); Phaser.Plugins.PluginCache.register('Loader', CacheBustedLoaderPlugin, 'load');
initI18n();
} }
preload() { preload() {

View File

@ -15,41 +15,52 @@ export interface MoveTranslations {
[key: string]: MoveTranslationEntry [key: string]: MoveTranslationEntry
} }
export interface Localizable {
localize(): void;
}
const DEFAULT_LANGUAGE_OVERRIDE = ''; const DEFAULT_LANGUAGE_OVERRIDE = '';
/** export function initI18n(): void {
* i18next is a localization library for maintaining and using translation resources. let lang = 'en';
*
* 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({ if (localStorage.getItem('prLang'))
lng: DEFAULT_LANGUAGE_OVERRIDE ? DEFAULT_LANGUAGE_OVERRIDE : 'en', lang = localStorage.getItem('prLang');
fallbackLng: 'en',
debug: true, /**
interpolation: { * i18next is a localization library for maintaining and using translation resources.
escapeValue: false, *
}, * Q: How do I add a new language?
resources: { * A: To add a new language, create a new folder in the locales directory with the language code.
en: { * Each language folder should contain a file for each namespace (ex. menu.ts) with the translations.
menu: enMenu, *
move: enMove, * 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: { resources: {
menu: itMenu, 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. // Module declared to make referencing keys in the localization files type-safe.
declare module 'i18next' { declare module 'i18next' {

View File

@ -1,13 +1,17 @@
import i18next from "i18next";
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
import { hasTouchscreen } from "../touch-controls"; import { hasTouchscreen } from "../touch-controls";
import { updateWindowType } from "../ui/ui-theme"; import { updateWindowType } from "../ui/ui-theme";
import { PlayerGender } from "./game-data"; import { PlayerGender } from "./game-data";
import { Mode } from "#app/ui/ui";
import SettingsUiHandler from "#app/ui/settings-ui-handler";
export enum Setting { export enum Setting {
Game_Speed = "GAME_SPEED", Game_Speed = "GAME_SPEED",
Master_Volume = "MASTER_VOLUME", Master_Volume = "MASTER_VOLUME",
BGM_Volume = "BGM_VOLUME", BGM_Volume = "BGM_VOLUME",
SE_Volume = "SE_VOLUME", SE_Volume = "SE_VOLUME",
Language = "LANGUAGE",
Damage_Numbers = "DAMAGE_NUMBERS", Damage_Numbers = "DAMAGE_NUMBERS",
UI_Theme = "UI_THEME", UI_Theme = "UI_THEME",
Window_Type = "WINDOW_TYPE", Window_Type = "WINDOW_TYPE",
@ -38,6 +42,7 @@ export const settingOptions: SettingOptions = {
[Setting.Master_Volume]: new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : 'Mute'), [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.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.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.Damage_Numbers]: [ 'Off', 'Simple', 'Fancy' ],
[Setting.UI_Theme]: [ 'Default', 'Legacy' ], [Setting.UI_Theme]: [ 'Default', 'Legacy' ],
[Setting.Window_Type]: new Array(5).fill(null).map((_, i) => (i + 1).toString()), [Setting.Window_Type]: new Array(5).fill(null).map((_, i) => (i + 1).toString()),
@ -60,6 +65,7 @@ export const settingDefaults: SettingDefaults = {
[Setting.Master_Volume]: 5, [Setting.Master_Volume]: 5,
[Setting.BGM_Volume]: 10, [Setting.BGM_Volume]: 10,
[Setting.SE_Volume]: 10, [Setting.SE_Volume]: 10,
[Setting.Language]: 0,
[Setting.Damage_Numbers]: 0, [Setting.Damage_Numbers]: 0,
[Setting.UI_Theme]: 0, [Setting.UI_Theme]: 0,
[Setting.Window_Type]: 0, [Setting.Window_Type]: 0,
@ -77,7 +83,7 @@ export const settingDefaults: SettingDefaults = {
[Setting.Vibration]: 0 [Setting.Vibration]: 0
}; };
export const reloadSettings: Setting[] = [ Setting.UI_Theme ]; export const reloadSettings: Setting[] = [ Setting.UI_Theme, Setting.Language ];
export function setSetting(scene: BattleScene, setting: Setting, value: integer): boolean { export function setSetting(scene: BattleScene, setting: Setting, value: integer): boolean {
switch (setting) { switch (setting) {
@ -151,6 +157,39 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer)
case Setting.Vibration: case Setting.Vibration:
scene.enableVibration = settingOptions[setting][value] !== 'Disabled' && hasTouchscreen(); scene.enableVibration = settingOptions[setting][value] !== 'Disabled' && hasTouchscreen();
break; break;
case Setting.Language:
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;
} }
return true; return true;

View File

@ -22,11 +22,13 @@ export default class SettingsUiHandler extends UiHandler {
private cursorObj: Phaser.GameObjects.NineSlice; private cursorObj: Phaser.GameObjects.NineSlice;
private reloadRequired: boolean; private reloadRequired: boolean;
private reloadI18n: boolean;
constructor(scene: BattleScene, mode?: Mode) { constructor(scene: BattleScene, mode?: Mode) {
super(scene, mode); super(scene, mode);
this.reloadRequired = false; this.reloadRequired = false;
this.reloadI18n = false;
} }
setup() { setup() {
@ -197,8 +199,11 @@ export default class SettingsUiHandler extends UiHandler {
if (save) { if (save) {
this.scene.gameData.saveSetting(setting, cursor) this.scene.gameData.saveSetting(setting, cursor)
if (reloadSettings.includes(setting)) if (reloadSettings.includes(setting)) {
this.reloadRequired = true; this.reloadRequired = true;
if (setting === Setting.Language)
this.reloadI18n = true;
}
} }
return true; return true;
@ -234,7 +239,7 @@ export default class SettingsUiHandler extends UiHandler {
this.eraseCursor(); this.eraseCursor();
if (this.reloadRequired) { if (this.reloadRequired) {
this.reloadRequired = false; this.reloadRequired = false;
this.scene.reset(true); this.scene.reset(true, false, true);
} }
} }