This commit is contained in:
sebseb7
2025-12-21 03:46:50 +01:00
parent eab4241e6e
commit 5febdf29c8
11 changed files with 398 additions and 65 deletions

82
src/client/I18nContext.js Normal file
View File

@@ -0,0 +1,82 @@
import React, { createContext, useContext, useState, useCallback, useMemo } from 'react';
import en from './i18n/en.json';
import de from './i18n/de.json';
const translations = { en, de };
// Cookie helpers
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
return match ? match[2] : null;
}
function setCookie(name, value, days = 365) {
const expires = new Date(Date.now() + days * 864e5).toUTCString();
document.cookie = `${name}=${value}; expires=${expires}; path=/; SameSite=Lax`;
}
// Get initial language from cookie or browser preference
function getInitialLanguage() {
const cookieLang = getCookie('lang');
if (cookieLang && translations[cookieLang]) {
return cookieLang;
}
// Check browser preference
const browserLang = navigator.language?.slice(0, 2);
if (browserLang === 'de') return 'de';
return 'en';
}
const I18nContext = createContext(null);
export function I18nProvider({ children }) {
const [language, setLanguageState] = useState(getInitialLanguage);
const setLanguage = useCallback((lang) => {
if (translations[lang]) {
setLanguageState(lang);
setCookie('lang', lang);
}
}, []);
// Translation function with nested key support (e.g., 'app.title')
const t = useCallback((key, params = {}) => {
const keys = key.split('.');
let value = translations[language];
for (const k of keys) {
if (value && typeof value === 'object') {
value = value[k];
} else {
return key; // fallback to key if not found
}
}
if (typeof value !== 'string') return key;
// Replace {param} placeholders
return value.replace(/\{(\w+)\}/g, (_, param) =>
params[param] !== undefined ? params[param] : `{${param}}`
);
}, [language]);
const value = useMemo(() => ({
language,
setLanguage,
t
}), [language, setLanguage, t]);
return (
<I18nContext.Provider value={value}>
{children}
</I18nContext.Provider>
);
}
export function useI18n() {
const context = useContext(I18nContext);
if (!context) {
throw new Error('useI18n must be used within an I18nProvider');
}
return context;
}