@flusys/nestjs-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.
Files changed (89) hide show
  1. package/README.md +870 -0
  2. package/cjs/config/index.js +19 -0
  3. package/cjs/config/localization.constants.js +20 -0
  4. package/cjs/config/message-keys.js +77 -0
  5. package/cjs/controllers/index.js +20 -0
  6. package/cjs/controllers/language.controller.js +188 -0
  7. package/cjs/controllers/translation-key.controller.js +174 -0
  8. package/cjs/controllers/translation.controller.js +254 -0
  9. package/cjs/dtos/index.js +20 -0
  10. package/cjs/dtos/language.dto.js +205 -0
  11. package/cjs/dtos/translation-key.dto.js +233 -0
  12. package/cjs/dtos/translation.dto.js +298 -0
  13. package/cjs/entities/index.js +42 -0
  14. package/cjs/entities/language.entity.js +126 -0
  15. package/cjs/entities/translation-key.entity.js +125 -0
  16. package/cjs/entities/translation.entity.js +120 -0
  17. package/cjs/index.js +24 -0
  18. package/cjs/interfaces/index.js +21 -0
  19. package/cjs/interfaces/language.interface.js +4 -0
  20. package/cjs/interfaces/localization-module-options.interface.js +4 -0
  21. package/cjs/interfaces/translation-key.interface.js +4 -0
  22. package/cjs/interfaces/translation.interface.js +4 -0
  23. package/cjs/modules/index.js +18 -0
  24. package/cjs/modules/localization.module.js +115 -0
  25. package/cjs/services/index.js +22 -0
  26. package/cjs/services/language.service.js +108 -0
  27. package/cjs/services/localization-config.service.js +66 -0
  28. package/cjs/services/localization-datasource.provider.js +122 -0
  29. package/cjs/services/translation-key.service.js +184 -0
  30. package/cjs/services/translation.service.js +222 -0
  31. package/config/index.d.ts +2 -0
  32. package/config/localization.constants.d.ts +2 -0
  33. package/config/message-keys.d.ts +96 -0
  34. package/controllers/index.d.ts +3 -0
  35. package/controllers/language.controller.d.ts +25 -0
  36. package/controllers/translation-key.controller.d.ts +22 -0
  37. package/controllers/translation.controller.d.ts +25 -0
  38. package/dtos/index.d.ts +3 -0
  39. package/dtos/language.dto.d.ts +24 -0
  40. package/dtos/translation-key.dto.d.ts +29 -0
  41. package/dtos/translation.dto.d.ts +42 -0
  42. package/entities/index.d.ts +8 -0
  43. package/entities/language.entity.d.ts +13 -0
  44. package/entities/translation-key.entity.d.ts +13 -0
  45. package/entities/translation.entity.d.ts +12 -0
  46. package/fesm/config/index.js +2 -0
  47. package/fesm/config/localization.constants.js +2 -0
  48. package/fesm/config/message-keys.js +54 -0
  49. package/fesm/controllers/index.js +3 -0
  50. package/fesm/controllers/language.controller.js +178 -0
  51. package/fesm/controllers/translation-key.controller.js +164 -0
  52. package/fesm/controllers/translation.controller.js +244 -0
  53. package/fesm/dtos/index.js +3 -0
  54. package/fesm/dtos/language.dto.js +184 -0
  55. package/fesm/dtos/translation-key.dto.js +209 -0
  56. package/fesm/dtos/translation.dto.js +265 -0
  57. package/fesm/entities/index.js +14 -0
  58. package/fesm/entities/language.entity.js +116 -0
  59. package/fesm/entities/translation-key.entity.js +115 -0
  60. package/fesm/entities/translation.entity.js +110 -0
  61. package/fesm/index.js +7 -0
  62. package/fesm/interfaces/index.js +4 -0
  63. package/fesm/interfaces/language.interface.js +1 -0
  64. package/fesm/interfaces/localization-module-options.interface.js +1 -0
  65. package/fesm/interfaces/translation-key.interface.js +1 -0
  66. package/fesm/interfaces/translation.interface.js +1 -0
  67. package/fesm/modules/index.js +1 -0
  68. package/fesm/modules/localization.module.js +105 -0
  69. package/fesm/services/index.js +5 -0
  70. package/fesm/services/language.service.js +98 -0
  71. package/fesm/services/localization-config.service.js +56 -0
  72. package/fesm/services/localization-datasource.provider.js +112 -0
  73. package/fesm/services/translation-key.service.js +174 -0
  74. package/fesm/services/translation.service.js +212 -0
  75. package/index.d.ts +7 -0
  76. package/interfaces/index.d.ts +4 -0
  77. package/interfaces/language.interface.d.ts +10 -0
  78. package/interfaces/localization-module-options.interface.d.ts +21 -0
  79. package/interfaces/translation-key.interface.d.ts +11 -0
  80. package/interfaces/translation.interface.d.ts +13 -0
  81. package/modules/index.d.ts +1 -0
  82. package/modules/localization.module.d.ts +8 -0
  83. package/package.json +81 -0
  84. package/services/index.d.ts +5 -0
  85. package/services/language.service.d.ts +18 -0
  86. package/services/localization-config.service.d.ts +11 -0
  87. package/services/localization-datasource.provider.d.ts +16 -0
  88. package/services/translation-key.service.d.ts +23 -0
  89. package/services/translation.service.d.ts +30 -0
