@flusys/nestjs-localization 7.1.0 → 8.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/config/index.d.ts +3 -3
- package/controllers/index.d.ts +3 -3
- package/controllers/language.controller.d.ts +4 -4
- package/controllers/translation-key.controller.d.ts +3 -3
- package/controllers/translation.controller.d.ts +4 -4
- package/docs/index.d.ts +1 -1
- package/docs/localization-swagger.config.d.ts +1 -1
- package/dtos/index.d.ts +3 -3
- package/dtos/translation.dto.d.ts +2 -2
- package/entities/index.d.ts +6 -6
- package/entities/language.entity.d.ts +1 -1
- package/entities/translation-key.entity.d.ts +1 -1
- package/entities/translation.entity.d.ts +2 -2
- package/fesm/349.js +1179 -0
- package/fesm/692.js +1205 -0
- package/fesm/config/index.js +101 -3
- package/fesm/controllers/index.js +15 -3
- package/fesm/docs/index.js +126 -1
- package/fesm/dtos/index.js +680 -3
- package/fesm/entities/index.js +405 -8
- package/fesm/index.js +257 -7
- package/fesm/interfaces/index.js +18 -4
- package/fesm/modules/index.js +152 -1
- package/fesm/runtime.js +85 -0
- package/fesm/services/index.js +15 -5
- package/index.d.ts +7 -7
- package/interfaces/index.d.ts +4 -4
- package/interfaces/language.interface.d.ts +1 -1
- package/interfaces/localization-module-options.interface.d.ts +3 -3
- package/interfaces/translation-key.interface.d.ts +1 -1
- package/interfaces/translation.interface.d.ts +3 -3
- package/modules/index.d.ts +1 -1
- package/modules/localization.module.d.ts +2 -2
- package/package.json +25 -34
- package/services/index.d.ts +5 -5
- package/services/language.service.d.ts +6 -6
- package/services/localization-config.service.d.ts +3 -3
- package/services/localization-datasource.provider.d.ts +3 -3
- package/services/translation-key.service.d.ts +6 -6
- package/services/translation.service.d.ts +5 -5
- package/cjs/config/event-actions.js +0 -33
- package/cjs/config/index.js +0 -20
- package/cjs/config/localization.constants.js +0 -11
- package/cjs/config/message-keys.js +0 -41
- package/cjs/controllers/index.js +0 -20
- package/cjs/controllers/language.controller.js +0 -188
- package/cjs/controllers/language.controller.spec.js +0 -154
- package/cjs/controllers/translation-key.controller.js +0 -135
- package/cjs/controllers/translation-key.controller.spec.js +0 -70
- package/cjs/controllers/translation.controller.js +0 -195
- package/cjs/controllers/translation.controller.spec.js +0 -169
- package/cjs/docs/index.js +0 -11
- package/cjs/docs/localization-swagger.config.js +0 -111
- package/cjs/docs/localization-swagger.config.spec.js +0 -30
- package/cjs/dtos/index.js +0 -20
- package/cjs/dtos/language.dto.js +0 -213
- package/cjs/dtos/translation-key.dto.js +0 -215
- package/cjs/dtos/translation.dto.js +0 -269
- package/cjs/entities/index.js +0 -42
- package/cjs/entities/index.spec.js +0 -18
- package/cjs/entities/language.entity.js +0 -126
- package/cjs/entities/translation-key.entity.js +0 -126
- package/cjs/entities/translation.entity.js +0 -120
- package/cjs/index.js +0 -24
- package/cjs/interfaces/index.js +0 -21
- package/cjs/interfaces/language.interface.js +0 -4
- package/cjs/interfaces/localization-module-options.interface.js +0 -4
- package/cjs/interfaces/translation-key.interface.js +0 -4
- package/cjs/interfaces/translation.interface.js +0 -4
- package/cjs/modules/index.js +0 -18
- package/cjs/modules/localization.module.js +0 -116
- package/cjs/modules/localization.module.spec.js +0 -132
- package/cjs/services/index.js +0 -22
- package/cjs/services/language.service.js +0 -181
- package/cjs/services/language.service.spec.js +0 -256
- package/cjs/services/localization-config.service.js +0 -66
- package/cjs/services/localization-config.service.spec.js +0 -80
- package/cjs/services/localization-datasource.provider.js +0 -79
- package/cjs/services/localization-datasource.provider.spec.js +0 -85
- package/cjs/services/translation-key.service.js +0 -179
- package/cjs/services/translation-key.service.spec.js +0 -303
- package/cjs/services/translation.service.js +0 -228
- package/cjs/services/translation.service.spec.js +0 -530
- package/fesm/config/event-actions.js +0 -12
- package/fesm/config/localization.constants.js +0 -1
- package/fesm/config/message-keys.js +0 -20
- package/fesm/controllers/language.controller.js +0 -178
- package/fesm/controllers/language.controller.spec.js +0 -150
- package/fesm/controllers/translation-key.controller.js +0 -125
- package/fesm/controllers/translation-key.controller.spec.js +0 -66
- package/fesm/controllers/translation.controller.js +0 -185
- package/fesm/controllers/translation.controller.spec.js +0 -165
- package/fesm/docs/localization-swagger.config.js +0 -101
- package/fesm/docs/localization-swagger.config.spec.js +0 -26
- package/fesm/dtos/language.dto.js +0 -192
- package/fesm/dtos/translation-key.dto.js +0 -194
- package/fesm/dtos/translation.dto.js +0 -239
- package/fesm/entities/index.spec.js +0 -14
- package/fesm/entities/language.entity.js +0 -116
- package/fesm/entities/translation-key.entity.js +0 -116
- package/fesm/entities/translation.entity.js +0 -110
- package/fesm/interfaces/language.interface.js +0 -1
- package/fesm/interfaces/localization-module-options.interface.js +0 -1
- package/fesm/interfaces/translation-key.interface.js +0 -1
- package/fesm/interfaces/translation.interface.js +0 -1
- package/fesm/modules/localization.module.js +0 -106
- package/fesm/modules/localization.module.spec.js +0 -128
- package/fesm/services/language.service.js +0 -171
- package/fesm/services/language.service.spec.js +0 -252
- package/fesm/services/localization-config.service.js +0 -56
- package/fesm/services/localization-config.service.spec.js +0 -76
- package/fesm/services/localization-datasource.provider.js +0 -69
- package/fesm/services/localization-datasource.provider.spec.js +0 -81
- package/fesm/services/translation-key.service.js +0 -169
- package/fesm/services/translation-key.service.spec.js +0 -299
- package/fesm/services/translation.service.js +0 -218
- package/fesm/services/translation.service.spec.js +0 -526
|
@@ -1,530 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", {
|
|
3
|
-
value: true
|
|
4
|
-
});
|
|
5
|
-
const _testing = require("@nestjs/testing");
|
|
6
|
-
const _classes = require("@flusys/nestjs-shared/classes");
|
|
7
|
-
const _modules = require("@flusys/nestjs-shared/modules");
|
|
8
|
-
const _repositorymock = require("@test-utils/mocks/repository.mock");
|
|
9
|
-
const _entities = require("../entities");
|
|
10
|
-
const _translationservice = require("./translation.service");
|
|
11
|
-
const _localizationdatasourceprovider = require("./localization-datasource.provider");
|
|
12
|
-
function buildLanguage(overrides = {}) {
|
|
13
|
-
return {
|
|
14
|
-
id: 'lang-en',
|
|
15
|
-
code: 'en',
|
|
16
|
-
name: 'English',
|
|
17
|
-
nativeName: 'English',
|
|
18
|
-
direction: 'ltr',
|
|
19
|
-
isActive: true,
|
|
20
|
-
isDefault: true,
|
|
21
|
-
serial: 1,
|
|
22
|
-
isReadonly: false,
|
|
23
|
-
createdAt: new Date(),
|
|
24
|
-
updatedAt: new Date(),
|
|
25
|
-
deletedAt: null,
|
|
26
|
-
createdById: null,
|
|
27
|
-
updatedById: null,
|
|
28
|
-
deletedById: null,
|
|
29
|
-
...overrides
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
function buildTranslationKey(overrides = {}) {
|
|
33
|
-
return {
|
|
34
|
-
id: 'tkey-1',
|
|
35
|
-
module: 'auth',
|
|
36
|
-
key: 'auth.login.success',
|
|
37
|
-
defaultMessage: 'Login successful',
|
|
38
|
-
description: null,
|
|
39
|
-
variables: null,
|
|
40
|
-
isActive: true,
|
|
41
|
-
isReadonly: false,
|
|
42
|
-
serial: 1,
|
|
43
|
-
createdAt: new Date(),
|
|
44
|
-
updatedAt: new Date(),
|
|
45
|
-
deletedAt: null,
|
|
46
|
-
createdById: null,
|
|
47
|
-
updatedById: null,
|
|
48
|
-
deletedById: null,
|
|
49
|
-
...overrides
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function buildTranslation(overrides = {}) {
|
|
53
|
-
return {
|
|
54
|
-
id: 'translation-1',
|
|
55
|
-
languageId: 'lang-en',
|
|
56
|
-
translationKeyId: 'tkey-1',
|
|
57
|
-
value: 'Login successful',
|
|
58
|
-
isVerified: false,
|
|
59
|
-
isActive: true,
|
|
60
|
-
createdAt: new Date(),
|
|
61
|
-
updatedAt: new Date(),
|
|
62
|
-
deletedAt: null,
|
|
63
|
-
createdById: null,
|
|
64
|
-
updatedById: null,
|
|
65
|
-
deletedById: null,
|
|
66
|
-
...overrides
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
describe('TranslationService', ()=>{
|
|
70
|
-
const mockUtilsService = {
|
|
71
|
-
clearCache: jest.fn(),
|
|
72
|
-
getCacheKey: jest.fn().mockReturnValue('cache-key'),
|
|
73
|
-
trackCacheKey: jest.fn()
|
|
74
|
-
};
|
|
75
|
-
// Call history is shared across tests in this file; reset it so each cache assertion is exact.
|
|
76
|
-
beforeEach(()=>{
|
|
77
|
-
mockUtilsService.clearCache.mockClear();
|
|
78
|
-
});
|
|
79
|
-
let service;
|
|
80
|
-
let mockRepo;
|
|
81
|
-
// `TranslationService` uses joins/aggregate query-builder methods (innerJoin, innerJoinAndSelect,
|
|
82
|
-
// groupBy, addSelect, getRawMany) that aren't part of the shared MockQueryBuilder — extend loosely.
|
|
83
|
-
let qb;
|
|
84
|
-
let languageRepo;
|
|
85
|
-
let translationKeyRepo;
|
|
86
|
-
let fakeDataSource;
|
|
87
|
-
beforeEach(async ()=>{
|
|
88
|
-
mockRepo = (0, _repositorymock.createMockRepository)();
|
|
89
|
-
qb = mockRepo.createQueryBuilder();
|
|
90
|
-
qb.innerJoin = jest.fn().mockReturnThis();
|
|
91
|
-
qb.innerJoinAndSelect = jest.fn().mockReturnThis();
|
|
92
|
-
qb.groupBy = jest.fn().mockReturnThis();
|
|
93
|
-
qb.addSelect = jest.fn().mockReturnThis();
|
|
94
|
-
qb.getRawMany = jest.fn().mockResolvedValue([]);
|
|
95
|
-
const mockDataSourceProvider = (0, _repositorymock.createMockDataSourceProvider)(mockRepo);
|
|
96
|
-
languageRepo = {
|
|
97
|
-
findOne: jest.fn()
|
|
98
|
-
};
|
|
99
|
-
translationKeyRepo = {
|
|
100
|
-
find: jest.fn()
|
|
101
|
-
};
|
|
102
|
-
fakeDataSource = {
|
|
103
|
-
getRepository: jest.fn((entity)=>{
|
|
104
|
-
if (entity === _entities.Language) return languageRepo;
|
|
105
|
-
if (entity === _entities.TranslationKey) return translationKeyRepo;
|
|
106
|
-
return mockRepo;
|
|
107
|
-
}),
|
|
108
|
-
createQueryRunner: jest.fn()
|
|
109
|
-
};
|
|
110
|
-
mockDataSourceProvider.getDataSource.mockResolvedValue(fakeDataSource);
|
|
111
|
-
const module = await _testing.Test.createTestingModule({
|
|
112
|
-
providers: [
|
|
113
|
-
_translationservice.TranslationService,
|
|
114
|
-
{
|
|
115
|
-
provide: 'CACHE_INSTANCE',
|
|
116
|
-
useValue: {}
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
provide: _modules.UtilsService,
|
|
120
|
-
useValue: mockUtilsService
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
provide: _localizationdatasourceprovider.LocalizationDataSourceProvider,
|
|
124
|
-
useValue: mockDataSourceProvider
|
|
125
|
-
}
|
|
126
|
-
]
|
|
127
|
-
}).compile();
|
|
128
|
-
service = await module.resolve(_translationservice.TranslationService);
|
|
129
|
-
});
|
|
130
|
-
describe('getByLanguageCode', ()=>{
|
|
131
|
-
it('returns an empty translations map when the language code is unknown', async ()=>{
|
|
132
|
-
languageRepo.findOne.mockResolvedValue(null);
|
|
133
|
-
const result = await service.getByLanguageCode('xx');
|
|
134
|
-
expect(result).toEqual({});
|
|
135
|
-
expect(qb.getMany).not.toHaveBeenCalled();
|
|
136
|
-
});
|
|
137
|
-
it('builds a translations map keyed by translation key, scoped to the active language', async ()=>{
|
|
138
|
-
languageRepo.findOne.mockResolvedValue(buildLanguage());
|
|
139
|
-
const translations = [
|
|
140
|
-
{
|
|
141
|
-
...buildTranslation({
|
|
142
|
-
value: 'Login successful'
|
|
143
|
-
}),
|
|
144
|
-
translationKey: buildTranslationKey({
|
|
145
|
-
key: 'auth.login.success'
|
|
146
|
-
})
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
...buildTranslation({
|
|
150
|
-
id: 'translation-2',
|
|
151
|
-
value: 'Logout successful'
|
|
152
|
-
}),
|
|
153
|
-
translationKey: buildTranslationKey({
|
|
154
|
-
id: 'tkey-2',
|
|
155
|
-
key: 'auth.logout.success'
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
];
|
|
159
|
-
qb.getMany.mockResolvedValue(translations);
|
|
160
|
-
const result = await service.getByLanguageCode('en');
|
|
161
|
-
expect(languageRepo.findOne).toHaveBeenCalledWith({
|
|
162
|
-
where: {
|
|
163
|
-
code: 'en',
|
|
164
|
-
isActive: true,
|
|
165
|
-
deletedAt: expect.anything()
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
expect(result).toEqual({
|
|
169
|
-
'auth.login.success': 'Login successful',
|
|
170
|
-
'auth.logout.success': 'Logout successful'
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
it('scopes translations by module when modules are supplied (module-based lazy loading)', async ()=>{
|
|
174
|
-
languageRepo.findOne.mockResolvedValue(buildLanguage());
|
|
175
|
-
qb.getMany.mockResolvedValue([]);
|
|
176
|
-
await service.getByLanguageCode('en', [
|
|
177
|
-
'auth',
|
|
178
|
-
'shared'
|
|
179
|
-
]);
|
|
180
|
-
expect(qb.andWhere).toHaveBeenCalledWith('tk.module IN (:...modules)', {
|
|
181
|
-
modules: [
|
|
182
|
-
'auth',
|
|
183
|
-
'shared'
|
|
184
|
-
]
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
it('does not apply a module filter when the modules array is empty', async ()=>{
|
|
188
|
-
languageRepo.findOne.mockResolvedValue(buildLanguage());
|
|
189
|
-
qb.getMany.mockResolvedValue([]);
|
|
190
|
-
await service.getByLanguageCode('en', []);
|
|
191
|
-
expect(qb.andWhere).not.toHaveBeenCalledWith(expect.stringContaining('tk.module IN'), expect.anything());
|
|
192
|
-
});
|
|
193
|
-
it('silently drops translations whose translationKey relation failed to join (fallback: key omitted, no throw)', async ()=>{
|
|
194
|
-
languageRepo.findOne.mockResolvedValue(buildLanguage());
|
|
195
|
-
qb.getMany.mockResolvedValue([
|
|
196
|
-
{
|
|
197
|
-
...buildTranslation(),
|
|
198
|
-
translationKey: undefined
|
|
199
|
-
}
|
|
200
|
-
]);
|
|
201
|
-
const result = await service.getByLanguageCode('en');
|
|
202
|
-
expect(result).toEqual({});
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
describe('getByModuleAndLanguage', ()=>{
|
|
206
|
-
it('returns an empty map for an unknown language code', async ()=>{
|
|
207
|
-
languageRepo.findOne.mockResolvedValue(null);
|
|
208
|
-
const result = await service.getByModuleAndLanguage('auth', 'xx');
|
|
209
|
-
expect(result).toEqual({});
|
|
210
|
-
});
|
|
211
|
-
it('filters translations to the requested module only', async ()=>{
|
|
212
|
-
languageRepo.findOne.mockResolvedValue(buildLanguage());
|
|
213
|
-
qb.getMany.mockResolvedValue([
|
|
214
|
-
{
|
|
215
|
-
...buildTranslation(),
|
|
216
|
-
translationKey: buildTranslationKey({
|
|
217
|
-
key: 'auth.login.success'
|
|
218
|
-
})
|
|
219
|
-
}
|
|
220
|
-
]);
|
|
221
|
-
const result = await service.getByModuleAndLanguage('auth', 'en');
|
|
222
|
-
expect(qb.andWhere).toHaveBeenCalledWith('tk.module = :module', {
|
|
223
|
-
module: 'auth'
|
|
224
|
-
});
|
|
225
|
-
expect(result).toEqual({
|
|
226
|
-
'auth.login.success': 'Login successful'
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
describe('bulkUpdate', ()=>{
|
|
231
|
-
function buildQueryRunner() {
|
|
232
|
-
return {
|
|
233
|
-
connect: jest.fn(),
|
|
234
|
-
startTransaction: jest.fn(),
|
|
235
|
-
commitTransaction: jest.fn(),
|
|
236
|
-
rollbackTransaction: jest.fn(),
|
|
237
|
-
release: jest.fn(),
|
|
238
|
-
manager: {
|
|
239
|
-
update: jest.fn(),
|
|
240
|
-
create: jest.fn((_entity, data)=>({
|
|
241
|
-
id: 'new-translation-id',
|
|
242
|
-
...data
|
|
243
|
-
})),
|
|
244
|
-
save: jest.fn(async (entity)=>entity)
|
|
245
|
-
}
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
it('returns an empty array without opening a transaction when there are no updates', async ()=>{
|
|
249
|
-
const result = await service.bulkUpdate([]);
|
|
250
|
-
expect(result).toEqual([]);
|
|
251
|
-
expect(fakeDataSource.createQueryRunner).not.toHaveBeenCalled();
|
|
252
|
-
});
|
|
253
|
-
it('updates existing translations by id and defaults isVerified to false', async ()=>{
|
|
254
|
-
const queryRunner = buildQueryRunner();
|
|
255
|
-
fakeDataSource.createQueryRunner.mockReturnValue(queryRunner);
|
|
256
|
-
mockRepo.find.mockResolvedValue([
|
|
257
|
-
buildTranslation({
|
|
258
|
-
id: 'translation-1'
|
|
259
|
-
})
|
|
260
|
-
]);
|
|
261
|
-
const result = await service.bulkUpdate([
|
|
262
|
-
{
|
|
263
|
-
id: 'translation-1',
|
|
264
|
-
languageId: 'lang-en',
|
|
265
|
-
translationKeyId: 'tkey-1',
|
|
266
|
-
value: 'Updated'
|
|
267
|
-
}
|
|
268
|
-
]);
|
|
269
|
-
expect(queryRunner.manager.update).toHaveBeenCalledWith(_entities.Translation, {
|
|
270
|
-
id: 'translation-1'
|
|
271
|
-
}, {
|
|
272
|
-
value: 'Updated',
|
|
273
|
-
isVerified: false
|
|
274
|
-
});
|
|
275
|
-
expect(queryRunner.commitTransaction).toHaveBeenCalled();
|
|
276
|
-
expect(mockRepo.find).toHaveBeenCalledWith({
|
|
277
|
-
where: {
|
|
278
|
-
id: expect.anything()
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
expect(result).toHaveLength(1);
|
|
282
|
-
});
|
|
283
|
-
it('invalidates the translation list cache and each written entry after committing', async ()=>{
|
|
284
|
-
const queryRunner = buildQueryRunner();
|
|
285
|
-
fakeDataSource.createQueryRunner.mockReturnValue(queryRunner);
|
|
286
|
-
mockRepo.find.mockResolvedValue([
|
|
287
|
-
buildTranslation({
|
|
288
|
-
id: 'translation-1'
|
|
289
|
-
})
|
|
290
|
-
]);
|
|
291
|
-
await service.bulkUpdate([
|
|
292
|
-
{
|
|
293
|
-
id: 'translation-1',
|
|
294
|
-
languageId: 'lang-en',
|
|
295
|
-
translationKeyId: 'tkey-1',
|
|
296
|
-
value: 'Updated'
|
|
297
|
-
}
|
|
298
|
-
]);
|
|
299
|
-
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('translation', expect.anything());
|
|
300
|
-
expect(mockUtilsService.clearCache).toHaveBeenCalledWith('translation', expect.anything(), 'translation-1');
|
|
301
|
-
});
|
|
302
|
-
it('creates new translations when no id is provided', async ()=>{
|
|
303
|
-
const queryRunner = buildQueryRunner();
|
|
304
|
-
fakeDataSource.createQueryRunner.mockReturnValue(queryRunner);
|
|
305
|
-
mockRepo.find.mockResolvedValue([
|
|
306
|
-
buildTranslation({
|
|
307
|
-
id: 'new-translation-id'
|
|
308
|
-
})
|
|
309
|
-
]);
|
|
310
|
-
await service.bulkUpdate([
|
|
311
|
-
{
|
|
312
|
-
languageId: 'lang-en',
|
|
313
|
-
translationKeyId: 'tkey-1',
|
|
314
|
-
value: 'Brand new',
|
|
315
|
-
isVerified: true
|
|
316
|
-
}
|
|
317
|
-
]);
|
|
318
|
-
expect(queryRunner.manager.create).toHaveBeenCalledWith(_entities.Translation, {
|
|
319
|
-
languageId: 'lang-en',
|
|
320
|
-
translationKeyId: 'tkey-1',
|
|
321
|
-
value: 'Brand new',
|
|
322
|
-
isVerified: true,
|
|
323
|
-
isActive: true
|
|
324
|
-
});
|
|
325
|
-
expect(queryRunner.manager.save).toHaveBeenCalled();
|
|
326
|
-
expect(queryRunner.commitTransaction).toHaveBeenCalled();
|
|
327
|
-
});
|
|
328
|
-
it('rolls back the transaction and rethrows the original error when a write fails', async ()=>{
|
|
329
|
-
const queryRunner = buildQueryRunner();
|
|
330
|
-
queryRunner.manager.update.mockRejectedValue(new Error('db write failed'));
|
|
331
|
-
fakeDataSource.createQueryRunner.mockReturnValue(queryRunner);
|
|
332
|
-
await expect(service.bulkUpdate([
|
|
333
|
-
{
|
|
334
|
-
id: 'translation-1',
|
|
335
|
-
languageId: 'lang-en',
|
|
336
|
-
translationKeyId: 'tkey-1',
|
|
337
|
-
value: 'x'
|
|
338
|
-
}
|
|
339
|
-
])).rejects.toThrow('db write failed');
|
|
340
|
-
expect(queryRunner.rollbackTransaction).toHaveBeenCalled();
|
|
341
|
-
expect(queryRunner.release).toHaveBeenCalled();
|
|
342
|
-
expect(queryRunner.commitTransaction).not.toHaveBeenCalled();
|
|
343
|
-
});
|
|
344
|
-
});
|
|
345
|
-
describe('getMissingTranslations', ()=>{
|
|
346
|
-
it('returns an empty array for an unknown language code', async ()=>{
|
|
347
|
-
languageRepo.findOne.mockResolvedValue(null);
|
|
348
|
-
const result = await service.getMissingTranslations('xx');
|
|
349
|
-
expect(result).toEqual([]);
|
|
350
|
-
expect(translationKeyRepo.find).not.toHaveBeenCalled();
|
|
351
|
-
});
|
|
352
|
-
it('returns translation keys that have no translation row yet for the language', async ()=>{
|
|
353
|
-
languageRepo.findOne.mockResolvedValue(buildLanguage());
|
|
354
|
-
const allKeys = [
|
|
355
|
-
buildTranslationKey({
|
|
356
|
-
id: 'tkey-1',
|
|
357
|
-
key: 'auth.login.success'
|
|
358
|
-
}),
|
|
359
|
-
buildTranslationKey({
|
|
360
|
-
id: 'tkey-2',
|
|
361
|
-
key: 'auth.logout.success'
|
|
362
|
-
})
|
|
363
|
-
];
|
|
364
|
-
translationKeyRepo.find.mockResolvedValue(allKeys);
|
|
365
|
-
mockRepo.find.mockResolvedValue([
|
|
366
|
-
{
|
|
367
|
-
translationKeyId: 'tkey-1'
|
|
368
|
-
}
|
|
369
|
-
]);
|
|
370
|
-
const result = await service.getMissingTranslations('en');
|
|
371
|
-
expect(result).toEqual([
|
|
372
|
-
allKeys[1]
|
|
373
|
-
]);
|
|
374
|
-
});
|
|
375
|
-
it('returns an empty array when every translation key already has a translation', async ()=>{
|
|
376
|
-
languageRepo.findOne.mockResolvedValue(buildLanguage());
|
|
377
|
-
const allKeys = [
|
|
378
|
-
buildTranslationKey({
|
|
379
|
-
id: 'tkey-1'
|
|
380
|
-
})
|
|
381
|
-
];
|
|
382
|
-
translationKeyRepo.find.mockResolvedValue(allKeys);
|
|
383
|
-
mockRepo.find.mockResolvedValue([
|
|
384
|
-
{
|
|
385
|
-
translationKeyId: 'tkey-1'
|
|
386
|
-
}
|
|
387
|
-
]);
|
|
388
|
-
const result = await service.getMissingTranslations('en');
|
|
389
|
-
expect(result).toEqual([]);
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
describe('getTranslationCounts', ()=>{
|
|
393
|
-
it('returns a map of translationKeyId -> translation count', async ()=>{
|
|
394
|
-
qb.getRawMany = jest.fn().mockResolvedValue([
|
|
395
|
-
{
|
|
396
|
-
keyId: 'tkey-1',
|
|
397
|
-
count: '3'
|
|
398
|
-
},
|
|
399
|
-
{
|
|
400
|
-
keyId: 'tkey-2',
|
|
401
|
-
count: '1'
|
|
402
|
-
}
|
|
403
|
-
]);
|
|
404
|
-
const result = await service.getTranslationCounts();
|
|
405
|
-
expect(result).toEqual({
|
|
406
|
-
'tkey-1': 3,
|
|
407
|
-
'tkey-2': 1
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
it('returns an empty object when there are no translations', async ()=>{
|
|
411
|
-
qb.getRawMany = jest.fn().mockResolvedValue([]);
|
|
412
|
-
const result = await service.getTranslationCounts();
|
|
413
|
-
expect(result).toEqual({});
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
describe('getFilterQuery (module/language/key filtering)', ()=>{
|
|
417
|
-
it('joins translation_key and filters by module when filter.module is provided', async ()=>{
|
|
418
|
-
const { query, isRaw } = await service.getFilterQuery(qb, {
|
|
419
|
-
module: 'auth'
|
|
420
|
-
}, null);
|
|
421
|
-
expect(qb.innerJoin).toHaveBeenCalledWith('translation.translationKey', 'tk');
|
|
422
|
-
expect(qb.andWhere).toHaveBeenCalledWith('tk.module = :module', {
|
|
423
|
-
module: 'auth'
|
|
424
|
-
});
|
|
425
|
-
expect(isRaw).toBe(false);
|
|
426
|
-
expect(query).toBe(qb);
|
|
427
|
-
});
|
|
428
|
-
it('filters by languageId without requiring a join', async ()=>{
|
|
429
|
-
await service.getFilterQuery(qb, {
|
|
430
|
-
languageId: 'lang-en'
|
|
431
|
-
}, null);
|
|
432
|
-
expect(qb.andWhere).toHaveBeenCalledWith('translation.languageId = :languageId', {
|
|
433
|
-
languageId: 'lang-en'
|
|
434
|
-
});
|
|
435
|
-
});
|
|
436
|
-
it('filters by translationKeyId', async ()=>{
|
|
437
|
-
await service.getFilterQuery(qb, {
|
|
438
|
-
translationKeyId: 'tkey-1'
|
|
439
|
-
}, null);
|
|
440
|
-
expect(qb.andWhere).toHaveBeenCalledWith('translation.translationKeyId = :translationKeyId', {
|
|
441
|
-
translationKeyId: 'tkey-1'
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
it('applies no filters when the filter object is empty', async ()=>{
|
|
445
|
-
await service.getFilterQuery(qb, {}, null);
|
|
446
|
-
expect(qb.innerJoin).not.toHaveBeenCalled();
|
|
447
|
-
expect(qb.andWhere).not.toHaveBeenCalled();
|
|
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
|
-
});
|
|
530
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
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
|
-
};
|
|
12
|
-
export const LOCALIZATION_EVENT_MODULE = 'localization';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const LOCALIZATION_MODULE_OPTIONS = 'LOCALIZATION_MODULE_OPTIONS';
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
// ==================== LOCALIZATION MODULE MESSAGE KEYS ====================
|
|
2
|
-
export const LANGUAGE_MESSAGES = {
|
|
3
|
-
ACTIVE_SUCCESS: 'language.active.success',
|
|
4
|
-
SET_DEFAULT_SUCCESS: 'language.set.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'
|
|
9
|
-
};
|
|
10
|
-
export const TRANSLATION_KEY_MESSAGES = {
|
|
11
|
-
MODULES_SUCCESS: 'translation.key.modules.success',
|
|
12
|
-
READONLY_DELETE_FORBIDDEN: 'translation.key.readonly.delete.forbidden',
|
|
13
|
-
READONLY_UPDATE_FORBIDDEN: 'translation.key.readonly.update.forbidden',
|
|
14
|
-
DUPLICATE_KEY_IN_MODULE: 'translation.key.duplicate.key.in.module'
|
|
15
|
-
};
|
|
16
|
-
export const TRANSLATION_MESSAGES = {
|
|
17
|
-
GET_ALL_SUCCESS: 'translation.get.all.success',
|
|
18
|
-
UPDATE_MANY_SUCCESS: 'translation.update.many.success',
|
|
19
|
-
BY_LANGUAGE_SUCCESS: 'translation.by.language.success'
|
|
20
|
-
};
|