@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.
- package/README.md +870 -0
- package/cjs/config/index.js +19 -0
- package/cjs/config/localization.constants.js +20 -0
- package/cjs/config/message-keys.js +77 -0
- package/cjs/controllers/index.js +20 -0
- package/cjs/controllers/language.controller.js +188 -0
- package/cjs/controllers/translation-key.controller.js +174 -0
- package/cjs/controllers/translation.controller.js +254 -0
- package/cjs/dtos/index.js +20 -0
- package/cjs/dtos/language.dto.js +205 -0
- package/cjs/dtos/translation-key.dto.js +233 -0
- package/cjs/dtos/translation.dto.js +298 -0
- package/cjs/entities/index.js +42 -0
- package/cjs/entities/language.entity.js +126 -0
- package/cjs/entities/translation-key.entity.js +125 -0
- package/cjs/entities/translation.entity.js +120 -0
- package/cjs/index.js +24 -0
- package/cjs/interfaces/index.js +21 -0
- package/cjs/interfaces/language.interface.js +4 -0
- package/cjs/interfaces/localization-module-options.interface.js +4 -0
- package/cjs/interfaces/translation-key.interface.js +4 -0
- package/cjs/interfaces/translation.interface.js +4 -0
- package/cjs/modules/index.js +18 -0
- package/cjs/modules/localization.module.js +115 -0
- package/cjs/services/index.js +22 -0
- package/cjs/services/language.service.js +108 -0
- package/cjs/services/localization-config.service.js +66 -0
- package/cjs/services/localization-datasource.provider.js +122 -0
- package/cjs/services/translation-key.service.js +184 -0
- package/cjs/services/translation.service.js +222 -0
- package/config/index.d.ts +2 -0
- package/config/localization.constants.d.ts +2 -0
- package/config/message-keys.d.ts +96 -0
- package/controllers/index.d.ts +3 -0
- package/controllers/language.controller.d.ts +25 -0
- package/controllers/translation-key.controller.d.ts +22 -0
- package/controllers/translation.controller.d.ts +25 -0
- package/dtos/index.d.ts +3 -0
- package/dtos/language.dto.d.ts +24 -0
- package/dtos/translation-key.dto.d.ts +29 -0
- package/dtos/translation.dto.d.ts +42 -0
- package/entities/index.d.ts +8 -0
- package/entities/language.entity.d.ts +13 -0
- package/entities/translation-key.entity.d.ts +13 -0
- package/entities/translation.entity.d.ts +12 -0
- package/fesm/config/index.js +2 -0
- package/fesm/config/localization.constants.js +2 -0
- package/fesm/config/message-keys.js +54 -0
- package/fesm/controllers/index.js +3 -0
- package/fesm/controllers/language.controller.js +178 -0
- package/fesm/controllers/translation-key.controller.js +164 -0
- package/fesm/controllers/translation.controller.js +244 -0
- package/fesm/dtos/index.js +3 -0
- package/fesm/dtos/language.dto.js +184 -0
- package/fesm/dtos/translation-key.dto.js +209 -0
- package/fesm/dtos/translation.dto.js +265 -0
- package/fesm/entities/index.js +14 -0
- package/fesm/entities/language.entity.js +116 -0
- package/fesm/entities/translation-key.entity.js +115 -0
- package/fesm/entities/translation.entity.js +110 -0
- package/fesm/index.js +7 -0
- package/fesm/interfaces/index.js +4 -0
- package/fesm/interfaces/language.interface.js +1 -0
- package/fesm/interfaces/localization-module-options.interface.js +1 -0
- package/fesm/interfaces/translation-key.interface.js +1 -0
- package/fesm/interfaces/translation.interface.js +1 -0
- package/fesm/modules/index.js +1 -0
- package/fesm/modules/localization.module.js +105 -0
- package/fesm/services/index.js +5 -0
- package/fesm/services/language.service.js +98 -0
- package/fesm/services/localization-config.service.js +56 -0
- package/fesm/services/localization-datasource.provider.js +112 -0
- package/fesm/services/translation-key.service.js +174 -0
- package/fesm/services/translation.service.js +212 -0
- package/index.d.ts +7 -0
- package/interfaces/index.d.ts +4 -0
- package/interfaces/language.interface.d.ts +10 -0
- package/interfaces/localization-module-options.interface.d.ts +21 -0
- package/interfaces/translation-key.interface.d.ts +11 -0
- package/interfaces/translation.interface.d.ts +13 -0
- package/modules/index.d.ts +1 -0
- package/modules/localization.module.d.ts +8 -0
- package/package.json +81 -0
- package/services/index.d.ts +5 -0
- package/services/language.service.d.ts +18 -0
- package/services/localization-config.service.d.ts +11 -0
- package/services/localization-datasource.provider.d.ts +16 -0
- package/services/translation-key.service.d.ts +23 -0
- 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
|