package/README.md ADDED
@@ -0,0 +1,870 @@
1
+ # Localization Package Guide
2
+
3
+ > **Package:** `@flusys/nestjs-localization`
4
+ > **Version:** 3.0.1
5
+ > **Type:** Multi-language backend support with TypeORM entities, caching, and translation management
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ `@flusys/nestjs-localization` provides a complete localization backend system:
12
+
13
+ - **Language Management** - CRUD operations for languages with RTL support
14
+ - **Translation Keys** - Module-based key organization with variables
15
+ - **Translations** - Per-language translations with verification status
16
+ - **Caching** - Optional hybrid caching for performance
17
+ - **Dynamic DataSource** - Request-scoped repository pattern
18
+ - **Variable Interpolation** - `{{variable}}` syntax support
19
+ - **Bulk Operations** - Batch update translations
20
+ - **Missing Translation Detection** - Find untranslated keys
21
+
22
+ ### Package Hierarchy
23
+
24
+ ```
25
+ @flusys/nestjs-core <- Foundation (IDataSourceServiceOptions)
26
+ |
27
+ @flusys/nestjs-shared <- Base classes (RequestScopedApiService)
28
+ |
29
+ @flusys/nestjs-localization <- Localization system (THIS PACKAGE)
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Package Architecture
35
+
36
+ ```
37
+ nestjs-localization/
38
+ ├── config/
39
+ │ ├── localization.constants.ts # LOCALIZATION_MODULE_OPTIONS
40
+ │ └── index.ts
41
+ ├── interfaces/
42
+ │ ├── language.interface.ts # ILanguage
43
+ │ ├── translation-key.interface.ts # ITranslationKey
44
+ │ ├── translation.interface.ts # ITranslation, ITranslationsMap
45
+ │ ├── localization-module-options.interface.ts # Module config
46
+ │ └── index.ts
47
+ ├── entities/
48
+ │ ├── language.entity.ts # Language entity
49
+ │ ├── translation-key.entity.ts # TranslationKey entity
50
+ │ ├── translation.entity.ts # Translation entity
51
+ │ └── index.ts
52
+ ├── dtos/
53
+ │ ├── language.dto.ts # Language DTOs
54
+ │ ├── translation-key.dto.ts # TranslationKey DTOs
55
+ │ ├── translation.dto.ts # Translation DTOs
56
+ │ └── index.ts
57
+ ├── services/
58
+ │ ├── localization-config.service.ts # Config management
59
+ │ ├── localization-datasource.provider.ts # DataSource provider
60
+ │ ├── localization-helper.service.ts # Interpolation utilities
61
+ │ ├── language.service.ts # Language CRUD
62
+ │ ├── translation-key.service.ts # TranslationKey CRUD
63
+ │ ├── translation.service.ts # Translation CRUD + queries
64
+ │ └── index.ts
65
+ ├── controllers/
66
+ │ ├── language.controller.ts # Language endpoints
67
+ │ ├── translation-key.controller.ts # TranslationKey endpoints
68
+ │ ├── translation.controller.ts # Translation endpoints
69
+ │ └── index.ts
70
+ ├── modules/
71
+ │ └── localization.module.ts # Module definition
72
+ └── index.ts
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Database Schema
78
+
79
+ ### Language Table
80
+
81
+ | Column | Type | Nullable | Description |
82
+ |--------|------|----------|-------------|
83
+ | `id` | uuid | No | Primary key |
84
+ | `code` | varchar(10) | No | ISO 639-1 code (e.g., 'en', 'ar') |
85
+ | `name` | varchar(100) | No | English name |
86
+ | `native_name` | varchar(100) | Yes | Native name (e.g., 'العربية') |
87
+ | `direction` | varchar(3) | Yes | 'ltr' or 'rtl' |
88
+ | `is_active` | boolean | No | Active status (default: true) |
89
+ | `is_default` | boolean | No | Default language flag |
90
+ | `serial` | int | Yes | Sort order |
91
+ | `created_at` | timestamp | No | Creation timestamp |
92
+ | `updated_at` | timestamp | No | Update timestamp |
93
+ | `deleted_at` | timestamp | Yes | Soft delete timestamp |
94
+
95
+ **Indexes:**
96
+ - `UNIQUE (code)`
97
+ - `INDEX (is_active)`
98
+ - `INDEX (is_default)`
99
+
100
+ ### TranslationKey Table
101
+
102
+ | Column | Type | Nullable | Description |
103
+ |--------|------|----------|-------------|
104
+ | `id` | uuid | No | Primary key |
105
+ | `module` | varchar(100) | No | Module grouping (e.g., 'auth') |
106
+ | `key` | varchar(255) | No | Translation key |
107
+ | `default_message` | varchar(500) | No | Default/fallback message |
108
+ | `description` | text | Yes | Description for translators |
109
+ | `variables` | simple-array | Yes | Required variables |
110
+ | `is_active` | boolean | No | Active status |
111
+ | `serial` | int | Yes | Sort order |
112
+ | `created_at` | timestamp | No | Creation timestamp |
113
+ | `updated_at` | timestamp | No | Update timestamp |
114
+ | `deleted_at` | timestamp | Yes | Soft delete timestamp |
115
+
116
+ **Indexes:**
117
+ - `UNIQUE (key)`
118
+ - `INDEX (module)`
119
+ - `INDEX (module, key)`
120
+ - `INDEX (is_active)`
121
+
122
+ ### Translation Table
123
+
124
+ | Column | Type | Nullable | Description |
125
+ |--------|------|----------|-------------|
126
+ | `id` | uuid | No | Primary key |
127
+ | `language_id` | uuid | No | FK to language |
128
+ | `translation_key_id` | uuid | No | FK to translation_key |
129
+ | `value` | text | No | Translated value |
130
+ | `is_verified` | boolean | No | Verified by translator |
131
+ | `is_active` | boolean | No | Active status |
132
+ | `created_at` | timestamp | No | Creation timestamp |
133
+ | `updated_at` | timestamp | No | Update timestamp |
134
+ | `deleted_at` | timestamp | Yes | Soft delete timestamp |
135
+
136
+ **Indexes:**
137
+ - `UNIQUE (language_id, translation_key_id)`
138
+ - `INDEX (language_id)`
139
+ - `INDEX (translation_key_id)`
140
+ - `INDEX (is_active)`
141
+
142
+ ---
143
+
144
+ ## Module Setup
145
+
146
+ ### Basic Setup
147
+
148
+ ```typescript
149
+ // app.module.ts
150
+ import { LocalizationModule } from '@flusys/nestjs-localization';
151
+
152
+ @Module({
153
+ imports: [
154
+ LocalizationModule.forRoot({
155
+ global: true,
156
+ includeController: true,
157
+ bootstrapAppConfig: {
158
+ dataSource: AppDataSource,
159
+ },
160
+ config: {
161
+ defaultLanguageCode: 'en',
162
+ enableCaching: true,
163
+ cacheTtlSeconds: 300,
164
+ },
165
+ }),
166
+ ],
167
+ })
168
+ export class AppModule {}
169
+ ```
170
+
171
+ ### Async Setup
172
+
173
+ ```typescript
174
+ // app.module.ts
175
+ import { LocalizationModule } from '@flusys/nestjs-localization';
176
+ import { ConfigModule, ConfigService } from '@nestjs/config';
177
+
178
+ @Module({
179
+ imports: [
180
+ LocalizationModule.forRootAsync({
181
+ global: true,
182
+ includeController: true,
183
+ imports: [ConfigModule],
184
+ bootstrapAppConfig: {
185
+ dataSource: AppDataSource,
186
+ },
187
+ useFactory: (configService: ConfigService) => ({
188
+ defaultLanguageCode: configService.get('DEFAULT_LANGUAGE', 'en'),
189
+ enableCaching: configService.get('ENABLE_CACHE', true),
190
+ cacheTtlSeconds: configService.get('CACHE_TTL', 300),
191
+ }),
192
+ inject: [ConfigService],
193
+ }),
194
+ ],
195
+ })
196
+ export class AppModule {}
197
+ ```
198
+
199
+ ### Module Options
200
+
201
+ ```typescript
202
+ interface ILocalizationModuleConfig {
203
+ defaultLanguageCode?: string; // Default: 'en'
204
+ enableCaching?: boolean; // Enable hybrid cache
205
+ cacheTtlSeconds?: number; // Cache TTL in seconds
206
+ }
207
+
208
+ interface LocalizationModuleOptions {
209
+ global?: boolean; // Register as global module
210
+ includeController?: boolean; // Include controllers (default: true)
211
+ bootstrapAppConfig: IBootstrapAppConfig;
212
+ config?: ILocalizationModuleConfig;
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Entities
219
+
220
+ ### Language Entity
221
+
222
+ ```typescript
223
+ @Entity({ name: 'language' })
224
+ export class Language extends Identity {
225
+ @Column({ type: 'varchar', length: 10 })
226
+ code!: string;
227
+
228
+ @Column({ type: 'varchar', length: 100 })
229
+ name!: string;
230
+
231
+ @Column({ type: 'varchar', length: 100, nullable: true, name: 'native_name' })
232
+ nativeName!: string | null;
233
+
234
+ @Column({ type: 'varchar', length: 3, nullable: true })
235
+ direction!: 'ltr' | 'rtl' | null;
236
+
237
+ @Column({ type: 'boolean', default: true, name: 'is_active' })
238
+ isActive!: boolean;
239
+
240
+ @Column({ type: 'boolean', default: false, name: 'is_default' })
241
+ isDefault!: boolean;
242
+
243
+ @Column({ type: 'int', nullable: true })
244
+ serial!: number | null;
245
+
246
+ @OneToMany('Translation', 'language')
247
+ translations?: Translation[];
248
+ }
249
+ ```
250
+
251
+ ### TranslationKey Entity
252
+
253
+ ```typescript
254
+ @Entity({ name: 'translation_key' })
255
+ export class TranslationKey extends Identity {
256
+ @Column({ type: 'varchar', length: 100 })
257
+ module!: string;
258
+
259
+ @Column({ type: 'varchar', length: 255 })
260
+ key!: string;
261
+
262
+ @Column({ type: 'varchar', length: 500, name: 'default_message' })
263
+ defaultMessage!: string;
264
+
265
+ @Column({ type: 'text', nullable: true })
266
+ description!: string | null;
267
+
268
+ @Column({ type: 'simple-array', nullable: true })
269
+ variables!: string[] | null;
270
+
271
+ @Column({ type: 'boolean', default: true, name: 'is_active' })
272
+ isActive!: boolean;
273
+
274
+ @Column({ type: 'int', nullable: true })
275
+ serial!: number | null;
276
+
277
+ @OneToMany('Translation', 'translationKey')
278
+ translations?: Translation[];
279
+ }
280
+ ```
281
+
282
+ ### Translation Entity
283
+
284
+ ```typescript
285
+ @Entity({ name: 'translation' })
286
+ export class Translation extends Identity {
287
+ @Column({ type: 'uuid', name: 'language_id' })
288
+ languageId!: string;
289
+
290
+ @Column({ type: 'uuid', name: 'translation_key_id' })
291
+ translationKeyId!: string;
292
+
293
+ @Column({ type: 'text' })
294
+ value!: string;
295
+
296
+ @Column({ type: 'boolean', default: false, name: 'is_verified' })
297
+ isVerified!: boolean;
298
+
299
+ @Column({ type: 'boolean', default: true, name: 'is_active' })
300
+ isActive!: boolean;
301
+
302
+ @ManyToOne(() => Language)
303
+ @JoinColumn({ name: 'language_id' })
304
+ language?: Language;
305
+
306
+ @ManyToOne(() => TranslationKey)
307
+ @JoinColumn({ name: 'translation_key_id' })
308
+ translationKey?: TranslationKey;
309
+ }
310
+ ```
311
+
312
+ ---
313
+
314
+ ## Services
315
+
316
+ ### LanguageService
317
+
318
+ CRUD operations for languages:
319
+
320
+ ```typescript
321
+ @Injectable({ scope: Scope.REQUEST })
322
+ export class LanguageService extends RequestScopedApiService<...> {
323
+ // Inherited CRUD from RequestScopedApiService
324
+ // insert(), update(), delete(), findById(), findAll()
325
+
326
+ // Get active languages (public, no auth)
327
+ async getActiveLanguages(): Promise<Language[]>;
328
+
329
+ // Get default language
330
+ async getDefaultLanguage(): Promise<Language | null>;
331
+
332
+ // Set default language (unsets previous default)
333
+ async setDefaultLanguage(id: string): Promise<Language>;
334
+ }
335
+ ```
336
+
337
+ ### TranslationKeyService
338
+
339
+ CRUD operations for translation keys:
340
+
341
+ ```typescript
342
+ @Injectable({ scope: Scope.REQUEST })
343
+ export class TranslationKeyService extends RequestScopedApiService<...> {
344
+ // Inherited CRUD from RequestScopedApiService
345
+
346
+ // Get keys by module
347
+ async getByModule(module: string): Promise<TranslationKey[]>;
348
+
349
+ // Get all unique modules
350
+ async getModules(): Promise<string[]>;
351
+ }
352
+ ```
353
+
354
+ ### TranslationService
355
+
356
+ Translation operations with queries:
357
+
358
+ ```typescript
359
+ @Injectable({ scope: Scope.REQUEST })
360
+ export class TranslationService extends RequestScopedApiService<...> {
361
+ // Inherited CRUD from RequestScopedApiService
362
+
363
+ // Get all translations for a language (public, no auth)
364
+ async getByLanguageCode(languageCode: string): Promise<ITranslationsMap>;
365
+
366
+ // Get translations by module and language (public, no auth)
367
+ async getByModuleAndLanguage(module: string, languageCode: string): Promise<ITranslationsMap>;
368
+
369
+ // Bulk update translation values
370
+ async bulkUpdate(updates: BulkUpdateTranslationDto[]): Promise<Translation[]>;
371
+
372
+ // Get translation keys missing translations for a language
373
+ async getMissingTranslations(languageCode: string): Promise<TranslationKey[]>;
374
+ }
375
+ ```
376
+
377
+ ### LocalizationHelperService
378
+
379
+ Variable interpolation utilities:
380
+
381
+ ```typescript
382
+ @Injectable()
383
+ export class LocalizationHelperService {
384
+ // Replace {{variable}} placeholders with values
385
+ interpolate(template: string, variables?: Record<string, string | number>): string;
386
+
387
+ // Extract variable names from template
388
+ extractVariables(template: string): string[];
389
+ }
390
+ ```
391
+
392
+ **Example:**
393
+
394
+ ```typescript
395
+ const helper = inject(LocalizationHelperService);
396
+
397
+ const result = helper.interpolate('Hello, {{name}}!', { name: 'John' });
398
+ // Output: "Hello, John!"
399
+
400
+ const vars = helper.extractVariables('Welcome {{user}}, you have {{count}} items');
401
+ // Output: ['user', 'count']
402
+ ```
403
+
404
+ ---
405
+
406
+ ## Controllers
407
+
408
+ All endpoints follow **POST-only RPC** style (not REST).
409
+
410
+ ### Language Controller
411
+
412
+ | Endpoint | Auth | Permission | Description |
413
+ |----------|------|------------|-------------|
414
+ | `POST /localization/language/insert` | JWT | `language.create` | Create language |
415
+ | `POST /localization/language/update` | JWT | `language.update` | Update language |
416
+ | `POST /localization/language/delete` | JWT | `language.delete` | Delete language |
417
+ | `POST /localization/language/get-by-id` | JWT | `language.read` | Get by ID |
418
+ | `POST /localization/language/get-all` | JWT | `language.read` | Get all with pagination |
419
+ | `POST /localization/language/get-active` | None | - | Get active languages |
420
+ | `POST /localization/language/get-default` | None | - | Get default language |
421
+ | `POST /localization/language/set-default` | JWT | `language.update` | Set default language |
422
+
423
+ ### TranslationKey Controller
424
+
425
+ | Endpoint | Auth | Permission | Description |
426
+ |----------|------|------------|-------------|
427
+ | `POST /localization/translation-key/insert` | JWT | `translation-key.create` | Create key |
428
+ | `POST /localization/translation-key/update` | JWT | `translation-key.update` | Update key |
429
+ | `POST /localization/translation-key/delete` | JWT | `translation-key.delete` | Delete key |
430
+ | `POST /localization/translation-key/get-by-id` | JWT | `translation-key.read` | Get by ID |
431
+ | `POST /localization/translation-key/get-all` | JWT | `translation-key.read` | Get all with pagination |
432
+ | `POST /localization/translation-key/get-by-module` | JWT | `translation-key.read` | Get keys by module |
433
+ | `POST /localization/translation-key/get-modules` | JWT | `translation-key.read` | Get all unique module names |
434
+
435
+ ### Translation Controller
436
+
437
+ | Endpoint | Auth | Permission | Description |
438
+ |----------|------|------------|-------------|
439
+ | `POST /localization/translation/insert` | JWT | `translation.create` | Create translation |
440
+ | `POST /localization/translation/update` | JWT | `translation.update` | Update translation |
441
+ | `POST /localization/translation/delete` | JWT | `translation.delete` | Delete translation |
442
+ | `POST /localization/translation/get-by-id` | JWT | `translation.read` | Get by ID |
443
+ | `POST /localization/translation/get-all` | JWT | `translation.read` | Get all with pagination |
444
+ | `POST /localization/translation/get-by-language` | None | - | Get translations by language code |
445
+ | `POST /localization/translation/get-by-module` | None | - | Get translations by module + language |
446
+ | `POST /localization/translation/update-many-values` | JWT | `translation.update` | Bulk update translations |
447
+ | `POST /localization/translation/get-missing` | JWT | `translation.read` | Get missing translations |
448
+
449
+ ---
450
+
451
+ ## DTOs
452
+
453
+ ### Language DTOs
454
+
455
+ ```typescript
456
+ // Create
457
+ interface CreateLanguageDto {
458
+ code: string; // Required, ISO 639-1
459
+ name: string; // Required
460
+ nativeName?: string;
461
+ direction?: 'ltr' | 'rtl';
462
+ isActive?: boolean;
463
+ isDefault?: boolean;
464
+ serial?: number;
465
+ }
466
+
467
+ // Update
468
+ interface UpdateLanguageDto extends Partial<CreateLanguageDto> {
469
+ id: string; // Required
470
+ }
471
+
472
+ // Response
473
+ interface LanguageResponseDto {
474
+ id: string;
475
+ code: string;
476
+ name: string;
477
+ nativeName: string | null;
478
+ direction: 'ltr' | 'rtl' | null;
479
+ isActive: boolean;
480
+ isDefault: boolean;
481
+ serial: number | null;
482
+ createdAt: Date;
483
+ updatedAt: Date;
484
+ }
485
+ ```
486
+
487
+ ### TranslationKey DTOs
488
+
489
+ ```typescript
490
+ // Create
491
+ interface CreateTranslationKeyDto {
492
+ module: string; // Required
493
+ key: string; // Required
494
+ defaultMessage: string; // Required
495
+ description?: string;
496
+ variables?: string[];
497
+ isActive?: boolean;
498
+ serial?: number;
499
+ }
500
+
501
+ // Update
502
+ interface UpdateTranslationKeyDto extends Partial<CreateTranslationKeyDto> {
503
+ id: string; // Required
504
+ }
505
+ ```
506
+
507
+ ### Translation DTOs
508
+
509
+ ```typescript
510
+ // Create
511
+ interface CreateTranslationDto {
512
+ languageId: string; // Required
513
+ translationKeyId: string; // Required
514
+ value: string; // Required
515
+ isVerified?: boolean;
516
+ isActive?: boolean;
517
+ }
518
+
519
+ // Update
520
+ interface UpdateTranslationDto extends Partial<CreateTranslationDto> {
521
+ id: string; // Required
522
+ }
523
+
524
+ // Bulk Update
525
+ interface BulkUpdateTranslationDto {
526
+ id: string;
527
+ value: string;
528
+ isVerified?: boolean;
529
+ }
530
+
531
+ interface BulkUpdateTranslationsDto {
532
+ translations: BulkUpdateTranslationDto[];
533
+ }
534
+
535
+ // Query DTOs
536
+ interface GetTranslationsByLanguageDto {
537
+ languageCode: string;
538
+ }
539
+
540
+ interface GetTranslationsByModuleDto {
541
+ module: string;
542
+ languageCode: string;
543
+ }
544
+ ```
545
+
546
+ ---
547
+
548
+ ## Interfaces
549
+
550
+ ### ILanguage
551
+
552
+ ```typescript
553
+ interface ILanguage {
554
+ id: string;
555
+ code: string;
556
+ name: string;
557
+ nativeName: string | null;
558
+ direction: 'ltr' | 'rtl' | null;
559
+ isActive: boolean;
560
+ isDefault: boolean;
561
+ serial: number | null;
562
+ }
563
+ ```
564
+
565
+ ### ITranslationKey
566
+
567
+ ```typescript
568
+ interface ITranslationKey {
569
+ id: string;
570
+ module: string;
571
+ key: string;
572
+ defaultMessage: string;
573
+ description: string | null;
574
+ variables: string[] | null;
575
+ isActive: boolean;
576
+ serial: number | null;
577
+ }
578
+ ```
579
+
580
+ ### ITranslation
581
+
582
+ ```typescript
583
+ interface ITranslation {
584
+ id: string;
585
+ languageId: string;
586
+ translationKeyId: string;
587
+ value: string;
588
+ isVerified: boolean;
589
+ isActive: boolean;
590
+ }
591
+ ```
592
+
593
+ ### ITranslationsMap
594
+
595
+ ```typescript
596
+ type ITranslationsMap = Record<string, string>;
597
+
598
+ // Example
599
+ {
600
+ 'shared.save': 'Save',
601
+ 'shared.cancel': 'Cancel',
602
+ 'auth.login.title': 'Login',
603
+ }
604
+ ```
605
+
606
+ ---
607
+
608
+ ## Permissions
609
+
610
+ | Resource | Permission | Description |
611
+ |----------|------------|-------------|
612
+ | Language | `language.create` | Create languages |
613
+ | Language | `language.read` | Read languages |
614
+ | Language | `language.update` | Update languages |
615
+ | Language | `language.delete` | Delete languages |
616
+ | TranslationKey | `translation-key.create` | Create keys |
617
+ | TranslationKey | `translation-key.read` | Read keys |
618
+ | TranslationKey | `translation-key.update` | Update keys |
619
+ | TranslationKey | `translation-key.delete` | Delete keys |
620
+ | Translation | `translation.create` | Create translations |
621
+ | Translation | `translation.read` | Read translations |
622
+ | Translation | `translation.update` | Update translations |
623
+ | Translation | `translation.delete` | Delete translations |
624
+
625
+ ---
626
+
627
+ ## Data Flow
628
+
629
+ ### Public Translation Request
630
+
631
+ ```
632
+ Frontend requests translations
633
+ ↓
634
+ POST /localization/translation/get-by-language
635
+ { languageCode: 'en' }
636
+ ↓
637
+ TranslationController.getByLanguage()
638
+ ↓
639
+ TranslationService.getByLanguageCode('en')
640
+ ↓
641
+ Find language by code (active only)
642
+ ↓
643
+ Query translations with JOIN on translation_key
644
+ ↓
645
+ Build ITranslationsMap { key: value }
646
+ ↓
647
+ Return SingleResponseDto<ITranslationsMap>
648
+ ```
649
+
650
+ ### Admin Translation Update
651
+
652
+ ```
653
+ Admin updates translations
654
+ ↓
655
+ POST /localization/translation/update-many-values
656
+ { translations: [{ id, value, isVerified }] }
657
+ ↓
658
+ JWT Auth + permission check (translation.update)
659
+ ↓
660
+ TranslationController.updateManyValues()
661
+ ↓
662
+ TranslationService.bulkUpdate(translations)
663
+ ↓
664
+ Update each translation in database
665
+ ↓
666
+ Return BulkResponseDto with updated count
667
+ ```
668
+
669
+ ---
670
+
671
+ ## Usage Examples
672
+
673
+ ### Inject Services
674
+
675
+ ```typescript
676
+ import { TranslationService, LanguageService } from '@flusys/nestjs-localization';
677
+
678
+ @Injectable()
679
+ export class MyService {
680
+ constructor(
681
+ private readonly translationService: TranslationService,
682
+ private readonly languageService: LanguageService,
683
+ ) {}
684
+
685
+ async getTranslationsForUser(languageCode: string) {
686
+ return this.translationService.getByLanguageCode(languageCode);
687
+ }
688
+
689
+ async getActiveLanguages() {
690
+ return this.languageService.getActiveLanguages();
691
+ }
692
+ }
693
+ ```
694
+
695
+ ### Variable Interpolation
696
+
697
+ ```typescript
698
+ import { LocalizationHelperService } from '@flusys/nestjs-localization';
699
+
700
+ @Injectable()
701
+ export class NotificationService {
702
+ constructor(private readonly helper: LocalizationHelperService) {}
703
+
704
+ formatMessage(template: string, user: User) {
705
+ return this.helper.interpolate(template, {
706
+ name: user.name,
707
+ count: user.notifications.length,
708
+ });
709
+ }
710
+ }
711
+ ```
712
+
713
+ ### Seeding Languages
714
+
715
+ ```typescript
716
+ // seed/languages.seed.ts
717
+ const languages = [
718
+ { code: 'en', name: 'English', nativeName: 'English', direction: 'ltr', isDefault: true, isActive: true },
719
+ { code: 'ar', name: 'Arabic', nativeName: 'العربية', direction: 'rtl', isActive: true },
720
+ { code: 'fr', name: 'French', nativeName: 'Français', direction: 'ltr', isActive: true },
721
+ ];
722
+
723
+ // Use LanguageService to seed
724
+ for (const lang of languages) {
725
+ await languageService.insert(lang);
726
+ }
727
+ ```
728
+
729
+ ### Seeding Translation Keys
730
+
731
+ ```typescript
732
+ const keys = [
733
+ { module: 'shared', key: 'shared.save', defaultMessage: 'Save' },
734
+ { module: 'shared', key: 'shared.cancel', defaultMessage: 'Cancel' },
735
+ { module: 'auth', key: 'auth.login.title', defaultMessage: 'Login' },
736
+ { module: 'auth', key: 'auth.welcome', defaultMessage: 'Welcome, {{name}}!', variables: ['name'] },
737
+ ];
738
+ ```
739
+
740
+ ---
741
+
742
+ ## Caching
743
+
744
+ When `enableCaching: true`:
745
+
746
+ - Translations are cached using HybridCache
747
+ - Cache key format: `localization:translations:{languageCode}`
748
+ - TTL configured via `cacheTtlSeconds`
749
+ - Cache invalidated on translation updates
750
+
751
+ ---
752
+
753
+ ## Frontend Integration
754
+
755
+ The backend serves translations to the frontend using **module-wise loading**:
756
+
757
+ ### Module-Wise Loading Pattern
758
+
759
+ Frontend loads translations **per-module** as users navigate, NOT all at once:
760
+
761
+ ```
762
+ App Init
763
+ ↓
764
+ POST /localization/language/get-active → Load available languages
765
+ ↓
766
+ User navigates to protected route
767
+ ↓
768
+ POST /localization/translation/get-by-module
769
+ { module: 'shared', languageCode: 'en' }
770
+ ↓
771
+ POST /localization/translation/get-by-module
772
+ { module: 'layout', languageCode: 'en' }
773
+ ↓
774
+ User navigates to /storage
775
+ ↓
776
+ POST /localization/translation/get-by-module
777
+ { module: 'storage', languageCode: 'en' }
778
+ ```
779
+
780
+ ### Available Modules
781
+
782
+ > **Key Format:** All translation keys use **full dot-case** — every word separated by a dot, all lowercase. No camelCase anywhere in keys.
783
+ > ```
784
+ > ✅ event.manager.event.create.success
785
+ > ❌ eventManager.eventCreateSuccess
786
+ > ```
787
+
788
+ | Module | Keys Prefix | Description |
789
+ |--------|-------------|-------------|
790
+ | `shared` | `shared.*` | Common UI labels, actions, validation messages |
791
+ | `layout` | `layout.*` | Layout, navigation, topbar labels |
792
+ | `auth` | `auth.*` | Login, register, password, user management |
793
+ | `iam` | `iam.*` | Roles, permissions, actions |
794
+ | `storage` | `storage.*` | File upload, folders, storage config |
795
+ | `formBuilder` | `formBuilder.*` | Form builder UI |
796
+ | `email` | `email.*` | Email templates, config |
797
+ | `notification` | `notification.*` | Notifications |
798
+ | `eventManager` | `event.manager.*` | Calendar, events |
799
+ | `localization` | `localization.*` | Language & translation admin UI |
800
+
801
+ ### When Frontend Localization is ENABLED
802
+
803
+ 1. **App Init:** Frontend calls `POST /localization/language/get-active`
804
+ 2. **Route Navigation:** Frontend calls `POST /localization/translation/get-by-module` for each feature
805
+ 3. **Language Switch:** Frontend reloads translations for already-loaded modules
806
+
807
+ ### When Frontend Localization is DISABLED
808
+
809
+ 1. Frontend skips all API calls to localization endpoints
810
+ 2. Frontend uses hardcoded fallback messages only
811
+ 3. Backend localization module is not utilized (can still be included for admin use)
812
+
813
+ ### Response Format
814
+
815
+ All public endpoints return consistent format:
816
+
817
+ ```typescript
818
+ // GET Active Languages
819
+ {
820
+ success: true,
821
+ data: [
822
+ { id: '...', code: 'en', name: 'English', nativeName: 'English', direction: 'ltr', isDefault: true },
823
+ { id: '...', code: 'ar', name: 'Arabic', nativeName: 'العربية', direction: 'rtl', isDefault: false }
824
+ ]
825
+ }
826
+
827
+ // GET Translations by Module
828
+ {
829
+ success: true,
830
+ data: {
831
+ 'storage.folders.title': 'Folders',
832
+ 'storage.upload.success': 'File uploaded successfully',
833
+ 'storage.delete.confirm': 'Are you sure you want to delete this file?'
834
+ }
835
+ }
836
+ ```
837
+
838
+ ---
839
+
840
+ ## Best Practices
841
+
842
+ | Practice | Description |
843
+ |----------|-------------|
844
+ | **Module Organization** | Group keys by module (auth, shared, storage) |
845
+ | **Key Naming** | Use full dot-case: `module.feature.action` — all lowercase, dots only, no camelCase |
846
+ | **Default Messages** | Always provide meaningful defaults |
847
+ | **Document Variables** | Use `variables` array to document required placeholders |
848
+ | **Description Field** | Add context for translators |
849
+ | **Soft Delete** | Use soft delete for translations (recoverable) |
850
+ | **Bulk Operations** | Use bulk update for efficiency |
851
+
852
+ ---
853
+
854
+ ## Public API Exports
855
+
856
+ | Category | Exports |
857
+ |----------|---------|
858
+ | **Entities** | `Language`, `TranslationKey`, `Translation` |
859
+ | **Interfaces** | `ILanguage`, `ITranslationKey`, `ITranslation`, `ITranslationsMap`, `ILocalizationModuleConfig`, `LocalizationModuleOptions` |
860
+ | **DTOs** | `CreateLanguageDto`, `UpdateLanguageDto`, `LanguageResponseDto`, `CreateTranslationKeyDto`, `UpdateTranslationKeyDto`, `TranslationKeyResponseDto`, `CreateTranslationDto`, `UpdateTranslationDto`, `TranslationResponseDto`, `BulkUpdateTranslationsDto`, `GetTranslationsByLanguageDto`, `GetTranslationsByModuleDto` |
861
+ | **Services** | `LanguageService`, `TranslationKeyService`, `TranslationService`, `LocalizationHelperService`, `LocalizationConfigService`, `LocalizationDataSourceProvider` |
862
+ | **Controllers** | `LanguageController`, `TranslationKeyController`, `TranslationController` |
863
+ | **Module** | `LocalizationModule` |
864
+ | **Constants** | `LOCALIZATION_MODULE_OPTIONS` |
865
+
866
+ ---
867
+
868
+ **Last Updated:** 2026-03-01
869
+ **Version:** 3.0.2
870
+ **NestJS Version:** 11