@flusys/ng-localization 4.1.1 → 5.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.
package/README.md CHANGED
@@ -1,92 +1,21 @@
1
1
  # @flusys/ng-localization
2
2
 
3
- > Database-driven internationalization (i18n) library for the FLUSYS Angular platform — multi-language support, RTL/LTR, TranslatePipe, language selector, and Angular APP_INITIALIZER bootstrap.
3
+ Database-driven i18n for FLUSYS Angular apps — translations loaded from the backend at startup, signal-reactive `TranslatePipe`, RTL/LTR auto-switching, and a built-in language selector for the topbar.
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@flusys/ng-localization.svg)](https://www.npmjs.com/package/@flusys/ng-localization)
6
- [![Angular](https://img.shields.io/badge/Angular-21-red.svg)](https://angular.io)
7
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org)
8
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
9
-
10
- ---
11
-
12
- ## Table of Contents
13
-
14
- - [Overview](#overview)
15
- - [Features](#features)
16
- - [Compatibility](#compatibility)
17
- - [Installation](#installation)
18
- - [Quick Start](#quick-start)
19
- - [Module Registration](#module-registration)
20
- - [TranslateService](#translateservice)
21
- - [TranslatePipe](#translatepipe)
22
- - [Variable Interpolation](#variable-interpolation)
23
- - [Language Management](#language-management)
24
- - [Components](#components)
25
- - [LanguageSelectorComponent](#languageselectorcomponent)
26
- - [Route Resolver](#route-resolver)
27
- - [RTL Support](#rtl-support)
28
- - [TRANSLATE_ADAPTER Token](#translate_adapter-token)
29
- - [API Endpoints](#api-endpoints)
30
- - [Configuration Reference](#configuration-reference)
31
- - [Troubleshooting](#troubleshooting)
32
- - [License](#license)
33
-
34
- ---
35
-
36
- ## Overview
37
-
38
- `@flusys/ng-localization` provides database-driven i18n for FLUSYS applications. Translations are stored in the backend (`nestjs-localization`) and loaded at app startup via `APP_INITIALIZER`. The `TranslatePipe` integrates with `TranslateService` through the `TRANSLATE_ADAPTER` injection token — keeping all other packages independent of `ng-localization`.
39
-
40
- Unlike file-based i18n (ngx-translate), FLUSYS translations are managed in the database and can be updated at runtime without rebuilding.
41
-
42
- ---
43
-
44
- ## Features
45
-
46
- - ✅ Database-driven translations (updated without rebuild)
47
- - ✅ `TranslatePipe` with signal reactivity — re-renders on language switch
48
- - ✅ `{{variableName}}` interpolation with `messageVariables`
49
- - ✅ RTL/LTR document direction auto-switch
50
- - ✅ APP_INITIALIZER bootstrap — translations loaded before app renders
51
- - ✅ Route resolver for per-route language loading
52
- - ✅ Language selector component for topbar
53
- - ✅ `provideLocalization()` — registers `TRANSLATE_ADAPTER` and `LAYOUT_LANGUAGE_SELECTOR`
54
- - ✅ Missing key detection and fallback
55
-
56
- ---
57
-
58
- ## Compatibility
59
-
60
- | Package | Version |
61
- |---------|---------|
62
- | Angular | 21+ |
63
- | @flusys/ng-core | 4.x |
64
- | @flusys/ng-shared | 4.x |
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
65
7
 
66
8
  ---
67
9
 
68
10
  ## Installation
69
11
 
70
12
  ```bash
71
- npm install @flusys/ng-localization @flusys/ng-core @flusys/ng-shared
13
+ npm install @flusys/ng-localization
72
14
  ```
73
15
 
74
- ---
75
-
76
- ## Quick Start
16
+ ## 1. Register Providers
77
17
 
78
- ### 1. Enable Localization in Config
79
-
80
- ```typescript
81
- // environments/environment.ts
82
- export const environment = {
83
- services: {
84
- localization: { enabled: true },
85
- },
86
- };
87
- ```
88
-
89
- ### 2. Register Localization Providers
18
+ Call `provideLocalization()` once in `app.config.ts`. It registers `TRANSLATE_ADAPTER` (consumed by `TranslatePipe` from `ng-shared`), an `APP_INITIALIZER` that loads translations before the first render, and optionally the topbar language selector.
90
19
 
91
20
  ```typescript
92
21
  // app.config.ts
@@ -94,363 +23,118 @@ import { provideLocalization } from '@flusys/ng-localization';
94
23
 
95
24
  export const appConfig: ApplicationConfig = {
96
25
  providers: [
97
- ...provideLocalization(),
98
- // Provides:
99
- // - TRANSLATE_ADAPTER (used by TranslatePipe, ng-shared)
100
- // - LAYOUT_LANGUAGE_SELECTOR (used by AppTopbar from ng-layout)
101
- // - APP_INITIALIZER (loads translations before app renders)
26
+ provideLocalization({
27
+ defaultLanguageCode: 'en', // required — fallback when no saved preference
28
+ loadStrategy: 'modules', // optional — 'modules' (default) or 'all'
29
+ initialModules: [], // optional — modules to pre-load at startup
30
+ enableLayoutSelector: true, // optional — renders language picker in AppTopbar
31
+ }),
102
32
  ],
103
33
  };
104
34
  ```
105
35
 
106
- ### 3. Use TranslatePipe in Templates
107
-
108
- ```html
109
- <!-- Basic translation -->
110
- <span>{{ 'common.save' | translate }}</span>
111
- <button>{{ 'common.cancel' | translate }}</button>
112
- <h1>{{ 'dashboard.title' | translate }}</h1>
113
-
114
- <!-- With parameters -->
115
- <p>{{ 'pagination.showing' | translate: { from: 1, to: 10, total: 100 } }}</p>
116
- ```
117
-
118
- ---
119
-
120
- ## Module Registration
121
-
122
- ### `provideLocalization()`
123
-
124
- ```typescript
125
- import { provideLocalization } from '@flusys/ng-localization';
126
-
127
- providers: [
128
- ...provideLocalization(),
129
- ]
130
- ```
131
-
132
- Registers:
133
- - `TRANSLATE_ADAPTER` — `TranslateService` implementation (used by `TranslatePipe` from `ng-shared`)
134
- - `LAYOUT_LANGUAGE_SELECTOR` — `ILanguageSelectorProvider` implementation (used by `AppTopbar`)
135
- - `APP_INITIALIZER` — loads translations for the default language before first render
136
-
137
- ---
138
-
139
- ## TranslateService
140
-
141
- The core service managing translations and language state:
142
-
143
- ```typescript
144
- import { TranslateService } from '@flusys/ng-localization';
145
-
146
- @Component({ ... })
147
- export class MyComponent {
148
- private translate = inject(TranslateService);
149
-
150
- // Signals
151
- currentLanguage = this.translate.currentLanguage; // Signal<ILanguage>
152
- availableLanguages = this.translate.languages; // Signal<ILanguage[]>
153
- isLoading = this.translate.isLoading; // Signal<boolean>
154
-
155
- // Methods
156
- translate(key: string, params?: Record<string, string | number>): string {
157
- return this.translate.get(key, params);
158
- }
159
-
160
- switchLanguage(languageCode: string): void {
161
- this.translate.setLanguage(languageCode);
162
- }
163
- }
164
- ```
165
-
166
- **TranslateService API:**
36
+ `loadStrategy: 'modules'` defers loading until a route resolver requests a module. Use `'all'` only for small translation sets.
167
37
 
168
- | Member | Type | Description |
169
- |--------|------|-------------|
170
- | `currentLanguage` | `Signal<ILanguage>` | Active language object |
171
- | `languages` | `Signal<ILanguage[]>` | All available languages |
172
- | `isLoading` | `Signal<boolean>` | True when switching languages |
173
- | `get(key, params?)` | `string` | Translate a key synchronously |
174
- | `setLanguage(code)` | `Observable<void>` | Switch language (loads translations) |
175
- | `reloadTranslations()` | `Observable<void>` | Force reload from backend |
176
-
177
- **ILanguage Model:**
38
+ ## 2. Use TranslatePipe in Templates
178
39
 
40
+ Import `TranslatePipe` from `@flusys/ng-shared` (not from this package). The pipe is signal-reactive and re-renders automatically on language change.
41
+ language
179
42
  ```typescript
180
- interface ILanguage {
181
- id: string;
182
- name: string; // "English"
183
- nativeName: string; // "English" / "العربية"
184
- code: string; // "en" / "ar"
185
- isDefault: boolean;
186
- isRtl: boolean;
187
- isActive: boolean;
188
- flagIcon?: string; // Country flag identifier
189
- }
190
- ```
191
-
192
- ---
43
+ import { TranslatePipe } from '@flusys/ng-shared';
193
44
 
194
- ## TranslatePipe
45
+ @Component({
46
+ imports: [TranslatePipe],
47
+ template: `
48
+ <h1>{{ 'dashboard.title' | translate }}</h1>
49
+ <button>{{ 'shared.save' | translate }}</button>
50
+ <input [placeholder]="'form.search.placeholder' | translate" />
195
51
 
196
- The `TranslatePipe` from `@flusys/ng-shared` is powered by `TRANSLATE_ADAPTER`. It is signal-reactive — when the language changes, all pipes re-render automatically.
197
-
198
- ```html
199
- <!-- Basic key -->
200
- {{ 'menu.dashboard' | translate }}
201
-
202
- <!-- With interpolation parameters -->
203
- {{ 'user.greeting' | translate: { name: userName() } }}
204
-
205
- <!-- In attribute binding -->
206
- <input [placeholder]="'form.search.placeholder' | translate" />
207
-
208
- <!-- In event handler label -->
209
- <button [title]="'action.delete.tooltip' | translate">
210
- <i class="pi pi-trash"></i>
211
- </button>
212
- ```
213
-
214
- **Translation Key Convention:**
215
-
216
- ```
217
- module.component.element
52
+ <!-- With interpolation -->
53
+ <p>{{ 'pagination.showing' | translate: { from: 1, to: 10, total: 100 } }}</p>
54
+ `,
55
+ })
56
+ export class DashboardComponent {}
218
57
  ```
219
58
 
220
- Examples:
59
+ Translation values stored in the database use `{{variableName}}` syntax:
221
60
  ```
222
- common.save
223
- common.cancel
224
- common.delete
225
- auth.login.title
226
- auth.login.email.label
227
- dashboard.title
228
- menu.products
229
- pagination.showing
61
+ Key: pagination.showing
62
+ Value: Showing {{from}}-{{to}} of {{total}} results
230
63
  ```
231
64
 
232
- ---
233
-
234
- ## Variable Interpolation
65
+ ## 3. Load Translations Per Route (Module Strategy)
235
66
 
236
- Use `{{variableName}}` in translation values stored in the database:
237
-
238
- ```
239
- # In database:
240
- Key: "pagination.showing"
241
- Value: "Showing {{from}}-{{to}} of {{total}} results"
242
-
243
- # In Arabic:
244
- Key: "pagination.showing"
245
- Value: "عرض {{from}}-{{to}} من {{total}} نتيجة"
246
- ```
247
-
248
- ```html
249
- <!-- Angular template -->
250
- {{ 'pagination.showing' | translate: { from: page * pageSize + 1, to: endItem, total: total() } }}
251
- ```
252
-
253
- **Backend `messageVariables` field:**
254
-
255
- When the backend sends translation responses, it includes a `messageVariables` field with the variable names the translation expects:
256
-
257
- ```json
258
- {
259
- "key": "pagination.showing",
260
- "value": "Showing {{from}}-{{to}} of {{total}} results",
261
- "messageVariables": ["from", "to", "total"]
262
- }
263
- ```
264
-
265
- ---
266
-
267
- ## Language Management
268
-
269
- Languages are managed via the `ng-localization` admin pages:
67
+ Use `resolveTranslationModule()` in your route definition so translations for a feature load before the page renders. Pass `fallbackMessages` so the UI works even without a backend.
270
68
 
271
69
  ```typescript
272
- // app.routes.ts
273
- import { LOCALIZATION_ROUTES } from '@flusys/ng-localization';
70
+ // Inside a feature's routes file
71
+ import { resolveTranslationModule } from '@flusys/ng-shared';
72
+ import { MY_FEATURE_MESSAGES } from './constants/messages';
274
73
 
275
- export const routes: Routes = [
74
+ export const MY_ROUTES: Routes = [
276
75
  {
277
- path: 'localization',
278
- loadChildren: () => LOCALIZATION_ROUTES,
76
+ path: '',
77
+ resolve: {
78
+ translations: resolveTranslationModule({
79
+ modules: ['myFeature'], // module name used by the backend
80
+ fallbackMessages: MY_FEATURE_MESSAGES, // static fallback map
81
+ }),
82
+ },
83
+ loadComponent: () => import('./my-feature-container.component')
84
+ .then(m => m.MyFeatureContainerComponent),
85
+ children: [/* ... */],
279
86
  },
280
87
  ];
281
88
  ```
282
89
 
283
- | Route | Description |
284
- |-------|-------------|
285
- | `/localization/languages` | Manage languages (add, enable, set RTL) |
286
- | `/localization/translations` | Manage translation keys and values |
287
- | `/localization/translations/:module` | Filter by module |
90
+ Already-loaded modules are cached — navigating back to the same route does not re-fetch.
91
+
92
+ ## 4. Translate Imperatively in Services / Components
288
93
 
289
- **`TranslateApiService`** for programmatic access:
94
+ Inject `TranslateService` when you need a string outside a template (e.g., toast messages, computed labels).
290
95
 
291
96
  ```typescript
292
- import { TranslateApiService } from '@flusys/ng-localization';
97
+ import { inject } from '@angular/core';
98
+ import { TranslateService } from '@flusys/ng-localization';
293
99
 
294
- @Injectable({ ... })
295
- export class MyService {
296
- private translateApi = inject(TranslateApiService);
100
+ export class MyComponent {
101
+ private readonly translate = inject(TranslateService);
297
102
 
298
- getLanguages(): Observable<IListResponse<ILanguage>> {
299
- return this.translateApi.getLanguages();
103
+ // Synchronous — returns fallback key if not found
104
+ getLabel(): string {
105
+ return this.translate.translate('shared.save');
300
106
  }
301
107
 
302
- getTranslations(languageCode: string): Observable<Record<string, string>> {
303
- return this.translateApi.getTranslations(languageCode);
108
+ // With params
109
+ getCountLabel(count: number): string {
110
+ return this.translate.translate('items.count', { count });
304
111
  }
305
- }
306
- ```
307
-
308
- ---
309
-
310
- ## Components
311
112
 
312
- ### LanguageSelectorComponent
113
+ // Signal-based — updates automatically on language change
114
+ readonly pageTitle = this.translate.translateSignal('dashboard.title');
313
115
 
314
- Language switcher dropdown. Registered via `LAYOUT_LANGUAGE_SELECTOR` and rendered automatically in `AppTopbar`.
315
-
316
- ```html
317
- <!-- Rendered automatically in AppTopbar when provideLocalization() is called -->
318
- <!-- Manual embedding: -->
319
- <flusys-language-selector />
116
+ // Read current language code
117
+ readonly lang = this.translate.languageCode; // Signal<string>
118
+ }
320
119
  ```
321
120
 
322
- Features:
323
- - Shows current language flag + name
324
- - Dropdown lists all active languages
325
- - Switches language and reloads translations on selection
326
-
327
- ---
328
-
329
- ## Route Resolver
330
-
331
- Use the route resolver to ensure translations are loaded before a route renders:
121
+ ## 5. Add Admin Routes (Language and Key Management)
332
122
 
333
123
  ```typescript
334
- import { localizationResolver } from '@flusys/ng-localization';
335
-
336
124
  // app.routes.ts
125
+ import { LOCALIZATION_ROUTES } from '@flusys/ng-localization';
126
+
337
127
  export const routes: Routes = [
338
128
  {
339
- path: '',
340
- resolve: { translations: localizationResolver },
341
- children: [
342
- // Routes that need translations pre-loaded
343
- ],
129
+ path: 'localization',
130
+ loadChildren: () => LOCALIZATION_ROUTES,
344
131
  },
345
132
  ];
346
133
  ```
347
134
 
348
- The resolver calls `TranslateService.reloadTranslations()` if translations are stale or not yet loaded for the current language.
349
-
350
- ---
351
-
352
135
  ## RTL Support
353
136
 
354
- When a user switches to an RTL language (e.g., Arabic, Hebrew), `TranslateService` automatically updates the document direction:
355
-
356
- ```typescript
357
- // Internal behavior (automatic)
358
- document.documentElement.dir = language.isRtl ? 'rtl' : 'ltr';
359
- document.documentElement.lang = language.code;
360
- ```
361
-
362
- Tailwind CSS RTL classes (`rtl:`, `ltr:`) work automatically once the `dir` attribute is set.
363
-
364
- PrimeNG also supports RTL via the `dir` attribute on the document root — no extra configuration needed.
365
-
366
- ---
367
-
368
- ## TRANSLATE_ADAPTER Token
369
-
370
- `TRANSLATE_ADAPTER` is defined in `@flusys/ng-core`. All packages that use `TranslatePipe` or need translation access inject this token — not `TranslateService` directly. This keeps them independent of `ng-localization`.
371
-
372
- ```typescript
373
- // Defined in ng-core
374
- import { TRANSLATE_ADAPTER, ITranslateAdapter } from '@flusys/ng-core';
375
-
376
- // ITranslateAdapter interface
377
- interface ITranslateAdapter {
378
- translate(key: string, params?: Record<string, string | number>): string;
379
- currentLanguage: Signal<ILanguage>;
380
- setLanguage(code: string): Observable<void>;
381
- }
382
-
383
- // ng-localization provides the implementation:
384
- // provideLocalization() → { provide: TRANSLATE_ADAPTER, useClass: TranslateService }
385
- ```
386
-
387
- ---
388
-
389
- ## API Endpoints
390
-
391
- | Method | Endpoint | Description |
392
- |--------|----------|-------------|
393
- | POST | `/localization/language/get-all` | List all languages |
394
- | POST | `/localization/language/get/:id` | Get language by ID |
395
- | POST | `/localization/language/insert` | Add language |
396
- | POST | `/localization/language/update` | Update language |
397
- | POST | `/localization/language/delete` | Delete language |
398
- | POST | `/localization/translation/get-all` | List translations |
399
- | POST | `/localization/translation/get-by-language` | Get all keys for a language |
400
- | POST | `/localization/translation/insert` | Add translation key |
401
- | POST | `/localization/translation/update` | Update translation value |
402
- | POST | `/localization/translation/delete` | Delete translation key |
403
- | POST | `/localization/translation/bulk-update` | Atomic bulk update |
404
-
405
- ---
406
-
407
- ## Configuration Reference
408
-
409
- | Config Key | Type | Default | Description |
410
- |------------|------|---------|-------------|
411
- | `services.localization.enabled` | `boolean` | `false` | Enable localization module |
412
-
413
- Default language is configured server-side in `nestjs-localization` via `ILanguage.isDefault`.
414
-
415
- ---
416
-
417
- ## Troubleshooting
418
-
419
- **TranslatePipe shows raw key (e.g., `common.save`)**
420
-
421
- Possible causes:
422
- 1. `provideLocalization()` is missing from `app.config.ts`
423
- 2. The key doesn't exist in the database for the current language
424
- 3. Translations failed to load — check network tab for `POST /localization/translation/get-by-language`
425
-
426
- **APP_INITIALIZER delays app startup**
427
-
428
- Translations are loaded before the first render. For large translation sets, this adds startup time. Consider splitting translations by module and using the route resolver for lazy loading.
429
-
430
- **Language switch doesn't update all components**
431
-
432
- `TranslatePipe` is signal-reactive. If a component uses raw `translate.get()` (synchronous call, not pipe), it won't auto-update. Use `{{ key | translate }}` in templates instead.
433
-
434
- **RTL layout not applying**
435
-
436
- Ensure Tailwind CSS is configured for RTL classes. Add to `tailwind.config.js`:
437
-
438
- ```javascript
439
- module.exports = {
440
- // Tailwind reads dir from document root automatically
441
- // No extra config needed for rtl: classes
442
- };
443
- ```
444
-
445
- **`No provider for TRANSLATE_ADAPTER`**
446
-
447
- Add `provideLocalization()` to `app.config.ts` providers.
448
-
449
- **Missing translations after adding new keys**
450
-
451
- Call `TranslateService.reloadTranslations()` or navigate away and back (triggers the route resolver). In-memory translations are cached until the next load.
452
-
453
- ---
137
+ When the user switches to an RTL language, `LocalizationStateService` sets `document.documentElement.dir` and `lang` automatically. Tailwind `rtl:` utility classes and PrimeNG RTL support both activate from this single attribute change — no extra configuration needed.
454
138
 
455
139
  ## License
456
140