diff --git a/src/ts/i18n.ts b/src/ts/i18n.ts index 91e3960..3f44c5d 100644 --- a/src/ts/i18n.ts +++ b/src/ts/i18n.ts @@ -150,6 +150,7 @@ export class I18n { // The language data itself private langs : Map = new Map(); private lang: Language = fallbackLanguage; + private countryNames: {[key: string]: string} | null = null; private languageDropdown: HTMLSpanElement = document.getElementById('languageDropdown') as HTMLSpanElement; private emitter: Emitter = createNanoEvents(); @@ -163,7 +164,7 @@ export class I18n { var res = await fetch("lang/languages.json"); if (!res.ok) { alert("Failed to load languages.json: " + res.statusText); - this.SetLanguage(fallbackLanguage, fallbackId); + await this.SetLanguage(fallbackLanguage, fallbackId); this.ReplaceStaticStrings(); return; } @@ -184,9 +185,9 @@ export class I18n { a.classList.add('dropdown-item'); a.href = '#'; a.innerText = `${_lang.flag} ${_lang.languageName}`; - a.addEventListener('click', (e) => { + a.addEventListener('click', async e => { e.preventDefault(); - this.SetLanguage(_lang, langId); + await this.SetLanguage(_lang, langId); this.ReplaceStaticStrings(); }); this.languageDropdown.appendChild(a); @@ -209,15 +210,33 @@ export class I18n { } // If all else fails, use the default language if (lang === null) lang = langData.defaultLanguage; - this.SetLanguage(this.langs.get(lang) as Language, lang); + await this.SetLanguage(this.langs.get(lang) as Language, lang); this.ReplaceStaticStrings(); } - private SetLanguage(lang: Language, id: string) { + private async getCountryNames(lang: string) : Promise<{[key: string]: string} | null> { + lang = lang.split('-')[0].toLowerCase(); + let res = await fetch(`https://www.unpkg.com/cldr-localenames-full@45.0.0/main/${lang}/territories.json`); + if (!res.ok) { + console.error(`Failed to load territories.json for ${lang}: ${res.statusText}`); + return null; + } + let data = await res.json(); + return data.main[lang].localeDisplayNames.territories; + } + + getCountryName(code: string) : string { + if (this.countryNames === null) return code; + return this.countryNames[code] || code; + } + + private async SetLanguage(lang: Language, id: string) { let lastId = this.langId; this.langId = id; this.lang = lang; + this.countryNames = await this.getCountryNames(id); + // Only replace static strings if (this.langId != lastId) this.ReplaceStaticStrings(); diff --git a/src/ts/main.ts b/src/ts/main.ts index 7c29c40..783ef35 100644 --- a/src/ts/main.ts +++ b/src/ts/main.ts @@ -10,7 +10,6 @@ import 'simple-keyboard/build/css/index.css'; import VoteStatus from './protocol/VoteStatus.js'; import * as bootstrap from 'bootstrap'; import MuteState from './protocol/MuteState.js'; -import { Unsubscribe } from 'nanoevents'; import { I18nStringKey, TheI18n } from './i18n.js'; import { Format } from './format.js'; import AuthManager from './AuthManager.js'; @@ -607,7 +606,10 @@ function addUser(user: User) { flagSpan.classList.add("userlist-flag"); usernameSpan.classList.add("userlist-username"); td.appendChild(flagSpan); - if (user.countryCode !== null) flagSpan.innerHTML = getFlagEmoji(user.countryCode); + if (user.countryCode !== null) { + flagSpan.innerHTML = getFlagEmoji(user.countryCode); + flagSpan.title = TheI18n.getCountryName(user.countryCode); + }; td.appendChild(usernameSpan); usernameSpan.innerText = user.username; switch (user.rank) { @@ -649,6 +651,7 @@ function getFlagEmoji(countryCode: string) { function flag() { for (let user of users.filter(u => u.user.countryCode !== null)) { user.flagElement.innerHTML = getFlagEmoji(user.user.countryCode!); + user.flagElement.title = TheI18n.getCountryName(user.user.countryCode!); } } @@ -1321,6 +1324,12 @@ document.addEventListener('DOMContentLoaded', async () => { elements.ghostTurnBtnText.innerText = TheI18n.GetString(I18nStringKey.kAdminVMButtons_GhostTurnOn); else elements.ghostTurnBtnText.innerText = TheI18n.GetString(I18nStringKey.kAdminVMButtons_GhostTurnOff); + + for (const user of users) { + if (user.user.countryCode !== null) { + user.flagElement.title = TheI18n.getCountryName(user.user.countryCode); + } + } }); // Load theme var _darktheme : boolean;