import i18n from 'i18next'; import { globalStorageAdapter } from './setup'; /** * Changes the active language, saves the preference locally, and optionally syncs with the backend. * * @param newLng The new language code (e.g., 'en', 'id') * @param syncCallback An optional callback to sync the preference to the backend. It receives the new language and the previous language. */ export async function changeLanguage( newLng: string, syncCallback?: (newLng: string, prevLng: string) => Promise, ): Promise { const prevLng = i18n.language; if (prevLng === newLng) return; // 1. Update local storage & i18next optimistically if (globalStorageAdapter) { await globalStorageAdapter.setLanguage(newLng); } await i18n.changeLanguage(newLng); // 2. Trigger optional backend sync if (syncCallback) { try { await syncCallback(newLng, prevLng); } catch (error) { console.error('[i18n] Backend sync failed, rolling back language', error); // Rollback on failure if (globalStorageAdapter) { await globalStorageAdapter.setLanguage(prevLng); } await i18n.changeLanguage(prevLng); throw error; // Rethrow so the caller can show an error toast } } } /** * Injects tenant-specific vocabulary overrides dynamically at runtime. * * Uses a deep-merge strategy. Overrides are applied to the currently active language, * or across all loaded languages if needed. * * @param namespace The i18n namespace to override (e.g., 'common', 'booking') * @param overrides A deeply nested object containing the overridden string keys and values. * @param lng Specific language to override. Defaults to currently active language. */ export function applyTenantOverrides(namespace: string, overrides: Record, lng?: string): void { const targetLng = lng || i18n.language; // deep: true -> merges with existing keys rather than replacing the whole namespace // overwrite: true -> allows replacing existing specific keys i18n.addResourceBundle(targetLng, namespace, overrides, true, true); }