lazy load locales (#1940)

* lazy load locales

* fix translation getter

* prettier ignore all generated i18n files

* fix menu translation reactivity

* put language and country translations into namespace

* use Intl.DisplayNames to provide language and region translations

* update typesafe-i18n

* fix newly added translations

* remove unused translations

* add fallback to locale code when Intl.DisplayNames is unavailable
This commit is contained in:
Lukas 2022-04-25 16:45:02 +02:00 committed by GitHub
parent 1c9caa690a
commit 9e0f43d542
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 55 additions and 82 deletions

View File

@ -1,5 +1,3 @@
src/Messages/generated
src/Messages/JsonMessages
src/i18n/i18n-svelte.ts
src/i18n/i18n-types.ts
src/i18n/i18n-util.ts
src/i18n/i18n-*.ts

View File

@ -1,5 +1,5 @@
{
"$schema": "https://unpkg.com/typesafe-i18n@2.59.0/schema/typesafe-i18n.json",
"$schema": "https://unpkg.com/typesafe-i18n@5.3.5/schema/typesafe-i18n.json",
"baseLocale": "en-US",
"adapter": "svelte"
}

View File

@ -60,7 +60,7 @@
"standardized-audio-context": "^25.2.4",
"ts-deferred": "^1.0.4",
"ts-proto": "^1.96.0",
"typesafe-i18n": "^2.59.0",
"typesafe-i18n": "^5.3.5",
"uuidv4": "^6.2.10",
"zod": "^3.14.3"
},

View File

@ -88,16 +88,11 @@
}
}
function translateMenuName(menu: MenuItem) {
if (menu.type === "scripting") {
return menu.label;
}
// Bypass the proxy of typesafe for getting the menu name : https://github.com/ivanhofer/typesafe-i18n/issues/156
const getMenuName = $LL.menu.sub[menu.key];
return getMenuName();
}
$: subMenuTranslations = $subMenusStore.map((subMenu) =>
subMenu.type === "scripting" ? subMenu.label : $LL.menu.sub[subMenu.key]()
);
$: activeSubMenuTranslation =
activeSubMenu.type === "scripting" ? activeSubMenu.label : $LL.menu.sub[activeSubMenu.key]();
</script>
<svelte:window on:keydown={onKeyDown} />
@ -106,20 +101,20 @@
<div class="menu-nav-sidebar nes-container is-rounded" transition:fly={{ x: -1000, duration: 500 }}>
<h2>{$LL.menu.title()}</h2>
<nav>
{#each $subMenusStore as submenu}
{#each $subMenusStore as submenu, i}
<button
type="button"
class="nes-btn {activeSubMenu === submenu ? 'is-disabled' : ''}"
on:click|preventDefault={() => void switchMenu(submenu)}
>
{translateMenuName(submenu)}
{subMenuTranslations[i]}
</button>
{/each}
</nav>
</div>
<div class="menu-submenu-container nes-container is-rounded" transition:fly={{ y: -1000, duration: 500 }}>
<button type="button" class="nes-btn is-error close" on:click={closeMenu}>&times</button>
<h2>{translateMenuName(activeSubMenu)}</h2>
<h2>{activeSubMenuTranslation}</h2>
<svelte:component this={activeComponent} {...props} />
</div>
</div>

View File

@ -28,12 +28,12 @@
let previewCameraPrivacySettings = valueCameraPrivacySettings;
let previewMicrophonePrivacySettings = valueMicrophonePrivacySettings;
function saveSetting() {
async function saveSetting() {
let change = false;
if (valueLocale !== previewValueLocale) {
previewValueLocale = valueLocale;
setCurrentLocale(valueLocale as Locales);
await setCurrentLocale(valueLocale as Locales);
}
if (valueVideo !== previewValueVideo) {
@ -174,7 +174,7 @@
<div class="nes-select is-dark">
<select class="languages-switcher" bind:value={valueLocale}>
{#each displayableLocales as locale (locale.id)}
<option value={locale.id}>{`${locale.language} (${locale.country})`}</option>
<option value={locale.id}>{`${locale.language} (${locale.region})`}</option>
{/each}
</select>
</div>

View File

@ -363,15 +363,13 @@ class ConnectionManager {
if (locale) {
try {
if (locales.indexOf(locale) == -1) {
locales.forEach((l) => {
if (l.startsWith(locale.split("-")[0])) {
setCurrentLocale(l);
return;
}
});
if (locales.indexOf(locale) !== -1) {
await setCurrentLocale(locale as Locales);
} else {
setCurrentLocale(locale as Locales);
const nonRegionSpecificLocale = locales.find((l) => l.startsWith(locale.split("-")[0]));
if (nonRegionSpecificLocale) {
await setCurrentLocale(nonRegionSpecificLocale);
}
}
} catch (err) {
console.warn("Could not set locale", err);

View File

@ -4,7 +4,7 @@ import { Character } from "../Entity/Character";
import type { GameScene } from "../Game/GameScene";
import type { PointInterface } from "../../Connexion/ConnexionModels";
import type { PlayerAnimationDirections } from "../Player/Animation";
import type { Unsubscriber } from "svelte/store";
import { get, Unsubscriber } from "svelte/store";
import type { ActivatableInterface } from "../Game/ActivatableInterface";
import type CancelablePromise from "cancelable-promise";
import LL from "../../i18n/i18n-svelte";
@ -113,7 +113,7 @@ export class RemotePlayer extends Character implements ActivatableInterface {
const actions: ActionsMenuAction[] = [];
if (this.visitCardUrl) {
actions.push({
actionName: LL.woka.menu.businessCard(),
actionName: get(LL).woka.menu.businessCard(),
protected: true,
priority: 1,
callback: () => {
@ -125,8 +125,8 @@ export class RemotePlayer extends Character implements ActivatableInterface {
actions.push({
actionName: blackListManager.isBlackListed(this.userUuid)
? LL.report.block.unblock()
: LL.report.block.block(),
? get(LL).report.block.unblock()
: get(LL).report.block.block(),
protected: true,
priority: -1,
style: "is-error",

View File

@ -1,3 +1 @@
i18n-svelte.ts
i18n-types.ts
i18n-util.ts
i18n-*.ts

View File

@ -16,8 +16,6 @@ import trigger from "./trigger";
const de_DE: Translation = {
...(en_US as Translation),
language: "Deutsch",
country: "Deutschland",
audio,
camera,
chat,

View File

@ -14,8 +14,6 @@ import emoji from "./emoji";
import trigger from "./trigger";
const en_US: BaseTranslation = {
language: "English",
country: "United States",
audio,
camera,
chat,

View File

@ -1,8 +1,8 @@
import type { AsyncFormattersInitializer } from "typesafe-i18n";
import type { FormattersInitializer } from "typesafe-i18n";
import type { Locales, Formatters } from "./i18n-types";
// eslint-disable-next-line @typescript-eslint/require-await
export const initFormatters: AsyncFormattersInitializer<Locales, Formatters> = async () => {
export const initFormatters: FormattersInitializer<Locales, Formatters> = async () => {
const formatters: Formatters = {
// add your formatter functions here
};

View File

@ -16,8 +16,6 @@ import trigger from "./trigger";
const fr_FR: Translation = {
...(en_US as Translation),
language: "Français",
country: "France",
audio,
camera,
chat,

View File

@ -1,52 +1,44 @@
import { detectLocale, navigatorDetector, initLocalStorageDetector } from "typesafe-i18n/detectors";
import { FALLBACK_LOCALE } from "../Enum/EnvironmentVariable";
import { initI18n, setLocale } from "./i18n-svelte";
import { setLocale } from "./i18n-svelte";
import type { Locales } from "./i18n-types";
import { baseLocale, getTranslationForLocale, locales } from "./i18n-util";
import { baseLocale, locales } from "./i18n-util";
import { loadLocaleAsync } from "./i18n-util.async";
const fallbackLocale = FALLBACK_LOCALE || baseLocale;
const fallbackLocale = (FALLBACK_LOCALE || baseLocale) as Locales;
const localStorageProperty = "language";
export const localeDetector = async () => {
const exist = localStorage.getItem(localStorageProperty);
let detectedLocale: Locales = fallbackLocale as Locales;
let detectedLocale: Locales = fallbackLocale;
if (exist) {
const localStorageDetector = initLocalStorageDetector(localStorageProperty);
detectedLocale = detectLocale(fallbackLocale, locales, localStorageDetector) as Locales;
detectedLocale = detectLocale(fallbackLocale, locales, localStorageDetector);
} else {
detectedLocale = detectLocale(fallbackLocale, locales, navigatorDetector) as Locales;
detectedLocale = detectLocale(fallbackLocale, locales, navigatorDetector);
}
await initI18n(detectedLocale);
await setCurrentLocale(detectedLocale);
};
export const setCurrentLocale = (locale: Locales) => {
export const setCurrentLocale = async (locale: Locales) => {
localStorage.setItem(localStorageProperty, locale);
setLocale(locale).catch(() => {
console.log("Cannot reload the locale!");
});
await loadLocaleAsync(locale);
setLocale(locale);
};
export type DisplayableLocale = { id: Locales; language: string; country: string };
export const displayableLocales: { id: Locales; language: string; region: string }[] = locales.map((locale) => {
const [language, region] = locale.split("-");
function getDisplayableLocales() {
const localesObject: DisplayableLocale[] = [];
locales.forEach((locale) => {
getTranslationForLocale(locale)
.then((translations) => {
localesObject.push({
id: locale,
language: translations.language,
country: translations.country,
});
})
.catch((error) => {
console.log(error);
});
});
// backwards compatibility
if (!Intl.DisplayNames) {
return { id: locale, language, region };
}
return localesObject;
}
export const displayableLocales = getDisplayableLocales();
return {
id: locale,
language: new Intl.DisplayNames(locale, { type: "language" }).of(language),
region: new Intl.DisplayNames(locale, { type: "region" }).of(region),
};
});

View File

@ -16,8 +16,6 @@ import trigger from "./trigger";
const zh_CN: Translation = {
...(en_US as Translation),
language: "中文",
country: "中国",
audio,
camera,
chat,

View File

@ -8,7 +8,7 @@
"moduleResolution": "node",
//"module": "CommonJS",
"module": "ESNext",
"target": "ES2017",
"target": "ES2020",
"declaration": false,
"downlevelIteration": true,
"jsx": "react",

View File

@ -2991,10 +2991,10 @@ type-fest@^0.21.3:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
typesafe-i18n@^2.59.0:
version "2.59.0"
resolved "https://registry.yarnpkg.com/typesafe-i18n/-/typesafe-i18n-2.59.0.tgz#09a9a32e61711418d927a389fa52e1c06a5fa5c4"
integrity sha512-Qv3Mrwmb8b73VNzQDPHPECzwymdBRVyDiZ3w2qnp4c2iv/7TGuiJegNHT/l3MooEN7IPbSpc5tbXw2x3MbGtFg==
typesafe-i18n@^5.3.5:
version "5.3.5"
resolved "https://registry.yarnpkg.com/typesafe-i18n/-/typesafe-i18n-5.3.5.tgz#8561648a2be0df660404aa087993f3eee584cb87"
integrity sha512-ZjCCQ2lCyyvUThtxJblXoxwpr62paOjMRi/Kia1PSEh3gRfwPvEorABS0zTdF6lZ75MQXoz0WqtobChVjkO5mQ==
typescript@*:
version "4.3.2"