From 17afad3dcdaf9b0144845964aef97ae00d5fa118 Mon Sep 17 00:00:00 2001 From: Elijah R Date: Tue, 9 Apr 2024 19:22:54 -0400 Subject: [PATCH] update dynamic strings on language change --- src/ts/i18n.ts | 32 ++++++++++++++++++++++- src/ts/main.ts | 43 ++++++++++++++++++++++++------- src/ts/protocol/CollabVMClient.ts | 4 +++ 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/src/ts/i18n.ts b/src/ts/i18n.ts index 6653f18..6d2cbb7 100644 --- a/src/ts/i18n.ts +++ b/src/ts/i18n.ts @@ -1,5 +1,6 @@ import { StringLike } from './StringLike'; import { Format } from './format'; +import { Emitter, Unsubscribe, createNanoEvents } from 'nanoevents'; /// All string keys. export enum I18nStringKey { @@ -100,6 +101,11 @@ export enum I18nStringKey { kNotLoggedIn = 'kNotLoggedIn', } +export interface I18nEvents { + // Called when the language is changed + languageChanged: (lang: string) => void; +} + // This models the JSON structure. export type Language = { languageName: string; @@ -141,6 +147,7 @@ export class I18n { private langs : Map = new Map(); private lang: Language = fallbackLanguage; private languageDropdown: HTMLSpanElement = document.getElementById('languageDropdown') as HTMLSpanElement; + private emitter: Emitter = createNanoEvents(); CurrentLanguage = () => this.langId; @@ -200,6 +207,8 @@ export class I18n { if (this.langId !== fallbackId) { window.localStorage.setItem('i18n-lang', this.langId); } + + this.emitter.emit('languageChanged', this.langId); console.log('i18n initalized for', id, 'sucessfully!'); } @@ -210,7 +219,6 @@ export class I18n { homeBtnText: I18nStringKey.kSiteButtons_Home, faqBtnText: I18nStringKey.kSiteButtons_FAQ, rulesBtnText: I18nStringKey.kSiteButtons_Rules, - accountDropdownUsername: I18nStringKey.kNotLoggedIn, accountLoginButton: I18nStringKey.kGeneric_Login, accountRegisterButton: I18nStringKey.kGeneric_Register, accountSettingsButton: I18nStringKey.kAccountModal_AccountSettings, @@ -339,6 +347,17 @@ export class I18n { }, }; + const kDomClassToStringMap: StringKeyMap = { + "mod-end-turn-btn": I18nStringKey.kVMButtons_EndTurn, + "mod-ban-btn": I18nStringKey.kAdminVMButtons_Ban, + "mod-kick-btn": I18nStringKey.kAdminVMButtons_Kick, + "mod-change-username-btn": I18nStringKey.kVMButtons_ChangeUsername, + "mod-temp-mute-btn": I18nStringKey.kAdminVMButtons_TempMute, + "mod-indef-mute-btn": I18nStringKey.kAdminVMButtons_IndefMute, + "mod-unmute-btn": I18nStringKey.kAdminVMButtons_Unmute, + "mod-get-ip-btn": I18nStringKey.kAdminVMButtons_GetIP, + } + for (let domId of Object.keys(kDomIdtoStringMap)) { let element = document.getElementById(domId); if (element == null) { @@ -367,6 +386,13 @@ export class I18n { element.setAttribute(attr, this.GetStringRaw(attributes[attr] as I18nStringKey)); } } + + for (let domClass of Object.keys(kDomClassToStringMap)) { + let elements = document.getElementsByClassName(domClass); + for (let element of elements) { + element.innerHTML = this.GetStringRaw(kDomClassToStringMap[domClass]); + } + } } // Returns a (raw, unformatted) string. Currently only used if we don't need formatting. @@ -389,6 +415,10 @@ export class I18n { GetString(key: I18nStringKey, ...replacements: StringLike[]): string { return Format(this.GetStringRaw(key), ...replacements); } + + on(event: e, cb: I18nEvents[e]): Unsubscribe { + return this.emitter.on(event, cb); + } } export let TheI18n = new I18n(); \ No newline at end of file diff --git a/src/ts/main.ts b/src/ts/main.ts index 45e04c4..64ff219 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -826,33 +826,33 @@ function userModOptions(user: { user: User; element: HTMLTableRowElement }) { td.setAttribute('aria-expanded', 'false'); let ul = document.createElement('ul'); ul.classList.add('dropdown-menu', 'dropdown-menu-dark', 'table-dark', 'text-light'); - if (perms.bypassturn) addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kVMButtons_EndTurn), () => VM!.endTurn(user.user.username)); - if (perms.ban) addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_Ban), () => VM!.ban(user.user.username)); - if (perms.kick) addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_Kick), () => VM!.kick(user.user.username)); + if (perms.bypassturn) addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kVMButtons_EndTurn), () => VM!.endTurn(user.user.username), "mod-end-turn-btn"); + if (perms.ban) addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_Ban), () => VM!.ban(user.user.username), "mod-ban-btn"); + if (perms.kick) addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_Kick), () => VM!.kick(user.user.username), "mod-kick-btn"); if (perms.rename) addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kVMButtons_ChangeUsername), () => { let newname = prompt(TheI18n.GetString(I18nStringKey.kVMPrompts_AdminChangeUsernamePrompt, user.user.username)); if (!newname) return; VM!.renameUser(user.user.username, newname); - }); + }, "mod-rename-btn"); if (perms.mute) { - addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_TempMute), () => VM!.mute(user.user.username, MuteState.Temp)); - addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_IndefMute), () => VM!.mute(user.user.username, MuteState.Perma)); - addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_Unmute), () => VM!.mute(user.user.username, MuteState.Unmuted)); + addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_TempMute), () => VM!.mute(user.user.username, MuteState.Temp), "mod-temp-mute-btn"); + addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_IndefMute), () => VM!.mute(user.user.username, MuteState.Perma), "mod-indef-mute-btn"); + addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_Unmute), () => VM!.mute(user.user.username, MuteState.Unmuted), "mod-unmute-btn"); } if (perms.grabip) addUserDropdownItem(ul, TheI18n.GetString(I18nStringKey.kAdminVMButtons_GetIP), async () => { let ip = await VM!.getip(user.user.username); alert(ip); - }); + }, "mod-get-ip-btn"); tr.appendChild(ul); } -function addUserDropdownItem(ul: HTMLUListElement, text: string, func: () => void) { +function addUserDropdownItem(ul: HTMLUListElement, text: string, func: () => void, classname: string) { let li = document.createElement('li'); let a = document.createElement('a'); a.href = '#'; - a.classList.add('dropdown-item'); + a.classList.add('dropdown-item', classname); a.innerHTML = text; a.addEventListener('click', () => func()); li.appendChild(a); @@ -1250,6 +1250,29 @@ w.VMName = null; document.addEventListener('DOMContentLoaded', async () => { // Initalize the i18n system await TheI18n.Init(); + TheI18n.on('languageChanged', lang => { + // Update all dynamic text + if (VM) { + document.title = Format('{0} - {1}', VM.getNode()!, TheI18n.GetString(I18nStringKey.kGeneric_CollabVM)); + if (turn !== -1) { + if (turn === 0) elements.turnstatus.innerText = TheI18n.GetString(I18nStringKey.kVM_TurnTimeTimer, turnTimer); + else elements.turnstatus.innerText = TheI18n.GetString(I18nStringKey.kVM_WaitingTurnTimer, turnTimer); + elements.turnBtnText.innerText = TheI18n.GetString(I18nStringKey.kVMButtons_EndTurn); + } + else + elements.turnBtnText.innerText = TheI18n.GetString(I18nStringKey.kVMButtons_TakeTurn); + if (VM!.getVoteStatus()) + elements.voteTimeText.innerText = TheI18n.GetString(I18nStringKey.kVM_VoteForResetTimer, voteTimer); + + } + else { + document.title = TheI18n.GetString(I18nStringKey.kGeneric_CollabVM); + } + if (!auth || !auth.account) elements.accountDropdownUsername.innerText = TheI18n.GetString(I18nStringKey.kNotLoggedIn); + if (darkTheme) elements.toggleThemeBtnText.innerHTML = TheI18n.GetString(I18nStringKey.kSiteButtons_LightMode); + else elements.toggleThemeBtnText.innerHTML = TheI18n.GetString(I18nStringKey.kSiteButtons_DarkMode); + + }); // Load theme var _darktheme : boolean; if (localStorage.getItem("cvm-dark-theme") === "0") diff --git a/src/ts/protocol/CollabVMClient.ts b/src/ts/protocol/CollabVMClient.ts index 38fd53e..88cdefb 100644 --- a/src/ts/protocol/CollabVMClient.ts +++ b/src/ts/protocol/CollabVMClient.ts @@ -620,6 +620,10 @@ export default class CollabVMClient { return this.auth; } + getNode() { + return this.node; + } + private onInternal(event: E, callback: CollabVMClientPrivateEvents[E]): Unsubscribe { return this.internalEmitter.on(event, callback); }