mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 17:43:47 +00:00
index.html ships <html lang='ru'> and that attribute never changed at runtime, regardless of the user's selected language. Consequences: - screen readers pronounced en/zh/fa content using Russian phonics - browsers offered to translate already-translated content - the Telegram login button and an OAuth widget read documentElement.lang to set their own locale and got the wrong one LanguageSwitcher had a half-fix that only touched dir for fa, and only locally — i18n changes from anywhere else (initial detect, Telegram adopt) were not covered. Move both lang and dir updates into a single languageChanged subscriber inside i18n.ts, run once for the initial detected language, and drop the now-redundant dir-setting in LanguageSwitcher. Layout direction flips automatically for the full RTL set (fa + future ar/he/ur).
103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
import i18n, { type ResourceLanguage } from 'i18next';
|
|
import { initReactI18next } from 'react-i18next';
|
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
import { getTelegramLanguageCode } from './hooks/useTelegramSDK';
|
|
|
|
const localeLoaders: Record<string, () => Promise<{ default: ResourceLanguage }>> = {
|
|
ru: () => import('./locales/ru.json'),
|
|
en: () => import('./locales/en.json'),
|
|
zh: () => import('./locales/zh.json'),
|
|
fa: () => import('./locales/fa.json'),
|
|
};
|
|
|
|
const SUPPORTED_LANGS = Object.keys(localeLoaders);
|
|
const FALLBACK_LNG = 'ru';
|
|
const LANGUAGE_STORAGE_KEY = 'cabinet_language';
|
|
|
|
const loadedLanguages = new Set<string>();
|
|
|
|
async function loadLanguage(lng: string): Promise<void> {
|
|
if (loadedLanguages.has(lng)) return;
|
|
|
|
const loader = localeLoaders[lng];
|
|
if (!loader) return;
|
|
|
|
const mod = await loader();
|
|
i18n.addResourceBundle(lng, 'translation', mod.default, true, true);
|
|
loadedLanguages.add(lng);
|
|
}
|
|
|
|
i18n
|
|
.use(LanguageDetector)
|
|
.use(initReactI18next)
|
|
.init({
|
|
fallbackLng: FALLBACK_LNG,
|
|
supportedLngs: SUPPORTED_LANGS,
|
|
partialBundledLanguages: true,
|
|
|
|
detection: {
|
|
order: ['localStorage', 'navigator'],
|
|
caches: ['localStorage'],
|
|
lookupLocalStorage: 'cabinet_language',
|
|
},
|
|
|
|
interpolation: {
|
|
escapeValue: false,
|
|
},
|
|
|
|
react: {
|
|
useSuspense: false,
|
|
},
|
|
|
|
showSupportNotice: false,
|
|
});
|
|
|
|
// Load detected language + fallback on startup
|
|
const detectedLng = i18n.language?.split('-')[0] || FALLBACK_LNG;
|
|
const langsToLoad = [FALLBACK_LNG, ...(detectedLng !== FALLBACK_LNG ? [detectedLng] : [])];
|
|
Promise.all(langsToLoad.map(loadLanguage));
|
|
|
|
// Keep <html lang> + dir in sync with i18n so screen readers pronounce
|
|
// content correctly, browsers don't offer to translate it, and RTL
|
|
// languages (fa) flip layout direction. index.html ships with lang="ru"
|
|
// for the first paint; runtime updates take over from there.
|
|
const RTL_LANGS = new Set(['fa', 'ar', 'he', 'ur']);
|
|
function syncHtmlLang(lng: string): void {
|
|
const code = lng.split('-')[0];
|
|
if (typeof document === 'undefined') return;
|
|
if (document.documentElement.lang !== code) {
|
|
document.documentElement.lang = code;
|
|
}
|
|
const dir = RTL_LANGS.has(code) ? 'rtl' : 'ltr';
|
|
if (document.documentElement.dir !== dir) {
|
|
document.documentElement.dir = dir;
|
|
}
|
|
}
|
|
syncHtmlLang(detectedLng);
|
|
|
|
// Lazy-load on language change
|
|
i18n.on('languageChanged', (lng: string) => {
|
|
const code = lng.split('-')[0];
|
|
loadLanguage(code);
|
|
syncHtmlLang(code);
|
|
});
|
|
|
|
/**
|
|
* On first run inside Telegram (no explicit stored choice), adopt the user's
|
|
* Telegram client language. Must be called after the Telegram SDK is initialised
|
|
* (e.g. from main.tsx), since launch params are unavailable before init().
|
|
*/
|
|
export function applyTelegramLanguage(): void {
|
|
try {
|
|
if (localStorage.getItem(LANGUAGE_STORAGE_KEY)) return; // explicit choice wins
|
|
} catch {
|
|
return;
|
|
}
|
|
const code = getTelegramLanguageCode();
|
|
if (code && SUPPORTED_LANGS.includes(code) && i18n.language?.split('-')[0] !== code) {
|
|
i18n.changeLanguage(code);
|
|
}
|
|
}
|
|
|
|
export default i18n;
|