From 3fb38f8614cd34490ee83ecd99846462ec77dd1a Mon Sep 17 00:00:00 2001 From: Elijah R Date: Sun, 23 Jun 2024 03:51:42 -0400 Subject: [PATCH] improve load times by consolidating metadata into languages.json and only loading the requested language --- src/ts/i18n.ts | 44 ++++++++++++++-------- static/lang/languages.json | 75 +++++++++++++++++++++++++++++++++++++- 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/src/ts/i18n.ts b/src/ts/i18n.ts index 3f44c5d..650ffcf 100644 --- a/src/ts/i18n.ts +++ b/src/ts/i18n.ts @@ -126,10 +126,15 @@ export type Language = { }; }; +export type LanguageMetadata = { + languageName: string; + flag: string; // country flag, can be blank if not applicable. will be displayed in language dropdown +}; + // `languages.json` export type LanguagesJson = { // Array of language IDs to allow loading - languages: Array; + languages: {[key: string]: LanguageMetadata}; // The default language (set if a invalid language not in the languages array is set, or no language is set) defaultLanguage: string; @@ -148,7 +153,7 @@ interface StringKeyMap { /// our fancy internationalization helper. export class I18n { // The language data itself - private langs : Map = new Map(); + private langs : Map = new Map(); private lang: Language = fallbackLanguage; private countryNames: {[key: string]: string} | null = null; private languageDropdown: HTMLSpanElement = document.getElementById('languageDropdown') as HTMLSpanElement; @@ -164,20 +169,13 @@ export class I18n { var res = await fetch("lang/languages.json"); if (!res.ok) { alert("Failed to load languages.json: " + res.statusText); - await this.SetLanguage(fallbackLanguage, fallbackId); + await this.SetLanguage(fallbackId); this.ReplaceStaticStrings(); return; } var langData = await res.json() as LanguagesJson; - for (const langId of langData.languages) { - let path = `./lang/${langId}.json`; - let res = await fetch(path); - if (!res.ok) { - console.error(`Failed to load lang/${langId}.json: ${res.statusText}`); - continue; - } - let _lang = await res.json() as Language; - this.langs.set(langId, _lang); + for (const langId in langData.languages) { + this.langs.set(langId, langData.languages[langId]); } this.langs.forEach((_lang, langId) => { // Add to language dropdown @@ -187,7 +185,7 @@ export class I18n { a.innerText = `${_lang.flag} ${_lang.languageName}`; a.addEventListener('click', async e => { e.preventDefault(); - await this.SetLanguage(_lang, langId); + await this.SetLanguage(langId); this.ReplaceStaticStrings(); }); this.languageDropdown.appendChild(a); @@ -201,7 +199,7 @@ export class I18n { else if (this.langs.has(browserLang)) lang = browserLang; else { // If the exact browser language isn't in the list, try to find a language with the same prefix - for (let langId of langData.languages) { + for (let langId in langData.languages) { if (langId.split('-')[0] === browserLang.split('-')[0]) { lang = langId; break; @@ -210,7 +208,7 @@ export class I18n { } // If all else fails, use the default language if (lang === null) lang = langData.defaultLanguage; - await this.SetLanguage(this.langs.get(lang) as Language, lang); + await this.SetLanguage(lang); this.ReplaceStaticStrings(); } @@ -230,9 +228,23 @@ export class I18n { return this.countryNames[code] || code; } - private async SetLanguage(lang: Language, id: string) { + private async SetLanguage(id: string) { let lastId = this.langId; this.langId = id; + + let lang; + if (id === fallbackId) lang = fallbackLanguage; + else { + let path = `./lang/${id}.json`; + let res = await fetch(path); + if (!res.ok) { + console.error(`Failed to load lang/${id}.json: ${res.statusText}`); + await this.SetLanguage(fallbackId); + return; + } + lang = await res.json() as Language; + } + this.lang = lang; this.countryNames = await this.getCountryNames(id); diff --git a/static/lang/languages.json b/static/lang/languages.json index 5664c17..975ac03 100644 --- a/static/lang/languages.json +++ b/static/lang/languages.json @@ -1,4 +1,77 @@ { - "languages": ["en-us", "fr-fr", "de-de", "ja-jp", "pl-pl", "es-es", "ru-ru", "hu-hu", "uk-ua", "hr-hr", "ro-ro", "tok", "id", "ko-kr", "pt-br", "th", "zh-cn", "vi-vn"], + "languages": { + "en-us": { + "languageName": "English (US)", + "flag": "🇺🇸" + }, + "fr-fr": { + "languageName": "Français", + "flag": "🇫🇷" + }, + "de-de": { + "languageName": "Deutsch", + "flag": "🇩🇪" + }, + "ja-jp": { + "languageName": "日本語", + "flag": "🇯🇵" + }, + "pl-pl": { + "languageName": "Polski", + "flag": "🇵🇱" + }, + "es-es": { + "languageName": "Español (España)", + "flag": "🇪🇸" + }, + "ru-ru": { + "languageName": "Русский", + "flag": "🇷🇺" + }, + "hu-hu": { + "languageName": "Magyar", + "flag": "🇭🇺" + }, + "uk-ua": { + "languageName": "Українська", + "flag": "🇺🇦" + }, + "hr-hr": { + "languageName": "Hrvatski", + "flag": "🇭🇷" + }, + "ro-ro": { + "languageName": "Română", + "flag": "🇷🇴" + }, + "tok": { + "languageName": "Toki Pona", + "flag": "🌐" + }, + "id": { + "languageName": "Bahasa Indonesia", + "flag": "🇮🇩" + }, + "ko-kr": { + "languageName": "한국어", + "flag": "🇰🇷" + }, + "pt-br": { + "languageName": "Português (Brasil)", + "flag": "🇧🇷" + }, + "th": { + "languageName": "ภาษาไทย", + "flag": "🇹🇭" + }, + "zh-cn": { + "languageName": "中文 (简体)", + "flag": "🇨🇳" + }, + "vi-vn": { + "languageName": "Tiếng Việt", + "flag": "🇻🇳" + } + }, "defaultLanguage": "en-us" }