@flusys/nestjs-localization 6.4.1 → 7.0.0-beta.1
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/cjs/config/event-actions.js +29 -0
- package/cjs/config/index.js +1 -0
- package/cjs/config/message-keys.js +4 -1
- package/cjs/dtos/language.dto.js +9 -1
- package/cjs/modules/localization.module.js +1 -0
- package/cjs/services/language.service.js +64 -0
- package/cjs/services/language.service.spec.js +75 -0
- package/cjs/services/translation-key.service.js +2 -1
- package/cjs/services/translation-key.service.spec.js +21 -0
- package/cjs/services/translation.service.js +7 -0
- package/cjs/services/translation.service.spec.js +81 -0
- package/config/event-actions.d.ts +8 -0
- package/config/index.d.ts +1 -0
- package/config/message-keys.d.ts +3 -0
- package/dtos/language.dto.d.ts +1 -0
- package/fesm/config/event-actions.js +11 -0
- package/fesm/config/index.js +1 -0
- package/fesm/config/message-keys.js +4 -1
- package/fesm/dtos/language.dto.js +9 -1
- package/fesm/modules/localization.module.js +2 -1
- package/fesm/services/language.service.js +66 -2
- package/fesm/services/language.service.spec.js +75 -0
- package/fesm/services/translation-key.service.js +2 -1
- package/fesm/services/translation-key.service.spec.js +21 -0
- package/fesm/services/translation.service.js +7 -0
- package/fesm/services/translation.service.spec.js +81 -0
- package/interfaces/localization-module-options.interface.d.ts +2 -0
- package/package.json +3 -3
- package/services/language.service.d.ts +5 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain event actions published by the localization module, alongside the CRUD
|
|
3
|
+
* lifecycle actions every ApiService emits.
|
|
4
|
+
*/ "use strict";
|
|
5
|
+
Object.defineProperty(exports, "__esModule", {
|
|
6
|
+
value: true
|
|
7
|
+
});
|
|
8
|
+
function _export(target, all) {
|
|
9
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
10
|
+
enumerable: true,
|
|
11
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
_export(exports, {
|
|
15
|
+
get LOCALIZATION_EVENT_ACTIONS () {
|
|
16
|
+
return LOCALIZATION_EVENT_ACTIONS;
|
|
17
|
+
},
|
|
18
|
+
get LOCALIZATION_EVENT_ENTITIES () {
|
|
19
|
+
return LOCALIZATION_EVENT_ENTITIES;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
const LOCALIZATION_EVENT_ACTIONS = {
|
|
23
|
+
BULK_UPDATED: 'bulk-updated'
|
|
24
|
+
};
|
|
25
|
+
const LOCALIZATION_EVENT_ENTITIES = {
|
|
26
|
+
LANGUAGE: 'language',
|
|
27
|
+
TRANSLATION_KEY: 'translationKey',
|
|
28
|
+
TRANSLATION: 'translation'
|
|
29
|
+
};
|
package/cjs/config/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
+
_export_star(require("./event-actions"), exports);
|
|
5
6
|
_export_star(require("./localization.constants"), exports);
|
|
6
7
|
_export_star(require("./message-keys"), exports);
|
|
7
8
|
function _export_star(from, to) {
|
|
@@ -23,7 +23,10 @@ _export(exports, {
|
|
|
23
23
|
const LANGUAGE_MESSAGES = {
|
|
24
24
|
ACTIVE_SUCCESS: 'language.active.success',
|
|
25
25
|
SET_DEFAULT_SUCCESS: 'language.set.default.success',
|
|
26
|
-
GET_DEFAULT_SUCCESS: 'language.get.default.success'
|
|
26
|
+
GET_DEFAULT_SUCCESS: 'language.get.default.success',
|
|
27
|
+
READONLY_UPDATE_FORBIDDEN: 'language.readonly.update.forbidden',
|
|
28
|
+
READONLY_DELETE_FORBIDDEN: 'language.readonly.delete.forbidden',
|
|
29
|
+
DEFAULT_DELETE_FORBIDDEN: 'language.default.delete.forbidden'
|
|
27
30
|
};
|
|
28
31
|
const TRANSLATION_KEY_MESSAGES = {
|
|
29
32
|
MODULES_SUCCESS: 'translation.key.modules.success',
|
package/cjs/dtos/language.dto.js
CHANGED
|
@@ -144,7 +144,7 @@ _ts_decorate([
|
|
|
144
144
|
], UpdateLanguageDto.prototype, "id", void 0);
|
|
145
145
|
let LanguageResponseDto = class LanguageResponseDto extends _dtos.IdentityResponseDto {
|
|
146
146
|
constructor(...args){
|
|
147
|
-
super(...args), _define_property(this, "code", void 0), _define_property(this, "name", void 0), _define_property(this, "nativeName", void 0), _define_property(this, "direction", void 0), _define_property(this, "isActive", void 0), _define_property(this, "isDefault", void 0), _define_property(this, "serial", void 0);
|
|
147
|
+
super(...args), _define_property(this, "code", void 0), _define_property(this, "name", void 0), _define_property(this, "nativeName", void 0), _define_property(this, "direction", void 0), _define_property(this, "isActive", void 0), _define_property(this, "isDefault", void 0), _define_property(this, "isReadonly", void 0), _define_property(this, "serial", void 0);
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
_ts_decorate([
|
|
@@ -193,6 +193,14 @@ _ts_decorate([
|
|
|
193
193
|
(0, _classtransformer.Expose)(),
|
|
194
194
|
_ts_metadata("design:type", Boolean)
|
|
195
195
|
], LanguageResponseDto.prototype, "isDefault", void 0);
|
|
196
|
+
_ts_decorate([
|
|
197
|
+
(0, _swagger.ApiProperty)({
|
|
198
|
+
example: false,
|
|
199
|
+
description: 'System language: cannot be renamed or deleted'
|
|
200
|
+
}),
|
|
201
|
+
(0, _classtransformer.Expose)(),
|
|
202
|
+
_ts_metadata("design:type", Boolean)
|
|
203
|
+
], LanguageResponseDto.prototype, "isReadonly", void 0);
|
|
196
204
|
_ts_decorate([
|
|
197
205
|
(0, _swagger.ApiPropertyOptional)({
|
|
198
206
|
example: 1
|
|
@@ -72,6 +72,7 @@ let LocalizationModule = class LocalizationModule {
|
|
|
72
72
|
useValue: options
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
|
+
providers.push((0, _modules.createModuleEventsProvider)('localization', _localizationconstants.LOCALIZATION_MODULE_OPTIONS));
|
|
75
76
|
return providers;
|
|
76
77
|
}
|
|
77
78
|
static createAsyncProviders(options) {
|
|
@@ -12,6 +12,7 @@ const _classes = require("@flusys/nestjs-shared/classes");
|
|
|
12
12
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
13
|
const _common = require("@nestjs/common");
|
|
14
14
|
const _typeorm = require("typeorm");
|
|
15
|
+
const _config = require("../config");
|
|
15
16
|
const _entities = require("../entities");
|
|
16
17
|
const _localizationdatasourceprovider = require("./localization-datasource.provider");
|
|
17
18
|
function _define_property(obj, key, value) {
|
|
@@ -42,6 +43,69 @@ function _ts_param(paramIndex, decorator) {
|
|
|
42
43
|
};
|
|
43
44
|
}
|
|
44
45
|
let LanguageService = class LanguageService extends _classes.ApiService {
|
|
46
|
+
async beforeUpdateOperation(dto, _user, _queryRunner) {
|
|
47
|
+
const dtos = Array.isArray(dto) ? dto : [
|
|
48
|
+
dto
|
|
49
|
+
];
|
|
50
|
+
const ids = dtos.map((d)=>d.id);
|
|
51
|
+
await this.ensureDataSourceRepository();
|
|
52
|
+
const existingLanguages = await this.repository.find({
|
|
53
|
+
where: {
|
|
54
|
+
id: (0, _typeorm.In)(ids),
|
|
55
|
+
deletedAt: (0, _typeorm.IsNull)()
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
for (const d of dtos){
|
|
59
|
+
const existing = existingLanguages.find((e)=>e.id === d.id);
|
|
60
|
+
if (!existing) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const changesIdentity = d.code !== undefined && d.code !== existing.code || d.name !== undefined && d.name !== existing.name || d.nativeName !== undefined && d.nativeName !== (existing.nativeName ?? undefined) || d.direction !== undefined && d.direction !== existing.direction;
|
|
64
|
+
if (existing.isReadonly && changesIdentity) {
|
|
65
|
+
throw new _common.ForbiddenException({
|
|
66
|
+
message: `Cannot modify code/name of readonly language "${existing.name}"`,
|
|
67
|
+
messageKey: _config.LANGUAGE_MESSAGES.READONLY_UPDATE_FORBIDDEN,
|
|
68
|
+
messageVariables: {
|
|
69
|
+
name: existing.name
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async beforeDeleteOperation(dto, _user, _queryRunner) {
|
|
76
|
+
const ids = Array.isArray(dto.id) ? dto.id : [
|
|
77
|
+
dto.id
|
|
78
|
+
];
|
|
79
|
+
await this.ensureDataSourceRepository();
|
|
80
|
+
const languages = await this.repository.find({
|
|
81
|
+
where: {
|
|
82
|
+
id: (0, _typeorm.In)(ids),
|
|
83
|
+
deletedAt: (0, _typeorm.IsNull)()
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const readonlyLanguages = languages.filter((l)=>l.isReadonly);
|
|
87
|
+
if (readonlyLanguages.length > 0) {
|
|
88
|
+
const names = readonlyLanguages.map((l)=>l.name).join(', ');
|
|
89
|
+
throw new _common.ForbiddenException({
|
|
90
|
+
message: `Cannot delete readonly language(s): ${names}`,
|
|
91
|
+
messageKey: _config.LANGUAGE_MESSAGES.READONLY_DELETE_FORBIDDEN,
|
|
92
|
+
messageVariables: {
|
|
93
|
+
names
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const defaultLanguages = languages.filter((l)=>l.isDefault);
|
|
98
|
+
if (defaultLanguages.length > 0) {
|
|
99
|
+
const names = defaultLanguages.map((l)=>l.name).join(', ');
|
|
100
|
+
throw new _common.ForbiddenException({
|
|
101
|
+
message: `Cannot delete the default language(s): ${names}. Set another language as default first.`,
|
|
102
|
+
messageKey: _config.LANGUAGE_MESSAGES.DEFAULT_DELETE_FORBIDDEN,
|
|
103
|
+
messageVariables: {
|
|
104
|
+
names
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
45
109
|
async getDefaultLanguage() {
|
|
46
110
|
await this.ensureDataSourceRepository();
|
|
47
111
|
return this.repository.findOne({
|
|
@@ -178,4 +178,79 @@ describe('LanguageService', ()=>{
|
|
|
178
178
|
});
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
|
+
describe('beforeUpdateOperation', ()=>{
|
|
182
|
+
it('rejects renaming the code of a readonly language', async ()=>{
|
|
183
|
+
mockRepo.find.mockResolvedValue([
|
|
184
|
+
buildLanguage({
|
|
185
|
+
isReadonly: true,
|
|
186
|
+
code: 'en'
|
|
187
|
+
})
|
|
188
|
+
]);
|
|
189
|
+
await expect(service.beforeUpdateOperation({
|
|
190
|
+
id: 'language-uuid-1',
|
|
191
|
+
code: 'fr'
|
|
192
|
+
}, null, {})).rejects.toThrow('Cannot modify code/name of readonly language');
|
|
193
|
+
});
|
|
194
|
+
it('allows updating isActive on a readonly language when identity fields are unchanged', async ()=>{
|
|
195
|
+
mockRepo.find.mockResolvedValue([
|
|
196
|
+
buildLanguage({
|
|
197
|
+
isReadonly: true,
|
|
198
|
+
code: 'en',
|
|
199
|
+
name: 'English'
|
|
200
|
+
})
|
|
201
|
+
]);
|
|
202
|
+
await expect(service.beforeUpdateOperation({
|
|
203
|
+
id: 'language-uuid-1',
|
|
204
|
+
code: 'en',
|
|
205
|
+
name: 'English',
|
|
206
|
+
isActive: false
|
|
207
|
+
}, null, {})).resolves.toBeUndefined();
|
|
208
|
+
});
|
|
209
|
+
it('allows any field change on a non-readonly language', async ()=>{
|
|
210
|
+
mockRepo.find.mockResolvedValue([
|
|
211
|
+
buildLanguage({
|
|
212
|
+
isReadonly: false,
|
|
213
|
+
code: 'en'
|
|
214
|
+
})
|
|
215
|
+
]);
|
|
216
|
+
await expect(service.beforeUpdateOperation({
|
|
217
|
+
id: 'language-uuid-1',
|
|
218
|
+
code: 'fr'
|
|
219
|
+
}, null, {})).resolves.toBeUndefined();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe('beforeDeleteOperation', ()=>{
|
|
223
|
+
it('rejects deleting a readonly language', async ()=>{
|
|
224
|
+
mockRepo.find.mockResolvedValue([
|
|
225
|
+
buildLanguage({
|
|
226
|
+
isReadonly: true
|
|
227
|
+
})
|
|
228
|
+
]);
|
|
229
|
+
await expect(service.beforeDeleteOperation({
|
|
230
|
+
id: 'language-uuid-1'
|
|
231
|
+
}, null, {})).rejects.toThrow('Cannot delete readonly language');
|
|
232
|
+
});
|
|
233
|
+
it('rejects deleting the default language', async ()=>{
|
|
234
|
+
mockRepo.find.mockResolvedValue([
|
|
235
|
+
buildLanguage({
|
|
236
|
+
isReadonly: false,
|
|
237
|
+
isDefault: true
|
|
238
|
+
})
|
|
239
|
+
]);
|
|
240
|
+
await expect(service.beforeDeleteOperation({
|
|
241
|
+
id: 'language-uuid-1'
|
|
242
|
+
}, null, {})).rejects.toThrow('Cannot delete the default language');
|
|
243
|
+
});
|
|
244
|
+
it('allows deleting a non-readonly, non-default language', async ()=>{
|
|
245
|
+
mockRepo.find.mockResolvedValue([
|
|
246
|
+
buildLanguage({
|
|
247
|
+
isReadonly: false,
|
|
248
|
+
isDefault: false
|
|
249
|
+
})
|
|
250
|
+
]);
|
|
251
|
+
await expect(service.beforeDeleteOperation({
|
|
252
|
+
id: 'language-uuid-1'
|
|
253
|
+
}, null, {})).resolves.toBeUndefined();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
181
256
|
});
|
|
@@ -67,7 +67,8 @@ let TranslationKeyService = class TranslationKeyService extends _classes.ApiServ
|
|
|
67
67
|
// Check if any readonly keys are being updated with key/module changes
|
|
68
68
|
for (const d of dtos){
|
|
69
69
|
const existing = existingKeys.find((e)=>e.id === d.id);
|
|
70
|
-
|
|
70
|
+
const changesIdentity = d.key !== undefined && d.key !== existing?.key || d.module !== undefined && d.module !== existing?.module;
|
|
71
|
+
if (existing?.isReadonly && changesIdentity) {
|
|
71
72
|
throw new _common.ForbiddenException({
|
|
72
73
|
message: `Cannot modify key or module of readonly translation key "${existing.key}"`,
|
|
73
74
|
messageKey: _config.TRANSLATION_KEY_MESSAGES.READONLY_UPDATE_FORBIDDEN,
|
|
@@ -217,6 +217,27 @@ describe('TranslationKeyService', ()=>{
|
|
|
217
217
|
defaultMessage: 'Updated readonly message'
|
|
218
218
|
}, (0, _loggedusermock.buildMockUser)())).resolves.toBeDefined();
|
|
219
219
|
});
|
|
220
|
+
it('allows resubmitting the same unchanged key/module of a readonly translation key', async ()=>{
|
|
221
|
+
// Regression test: the edit form always resends key + module even when only
|
|
222
|
+
// other fields changed, so the check must compare values, not just presence.
|
|
223
|
+
const existing = buildTranslationKey({
|
|
224
|
+
id: 'tkey-1',
|
|
225
|
+
isReadonly: true,
|
|
226
|
+
key: 'system.ready',
|
|
227
|
+
module: 'system'
|
|
228
|
+
});
|
|
229
|
+
mockRepo.find.mockResolvedValue([
|
|
230
|
+
existing
|
|
231
|
+
]);
|
|
232
|
+
mockRepo.findOne.mockResolvedValue(existing);
|
|
233
|
+
queryRunner.manager.save.mockResolvedValue(existing);
|
|
234
|
+
await expect(service.update({
|
|
235
|
+
id: 'tkey-1',
|
|
236
|
+
key: 'system.ready',
|
|
237
|
+
module: 'system',
|
|
238
|
+
defaultMessage: 'Updated readonly message'
|
|
239
|
+
}, (0, _loggedusermock.buildMockUser)())).resolves.toBeDefined();
|
|
240
|
+
});
|
|
220
241
|
it('rejects with BadRequestException when the new key/module already exists on another row', async ()=>{
|
|
221
242
|
const existing = buildTranslationKey({
|
|
222
243
|
id: 'tkey-1',
|
|
@@ -12,6 +12,7 @@ const _classes = require("@flusys/nestjs-shared/classes");
|
|
|
12
12
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
13
13
|
const _common = require("@nestjs/common");
|
|
14
14
|
const _typeorm = require("typeorm");
|
|
15
|
+
const _config = require("../config");
|
|
15
16
|
const _entities = require("../entities");
|
|
16
17
|
const _localizationdatasourceprovider = require("./localization-datasource.provider");
|
|
17
18
|
function _define_property(obj, key, value) {
|
|
@@ -141,6 +142,12 @@ let TranslationService = class TranslationService extends _classes.ApiService {
|
|
|
141
142
|
this.clearCacheForAll(),
|
|
142
143
|
this.clearCacheForId(saved)
|
|
143
144
|
]);
|
|
145
|
+
await this.publishDomainAction(_config.LOCALIZATION_EVENT_ACTIONS.BULK_UPDATED, {
|
|
146
|
+
ids: saved.map((translation)=>translation.id),
|
|
147
|
+
metadata: {
|
|
148
|
+
count: saved.length
|
|
149
|
+
}
|
|
150
|
+
});
|
|
144
151
|
return saved;
|
|
145
152
|
} catch (error) {
|
|
146
153
|
await queryRunner.rollbackTransaction();
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
5
|
const _testing = require("@nestjs/testing");
|
|
6
|
+
const _classes = require("@flusys/nestjs-shared/classes");
|
|
6
7
|
const _modules = require("@flusys/nestjs-shared/modules");
|
|
7
8
|
const _repositorymock = require("@test-utils/mocks/repository.mock");
|
|
8
9
|
const _entities = require("../entities");
|
|
@@ -446,4 +447,84 @@ describe('TranslationService', ()=>{
|
|
|
446
447
|
expect(qb.andWhere).not.toHaveBeenCalled();
|
|
447
448
|
});
|
|
448
449
|
});
|
|
450
|
+
describe('domain events', ()=>{
|
|
451
|
+
let publish;
|
|
452
|
+
function buildEventQueryRunner() {
|
|
453
|
+
return {
|
|
454
|
+
connect: jest.fn(),
|
|
455
|
+
startTransaction: jest.fn(),
|
|
456
|
+
commitTransaction: jest.fn(),
|
|
457
|
+
rollbackTransaction: jest.fn(),
|
|
458
|
+
release: jest.fn(),
|
|
459
|
+
manager: {
|
|
460
|
+
update: jest.fn(),
|
|
461
|
+
create: jest.fn((_entity, data)=>({
|
|
462
|
+
id: 'new-translation-id',
|
|
463
|
+
...data
|
|
464
|
+
})),
|
|
465
|
+
save: jest.fn(async (entity)=>entity)
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
beforeEach(()=>{
|
|
470
|
+
publish = jest.fn().mockResolvedValue(undefined);
|
|
471
|
+
_classes.EventBusRegistry.reset();
|
|
472
|
+
_classes.EventBusRegistry.setBus({
|
|
473
|
+
publish,
|
|
474
|
+
subscribe: jest.fn(),
|
|
475
|
+
close: jest.fn()
|
|
476
|
+
});
|
|
477
|
+
_classes.EventBusRegistry.configureModule('localization', {
|
|
478
|
+
enabled: true
|
|
479
|
+
});
|
|
480
|
+
fakeDataSource.createQueryRunner.mockReturnValue(buildEventQueryRunner());
|
|
481
|
+
mockRepo.find.mockResolvedValue([
|
|
482
|
+
buildTranslation({
|
|
483
|
+
id: 'translation-1'
|
|
484
|
+
})
|
|
485
|
+
]);
|
|
486
|
+
});
|
|
487
|
+
afterEach(()=>{
|
|
488
|
+
_classes.EventBusRegistry.reset();
|
|
489
|
+
});
|
|
490
|
+
it('publishes localization.translation.bulk-updated with the written count', async ()=>{
|
|
491
|
+
await service.bulkUpdate([
|
|
492
|
+
{
|
|
493
|
+
id: 'translation-1',
|
|
494
|
+
languageId: 'lang-en',
|
|
495
|
+
translationKeyId: 'tkey-1',
|
|
496
|
+
value: 'x'
|
|
497
|
+
}
|
|
498
|
+
]);
|
|
499
|
+
expect(publish).toHaveBeenCalledWith(expect.objectContaining({
|
|
500
|
+
name: 'localization.translation.bulk-updated',
|
|
501
|
+
payload: expect.objectContaining({
|
|
502
|
+
ids: [
|
|
503
|
+
'translation-1'
|
|
504
|
+
],
|
|
505
|
+
metadata: {
|
|
506
|
+
count: 1
|
|
507
|
+
}
|
|
508
|
+
})
|
|
509
|
+
}));
|
|
510
|
+
});
|
|
511
|
+
it('does not publish when the localization module has events disabled', async ()=>{
|
|
512
|
+
_classes.EventBusRegistry.configureModule('localization', {
|
|
513
|
+
enabled: false
|
|
514
|
+
});
|
|
515
|
+
await service.bulkUpdate([
|
|
516
|
+
{
|
|
517
|
+
id: 'translation-1',
|
|
518
|
+
languageId: 'lang-en',
|
|
519
|
+
translationKeyId: 'tkey-1',
|
|
520
|
+
value: 'x'
|
|
521
|
+
}
|
|
522
|
+
]);
|
|
523
|
+
expect(publish).not.toHaveBeenCalled();
|
|
524
|
+
});
|
|
525
|
+
it('publishes nothing when there is nothing to update', async ()=>{
|
|
526
|
+
await service.bulkUpdate([]);
|
|
527
|
+
expect(publish).not.toHaveBeenCalled();
|
|
528
|
+
});
|
|
529
|
+
});
|
|
449
530
|
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const LOCALIZATION_EVENT_ACTIONS: {
|
|
2
|
+
readonly BULK_UPDATED: "bulk-updated";
|
|
3
|
+
};
|
|
4
|
+
export declare const LOCALIZATION_EVENT_ENTITIES: {
|
|
5
|
+
readonly LANGUAGE: "language";
|
|
6
|
+
readonly TRANSLATION_KEY: "translationKey";
|
|
7
|
+
readonly TRANSLATION: "translation";
|
|
8
|
+
};
|
package/config/index.d.ts
CHANGED
package/config/message-keys.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ export declare const LANGUAGE_MESSAGES: {
|
|
|
2
2
|
readonly ACTIVE_SUCCESS: "language.active.success";
|
|
3
3
|
readonly SET_DEFAULT_SUCCESS: "language.set.default.success";
|
|
4
4
|
readonly GET_DEFAULT_SUCCESS: "language.get.default.success";
|
|
5
|
+
readonly READONLY_UPDATE_FORBIDDEN: "language.readonly.update.forbidden";
|
|
6
|
+
readonly READONLY_DELETE_FORBIDDEN: "language.readonly.delete.forbidden";
|
|
7
|
+
readonly DEFAULT_DELETE_FORBIDDEN: "language.default.delete.forbidden";
|
|
5
8
|
};
|
|
6
9
|
export declare const TRANSLATION_KEY_MESSAGES: {
|
|
7
10
|
readonly MODULES_SUCCESS: "translation.key.modules.success";
|
package/dtos/language.dto.d.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain event actions published by the localization module, alongside the CRUD
|
|
3
|
+
* lifecycle actions every ApiService emits.
|
|
4
|
+
*/ export const LOCALIZATION_EVENT_ACTIONS = {
|
|
5
|
+
BULK_UPDATED: 'bulk-updated'
|
|
6
|
+
};
|
|
7
|
+
/** Every entity the localization module publishes under */ export const LOCALIZATION_EVENT_ENTITIES = {
|
|
8
|
+
LANGUAGE: 'language',
|
|
9
|
+
TRANSLATION_KEY: 'translationKey',
|
|
10
|
+
TRANSLATION: 'translation'
|
|
11
|
+
};
|
package/fesm/config/index.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
export const LANGUAGE_MESSAGES = {
|
|
3
3
|
ACTIVE_SUCCESS: 'language.active.success',
|
|
4
4
|
SET_DEFAULT_SUCCESS: 'language.set.default.success',
|
|
5
|
-
GET_DEFAULT_SUCCESS: 'language.get.default.success'
|
|
5
|
+
GET_DEFAULT_SUCCESS: 'language.get.default.success',
|
|
6
|
+
READONLY_UPDATE_FORBIDDEN: 'language.readonly.update.forbidden',
|
|
7
|
+
READONLY_DELETE_FORBIDDEN: 'language.readonly.delete.forbidden',
|
|
8
|
+
DEFAULT_DELETE_FORBIDDEN: 'language.default.delete.forbidden'
|
|
6
9
|
};
|
|
7
10
|
export const TRANSLATION_KEY_MESSAGES = {
|
|
8
11
|
MODULES_SUCCESS: 'translation.key.modules.success',
|
|
@@ -123,7 +123,7 @@ _ts_decorate([
|
|
|
123
123
|
], UpdateLanguageDto.prototype, "id", void 0);
|
|
124
124
|
export class LanguageResponseDto extends IdentityResponseDto {
|
|
125
125
|
constructor(...args){
|
|
126
|
-
super(...args), _define_property(this, "code", void 0), _define_property(this, "name", void 0), _define_property(this, "nativeName", void 0), _define_property(this, "direction", void 0), _define_property(this, "isActive", void 0), _define_property(this, "isDefault", void 0), _define_property(this, "serial", void 0);
|
|
126
|
+
super(...args), _define_property(this, "code", void 0), _define_property(this, "name", void 0), _define_property(this, "nativeName", void 0), _define_property(this, "direction", void 0), _define_property(this, "isActive", void 0), _define_property(this, "isDefault", void 0), _define_property(this, "isReadonly", void 0), _define_property(this, "serial", void 0);
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
_ts_decorate([
|
|
@@ -172,6 +172,14 @@ _ts_decorate([
|
|
|
172
172
|
Expose(),
|
|
173
173
|
_ts_metadata("design:type", Boolean)
|
|
174
174
|
], LanguageResponseDto.prototype, "isDefault", void 0);
|
|
175
|
+
_ts_decorate([
|
|
176
|
+
ApiProperty({
|
|
177
|
+
example: false,
|
|
178
|
+
description: 'System language: cannot be renamed or deleted'
|
|
179
|
+
}),
|
|
180
|
+
Expose(),
|
|
181
|
+
_ts_metadata("design:type", Boolean)
|
|
182
|
+
], LanguageResponseDto.prototype, "isReadonly", void 0);
|
|
175
183
|
_ts_decorate([
|
|
176
184
|
ApiPropertyOptional({
|
|
177
185
|
example: 1
|
|
@@ -4,7 +4,7 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
4
4
|
else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
}
|
|
7
|
-
import { CacheModule, UtilsModule } from '@flusys/nestjs-shared/modules';
|
|
7
|
+
import { CacheModule, UtilsModule, createModuleEventsProvider } from '@flusys/nestjs-shared/modules';
|
|
8
8
|
import { Module } from '@nestjs/common';
|
|
9
9
|
import { LOCALIZATION_MODULE_OPTIONS } from '../config/localization.constants';
|
|
10
10
|
import { LanguageController, TranslationKeyController, TranslationController } from '../controllers';
|
|
@@ -62,6 +62,7 @@ export class LocalizationModule {
|
|
|
62
62
|
useValue: options
|
|
63
63
|
});
|
|
64
64
|
}
|
|
65
|
+
providers.push(createModuleEventsProvider('localization', LOCALIZATION_MODULE_OPTIONS));
|
|
65
66
|
return providers;
|
|
66
67
|
}
|
|
67
68
|
static createAsyncProviders(options) {
|
|
@@ -27,11 +27,75 @@ function _ts_param(paramIndex, decorator) {
|
|
|
27
27
|
}
|
|
28
28
|
import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
|
|
29
29
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
|
-
import { Inject, Injectable, Scope } from '@nestjs/common';
|
|
31
|
-
import { IsNull } from 'typeorm';
|
|
30
|
+
import { ForbiddenException, Inject, Injectable, Scope } from '@nestjs/common';
|
|
31
|
+
import { In, IsNull } from 'typeorm';
|
|
32
|
+
import { LANGUAGE_MESSAGES } from '../config';
|
|
32
33
|
import { Language } from '../entities';
|
|
33
34
|
import { LocalizationDataSourceProvider } from './localization-datasource.provider';
|
|
34
35
|
export class LanguageService extends ApiService {
|
|
36
|
+
async beforeUpdateOperation(dto, _user, _queryRunner) {
|
|
37
|
+
const dtos = Array.isArray(dto) ? dto : [
|
|
38
|
+
dto
|
|
39
|
+
];
|
|
40
|
+
const ids = dtos.map((d)=>d.id);
|
|
41
|
+
await this.ensureDataSourceRepository();
|
|
42
|
+
const existingLanguages = await this.repository.find({
|
|
43
|
+
where: {
|
|
44
|
+
id: In(ids),
|
|
45
|
+
deletedAt: IsNull()
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
for (const d of dtos){
|
|
49
|
+
const existing = existingLanguages.find((e)=>e.id === d.id);
|
|
50
|
+
if (!existing) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const changesIdentity = d.code !== undefined && d.code !== existing.code || d.name !== undefined && d.name !== existing.name || d.nativeName !== undefined && d.nativeName !== (existing.nativeName ?? undefined) || d.direction !== undefined && d.direction !== existing.direction;
|
|
54
|
+
if (existing.isReadonly && changesIdentity) {
|
|
55
|
+
throw new ForbiddenException({
|
|
56
|
+
message: `Cannot modify code/name of readonly language "${existing.name}"`,
|
|
57
|
+
messageKey: LANGUAGE_MESSAGES.READONLY_UPDATE_FORBIDDEN,
|
|
58
|
+
messageVariables: {
|
|
59
|
+
name: existing.name
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async beforeDeleteOperation(dto, _user, _queryRunner) {
|
|
66
|
+
const ids = Array.isArray(dto.id) ? dto.id : [
|
|
67
|
+
dto.id
|
|
68
|
+
];
|
|
69
|
+
await this.ensureDataSourceRepository();
|
|
70
|
+
const languages = await this.repository.find({
|
|
71
|
+
where: {
|
|
72
|
+
id: In(ids),
|
|
73
|
+
deletedAt: IsNull()
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
const readonlyLanguages = languages.filter((l)=>l.isReadonly);
|
|
77
|
+
if (readonlyLanguages.length > 0) {
|
|
78
|
+
const names = readonlyLanguages.map((l)=>l.name).join(', ');
|
|
79
|
+
throw new ForbiddenException({
|
|
80
|
+
message: `Cannot delete readonly language(s): ${names}`,
|
|
81
|
+
messageKey: LANGUAGE_MESSAGES.READONLY_DELETE_FORBIDDEN,
|
|
82
|
+
messageVariables: {
|
|
83
|
+
names
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const defaultLanguages = languages.filter((l)=>l.isDefault);
|
|
88
|
+
if (defaultLanguages.length > 0) {
|
|
89
|
+
const names = defaultLanguages.map((l)=>l.name).join(', ');
|
|
90
|
+
throw new ForbiddenException({
|
|
91
|
+
message: `Cannot delete the default language(s): ${names}. Set another language as default first.`,
|
|
92
|
+
messageKey: LANGUAGE_MESSAGES.DEFAULT_DELETE_FORBIDDEN,
|
|
93
|
+
messageVariables: {
|
|
94
|
+
names
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
35
99
|
async getDefaultLanguage() {
|
|
36
100
|
await this.ensureDataSourceRepository();
|
|
37
101
|
return this.repository.findOne({
|
|
@@ -174,4 +174,79 @@ describe('LanguageService', ()=>{
|
|
|
174
174
|
});
|
|
175
175
|
});
|
|
176
176
|
});
|
|
177
|
+
describe('beforeUpdateOperation', ()=>{
|
|
178
|
+
it('rejects renaming the code of a readonly language', async ()=>{
|
|
179
|
+
mockRepo.find.mockResolvedValue([
|
|
180
|
+
buildLanguage({
|
|
181
|
+
isReadonly: true,
|
|
182
|
+
code: 'en'
|
|
183
|
+
})
|
|
184
|
+
]);
|
|
185
|
+
await expect(service.beforeUpdateOperation({
|
|
186
|
+
id: 'language-uuid-1',
|
|
187
|
+
code: 'fr'
|
|
188
|
+
}, null, {})).rejects.toThrow('Cannot modify code/name of readonly language');
|
|
189
|
+
});
|
|
190
|
+
it('allows updating isActive on a readonly language when identity fields are unchanged', async ()=>{
|
|
191
|
+
mockRepo.find.mockResolvedValue([
|
|
192
|
+
buildLanguage({
|
|
193
|
+
isReadonly: true,
|
|
194
|
+
code: 'en',
|
|
195
|
+
name: 'English'
|
|
196
|
+
})
|
|
197
|
+
]);
|
|
198
|
+
await expect(service.beforeUpdateOperation({
|
|
199
|
+
id: 'language-uuid-1',
|
|
200
|
+
code: 'en',
|
|
201
|
+
name: 'English',
|
|
202
|
+
isActive: false
|
|
203
|
+
}, null, {})).resolves.toBeUndefined();
|
|
204
|
+
});
|
|
205
|
+
it('allows any field change on a non-readonly language', async ()=>{
|
|
206
|
+
mockRepo.find.mockResolvedValue([
|
|
207
|
+
buildLanguage({
|
|
208
|
+
isReadonly: false,
|
|
209
|
+
code: 'en'
|
|
210
|
+
})
|
|
211
|
+
]);
|
|
212
|
+
await expect(service.beforeUpdateOperation({
|
|
213
|
+
id: 'language-uuid-1',
|
|
214
|
+
code: 'fr'
|
|
215
|
+
}, null, {})).resolves.toBeUndefined();
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe('beforeDeleteOperation', ()=>{
|
|
219
|
+
it('rejects deleting a readonly language', async ()=>{
|
|
220
|
+
mockRepo.find.mockResolvedValue([
|
|
221
|
+
buildLanguage({
|
|
222
|
+
isReadonly: true
|
|
223
|
+
})
|
|
224
|
+
]);
|
|
225
|
+
await expect(service.beforeDeleteOperation({
|
|
226
|
+
id: 'language-uuid-1'
|
|
227
|
+
}, null, {})).rejects.toThrow('Cannot delete readonly language');
|
|
228
|
+
});
|
|
229
|
+
it('rejects deleting the default language', async ()=>{
|
|
230
|
+
mockRepo.find.mockResolvedValue([
|
|
231
|
+
buildLanguage({
|
|
232
|
+
isReadonly: false,
|
|
233
|
+
isDefault: true
|
|
234
|
+
})
|
|
235
|
+
]);
|
|
236
|
+
await expect(service.beforeDeleteOperation({
|
|
237
|
+
id: 'language-uuid-1'
|
|
238
|
+
}, null, {})).rejects.toThrow('Cannot delete the default language');
|
|
239
|
+
});
|
|
240
|
+
it('allows deleting a non-readonly, non-default language', async ()=>{
|
|
241
|
+
mockRepo.find.mockResolvedValue([
|
|
242
|
+
buildLanguage({
|
|
243
|
+
isReadonly: false,
|
|
244
|
+
isDefault: false
|
|
245
|
+
})
|
|
246
|
+
]);
|
|
247
|
+
await expect(service.beforeDeleteOperation({
|
|
248
|
+
id: 'language-uuid-1'
|
|
249
|
+
}, null, {})).resolves.toBeUndefined();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
177
252
|
});
|
|
@@ -57,7 +57,8 @@ export class TranslationKeyService extends ApiService {
|
|
|
57
57
|
// Check if any readonly keys are being updated with key/module changes
|
|
58
58
|
for (const d of dtos){
|
|
59
59
|
const existing = existingKeys.find((e)=>e.id === d.id);
|
|
60
|
-
|
|
60
|
+
const changesIdentity = d.key !== undefined && d.key !== existing?.key || d.module !== undefined && d.module !== existing?.module;
|
|
61
|
+
if (existing?.isReadonly && changesIdentity) {
|
|
61
62
|
throw new ForbiddenException({
|
|
62
63
|
message: `Cannot modify key or module of readonly translation key "${existing.key}"`,
|
|
63
64
|
messageKey: TRANSLATION_KEY_MESSAGES.READONLY_UPDATE_FORBIDDEN,
|
|
@@ -213,6 +213,27 @@ describe('TranslationKeyService', ()=>{
|
|
|
213
213
|
defaultMessage: 'Updated readonly message'
|
|
214
214
|
}, buildMockUser())).resolves.toBeDefined();
|
|
215
215
|
});
|
|
216
|
+
it('allows resubmitting the same unchanged key/module of a readonly translation key', async ()=>{
|
|
217
|
+
// Regression test: the edit form always resends key + module even when only
|
|
218
|
+
// other fields changed, so the check must compare values, not just presence.
|
|
219
|
+
const existing = buildTranslationKey({
|
|
220
|
+
id: 'tkey-1',
|
|
221
|
+
isReadonly: true,
|
|
222
|
+
key: 'system.ready',
|
|
223
|
+
module: 'system'
|
|
224
|
+
});
|
|
225
|
+
mockRepo.find.mockResolvedValue([
|
|
226
|
+
existing
|
|
227
|
+
]);
|
|
228
|
+
mockRepo.findOne.mockResolvedValue(existing);
|
|
229
|
+
queryRunner.manager.save.mockResolvedValue(existing);
|
|
230
|
+
await expect(service.update({
|
|
231
|
+
id: 'tkey-1',
|
|
232
|
+
key: 'system.ready',
|
|
233
|
+
module: 'system',
|
|
234
|
+
defaultMessage: 'Updated readonly message'
|
|
235
|
+
}, buildMockUser())).resolves.toBeDefined();
|
|
236
|
+
});
|
|
216
237
|
it('rejects with BadRequestException when the new key/module already exists on another row', async ()=>{
|
|
217
238
|
const existing = buildTranslationKey({
|
|
218
239
|
id: 'tkey-1',
|
|
@@ -29,6 +29,7 @@ import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
|
|
|
29
29
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
30
30
|
import { Inject, Injectable, Scope } from '@nestjs/common';
|
|
31
31
|
import { In, IsNull } from 'typeorm';
|
|
32
|
+
import { LOCALIZATION_EVENT_ACTIONS } from '../config';
|
|
32
33
|
import { Language, Translation, TranslationKey } from '../entities';
|
|
33
34
|
import { LocalizationDataSourceProvider } from './localization-datasource.provider';
|
|
34
35
|
export class TranslationService extends ApiService {
|
|
@@ -131,6 +132,12 @@ export class TranslationService extends ApiService {
|
|
|
131
132
|
this.clearCacheForAll(),
|
|
132
133
|
this.clearCacheForId(saved)
|
|
133
134
|
]);
|
|
135
|
+
await this.publishDomainAction(LOCALIZATION_EVENT_ACTIONS.BULK_UPDATED, {
|
|
136
|
+
ids: saved.map((translation)=>translation.id),
|
|
137
|
+
metadata: {
|
|
138
|
+
count: saved.length
|
|
139
|
+
}
|
|
140
|
+
});
|
|
134
141
|
return saved;
|
|
135
142
|
} catch (error) {
|
|
136
143
|
await queryRunner.rollbackTransaction();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Test } from '@nestjs/testing';
|
|
2
|
+
import { EventBusRegistry } from '@flusys/nestjs-shared/classes';
|
|
2
3
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
3
4
|
import { createMockDataSourceProvider, createMockRepository } from '@test-utils/mocks/repository.mock';
|
|
4
5
|
import { Language, Translation, TranslationKey } from '../entities';
|
|
@@ -442,4 +443,84 @@ describe('TranslationService', ()=>{
|
|
|
442
443
|
expect(qb.andWhere).not.toHaveBeenCalled();
|
|
443
444
|
});
|
|
444
445
|
});
|
|
446
|
+
describe('domain events', ()=>{
|
|
447
|
+
let publish;
|
|
448
|
+
function buildEventQueryRunner() {
|
|
449
|
+
return {
|
|
450
|
+
connect: jest.fn(),
|
|
451
|
+
startTransaction: jest.fn(),
|
|
452
|
+
commitTransaction: jest.fn(),
|
|
453
|
+
rollbackTransaction: jest.fn(),
|
|
454
|
+
release: jest.fn(),
|
|
455
|
+
manager: {
|
|
456
|
+
update: jest.fn(),
|
|
457
|
+
create: jest.fn((_entity, data)=>({
|
|
458
|
+
id: 'new-translation-id',
|
|
459
|
+
...data
|
|
460
|
+
})),
|
|
461
|
+
save: jest.fn(async (entity)=>entity)
|
|
462
|
+
}
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
beforeEach(()=>{
|
|
466
|
+
publish = jest.fn().mockResolvedValue(undefined);
|
|
467
|
+
EventBusRegistry.reset();
|
|
468
|
+
EventBusRegistry.setBus({
|
|
469
|
+
publish,
|
|
470
|
+
subscribe: jest.fn(),
|
|
471
|
+
close: jest.fn()
|
|
472
|
+
});
|
|
473
|
+
EventBusRegistry.configureModule('localization', {
|
|
474
|
+
enabled: true
|
|
475
|
+
});
|
|
476
|
+
fakeDataSource.createQueryRunner.mockReturnValue(buildEventQueryRunner());
|
|
477
|
+
mockRepo.find.mockResolvedValue([
|
|
478
|
+
buildTranslation({
|
|
479
|
+
id: 'translation-1'
|
|
480
|
+
})
|
|
481
|
+
]);
|
|
482
|
+
});
|
|
483
|
+
afterEach(()=>{
|
|
484
|
+
EventBusRegistry.reset();
|
|
485
|
+
});
|
|
486
|
+
it('publishes localization.translation.bulk-updated with the written count', async ()=>{
|
|
487
|
+
await service.bulkUpdate([
|
|
488
|
+
{
|
|
489
|
+
id: 'translation-1',
|
|
490
|
+
languageId: 'lang-en',
|
|
491
|
+
translationKeyId: 'tkey-1',
|
|
492
|
+
value: 'x'
|
|
493
|
+
}
|
|
494
|
+
]);
|
|
495
|
+
expect(publish).toHaveBeenCalledWith(expect.objectContaining({
|
|
496
|
+
name: 'localization.translation.bulk-updated',
|
|
497
|
+
payload: expect.objectContaining({
|
|
498
|
+
ids: [
|
|
499
|
+
'translation-1'
|
|
500
|
+
],
|
|
501
|
+
metadata: {
|
|
502
|
+
count: 1
|
|
503
|
+
}
|
|
504
|
+
})
|
|
505
|
+
}));
|
|
506
|
+
});
|
|
507
|
+
it('does not publish when the localization module has events disabled', async ()=>{
|
|
508
|
+
EventBusRegistry.configureModule('localization', {
|
|
509
|
+
enabled: false
|
|
510
|
+
});
|
|
511
|
+
await service.bulkUpdate([
|
|
512
|
+
{
|
|
513
|
+
id: 'translation-1',
|
|
514
|
+
languageId: 'lang-en',
|
|
515
|
+
translationKeyId: 'tkey-1',
|
|
516
|
+
value: 'x'
|
|
517
|
+
}
|
|
518
|
+
]);
|
|
519
|
+
expect(publish).not.toHaveBeenCalled();
|
|
520
|
+
});
|
|
521
|
+
it('publishes nothing when there is nothing to update', async ()=>{
|
|
522
|
+
await service.bulkUpdate([]);
|
|
523
|
+
expect(publish).not.toHaveBeenCalled();
|
|
524
|
+
});
|
|
525
|
+
});
|
|
445
526
|
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { IBootstrapAppConfig, IDataSourceServiceOptions, IDynamicModuleConfig, IModuleOptionsFactory } from '@flusys/nestjs-core';
|
|
2
|
+
import { IModuleEventsConfig } from '@flusys/nestjs-shared/interfaces';
|
|
2
3
|
import { ModuleMetadata, Type } from '@nestjs/common';
|
|
3
4
|
export interface ILocalizationModuleConfig extends IDataSourceServiceOptions {
|
|
4
5
|
defaultLanguageCode?: string;
|
|
5
6
|
enableCaching?: boolean;
|
|
6
7
|
cacheTtlSeconds?: number;
|
|
8
|
+
events?: IModuleEventsConfig;
|
|
7
9
|
}
|
|
8
10
|
export interface LocalizationModuleOptions extends IDynamicModuleConfig {
|
|
9
11
|
bootstrapAppConfig?: IBootstrapAppConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flusys/nestjs-localization",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.0.0-beta.1",
|
|
4
4
|
"description": "Localization module for multi-language support",
|
|
5
5
|
"main": "cjs/index.js",
|
|
6
6
|
"module": "fesm/index.js",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"express": "^5.0.0"
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
|
-
"@flusys/nestjs-core": "
|
|
84
|
-
"@flusys/nestjs-shared": "
|
|
83
|
+
"@flusys/nestjs-core": "7.0.0-beta.1",
|
|
84
|
+
"@flusys/nestjs-shared": "7.0.0-beta.1"
|
|
85
85
|
}
|
|
86
86
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { HybridCache, ApiService } from '@flusys/nestjs-shared/classes';
|
|
2
|
+
import { DeleteDto } from '@flusys/nestjs-shared/dtos';
|
|
3
|
+
import { ILoggedUserInfo } from '@flusys/nestjs-shared/interfaces';
|
|
2
4
|
import { UtilsService } from '@flusys/nestjs-shared/modules';
|
|
5
|
+
import { QueryRunner } from 'typeorm';
|
|
3
6
|
import { CreateLanguageDto, UpdateLanguageDto } from '../dtos';
|
|
4
7
|
import { Language } from '../entities';
|
|
5
8
|
import { ILanguage } from '../interfaces';
|
|
@@ -8,6 +11,8 @@ export declare class LanguageService extends ApiService<CreateLanguageDto, Updat
|
|
|
8
11
|
protected cacheManager: HybridCache;
|
|
9
12
|
protected utilsService: UtilsService;
|
|
10
13
|
constructor(cacheManager: HybridCache, utilsService: UtilsService, dataSourceProvider: LocalizationDataSourceProvider);
|
|
14
|
+
protected beforeUpdateOperation(dto: UpdateLanguageDto | UpdateLanguageDto[], _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
|
|
15
|
+
protected beforeDeleteOperation(dto: DeleteDto, _user: ILoggedUserInfo | null, _queryRunner: QueryRunner): Promise<void>;
|
|
11
16
|
getDefaultLanguage(): Promise<Language | null>;
|
|
12
17
|
getActiveLanguages(): Promise<Language[]>;
|
|
13
18
|
setAsDefault(id: string): Promise<Language | null>;
|