ngx-kit 8.5.0 → 9.0.0

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,455 @@
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. [Concepts](#concepts)
21
+ 4. [Using translations](#using-translations)
22
+ 5. [Scopes and routing](#scopes-and-routing)
23
+ 6. [Language switching](#language-switching)
24
+ 7. [Fallbacks and missing keys](#fallbacks-and-missing-keys)
25
+ 8. [Interpolation, plurals and ICU](#interpolation-plurals-and-icu)
26
+ 9. [Loaders](#loaders)
27
+ 10. [Key extractor CLI](#key-extractor-cli)
28
+ 11. [Configuration reference](#configuration-reference)
29
+ 12. [API reference](#api-reference)
30
+ 13. [Testing your app](#testing-your-app)
31
+ 14. [Performance notes](#performance-notes)
32
+ 15. [Migrating from Transloco](#migrating-from-transloco)
33
+ 16. [Limitations](#limitations)
34
+ 17. [Developing this library](#developing-this-library)
35
+
36
+ ---
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ npm i ngx-kit
42
+ ```
43
+
44
+
45
+ ## Quick start
46
+
47
+ **1. Provide it** (`app.config.ts`)
48
+
49
+ ```ts
50
+ import { provideHttpClient } from '@angular/common/http';
51
+ import { provideRouter } from '@angular/router';
52
+ import { provideI18n } from 'ngx-kit/lazy-i18n';
53
+
54
+ export const appConfig: ApplicationConfig = {
55
+ providers: [
56
+ provideRouter(routes),
57
+ provideHttpClient(),
58
+ provideI18n({
59
+ langs: ['fa', 'en'],
60
+ defaultLang: 'fa',
61
+ fallbackLangs: ['en'],
62
+ baseUrl: '/i18n', // GET /i18n/fa and GET /i18n/setting/fa
63
+ }),
64
+ ],
65
+ };
66
+ ```
67
+
68
+ The global file of the initial language is loaded **before** the app bootstraps.
69
+
70
+ **2. Declare scopes on routes and add the guard once** (`app.routes.ts`)
71
+
72
+ ```ts
73
+ import { i18nReadyGuard, provideI18nScope } from 'ngx-kit/lazy-i18n';
74
+
75
+ export const routes: Routes = [
76
+ {
77
+ path: '',
78
+ canActivateChild: [i18nReadyGuard], // once, on the root path-less route
79
+ children: [
80
+ {
81
+ path: 'setting',
82
+ providers: [provideI18nScope('setting')], // loads /i18n/setting/{lang}
83
+ loadChildren: () => import('./setting/setting.routes'),
84
+ },
85
+ {
86
+ path: 'orders',
87
+ providers: [provideI18nScope(['orders', 'shared-grid'])], // several scopes are fine
88
+ loadComponent: () => import('./orders/orders.page'),
89
+ },
90
+ ],
91
+ },
92
+ ];
93
+ ```
94
+
95
+ Child routes of `setting` need **nothing**. Components never mention a scope.
96
+
97
+ **3. Translate**
98
+
99
+ ```ts
100
+ import { Component } from '@angular/core';
101
+ import { TR, translateSignal } from 'ngx-kit/lazy-i18n';
102
+
103
+ @Component({
104
+ selector: 'app-requests',
105
+ imports: [TR], // pipe + both directives
106
+ template: `
107
+ <h1>{{ 'Request' | tr }}</h1>
108
+ <p>{{ 'welcome' | tr: { name: user.name } }}</p>
109
+ <input trKey="Search" trAttr="placeholder" />
110
+ <h2>{{ title() }}</h2>
111
+ `,
112
+ })
113
+ export class RequestsComponent {
114
+ user = { name: 'Ali' };
115
+ title = translateSignal('Request');
116
+ }
117
+ ```
118
+
119
+ ## Concepts
120
+
121
+ | Term | Meaning |
122
+ |---|---|
123
+ | **Global file** | `GET {baseUrl}/{lang}`, loaded at startup. |
124
+ | **Scope file** | `GET {baseUrl}/{scope}/{lang}`, loaded when a route registers the scope. |
125
+ | **Flat namespace** | All files are merged at the **root** of one language dictionary. Keys never carry the scope name. |
126
+ | **Registered scope** | A scope that any route has requested. Registered scopes stay registered, so they are also loaded when the language changes. |
127
+ | **Revision** | A counter signal that changes whenever the language or any dictionary changes. Pipe and directives depend on it. |
128
+
129
+ 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.
130
+
131
+ ## Using translations
132
+
133
+ ### Pipe
134
+
135
+ ```html
136
+ {{ 'orders.title' | tr }}
137
+ {{ 'hello' | tr: { name: user.name } }}
138
+ <button [title]="'common.save' | tr">…</button>
139
+ ```
140
+
141
+ ### Structural directive
142
+
143
+ The view is created once; `t` is a signal-aware function, so only the bindings that call it update.
144
+
145
+ ```html
146
+ <ng-container *tr="let t; prefix: 'orders'">
147
+ <h1>{{ t('title') }}</h1> <!-- orders.title -->
148
+ <p>{{ t('count', { n: items.length }) }}</p>
149
+ </ng-container>
150
+ ```
151
+
152
+ Context: `$implicit` / `t` (the translate function) and `lang` (a `Signal<string>`).
153
+
154
+ ### Attribute directive
155
+
156
+ ```html
157
+ <span trKey="orders.title"></span>
158
+ <input trKey="form.name" trAttr="placeholder" />
159
+ <p trKey="hello" [trParams]="{ name: user.name }"></p>
160
+ <span trKey="title" trPrefix="orders"></span>
161
+ ```
162
+
163
+ `trAttr` sets an attribute instead of the text content.
164
+
165
+ ### Signals
166
+
167
+ ```ts
168
+ import { computed, inject } from '@angular/core';
169
+ import { I18nService, injectT, translateSignal } from 'ngx-kit/lazy-i18n';
170
+
171
+ class Demo {
172
+ private i18n = inject(I18nService);
173
+ title = translateSignal('orders.title');
174
+ hello = translateSignal('hello', () => ({ name: this.name() })); // params may read signals
175
+ t = injectT();
176
+ label = computed(() => this.t('orders.count', { n: this.n() }));
177
+ lang = this.i18n.lang; // Signal<string>
178
+ dir = this.i18n.dir; // Signal<'rtl' | 'ltr'>
179
+ }
180
+ ```
181
+
182
+ ### Service
183
+
184
+ ```ts
185
+ i18n.t('key', params?) // reactive when read inside computed/effect/template
186
+ i18n.instant('key', params?) // same lookup, never creates a reactive dependency
187
+ i18n.select('key', params?) // Signal<string>
188
+ i18n.has('key') // true if non-empty in active lang or a fallback
189
+ i18n.getObject('orders') // nested object of everything under "orders."
190
+ ```
191
+
192
+ ## Scopes and routing
193
+
194
+ `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.
195
+
196
+ Rules of thumb:
197
+
198
+ - Put `i18nReadyGuard` **once** on the root path-less route as `canActivateChild`.
199
+ - Put `provideI18nScope(...)` on the route that owns the module.
200
+ - 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.
201
+ - For a scope not tied to a route (a dialog library, for example) call `i18n.registerScope('name')` and `await i18n.whenReady()`.
202
+ - A failed scope request is retried (`retries`, default 1), then logged. It never blocks navigation forever.
203
+
204
+ ## Language switching
205
+
206
+ ```ts
207
+ await i18n.use('en');
208
+ ```
209
+
210
+ `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.
211
+
212
+ `i18n.preload('en')` downloads a language without activating it.
213
+
214
+ ## Fallbacks and missing keys
215
+
216
+ ```ts
217
+ provideI18n({
218
+ langs: ['fa', 'en'], defaultLang: 'fa',
219
+ fallbackLangs: ['en'], // looked up in order
220
+ missing: (key, lang) => `⚠ ${key}`, // 'key' (default) | 'empty' | function
221
+ warnMissing: true, // console.warn once per key (default: dev mode)
222
+ });
223
+ ```
224
+
225
+ A value of `''` counts as **missing**, so the placeholders written by the extractor fall back to the next language automatically.
226
+
227
+ ## Interpolation, plurals and ICU
228
+
229
+ Default syntax: `Hello {{ name }}`, nested paths (`{{ user.name }}`) are supported, unknown placeholders are left untouched.
230
+
231
+ Plug in anything else with `transpile`:
232
+
233
+ ```ts
234
+ provideI18n({
235
+ …,
236
+ transpile: (value, params, lang) => {
237
+ // example: tiny plural helper "{n, plural, one {# item} other {# items}}"
238
+ return value.replace(/\{(\w+), plural, one \{([^}]*)\} other \{([^}]*)\}\}/g, (_, k, one, other) => {
239
+ const n = Number(params?.[k] ?? 0);
240
+ const form = new Intl.PluralRules(lang).select(n) === 'one' ? one : other;
241
+ return form.replace('#', String(n));
242
+ });
243
+ },
244
+ });
245
+ ```
246
+
247
+ Use a real ICU library (for example `intl-messageformat`) inside `transpile` for full ICU support, and cache compiled messages there.
248
+
249
+ ## Loaders
250
+
251
+ The default `HttpI18nLoader` uses `HttpClient`, so your interceptors (auth, base URL, retry) apply.
252
+
253
+ ```ts
254
+ provideI18n({
255
+ …,
256
+ baseUrl: '/api/translations', // /api/translations/fa, /api/translations/setting/fa
257
+ fileExtension: '.json', // static files: /assets/i18n/fa.json
258
+ // or full control:
259
+ url: (lang, scope) => `/api/i18n?lang=${lang}${scope ? `&scope=${scope}` : ''}`,
260
+ });
261
+ ```
262
+
263
+ Custom loader (anything that returns a nested or flat object, as `Observable` or `Promise`):
264
+
265
+ ```ts
266
+ @Injectable({ providedIn: 'root' })
267
+ export class MyLoader implements I18nLoader {
268
+ load(lang: string, scope?: string) {
269
+ return fetch(`/x/${scope ?? '_global'}/${lang}`).then((r) => r.json());
270
+ }
271
+ }
272
+ provideI18n({ …, loader: MyLoader });
273
+ ```
274
+
275
+ Nested JSON (`{ "a": { "b": "x" } }`) and flat JSON (`{ "a.b": "x" }`) are both accepted.
276
+
277
+ ## Key extractor CLI
278
+
279
+ Generates and updates your JSON files from the source code, **per scope**.
280
+
281
+ ```bash
282
+ npx ngx-lazy-i18n-extract # reads ./i18n.config.json
283
+ npx ngx-lazy-i18n-extract --check # CI: exit 1 if a used key is missing from the files
284
+ npx ngx-lazy-i18n-extract --remove # also delete keys no longer used
285
+ npx ngx-lazy-i18n-extract --langs fa,en --output src/assets/i18n --input src
286
+ ```
287
+
288
+ `i18n.config.json`:
289
+
290
+ ```json
291
+ {
292
+ "input": ["src"],
293
+ "output": "src/assets/i18n",
294
+ "langs": ["fa", "en"],
295
+ "scopes": { "setting": "src/app/setting", "orders": "src/app/orders" },
296
+ "extensions": ["html", "ts"],
297
+ "ignore": [],
298
+ "placeholder": "",
299
+ "sort": true,
300
+ "unflat": false,
301
+ "remove": false
302
+ }
303
+ ```
304
+
305
+ - A source file belongs to the scope whose directory is the **longest match**; everything else is global.
306
+ - Output: `{output}/{lang}.json` for global, `{output}/{scope}/{lang}.json` for scopes.
307
+ - Existing values are never overwritten. New keys get `placeholder` (empty by default, which makes the runtime fall back).
308
+ - `unflat: true` writes nested objects; a key that cannot be nested (leaf/branch conflict) stays dotted.
309
+ - A second run is idempotent: unchanged files are not rewritten.
310
+
311
+ What it recognises:
312
+
313
+ | Source | Extracted |
314
+ |---|---|
315
+ | `{{ 'a.b' \| tr }}` and `(c ? 'a' : 'b') \| tr` | `a.b`, `a`, `b` |
316
+ | `*tr="let t; prefix: 'orders'"` then `t('title')` | `orders.title` |
317
+ | `trKey="a.b"`, `[trKey]="'a.b'"`, with `trPrefix="p"` | `a.b` / `p.a.b` |
318
+ | `i18n.t('a')`, `.select('a')`, `.has('a')`, `.getObject('a')`, `.instant('a')` | `a` |
319
+ | `translateSignal('a')`, `marker('a')` | `a` |
320
+ | `// tr-keys: a, b` or `<!-- tr-keys(prefix): a, b -->` | declares dynamic keys |
321
+
322
+ Dynamic keys (template strings with `${}`, variables) cannot be known statically. Declare them with a `tr-keys` comment or wrap constants in `marker('key')`:
323
+
324
+ ```ts
325
+ import { marker } from 'ngx-kit/lazy-i18n';
326
+ const STATUS = { open: marker('status.open'), closed: marker('status.closed') };
327
+ // then: i18n.t(STATUS[s])
328
+ ```
329
+
330
+ Add to `package.json` scripts: `"i18n": "ngx-lazy-i18n-extract"`, `"i18n:check": "ngx-lazy-i18n-extract --check"`.
331
+
332
+ ## Configuration reference
333
+
334
+ | Option | Type | Default | Description |
335
+ |---|---|---|---|
336
+ | `langs` | `string[]` | required | Supported languages. |
337
+ | `defaultLang` | `string` | required | Used when nothing is stored or detected. |
338
+ | `fallbackLangs` | `string[]` | `[]` | Languages tried, in order, for missing/empty keys. |
339
+ | `missing` | `'key' \| 'empty' \| (key, lang) => string` | `'key'` | What to render for a missing key. |
340
+ | `warnMissing` | `boolean` | `isDevMode()` | Log each missing key once. |
341
+ | `transpile` | `(value, params, lang) => string` | built-in `{{ }}` | Custom message formatter. |
342
+ | `loader` | `Type<I18nLoader>` | `HttpI18nLoader` | Custom loader class. |
343
+ | `baseUrl` | `string` | `'/i18n'` | HTTP loader base. |
344
+ | `fileExtension` | `string` | `''` | HTTP loader extension (e.g. `.json`). |
345
+ | `url` | `(lang, scope?) => string` | none | HTTP loader full URL control. |
346
+ | `retries` | `number` | `1` | Retries per failed request (250 ms delay). |
347
+ | `storageKey` | `string \| null` | `'lazy-i18n.lang'` | Persist language; `null` disables. |
348
+ | `detectBrowserLang` | `boolean` | `false` | Use `navigator.languages` when nothing is stored. |
349
+ | `manageDocument` | `boolean` | `true` | Keep `<html lang dir>` in sync. |
350
+
351
+ ## API reference
352
+
353
+ **Providers / guards:** `provideI18n(config)`, `provideI18nScope(scope | scope[])`, `i18nReadyGuard`
354
+
355
+ **Template:** `TrPipe` (`tr`), `TrDirective` (`*tr`), `TrKeyDirective` (`trKey`), bundle `TR`
356
+
357
+ **Functions:** `translateSignal(key, params?)`, `injectT()`, `marker(key)`, `interpolate(text, params)`, `flatten(dict)`
358
+
359
+ **`I18nService`**
360
+
361
+ | Member | Description |
362
+ |---|---|
363
+ | `lang: Signal<string>` | Active language. |
364
+ | `dir: Signal<'rtl' \| 'ltr'>` | Direction for the active language. |
365
+ | `revision: Signal<number>` | Changes on every language/dictionary change. |
366
+ | `langs: readonly string[]` | Supported languages. |
367
+ | `t(key, params?)` | Translate (reactive). |
368
+ | `instant(key, params?)` | Translate (non-reactive). |
369
+ | `select(key, params?)` | `Signal<string>`; `params` may be a function. |
370
+ | `has(key)` / `getObject(prefix)` | Introspection. |
371
+ | `use(lang)` | Load everything, then activate. |
372
+ | `preload(lang)` | Download without activating. |
373
+ | `registerScope(scope)` | Register and start loading a scope. |
374
+ | `whenReady()` | Resolves when all registered scopes are loaded. Never rejects. |
375
+ | `setTranslation(lang, dict, merge = true)` | Inject translations manually. |
376
+ | `detectInitialLang()` | Stored, browser (opt-in) or default. |
377
+
378
+ ## Testing your app
379
+
380
+ Skip the network by providing a loader that returns objects:
381
+
382
+ ```ts
383
+ @Injectable({ providedIn: 'root' })
384
+ class TestLoader implements I18nLoader {
385
+ load(lang: string, scope?: string) {
386
+ return of(DICTS[`${lang}|${scope ?? ''}`] ?? {});
387
+ }
388
+ }
389
+
390
+ TestBed.configureTestingModule({
391
+ providers: [provideI18n({ langs: ['en'], defaultLang: 'en', loader: TestLoader, storageKey: null })],
392
+ });
393
+ ```
394
+
395
+ Or call `TestBed.inject(I18nService).setTranslation('en', { a: 'A' })` and `await svc.use('en')`.
396
+
397
+ ## Performance notes
398
+
399
+ - **Lookup:** one `Map.get` on the flattened active-language map, plus one per fallback language only on a miss.
400
+ - **Pipe:** impure so it follows signals, but memoised on key, params (shallow compare) and `revision`. Unchanged inputs return the cached string without a lookup.
401
+ - **Directive:** creates its view once. Updates happen through signal reads in the bindings, so nothing is re-created on language change.
402
+ - **Interpolation:** `indexOf('{{')` fast path; no regex for static strings.
403
+ - **Network:** each `(lang, scope)` file is requested at most once (in-flight requests are shared). The language switch loads global + scopes + fallbacks in parallel.
404
+ - **Bundle:** no runtime dependency besides Angular, RxJS and `tslib`. The extractor is a separate Node script and is not bundled into apps.
405
+
406
+ ## Migrating from Transloco
407
+
408
+ | Transloco | ngx-kit/lazy-i18n |
409
+ |---|---|
410
+ | `provideTransloco(...)` | `provideI18n(...)` |
411
+ | `provideTranslocoScope('x')` | `provideI18nScope('x')` (route `providers`) |
412
+ | `{{ 'x.key' \| transloco }}` | `{{ 'key' \| tr }}` |
413
+ | `*transloco="let t; read: 'x'"` | `*tr="let t; prefix: 'x'"` |
414
+ | `translateSignal('x.key')` | `translateSignal('key')` |
415
+ | `service.selectTranslate()` | `i18n.select()` / `translateSignal()` |
416
+ | `setActiveLang()` | `await i18n.use()` |
417
+ | `TranslocoLoader` | `I18nLoader` |
418
+ | `transloco-keys-manager` | `ngx-lazy-i18n-extract` |
419
+
420
+ 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.
421
+
422
+ ## Limitations
423
+
424
+ - **Flat namespace.** Cross-scope key collisions are your responsibility (dev-mode warning only).
425
+ - **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.
426
+ - **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.
427
+ - **Pluralisation/ICU** are not built in; plug a formatter into `transpile`.
428
+ - Signal `input()` is deliberately not used for the directives, so they work in JIT test environments and older Angular versions.
429
+
430
+ ## Developing this library
431
+
432
+ ```bash
433
+ npm install
434
+ npm test # vitest: service, pipe/directives, routing, extractor
435
+ npm run build # ng-packagr -> dist/
436
+ cd dist && npm publish
437
+ ```
438
+
439
+ Source layout:
440
+
441
+ ```
442
+ src/lib/
443
+ i18n.service.ts state, loading, scopes, fallbacks, language switching
444
+ pipe.ts `tr` pipe (memoised)
445
+ directives.ts *tr and trKey
446
+ routing.ts provideI18nScope, i18nReadyGuard
447
+ provide-i18n.ts provideI18n
448
+ http-loader.ts default loader
449
+ signals.ts translateSignal, injectT
450
+ utils.ts flatten, interpolate, marker
451
+ tools/extractor.mjs key extraction engine
452
+ bin/extract.mjs CLI wrapper
453
+ ```
454
+
455
+ MIT licensed.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-kit",
3
- "version": "8.5.0",
3
+ "version": "9.0.0",
4
4
  "description": "Angular input components",
5
5
  "author": "Mohammadreza Samani <mr.samani1368@gmail.com>",
6
6
  "license": "MIT",
@@ -13,7 +13,43 @@
13
13
  "url": "https://github.com/mr-samani/ngx-kit/issues"
14
14
  },
15
15
  "keywords": [
16
- "angular"
16
+ "angular",
17
+ "i18n",
18
+ "translate",
19
+ "signals",
20
+ "lazy",
21
+ "scopes",
22
+ "input-color",
23
+ "color-picker",
24
+ "gradient-picker",
25
+ "select-angle",
26
+ "date-picker",
27
+ "calendar",
28
+ "file",
29
+ "drop-zone",
30
+ "image-viewer",
31
+ "draggable",
32
+ "resizable",
33
+ "kanban-view",
34
+ "drag-drop",
35
+ "shadow",
36
+ "message",
37
+ "notify",
38
+ "alert",
39
+ "dialog",
40
+ "modal",
41
+ "time-picker",
42
+ "tooltip",
43
+ "data-table",
44
+ "table",
45
+ "pagination",
46
+ "drop-zone",
47
+ "image-editor",
48
+ "media-control",
49
+ "media-player",
50
+ "audio-player",
51
+ "infinit-scroll",
52
+ "virtual-scroll"
17
53
  ],
18
54
  "peerDependencies": {
19
55
  "@angular/common": ">=22.0.0",
@@ -23,6 +59,9 @@
23
59
  "tslib": "^2.3.0"
24
60
  },
25
61
  "sideEffects": false,
62
+ "bin": {
63
+ "ngx-lazy-i18n-extract": "./lazy-i18n/bin/extract.mjs"
64
+ },
26
65
  "module": "fesm2022/ngx-kit.mjs",
27
66
  "typings": "types/ngx-kit.d.ts",
28
67
  "exports": {
@@ -93,6 +132,10 @@
93
132
  "types": "./types/ngx-kit-infinite-scroll.d.ts",
94
133
  "default": "./fesm2022/ngx-kit-infinite-scroll.mjs"
95
134
  },
135
+ "./lazy-i18n": {
136
+ "types": "./types/ngx-kit-lazy-i18n.d.ts",
137
+ "default": "./fesm2022/ngx-kit-lazy-i18n.mjs"
138
+ },
96
139
  "./media-control": {
97
140
  "types": "./types/ngx-kit-media-control.d.ts",
98
141
  "default": "./fesm2022/ngx-kit-media-control.mjs"