@flusys/ng-localization 4.0.1 → 4.1.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 +279 -797
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,975 +1,457 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @flusys/ng-localization
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> **Version:** 4.0.1
|
|
5
|
-
> **Type:** Multi-language support with signal-based state management, RTL support, and dynamic translation loading
|
|
3
|
+
> Database-driven internationalization (i18n) library for the FLUSYS Angular platform — multi-language support, RTL/LTR, TranslatePipe, language selector, and Angular APP_INITIALIZER bootstrap.
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
```
|
|
5
|
+
[](https://www.npmjs.com/package/@flusys/ng-localization)
|
|
6
|
+
[](https://angular.io)
|
|
7
|
+
[](https://www.typescriptlang.org)
|
|
8
|
+
[](LICENSE)
|
|
50
9
|
|
|
51
10
|
---
|
|
52
11
|
|
|
53
|
-
##
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
```
|
|
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)
|
|
89
33
|
|
|
90
34
|
---
|
|
91
35
|
|
|
92
|
-
##
|
|
93
|
-
|
|
94
|
-
### localStorage
|
|
95
|
-
|
|
96
|
-
| Key | Purpose |
|
|
97
|
-
|-----|---------|
|
|
98
|
-
| `flusys.language` | Persists selected language code |
|
|
36
|
+
## Overview
|
|
99
37
|
|
|
100
|
-
|
|
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`.
|
|
101
39
|
|
|
102
|
-
|
|
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 |
|
|
40
|
+
Unlike file-based i18n (ngx-translate), FLUSYS translations are managed in the database and can be updated at runtime without rebuilding.
|
|
109
41
|
|
|
110
42
|
---
|
|
111
43
|
|
|
112
|
-
##
|
|
44
|
+
## Features
|
|
113
45
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 |
|
|
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
|
|
142
55
|
|
|
143
56
|
---
|
|
144
57
|
|
|
145
|
-
##
|
|
146
|
-
|
|
147
|
-
The localization system has two modes controlled by provider presence:
|
|
58
|
+
## Compatibility
|
|
148
59
|
|
|
149
|
-
|
|
60
|
+
| Package | Version |
|
|
61
|
+
|---------|---------|
|
|
62
|
+
| Angular | 21+ |
|
|
63
|
+
| @flusys/ng-core | 4.x |
|
|
64
|
+
| @flusys/ng-shared | 4.x |
|
|
150
65
|
|
|
151
|
-
|
|
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 |
|
|
66
|
+
---
|
|
179
67
|
|
|
180
|
-
|
|
68
|
+
## Installation
|
|
181
69
|
|
|
70
|
+
```bash
|
|
71
|
+
npm install @flusys/ng-localization @flusys/ng-core @flusys/ng-shared
|
|
182
72
|
```
|
|
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
73
|
|
|
198
74
|
---
|
|
199
75
|
|
|
200
|
-
##
|
|
76
|
+
## Quick Start
|
|
201
77
|
|
|
202
|
-
###
|
|
78
|
+
### 1. Enable Localization in Config
|
|
203
79
|
|
|
204
80
|
```typescript
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
// Enable full localization with API support
|
|
211
|
-
...provideLocalization({
|
|
212
|
-
defaultLanguageCode: 'en',
|
|
213
|
-
loadStrategy: 'modules',
|
|
214
|
-
initialModules: ['shared', 'layout'],
|
|
215
|
-
enableLayoutSelector: true,
|
|
216
|
-
}),
|
|
217
|
-
],
|
|
81
|
+
// environments/environment.ts
|
|
82
|
+
export const environment = {
|
|
83
|
+
services: {
|
|
84
|
+
localization: { enabled: true },
|
|
85
|
+
},
|
|
218
86
|
};
|
|
219
87
|
```
|
|
220
88
|
|
|
221
|
-
###
|
|
89
|
+
### 2. Register Localization Providers
|
|
222
90
|
|
|
223
91
|
```typescript
|
|
224
92
|
// app.config.ts
|
|
225
|
-
import {
|
|
93
|
+
import { provideLocalization } from '@flusys/ng-localization';
|
|
226
94
|
|
|
227
95
|
export const appConfig: ApplicationConfig = {
|
|
228
96
|
providers: [
|
|
229
|
-
|
|
230
|
-
|
|
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)
|
|
231
102
|
],
|
|
232
103
|
};
|
|
233
104
|
```
|
|
234
105
|
|
|
235
|
-
###
|
|
106
|
+
### 3. Use TranslatePipe in Templates
|
|
236
107
|
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
{
|
|
240
|
-
|
|
241
|
-
|
|
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
|
|
108
|
+
```html
|
|
109
|
+
<!-- Basic translation -->
|
|
110
|
+
<span>{{ 'common.save' | translate }}</span>
|
|
111
|
+
<button>{{ 'common.cancel' | translate }}</button>
|
|
112
|
+
<h1>{{ 'dashboard.title' | translate }}</h1>
|
|
258
113
|
|
|
259
|
-
|
|
114
|
+
<!-- With parameters -->
|
|
115
|
+
<p>{{ 'pagination.showing' | translate: { from: 1, to: 10, total: 100 } }}</p>
|
|
116
|
+
```
|
|
260
117
|
|
|
261
|
-
|
|
262
|
-
2. **Route navigation** - Route resolver loads that module's translations
|
|
263
|
-
3. **Translations merge** - New module translations merge with existing ones
|
|
118
|
+
---
|
|
264
119
|
|
|
265
|
-
|
|
120
|
+
## Module Registration
|
|
266
121
|
|
|
267
|
-
|
|
122
|
+
### `provideLocalization()`
|
|
268
123
|
|
|
269
124
|
```typescript
|
|
270
|
-
|
|
271
|
-
import { resolveTranslationModule, SHARED_MESSAGES, LAYOUT_MESSAGES } from '@flusys/ng-shared';
|
|
125
|
+
import { provideLocalization } from '@flusys/ng-localization';
|
|
272
126
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
];
|
|
127
|
+
providers: [
|
|
128
|
+
...provideLocalization(),
|
|
129
|
+
]
|
|
292
130
|
```
|
|
293
131
|
|
|
294
|
-
|
|
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
|
|
295
136
|
|
|
296
|
-
|
|
297
|
-
// storage.routes.ts
|
|
298
|
-
import { resolveTranslationModule, SHARED_MESSAGES } from '@flusys/ng-shared';
|
|
299
|
-
import { STORAGE_MESSAGES } from '@flusys/ng-storage';
|
|
137
|
+
---
|
|
300
138
|
|
|
301
|
-
|
|
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
|
-
```
|
|
139
|
+
## TranslateService
|
|
316
140
|
|
|
317
|
-
|
|
141
|
+
The core service managing translations and language state:
|
|
318
142
|
|
|
319
143
|
```typescript
|
|
320
|
-
|
|
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
|
|
144
|
+
import { TranslateService } from '@flusys/ng-localization';
|
|
333
145
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
-
```
|
|
146
|
+
@Component({ ... })
|
|
147
|
+
export class MyComponent {
|
|
148
|
+
private translate = inject(TranslateService);
|
|
375
149
|
|
|
376
|
-
|
|
150
|
+
// Signals
|
|
151
|
+
currentLanguage = this.translate.currentLanguage; // Signal<ILanguage>
|
|
152
|
+
availableLanguages = this.translate.languages; // Signal<ILanguage[]>
|
|
153
|
+
isLoading = this.translate.isLoading; // Signal<boolean>
|
|
377
154
|
|
|
378
|
-
|
|
155
|
+
// Methods
|
|
156
|
+
translate(key: string, params?: Record<string, string | number>): string {
|
|
157
|
+
return this.translate.get(key, params);
|
|
158
|
+
}
|
|
379
159
|
|
|
380
|
-
|
|
381
|
-
|
|
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
|
-
],
|
|
160
|
+
switchLanguage(languageCode: string): void {
|
|
161
|
+
this.translate.setLanguage(languageCode);
|
|
162
|
+
}
|
|
404
163
|
}
|
|
405
164
|
```
|
|
406
165
|
|
|
407
|
-
|
|
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
|
-
---
|
|
166
|
+
**TranslateService API:**
|
|
414
167
|
|
|
415
|
-
|
|
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 |
|
|
416
176
|
|
|
417
|
-
|
|
177
|
+
**ILanguage Model:**
|
|
418
178
|
|
|
419
179
|
```typescript
|
|
420
180
|
interface ILanguage {
|
|
421
181
|
id: string;
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
direction: 'ltr' | 'rtl' | null;
|
|
426
|
-
isActive: boolean;
|
|
182
|
+
name: string; // "English"
|
|
183
|
+
nativeName: string; // "English" / "العربية"
|
|
184
|
+
code: string; // "en" / "ar"
|
|
427
185
|
isDefault: boolean;
|
|
428
|
-
|
|
186
|
+
isRtl: boolean;
|
|
187
|
+
isActive: boolean;
|
|
188
|
+
flagIcon?: string; // Country flag identifier
|
|
429
189
|
}
|
|
430
190
|
```
|
|
431
191
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
```typescript
|
|
435
|
-
type ITranslationsMap = Record<string, string>;
|
|
192
|
+
---
|
|
436
193
|
|
|
437
|
-
|
|
438
|
-
{
|
|
439
|
-
'shared.save': 'Save',
|
|
440
|
-
'shared.cancel': 'Cancel',
|
|
441
|
-
'auth.login.title': 'Login',
|
|
442
|
-
}
|
|
443
|
-
```
|
|
194
|
+
## TranslatePipe
|
|
444
195
|
|
|
445
|
-
|
|
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.
|
|
446
197
|
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
```
|
|
198
|
+
```html
|
|
199
|
+
<!-- Basic key -->
|
|
200
|
+
{{ 'menu.dashboard' | translate }}
|
|
455
201
|
|
|
456
|
-
|
|
202
|
+
<!-- With interpolation parameters -->
|
|
203
|
+
{{ 'user.greeting' | translate: { name: userName() } }}
|
|
457
204
|
|
|
458
|
-
|
|
205
|
+
<!-- In attribute binding -->
|
|
206
|
+
<input [placeholder]="'form.search.placeholder' | translate" />
|
|
459
207
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
}
|
|
208
|
+
<!-- In event handler label -->
|
|
209
|
+
<button [title]="'action.delete.tooltip' | translate">
|
|
210
|
+
<i class="pi pi-trash"></i>
|
|
211
|
+
</button>
|
|
472
212
|
```
|
|
473
213
|
|
|
474
|
-
**
|
|
475
|
-
|
|
476
|
-
### ITranslation
|
|
214
|
+
**Translation Key Convention:**
|
|
477
215
|
|
|
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
216
|
```
|
|
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');
|
|
217
|
+
module.component.element
|
|
499
218
|
```
|
|
500
219
|
|
|
501
|
-
|
|
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');
|
|
220
|
+
Examples:
|
|
507
221
|
```
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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');
|
|
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
|
|
521
230
|
```
|
|
522
231
|
|
|
523
232
|
---
|
|
524
233
|
|
|
525
|
-
##
|
|
526
|
-
|
|
527
|
-
### LocalizationStateService
|
|
234
|
+
## Variable Interpolation
|
|
528
235
|
|
|
529
|
-
|
|
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>;
|
|
236
|
+
Use `{{variableName}}` in translation values stored in the database:
|
|
544
237
|
|
|
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
238
|
```
|
|
239
|
+
# In database:
|
|
240
|
+
Key: "pagination.showing"
|
|
241
|
+
Value: "Showing {{from}}-{{to}} of {{total}} results"
|
|
556
242
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
### TranslateService
|
|
243
|
+
# In Arabic:
|
|
244
|
+
Key: "pagination.showing"
|
|
245
|
+
Value: "عرض {{from}}-{{to}} من {{total}} نتيجة"
|
|
246
|
+
```
|
|
563
247
|
|
|
564
|
-
|
|
248
|
+
```html
|
|
249
|
+
<!-- Angular template -->
|
|
250
|
+
{{ 'pagination.showing' | translate: { from: page * pageSize + 1, to: endItem, total: total() } }}
|
|
251
|
+
```
|
|
565
252
|
|
|
566
|
-
|
|
567
|
-
@Injectable({ providedIn: 'root' })
|
|
568
|
-
export class TranslateService {
|
|
569
|
-
// Synchronous translation
|
|
570
|
-
translate(key: string, variables?: Record<string, string | number>): string;
|
|
253
|
+
**Backend `messageVariables` field:**
|
|
571
254
|
|
|
572
|
-
|
|
573
|
-
translateSignal(key: string, variables?: Record<string, string | number>): Signal<string>;
|
|
255
|
+
When the backend sends translation responses, it includes a `messageVariables` field with the variable names the translation expects:
|
|
574
256
|
|
|
575
|
-
|
|
576
|
-
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"key": "pagination.showing",
|
|
260
|
+
"value": "Showing {{from}}-{{to}} of {{total}} results",
|
|
261
|
+
"messageVariables": ["from", "to", "total"]
|
|
577
262
|
}
|
|
578
263
|
```
|
|
579
264
|
|
|
580
|
-
|
|
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
|
|
265
|
+
---
|
|
585
266
|
|
|
586
|
-
|
|
267
|
+
## Language Management
|
|
587
268
|
|
|
588
|
-
|
|
269
|
+
Languages are managed via the `ng-localization` admin pages:
|
|
589
270
|
|
|
590
271
|
```typescript
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
// Get active languages (no auth required)
|
|
594
|
-
getActiveLanguages(): Observable<IListResponse<ILanguage>>;
|
|
272
|
+
// app.routes.ts
|
|
273
|
+
import { LOCALIZATION_ROUTES } from '@flusys/ng-localization';
|
|
595
274
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
275
|
+
export const routes: Routes = [
|
|
276
|
+
{
|
|
277
|
+
path: 'localization',
|
|
278
|
+
loadChildren: () => LOCALIZATION_ROUTES,
|
|
279
|
+
},
|
|
280
|
+
];
|
|
599
281
|
```
|
|
600
282
|
|
|
601
|
-
|
|
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 |
|
|
602
288
|
|
|
603
|
-
|
|
289
|
+
**`TranslateApiService`** for programmatic access:
|
|
604
290
|
|
|
605
291
|
```typescript
|
|
606
|
-
|
|
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
|
|
292
|
+
import { TranslateApiService } from '@flusys/ng-localization';
|
|
618
293
|
|
|
619
|
-
|
|
294
|
+
@Injectable({ ... })
|
|
295
|
+
export class MyService {
|
|
296
|
+
private translateApi = inject(TranslateApiService);
|
|
620
297
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
### TranslatePipe
|
|
626
|
-
|
|
627
|
-
Optimized pipe with signal-based language change detection:
|
|
298
|
+
getLanguages(): Observable<IListResponse<ILanguage>> {
|
|
299
|
+
return this.translateApi.getLanguages();
|
|
300
|
+
}
|
|
628
301
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
pure: false, // Reacts to language changes
|
|
633
|
-
})
|
|
634
|
-
export class TranslatePipe implements PipeTransform {
|
|
635
|
-
transform(key: string, variables?: Record<string, string | number>): string;
|
|
302
|
+
getTranslations(languageCode: string): Observable<Record<string, string>> {
|
|
303
|
+
return this.translateApi.getTranslations(languageCode);
|
|
304
|
+
}
|
|
636
305
|
}
|
|
637
306
|
```
|
|
638
307
|
|
|
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
308
|
---
|
|
652
309
|
|
|
653
310
|
## Components
|
|
654
311
|
|
|
655
312
|
### LanguageSelectorComponent
|
|
656
313
|
|
|
657
|
-
|
|
314
|
+
Language switcher dropdown. Registered via `LAYOUT_LANGUAGE_SELECTOR` and rendered automatically in `AppTopbar`.
|
|
658
315
|
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
})
|
|
664
|
-
export class LanguageSelectorComponent {
|
|
665
|
-
readonly languages: Signal<ILanguage[]>;
|
|
666
|
-
selectedLanguageCode: WritableSignal<string>;
|
|
667
|
-
|
|
668
|
-
onLanguageChange(code: string): void;
|
|
669
|
-
}
|
|
316
|
+
```html
|
|
317
|
+
<!-- Rendered automatically in AppTopbar when provideLocalization() is called -->
|
|
318
|
+
<!-- Manual embedding: -->
|
|
319
|
+
<flusys-language-selector />
|
|
670
320
|
```
|
|
671
321
|
|
|
672
|
-
|
|
673
|
-
-
|
|
674
|
-
-
|
|
675
|
-
-
|
|
676
|
-
- Automatically loads translations on change
|
|
322
|
+
Features:
|
|
323
|
+
- Shows current language flag + name
|
|
324
|
+
- Dropdown lists all active languages
|
|
325
|
+
- Switches language and reloads translations on selection
|
|
677
326
|
|
|
678
327
|
---
|
|
679
328
|
|
|
680
|
-
##
|
|
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 |
|
|
329
|
+
## Route Resolver
|
|
692
330
|
|
|
693
|
-
|
|
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
|
-
---
|
|
331
|
+
Use the route resolver to ensure translations are loaded before a route renders:
|
|
705
332
|
|
|
706
|
-
|
|
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
|
|
333
|
+
```typescript
|
|
334
|
+
import { localizationResolver } from '@flusys/ng-localization';
|
|
741
335
|
|
|
336
|
+
// app.routes.ts
|
|
337
|
+
export const routes: Routes = [
|
|
338
|
+
{
|
|
339
|
+
path: '',
|
|
340
|
+
resolve: { translations: localizationResolver },
|
|
341
|
+
children: [
|
|
342
|
+
// Routes that need translations pre-loaded
|
|
343
|
+
],
|
|
344
|
+
},
|
|
345
|
+
];
|
|
742
346
|
```
|
|
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
347
|
|
|
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
|
-
```
|
|
348
|
+
The resolver calls `TranslateService.reloadTranslations()` if translations are stale or not yet loaded for the current language.
|
|
783
349
|
|
|
784
350
|
---
|
|
785
351
|
|
|
786
|
-
##
|
|
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
|
-
```
|
|
352
|
+
## RTL Support
|
|
813
353
|
|
|
814
|
-
|
|
354
|
+
When a user switches to an RTL language (e.g., Arabic, Hebrew), `TranslateService` automatically updates the document direction:
|
|
815
355
|
|
|
816
356
|
```typescript
|
|
817
|
-
//
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
// Template
|
|
822
|
-
{{ 'shared.itemCount' | translate: { count: items.length } }}
|
|
357
|
+
// Internal behavior (automatic)
|
|
358
|
+
document.documentElement.dir = language.isRtl ? 'rtl' : 'ltr';
|
|
359
|
+
document.documentElement.lang = language.code;
|
|
823
360
|
```
|
|
824
361
|
|
|
825
|
-
|
|
362
|
+
Tailwind CSS RTL classes (`rtl:`, `ltr:`) work automatically once the `dir` attribute is set.
|
|
826
363
|
|
|
827
|
-
|
|
828
|
-
import { LocalizationStateService } from '@flusys/ng-localization';
|
|
829
|
-
|
|
830
|
-
@Component({})
|
|
831
|
-
export class MyComponent {
|
|
832
|
-
private readonly localizationState = inject(LocalizationStateService);
|
|
364
|
+
PrimeNG also supports RTL via the `dir` attribute on the document root — no extra configuration needed.
|
|
833
365
|
|
|
834
|
-
|
|
835
|
-
readonly isRtl = this.localizationState.isRtl;
|
|
366
|
+
---
|
|
836
367
|
|
|
837
|
-
|
|
838
|
-
// [dir]="isRtl() ? 'rtl' : 'ltr'"
|
|
839
|
-
}
|
|
840
|
-
```
|
|
368
|
+
## TRANSLATE_ADAPTER Token
|
|
841
369
|
|
|
842
|
-
|
|
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`.
|
|
843
371
|
|
|
844
372
|
```typescript
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
@Component({})
|
|
848
|
-
export class MyComponent {
|
|
849
|
-
private readonly localizationState = inject(LocalizationStateService);
|
|
850
|
-
private readonly localizationApi = inject(LocalizationApiService);
|
|
373
|
+
// Defined in ng-core
|
|
374
|
+
import { TRANSLATE_ADAPTER, ITranslateAdapter } from '@flusys/ng-core';
|
|
851
375
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
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
|
-
}
|
|
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>;
|
|
868
381
|
}
|
|
382
|
+
|
|
383
|
+
// ng-localization provides the implementation:
|
|
384
|
+
// provideLocalization() → { provide: TRANSLATE_ADAPTER, useClass: TranslateService }
|
|
869
385
|
```
|
|
870
386
|
|
|
871
387
|
---
|
|
872
388
|
|
|
873
|
-
##
|
|
874
|
-
|
|
875
|
-
|
|
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 |
|
|
876
404
|
|
|
877
|
-
|
|
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
|
|
405
|
+
---
|
|
891
406
|
|
|
892
|
-
|
|
893
|
-
import { ALL_MESSAGES, SHARED_MESSAGES, AUTH_MESSAGES } from '@flusys/ng-shared';
|
|
407
|
+
## Configuration Reference
|
|
894
408
|
|
|
895
|
-
|
|
896
|
-
|
|
409
|
+
| Config Key | Type | Default | Description |
|
|
410
|
+
|------------|------|---------|-------------|
|
|
411
|
+
| `services.localization.enabled` | `boolean` | `false` | Enable localization module |
|
|
897
412
|
|
|
898
|
-
|
|
899
|
-
provideLocalizationFallback({
|
|
900
|
-
...SHARED_MESSAGES,
|
|
901
|
-
...AUTH_MESSAGES,
|
|
902
|
-
})
|
|
903
|
-
```
|
|
413
|
+
Default language is configured server-side in `nestjs-localization` via `ILanguage.isDefault`.
|
|
904
414
|
|
|
905
415
|
---
|
|
906
416
|
|
|
907
|
-
##
|
|
417
|
+
## Troubleshooting
|
|
908
418
|
|
|
909
|
-
|
|
419
|
+
**TranslatePipe shows raw key (e.g., `common.save`)**
|
|
910
420
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
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`
|
|
914
425
|
|
|
915
|
-
**
|
|
426
|
+
**APP_INITIALIZER delays app startup**
|
|
916
427
|
|
|
917
|
-
|
|
918
|
-
@Component({
|
|
919
|
-
host: {
|
|
920
|
-
'[class.rtl]': 'localizationState.isRtl()',
|
|
921
|
-
},
|
|
922
|
-
})
|
|
923
|
-
export class MyComponent {
|
|
924
|
-
readonly localizationState = inject(LocalizationStateService);
|
|
925
|
-
}
|
|
926
|
-
```
|
|
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.
|
|
927
429
|
|
|
928
|
-
|
|
430
|
+
**Language switch doesn't update all components**
|
|
929
431
|
|
|
930
|
-
|
|
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.
|
|
931
433
|
|
|
932
|
-
|
|
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) |
|
|
434
|
+
**RTL layout not applying**
|
|
940
435
|
|
|
941
|
-
|
|
436
|
+
Ensure Tailwind CSS is configured for RTL classes. Add to `tailwind.config.js`:
|
|
942
437
|
|
|
943
|
-
|
|
438
|
+
```javascript
|
|
439
|
+
module.exports = {
|
|
440
|
+
// Tailwind reads dir from document root automatically
|
|
441
|
+
// No extra config needed for rtl: classes
|
|
442
|
+
};
|
|
443
|
+
```
|
|
944
444
|
|
|
945
|
-
|
|
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` |
|
|
445
|
+
**`No provider for TRANSLATE_ADAPTER`**
|
|
956
446
|
|
|
957
|
-
|
|
447
|
+
Add `provideLocalization()` to `app.config.ts` providers.
|
|
958
448
|
|
|
959
|
-
|
|
449
|
+
**Missing translations after adding new keys**
|
|
960
450
|
|
|
961
|
-
|
|
962
|
-
**Version:** 3.0.3
|
|
963
|
-
**Angular Version:** 21
|
|
451
|
+
Call `TranslateService.reloadTranslations()` or navigate away and back (triggers the route resolver). In-memory translations are cached until the next load.
|
|
964
452
|
|
|
965
453
|
---
|
|
966
454
|
|
|
967
|
-
##
|
|
455
|
+
## License
|
|
968
456
|
|
|
969
|
-
|
|
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)
|
|
457
|
+
MIT © FLUSYS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/ng-localization",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Localization module for multi-language support in FLUSYS Angular apps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"peerDependencies": {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"@angular/core": ">=21.0.0",
|
|
9
9
|
"@angular/forms": ">=21.0.0",
|
|
10
10
|
"@angular/router": ">=21.0.0",
|
|
11
|
-
"@flusys/ng-core": ">=4.0
|
|
12
|
-
"@flusys/ng-shared": ">=4.0
|
|
11
|
+
"@flusys/ng-core": ">=4.1.0",
|
|
12
|
+
"@flusys/ng-shared": ">=4.1.0",
|
|
13
13
|
"@primeuix/themes": ">=1.0.0",
|
|
14
14
|
"primeicons": ">=7.0.0",
|
|
15
15
|
"primeng": ">=21.0.0",
|
|
@@ -29,5 +29,8 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"tslib": "^2.3.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"prepublishOnly": "node --eval \"console.error('ERROR: Trying to publish a package that has been compiled in full compilation mode. This is not allowed.\\nPlease delete and rebuild the package with partial compilation mode, before attempting to publish.\\n')\" && exit 1"
|
|
32
35
|
}
|
|
33
36
|
}
|