78 lines
2.6 KiB
TypeScript
78 lines
2.6 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
import de from "@/locales/de.json";
|
|
import en from "@/locales/en.json";
|
|
import es from "@/locales/es.json";
|
|
import fr from "@/locales/fr.json";
|
|
import it from "@/locales/it.json";
|
|
import sv from "@/locales/sv.json";
|
|
|
|
const bundles = { en, es, fr, de, it, sv } as const;
|
|
|
|
export type Locale = keyof typeof bundles;
|
|
export type MessageKey = string;
|
|
|
|
export type LocaleOption = {
|
|
code: Locale;
|
|
name: string;
|
|
emoji: string;
|
|
};
|
|
|
|
const localeOptions: LocaleOption[] = [
|
|
{ code: "en", name: "English", emoji: "🇺🇸" },
|
|
{ code: "es", name: "Español", emoji: "🇪🇸" },
|
|
{ code: "fr", name: "Français", emoji: "🇫🇷" },
|
|
{ code: "de", name: "Deutsch", emoji: "🇩🇪" },
|
|
{ code: "it", name: "Italiano", emoji: "🇮🇹" },
|
|
{ code: "sv", name: "Svenska", emoji: "🇸🇪" },
|
|
];
|
|
|
|
export function useI18n(defaultLocale: Locale = "en") {
|
|
const [locale, setLocale] = useState<Locale>(defaultLocale);
|
|
const hasDetectedLocale = useRef(false);
|
|
|
|
useEffect(() => {
|
|
if (hasDetectedLocale.current) return;
|
|
hasDetectedLocale.current = true;
|
|
const navLangs =
|
|
typeof navigator !== "undefined" ? navigator.languages || [navigator.language] : [];
|
|
const mapToLocale = (lang: string | undefined | null): Locale | null => {
|
|
if (!lang) return null;
|
|
const lower = lang.toLowerCase();
|
|
if (lower.startsWith("es")) return "es";
|
|
if (lower.startsWith("fr")) return "fr";
|
|
if (lower.startsWith("de")) return "de";
|
|
if (lower.startsWith("it")) return "it";
|
|
if (lower.startsWith("sv")) return "sv";
|
|
if (lower.startsWith("en")) return "en";
|
|
return null;
|
|
};
|
|
const detected = navLangs.map((l) => mapToLocale(l)).find(Boolean);
|
|
if (detected && detected !== locale) {
|
|
setLocale(detected);
|
|
}
|
|
}, [locale]);
|
|
|
|
const t = useCallback(
|
|
(key: MessageKey, vars?: Record<string, string>): string => {
|
|
const parts = key.split(".");
|
|
const dict = bundles[locale] ?? bundles.en;
|
|
const fallbackDict = bundles.en;
|
|
const resolveKey = (source: unknown) =>
|
|
parts.reduce<unknown>(
|
|
(acc, part) =>
|
|
acc && typeof acc === "object" ? (acc as Record<string, unknown>)[part] : undefined,
|
|
source
|
|
);
|
|
const value = resolveKey(dict) ?? resolveKey(fallbackDict);
|
|
const text = typeof value === "string" ? value : key;
|
|
if (!vars) return text;
|
|
return text.replace(/\{(\w+)\}/g, (_: string, k: string) => (vars[k] ? vars[k] : `{${k}}`));
|
|
},
|
|
[locale]
|
|
);
|
|
|
|
const options = useMemo(() => localeOptions, []);
|
|
|
|
return { locale, setLocale, t, localeOptions: options };
|
|
}
|