import { Injectable, computed, signal } from '@angular/core'; import { DICTS, type Messages } from '@shared/i18n'; import type { AppSettings, SupportedLocale } from '@shared/types'; import { formatDate, formatSize, formatSpeed } from './format'; type Params = Record; function lookup(dict: Messages, key: string): string | Messages | undefined { let node: string | Messages | undefined = dict; for (const part of key.split('.')) { if (typeof node !== 'object') return undefined; node = node[part]; } return node; } function interpolate(template: string, params?: Params): string { if (!params) return template; return template.replace(/\{(\w+)\}/g, (_, name: string) => name in params ? String(params[name]) : `{${name}}`, ); } /** * Runtime translator. `t()` reads the `locale` signal, so any template binding * that calls it re-renders live on a language switch (the same reactive * function-call tracking the file-list icon classes rely on). Missing keys fall * back to English, then to the raw key so gaps are visible in dev. */ @Injectable({ providedIn: 'root' }) export class I18nService { readonly locale = signal('en'); private readonly dict = computed(() => DICTS[this.locale()]); t(key: string, params?: Params): string { let value = lookup(this.dict(), key) ?? lookup(DICTS.en, key); // plural map: pick the CLDR category for params.count (fallback to English) if (value && typeof value === 'object') { const count = Number(params?.['count'] ?? NaN); if (Number.isFinite(count)) { const rule = new Intl.PluralRules(this.locale()).select(count); const picked = value[rule] ?? value['other'] ?? lookup(DICTS.en, `${key}.other`); value = typeof picked === 'string' ? picked : undefined; } else { value = undefined; } } return typeof value === 'string' ? interpolate(value, params) : key; } // Locale-reactive formatting (live-updates the file list on switch). date(epochMs: number): string { return formatDate(epochMs, this.locale()); } size(bytes: number): string { return formatSize(bytes, this.locale()); } speed(bytesPerSec: number, unit: AppSettings['speedUnit']): string { return formatSpeed(bytesPerSec, unit, this.locale()); } }