@flusys/ng-localization 4.0.0-lts
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 +975 -0
- package/fesm2022/flusys-ng-localization.mjs +3303 -0
- package/fesm2022/flusys-ng-localization.mjs.map +1 -0
- package/package.json +33 -0
- package/types/flusys-ng-localization.d.ts +506 -0
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)
|