import type {Language} from './languages.js'; // Static imports for all translation files import enTranslations from './locales/en.json' with {type: 'json'}; import nlTranslations from './locales/nl.json' with {type: 'json'}; import frTranslations from './locales/fr.json' with {type: 'json'}; import deTranslations from './locales/de.json' with {type: 'json'}; import hiTranslations from './locales/hi.json' with {type: 'json'}; import ptTranslations from './locales/pt.json' with {type: 'json'}; import bgTranslations from './locales/bg.json' with {type: 'json'}; import csTranslations from './locales/cs.json' with {type: 'json'}; import plTranslations from './locales/pl.json' with {type: 'json'}; import esTranslations from './locales/es.json' with {type: 'json'}; export { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE, isValidLanguageCode, getLanguageByCode, type Language, } from './languages.js'; export type TranslationKey = string; export interface Translations { pages: { unsubscribe: Record; subscribe: Record; manage: Record; common: Record; }; email: { footer: Record; }; } // Static mapping of language codes to translations const translationsMap: Record = { en: enTranslations, nl: nlTranslations, de: deTranslations, fr: frTranslations, hi: hiTranslations, pt: ptTranslations, bg: bgTranslations, cs: csTranslations, pl: plTranslations, es: esTranslations, }; // In-memory cache for loaded translations const translationCache = new Map(); /** * Load translations for a given language code (async) * Uses static imports for reliable resolution */ export async function loadTranslations(languageCode: string): Promise { // Check cache first if (translationCache.has(languageCode)) { return translationCache.get(languageCode)!; } // Get translations from static map const translations = translationsMap[languageCode]; if (translations) { translationCache.set(languageCode, translations); return translations; } // Fallback to English if language not found console.warn(`Translation file not found for ${languageCode}, falling back to English`); if (languageCode !== 'en') { return loadTranslations('en'); } throw new Error(`Failed to load translations for ${languageCode}`); } /** * Synchronous translation loader (requires pre-built translations) * For API server use (compiled at build time) */ export function loadTranslationsSync(languageCode: string): Translations { if (translationCache.has(languageCode)) { return translationCache.get(languageCode)!; } // Get translations from static map const translations = translationsMap[languageCode]; if (translations) { translationCache.set(languageCode, translations); return translations; } // Fallback to English if language not found if (languageCode !== 'en') { return loadTranslationsSync('en'); } throw new Error(`Failed to load translations for ${languageCode}`); } /** * Simple template string replacement * Example: interpolate("Hello {name}", { name: "World" }) => "Hello World" */ export function interpolate(template: string, values: Record): string { return template.replace(/\{(\w+)\}/g, (_, key) => values[key] || `{${key}}`); } /** * Get nested translation value by dot notation path * Example: getTranslation(translations, "pages.unsubscribe.title") */ export function getTranslation(translations: Translations, path: string): string { const keys = path.split('.'); // eslint-disable-next-line @typescript-eslint/no-explicit-any let value: any = translations; for (const key of keys) { value = value?.[key]; if (value === undefined) { console.warn(`Translation key not found: ${path}`); return path; // Return the key itself as fallback } } return typeof value === 'string' ? value : path; } /** * Translation helper class for easier use */ export class Translator { private translations: Translations; private languageCode: string; constructor(translations: Translations, languageCode: string) { this.translations = translations; this.languageCode = languageCode; } /** * Translate a key with optional interpolation * Example: t("pages.unsubscribe.description", { email: "user@example.com" }) */ t(key: string, values?: Record): string { const translation = getTranslation(this.translations, key); return values ? interpolate(translation, values) : translation; } getLanguageCode(): string { return this.languageCode; } } /** * Create a translator instance for a given language (async) */ export async function createTranslator(languageCode: string): Promise { const translations = await loadTranslations(languageCode); return new Translator(translations, languageCode); } /** * Create a translator instance for a given language (sync) */ export function createTranslatorSync(languageCode: string): Translator { const translations = loadTranslationsSync(languageCode); return new Translator(translations, languageCode); }