initial commit
This commit is contained in:
77
app/hooks/useI18n.ts
Normal file
77
app/hooks/useI18n.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user