import { DEFAULT_LANGUAGE } from '../lib/constants'; import type { LanguageCode } from '../types/runtime'; import type { Namespace } from './constants'; import { detectSystemLanguage, resolveSupportedLanguage } from './resolver'; import { messages, type MessageTree } from './messages'; type InterpolationParams = Record; function lookupMessage(source: MessageTree, path: string): unknown { return path.split('.').reduce((current, segment) => { if (!current || typeof current !== 'object') return undefined; return (current as Record)[segment]; }, source); } function interpolate(template: string, params?: InterpolationParams): string { if (!params) return template; return template.replace(/\{(\w+)\}/g, (_match, token) => { const value = params[token]; return typeof value === 'undefined' ? `{${token}}` : String(value); }); } export interface I18nBridge { getLocale(): LanguageCode; setLocale(locale: string | null | undefined): LanguageCode; t(path: string, params?: InterpolationParams): string; has(path: string): boolean; getMessages(locale?: LanguageCode): MessageTree; subscribe(listener: (locale: LanguageCode) => void): () => void; } export function createI18nBridge(initialLocale?: string | null): I18nBridge { let locale = resolveSupportedLanguage(initialLocale ?? detectSystemLanguage() ?? DEFAULT_LANGUAGE); const listeners = new Set<(locale: LanguageCode) => void>(); const notify = () => { for (const listener of listeners) { listener(locale); } }; const api: I18nBridge = { getLocale() { return locale; }, setLocale(nextLocale: string | null | undefined) { const resolved = resolveSupportedLanguage(nextLocale, locale); if (resolved !== locale) { locale = resolved; notify(); } return locale; }, t(path: string, params?: InterpolationParams) { const localeMessages = messages[locale] ?? messages[DEFAULT_LANGUAGE]; const fallbackMessages = messages[DEFAULT_LANGUAGE]; const translated = lookupMessage(localeMessages, path) ?? lookupMessage(fallbackMessages, path); if (typeof translated === 'string' || typeof translated === 'number') { return interpolate(String(translated), params); } return path; }, has(path: string) { const localeMessages = messages[locale] ?? messages[DEFAULT_LANGUAGE]; const fallbackMessages = messages[DEFAULT_LANGUAGE]; return typeof lookupMessage(localeMessages, path) !== 'undefined' || typeof lookupMessage(fallbackMessages, path) !== 'undefined'; }, getMessages(targetLocale?: LanguageCode) { return messages[targetLocale ?? locale] ?? messages[DEFAULT_LANGUAGE]; }, subscribe(listener: (locale: LanguageCode) => void) { listeners.add(listener); return () => listeners.delete(listener); }, }; return api; } export const i18n = createI18nBridge(); export function setLocale(locale: string | null | undefined): LanguageCode { return i18n.setLocale(locale); } export function getLocale(): LanguageCode { return i18n.getLocale(); } export function t(path: string, params?: InterpolationParams): string { return i18n.t(path, params); } export function hasMessage(path: string): boolean { return i18n.has(path); } export function getMessages(locale?: LanguageCode): MessageTree { return i18n.getMessages(locale); } export { SUPPORTED_LANGUAGE_CODES, SUPPORTED_LANGUAGES, NAMESPACES } from './constants'; export type { LanguageCode, Namespace };