ngx-kit 8.5.0 → 9.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ngx-kit-lazy-i18n.mjs","sources":["../../../projects/libs/lazy-i18n/types/types.ts","../../../projects/libs/lazy-i18n/utils/utils.ts","../../../projects/libs/lazy-i18n/services/i18n.service.ts","../../../projects/libs/lazy-i18n/pipes/pipe.ts","../../../projects/libs/lazy-i18n/directives/directives.ts","../../../projects/libs/lazy-i18n/loader/http-loader.ts","../../../projects/libs/lazy-i18n/providers/provide-i18n.ts","../../../projects/libs/lazy-i18n/providers/routing.ts","../../../projects/libs/lazy-i18n/signals/signals.ts","../../../projects/libs/lazy-i18n/public-api.ts","../../../projects/libs/lazy-i18n/ngx-kit-lazy-i18n.ts"],"sourcesContent":["import { InjectionToken, Type } from '@angular/core';\nimport type { Observable } from 'rxjs';\n\n/** A (possibly nested) translation file as returned by the server. */\nexport interface Dictionary {\n [key: string]: string | number | boolean | null | Dictionary | Array<unknown>;\n}\nexport type Params = Record<string, unknown>;\nexport type TranslateFn = (key: string, params?: Params) => string;\nexport type MissingHandler = (key: string, lang: string) => string;\nexport type Transpiler = (value: string, params: Params | undefined, lang: string) => string;\n\nexport interface I18nLoader {\n /** `scope === undefined` means the global file. */\n load(lang: string, scope?: string): Observable<Dictionary> | Promise<Dictionary>;\n}\n\n/**\n * Every option is optional so the library can be provided at startup and completed later\n * (for example after your own APP_INITIALIZER has fetched the language list from the backend):\n * see `I18nService.init()` / `I18nService.configure()`.\n */\nexport interface I18nConfig {\n /** All supported languages. Required before the first language is activated. */\n langs?: string[];\n /** Language used when nothing is stored or detected. Required before the first language is activated. */\n defaultLang?: string;\n /** Looked up (in order) when a key is empty/missing in the active language. */\n fallbackLangs?: string[];\n /** Default `'key'` (returns the key itself). */\n missing?: 'key' | 'empty' | MissingHandler;\n /** Log each missing key once. Default: `isDevMode()`. */\n warnMissing?: boolean;\n /** Replace the default `{{ name }}` interpolation (ICU, plurals, ...). */\n transpile?: Transpiler;\n /** Custom loader class (must be `@Injectable({providedIn:'root'})` or constructible without args). Default: HTTP. Provide-time only. */\n loader?: Type<I18nLoader>;\n /** Default HTTP loader: base URL. Default `'/i18n'` -> `/i18n/fa.json`, `/i18n/setting/fa.json`. Slashes are normalised. */\n baseUrl?: string;\n /** Default HTTP loader: file extension appended to the URL, e.g. `'.json'`. Default `'.json'`; use `''` for none. */\n fileExtension?: string;\n /** Default HTTP loader: full control over the URL. */\n url?: (lang: string, scope?: string) => string;\n /** Retries per failed request. Default `1`. */\n retries?: number;\n /** localStorage key used to remember the language. `null` disables. Default `'lazy-i18n.lang'`. */\n storageKey?: string | null;\n /** Pick the initial language from `navigator.languages` when nothing is stored. Default `false`. */\n detectBrowserLang?: boolean;\n /** Keep `<html lang dir>` in sync. Default `true`. */\n manageDocument?: boolean;\n /**\n * Activate the initial language in an APP_INITIALIZER created by `provideI18n()`.\n * Default: `true` when both `langs` and `defaultLang` are given to `provideI18n()`, otherwise `false`\n * (then you call `I18nService.init()` yourself, typically from your own initializer).\n * Provide-time only.\n */\n autoInit?: boolean;\n}\n\n/** Options that can be changed after the app has started. */\nexport type I18nRuntimeConfig = Omit<I18nConfig, 'loader' | 'autoInit'>;\n\n/** `I18nService.init()` options: runtime config + the language to start with. */\nexport interface I18nInitOptions extends I18nRuntimeConfig {\n /** Preferred starting language (e.g. the user's saved language from the backend). Ignored if not in `langs`. */\n lang?: string;\n}\n\n/** Defaults applied by `provideI18n()`. */\nexport const I18N_DEFAULTS: I18nConfig = { fileExtension: '.json', retries: 1 };\n\nexport const I18N_CONFIG = new InjectionToken<I18nConfig>('I18N_CONFIG');\nexport const I18N_LOADER = new InjectionToken<I18nLoader>('I18N_LOADER');\n","import type { Dictionary, Params } from '../types/types';\n\n/** Flatten a nested dictionary into `target` (dot-separated keys). Non-string leaves are stringified. */\nexport function flatten(\n dict: Dictionary,\n target: Map<string, string> = new Map(),\n onConflict?: (key: string) => void,\n prefix = '',\n): Map<string, string> {\n for (const k of Object.keys(dict)) {\n const v = (dict as Record<string, unknown>)[k];\n const key = prefix ? `${prefix}.${k}` : k;\n if (v !== null && typeof v === 'object') {\n flatten(v as Dictionary, target, onConflict, key);\n } else if (v !== null && v !== undefined) {\n const s = String(v);\n if (onConflict) {\n const prev = target.get(key);\n if (prev !== undefined && prev !== '' && s !== '' && prev !== s) onConflict(key);\n }\n target.set(key, s);\n }\n }\n return target;\n}\n\nconst RE = /\\{\\{\\s*([\\w$.-]+)\\s*\\}\\}/g;\n\nfunction getPath(obj: Params, path: string): unknown {\n if (path in obj) return obj[path];\n let cur: any = obj;\n for (const part of path.split('.')) {\n if (cur == null) return undefined;\n cur = cur[part];\n }\n return cur;\n}\n\n/** `Hello {{ name }}` + `{name:'Ali'}` -> `Hello Ali`. Unknown placeholders are left untouched. */\nexport function interpolate(text: string, params?: Params): string {\n if (!params || text.indexOf('{{') === -1) return text; // fast path: no regex\n return text.replace(RE, (m, k: string) => {\n const v = getPath(params, k);\n return v == null ? m : String(v);\n });\n}\n\nexport function shallowEqual(a?: Params, b?: Params): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n const ka = Object.keys(a);\n if (ka.length !== Object.keys(b).length) return false;\n for (const k of ka) if (a[k] !== b[k]) return false;\n return true;\n}\n\nexport const RTL_LANGS = new Set(['ar', 'fa', 'he', 'ur', 'ps', 'sd', 'ug', 'yi', 'ckb', 'dv']);\n\n/** Identity function the extractor recognises: `const k = marker('orders.title')`. */\nexport function marker<T extends string>(key: T): T {\n return key;\n}\n\n/** Copy only the properties of `patch` that are not `undefined` (so an omitted option never wipes a default). */\nexport function assignDefined<T extends object>(target: T, patch: Partial<T>): T {\n for (const k of Object.keys(patch) as Array<keyof T>) {\n if (patch[k] !== undefined) target[k] = patch[k] as T[keyof T];\n }\n return target;\n}\n\n/**\n * Build `{base}/{scope}/{lang}{ext}` without ever producing `undefined`, `//` or a trailing slash problem:\n * (\"/i18n\", undefined, \"fa\", \".json\") -> \"/i18n/fa.json\"\n * (\"https://x.com/\", \"setting\", \"fa\", \"json\") -> \"https://x.com/setting/fa.json\"\n * (undefined | \"\", undefined, \"fa\") -> \"/fa\"\n * The protocol part of `base` (\"https://\") is left alone.\n */\nexport function buildI18nUrl(\n base: string | null | undefined,\n scope: string | null | undefined,\n lang: string,\n ext: string | null | undefined = '',\n): string {\n const trim = (s: string) => s.trim().replace(/^\\/+|\\/+$/g, '');\n const e = ext ? (ext.startsWith('.') ? ext : '.' + ext) : '';\n const path = [scope ? trim(scope) : '', trim(lang) + e]\n .filter(Boolean)\n .join('/')\n .replace(/\\/{2,}/g, '/');\n return `${(base ?? '').trim().replace(/\\/+$/, '')}/${path}`;\n}\n","import { DOCUMENT } from '@angular/common';\nimport { Injectable, Signal, computed, inject, isDevMode, signal } from '@angular/core';\nimport { defer, firstValueFrom, from, retry } from 'rxjs';\nimport {\n I18N_CONFIG,\n I18N_LOADER,\n type Dictionary,\n type I18nInitOptions,\n type I18nRuntimeConfig,\n type Params,\n} from '../types/types';\nimport { RTL_LANGS, assignDefined, flatten, interpolate } from '../utils/utils';\n\nconst TAG = '[lazy-i18n]';\nconst EMPTY: readonly string[] = [];\n\n@Injectable({ providedIn: 'root' })\nexport class I18nService {\n private readonly cfg = inject(I18N_CONFIG);\n private readonly loader = inject(I18N_LOADER);\n private readonly doc = inject(DOCUMENT);\n\n private readonly _lang = signal(this.cfg.defaultLang ?? '');\n private readonly _rev = signal(0);\n private readonly _langs = signal<readonly string[]>(this.cfg.langs ?? EMPTY);\n private readonly _ready = signal(false);\n /** Active language (reactive). */\n readonly lang = this._lang.asReadonly();\n /** Bumps whenever the active language or any dictionary changes. */\n readonly revision = this._rev.asReadonly();\n readonly dir = computed<'rtl' | 'ltr'>(() =>\n RTL_LANGS.has(this._lang().split('-')[0]) ? 'rtl' : 'ltr',\n );\n /** Supported languages (a signal: they may be supplied after startup via `init()` / `configure()`). */\n readonly langs = this._langs.asReadonly();\n /** `true` once the first language has been activated. Handy for a splash screen. */\n readonly ready = this._ready.asReadonly();\n\n // read live, so values supplied later through configure() are honoured\n private get fallbacks(): readonly string[] {\n return this.cfg.fallbackLangs ?? EMPTY;\n }\n private get warn(): boolean {\n return this.cfg.warnMissing ?? isDevMode();\n }\n private readonly dicts = new Map<string, Map<string, string>>(); // lang -> flat map\n private readonly loaded = new Set<string>(); // `${lang}|${scope}`\n private readonly inflight = new Map<string, Promise<void>>();\n private readonly scopes = new Set<string>();\n private readonly pending = new Set<Promise<void>>();\n private readonly warned = new Set<string>();\n private useToken = 0;\n\n // ───────────────────────── translate ─────────────────────────\n\n /** Translate. Reads signals, so it is reactive inside `computed`, `effect` and templates. */\n t(key: string, params?: Params): string {\n this._rev();\n return this.resolve(key, params, this._lang());\n }\n\n /** Same as `t`, but never creates a reactive dependency. */\n instant(key: string, params?: Params): string {\n return this.resolve(key, params, this._lang());\n }\n\n /** `computed` signal for one key. `params` may be a function reading other signals. */\n select(key: string, params?: Params | (() => Params)): Signal<string> {\n return computed(() => this.t(key, typeof params === 'function' ? params() : params));\n }\n\n /** True when the key has a non-empty value in the active language or a fallback. */\n has(key: string): boolean {\n this._rev();\n return this.lookup(key, this._lang()) !== undefined;\n }\n\n /** Returns every key below `prefix` as a nested object (values are raw, not interpolated). */\n getObject(prefix: string): Dictionary {\n this._rev();\n const out: Dictionary = {};\n const p = prefix + '.';\n for (const lang of [this._lang(), ...this.fallbacks]) {\n for (const [k, v] of this.dicts.get(lang) ?? []) {\n if (!k.startsWith(p) || !v) continue;\n const parts = k.slice(p.length).split('.');\n let cur: any = out;\n for (let i = 0; i < parts.length - 1; i++)\n cur = typeof cur[parts[i]] === 'object' ? cur[parts[i]] : (cur[parts[i]] = {});\n if (!(parts[parts.length - 1] in cur)) cur[parts[parts.length - 1]] = v;\n }\n }\n return out;\n }\n\n private lookup(key: string, lang: string): string | undefined {\n let raw = this.dicts.get(lang)?.get(key);\n if (raw) return raw;\n for (const fb of this.fallbacks) {\n if (fb === lang) continue;\n raw = this.dicts.get(fb)?.get(key);\n if (raw) return raw;\n }\n return undefined; // '' (extractor placeholder) counts as missing -> falls back\n }\n\n private resolve(key: string, params: Params | undefined, lang: string): string {\n const raw = this.lookup(key, lang);\n if (raw === undefined) return this.onMissing(key, lang);\n return this.cfg.transpile ? this.cfg.transpile(raw, params, lang) : interpolate(raw, params);\n }\n\n private onMissing(key: string, lang: string): string {\n if (this.warn && this.dicts.has(lang) && !this.warned.has(key)) {\n this.warned.add(key);\n console.warn(`${TAG} Missing translation \"${key}\" (lang: ${lang})`);\n }\n const h = this.cfg.missing ?? 'key';\n return typeof h === 'function' ? h(key, lang) : h === 'empty' ? '' : key;\n }\n\n // ───────────────────────── language ─────────────────────────\n\n /**\n * Load global + all registered scopes (and fallback languages) for `lang`, THEN activate it.\n * Nothing renders half-translated and no \"missing\" flash occurs.\n */\n use(lang: string): Promise<void> {\n const p = this.activate(lang);\n // tracked so whenReady() / i18nReadyGuard also wait for an in-flight language change\n const tracked: Promise<void> = p\n .then(\n () => undefined,\n () => undefined,\n )\n .then(() => {\n this.pending.delete(tracked);\n });\n this.pending.add(tracked);\n return p;\n }\n\n /**\n * Complete the configuration and activate the first language in one call. Use it from your own\n * APP_INITIALIZER when the language list is only known at runtime (e.g. from the backend):\n *\n * ```ts\n * await i18n.init({ langs: ['fa', 'en'], defaultLang: 'fa', baseUrl: apiUrl, lang: user.lang });\n * ```\n * `lang` is used when it is in `langs`; otherwise stored > browser (opt-in) > `defaultLang`.\n * Rejects when the files cannot be loaded, so the initializer can show an error page.\n */\n init(options: I18nInitOptions = {}): Promise<void> {\n const { lang, ...patch } = options;\n this.configure(patch);\n const wanted =\n lang && (this.cfg.langs ?? EMPTY).includes(lang) ? lang : this.detectInitialLang();\n return this.use(wanted);\n }\n\n /**\n * Update options after startup (languages, baseUrl, fallbacks, ...). Already downloaded files stay cached.\n * `loader` and `autoInit` can only be set in `provideI18n()`.\n */\n configure(patch: I18nRuntimeConfig): void {\n assignDefined(this.cfg, patch);\n this.syncLangs();\n if (patch.defaultLang && !this._ready()) this._lang.set(patch.defaultLang);\n }\n\n private syncLangs(): void {\n const l = this.cfg.langs ?? EMPTY;\n if (l !== this._langs()) this._langs.set(l);\n }\n\n private async activate(lang: string): Promise<void> {\n this.syncLangs();\n const langs = this.cfg.langs ?? EMPTY;\n if (!langs.length) {\n throw new Error(\n `${TAG} No languages configured. Pass \"langs\" to provideI18n() or call I18nService.init({ langs, defaultLang }) first.`,\n );\n }\n if (!langs.includes(lang)) throw new Error(`${TAG} Unsupported language \"${lang}\"`);\n const token = ++this.useToken;\n do {\n await this.loadLang(lang);\n } while (!this.isComplete(lang)); // a scope may have been registered meanwhile\n if (token !== this.useToken) return; // a newer use() won\n this._lang.set(lang);\n this._rev.update((v) => v + 1);\n this._ready.set(true);\n this.persist(lang);\n this.applyDocument(lang);\n }\n\n /** Download a language without activating it. */\n preload(lang: string): Promise<void> {\n return this.loadLang(lang);\n }\n\n /** Language to start with: stored > browser (opt-in) > default. */\n detectInitialLang(): string {\n const langs = this.cfg.langs ?? EMPTY;\n const key = this.storageKey();\n try {\n const s = key ? localStorage.getItem(key) : null;\n if (s && langs.includes(s)) return s;\n } catch {\n /* storage unavailable (SSR / privacy mode) */\n }\n if (this.cfg.detectBrowserLang && typeof navigator !== 'undefined') {\n for (const n of navigator.languages ?? [navigator.language]) {\n if (langs.includes(n)) return n;\n const base = n.split('-')[0];\n if (langs.includes(base)) return base;\n }\n }\n const def = this.cfg.defaultLang ?? '';\n return langs.includes(def) ? def : (langs[0] ?? def);\n }\n\n // ───────────────────────── scopes ─────────────────────────\n\n /** Register a scope and start loading it for the active language. Idempotent. Used by `provideI18nScope`. */\n registerScope(scope: string): void {\n if (!scope) return;\n this.scopes.add(scope);\n // before the first activation only remember it: init()/use() loads every registered scope\n if (!this._ready()) return;\n const lang = this._lang();\n const p: Promise<void> = Promise.all(\n [lang, ...this.fallbacks.filter((f) => f !== lang)].map((l) =>\n this.fetch(l, scope).catch((e) => {\n console.error(`${TAG} Failed to load scope \"${scope}\" (${l})`, e);\n }),\n ),\n ).then(() => {\n this.pending.delete(p);\n });\n this.pending.add(p);\n }\n\n /** Resolves once every scope registered so far is available. Never rejects. */\n async whenReady(): Promise<void> {\n while (this.pending.size) await Promise.all([...this.pending]);\n }\n\n // ───────────────────────── data ─────────────────────────\n\n /** Manually set translations (tests, SSR transfer state, ...). */\n setTranslation(lang: string, dict: Dictionary, merge = true): void {\n if (!merge) this.dicts.delete(lang);\n this.merge(lang, dict);\n }\n\n private merge(lang: string, dict: Dictionary): void {\n let m = this.dicts.get(lang);\n if (!m) this.dicts.set(lang, (m = new Map()));\n flatten(\n dict,\n m,\n this.warn\n ? (k) => console.warn(`${TAG} Key \"${k}\" is defined twice with different values (${lang})`)\n : undefined,\n );\n this._rev.update((v) => v + 1);\n }\n\n private fetch(lang: string, scope?: string): Promise<void> {\n scope = scope || undefined; // never hand '' / null to a loader\n const id = `${lang}|${scope ?? ''}`;\n if (this.loaded.has(id)) return Promise.resolve();\n let p = this.inflight.get(id);\n if (!p) {\n p = firstValueFrom(\n defer(() => from(this.loader.load(lang, scope))).pipe(\n retry({ count: this.cfg.retries ?? 1, delay: 250 }),\n ),\n )\n .then((d) => {\n this.merge(lang, d);\n this.loaded.add(id);\n })\n .finally(() => this.inflight.delete(id));\n this.inflight.set(id, p);\n }\n return p;\n }\n\n private loadLang(lang: string): Promise<void> {\n const scopes: Array<string | undefined> = [undefined, ...this.scopes];\n const main = scopes.map((s) => this.fetch(lang, s));\n const fb = this.fallbacks\n .filter((f) => f !== lang)\n .flatMap((f) =>\n scopes.map((s) =>\n this.fetch(f, s).catch((e) => console.error(`${TAG} Fallback \"${f}\" failed`, e)),\n ),\n );\n return Promise.all([...main, ...fb]).then(() => undefined);\n }\n\n private isComplete(lang: string): boolean {\n return (\n this.loaded.has(`${lang}|`) && [...this.scopes].every((s) => this.loaded.has(`${lang}|${s}`))\n );\n }\n\n private storageKey(): string | null {\n return this.cfg.storageKey === undefined ? 'lazy-i18n.lang' : this.cfg.storageKey;\n }\n\n private persist(lang: string): void {\n const key = this.storageKey();\n try {\n if (key) localStorage.setItem(key, lang);\n } catch {\n /* ignore */\n }\n }\n\n private applyDocument(lang: string): void {\n if (this.cfg.manageDocument === false) return;\n const el = this.doc?.documentElement;\n if (!el) return;\n el.setAttribute('lang', lang);\n el.setAttribute('dir', this.dir());\n }\n}\n","import { Pipe, PipeTransform, inject } from '@angular/core';\nimport { I18nService } from '../services/i18n.service';\nimport type { Params } from '../types/types';\nimport { shallowEqual } from '../utils/utils';\n\n/**\n * `{{ 'orders.title' | tr }}` / `{{ 'hello' | tr: { name } }}`\n *\n * Impure on purpose (it must follow signals), but memoised: when key, params and the\n * service revision are unchanged it returns the cached string in O(1) with no lookup.\n */\n@Pipe({ name: 'tr', pure: false })\nexport class TrPipe implements PipeTransform {\n private readonly svc = inject(I18nService);\n private k?: string;\n private p?: Params;\n private rev = -1;\n private out = '';\n\n transform(key: string | null | undefined, params?: Params): string {\n if (!key) return '';\n const rev = this.svc.revision(); // tracked: marks the view dirty on lang / dictionary change\n if (rev === this.rev && key === this.k && shallowEqual(params, this.p)) return this.out;\n this.k = key;\n this.p = params;\n this.rev = rev;\n return (this.out = this.svc.t(key, params));\n }\n}\n","import {\n Directive, ElementRef, Input, Renderer2, Signal, TemplateRef, ViewContainerRef, effect, inject, signal,\n} from '@angular/core';\nimport { I18nService } from '../services/i18n.service';\nimport type { Params, TranslateFn } from '../types/types';\n\nexport interface TrContext {\n $implicit: TranslateFn;\n t: TranslateFn;\n lang: Signal<string>;\n}\n\n/**\n * Structural directive. The view is created ONCE; `t` reads signals, so only the bindings that use it update.\n *\n * <ng-container *tr=\"let t; prefix: 'orders'\">\n * <h1>{{ t('title') }}</h1> <!-- orders.title -->\n * </ng-container>\n */\n@Directive({ selector: '[tr],[translate]' })\nexport class TrDirective {\n private readonly tpl = inject<TemplateRef<TrContext>>(TemplateRef);\n private readonly vcr = inject(ViewContainerRef);\n private readonly svc = inject(I18nService);\n private readonly prefix = signal<string | undefined>(undefined);\n /** `prefix: 'orders'` in the microsyntax. */\n @Input('trPrefix') set trPrefix(v: string | undefined) { this.prefix.set(v); }\n\n static ngTemplateContextGuard(_dir: TrDirective, _ctx: unknown): _ctx is TrContext {\n return true;\n }\n\n constructor() {\n const t: TranslateFn = (key, params) => {\n const p = this.prefix();\n return this.svc.t(p ? p + '.' + key : key, params);\n };\n this.vcr.createEmbeddedView(this.tpl, { $implicit: t, t, lang: this.svc.lang });\n }\n}\n\n/**\n * Attribute directive.\n * <span trKey=\"orders.title\"></span>\n * <input trKey=\"form.name\" trAttr=\"placeholder\" />\n * <p trKey=\"hello\" [trParams]=\"{ name: user.name }\"></p>\n */\n@Directive({ selector: '[trKey]' })\nexport class TrKeyDirective {\n // @Input setters feeding signals: works with JIT, AOT and every supported Angular version.\n private readonly key = signal<string | undefined>(undefined);\n private readonly params = signal<Params | undefined>(undefined);\n private readonly attr = signal<string | undefined>(undefined);\n private readonly prefix = signal<string | undefined>(undefined);\n @Input('trKey') set trKey(v: string | undefined) { this.key.set(v); }\n @Input('trParams') set trParams(v: Params | undefined) { this.params.set(v); }\n @Input('trAttr') set trAttr(v: string | undefined) { this.attr.set(v); }\n @Input('trPrefix') set trPrefix(v: string | undefined) { this.prefix.set(v); }\n\n constructor() {\n const svc = inject(I18nService);\n const el = inject<ElementRef<HTMLElement>>(ElementRef).nativeElement;\n const r = inject(Renderer2);\n effect(() => {\n const k = this.key();\n if (!k) return;\n const p = this.prefix();\n const text = svc.t(p ? p + '.' + k : k, this.params());\n const a = this.attr();\n if (a) r.setAttribute(el, a, text);\n else if (el.textContent !== text) el.textContent = text;\n });\n }\n}\n","import { HttpClient } from '@angular/common/http';\nimport { Injectable, inject } from '@angular/core';\nimport type { Observable } from 'rxjs';\nimport { I18N_CONFIG, I18N_DEFAULTS, type Dictionary, type I18nLoader } from '../types/types';\nimport { buildI18nUrl } from '../utils/utils';\n\n/**\n * Default loader: GET `{baseUrl}/{lang}{ext}` and `{baseUrl}/{scope}/{lang}{ext}`.\n * Goes through your HttpClient interceptors.\n * Config is read on every request, so values supplied later via `I18nService.init()` /\n * `configure()` (for example `baseUrl`) are picked up.\n */\n@Injectable({ providedIn: 'root' })\nexport class HttpI18nLoader implements I18nLoader {\n private readonly http = inject(HttpClient);\n private readonly cfg = inject(I18N_CONFIG);\n\n load(lang: string, scope?: string): Observable<Dictionary> {\n const { url, baseUrl, fileExtension } = this.cfg;\n const u = url\n ? url(lang, scope)\n : buildI18nUrl(baseUrl ?? '/i18n', scope, lang, fileExtension ?? I18N_DEFAULTS.fileExtension);\n return this.http.get<Dictionary>(u);\n }\n}\n","import {\n EnvironmentProviders,\n inject,\n makeEnvironmentProviders,\n provideAppInitializer,\n} from '@angular/core';\nimport { HttpI18nLoader } from '../loader/http-loader';\nimport { I18nService } from '../services/i18n.service';\nimport { I18N_CONFIG, I18N_DEFAULTS, I18N_LOADER, I18nConfig } from '../types/types';\nimport { assignDefined } from '../utils/utils';\n\n/**\n * Static configuration (everything known at build time):\n * ```ts\n * provideHttpClient(),\n * provideI18n({ langs: ['fa', 'en'], defaultLang: 'fa', fallbackLangs: ['en'] })\n * ```\n * The global dictionary of the initial language is then loaded before the app starts.\n *\n * Deferred configuration (languages come from your backend): omit `langs`/`defaultLang`\n * and call `await inject(I18nService).init({ langs, defaultLang, baseUrl, lang })`\n * from your own APP_INITIALIZER once you know them:\n * ```ts\n * provideI18n({ fallbackLangs: ['fa'] }), // no automatic initializer\n * provideAppInitializer(() => AppInitializer()), // calls i18n.init(...)\n * ```\n * Pass `autoInit: false` to force this mode even when `langs` is given.\n */\nexport function provideI18n(config: I18nConfig = {}): EnvironmentProviders {\n const autoInit = config.autoInit ?? (!!config.langs?.length && !!config.defaultLang);\n return makeEnvironmentProviders([\n // a fresh, mutable object per injector (the service updates it through configure())\n { provide: I18N_CONFIG, useFactory: () => assignDefined({ ...I18N_DEFAULTS }, config) },\n ...(config.loader ? [config.loader] : []),\n { provide: I18N_LOADER, useFactory: () => inject(config.loader ?? HttpI18nLoader) },\n ...(autoInit\n ? [\n provideAppInitializer(() => {\n const svc = inject(I18nService);\n return svc.use(svc.detectInitialLang());\n }),\n ]\n : []),\n ]);\n}\n","import { EnvironmentProviders, inject, makeEnvironmentProviders, provideEnvironmentInitializer } from '@angular/core';\nimport type { CanActivateChildFn } from '@angular/router';\nimport { I18nService } from '../services/i18n.service';\n\n/**\n * Put in `route.providers`. Runs when the route injector is created (before guards),\n * so loading starts as early as possible. Child routes need nothing.\n */\nexport function provideI18nScope(scope: string | string[]): EnvironmentProviders {\n const scopes = Array.isArray(scope) ? scope : [scope];\n return makeEnvironmentProviders([\n provideEnvironmentInitializer(() => {\n const svc = inject(I18nService);\n for (const s of scopes) svc.registerScope(s);\n }),\n ]);\n}\n\n/** Put ONCE on the root path-less route: `canActivateChild: [i18nReadyGuard]`. Waits for every registered scope. */\nexport const i18nReadyGuard: CanActivateChildFn = async () => {\n await inject(I18nService).whenReady();\n return true;\n};\n","import { Signal, assertInInjectionContext, inject } from '@angular/core';\nimport { I18nService } from '../services/i18n.service';\nimport type { Params, TranslateFn } from '../types/types';\n\n/** `readonly title = translateSignal('orders.title')` (injection context) */\nexport function translateSignal(key: string, params?: Params | (() => Params)): Signal<string> {\n assertInInjectionContext(translateSignal);\n return inject(I18nService).select(key, params);\n}\n\n/** `const t = injectT(); computed(() => t('a.b'))` — reactive translate function. */\nexport function injectT(): TranslateFn {\n assertInInjectionContext(injectT);\n const svc = inject(I18nService);\n return (key, params) => svc.t(key, params);\n}\n","import { TrPipe } from './pipes/pipe';\nimport { TrDirective, TrKeyDirective } from './directives/directives';\n\nexport * from './types/types';\nexport { buildI18nUrl, flatten, interpolate, marker } from './utils/utils';\nexport { HttpI18nLoader } from './loader/http-loader';\nexport { I18nService } from './services/i18n.service';\nexport { provideI18n } from './providers/provide-i18n';\nexport { provideI18nScope, i18nReadyGuard } from './providers/routing';\nexport { translateSignal, injectT } from './signals/signals';\nexport { TrPipe } from './pipes/pipe';\nexport { TrDirective, TrKeyDirective, type TrContext } from './directives/directives';\n\n/** `imports: [TR]` brings the pipe and both directives into a standalone component. */\nexport const TR = [TrPipe, TrDirective, TrKeyDirective] as const;\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,521 @@
1
+ # ngx-kit/lazy-i18n
2
+
3
+ Tiny, signal-based, **lazy-scoped** i18n for Angular.
4
+ Flat keys, route-level scopes, pipe, directives, fallbacks and a key extractor CLI
5
+
6
+
7
+ - **Route-level scopes, no key prefix.** A module's translations are loaded when its route is created and are used as plain keys (`Request`, not `setting.Request`).
8
+ - **Routes wait for their translations.** One guard on the root route makes navigation resolve only after every scope on the path has loaded. No "missing translation" flashes.
9
+ - **Signals first.** `t()`, `select()`, `translateSignal()` are reactive in `computed`, `effect` and templates. Works zoneless and with `OnPush`.
10
+ - **Fast.** Dictionaries are flattened once into a `Map`, so a lookup is O(1). The pipe is memoised, the structural directive creates its view once, and interpolation skips the regex when a string has no `{{`.
11
+ - **Complete.** Pipe, structural and attribute directives, fallback languages, missing-key handling, custom transpiler (ICU, plurals), custom loaders, RTL/`<html lang dir>` handling, and a **key extractor CLI** that generates your JSON files per scope.
12
+ - Angular 19+ (standalone APIs, `provideAppInitializer`, `provideEnvironmentInitializer`). ESM only. Zero runtime dependencies besides `tslib`/`rxjs`.
13
+
14
+ ---
15
+
16
+ ## Contents
17
+
18
+ 1. [Install](#install)
19
+ 2. [Quick start](#quick-start)
20
+ 3. [Deferred configuration (languages from the backend)](#deferred-configuration-languages-from-the-backend)
21
+ 4. [Concepts](#concepts)
22
+ 5. [Using translations](#using-translations)
23
+ 6. [Scopes and routing](#scopes-and-routing)
24
+ 7. [Language switching](#language-switching)
25
+ 8. [Fallbacks and missing keys](#fallbacks-and-missing-keys)
26
+ 9. [Interpolation, plurals and ICU](#interpolation-plurals-and-icu)
27
+ 10. [Loaders](#loaders)
28
+ 11. [Key extractor CLI](#key-extractor-cli)
29
+ 12. [Configuration reference](#configuration-reference)
30
+ 13. [API reference](#api-reference)
31
+ 14. [Testing your app](#testing-your-app)
32
+ 15. [Performance notes](#performance-notes)
33
+ 16. [Migrating from Transloco](#migrating-from-transloco)
34
+ 17. [Limitations](#limitations)
35
+ 18. [Developing this library](#developing-this-library)
36
+
37
+ ---
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ npm i ngx-kit
43
+ ```
44
+
45
+
46
+ ## Quick start
47
+
48
+ **1. Provide it** (`app.config.ts`)
49
+
50
+ ```ts
51
+ import { provideHttpClient } from '@angular/common/http';
52
+ import { provideRouter } from '@angular/router';
53
+ import { provideI18n } from 'ngx-kit/lazy-i18n';
54
+
55
+ export const appConfig: ApplicationConfig = {
56
+ providers: [
57
+ provideRouter(routes),
58
+ provideHttpClient(),
59
+ provideI18n({
60
+ langs: ['fa', 'en'],
61
+ defaultLang: 'fa',
62
+ fallbackLangs: ['en'],
63
+ baseUrl: '/i18n', // GET /i18n/fa and GET /i18n/setting/fa
64
+ }),
65
+ ],
66
+ };
67
+ ```
68
+
69
+ The global file of the initial language is loaded **before** the app bootstraps.
70
+
71
+ **2. Declare scopes on routes and add the guard once** (`app.routes.ts`)
72
+
73
+ ```ts
74
+ import { i18nReadyGuard, provideI18nScope } from 'ngx-kit/lazy-i18n';
75
+
76
+ export const routes: Routes = [
77
+ {
78
+ path: '',
79
+ canActivateChild: [i18nReadyGuard], // once, on the root path-less route
80
+ children: [
81
+ {
82
+ path: 'setting',
83
+ providers: [provideI18nScope('setting')], // loads /i18n/setting/{lang}
84
+ loadChildren: () => import('./setting/setting.routes'),
85
+ },
86
+ {
87
+ path: 'orders',
88
+ providers: [provideI18nScope(['orders', 'shared-grid'])], // several scopes are fine
89
+ loadComponent: () => import('./orders/orders.page'),
90
+ },
91
+ ],
92
+ },
93
+ ];
94
+ ```
95
+
96
+ Child routes of `setting` need **nothing**. Components never mention a scope.
97
+
98
+ **3. Translate**
99
+
100
+ ```ts
101
+ import { Component } from '@angular/core';
102
+ import { TR, translateSignal } from 'ngx-kit/lazy-i18n';
103
+
104
+ @Component({
105
+ selector: 'app-requests',
106
+ imports: [TR], // pipe + both directives
107
+ template: `
108
+ <h1>{{ 'Request' | tr }}</h1>
109
+ <p>{{ 'welcome' | tr: { name: user.name } }}</p>
110
+ <input trKey="Search" trAttr="placeholder" />
111
+ <h2>{{ title() }}</h2>
112
+ `,
113
+ })
114
+ export class RequestsComponent {
115
+ user = { name: 'Ali' };
116
+ title = translateSignal('Request');
117
+ }
118
+ ```
119
+
120
+ ## Deferred configuration (languages from the backend)
121
+
122
+ Often the list of languages, the default language and the user's saved language only exist **after** your own
123
+ `APP_INITIALIZER` has talked to the backend. Do not try to build the config earlier (no `setTimeout`, no mutating
124
+ the config token, no async `useFactory`): provide the library **without** languages and finish the setup with
125
+ `I18nService.init()` when you know them.
126
+
127
+ ```ts
128
+ // app.config.ts
129
+ provideHttpClient(),
130
+ provideI18n({ fallbackLangs: ['fa'] }), // no langs/defaultLang -> no automatic initializer
131
+ provideAppInitializer(() => AppInitializer()),
132
+ ```
133
+
134
+ ```ts
135
+ // app-initializer.ts
136
+ export async function AppInitializer(): Promise<void> {
137
+ const http = inject(HttpClient);
138
+ const platformLocation = inject(PlatformLocation);
139
+ const i18n = inject(I18nService);
140
+ inject(ColorSchemaService).load();
141
+
142
+ // 1) appconfig.json 2) /api/Common/InitialApp (as you do today)
143
+ const config = await firstValueFrom(http.get<any>(configUrl(platformLocation)));
144
+ AppConst.apiUrl = config.apiUrl; /* … */
145
+ const resp = await firstValueFrom(http.get<InitialAppDto>(AppConst.apiUrl + '/api/Common/InitialApp'));
146
+ AppConst.languageList = resp.languages ?? [];
147
+ AppConst.defaultLang = resp.defaultLang ?? 'fa';
148
+
149
+ // 3) finish the i18n setup and load the first language. AWAIT it: the app starts when translations are ready.
150
+ await i18n.init({
151
+ langs: AppConst.languageList.map((l) => l.culture),
152
+ defaultLang: AppConst.defaultLang,
153
+ baseUrl: AppConst.apiUrl, // read at request time, so a value known only now is fine
154
+ lang: AppConst.currentLanguage, // optional: user's saved language; ignored if not in `langs`
155
+ });
156
+ }
157
+ ```
158
+
159
+ What `init()` does: merges the options into the live config (`configure()`), picks the language
160
+ (`lang` if supported, otherwise stored > browser (opt-in) > `defaultLang`), downloads the global file, every scope
161
+ registered so far and the fallback languages, and only then activates the language. It rejects if loading fails, so
162
+ you can catch it in the initializer and show an error page.
163
+
164
+ Notes:
165
+
166
+ - `loader` and `autoInit` can only be set in `provideI18n()`. Everything else can be changed later with
167
+ `i18n.configure({...})`; files that are already downloaded stay cached.
168
+ - When `langs` **and** `defaultLang` are given to `provideI18n()`, an initializer is added automatically
169
+ (`autoInit` defaults to `true`). Use `autoInit: false` if you prefer to call `init()` yourself.
170
+ - `i18n.langs` is a **signal** (`i18n.langs()`) because the list can arrive after startup. `i18n.ready()` becomes
171
+ `true` after the first language is active.
172
+ - Scopes registered before `init()` (for example by an early service) are remembered and loaded by `init()`.
173
+ `whenReady()` and `i18nReadyGuard` also wait for an in-flight `init()` / `use()`.
174
+ - Do not call `use()` without awaiting it inside an initializer: the app would start before the files arrive.
175
+
176
+ ## Concepts
177
+
178
+ | Term | Meaning |
179
+ |---|---|
180
+ | **Global file** | `GET {baseUrl}/{lang}`, loaded at startup. |
181
+ | **Scope file** | `GET {baseUrl}/{scope}/{lang}`, loaded when a route registers the scope. |
182
+ | **Flat namespace** | All files are merged at the **root** of one language dictionary. Keys never carry the scope name. |
183
+ | **Registered scope** | A scope that any route has requested. Registered scopes stay registered, so they are also loaded when the language changes. |
184
+ | **Revision** | A counter signal that changes whenever the language or any dictionary changes. Pipe and directives depend on it. |
185
+
186
+ Because the namespace is flat, **two scopes must not define the same key with different values**. In dev mode a warning is logged when that happens. Put shared words (`Save`, `Cancel`) in the global file.
187
+
188
+ ## Using translations
189
+
190
+ ### Pipe
191
+
192
+ ```html
193
+ {{ 'orders.title' | tr }}
194
+ {{ 'hello' | tr: { name: user.name } }}
195
+ <button [title]="'common.save' | tr">…</button>
196
+ ```
197
+
198
+ ### Structural directive
199
+
200
+ The view is created once; `t` is a signal-aware function, so only the bindings that call it update.
201
+
202
+ ```html
203
+ <ng-container *tr="let t; prefix: 'orders'">
204
+ <h1>{{ t('title') }}</h1> <!-- orders.title -->
205
+ <p>{{ t('count', { n: items.length }) }}</p>
206
+ </ng-container>
207
+ ```
208
+
209
+ Context: `$implicit` / `t` (the translate function) and `lang` (a `Signal<string>`).
210
+
211
+ ### Attribute directive
212
+
213
+ ```html
214
+ <span trKey="orders.title"></span>
215
+ <input trKey="form.name" trAttr="placeholder" />
216
+ <p trKey="hello" [trParams]="{ name: user.name }"></p>
217
+ <span trKey="title" trPrefix="orders"></span>
218
+ ```
219
+
220
+ `trAttr` sets an attribute instead of the text content.
221
+
222
+ ### Signals
223
+
224
+ ```ts
225
+ import { computed, inject } from '@angular/core';
226
+ import { I18nService, injectT, translateSignal } from 'ngx-kit/lazy-i18n';
227
+
228
+ class Demo {
229
+ private i18n = inject(I18nService);
230
+ title = translateSignal('orders.title');
231
+ hello = translateSignal('hello', () => ({ name: this.name() })); // params may read signals
232
+ t = injectT();
233
+ label = computed(() => this.t('orders.count', { n: this.n() }));
234
+ lang = this.i18n.lang; // Signal<string>
235
+ dir = this.i18n.dir; // Signal<'rtl' | 'ltr'>
236
+ }
237
+ ```
238
+
239
+ ### Service
240
+
241
+ ```ts
242
+ i18n.t('key', params?) // reactive when read inside computed/effect/template
243
+ i18n.instant('key', params?) // same lookup, never creates a reactive dependency
244
+ i18n.select('key', params?) // Signal<string>
245
+ i18n.has('key') // true if non-empty in active lang or a fallback
246
+ i18n.getObject('orders') // nested object of everything under "orders."
247
+ ```
248
+
249
+ ## Scopes and routing
250
+
251
+ `provideI18nScope('x')` is an environment initializer. Angular creates a route's injector while it recognises the URL, which happens **before guards run**, so loading starts as early as possible. `i18nReadyGuard` then waits until every registered scope has loaded and lets the navigation continue. Lazy-loaded children behave the same way.
252
+
253
+ Rules of thumb:
254
+
255
+ - Put `i18nReadyGuard` **once** on the root path-less route as `canActivateChild`.
256
+ - Put `provideI18nScope(...)` on the route that owns the module.
257
+ - Without the guard, scopes still load, but a component may render before the files arrive. Use `await i18n.whenReady()` if you must translate imperatively at startup.
258
+ - For a scope not tied to a route (a dialog library, for example) call `i18n.registerScope('name')` and `await i18n.whenReady()`.
259
+ - A failed scope request is retried (`retries`, default 1), then logged. It never blocks navigation forever.
260
+
261
+ ## Language switching
262
+
263
+ ```ts
264
+ await i18n.use('en');
265
+ ```
266
+
267
+ `use()` downloads the global file, **all registered scopes** and the fallback languages first, and only then switches `lang`. Nothing flashes untranslated. Calling `use()` repeatedly is safe: the last call wins. The choice is stored in `localStorage` (`storageKey`) and `<html lang dir>` is updated.
268
+
269
+ `i18n.preload('en')` downloads a language without activating it.
270
+
271
+ ## Fallbacks and missing keys
272
+
273
+ ```ts
274
+ provideI18n({
275
+ langs: ['fa', 'en'], defaultLang: 'fa',
276
+ fallbackLangs: ['en'], // looked up in order
277
+ missing: (key, lang) => `⚠ ${key}`, // 'key' (default) | 'empty' | function
278
+ warnMissing: true, // console.warn once per key (default: dev mode)
279
+ });
280
+ ```
281
+
282
+ A value of `''` counts as **missing**, so the placeholders written by the extractor fall back to the next language automatically.
283
+
284
+ ## Interpolation, plurals and ICU
285
+
286
+ Default syntax: `Hello {{ name }}`, nested paths (`{{ user.name }}`) are supported, unknown placeholders are left untouched.
287
+
288
+ Plug in anything else with `transpile`:
289
+
290
+ ```ts
291
+ provideI18n({
292
+ …,
293
+ transpile: (value, params, lang) => {
294
+ // example: tiny plural helper "{n, plural, one {# item} other {# items}}"
295
+ return value.replace(/\{(\w+), plural, one \{([^}]*)\} other \{([^}]*)\}\}/g, (_, k, one, other) => {
296
+ const n = Number(params?.[k] ?? 0);
297
+ const form = new Intl.PluralRules(lang).select(n) === 'one' ? one : other;
298
+ return form.replace('#', String(n));
299
+ });
300
+ },
301
+ });
302
+ ```
303
+
304
+ Use a real ICU library (for example `intl-messageformat`) inside `transpile` for full ICU support, and cache compiled messages there.
305
+
306
+ ## Loaders
307
+
308
+ The default `HttpI18nLoader` uses `HttpClient`, so your interceptors (auth, base URL, retry) apply.
309
+
310
+ URLs are built as `{baseUrl}/{scope}/{lang}{fileExtension}` (global file: `{baseUrl}/{lang}{fileExtension}`).
311
+ The builder is safe by construction: a missing scope never becomes `undefined`, repeated or trailing slashes
312
+ are collapsed (`'https://api.x.com/'` + no scope -> `https://api.x.com/fa.json`), and the extension may be written
313
+ with or without the dot. `buildI18nUrl()` is exported if you need the same logic in a custom loader.
314
+
315
+ ```ts
316
+ provideI18n({
317
+ …,
318
+ baseUrl: '/api/translations', // /api/translations/fa, /api/translations/setting/fa
319
+ fileExtension: '.json', // static files: /assets/i18n/fa.json
320
+ // or full control:
321
+ url: (lang, scope) => `/api/i18n?lang=${lang}${scope ? `&scope=${scope}` : ''}`,
322
+ });
323
+ ```
324
+
325
+ Custom loader (anything that returns a nested or flat object, as `Observable` or `Promise`):
326
+
327
+ ```ts
328
+ @Injectable({ providedIn: 'root' })
329
+ export class MyLoader implements I18nLoader {
330
+ load(lang: string, scope?: string) {
331
+ return fetch(`/x/${scope ?? '_global'}/${lang}`).then((r) => r.json());
332
+ }
333
+ }
334
+ provideI18n({ …, loader: MyLoader });
335
+ ```
336
+
337
+ Nested JSON (`{ "a": { "b": "x" } }`) and flat JSON (`{ "a.b": "x" }`) are both accepted.
338
+
339
+ ## Key extractor CLI
340
+
341
+ Generates and updates your JSON files from the source code, **per scope**.
342
+
343
+ ```bash
344
+ npx ngx-lazy-i18n-extract # reads ./i18n.config.json
345
+ npx ngx-lazy-i18n-extract --check # CI: exit 1 if a used key is missing from the files
346
+ npx ngx-lazy-i18n-extract --remove # also delete keys no longer used
347
+ npx ngx-lazy-i18n-extract --langs fa,en --output src/assets/i18n --input src
348
+ ```
349
+
350
+ `i18n.config.json`:
351
+
352
+ ```json
353
+ {
354
+ "input": ["src"],
355
+ "output": "src/assets/i18n",
356
+ "langs": ["fa", "en"],
357
+ "scopes": { "setting": "src/app/setting", "orders": "src/app/orders" },
358
+ "extensions": ["html", "ts"],
359
+ "ignore": [],
360
+ "placeholder": "",
361
+ "sort": true,
362
+ "unflat": false,
363
+ "remove": false
364
+ }
365
+ ```
366
+
367
+ - A source file belongs to the scope whose directory is the **longest match**; everything else is global.
368
+ - Output: `{output}/{lang}.json` for global, `{output}/{scope}/{lang}.json` for scopes.
369
+ - Existing values are never overwritten. New keys get `placeholder` (empty by default, which makes the runtime fall back).
370
+ - `unflat: true` writes nested objects; a key that cannot be nested (leaf/branch conflict) stays dotted.
371
+ - A second run is idempotent: unchanged files are not rewritten.
372
+
373
+ What it recognises:
374
+
375
+ | Source | Extracted |
376
+ |---|---|
377
+ | `{{ 'a.b' \| tr }}` and `(c ? 'a' : 'b') \| tr` | `a.b`, `a`, `b` |
378
+ | `*tr="let t; prefix: 'orders'"` then `t('title')` | `orders.title` |
379
+ | `trKey="a.b"`, `[trKey]="'a.b'"`, with `trPrefix="p"` | `a.b` / `p.a.b` |
380
+ | `i18n.t('a')`, `.select('a')`, `.has('a')`, `.getObject('a')`, `.instant('a')` | `a` |
381
+ | `translateSignal('a')`, `marker('a')` | `a` |
382
+ | `// tr-keys: a, b` or `<!-- tr-keys(prefix): a, b -->` | declares dynamic keys |
383
+
384
+ Dynamic keys (template strings with `${}`, variables) cannot be known statically. Declare them with a `tr-keys` comment or wrap constants in `marker('key')`:
385
+
386
+ ```ts
387
+ import { marker } from 'ngx-kit/lazy-i18n';
388
+ const STATUS = { open: marker('status.open'), closed: marker('status.closed') };
389
+ // then: i18n.t(STATUS[s])
390
+ ```
391
+
392
+ Add to `package.json` scripts: `"i18n": "ngx-lazy-i18n-extract"`, `"i18n:check": "ngx-lazy-i18n-extract --check"`.
393
+
394
+ ## Configuration reference
395
+
396
+ | Option | Type | Default | Description |
397
+ |---|---|---|---|
398
+ | `langs` | `string[]` | required before first activation | Supported languages. May be supplied later with `init()`. |
399
+ | `defaultLang` | `string` | required before first activation | Used when nothing is stored or detected. May be supplied later. |
400
+ | `autoInit` | `boolean` | `true` if `langs` and `defaultLang` are given | Add an APP_INITIALIZER that activates the initial language. Provide-time only. |
401
+ | `fallbackLangs` | `string[]` | `[]` | Languages tried, in order, for missing/empty keys. |
402
+ | `missing` | `'key' \| 'empty' \| (key, lang) => string` | `'key'` | What to render for a missing key. |
403
+ | `warnMissing` | `boolean` | `isDevMode()` | Log each missing key once. |
404
+ | `transpile` | `(value, params, lang) => string` | built-in `{{ }}` | Custom message formatter. |
405
+ | `loader` | `Type<I18nLoader>` | `HttpI18nLoader` | Custom loader class. |
406
+ | `baseUrl` | `string` | `'/i18n'` | HTTP loader base. |
407
+ | `fileExtension` | `string` | `'.json'` | HTTP loader extension; `''` for none. |
408
+ | `url` | `(lang, scope?) => string` | none | HTTP loader full URL control. |
409
+ | `retries` | `number` | `1` | Retries per failed request (250 ms delay). |
410
+ | `storageKey` | `string \| null` | `'lazy-i18n.lang'` | Persist language; `null` disables. |
411
+ | `detectBrowserLang` | `boolean` | `false` | Use `navigator.languages` when nothing is stored. |
412
+ | `manageDocument` | `boolean` | `true` | Keep `<html lang dir>` in sync. |
413
+
414
+ ## API reference
415
+
416
+ **Providers / guards:** `provideI18n(config)`, `provideI18nScope(scope | scope[])`, `i18nReadyGuard`
417
+
418
+ **Template:** `TrPipe` (`tr`), `TrDirective` (`*tr`), `TrKeyDirective` (`trKey`), bundle `TR`
419
+
420
+ **Functions:** `translateSignal(key, params?)`, `injectT()`, `marker(key)`, `interpolate(text, params)`, `flatten(dict)`
421
+
422
+ **`I18nService`**
423
+
424
+ | Member | Description |
425
+ |---|---|
426
+ | `lang: Signal<string>` | Active language. |
427
+ | `dir: Signal<'rtl' \| 'ltr'>` | Direction for the active language. |
428
+ | `revision: Signal<number>` | Changes on every language/dictionary change. |
429
+ | `langs: Signal<readonly string[]>` | Supported languages (reactive; may arrive after startup). |
430
+ | `ready: Signal<boolean>` | `true` after the first language is active. |
431
+ | `init({ langs, defaultLang, baseUrl, lang, … })` | Complete the config and activate the first language. Use in your own initializer. |
432
+ | `configure(patch)` | Change options after startup (not `loader`/`autoInit`). |
433
+ | `t(key, params?)` | Translate (reactive). |
434
+ | `instant(key, params?)` | Translate (non-reactive). |
435
+ | `select(key, params?)` | `Signal<string>`; `params` may be a function. |
436
+ | `has(key)` / `getObject(prefix)` | Introspection. |
437
+ | `use(lang)` | Load everything, then activate. |
438
+ | `preload(lang)` | Download without activating. |
439
+ | `registerScope(scope)` | Register and start loading a scope. |
440
+ | `whenReady()` | Resolves when all registered scopes and any in-flight `init()`/`use()` are done. Never rejects. |
441
+ | `setTranslation(lang, dict, merge = true)` | Inject translations manually. |
442
+ | `detectInitialLang()` | Stored, browser (opt-in) or default. |
443
+
444
+ ## Testing your app
445
+
446
+ Skip the network by providing a loader that returns objects:
447
+
448
+ ```ts
449
+ @Injectable({ providedIn: 'root' })
450
+ class TestLoader implements I18nLoader {
451
+ load(lang: string, scope?: string) {
452
+ return of(DICTS[`${lang}|${scope ?? ''}`] ?? {});
453
+ }
454
+ }
455
+
456
+ TestBed.configureTestingModule({
457
+ providers: [provideI18n({ langs: ['en'], defaultLang: 'en', loader: TestLoader, storageKey: null })],
458
+ });
459
+ ```
460
+
461
+ Or call `TestBed.inject(I18nService).setTranslation('en', { a: 'A' })` and `await svc.use('en')`.
462
+
463
+ ## Performance notes
464
+
465
+ - **Lookup:** one `Map.get` on the flattened active-language map, plus one per fallback language only on a miss.
466
+ - **Pipe:** impure so it follows signals, but memoised on key, params (shallow compare) and `revision`. Unchanged inputs return the cached string without a lookup.
467
+ - **Directive:** creates its view once. Updates happen through signal reads in the bindings, so nothing is re-created on language change.
468
+ - **Interpolation:** `indexOf('{{')` fast path; no regex for static strings.
469
+ - **Network:** each `(lang, scope)` file is requested at most once (in-flight requests are shared). The language switch loads global + scopes + fallbacks in parallel.
470
+ - **Bundle:** no runtime dependency besides Angular, RxJS and `tslib`. The extractor is a separate Node script and is not bundled into apps.
471
+
472
+ ## Migrating from Transloco
473
+
474
+ | Transloco | ngx-kit/lazy-i18n |
475
+ |---|---|
476
+ | `provideTransloco(...)` | `provideI18n(...)` |
477
+ | `provideTranslocoScope('x')` | `provideI18nScope('x')` (route `providers`) |
478
+ | `{{ 'x.key' \| transloco }}` | `{{ 'key' \| tr }}` |
479
+ | `*transloco="let t; read: 'x'"` | `*tr="let t; prefix: 'x'"` |
480
+ | `translateSignal('x.key')` | `translateSignal('key')` |
481
+ | `service.selectTranslate()` | `i18n.select()` / `translateSignal()` |
482
+ | `setActiveLang()` | `await i18n.use()` |
483
+ | `TranslocoLoader` | `I18nLoader` |
484
+ | `transloco-keys-manager` | `ngx-lazy-i18n-extract` |
485
+
486
+ Server files can stay as they are (`/i18n/{scope}/{lang}`). Keys that used to be written with the scope prefix must be written without it, or the scope name must be moved inside your JSON.
487
+
488
+ ## Limitations
489
+
490
+ - **Flat namespace.** Cross-scope key collisions are your responsibility (dev-mode warning only).
491
+ - **Extractor is a static scanner.** It uses regular expressions and a small call parser, not the Angular compiler. `t()` calls are attributed to the **nearest preceding** `*tr="let t"` with the same variable name in the same file, so nested `*tr` blocks that reuse the same variable name with different prefixes can be mis-attributed. Use distinct variable names (`let t`, `let tOrders`) or `tr-keys` comments.
492
+ - **SSR.** `localStorage`/`document` access is guarded, but relative HTTP URLs on the server need an absolute-URL interceptor or a custom loader. Transfer-state is not implemented; use `setTranslation()` if you need it.
493
+ - **Pluralisation/ICU** are not built in; plug a formatter into `transpile`.
494
+ - Signal `input()` is deliberately not used for the directives, so they work in JIT test environments and older Angular versions.
495
+
496
+ ## Developing this library
497
+
498
+ ```bash
499
+ npm install
500
+ npm test # vitest: service, pipe/directives, routing, extractor
501
+ npm run build # ng-packagr -> dist/
502
+ cd dist && npm publish
503
+ ```
504
+
505
+ Source layout:
506
+
507
+ ```
508
+ src/lib/
509
+ i18n.service.ts state, loading, scopes, fallbacks, language switching
510
+ pipe.ts `tr` pipe (memoised)
511
+ directives.ts *tr and trKey
512
+ routing.ts provideI18nScope, i18nReadyGuard
513
+ provide-i18n.ts provideI18n
514
+ http-loader.ts default loader
515
+ signals.ts translateSignal, injectT
516
+ utils.ts flatten, interpolate, marker
517
+ tools/extractor.mjs key extraction engine
518
+ bin/extract.mjs CLI wrapper
519
+ ```
520
+
521
+ MIT licensed.