@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
|
@@ -27,15 +27,15 @@ function _ts_param(paramIndex, decorator) {
|
|
|
27
27
|
}
|
|
28
28
|
import { createApiController, TRANSLATION_KEY_PERMISSIONS } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { CurrentUser, RequirePermission } from '@flusys/nestjs-shared/decorators';
|
|
30
|
-
import { TRANSLATION_KEY_MESSAGES } from '../config';
|
|
31
30
|
import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
32
31
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
33
|
-
import {
|
|
32
|
+
import { Controller, Inject, Post, UseGuards } from '@nestjs/common';
|
|
34
33
|
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
35
|
-
import {
|
|
34
|
+
import { TRANSLATION_KEY_MESSAGES } from '../config';
|
|
35
|
+
import { CreateTranslationKeyDto, TranslationKeyResponseDto, UpdateTranslationKeyDto } from '../dtos';
|
|
36
36
|
import { TranslationKeyService } from '../services';
|
|
37
37
|
export class TranslationKeyController extends createApiController(CreateTranslationKeyDto, UpdateTranslationKeyDto, TranslationKeyResponseDto, {
|
|
38
|
-
entityName: '
|
|
38
|
+
entityName: 'translation.key',
|
|
39
39
|
security: {
|
|
40
40
|
insert: {
|
|
41
41
|
level: 'permission',
|
|
@@ -90,21 +90,6 @@ export class TranslationKeyController extends createApiController(CreateTranslat
|
|
|
90
90
|
data
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
-
async getByModule(body, _user) {
|
|
94
|
-
const data = await this.service.findByModule(body.module);
|
|
95
|
-
return {
|
|
96
|
-
success: true,
|
|
97
|
-
message: 'Translation keys retrieved successfully',
|
|
98
|
-
messageKey: TRANSLATION_KEY_MESSAGES.GET_ALL_SUCCESS,
|
|
99
|
-
data,
|
|
100
|
-
meta: {
|
|
101
|
-
total: data.length,
|
|
102
|
-
page: 0,
|
|
103
|
-
pageSize: data.length,
|
|
104
|
-
count: data.length
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
93
|
constructor(service){
|
|
109
94
|
super(service), _define_property(this, "service", void 0), this.service = service;
|
|
110
95
|
}
|
|
@@ -130,29 +115,6 @@ _ts_decorate([
|
|
|
130
115
|
]),
|
|
131
116
|
_ts_metadata("design:returntype", Promise)
|
|
132
117
|
], TranslationKeyController.prototype, "getModules", null);
|
|
133
|
-
_ts_decorate([
|
|
134
|
-
Post('get-by-module'),
|
|
135
|
-
UseGuards(JwtAuthGuard),
|
|
136
|
-
ApiBearerAuth(),
|
|
137
|
-
RequirePermission(TRANSLATION_KEY_PERMISSIONS.READ),
|
|
138
|
-
ApiOperation({
|
|
139
|
-
summary: 'Get translation keys by module'
|
|
140
|
-
}),
|
|
141
|
-
ApiResponse({
|
|
142
|
-
status: 200,
|
|
143
|
-
type: [
|
|
144
|
-
TranslationKeyResponseDto
|
|
145
|
-
]
|
|
146
|
-
}),
|
|
147
|
-
_ts_param(0, Body()),
|
|
148
|
-
_ts_param(1, CurrentUser()),
|
|
149
|
-
_ts_metadata("design:type", Function),
|
|
150
|
-
_ts_metadata("design:paramtypes", [
|
|
151
|
-
typeof GetByModuleDto === "undefined" ? Object : GetByModuleDto,
|
|
152
|
-
typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo
|
|
153
|
-
]),
|
|
154
|
-
_ts_metadata("design:returntype", Promise)
|
|
155
|
-
], TranslationKeyController.prototype, "getByModule", null);
|
|
156
118
|
TranslationKeyController = _ts_decorate([
|
|
157
119
|
ApiTags('Localization - Translation Key'),
|
|
158
120
|
Controller('localization/translation-key'),
|
|
@@ -32,7 +32,7 @@ import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
|
|
|
32
32
|
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
33
33
|
import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
|
|
34
34
|
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
|
35
|
-
import { BulkUpdateTranslationsDto, CreateTranslationDto, GetTranslationsByLanguageDto,
|
|
35
|
+
import { BulkUpdateTranslationsDto, CreateTranslationDto, GetTranslationsByLanguageDto, TranslationResponseDto, UpdateTranslationDto } from '../dtos';
|
|
36
36
|
import { TranslationService } from '../services';
|
|
37
37
|
export class TranslationController extends createApiController(CreateTranslationDto, UpdateTranslationDto, TranslationResponseDto, {
|
|
38
38
|
entityName: 'translation',
|
|
@@ -90,15 +90,6 @@ export class TranslationController extends createApiController(CreateTranslation
|
|
|
90
90
|
data
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
-
async getByModule(body) {
|
|
94
|
-
const data = await this.service.getByModuleAndLanguage(body.module, body.languageCode);
|
|
95
|
-
return {
|
|
96
|
-
success: true,
|
|
97
|
-
message: 'Translations by module retrieved successfully',
|
|
98
|
-
messageKey: TRANSLATION_MESSAGES.BY_MODULE_SUCCESS,
|
|
99
|
-
data
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
93
|
async updateManyValues(body, _user) {
|
|
103
94
|
const data = await this.service.bulkUpdate(body.translations);
|
|
104
95
|
return {
|
|
@@ -114,21 +105,6 @@ export class TranslationController extends createApiController(CreateTranslation
|
|
|
114
105
|
}
|
|
115
106
|
};
|
|
116
107
|
}
|
|
117
|
-
async getMissing(body, _user) {
|
|
118
|
-
const data = await this.service.getMissingTranslations(body.languageCode);
|
|
119
|
-
return {
|
|
120
|
-
success: true,
|
|
121
|
-
message: 'Missing translations retrieved successfully',
|
|
122
|
-
messageKey: TRANSLATION_MESSAGES.MISSING_SUCCESS,
|
|
123
|
-
data,
|
|
124
|
-
meta: {
|
|
125
|
-
total: data.length,
|
|
126
|
-
page: 0,
|
|
127
|
-
pageSize: data.length,
|
|
128
|
-
count: data.length
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
108
|
async getCounts(_user) {
|
|
133
109
|
const data = await this.service.getTranslationCounts();
|
|
134
110
|
return {
|
|
@@ -157,21 +133,6 @@ _ts_decorate([
|
|
|
157
133
|
]),
|
|
158
134
|
_ts_metadata("design:returntype", Promise)
|
|
159
135
|
], TranslationController.prototype, "getByLanguage", null);
|
|
160
|
-
_ts_decorate([
|
|
161
|
-
Post('get-by-module'),
|
|
162
|
-
ApiOperation({
|
|
163
|
-
summary: 'Get translations by module and language'
|
|
164
|
-
}),
|
|
165
|
-
ApiResponse({
|
|
166
|
-
status: 200
|
|
167
|
-
}),
|
|
168
|
-
_ts_param(0, Body()),
|
|
169
|
-
_ts_metadata("design:type", Function),
|
|
170
|
-
_ts_metadata("design:paramtypes", [
|
|
171
|
-
typeof GetTranslationsByModuleDto === "undefined" ? Object : GetTranslationsByModuleDto
|
|
172
|
-
]),
|
|
173
|
-
_ts_metadata("design:returntype", Promise)
|
|
174
|
-
], TranslationController.prototype, "getByModule", null);
|
|
175
136
|
_ts_decorate([
|
|
176
137
|
Post('update-many-values'),
|
|
177
138
|
UseGuards(JwtAuthGuard),
|
|
@@ -195,26 +156,6 @@ _ts_decorate([
|
|
|
195
156
|
]),
|
|
196
157
|
_ts_metadata("design:returntype", Promise)
|
|
197
158
|
], TranslationController.prototype, "updateManyValues", null);
|
|
198
|
-
_ts_decorate([
|
|
199
|
-
Post('get-missing'),
|
|
200
|
-
UseGuards(JwtAuthGuard),
|
|
201
|
-
ApiBearerAuth(),
|
|
202
|
-
RequirePermission(TRANSLATION_PERMISSIONS.READ),
|
|
203
|
-
ApiOperation({
|
|
204
|
-
summary: 'Get translation keys missing translations for a language'
|
|
205
|
-
}),
|
|
206
|
-
ApiResponse({
|
|
207
|
-
status: 200
|
|
208
|
-
}),
|
|
209
|
-
_ts_param(0, Body()),
|
|
210
|
-
_ts_param(1, CurrentUser()),
|
|
211
|
-
_ts_metadata("design:type", Function),
|
|
212
|
-
_ts_metadata("design:paramtypes", [
|
|
213
|
-
typeof GetTranslationsByLanguageDto === "undefined" ? Object : GetTranslationsByLanguageDto,
|
|
214
|
-
typeof ILoggedUserInfo === "undefined" ? Object : ILoggedUserInfo
|
|
215
|
-
]),
|
|
216
|
-
_ts_metadata("design:returntype", Promise)
|
|
217
|
-
], TranslationController.prototype, "getMissing", null);
|
|
218
159
|
_ts_decorate([
|
|
219
160
|
Post('get-counts'),
|
|
220
161
|
UseGuards(JwtAuthGuard),
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { localizationSwaggerConfig } from './localization-swagger.config';
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
export function localizationSwaggerConfig(bootstrapConfig) {
|
|
2
|
+
const isMultiTenant = bootstrapConfig?.databaseMode === 'multi-tenant';
|
|
3
|
+
const multiTenantNote = isMultiTenant ? `\n> **Multi-Tenant Mode**: Include \`x-tenant-id\` header to target a specific tenant database.\n` : '';
|
|
4
|
+
return {
|
|
5
|
+
title: 'Localization API',
|
|
6
|
+
description: `
|
|
7
|
+
## Localization API
|
|
8
|
+
${multiTenantNote}
|
|
9
|
+
|
|
10
|
+
Database-driven internationalization system. Manage languages, translation keys, and translations — all stored in the database and served via API.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
### 🌐 Language Management
|
|
15
|
+
- Create and manage languages with LTR/RTL direction support
|
|
16
|
+
- Set a default language
|
|
17
|
+
- Mark languages as readonly to protect seeded entries
|
|
18
|
+
|
|
19
|
+
### 🔑 Translation Keys
|
|
20
|
+
- Organize keys by module for lazy loading (e.g. \`auth\`, \`iam\`, \`common\`)
|
|
21
|
+
- Dot-case naming convention: \`auth.login.title\`, \`common.button.save\`
|
|
22
|
+
- Support for interpolation variables: \`['count', 'name']\`
|
|
23
|
+
- Readonly protection — system keys cannot be deleted or have their key/module changed
|
|
24
|
+
|
|
25
|
+
### 📝 Translations
|
|
26
|
+
- Store translated values per language per key
|
|
27
|
+
- Mark translations as verified
|
|
28
|
+
- Bulk upsert in a single transaction — create or update in one call
|
|
29
|
+
- Detect missing translations for a given language
|
|
30
|
+
- Track translation counts per key
|
|
31
|
+
|
|
32
|
+
### 🚀 Angular Lazy Loading
|
|
33
|
+
- Public endpoints return flat key-value maps: \`{ "auth.login.title": "Sign In" }\`
|
|
34
|
+
- Filter by one or multiple modules to load only what is needed
|
|
35
|
+
- No authentication required for translation retrieval endpoints
|
|
36
|
+
|
|
37
|
+
## Key Naming Convention
|
|
38
|
+
|
|
39
|
+
Translation keys use dot-case and must be unique per module:
|
|
40
|
+
|
|
41
|
+
\`\`\`
|
|
42
|
+
auth.login.title
|
|
43
|
+
auth.login.submit
|
|
44
|
+
auth.register.email.placeholder
|
|
45
|
+
common.button.save
|
|
46
|
+
common.button.cancel
|
|
47
|
+
iam.role.create.success
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
## Module-Based Lazy Loading
|
|
51
|
+
|
|
52
|
+
Keys are grouped by \`module\` so the frontend loads translations on demand:
|
|
53
|
+
|
|
54
|
+
| Module | Purpose |
|
|
55
|
+
|--------|---------|
|
|
56
|
+
| auth | Login, register, profile pages |
|
|
57
|
+
| iam | Roles, permissions management |
|
|
58
|
+
| storage | Files and folders |
|
|
59
|
+
| event-manager | Calendar events |
|
|
60
|
+
| form-builder | Dynamic form fields |
|
|
61
|
+
| notification | Notification titles and messages |
|
|
62
|
+
| email | Email template content |
|
|
63
|
+
| common | Shared labels used across modules |
|
|
64
|
+
|
|
65
|
+
## Public vs Authenticated Endpoints
|
|
66
|
+
|
|
67
|
+
**Public** (no auth required — used by Angular to load translations):
|
|
68
|
+
- POST /localization/language/get-active
|
|
69
|
+
- POST /localization/language/get-default
|
|
70
|
+
- POST /localization/translation/get-by-language
|
|
71
|
+
- POST /localization/translation/get-by-module
|
|
72
|
+
|
|
73
|
+
**Authenticated** (require JWT + permission):
|
|
74
|
+
- All create, update, delete operations
|
|
75
|
+
- get-missing, get-counts, update-many-values
|
|
76
|
+
|
|
77
|
+
## Bulk Update
|
|
78
|
+
|
|
79
|
+
POST /localization/translation/update-many-values accepts an array of translation objects. Each item is either an upsert (provide **id** to update, omit to create). The entire batch runs in a single database transaction.
|
|
80
|
+
|
|
81
|
+
## Readonly Protection
|
|
82
|
+
|
|
83
|
+
Languages and translation keys with \`isReadonly: true\` are protected at the service layer:
|
|
84
|
+
- Cannot be deleted
|
|
85
|
+
- Key and module fields cannot be modified
|
|
86
|
+
|
|
87
|
+
Use \`isReadonly\` to protect entries that are seeded at startup and should not be changed by users.
|
|
88
|
+
|
|
89
|
+
## Getting Started
|
|
90
|
+
|
|
91
|
+
1. **Authenticate**: Obtain JWT token from POST /auth/login
|
|
92
|
+
2. **Create languages**: Add languages via POST /localization/language/insert
|
|
93
|
+
3. **Seed translation keys**: Bulk insert keys via POST /localization/translation-key/insert-many
|
|
94
|
+
4. **Add translations**: Use POST /localization/translation/update-many-values for bulk upsert
|
|
95
|
+
5. **Load in Angular**: Call POST /localization/translation/get-by-language with { languageCode: "en", modules: ["auth"] }
|
|
96
|
+
`,
|
|
97
|
+
version: '1.0',
|
|
98
|
+
path: 'api/docs/localization',
|
|
99
|
+
bearerAuth: true
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -192,18 +192,3 @@ _ts_decorate([
|
|
|
192
192
|
TranslationKeyResponseDto = _ts_decorate([
|
|
193
193
|
Exclude()
|
|
194
194
|
], TranslationKeyResponseDto);
|
|
195
|
-
export class GetByModuleDto {
|
|
196
|
-
constructor(){
|
|
197
|
-
_define_property(this, "module", void 0);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
_ts_decorate([
|
|
201
|
-
IsString(),
|
|
202
|
-
MaxLength(100),
|
|
203
|
-
Transform(({ value })=>value?.trim()?.toLowerCase()),
|
|
204
|
-
ApiProperty({
|
|
205
|
-
example: 'auth',
|
|
206
|
-
description: 'Module name'
|
|
207
|
-
}),
|
|
208
|
-
_ts_metadata("design:type", String)
|
|
209
|
-
], GetByModuleDto.prototype, "module", void 0);
|
|
@@ -237,29 +237,3 @@ _ts_decorate([
|
|
|
237
237
|
}),
|
|
238
238
|
_ts_metadata("design:type", Array)
|
|
239
239
|
], GetTranslationsByLanguageDto.prototype, "modules", void 0);
|
|
240
|
-
export class GetTranslationsByModuleDto {
|
|
241
|
-
constructor(){
|
|
242
|
-
_define_property(this, "module", void 0);
|
|
243
|
-
_define_property(this, "languageCode", void 0);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
_ts_decorate([
|
|
247
|
-
IsString(),
|
|
248
|
-
MaxLength(100),
|
|
249
|
-
Transform(({ value })=>value?.trim()?.toLowerCase()),
|
|
250
|
-
ApiProperty({
|
|
251
|
-
example: 'auth',
|
|
252
|
-
description: 'Module name'
|
|
253
|
-
}),
|
|
254
|
-
_ts_metadata("design:type", String)
|
|
255
|
-
], GetTranslationsByModuleDto.prototype, "module", void 0);
|
|
256
|
-
_ts_decorate([
|
|
257
|
-
IsString(),
|
|
258
|
-
MaxLength(10),
|
|
259
|
-
Transform(({ value })=>value?.trim()?.toLowerCase()),
|
|
260
|
-
ApiProperty({
|
|
261
|
-
example: 'en',
|
|
262
|
-
description: 'Language code'
|
|
263
|
-
}),
|
|
264
|
-
_ts_metadata("design:type", String)
|
|
265
|
-
], GetTranslationsByModuleDto.prototype, "languageCode", void 0);
|
|
@@ -67,7 +67,7 @@ export class TranslationKeyService extends RequestScopedApiService {
|
|
|
67
67
|
throw new ForbiddenException({
|
|
68
68
|
message: `Cannot modify key or module of readonly translation key "${existing.key}"`,
|
|
69
69
|
messageKey: TRANSLATION_KEY_MESSAGES.READONLY_UPDATE_FORBIDDEN,
|
|
70
|
-
|
|
70
|
+
messageVariables: {
|
|
71
71
|
key: existing.key
|
|
72
72
|
}
|
|
73
73
|
});
|
|
@@ -103,7 +103,7 @@ export class TranslationKeyService extends RequestScopedApiService {
|
|
|
103
103
|
throw new ForbiddenException({
|
|
104
104
|
message: `Cannot delete readonly translation keys: ${keys}`,
|
|
105
105
|
messageKey: TRANSLATION_KEY_MESSAGES.READONLY_DELETE_FORBIDDEN,
|
|
106
|
-
|
|
106
|
+
messageVariables: {
|
|
107
107
|
keys
|
|
108
108
|
}
|
|
109
109
|
});
|
|
@@ -146,7 +146,7 @@ export class TranslationKeyService extends RequestScopedApiService {
|
|
|
146
146
|
throw new BadRequestException({
|
|
147
147
|
message: `Translation key "${key}" already exists in module "${module}"`,
|
|
148
148
|
messageKey: TRANSLATION_KEY_MESSAGES.DUPLICATE_KEY_IN_MODULE,
|
|
149
|
-
|
|
149
|
+
messageVariables: {
|
|
150
150
|
key,
|
|
151
151
|
module
|
|
152
152
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-localization",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"description": "Localization module for multi-language support",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -31,6 +31,11 @@
|
|
|
31
31
|
"import": "./fesm/controllers/index.js",
|
|
32
32
|
"require": "./cjs/controllers/index.js"
|
|
33
33
|
},
|
|
34
|
+
"./docs": {
|
|
35
|
+
"types": "./docs/index.d.ts",
|
|
36
|
+
"import": "./fesm/docs/index.js",
|
|
37
|
+
"require": "./cjs/docs/index.js"
|
|
38
|
+
},
|
|
34
39
|
"./dtos": {
|
|
35
40
|
"types": "./dtos/index.d.ts",
|
|
36
41
|
"import": "./fesm/dtos/index.js",
|
|
@@ -75,7 +80,7 @@
|
|
|
75
80
|
"express": "^4.18.0"
|
|
76
81
|
},
|
|
77
82
|
"dependencies": {
|
|
78
|
-
"@flusys/nestjs-core": "
|
|
79
|
-
"@flusys/nestjs-shared": "
|
|
83
|
+
"@flusys/nestjs-core": "5.0.0",
|
|
84
|
+
"@flusys/nestjs-shared": "5.0.0"
|
|
80
85
|
}
|
|
81
86
|
}
|