feat(i18n): Complete localization of marketing frontend with react-i18next, prefixed URLs, JSON migrations, and automation

This commit is contained in:
Codex Agent
2025-10-03 13:05:13 +02:00
parent 1845d83583
commit 60f8de9162
46 changed files with 3454 additions and 590 deletions

View File

@@ -0,0 +1,68 @@
import { useEffect, useState } from 'react';
type Appearance = 'light' | 'dark' | 'system';
export function useAppearance(): { appearance: Appearance; updateAppearance: (mode: Appearance) => void } {
const [appearance, setAppearance] = useState<Appearance>('system');
useEffect(() => {
const stored = localStorage.getItem('theme') as Appearance | null;
if (stored) {
setAppearance(stored);
} else {
setAppearance('system');
}
}, []);
const updateAppearance = (mode: Appearance) => {
setAppearance(mode);
localStorage.setItem('theme', mode);
if (mode === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
};
useEffect(() => {
if (appearance === 'system') {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (mediaQuery.matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
const listener = (e: MediaQueryListEvent) => {
if (e.matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
};
mediaQuery.addEventListener('change', listener);
return () => mediaQuery.removeEventListener('change', listener);
} else if (appearance === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}, [appearance]);
return { appearance, updateAppearance };
}
export function initializeTheme() {
const stored = localStorage.getItem('theme') as Appearance | null;
if (stored) {
if (stored === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
} else {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (mediaQuery.matches) {
document.documentElement.classList.add('dark');
}
}
}