@flusys/nestjs-localization 4.1.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -473
- package/cjs/config/localization.constants.js +3 -12
- package/cjs/config/message-keys.js +1 -40
- package/cjs/controllers/translation-key.controller.js +2 -40
- package/cjs/controllers/translation.controller.js +0 -59
- package/cjs/docs/index.js +11 -0
- package/cjs/docs/localization-swagger.config.js +111 -0
- package/cjs/dtos/translation-key.dto.js +0 -18
- package/cjs/dtos/translation.dto.js +0 -29
- package/cjs/entities/translation-key.entity.js +1 -1
- package/cjs/services/translation-key.service.js +3 -3
- package/config/localization.constants.d.ts +0 -1
- package/config/message-keys.d.ts +0 -80
- package/controllers/translation-key.controller.d.ts +4 -6
- package/controllers/translation.controller.d.ts +5 -7
- package/docs/index.d.ts +1 -0
- package/docs/localization-swagger.config.d.ts +4 -0
- package/dtos/translation-key.dto.d.ts +0 -3
- package/dtos/translation.dto.d.ts +0 -4
- package/fesm/config/localization.constants.js +0 -1
- package/fesm/config/message-keys.js +1 -38
- package/fesm/controllers/translation-key.controller.js +4 -42
- package/fesm/controllers/translation.controller.js +1 -60
- package/fesm/docs/index.js +1 -0
- package/fesm/docs/localization-swagger.config.js +101 -0
- package/fesm/dtos/translation-key.dto.js +0 -15
- package/fesm/dtos/translation.dto.js +0 -26
- package/fesm/entities/translation-key.entity.js +1 -1
- package/fesm/services/translation-key.service.js +3 -3
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -1,73 +1,9 @@
|
|
|
1
1
|
# @flusys/nestjs-localization
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Database-driven multi-language backend for NestJS — manage languages, translation keys (grouped by module), and per-language translation values with verification workflow, missing-key detection, and atomic bulk updates.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@flusys/nestjs-localization)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://nestjs.com/)
|
|
8
|
-
[](https://www.typescriptlang.org/)
|
|
9
|
-
[](https://nodejs.org/)
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Table of Contents
|
|
14
|
-
|
|
15
|
-
- [Overview](#overview)
|
|
16
|
-
- [Features](#features)
|
|
17
|
-
- [Compatibility](#compatibility)
|
|
18
|
-
- [Installation](#installation)
|
|
19
|
-
- [Quick Start](#quick-start)
|
|
20
|
-
- [Module Registration](#module-registration)
|
|
21
|
-
- [forRoot (Sync)](#forroot-sync)
|
|
22
|
-
- [forRootAsync (Factory)](#forrootasync-factory)
|
|
23
|
-
- [Configuration Reference](#configuration-reference)
|
|
24
|
-
- [Feature Toggles](#feature-toggles)
|
|
25
|
-
- [API Endpoints](#api-endpoints)
|
|
26
|
-
- [Entities](#entities)
|
|
27
|
-
- [Translation Workflow](#translation-workflow)
|
|
28
|
-
- [Variable Interpolation](#variable-interpolation)
|
|
29
|
-
- [Fallback Chain](#fallback-chain)
|
|
30
|
-
- [RTL Support](#rtl-support)
|
|
31
|
-
- [Caching](#caching)
|
|
32
|
-
- [Exported Services](#exported-services)
|
|
33
|
-
- [Integration with API Responses](#integration-with-api-responses)
|
|
34
|
-
- [Troubleshooting](#troubleshooting)
|
|
35
|
-
- [License](#license)
|
|
36
|
-
|
|
37
|
-
---
|
|
38
|
-
|
|
39
|
-
## Overview
|
|
40
|
-
|
|
41
|
-
`@flusys/nestjs-localization` manages the backend side of multi-language support. Translations are stored in the database and served to the frontend via API. The package provides a full admin workflow: create languages, define translation keys (grouped by module), add translations per language, verify them, and detect missing ones.
|
|
42
|
-
|
|
43
|
-
All API responses in the FLUSYS ecosystem return a `messageKey` field (e.g., `'auth.login_success'`). The frontend looks up this key in the translations returned by this package.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
|
-
## Features
|
|
48
|
-
|
|
49
|
-
- **Language management** — Create languages with ISO 639-1 codes, native names, and RTL/LTR direction
|
|
50
|
-
- **Translation keys** — Module-scoped key registry with variable declarations and readonly protection
|
|
51
|
-
- **Translations** — Per-language values with a verification workflow (draft → verified)
|
|
52
|
-
- **Variable interpolation** — `{{variableName}}` syntax in messages with `messageVariables` field in API responses
|
|
53
|
-
- **Missing key detection** — Find which keys have no translation for a given language
|
|
54
|
-
- **Bulk operations** — Atomic bulk update of multiple translations in one transaction
|
|
55
|
-
- **Default language** — Configurable fallback language (e.g., `'en'`)
|
|
56
|
-
- **Module scoping** — Keys grouped by module for partial frontend loading
|
|
57
|
-
- **Caching** — Optional HybridCache for translation lookups with configurable TTL
|
|
58
|
-
- **Multi-tenant** — Per-tenant DataSource isolation
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Compatibility
|
|
63
|
-
|
|
64
|
-
| Package | Version |
|
|
65
|
-
|---------|---------|
|
|
66
|
-
| `@flusys/nestjs-core` | `^4.0.0` |
|
|
67
|
-
| `@flusys/nestjs-shared` | `^4.0.0` |
|
|
68
|
-
| `@nestjs/core` | `^11.0.0` |
|
|
69
|
-
| `typeorm` | `^0.3.0` |
|
|
70
|
-
| Node.js | `>= 18.x` |
|
|
71
7
|
|
|
72
8
|
---
|
|
73
9
|
|
|
@@ -79,457 +15,99 @@ npm install @flusys/nestjs-localization @flusys/nestjs-shared @flusys/nestjs-cor
|
|
|
79
15
|
|
|
80
16
|
---
|
|
81
17
|
|
|
82
|
-
##
|
|
18
|
+
## 1. Module Registration
|
|
83
19
|
|
|
84
|
-
###
|
|
20
|
+
### forRoot (sync)
|
|
85
21
|
|
|
86
22
|
```typescript
|
|
87
|
-
import { Module } from '@nestjs/common';
|
|
88
23
|
import { LocalizationModule } from '@flusys/nestjs-localization';
|
|
89
24
|
|
|
90
|
-
@Module({
|
|
91
|
-
imports: [
|
|
92
|
-
LocalizationModule.forRoot({
|
|
93
|
-
global: true,
|
|
94
|
-
includeController: true,
|
|
95
|
-
bootstrapAppConfig: {
|
|
96
|
-
databaseMode: 'single',
|
|
97
|
-
enableCompanyFeature: false,
|
|
98
|
-
},
|
|
99
|
-
config: {
|
|
100
|
-
defaultDatabaseConfig: {
|
|
101
|
-
type: 'postgres',
|
|
102
|
-
host: process.env.DB_HOST,
|
|
103
|
-
port: Number(process.env.DB_PORT ?? 5432),
|
|
104
|
-
username: process.env.DB_USER,
|
|
105
|
-
password: process.env.DB_PASSWORD,
|
|
106
|
-
database: process.env.DB_NAME,
|
|
107
|
-
},
|
|
108
|
-
defaultLanguageCode: 'en',
|
|
109
|
-
enableCaching: false,
|
|
110
|
-
cacheTtlSeconds: 300,
|
|
111
|
-
},
|
|
112
|
-
}),
|
|
113
|
-
],
|
|
114
|
-
})
|
|
115
|
-
export class AppModule {}
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
After startup, create languages and translation keys via the API. The frontend fetches translations on init and uses the `messageKey` returned by each API response to look up the localized message.
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Module Registration
|
|
123
|
-
|
|
124
|
-
### forRoot (Sync)
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
25
|
LocalizationModule.forRoot({
|
|
128
|
-
global?: boolean;
|
|
129
|
-
includeController?: boolean; // Default: true
|
|
130
|
-
bootstrapAppConfig?: {
|
|
131
|
-
databaseMode: 'single' | 'multi-tenant';
|
|
132
|
-
enableCompanyFeature: boolean; // No company variants — shared data only
|
|
133
|
-
};
|
|
134
|
-
config: ILocalizationModuleConfig;
|
|
135
|
-
})
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### forRootAsync (Factory)
|
|
139
|
-
|
|
140
|
-
```typescript
|
|
141
|
-
import { ConfigService } from '@nestjs/config';
|
|
142
|
-
|
|
143
|
-
LocalizationModule.forRootAsync({
|
|
144
26
|
global: true,
|
|
145
|
-
includeController: true,
|
|
27
|
+
includeController: true, // registers Language, TranslationKey, Translation controllers
|
|
146
28
|
bootstrapAppConfig: {
|
|
147
|
-
databaseMode: 'single',
|
|
148
|
-
enableCompanyFeature: false,
|
|
29
|
+
databaseMode: 'single', // 'single' | 'multi-tenant'
|
|
30
|
+
enableCompanyFeature: false, // localization has no company variants — this is ignored
|
|
149
31
|
},
|
|
150
|
-
|
|
151
|
-
|
|
32
|
+
config: {
|
|
33
|
+
defaultLanguageCode: 'en', // fallback language code
|
|
34
|
+
enableCaching: false, // optional HybridCache for translation lookups
|
|
35
|
+
cacheTtlSeconds: 300,
|
|
152
36
|
defaultDatabaseConfig: {
|
|
153
|
-
type: '
|
|
154
|
-
host:
|
|
155
|
-
port:
|
|
156
|
-
username:
|
|
157
|
-
password:
|
|
158
|
-
database:
|
|
37
|
+
type: 'mysql',
|
|
38
|
+
host: process.env.DB_HOST,
|
|
39
|
+
port: Number(process.env.DB_PORT),
|
|
40
|
+
username: process.env.DB_USER,
|
|
41
|
+
password: process.env.DB_PASSWORD,
|
|
42
|
+
database: process.env.DB_NAME,
|
|
159
43
|
},
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
cacheTtlSeconds: 300,
|
|
163
|
-
}),
|
|
164
|
-
inject: [ConfigService],
|
|
165
|
-
})
|
|
44
|
+
},
|
|
45
|
+
});
|
|
166
46
|
```
|
|
167
47
|
|
|
168
|
-
###
|
|
48
|
+
### forRootAsync (factory — recommended)
|
|
169
49
|
|
|
170
50
|
```typescript
|
|
51
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
52
|
+
import { LocalizationModule } from '@flusys/nestjs-localization';
|
|
53
|
+
|
|
171
54
|
LocalizationModule.forRootAsync({
|
|
172
55
|
global: true,
|
|
173
56
|
bootstrapAppConfig: {
|
|
174
|
-
databaseMode: '
|
|
57
|
+
databaseMode: 'single',
|
|
175
58
|
enableCompanyFeature: false,
|
|
176
59
|
},
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
60
|
+
imports: [ConfigModule],
|
|
61
|
+
useFactory: (cfg: ConfigService) => ({
|
|
62
|
+
defaultLanguageCode: cfg.get('DEFAULT_LANGUAGE', 'en'),
|
|
63
|
+
enableCaching: cfg.get<boolean>('LOCALIZATION_CACHE_ENABLED', false),
|
|
64
|
+
cacheTtlSeconds: cfg.get<number>('LOCALIZATION_CACHE_TTL', 300),
|
|
65
|
+
defaultDatabaseConfig: {
|
|
66
|
+
type: 'mysql',
|
|
67
|
+
host: cfg.get('DB_HOST'),
|
|
68
|
+
port: cfg.get<number>('DB_PORT'),
|
|
69
|
+
username: cfg.get('DB_USER'),
|
|
70
|
+
password: cfg.get('DB_PASSWORD'),
|
|
71
|
+
database: cfg.get('DB_NAME'),
|
|
184
72
|
},
|
|
185
|
-
tenants: [
|
|
186
|
-
{ id: 'tenant1', database: 'tenant1_db' },
|
|
187
|
-
{ id: 'tenant2', database: 'tenant2_db' },
|
|
188
|
-
],
|
|
189
|
-
defaultLanguageCode: 'en',
|
|
190
73
|
}),
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
**Exported services:**
|
|
195
|
-
- `LocalizationConfigService`
|
|
196
|
-
- `LocalizationDataSourceProvider`
|
|
197
|
-
- `LanguageService`
|
|
198
|
-
- `TranslationKeyService`
|
|
199
|
-
- `TranslationService`
|
|
200
|
-
|
|
201
|
-
---
|
|
202
|
-
|
|
203
|
-
## Configuration Reference
|
|
204
|
-
|
|
205
|
-
```typescript
|
|
206
|
-
interface ILocalizationModuleConfig extends IDataSourceServiceOptions {
|
|
207
|
-
/** Default language code for fallback (default: 'en') */
|
|
208
|
-
defaultLanguageCode?: string;
|
|
209
|
-
|
|
210
|
-
/** Enable translation caching (default: false) */
|
|
211
|
-
enableCaching?: boolean;
|
|
212
|
-
|
|
213
|
-
/** Cache TTL in seconds (default: 300) */
|
|
214
|
-
cacheTtlSeconds?: number;
|
|
215
|
-
}
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## Feature Toggles
|
|
221
|
-
|
|
222
|
-
| Feature | Config | Default | Effect |
|
|
223
|
-
|---------|--------|---------|--------|
|
|
224
|
-
| Caching | `enableCaching: true` | `false` | Translations cached in HybridCache with `cacheTtlSeconds` TTL |
|
|
225
|
-
| Multi-tenant | `databaseMode: 'multi-tenant'` | `'single'` | Per-tenant DataSource connections |
|
|
226
|
-
|
|
227
|
-
> **Note:** Localization entities do **not** have company variants (`enableCompanyFeature` is ignored). All translation data is shared across companies.
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## API Endpoints
|
|
232
|
-
|
|
233
|
-
All endpoints use **POST**. Authentication is required unless noted.
|
|
234
|
-
|
|
235
|
-
### Languages — `POST /localization/language/*`
|
|
236
|
-
|
|
237
|
-
| Endpoint | Permission | Description |
|
|
238
|
-
|----------|-----------|-------------|
|
|
239
|
-
| `POST /localization/language/insert` | `language.create` | Create a language |
|
|
240
|
-
| `POST /localization/language/get-all` | Public | List all active languages |
|
|
241
|
-
| `POST /localization/language/get/:id` | `language.read` | Get language by ID |
|
|
242
|
-
| `POST /localization/language/update` | `language.update` | Update language |
|
|
243
|
-
| `POST /localization/language/delete` | `language.delete` | Delete language |
|
|
244
|
-
| `POST /localization/language/set-default` | `language.update` | Set as default language |
|
|
245
|
-
|
|
246
|
-
**Create language:**
|
|
247
|
-
|
|
248
|
-
```json
|
|
249
|
-
POST /localization/language/insert
|
|
250
|
-
{
|
|
251
|
-
"name": "English",
|
|
252
|
-
"nativeName": "English",
|
|
253
|
-
"code": "en",
|
|
254
|
-
"direction": "ltr",
|
|
255
|
-
"isActive": true,
|
|
256
|
-
"isDefault": true
|
|
257
|
-
}
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
```json
|
|
261
|
-
POST /localization/language/insert
|
|
262
|
-
{
|
|
263
|
-
"name": "Arabic",
|
|
264
|
-
"nativeName": "العربية",
|
|
265
|
-
"code": "ar",
|
|
266
|
-
"direction": "rtl",
|
|
267
|
-
"isActive": true
|
|
268
|
-
}
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### Translation Keys — `POST /localization/translation-key/*`
|
|
272
|
-
|
|
273
|
-
| Endpoint | Permission | Description |
|
|
274
|
-
|----------|-----------|-------------|
|
|
275
|
-
| `POST /localization/translation-key/insert` | `translation-key.create` | Register a new translation key |
|
|
276
|
-
| `POST /localization/translation-key/get-all` | `translation-key.read` | List all keys |
|
|
277
|
-
| `POST /localization/translation-key/get/:id` | `translation-key.read` | Get key by ID |
|
|
278
|
-
| `POST /localization/translation-key/get-by-module` | `translation-key.read` | Get all keys for a module |
|
|
279
|
-
| `POST /localization/translation-key/update` | `translation-key.update` | Update key (not readonly keys) |
|
|
280
|
-
| `POST /localization/translation-key/delete` | `translation-key.delete` | Delete key (not readonly) |
|
|
281
|
-
| `POST /localization/translation-key/get-missing` | `translation-key.read` | Get keys with no translation for a language |
|
|
282
|
-
|
|
283
|
-
**Register a key:**
|
|
284
|
-
|
|
285
|
-
```json
|
|
286
|
-
POST /localization/translation-key/insert
|
|
287
|
-
{
|
|
288
|
-
"key": "auth.login_success",
|
|
289
|
-
"module": "auth",
|
|
290
|
-
"description": "Shown after successful login",
|
|
291
|
-
"defaultValue": "Login successful",
|
|
292
|
-
"variables": [],
|
|
293
|
-
"isReadonly": true
|
|
294
|
-
}
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
**Key with variables:**
|
|
298
|
-
|
|
299
|
-
```json
|
|
300
|
-
{
|
|
301
|
-
"key": "user.welcome",
|
|
302
|
-
"module": "auth",
|
|
303
|
-
"description": "Welcome message with user name",
|
|
304
|
-
"defaultValue": "Welcome, {{name}}!",
|
|
305
|
-
"variables": ["name"],
|
|
306
|
-
"isReadonly": false
|
|
307
|
-
}
|
|
308
|
-
```
|
|
309
|
-
|
|
310
|
-
### Translations — `POST /localization/translation/*`
|
|
311
|
-
|
|
312
|
-
| Endpoint | Permission | Description |
|
|
313
|
-
|----------|-----------|-------------|
|
|
314
|
-
| `POST /localization/translation/insert` | `translation.create` | Add a translation value |
|
|
315
|
-
| `POST /localization/translation/get-all` | `translation.read` | List all translations |
|
|
316
|
-
| `POST /localization/translation/get-by-language` | Public | Get all translations for a language |
|
|
317
|
-
| `POST /localization/translation/get-by-language-module` | Public | Get translations for a language + module |
|
|
318
|
-
| `POST /localization/translation/update` | `translation.update` | Update a translation value |
|
|
319
|
-
| `POST /localization/translation/bulk-update` | `translation.update` | Bulk update translations (atomic) |
|
|
320
|
-
| `POST /localization/translation/delete` | `translation.delete` | Delete a translation |
|
|
321
|
-
| `POST /localization/translation/verify` | `translation.update` | Mark a translation as verified |
|
|
322
|
-
| `POST /localization/translation/unverify` | `translation.update` | Mark a translation as unverified |
|
|
323
|
-
|
|
324
|
-
**Add a translation:**
|
|
325
|
-
|
|
326
|
-
```json
|
|
327
|
-
POST /localization/translation/insert
|
|
328
|
-
{
|
|
329
|
-
"keyId": "uuid-of-translation-key",
|
|
330
|
-
"languageId": "uuid-of-language",
|
|
331
|
-
"value": "Connexion réussie"
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
**Bulk update (atomic transaction):**
|
|
336
|
-
|
|
337
|
-
```json
|
|
338
|
-
POST /localization/translation/bulk-update
|
|
339
|
-
{
|
|
340
|
-
"languageId": "uuid-of-french",
|
|
341
|
-
"translations": [
|
|
342
|
-
{ "keyId": "uuid-1", "value": "Connexion réussie" },
|
|
343
|
-
{ "keyId": "uuid-2", "value": "Bienvenue, {{name}}!" },
|
|
344
|
-
{ "keyId": "uuid-3", "value": "Mot de passe incorrect" }
|
|
345
|
-
]
|
|
346
|
-
}
|
|
74
|
+
inject: [ConfigService],
|
|
75
|
+
});
|
|
347
76
|
```
|
|
348
77
|
|
|
349
78
|
---
|
|
350
79
|
|
|
351
|
-
## Entities
|
|
352
|
-
|
|
353
|
-
| Entity | Table | Description |
|
|
354
|
-
|--------|-------|-------------|
|
|
355
|
-
| `Language` | `localization_language` | Language with code, direction, default flag |
|
|
356
|
-
| `TranslationKey` | `localization_translation_key` | Key registry with module, variables, readonly flag |
|
|
357
|
-
| `Translation` | `localization_translation` | Per-language translation values with verification status |
|
|
358
|
-
|
|
359
|
-
> Localization entities have **no company variants**. All translation data is shared globally.
|
|
80
|
+
## 2. Entities
|
|
360
81
|
|
|
361
82
|
```typescript
|
|
362
|
-
import {
|
|
83
|
+
import { getLocalizationEntities } from '@flusys/nestjs-localization/entities';
|
|
363
84
|
|
|
364
85
|
TypeOrmModule.forRoot({
|
|
365
|
-
entities: [
|
|
366
|
-
|
|
367
|
-
// other entities
|
|
368
|
-
],
|
|
369
|
-
})
|
|
86
|
+
entities: [...getLocalizationEntities()],
|
|
87
|
+
});
|
|
370
88
|
```
|
|
371
89
|
|
|
372
90
|
---
|
|
373
91
|
|
|
374
|
-
## Translation Workflow
|
|
375
92
|
|
|
376
|
-
|
|
377
|
-
1. Create Language → POST /localization/language/insert
|
|
378
|
-
2. Register Key → POST /localization/translation-key/insert
|
|
379
|
-
3. Add Translation → POST /localization/translation/insert
|
|
380
|
-
4. Verify Translation → POST /localization/translation/verify
|
|
381
|
-
5. Detect Missing → POST /localization/translation-key/get-missing
|
|
382
|
-
6. Bulk Update → POST /localization/translation/bulk-update
|
|
383
|
-
```
|
|
384
|
-
|
|
385
|
-
Translation statuses:
|
|
386
|
-
- **Unverified** (default) — Draft translation, may not be production-ready
|
|
387
|
-
- **Verified** — Approved by a translator or admin
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
## Variable Interpolation
|
|
93
|
+
## 4. How API responses use messageKey
|
|
392
94
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
**Server response:**
|
|
396
|
-
|
|
397
|
-
```json
|
|
398
|
-
{
|
|
399
|
-
"success": true,
|
|
400
|
-
"message": "Welcome, John!",
|
|
401
|
-
"messageKey": "user.welcome",
|
|
402
|
-
"messageVariables": { "name": "John" }
|
|
403
|
-
}
|
|
404
|
-
```
|
|
405
|
-
|
|
406
|
-
**Frontend interpolation:**
|
|
95
|
+
Every FLUSYS API response includes a `messageKey` field. The frontend fetches translations once on init, then uses that key to look up the localized message:
|
|
407
96
|
|
|
408
97
|
```typescript
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
const message = template.replace(/\{\{(\w+)\}\}/g, (_, key) => variables[key] ?? key);
|
|
412
|
-
// Result: "Bienvenue, John!"
|
|
413
|
-
```
|
|
98
|
+
// Backend throws
|
|
99
|
+
throw new NotFoundException({ message: 'Form not found', messageKey: LANGUAGE_MESSAGES.[key]'form.not.found' });
|
|
414
100
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
## Fallback Chain
|
|
418
|
-
|
|
419
|
-
When a translation is not found for the requested language, the resolution falls back in this order:
|
|
420
|
-
|
|
421
|
-
```
|
|
422
|
-
Requested language → Default language (e.g., 'en') → key.defaultValue → key itself
|
|
423
|
-
```
|
|
424
|
-
|
|
425
|
-
---
|
|
426
|
-
|
|
427
|
-
## RTL Support
|
|
428
|
-
|
|
429
|
-
Each language has a `direction` field: `'ltr'` (left-to-right) or `'rtl'` (right-to-left).
|
|
430
|
-
|
|
431
|
-
The frontend uses this to switch layout direction:
|
|
432
|
-
|
|
433
|
-
```typescript
|
|
434
|
-
// Angular
|
|
435
|
-
const lang = await this.http.post('/localization/language/get-all', {}).toPromise();
|
|
436
|
-
const current = lang.data.find(l => l.code === userLanguageCode);
|
|
437
|
-
document.dir = current.direction; // 'ltr' or 'rtl'
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
## Caching
|
|
443
|
-
|
|
444
|
-
When `enableCaching: true`, translation lookups are cached with the key pattern `loc:{languageCode}:{module}`. Cache is invalidated when translations are updated via the bulk-update or individual update endpoints.
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
config: {
|
|
448
|
-
enableCaching: true,
|
|
449
|
-
cacheTtlSeconds: 600, // 10 minutes
|
|
450
|
-
}
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
Requires `HybridCache` (Redis or in-memory) to be configured via `CacheModule` from `@flusys/nestjs-shared`.
|
|
454
|
-
|
|
455
|
-
---
|
|
456
|
-
|
|
457
|
-
## Exported Services
|
|
458
|
-
|
|
459
|
-
| Service | Description |
|
|
460
|
-
|---------|-------------|
|
|
461
|
-
| `LanguageService` | Language CRUD, default language management |
|
|
462
|
-
| `TranslationKeyService` | Key CRUD, missing-key detection, module filtering |
|
|
463
|
-
| `TranslationService` | Translation CRUD, bulk update, verification |
|
|
464
|
-
| `LocalizationConfigService` | Runtime config (default language, caching settings) |
|
|
465
|
-
| `LocalizationDataSourceProvider` | Dynamic DataSource per request |
|
|
466
|
-
|
|
467
|
-
---
|
|
468
|
-
|
|
469
|
-
## Integration with API Responses
|
|
470
|
-
|
|
471
|
-
All FLUSYS response DTOs include `messageKey` and optionally `messageVariables`. The frontend uses these to display localized messages:
|
|
472
|
-
|
|
473
|
-
**Backend (any service):**
|
|
474
|
-
|
|
475
|
-
```typescript
|
|
101
|
+
// Backend returns:
|
|
476
102
|
return {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
103
|
+
success: true,
|
|
104
|
+
message: 'Active languages retrieved successfully',
|
|
105
|
+
messageKey: LANGUAGE_MESSAGES.ACTIVE_SUCCESS,
|
|
106
|
+
data,
|
|
107
|
+
meta: { total: data.length, page: 0, pageSize: data.length, count: data.length },
|
|
482
108
|
};
|
|
483
109
|
```
|
|
484
110
|
|
|
485
|
-
**Frontend flow:**
|
|
486
|
-
|
|
487
|
-
1. On app init: `GET /localization/translation/get-by-language` for the user's language
|
|
488
|
-
2. Cache translations in a map: `{ 'user.welcome': 'Bienvenue, {{name}}!' }`
|
|
489
|
-
3. On every API response: look up `messageKey`, interpolate `messageVariables`
|
|
490
|
-
4. Display the localized message
|
|
491
|
-
|
|
492
|
-
---
|
|
493
|
-
|
|
494
|
-
## Troubleshooting
|
|
495
|
-
|
|
496
|
-
**`Language not found` for a valid code**
|
|
497
|
-
|
|
498
|
-
Use the language's UUID (`id`), not the ISO code, in most endpoints. Use `get-by-language` with the code only for the public translation endpoints.
|
|
499
|
-
|
|
500
|
-
---
|
|
501
|
-
|
|
502
|
-
**Translation not returned for a module**
|
|
503
|
-
|
|
504
|
-
Check the `module` field on the `TranslationKey`. The `get-by-language-module` endpoint filters strictly by module name. Ensure it matches exactly (case-sensitive).
|
|
505
|
-
|
|
506
|
-
---
|
|
507
|
-
|
|
508
|
-
**Missing keys not detected**
|
|
509
|
-
|
|
510
|
-
`get-missing` returns keys that have no `Translation` record for the given language. If you created a translation but left `value` empty, it is still counted as "present" (not missing). Delete or update the empty translation to see it in the missing list.
|
|
511
|
-
|
|
512
|
-
---
|
|
513
|
-
|
|
514
|
-
**Caching returning stale translations**
|
|
515
|
-
|
|
516
|
-
When `enableCaching: true`, call `POST /localization/translation/bulk-update` (which invalidates the cache) instead of individual updates if you need immediate cache refresh.
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
**`No metadata for entity`**
|
|
521
|
-
|
|
522
|
-
Register entities:
|
|
523
|
-
```typescript
|
|
524
|
-
entities: [...LocalizationModule.getEntities()]
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
---
|
|
528
|
-
|
|
529
111
|
## License
|
|
530
112
|
|
|
531
113
|
MIT © FLUSYS
|
|
532
|
-
|
|
533
|
-
---
|
|
534
|
-
|
|
535
|
-
> Part of the **FLUSYS** framework — a full-stack monorepo powering Angular 21 + NestJS 11 applications.
|
|
@@ -2,19 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
_export(exports, {
|
|
12
|
-
get DEFAULT_LANGUAGE_CODE () {
|
|
13
|
-
return DEFAULT_LANGUAGE_CODE;
|
|
14
|
-
},
|
|
15
|
-
get LOCALIZATION_MODULE_OPTIONS () {
|
|
5
|
+
Object.defineProperty(exports, "LOCALIZATION_MODULE_OPTIONS", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
16
8
|
return LOCALIZATION_MODULE_OPTIONS;
|
|
17
9
|
}
|
|
18
10
|
});
|
|
19
11
|
const LOCALIZATION_MODULE_OPTIONS = 'LOCALIZATION_MODULE_OPTIONS';
|
|
20
|
-
const DEFAULT_LANGUAGE_CODE = 'en';
|
|
@@ -13,9 +13,6 @@ _export(exports, {
|
|
|
13
13
|
get LANGUAGE_MESSAGES () {
|
|
14
14
|
return LANGUAGE_MESSAGES;
|
|
15
15
|
},
|
|
16
|
-
get LOCALIZATION_MODULE_MESSAGES () {
|
|
17
|
-
return LOCALIZATION_MODULE_MESSAGES;
|
|
18
|
-
},
|
|
19
16
|
get TRANSLATION_KEY_MESSAGES () {
|
|
20
17
|
return TRANSLATION_KEY_MESSAGES;
|
|
21
18
|
},
|
|
@@ -24,54 +21,18 @@ _export(exports, {
|
|
|
24
21
|
}
|
|
25
22
|
});
|
|
26
23
|
const LANGUAGE_MESSAGES = {
|
|
27
|
-
CREATE_SUCCESS: 'language.create.success',
|
|
28
|
-
CREATE_MANY_SUCCESS: 'language.create.many.success',
|
|
29
|
-
GET_SUCCESS: 'language.get.success',
|
|
30
|
-
GET_ALL_SUCCESS: 'language.get.all.success',
|
|
31
|
-
UPDATE_SUCCESS: 'language.update.success',
|
|
32
|
-
UPDATE_MANY_SUCCESS: 'language.update.many.success',
|
|
33
|
-
DELETE_SUCCESS: 'language.delete.success',
|
|
34
|
-
RESTORE_SUCCESS: 'language.restore.success',
|
|
35
|
-
NOT_FOUND: 'language.not.found',
|
|
36
24
|
ACTIVE_SUCCESS: 'language.active.success',
|
|
37
25
|
SET_DEFAULT_SUCCESS: 'language.set.default.success',
|
|
38
26
|
GET_DEFAULT_SUCCESS: 'language.get.default.success'
|
|
39
27
|
};
|
|
40
28
|
const TRANSLATION_KEY_MESSAGES = {
|
|
41
|
-
CREATE_SUCCESS: 'translation.key.create.success',
|
|
42
|
-
CREATE_MANY_SUCCESS: 'translation.key.create.many.success',
|
|
43
|
-
GET_SUCCESS: 'translation.key.get.success',
|
|
44
|
-
GET_ALL_SUCCESS: 'translation.key.get.all.success',
|
|
45
|
-
UPDATE_SUCCESS: 'translation.key.update.success',
|
|
46
|
-
UPDATE_MANY_SUCCESS: 'translation.key.update.many.success',
|
|
47
|
-
DELETE_SUCCESS: 'translation.key.delete.success',
|
|
48
|
-
RESTORE_SUCCESS: 'translation.key.restore.success',
|
|
49
|
-
NOT_FOUND: 'translation.key.not.found',
|
|
50
|
-
IMPORT_SUCCESS: 'translation.key.import.success',
|
|
51
|
-
EXPORT_SUCCESS: 'translation.key.export.success',
|
|
52
29
|
MODULES_SUCCESS: 'translation.key.modules.success',
|
|
53
30
|
READONLY_DELETE_FORBIDDEN: 'translation.key.readonly.delete.forbidden',
|
|
54
31
|
READONLY_UPDATE_FORBIDDEN: 'translation.key.readonly.update.forbidden',
|
|
55
32
|
DUPLICATE_KEY_IN_MODULE: 'translation.key.duplicate.key.in.module'
|
|
56
33
|
};
|
|
57
34
|
const TRANSLATION_MESSAGES = {
|
|
58
|
-
CREATE_SUCCESS: 'translation.create.success',
|
|
59
|
-
CREATE_MANY_SUCCESS: 'translation.create.many.success',
|
|
60
|
-
GET_SUCCESS: 'translation.get.success',
|
|
61
35
|
GET_ALL_SUCCESS: 'translation.get.all.success',
|
|
62
|
-
UPDATE_SUCCESS: 'translation.update.success',
|
|
63
36
|
UPDATE_MANY_SUCCESS: 'translation.update.many.success',
|
|
64
|
-
|
|
65
|
-
RESTORE_SUCCESS: 'translation.restore.success',
|
|
66
|
-
NOT_FOUND: 'translation.not.found',
|
|
67
|
-
BY_LANGUAGE_SUCCESS: 'translation.by.language.success',
|
|
68
|
-
BY_MODULE_SUCCESS: 'translation.by.module.success',
|
|
69
|
-
MISSING_SUCCESS: 'translation.missing.success',
|
|
70
|
-
SYNC_SUCCESS: 'translation.sync.success',
|
|
71
|
-
UPDATE_VALUES_SUCCESS: 'translation.update.values.success'
|
|
72
|
-
};
|
|
73
|
-
const LOCALIZATION_MODULE_MESSAGES = {
|
|
74
|
-
LANGUAGE: LANGUAGE_MESSAGES,
|
|
75
|
-
TRANSLATION_KEY: TRANSLATION_KEY_MESSAGES,
|
|
76
|
-
TRANSLATION: TRANSLATION_MESSAGES
|
|
37
|
+
BY_LANGUAGE_SUCCESS: 'translation.by.language.success'
|
|
77
38
|
};
|