From c9b4c8bd71029976fe900b40a2297b52200a216b Mon Sep 17 00:00:00 2001 From: "Max P." Date: Wed, 28 May 2025 18:09:04 +0200 Subject: [PATCH] feat(i18n): add i18n module for localization support - Introduce functions to initialize and load locale files dynamically - Add support for translation keys with placeholder replacements - Default to English if locale files are missing or not found - Determine system language using environment variables --- src/i18n/i18n.ts | 74 ++++++++++++++++++++++++++++++++++++++++++++++++ src/i18n/mod.ts | 1 + 2 files changed, 75 insertions(+) create mode 100644 src/i18n/i18n.ts create mode 100644 src/i18n/mod.ts diff --git a/src/i18n/i18n.ts b/src/i18n/i18n.ts new file mode 100644 index 0000000..7cce973 --- /dev/null +++ b/src/i18n/i18n.ts @@ -0,0 +1,74 @@ +/** + * Initializes the i18n module by loading + * the appropriate locale file based on the system language. + */ +export async function initI18n(): Promise { + await loadLocale(getCurrentLanguage()); +} + +/** + * Contains the loaded translations for the current language. + * The keys represent i18n identifiers, the values are the localized strings. + */ +let translations: Record = {}; + +/** + * Loads the translation file for the specified locale. + * + * Expects a JSON file in the same directory named like `de.json` or `en.json`. + * Falls back to English ('en') if the specified file does not exist. + * + * @param locale - The language code (e.g., 'de', 'en') to load + * @returns Promise that resolves once the translations have been loaded + */ +export async function loadLocale(locale: string): Promise { + try { + const localeUrl = new URL(`./${locale}.json`, import.meta.url); + const file = await Deno.readTextFile(localeUrl); + translations = JSON.parse(file); + } catch (err) { + if (err instanceof Deno.errors.NotFound) { + console.warn( + `Locale '${locale}' not found – falling back to 'en'.`, + ); + if (locale !== 'en') { + await loadLocale('en'); + } + } else { + console.error('Error loading translation file:', err); + } + } +} + +/** + * Translates a given key using the loaded translation data. + * Replaces placeholders in the format `{variable}` with the provided values. + * + * @param key - The i18n key (e.g., 'timer_created') + * @param vars - Optional replacements for placeholders in the string + * @returns The translated string, or the key itself if no translation is found + */ +export function t(key: string, vars: Record = {}): string { + let text = translations[key] ?? key; + for (const [k, v] of Object.entries(vars)) { + text = text.replace(`{${k}}`, v); + } + return text; +} + +/** + * Determines the current language from the system environment. + * + * Priority: SYSTEMD_TIMER_LANG > LC_ALL > LC_MESSAGES > LANG. + * + * @returns The language code (e.g., 'de', 'en'), defaults to 'en' + */ +export function getCurrentLanguage(): string { + return ( + Deno.env.get('SYSTEMD_TIMER_LANG') ?? + Deno.env.get('LC_ALL') ?? + Deno.env.get('LC_MESSAGES') ?? + Deno.env.get('LANG') ?? + 'en' + ).split('.')[0].split('_')[0].toLowerCase(); +} diff --git a/src/i18n/mod.ts b/src/i18n/mod.ts new file mode 100644 index 0000000..47d6a5a --- /dev/null +++ b/src/i18n/mod.ts @@ -0,0 +1 @@ +export { getCurrentLanguage, initI18n, loadLocale, t } from './i18n.ts';