@flusys/ng-localization 4.0.0-rc

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 ADDED
@@ -0,0 +1,975 @@
1
+ # Localization Package Guide
2
+
3
+ > **Package:** `@flusys/ng-localization`
4
+ > **Version:** 4.0.0-rc
5
+ > **Type:** Multi-language support with signal-based state management, RTL support, and dynamic translation loading
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ `@flusys/ng-localization` provides a complete localization system:
12
+
13
+ - **Multi-Language Support** - Dynamic language switching at runtime
14
+ - **RTL Support** - Automatic right-to-left direction handling
15
+ - **Signal-Based State** - Reactive state management with Angular signals
16
+ - **Fallback Messages** - Static fallback messages when localization is disabled
17
+ - **Variable Interpolation** - `{{variable}}` syntax in translations
18
+ - **Lazy Loading** - Load translations per module or language
19
+ - **Language Selector** - Dropdown component for topbar integration
20
+ - **Admin Pages** - Language and translation management UI
21
+ - **Provider Pattern** - Independent via injection tokens
22
+
23
+ ### Package Hierarchy
24
+
25
+ ```
26
+ @flusys/ng-core <- Foundation (APP_CONFIG, TRANSLATE_ADAPTER token)
27
+ |
28
+ @flusys/ng-shared <- TranslatePipe (uses TRANSLATE_ADAPTER)
29
+ |
30
+ @flusys/ng-layout <- Layout (LAYOUT_LANGUAGE_SELECTOR token)
31
+ |
32
+ @flusys/ng-localization <- Localization system (THIS PACKAGE)
33
+ ```
34
+
35
+ ### Layout Integration
36
+
37
+ Language selector integrates with ng-layout via Provider Interface Pattern:
38
+
39
+ ```typescript
40
+ import { LAYOUT_LANGUAGE_SELECTOR } from '@flusys/ng-layout';
41
+
42
+ // ng-localization provides the selector component
43
+ { provide: LAYOUT_LANGUAGE_SELECTOR, useValue: LanguageSelectorComponent }
44
+
45
+ // ng-layout renders it dynamically
46
+ @if (languageSelectorComponent) {
47
+ <ng-container *ngComponentOutlet="languageSelectorComponent" />
48
+ }
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Package Architecture
54
+
55
+ ```
56
+ ng-localization/
57
+ ├── constants/
58
+ │ ├── localization.constants.ts # STORAGE_KEYS, DEFAULT_LANGUAGE_CODE
59
+ │ └── index.ts
60
+ ├── interfaces/
61
+ │ ├── language.interface.ts # ILanguage, ITranslationsMap
62
+ │ ├── localization-config.interface.ts # ILocalizationConfig, tokens
63
+ │ ├── translation-key.interface.ts # ITranslationKey
64
+ │ ├── translation.interface.ts # ITranslation
65
+ │ └── index.ts
66
+ ├── services/
67
+ │ ├── localization-state.service.ts # Signal-based state management
68
+ │ ├── localization-api.service.ts # API calls to backend
69
+ │ ├── localization-initializer.service.ts # APP_INITIALIZER bootstrap
70
+ │ ├── translate.service.ts # Translation service
71
+ │ ├── language-api.service.ts # Language CRUD API
72
+ │ ├── translation-key-api.service.ts # Translation key CRUD API
73
+ │ ├── translation-api.service.ts # Translation CRUD API
74
+ │ └── index.ts
75
+ ├── pipes/
76
+ │ └── translate.pipe.ts # Optimized pipe with signal tracking
77
+ ├── components/
78
+ │ └── language-selector/
79
+ │ └── language-selector.component.ts # Topbar language dropdown
80
+ ├── providers/
81
+ │ └── localization.providers.ts # provideLocalization(), etc.
82
+ ├── pages/
83
+ │ ├── routes.ts # LOCALIZATION_ROUTES
84
+ │ ├── language-list/ # Language management list
85
+ │ ├── language-form/ # Language create/edit form
86
+ │ └── translation-list/ # Translation management
87
+ └── public-api.ts
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Storage
93
+
94
+ ### localStorage
95
+
96
+ | Key | Purpose |
97
+ |-----|---------|
98
+ | `flusys.language` | Persists selected language code |
99
+
100
+ ### Signal State
101
+
102
+ | Signal | Type | Purpose |
103
+ |--------|------|---------|
104
+ | `currentLanguage` | `ILanguage \| null` | Current selected language |
105
+ | `availableLanguages` | `ILanguage[]` | List of available languages |
106
+ | `translations` | `ITranslationsMap` | Key-value translation map |
107
+ | `isLoading` | `boolean` | Loading state |
108
+ | `isInitialized` | `boolean` | Initialization complete |
109
+
110
+ ---
111
+
112
+ ## Route Integration
113
+
114
+ ### Adding Localization Routes
115
+
116
+ ```typescript
117
+ // app.routes.ts
118
+ import { LOCALIZATION_ROUTES } from '@flusys/ng-localization';
119
+
120
+ export const routes: Routes = [
121
+ {
122
+ path: '',
123
+ component: AppLayout,
124
+ children: [
125
+ {
126
+ path: 'localization',
127
+ children: LOCALIZATION_ROUTES,
128
+ },
129
+ ],
130
+ },
131
+ ];
132
+ ```
133
+
134
+ ### Route Structure
135
+
136
+ | Path | Component | Purpose |
137
+ |------|-----------|---------|
138
+ | `/localization/languages` | LanguageListPage | Language management |
139
+ | `/localization/languages/new` | LanguageFormPage | Create language |
140
+ | `/localization/languages/:id` | LanguageFormPage | Edit language |
141
+ | `/localization/translations` | TranslationListPage | Translation management |
142
+
143
+ ---
144
+
145
+ ## Enabled vs Disabled Behavior
146
+
147
+ The localization system has two modes controlled by provider presence:
148
+
149
+ ### When Localization is ENABLED (`provideLocalization()` provided)
150
+
151
+ | Aspect | Behavior |
152
+ |--------|----------|
153
+ | **Initialization** | Fetches active languages and translations from backend API |
154
+ | **Translation Resolution** | API translations → Fallback messages → Key |
155
+ | **API Calls** | Yes (on app init and language switch) |
156
+ | **Language Switching** | Fully functional via `LanguageSelectorComponent` |
157
+ | **RTL Support** | Dynamic based on selected language direction |
158
+ | **Error Messages** | Translated using API-loaded translations |
159
+
160
+ ### When Localization is DISABLED (only `provideFallbackLocalization()` or no provider)
161
+
162
+ | Aspect | Behavior |
163
+ |--------|----------|
164
+ | **Initialization** | Skips all API calls, immediately marks as initialized |
165
+ | **Translation Resolution** | Fallback messages → Key (no API lookup) |
166
+ | **API Calls** | None (better performance, no network dependency) |
167
+ | **Language Switching** | Not functional (no languages loaded) |
168
+ | **RTL Support** | No effect (defaults to LTR) |
169
+ | **Error Messages** | Uses hardcoded fallback messages from route resolvers |
170
+
171
+ ### Use Cases
172
+
173
+ | Scenario | Configuration |
174
+ |----------|---------------|
175
+ | **Multi-language app** | `provideLocalization(config)` with backend translations |
176
+ | **Single-language app** | `provideFallbackLocalization()` with fallback messages only |
177
+ | **Development/Testing** | `provideFallbackLocalization()` for faster startup |
178
+ | **Offline-first apps** | `provideFallbackLocalization()` with embedded messages |
179
+
180
+ ### Translation Resolution Flow
181
+
182
+ ```
183
+ TranslatePipe receives key
184
+ ↓
185
+ Check if LOCALIZATION_CONFIG exists (provider was provided)
186
+ ↓
187
+ If not → Look up in fallbackMessages (from route resolver) → Return value or key
188
+ ↓
189
+ If yes → Look up in LocalizationStateService.translations()
190
+ ↓
191
+ If not found → Look up in LocalizationStateService.fallbackMessages()
192
+ ↓
193
+ If still not found → Return original key
194
+ ```
195
+
196
+ **Note:** Fallback messages are populated per-module via route resolvers. Each module's fallbacks are merged into the state service when the route loads.
197
+
198
+ ---
199
+
200
+ ## Provider Setup
201
+
202
+ ### Basic Setup (Localization Enabled with API)
203
+
204
+ ```typescript
205
+ // app.config.ts
206
+ import { provideLocalization } from '@flusys/ng-localization';
207
+
208
+ export const appConfig: ApplicationConfig = {
209
+ providers: [
210
+ // Enable full localization with API support
211
+ ...provideLocalization({
212
+ defaultLanguageCode: 'en',
213
+ loadStrategy: 'modules',
214
+ initialModules: ['shared', 'layout'],
215
+ enableLayoutSelector: true,
216
+ }),
217
+ ],
218
+ };
219
+ ```
220
+
221
+ ### Fallback-Only Setup (Localization Disabled)
222
+
223
+ ```typescript
224
+ // app.config.ts
225
+ import { provideFallbackLocalization } from '@flusys/ng-shared';
226
+
227
+ export const appConfig: ApplicationConfig = {
228
+ providers: [
229
+ // Use fallback messages only (no API calls)
230
+ ...provideFallbackLocalization(),
231
+ ],
232
+ };
233
+ ```
234
+
235
+ ### What Providers Register
236
+
237
+ ```typescript
238
+ // provideLocalization(config)
239
+ { provide: LOCALIZATION_CONFIG, useValue: config },
240
+ LocalizationStateService,
241
+ LocalizationApiService,
242
+ LocalizationInitializerService,
243
+ TranslateService,
244
+ { provide: TRANSLATE_ADAPTER, useExisting: TranslateService },
245
+ { provide: APP_INITIALIZER, useFactory: initializeLocalization, multi: true },
246
+
247
+ // if enableLayoutSelector: true
248
+ { provide: LAYOUT_LANGUAGE_SELECTOR, useValue: LanguageSelectorComponent }
249
+ ```
250
+
251
+ ### Module-Wise Translation Loading (Mandatory)
252
+
253
+ Translations are loaded **per-module via route resolvers**, NOT at app initialization. This provides:
254
+
255
+ - **Faster initial load** - Only load translations needed for current route
256
+ - **Smaller memory footprint** - Each route loads only its module's translations
257
+ - **On-demand loading** - Translations fetched when navigating to a feature
258
+
259
+ ### How It Works
260
+
261
+ 1. **App initialization** - Only loads available languages from API (no translations)
262
+ 2. **Route navigation** - Route resolver loads that module's translations
263
+ 3. **Translations merge** - New module translations merge with existing ones
264
+
265
+ ### Setup Pattern
266
+
267
+ **Step 1: Configure main app layout with shared/layout modules and their fallback messages**
268
+
269
+ ```typescript
270
+ // app.routes.ts
271
+ import { resolveTranslationModule, SHARED_MESSAGES, LAYOUT_MESSAGES } from '@flusys/ng-shared';
272
+
273
+ export const routes: Routes = [
274
+ {
275
+ path: '',
276
+ component: AppLayout,
277
+ canActivate: [appInitGuard],
278
+ // Load shared + layout translations with fallback messages
279
+ resolve: {
280
+ translations: resolveTranslationModule({
281
+ modules: ['shared', 'layout'],
282
+ fallbackMessages: { ...SHARED_MESSAGES, ...LAYOUT_MESSAGES }
283
+ })
284
+ },
285
+ children: [
286
+ { path: 'storage', children: STORAGE_ROUTES },
287
+ { path: 'iam', children: IAM_ROUTES },
288
+ // ...
289
+ ],
290
+ },
291
+ ];
292
+ ```
293
+
294
+ **Step 2: Each feature module loads its own translations with its fallback messages**
295
+
296
+ ```typescript
297
+ // storage.routes.ts
298
+ import { resolveTranslationModule, SHARED_MESSAGES } from '@flusys/ng-shared';
299
+ import { STORAGE_MESSAGES } from '@flusys/ng-storage';
300
+
301
+ export const STORAGE_ROUTES: Routes = [
302
+ {
303
+ path: '',
304
+ resolve: {
305
+ translations: resolveTranslationModule({
306
+ modules: ['storage'],
307
+ fallbackMessages: STORAGE_MESSAGES
308
+ })
309
+ },
310
+ children: [
311
+ // Storage pages...
312
+ ],
313
+ },
314
+ ];
315
+ ```
316
+
317
+ ### Route Resolver Interface
318
+
319
+ ```typescript
320
+ interface IResolverModuleConfig {
321
+ modules: string[]; // Module names to load
322
+ fallbackMessages?: ITranslationsMap; // Fallback messages for these modules
323
+ }
324
+ ```
325
+
326
+ ### Route Resolver Function
327
+
328
+ | Function | Usage | Example |
329
+ |----------|-------|---------|
330
+ | `resolveTranslationModule(config)` | Load modules with fallback messages | `resolveTranslationModule({ modules: ['auth'], fallbackMessages: AUTH_MESSAGES })` |
331
+
332
+ ### Available Modules
333
+
334
+ > **Key Format:** All translation keys use **full dot-case** — every word separated by a dot, all lowercase. No camelCase anywhere in keys.
335
+ > ```
336
+ > ✅ event.manager.form.title.required
337
+ > ❌ eventManager.formTitleRequired
338
+ > ```
339
+
340
+ | Module | Keys Prefix | Package | Route |
341
+ |--------|-------------|---------|-------|
342
+ | `shared` | `shared.*` | ng-shared | Main layout (always loaded) |
343
+ | `layout` | `layout.*` | ng-layout | Main layout (always loaded) |
344
+ | `auth` | `auth.*` | ng-auth | `/auth/*`, `/administration/*` |
345
+ | `iam` | `iam.*` | ng-iam | `/iam/*` |
346
+ | `storage` | `storage.*` | ng-storage | `/storage/*` |
347
+ | `formBuilder` | `formBuilder.*` | ng-form-builder | `/forms/*` |
348
+ | `email` | `email.*` | ng-email | `/email/*` |
349
+ | `notification` | `notification.*` | ng-notification | `/notifications/*` |
350
+ | `eventManager` | `event.manager.*` | ng-event-manager | `/event-manager/*` |
351
+ | `localization` | `localization.*` | ng-localization | `/localization/*` |
352
+
353
+ ### Module Loading Flow
354
+
355
+ ```
356
+ User navigates to /storage/folders
357
+ ↓
358
+ Router activates STORAGE_ROUTES
359
+ ↓
360
+ Route resolver resolveTranslationModule(config) executes
361
+ ↓
362
+ Register fallback messages for the module
363
+ ↓
364
+ Check if LOCALIZATION_CONFIG exists (provider was used)
365
+ ↓
366
+ If not → Return immediately (use fallback messages)
367
+ ↓
368
+ POST /localization/translation/get-by-language
369
+ { languageCode: currentLanguage, modules: ['storage'] }
370
+ ↓
371
+ LocalizationStateService.mergeTranslations(newTranslations)
372
+ ↓
373
+ Component renders with storage translations available
374
+ ```
375
+
376
+ ### Best Practice: Hierarchical Module Loading with Per-Module Fallbacks
377
+
378
+ Parent routes should load common modules, child routes load specific ones. Each route provides its own fallback messages:
379
+
380
+ ```typescript
381
+ // Main layout loads shared + layout (needed everywhere)
382
+ {
383
+ path: '',
384
+ component: AppLayout,
385
+ resolve: {
386
+ translations: resolveTranslationModule({
387
+ modules: ['shared', 'layout'],
388
+ fallbackMessages: { ...SHARED_MESSAGES, ...LAYOUT_MESSAGES }
389
+ })
390
+ },
391
+ children: [
392
+ // Feature routes load their own modules with their fallbacks
393
+ {
394
+ path: 'auth',
395
+ resolve: { translations: resolveTranslationModule({ modules: ['auth'], fallbackMessages: AUTH_MESSAGES }) },
396
+ ...
397
+ },
398
+ {
399
+ path: 'storage',
400
+ resolve: { translations: resolveTranslationModule({ modules: ['storage'], fallbackMessages: STORAGE_MESSAGES }) },
401
+ ...
402
+ },
403
+ ],
404
+ }
405
+ ```
406
+
407
+ This ensures:
408
+ - `shared` and `layout` translations are available immediately after login
409
+ - Feature-specific translations load only when needed
410
+ - Each module has its own fallback messages that load with the module
411
+ - No need to load ALL_MESSAGES at app startup
412
+
413
+ ---
414
+
415
+ ## Interfaces
416
+
417
+ ### ILanguage
418
+
419
+ ```typescript
420
+ interface ILanguage {
421
+ id: string;
422
+ code: string; // ISO 639-1 code (e.g., 'en', 'ar')
423
+ name: string; // English name
424
+ nativeName: string | null; // Native name (e.g., 'العربية')
425
+ direction: 'ltr' | 'rtl' | null;
426
+ isActive: boolean;
427
+ isDefault: boolean;
428
+ serial: number | null;
429
+ }
430
+ ```
431
+
432
+ ### ITranslationsMap
433
+
434
+ ```typescript
435
+ type ITranslationsMap = Record<string, string>;
436
+
437
+ // Example
438
+ {
439
+ 'shared.save': 'Save',
440
+ 'shared.cancel': 'Cancel',
441
+ 'auth.login.title': 'Login',
442
+ }
443
+ ```
444
+
445
+ ### ILocalizationConfig
446
+
447
+ ```typescript
448
+ interface ILocalizationConfig {
449
+ defaultLanguageCode: string; // Default language code (e.g., 'en')
450
+ loadStrategy?: 'all' | 'modules'; // 'all' = load all translations, 'modules' = load per-module (default: 'modules')
451
+ initialModules?: string[]; // Modules to load on app initialization (default: ['shared', 'layout'])
452
+ enableLayoutSelector?: boolean; // Enable language selector in layout (default: false)
453
+ }
454
+ ```
455
+
456
+ **Note:** Translations are always loaded per-module via route resolvers. The config only affects initialization behavior.
457
+
458
+ ### ITranslationKey
459
+
460
+ ```typescript
461
+ interface ITranslationKey {
462
+ id: string;
463
+ module: string; // Module grouping (e.g., 'auth', 'shared')
464
+ key: string; // Translation key (e.g., 'auth.login.title')
465
+ defaultMessage: string; // Default message (fallback)
466
+ description: string | null;
467
+ variables: string[] | null; // Required variables ['name', 'count']
468
+ isActive: boolean;
469
+ isReadonly: boolean; // System keys cannot be deleted
470
+ serial: number | null;
471
+ }
472
+ ```
473
+
474
+ **Note:** Keys with `isReadonly: true` are system keys that cannot be deleted by users. The `module + key` combination must be unique (you cannot have duplicate keys within the same module).
475
+
476
+ ### ITranslation
477
+
478
+ ```typescript
479
+ interface ITranslation {
480
+ id: string;
481
+ languageId: string;
482
+ translationKeyId: string;
483
+ value: string;
484
+ isVerified: boolean;
485
+ isActive: boolean;
486
+ }
487
+ ```
488
+
489
+ ---
490
+
491
+ ## Tokens
492
+
493
+ ### LOCALIZATION_CONFIG
494
+
495
+ Configuration for localization behavior:
496
+
497
+ ```typescript
498
+ export const LOCALIZATION_CONFIG = new InjectionToken<ILocalizationConfig>('LOCALIZATION_CONFIG');
499
+ ```
500
+
501
+ ### LOCALIZATION_MODULE_FALLBACKS
502
+
503
+ Module-specific fallback messages (populated via route resolvers):
504
+
505
+ ```typescript
506
+ export const LOCALIZATION_MODULE_FALLBACKS = new InjectionToken<Map<string, ITranslationsMap>>('LOCALIZATION_MODULE_FALLBACKS');
507
+ ```
508
+
509
+ **Note:** Fallback messages are now loaded per-module via route resolvers instead of globally at app startup. Use `resolveTranslationModule({ modules: [...], fallbackMessages: ... })` to register fallbacks for each module.
510
+
511
+ ### TRANSLATE_ADAPTER (from ng-core)
512
+
513
+ Adapter interface for translation service:
514
+
515
+ ```typescript
516
+ interface ITranslateAdapter {
517
+ translate(key: string, variables?: Record<string, string | number>): string;
518
+ }
519
+
520
+ export const TRANSLATE_ADAPTER = new InjectionToken<ITranslateAdapter>('TRANSLATE_ADAPTER');
521
+ ```
522
+
523
+ ---
524
+
525
+ ## Services
526
+
527
+ ### LocalizationStateService
528
+
529
+ Central state management with signals:
530
+
531
+ ```typescript
532
+ @Injectable({ providedIn: 'root' })
533
+ export class LocalizationStateService {
534
+ // Public readonly signals
535
+ readonly currentLanguage: Signal<ILanguage | null>;
536
+ readonly availableLanguages: Signal<ILanguage[]>;
537
+ readonly translations: Signal<ITranslationsMap>;
538
+ readonly isLoading: Signal<boolean>;
539
+ readonly isInitialized: Signal<boolean>;
540
+
541
+ // Computed signals
542
+ readonly currentLanguageCode: Signal<string>; // Defaults to 'en'
543
+ readonly isRtl: Signal<boolean>;
544
+
545
+ // Methods
546
+ setCurrentLanguage(language: ILanguage): void;
547
+ setAvailableLanguages(languages: ILanguage[]): void;
548
+ setTranslations(translations: ITranslationsMap): void;
549
+ mergeTranslations(translations: ITranslationsMap): void;
550
+ clearTranslations(): void;
551
+ setLoading(loading: boolean): void;
552
+ setInitialized(initialized: boolean): void;
553
+ getSavedLanguageCode(): string | null;
554
+ }
555
+ ```
556
+
557
+ **Automatic Features:**
558
+ - Updates `document.dir` on language change (ltr/rtl)
559
+ - Updates `document.lang` attribute
560
+ - Persists language code to localStorage
561
+
562
+ ### TranslateService
563
+
564
+ Translation with variable interpolation:
565
+
566
+ ```typescript
567
+ @Injectable({ providedIn: 'root' })
568
+ export class TranslateService {
569
+ // Synchronous translation
570
+ translate(key: string, variables?: Record<string, string | number>): string;
571
+
572
+ // Signal-based translation (reactive)
573
+ translateSignal(key: string, variables?: Record<string, string | number>): Signal<string>;
574
+
575
+ // Alias for translate()
576
+ instant(key: string, variables?: Record<string, string | number>): string;
577
+ }
578
+ ```
579
+
580
+ **Translation Resolution Order:**
581
+ 1. If `isActive: false`, use fallback messages
582
+ 2. Look up key in current language translations
583
+ 3. Fall back to `LOCALIZATION_FALLBACK_MESSAGES`
584
+ 4. Return key if not found anywhere
585
+
586
+ ### LocalizationApiService
587
+
588
+ API calls to backend:
589
+
590
+ ```typescript
591
+ @Injectable({ providedIn: 'root' })
592
+ export class LocalizationApiService {
593
+ // Get active languages (no auth required)
594
+ getActiveLanguages(): Observable<IListResponse<ILanguage>>;
595
+
596
+ // Get translations for one or more modules in a language (no auth required)
597
+ getTranslationsByLanguage(languageCode: string, modules?: string[]): Observable<ISingleResponse<ITranslationsMap>>;
598
+ }
599
+ ```
600
+
601
+ ### LocalizationInitializerService
602
+
603
+ APP_INITIALIZER bootstrap (languages only, NOT translations):
604
+
605
+ ```typescript
606
+ @Injectable({ providedIn: 'root' })
607
+ export class LocalizationInitializerService {
608
+ async initialize(): Promise<void>;
609
+ }
610
+ ```
611
+
612
+ **Initialization Flow:**
613
+ 1. Check if `LOCALIZATION_CONFIG` is provided (if not, mark initialized and return)
614
+ 2. Load available languages from API
615
+ 3. Determine language: saved → default → first available
616
+ 4. Set current language
617
+ 5. Mark as initialized
618
+
619
+ **Important:** Translations are NOT loaded during initialization. They are loaded per-module via route resolvers (`resolveTranslationModule`). This ensures faster app startup and on-demand translation loading
620
+
621
+ ---
622
+
623
+ ## Pipes
624
+
625
+ ### TranslatePipe
626
+
627
+ Optimized pipe with signal-based language change detection:
628
+
629
+ ```typescript
630
+ @Pipe({
631
+ name: 'translate',
632
+ pure: false, // Reacts to language changes
633
+ })
634
+ export class TranslatePipe implements PipeTransform {
635
+ transform(key: string, variables?: Record<string, string | number>): string;
636
+ }
637
+ ```
638
+
639
+ **Note:** `standalone: true` is implicit in Angular 21 and not required in decorators.
640
+
641
+ **Usage:**
642
+
643
+ ```html
644
+ <!-- Simple translation -->
645
+ <span>{{ 'shared.save' | translate }}</span>
646
+
647
+ <!-- With variables -->
648
+ <span>{{ 'shared.welcome' | translate: { name: userName } }}</span>
649
+ ```
650
+
651
+ ---
652
+
653
+ ## Components
654
+
655
+ ### LanguageSelectorComponent
656
+
657
+ Topbar language dropdown:
658
+
659
+ ```typescript
660
+ @Component({
661
+ selector: 'lib-language-selector',
662
+ standalone: true,
663
+ })
664
+ export class LanguageSelectorComponent {
665
+ readonly languages: Signal<ILanguage[]>;
666
+ selectedLanguageCode: WritableSignal<string>;
667
+
668
+ onLanguageChange(code: string): void;
669
+ }
670
+ ```
671
+
672
+ **Features:**
673
+ - PrimeNG p-select dropdown
674
+ - Shows language code in selected item (uppercase)
675
+ - Shows native name in dropdown options
676
+ - Automatically loads translations on change
677
+
678
+ ---
679
+
680
+ ## Backend Integration
681
+
682
+ All endpoints use **POST-only RPC** style (not REST).
683
+
684
+ ### API Endpoints
685
+
686
+ | Endpoint | Auth | Description |
687
+ |----------|------|-------------|
688
+ | `POST /localization/language/get-active` | None | Get active languages |
689
+ | `POST /localization/language/get-default` | None | Get default language |
690
+ | `POST /localization/translation/get-by-language` | None | Get translations by language code |
691
+ | `POST /localization/translation/get-by-module` | None | Get translations by module + language |
692
+
693
+ ### Admin Endpoints
694
+
695
+ | Endpoint | Permission | Description |
696
+ |----------|------------|-------------|
697
+ | `POST /localization/language/insert` | `language.create` | Create language |
698
+ | `POST /localization/language/update` | `language.update` | Update language |
699
+ | `POST /localization/language/delete` | `language.delete` | Delete language |
700
+ | `POST /localization/language/set-default` | `language.update` | Set default language |
701
+ | `POST /localization/translation/update-many-values` | `translation.update` | Bulk update translations |
702
+ | `POST /localization/translation/get-missing` | `translation.read` | Get missing translations |
703
+
704
+ ---
705
+
706
+ ## Data Flow
707
+
708
+ ### Initialization Flow (Languages Only)
709
+
710
+ ```
711
+ App bootstraps
712
+ ↓
713
+ APP_INITIALIZER runs LocalizationInitializerService.initialize()
714
+ ↓
715
+ Check if LOCALIZATION_CONFIG was provided
716
+ ↓
717
+ If not provided → setInitialized(true) and return (use fallback only)
718
+ ↓
719
+ POST /localization/language/get-active
720
+ ↓
721
+ Set available languages
722
+ ↓
723
+ Determine language (localStorage → default → first)
724
+ ↓
725
+ Set current language, mark initialized
726
+ ↓
727
+ App ready (NO translations loaded yet)
728
+ ↓
729
+ User navigates to protected route
730
+ ↓
731
+ Route resolver loads shared + layout translations + fallback messages
732
+ ↓
733
+ User navigates to feature route
734
+ ↓
735
+ Route resolver loads feature-specific translations + fallback messages
736
+ ```
737
+
738
+ **Key Point:** Translations are NOT loaded during app initialization. They are loaded on-demand via route resolvers when navigating to each feature. Fallback messages are always available regardless of whether localization is enabled.
739
+
740
+ ### Language Switch Flow
741
+
742
+ ```
743
+ User selects language in dropdown
744
+ ↓
745
+ LanguageSelectorComponent.onLanguageChange(code)
746
+ ↓
747
+ LocalizationStateService.setCurrentLanguage(language)
748
+ ↓
749
+ Effect updates document.dir, document.lang, localStorage
750
+ ↓
751
+ POST /localization/translation/get-by-language
752
+ ↓
753
+ LocalizationStateService.setTranslations(translations)
754
+ ↓
755
+ TranslatePipe re-evaluates (pure: false)
756
+ ↓
757
+ UI updates with new translations
758
+ ```
759
+
760
+ ### Translation Resolution Flow
761
+
762
+ ```
763
+ {{ 'some.key' | translate: { var: value } }}
764
+ ↓
765
+ TranslatePipe.transform(key, variables)
766
+ ↓
767
+ TRANSLATE_ADAPTER.translate(key, variables)
768
+ ↓
769
+ TranslateService.translate(key, variables)
770
+ ↓
771
+ Check if LOCALIZATION_CONFIG exists
772
+ ↓
773
+ If not provided → Look up in fallback messages (from route resolver)
774
+ ↓
775
+ If provided → Look up in LocalizationStateService.translations()
776
+ ↓
777
+ If found → Interpolate variables and return
778
+ ↓
779
+ If not found → Look up in LocalizationStateService.fallbackMessages()
780
+ ↓
781
+ If not found → Return original key
782
+ ```
783
+
784
+ ---
785
+
786
+ ## Usage Examples
787
+
788
+ ### Basic Translation
789
+
790
+ ```typescript
791
+ import { TranslateService, TranslatePipe } from '@flusys/ng-localization';
792
+
793
+ @Component({
794
+ imports: [TranslatePipe],
795
+ template: `
796
+ <h1>{{ 'auth.login.title' | translate }}</h1>
797
+ <button>{{ saveLabel() }}</button>
798
+ `,
799
+ })
800
+ export class MyComponent {
801
+ private readonly translateService = inject(TranslateService);
802
+
803
+ // Reactive signal-based translation
804
+ readonly saveLabel = this.translateService.translateSignal('shared.save');
805
+
806
+ // Imperative translation
807
+ showMessage() {
808
+ const message = this.translateService.translate('shared.success');
809
+ console.log(message);
810
+ }
811
+ }
812
+ ```
813
+
814
+ ### With Variables
815
+
816
+ ```typescript
817
+ // Translation key: "shared.welcome": "Welcome, {{name}}!"
818
+ const greeting = translateService.translate('shared.welcome', { name: 'John' });
819
+ // Output: "Welcome, John!"
820
+
821
+ // Template
822
+ {{ 'shared.itemCount' | translate: { count: items.length } }}
823
+ ```
824
+
825
+ ### Checking Current Language
826
+
827
+ ```typescript
828
+ import { LocalizationStateService } from '@flusys/ng-localization';
829
+
830
+ @Component({})
831
+ export class MyComponent {
832
+ private readonly localizationState = inject(LocalizationStateService);
833
+
834
+ readonly currentLang = this.localizationState.currentLanguageCode;
835
+ readonly isRtl = this.localizationState.isRtl;
836
+
837
+ // Use in template
838
+ // [dir]="isRtl() ? 'rtl' : 'ltr'"
839
+ }
840
+ ```
841
+
842
+ ### Manual Language Switch
843
+
844
+ ```typescript
845
+ import { LocalizationStateService, LocalizationApiService } from '@flusys/ng-localization';
846
+
847
+ @Component({})
848
+ export class MyComponent {
849
+ private readonly localizationState = inject(LocalizationStateService);
850
+ private readonly localizationApi = inject(LocalizationApiService);
851
+
852
+ async switchToArabic() {
853
+ const language = this.localizationState.availableLanguages()
854
+ .find(l => l.code === 'ar');
855
+
856
+ if (language) {
857
+ this.localizationState.setCurrentLanguage(language);
858
+
859
+ const response = await firstValueFrom(
860
+ this.localizationApi.getTranslationsByLanguage('ar')
861
+ );
862
+
863
+ if (response.success && response.data) {
864
+ this.localizationState.setTranslations(response.data);
865
+ }
866
+ }
867
+ }
868
+ }
869
+ ```
870
+
871
+ ---
872
+
873
+ ## Fallback Messages
874
+
875
+ ### Available Message Sets
876
+
877
+ | File | Export | Module |
878
+ |------|--------|--------|
879
+ | `shared.messages.ts` | `SHARED_MESSAGES` | Common actions, labels, validation |
880
+ | `auth.messages.ts` | `AUTH_MESSAGES` | Login, logout, password |
881
+ | `iam.messages.ts` | `IAM_MESSAGES` | Roles, permissions, companies |
882
+ | `storage.messages.ts` | `STORAGE_MESSAGES` | File upload/download |
883
+ | `form-builder.messages.ts` | `FORM_BUILDER_MESSAGES` | Form builder |
884
+ | `email.messages.ts` | `EMAIL_MESSAGES` | Email |
885
+ | `notification.messages.ts` | `NOTIFICATION_MESSAGES` | Notifications |
886
+ | `event-manager.messages.ts` | `EVENT_MANAGER_MESSAGES` | Calendar, events |
887
+ | `localization.messages.ts` | `LOCALIZATION_MESSAGES` | Language & translation management |
888
+ | `index.ts` | `ALL_MESSAGES` | Combined export of all messages |
889
+
890
+ ### Using Fallback Messages
891
+
892
+ ```typescript
893
+ import { ALL_MESSAGES, SHARED_MESSAGES, AUTH_MESSAGES } from '@flusys/ng-shared';
894
+
895
+ // All messages combined
896
+ provideLocalizationFallback(ALL_MESSAGES)
897
+
898
+ // Or specific modules only
899
+ provideLocalizationFallback({
900
+ ...SHARED_MESSAGES,
901
+ ...AUTH_MESSAGES,
902
+ })
903
+ ```
904
+
905
+ ---
906
+
907
+ ## RTL Support
908
+
909
+ When a language with `direction: 'rtl'` is selected:
910
+
911
+ 1. `document.documentElement.dir` is set to `'rtl'`
912
+ 2. `document.documentElement.lang` is set to the language code
913
+ 3. `LocalizationStateService.isRtl()` returns `true`
914
+
915
+ **Usage in Components:**
916
+
917
+ ```typescript
918
+ @Component({
919
+ host: {
920
+ '[class.rtl]': 'localizationState.isRtl()',
921
+ },
922
+ })
923
+ export class MyComponent {
924
+ readonly localizationState = inject(LocalizationStateService);
925
+ }
926
+ ```
927
+
928
+ ---
929
+
930
+ ## Best Practices
931
+
932
+ | Practice | Description |
933
+ |----------|-------------|
934
+ | **Use TranslatePipe** | Prefer pipe over service for template translations |
935
+ | **Signal-Based Translations** | Use `translateSignal()` for computed values that need to react to language changes |
936
+ | **Organize Keys by Module** | Use `module.feature.action` naming (e.g., `auth.login.title`) |
937
+ | **Provide Fallbacks** | Always provide `LOCALIZATION_FALLBACK_MESSAGES` |
938
+ | **Handle Loading State** | Check `isInitialized()` before rendering translated content |
939
+ | **RTL-Aware Styles** | Use logical CSS properties (margin-inline-start vs margin-left) |
940
+
941
+ ---
942
+
943
+ ## Public API Exports
944
+
945
+ | Category | Exports |
946
+ |----------|---------|
947
+ | **Interfaces** | `ILanguage`, `ITranslationsMap`, `ILocalizationConfig`, `LocalizationLoadStrategy`, `ITranslationKey`, `ITranslation`, `IResolverModuleConfig` |
948
+ | **Tokens** | `LOCALIZATION_CONFIG`, `LOCALIZATION_MODULE_FALLBACKS` |
949
+ | **Constants** | `STORAGE_KEYS`, `DEFAULT_LANGUAGE_CODE` |
950
+ | **Services** | `LocalizationStateService`, `LocalizationApiService`, `LocalizationInitializerService`, `TranslateService`, `LanguageApiService`, `TranslationKeyApiService`, `TranslationApiService` |
951
+ | **Pipes** | `TranslatePipe` (optimized with signal tracking) |
952
+ | **Components** | `LanguageSelectorComponent` |
953
+ | **Providers** | `provideLocalization` |
954
+ | **Routes** | `LOCALIZATION_ROUTES` |
955
+ | **Pages** | `LanguageListPage`, `LanguageFormPage`, `TranslationListPage` |
956
+
957
+ **Note:** `resolveTranslationModule` is now exported from `@flusys/ng-shared` for use across all packages. The ng-localization package uses it internally but consumers should import from ng-shared.
958
+
959
+ ---
960
+
961
+ **Last Updated:** 2026-03-03
962
+ **Version:** 3.0.3
963
+ **Angular Version:** 21
964
+
965
+ ---
966
+
967
+ ## Latest Changes (v3.0.3)
968
+
969
+ - **Removed deprecated `resolveTranslationModules()` function** - Use `resolveTranslationModule()` with config object
970
+ - **Removed unused `getDefaultLanguage()` and `getTranslationsByModule()` from LocalizationApiService**
971
+ - **Consolidated translation loading logic** in `TranslationLoaderService` for better maintainability
972
+ - **Moved `resolveTranslationModule` to ng-shared** - Now part of base package, not ng-localization
973
+ - **Simplified ILocalizationConfig** - Removed `isActive` and `cacheTimeout` properties
974
+ - **Behavior change** - Localization presence is determined by provider existence, not `isActive` property
975
+ - **Removed `standalone: true`** from TranslatePipe (implicit in Angular 21)