gohorsejobs/frontend/src/lib/i18n.tsx
2026-01-27 01:26:10 +00:00

116 lines
3.6 KiB
TypeScript

'use client';
import React, { createContext, useContext, useState, useCallback, ReactNode, useEffect } from 'react';
import en from '@/i18n/en.json';
import es from '@/i18n/es.json';
import ptBR from '@/i18n/pt-BR.json';
type Locale = 'en' | 'es' | 'pt-BR';
interface I18nContextType {
locale: Locale;
setLocale: (locale: Locale) => void;
t: (key: string, params?: Record<string, string | number>) => string;
}
const dictionaries: Record<Locale, typeof en> = {
en,
es,
'pt-BR': ptBR,
};
const I18nContext = createContext<I18nContextType | null>(null);
const localeStorageKey = 'locale';
const normalizeLocale = (language: string): Locale => {
if (language.startsWith('pt')) return 'pt-BR';
if (language.startsWith('es')) return 'es';
return 'en';
};
const getInitialLocale = (): Locale => {
if (typeof window === 'undefined') return 'en';
const storedLocale = localStorage.getItem(localeStorageKey);
if (storedLocale && storedLocale in dictionaries) {
return storedLocale as Locale;
}
// Default to English instead of browser language for consistency
return 'en';
};
export function I18nProvider({ children }: { children: ReactNode }) {
// FIX: Initialize with 'en' to match Server-Side Rendering (SSR)
// This prevents hydration mismatch errors.
const [locale, setLocaleState] = useState<Locale>('en');
const setLocale = (newLocale: Locale) => {
console.log(`[I18n] Setting locale to: ${newLocale}`);
setLocaleState(newLocale);
};
const t = useCallback((key: string, params?: Record<string, string | number>) => {
const keys = key.split('.');
let value: unknown = dictionaries[locale];
for (const k of keys) {
if (value && typeof value === 'object' && value !== null && k in value) {
value = (value as Record<string, unknown>)[k];
} else {
return key; // Return key if not found
}
}
if (typeof value !== 'string') return key;
// Replace parameters like {count}, {time}, {year}
if (params) {
return Object.entries(params).reduce(
(str, [paramKey, paramValue]) => str.replace(`{${paramKey}}`, String(paramValue)),
value
);
}
return value;
}, [locale]);
// Sync from localStorage on mount (Client-side only)
useEffect(() => {
const stored = getInitialLocale();
if (stored !== 'en') {
console.log('[I18n] Restoring locale from storage on client:', stored);
setLocaleState(stored);
}
}, []);
useEffect(() => {
if (typeof window === 'undefined') return;
localStorage.setItem(localeStorageKey, locale);
document.documentElement.lang = locale;
}, [locale]);
return (
<I18nContext.Provider value={{ locale, setLocale, t }}>
{children}
</I18nContext.Provider>
);
}
export function useI18n() {
const context = useContext(I18nContext);
if (!context) {
throw new Error('useI18n must be used within an I18nProvider');
}
return context;
}
export function useTranslation() {
const { t, locale, setLocale } = useI18n();
return { t, locale, setLocale };
}
export const locales: { code: Locale; name: string; flag: string }[] = [
{ code: 'en', name: 'English', flag: '🇺🇸' },
{ code: 'es', name: 'Español', flag: '🇪🇸' },
{ code: 'pt-BR', name: 'Português', flag: '🇧🇷' },
];