frontend_nyla/src/providers/language/LanguageContext.tsx
2026-04-11 22:23:35 +02:00

154 lines
5.1 KiB
TypeScript

import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback } from 'react';
import { Language, TranslationKeys, loadLanguage, fetchAvailableLanguageCodes, I18nCodeInfo } from '../../locales';
import { getUserDataCache } from '../../utils/userCache';
export type { Language };
type TranslateParams = Record<string, string | number | boolean | null | undefined>;
interface LanguageContextType {
currentLanguage: Language;
setLanguage: (language: Language) => void;
t: (key: string, params?: TranslateParams) => string;
isLoading: boolean;
reloadLanguage: () => Promise<void>;
availableLanguages: I18nCodeInfo[];
refreshAvailableLanguages: () => Promise<void>;
}
const LanguageContext = createContext<LanguageContextType | undefined>(undefined);
interface LanguageProviderProps {
children: ReactNode;
}
export const LanguageProvider: React.FC<LanguageProviderProps> = ({ children }) => {
const [currentLanguage, setCurrentLanguage] = useState<Language>('de');
const [translations, setTranslations] = useState<TranslationKeys>({});
const [isLoading, setIsLoading] = useState(true);
const [availableLanguages, setAvailableLanguages] = useState<I18nCodeInfo[]>([]);
const loadAndSetLanguage = async (language: Language) => {
setIsLoading(true);
try {
const targetKeys = await loadLanguage(language);
setTranslations(targetKeys);
setCurrentLanguage(language);
} catch (error) {
console.error('Failed to load language:', error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
const initializeLanguage = async () => {
let initialLanguage: Language = 'de';
const userData = getUserDataCache();
if (userData?.language && String(userData.language).trim()) {
initialLanguage = String(userData.language).trim() as Language;
await loadAndSetLanguage(initialLanguage);
return;
}
const browserLang = navigator.language.split('-')[0] as Language;
try {
const codes = await fetchAvailableLanguageCodes();
const codeSet = new Set(codes.map((c) => c.code));
if (codeSet.has(browserLang) && browserLang !== 'xx') {
initialLanguage = browserLang;
}
} catch {
// keep default
}
await loadAndSetLanguage(initialLanguage);
};
initializeLanguage();
const handleUserUpdate = async () => {
const userData = getUserDataCache();
if (userData?.language && String(userData.language).trim()) {
const userLanguage = String(userData.language).trim() as Language;
if (userLanguage !== currentLanguage) {
loadAndSetLanguage(userLanguage);
}
}
try {
const list = await fetchAvailableLanguageCodes();
setAvailableLanguages(list.filter((l) => l.code !== 'xx'));
} catch {
// silent
}
};
window.addEventListener('userInfoUpdated', handleUserUpdate);
return () => {
window.removeEventListener('userInfoUpdated', handleUserUpdate);
};
}, []);
const setLanguage = async (language: Language) => {
await loadAndSetLanguage(language);
};
const reloadLanguage = async () => {
await loadAndSetLanguage(currentLanguage);
};
const refreshAvailableLanguages = useCallback(async () => {
try {
const list = await fetchAvailableLanguageCodes();
setAvailableLanguages(list.filter((l) => l.code !== 'xx'));
} catch (e) {
console.error('Failed to load language codes:', e);
}
}, []);
useEffect(() => {
refreshAvailableLanguages();
}, [refreshAvailableLanguages]);
const _applyParams = (template: string, params?: TranslateParams): string => {
if (!params) return template;
let out = template;
for (const [paramKey, rawVal] of Object.entries(params)) {
if (rawVal === undefined || rawVal === null) continue;
out = out.split(`{${paramKey}}`).join(String(rawVal));
}
return out;
};
const t = (key: string, params?: TranslateParams): string => {
const resolved = translations[key] ?? `[${key}]`;
return _applyParams(resolved, params);
};
const contextValue: LanguageContextType = {
currentLanguage,
setLanguage,
t,
isLoading,
reloadLanguage,
availableLanguages,
refreshAvailableLanguages,
};
return (
<LanguageContext.Provider value={contextValue}>
{children}
</LanguageContext.Provider>
);
};
export const useLanguage = (): LanguageContextType => {
const context = useContext(LanguageContext);
if (!context) {
throw new Error('useLanguage must be used within a LanguageProvider');
}
return context;
};