From cb6a242fe8292cf0d8939d3878a6add5af74d21e Mon Sep 17 00:00:00 2001 From: cocobo1 Date: Wed, 18 Mar 2026 17:53:26 +0200 Subject: [PATCH] feat: make i18n work (thanks serstars) --- src/core/i18n/index.ts | 165 ++++++++++++++++++++++++++++++++--------- 1 file changed, 129 insertions(+), 36 deletions(-) diff --git a/src/core/i18n/index.ts b/src/core/i18n/index.ts index 3ddf999..c3ec1ef 100644 --- a/src/core/i18n/index.ts +++ b/src/core/i18n/index.ts @@ -1,10 +1,13 @@ +import { logger } from "@lib/utils/logger"; import { FluxDispatcher } from "@metro/common"; -import { findByNameLazy } from "@metro/wrappers"; +import { findByNameLazy, findByStoreName } from "@metro/wrappers"; import { PrimitiveType } from "intl-messageformat"; import langDefault from "./default.json"; -const IntlMessageFormat = findByNameLazy("MessageFormat") as typeof import("intl-messageformat").default; +const IntlMessageFormat = findByNameLazy( + "MessageFormat", +) as typeof import("intl-messageformat").default; type I18nKey = keyof typeof langDefault; @@ -14,54 +17,144 @@ let _lastSetLocale: string | null = null; const _loadedLocale = new Set(); const _loadedStrings = {} as Record; -export const Strings = new Proxy({}, { - get: (_t, prop: keyof typeof langDefault) => { - if (_currentLocale && _loadedStrings[_currentLocale]?.[prop]) { - return _loadedStrings[_currentLocale]?.[prop]; +export const Strings = new Proxy( + {}, + { + get: (_t, prop: keyof typeof langDefault) => { + if (_currentLocale && _loadedStrings[_currentLocale]?.[prop]) { + return _loadedStrings[_currentLocale]?.[prop]; + } + return langDefault[prop]; + }, + }, +) as Record; + +const languageMap: Record = { + "ar-SA": "ar", + "bn-BD": "bn", + "ca-ES": "ca", + "de-DE": "de", + "es-ES": "es", + "es-419": "es", + "fa-IR": "fa", + "fi-FI": "fi", + "fr-FR": "fr", + "hi-IN": "hi", + "hr-HR": "hr", + "hu-HU": "hu", + "id-ID": "id", + "it-IT": "it", + "ja-JP": "ja", + "pl-PL": "pl", + "pt-BR": "pt_BR", + "ru-RU": "ru", + "sk-SK": "sk", + "sv-SE": "sv", + "tr-TR": "tr", + "vi-VN": "vi", +}; + +function fetchLocale(locale: string) { + const resolvedLocale = (_lastSetLocale = languageMap[locale] ?? locale); + + logger.log("[i18n] fetchLocale called:", locale, "->", resolvedLocale); + + if (!_loadedLocale.has(resolvedLocale)) { + _loadedLocale.add(resolvedLocale); + + if (resolvedLocale.toLowerCase().startsWith("en")) { + // use local default.json for english + logger.log("[i18n] Using local default.json for English locale"); + _loadedStrings[resolvedLocale] = langDefault; + _currentLocale = resolvedLocale; + } else { + fetch(`https://codeberg.org/cocobo1/kettu-i18n/raw/branch/main/base/${resolvedLocale}.json`) + .then(r => r.json()) + .then(strings => { + logger.log("[i18n] Loaded strings for:", resolvedLocale); + _loadedStrings[resolvedLocale] = strings; + _currentLocale = resolvedLocale; + }) + .catch(e => + logger.error(`[i18n] Error fetching strings for ${resolvedLocale}: ${e}`), + ); } - return langDefault[prop]; + } else { + _currentLocale = resolvedLocale; } -}) as Record; +} export function initFetchI18nStrings() { - const cb = ({ locale }: { locale: string; }) => { - const languageMap = { - "es-ES": "es", - "es-419": "es_419", - "zh-TW": "zh-Hant", - "zh-CN": "zh-Hans", - "pt-PT": "pt", - "pt-BR": "pt_BR", - "sv-SE": "sv" - } as Record; + let attempts = 0; + const checkAndFetch = () => { + attempts++; - const resolvedLocale = _lastSetLocale = languageMap[locale] ?? locale; + try { + const LocaleStore = findByStoreName("LocaleStore"); + logger.log("[i18n] Attempt", attempts, "- LocaleStore:", !!LocaleStore); - if (resolvedLocale.startsWith("en-")) { - _currentLocale = null; - return; + if (!LocaleStore) { + logger.log("[i18n] LocaleStore not found yet"); + return false; + } + + if (LocaleStore?._isInitialized !== true) { + logger.log("[i18n] LocaleStore not initialized yet"); + return false; + } + + const locale = LocaleStore.locale; + + if (locale) { + logger.log("[i18n] Using LocaleStore:", locale); + fetchLocale(locale); + return true; + } + + } catch (e) { + logger.log("[i18n] Error:", e); } + return false; + }; - if (!_loadedLocale.has(resolvedLocale)) { - _loadedLocale.add(resolvedLocale); - - fetch(`https://codeberg.org/cocobo1/kettu-i18n/src/branch/main/base/${resolvedLocale}.json`) - .then(r => r.json()) - .then(strings => _loadedStrings[resolvedLocale] = strings) - .then(() => resolvedLocale === _lastSetLocale && (_currentLocale = resolvedLocale)) - .catch(e => console.error(`An error occured while fetching strings for ${resolvedLocale}: ${e}`)); - } else { - _currentLocale = resolvedLocale; + const tryTimes = () => { + if (checkAndFetch()) return; + if (attempts < 15) { + setTimeout(tryTimes, 500); } }; - FluxDispatcher.subscribe("I18N_LOAD_SUCCESS", cb); - return () => FluxDispatcher.unsubscribe("I18N_LOAD_SUCCESS", cb); + tryTimes(); + + const cb = (e: any) => { + if (e?.settings?.changes?.loading) { + logger.log("[i18n] Settings loading, skipping..."); + return; + } + + const locale = e?.settings?.changes?.protoToSave?.localization?.locale?.value; + logger.log("[i18n] Locale changed:", locale); + if (locale) { + logger.log("[i18n] Found locale in event:", locale); + fetchLocale(locale); + } + }; + + FluxDispatcher.subscribe("USER_SETTINGS_PROTO_UPDATE_EDIT_INFO", cb); + + return () => { + FluxDispatcher.unsubscribe("USER_SETTINGS_PROTO_UPDATE_EDIT_INFO", cb); + }; } -type FormatStringRet = T extends PrimitiveType ? string : string | T | (string | T)[]; +type FormatStringRet = T extends PrimitiveType + ? string + : string | T | (string | T)[]; -export function formatString(key: I18nKey, val: Record): FormatStringRet { +export function formatString( + key: I18nKey, + val: Record, +): FormatStringRet { const str = Strings[key]; // @ts-ignore return new IntlMessageFormat(str).format(val);