"use client" import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react" import enMessages from "../../messages/en/common.json" import skMessages from "../../messages/sk/common.json" import esMessages from "../../messages/es/common.json" import frMessages from "../../messages/fr/common.json" import deMessages from "../../messages/de/common.json" import itMessages from "../../messages/it/common.json" import ptMessages from "../../messages/pt/common.json" import { DEFAULT_LANGUAGE, LANGUAGE_STORAGE_KEY, type LanguageCode, SUPPORTED_LANGUAGES, detectBrowserLanguage, isSupportedLanguage, } from "./languages" type MessageTree = Record type TranslationParams = Record const MESSAGE_CATALOG: Record = { en: enMessages as MessageTree, sk: skMessages as MessageTree, es: esMessages as MessageTree, fr: frMessages as MessageTree, de: deMessages as MessageTree, it: itMessages as MessageTree, pt: ptMessages as MessageTree, } interface I18nContextValue { language: LanguageCode setLanguage: (language: LanguageCode) => void t: (key: string, params?: TranslationParams) => string } const I18nContext = createContext(null) function getInitialLanguage(): LanguageCode { if (typeof window === "undefined") return DEFAULT_LANGUAGE try { const stored = window.localStorage.getItem(LANGUAGE_STORAGE_KEY) if (isSupportedLanguage(stored)) return stored } catch { // localStorage may be unavailable in private browsing. } return detectBrowserLanguage() } function getMessage(messages: MessageTree, key: string): string | undefined { const value = key.split(".").reduce((cursor, segment) => { if (!cursor || typeof cursor !== "object") return undefined return (cursor as Record)[segment] }, messages) return typeof value === "string" ? value : undefined } function interpolate(template: string, params?: TranslationParams): string { if (!params) return template return template.replace(/\{(\w+)\}/g, (match, name) => { const value = params[name] return value === undefined ? match : String(value) }) } export function I18nProvider({ children }: { children: React.ReactNode }) { const [language, setLanguageState] = useState(DEFAULT_LANGUAGE) const [isHydrated, setIsHydrated] = useState(false) useEffect(() => { setLanguageState(getInitialLanguage()) setIsHydrated(true) }, []) useEffect(() => { if (!isHydrated) return document.documentElement.lang = language try { window.localStorage.setItem(LANGUAGE_STORAGE_KEY, language) } catch { // Best-effort; the in-memory language still works for this session. } }, [isHydrated, language]) useEffect(() => { const onStorage = (event: StorageEvent) => { if (event.key === LANGUAGE_STORAGE_KEY && isSupportedLanguage(event.newValue)) { setLanguageState(event.newValue) } } window.addEventListener("storage", onStorage) return () => window.removeEventListener("storage", onStorage) }, []) const setLanguage = useCallback((nextLanguage: LanguageCode) => { setLanguageState(nextLanguage) }, []) const t = useCallback( (key: string, params?: TranslationParams) => { const localized = getMessage(MESSAGE_CATALOG[language], key) const fallback = getMessage(MESSAGE_CATALOG.en, key) return interpolate(localized ?? fallback ?? key, params) }, [language], ) const value = useMemo(() => ({ language, setLanguage, t }), [language, setLanguage, t]) return {children} } export function useI18n() { const context = useContext(I18nContext) if (!context) { throw new Error("useI18n must be used within I18nProvider") } return context } export function useT() { return useI18n().t } export { SUPPORTED_LANGUAGES }