@flusys/nestjs-localization 4.1.0 → 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.
@@ -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 { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
32
+ import { Controller, Inject, Post, UseGuards } from '@nestjs/common';
34
33
  import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
35
- import { CreateTranslationKeyDto, GetByModuleDto, TranslationKeyResponseDto, UpdateTranslationKeyDto } from '../dtos';
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: 'translationKey',
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, GetTranslationsByModuleDto, TranslationResponseDto, UpdateTranslationDto } from '../dtos';
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);
@@ -61,7 +61,7 @@ _ts_decorate([
61
61
  ], TranslationKey.prototype, "description", void 0);
62
62
  _ts_decorate([
63
63
  Column({
64
- type: 'simple-array',
64
+ type: 'json',
65
65
  nullable: true
66
66
  }),
67
67
  _ts_metadata("design:type", Object)
@@ -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
- messageParams: {
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
- messageParams: {
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
- messageParams: {
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": "4.1.0",
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": "4.1.0",
79
- "@flusys/nestjs-shared": "4.1.0"
83
+ "@flusys/nestjs-core": "5.0.0",
84
+ "@flusys/nestjs-shared": "5.0.0"
80
85
  }
81
86
  }