Add video
This commit is contained in:
parent
589de0c5ac
commit
7c36cbfdd4
18 changed files with 2292 additions and 333 deletions
|
|
@ -1,56 +1,124 @@
|
|||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import en from './locales/en';
|
||||
import de from './locales/de';
|
||||
import fr from './locales/fr';
|
||||
import hu from './locales/hu';
|
||||
import zh from './locales/zh';
|
||||
|
||||
export const SUPPORTED_LANGUAGES = [
|
||||
{ code: 'en', label: 'English', flag: '\uD83C\uDDEC\uD83C\uDDE7' },
|
||||
{ code: 'fr', label: 'Fran\u00E7ais', flag: '\uD83C\uDDEB\uD83C\uDDF7' },
|
||||
{ code: 'de', label: 'Deutsch', flag: '\uD83C\uDDE9\uD83C\uDDEA' },
|
||||
{ code: 'hu', label: 'Magyar', flag: '\uD83C\uDDED\uD83C\uDDFA' },
|
||||
{ code: 'zh', label: '\u4E2D\u6587', flag: '\uD83C\uDDE8\uD83C\uDDF3' },
|
||||
{ code: 'hi', label: '\u0939\u093F\u0928\u094D\u0926\u0940', flag: '\uD83C\uDDEE\uD83C\uDDF3' },
|
||||
{ code: 'hu', label: 'Magyar', flag: '\uD83C\uDDED\uD83C\uDDFA' },
|
||||
] as const;
|
||||
|
||||
export type LanguageCode = (typeof SUPPORTED_LANGUAGES)[number]['code'];
|
||||
|
||||
const supportedCodes: Set<string> = new Set(SUPPORTED_LANGUAGES.map((l) => l.code));
|
||||
|
||||
function detectLanguage(): string {
|
||||
function toSupportedLanguage(value: string): LanguageCode | null {
|
||||
const lower = value.toLowerCase();
|
||||
if (supportedCodes.has(lower)) return lower as LanguageCode;
|
||||
|
||||
const prefix = lower.split('-')[0];
|
||||
if (supportedCodes.has(prefix)) return prefix as LanguageCode;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getStoredLanguage(): LanguageCode | null {
|
||||
try {
|
||||
const stored = localStorage.getItem('language');
|
||||
return stored ? toSupportedLanguage(stored) : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getBrowserLanguages(): readonly string[] {
|
||||
if (typeof navigator === 'undefined') return [];
|
||||
return navigator.languages?.length ? navigator.languages : [navigator.language];
|
||||
}
|
||||
|
||||
function detectLanguage(): LanguageCode {
|
||||
// 1. Explicit user choice (persisted from the language dropdown)
|
||||
const stored = localStorage.getItem('language');
|
||||
if (stored && supportedCodes.has(stored)) return stored;
|
||||
const stored = getStoredLanguage();
|
||||
if (stored) return stored;
|
||||
|
||||
// 2. Browser preference (navigator.languages falls back to navigator.language)
|
||||
for (const tag of navigator.languages ?? [navigator.language]) {
|
||||
// Match full tag first (e.g. "zh-CN" → "zh"), then just the prefix
|
||||
const lower = tag.toLowerCase();
|
||||
if (supportedCodes.has(lower)) return lower;
|
||||
const prefix = lower.split('-')[0];
|
||||
if (supportedCodes.has(prefix)) return prefix;
|
||||
for (const tag of getBrowserLanguages()) {
|
||||
const language = toSupportedLanguage(tag);
|
||||
if (language) return language;
|
||||
}
|
||||
|
||||
return 'en';
|
||||
}
|
||||
|
||||
const initialLang = detectLanguage();
|
||||
export const INITIAL_LANGUAGE = detectLanguage();
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
type TranslationResource = Record<string, unknown>;
|
||||
|
||||
const localeLoaders: Record<
|
||||
Exclude<LanguageCode, 'en'>,
|
||||
() => Promise<{ default: TranslationResource }>
|
||||
> = {
|
||||
fr: () => import('./locales/fr'),
|
||||
de: () => import('./locales/de'),
|
||||
hu: () => import('./locales/hu'),
|
||||
zh: () => import('./locales/zh'),
|
||||
hi: () => import('./locales/hi'),
|
||||
};
|
||||
|
||||
async function getLanguageResource(
|
||||
code: Exclude<LanguageCode, 'en'>
|
||||
): Promise<TranslationResource> {
|
||||
const module = await localeLoaders[code]();
|
||||
return module.default;
|
||||
}
|
||||
|
||||
function setDocumentLanguage(code: LanguageCode) {
|
||||
if (typeof document !== 'undefined') {
|
||||
document.documentElement.lang = code;
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadLanguage(code: LanguageCode): Promise<void> {
|
||||
if (code === 'en' || i18n.hasResourceBundle(code, 'translation')) return;
|
||||
const resource = await getLanguageResource(code);
|
||||
i18n.addResourceBundle(code, 'translation', resource, true, true);
|
||||
}
|
||||
|
||||
export async function changeLanguage(code: LanguageCode): Promise<void> {
|
||||
await loadLanguage(code);
|
||||
await i18n.changeLanguage(code);
|
||||
setDocumentLanguage(code);
|
||||
}
|
||||
|
||||
export const i18nReady = (async () => {
|
||||
const resources: Record<string, { translation: TranslationResource }> = {
|
||||
en: { translation: en },
|
||||
fr: { translation: fr },
|
||||
de: { translation: de },
|
||||
hu: { translation: hu },
|
||||
zh: { translation: zh },
|
||||
},
|
||||
lng: initialLang,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false, // React already escapes
|
||||
},
|
||||
});
|
||||
};
|
||||
let language = INITIAL_LANGUAGE;
|
||||
|
||||
if (language !== 'en') {
|
||||
try {
|
||||
resources[language] = { translation: await getLanguageResource(language) };
|
||||
} catch (error) {
|
||||
console.error(`Failed to load ${language} translations`, error);
|
||||
language = 'en';
|
||||
}
|
||||
}
|
||||
|
||||
await i18n.use(initReactI18next).init({
|
||||
resources,
|
||||
lng: language,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false, // React already escapes
|
||||
},
|
||||
});
|
||||
|
||||
setDocumentLanguage(language);
|
||||
})();
|
||||
|
||||
/**
|
||||
* Translate a key that is computed at runtime (not a literal).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue