@adminforth/i18n 1.0.19 → 1.0.20
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/Changelog.md +4 -0
- package/custom/LanguageInUserMenu.vue +1 -1
- package/dist/custom/LanguageInUserMenu.vue +1 -1
- package/dist/index.js +43 -11
- package/index.ts +40 -6
- package/package.json +1 -1
package/Changelog.md
CHANGED
|
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.0.20]
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- fix automatic translations
|
|
8
12
|
|
|
9
13
|
## [1.0.14]
|
|
10
14
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div class="min-w-40">
|
|
3
3
|
<div class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black
|
|
4
4
|
hover:bg-html dark:text-darkSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextActive
|
|
5
5
|
w-full select-none "
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div>
|
|
2
|
+
<div class="min-w-40">
|
|
3
3
|
<div class="cursor-pointer flex items-center gap-1 block px-4 py-2 text-sm text-black
|
|
4
4
|
hover:bg-html dark:text-darkSidebarTextHover dark:hover:bg-darkSidebarItemHover dark:hover:text-darkSidebarTextActive
|
|
5
5
|
w-full select-none "
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,16 @@ class CachingAdapterMemory {
|
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
+
function ensureTemplateHasAllParams(template, newTemplate) {
|
|
56
|
+
// string ensureTemplateHasAllParams("a {b} c {d}", "я {b} і {d} в") // true
|
|
57
|
+
// string ensureTemplateHasAllParams("a {b} c {d}", "я і {d} в") // false
|
|
58
|
+
// string ensureTemplateHasAllParams("a {b} c {d}", "я {bb} і {d} в") // false
|
|
59
|
+
const existingParams = template.match(/{[^}]+}/g);
|
|
60
|
+
const newParams = newTemplate.match(/{[^}]+}/g);
|
|
61
|
+
const existingParamsSet = new Set(existingParams);
|
|
62
|
+
const newParamsSet = new Set(newParams);
|
|
63
|
+
return existingParamsSet.size === newParamsSet.size && [...existingParamsSet].every(p => newParamsSet.has(p));
|
|
64
|
+
}
|
|
55
65
|
class AiTranslateError extends Error {
|
|
56
66
|
constructor(message) {
|
|
57
67
|
super(message);
|
|
@@ -189,8 +199,23 @@ export default class I18N extends AdminForthPlugin {
|
|
|
189
199
|
});
|
|
190
200
|
// disable create allowedActions for translations
|
|
191
201
|
resourceConfig.options.allowedActions.create = false;
|
|
202
|
+
// add hook to validate user did not screw up with template params
|
|
203
|
+
resourceConfig.hooks.edit.beforeSave.push((_c) => __awaiter(this, [_c], void 0, function* ({ updates, oldRecord }) {
|
|
204
|
+
for (const lang of this.options.supportedLanguages) {
|
|
205
|
+
if (lang === 'en') {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (updates[this.trFieldNames[lang]]) { // if user set '', it will have '' in updates, then it is fine, we shoudl nto check it
|
|
209
|
+
if (!ensureTemplateHasAllParams(oldRecord[this.enFieldName], updates[this.trFieldNames[lang]])) {
|
|
210
|
+
return { ok: false, error: `Template params mismatch for ${updates[this.enFieldName]}. Template param names should be the same as in original string. E. g. 'Hello {name}', should be 'Hola {name}' and not 'Hola {nombre}'!` };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return { ok: true };
|
|
215
|
+
}));
|
|
192
216
|
// add hook on edit of any translation
|
|
193
|
-
resourceConfig.hooks.edit.afterSave.push((
|
|
217
|
+
resourceConfig.hooks.edit.afterSave.push((_d) => __awaiter(this, [_d], void 0, function* ({ updates, oldRecord }) {
|
|
218
|
+
console.log('🪲edit.afterSave', JSON.stringify(updates, null, 2), '-----', JSON.stringify(oldRecord, null, 2));
|
|
194
219
|
if (oldRecord) {
|
|
195
220
|
// find lang which changed
|
|
196
221
|
let langsChanged = [];
|
|
@@ -198,9 +223,6 @@ export default class I18N extends AdminForthPlugin {
|
|
|
198
223
|
if (lang === 'en') {
|
|
199
224
|
continue;
|
|
200
225
|
}
|
|
201
|
-
if (record[this.trFieldNames[lang]] !== oldRecord[this.trFieldNames[lang]]) {
|
|
202
|
-
langsChanged.push(lang);
|
|
203
|
-
}
|
|
204
226
|
}
|
|
205
227
|
// clear frontend cache for all langsChanged
|
|
206
228
|
for (const lang of langsChanged) {
|
|
@@ -212,7 +234,7 @@ export default class I18N extends AdminForthPlugin {
|
|
|
212
234
|
return { ok: true };
|
|
213
235
|
}));
|
|
214
236
|
// add hook on delete of any translation to reset cache
|
|
215
|
-
resourceConfig.hooks.delete.afterSave.push((
|
|
237
|
+
resourceConfig.hooks.delete.afterSave.push((_e) => __awaiter(this, [_e], void 0, function* ({ record }) {
|
|
216
238
|
for (const lang of this.options.supportedLanguages) {
|
|
217
239
|
this.cache.clear(`${this.resourceConfig.resourceId}:frontend:${lang}`);
|
|
218
240
|
this.cache.clear(`${this.resourceConfig.resourceId}:${record[this.options.categoryFieldName]}:${lang}:${record[this.enFieldName]}`);
|
|
@@ -226,23 +248,23 @@ export default class I18N extends AdminForthPlugin {
|
|
|
226
248
|
// form list of all langs, sorted by alphabet, without en, to get 'al|ro|uk'
|
|
227
249
|
record.fully_translated = this.fullCompleatedFieldValue === record[this.options.completedFieldName];
|
|
228
250
|
};
|
|
229
|
-
resourceConfig.hooks.list.afterDatasourceResponse.push((
|
|
251
|
+
resourceConfig.hooks.list.afterDatasourceResponse.push((_f) => __awaiter(this, [_f], void 0, function* ({ response }) {
|
|
230
252
|
response.forEach(addIncompleteField);
|
|
231
253
|
return { ok: true };
|
|
232
254
|
}));
|
|
233
|
-
resourceConfig.hooks.show.afterDatasourceResponse.push((
|
|
255
|
+
resourceConfig.hooks.show.afterDatasourceResponse.push((_g) => __awaiter(this, [_g], void 0, function* ({ response }) {
|
|
234
256
|
addIncompleteField(response.length && response[0]);
|
|
235
257
|
return { ok: true };
|
|
236
258
|
}));
|
|
237
259
|
// also add edit hook beforeSave to update completedFieldName
|
|
238
|
-
resourceConfig.hooks.edit.beforeSave.push((
|
|
260
|
+
resourceConfig.hooks.edit.beforeSave.push((_h) => __awaiter(this, [_h], void 0, function* ({ record, oldRecord }) {
|
|
239
261
|
const futureRecord = Object.assign(Object.assign({}, oldRecord), record);
|
|
240
262
|
const futureCompletedFieldValue = yield this.computeCompletedFieldValue(futureRecord);
|
|
241
263
|
record[this.options.completedFieldName] = futureCompletedFieldValue;
|
|
242
264
|
return { ok: true };
|
|
243
265
|
}));
|
|
244
266
|
// add list hook to support filtering by fully_translated virtual field
|
|
245
|
-
resourceConfig.hooks.list.beforeDatasourceRequest.push((
|
|
267
|
+
resourceConfig.hooks.list.beforeDatasourceRequest.push((_j) => __awaiter(this, [_j], void 0, function* ({ query }) {
|
|
246
268
|
if (!query.filters || query.filters.length === 0) {
|
|
247
269
|
query.filters = [];
|
|
248
270
|
}
|
|
@@ -275,12 +297,17 @@ export default class I18N extends AdminForthPlugin {
|
|
|
275
297
|
}
|
|
276
298
|
if (this.options.completeAdapter) {
|
|
277
299
|
resourceConfig.options.bulkActions.push({
|
|
300
|
+
id: 'translate_all',
|
|
278
301
|
label: 'Translate selected',
|
|
279
302
|
icon: 'flowbite:language-outline',
|
|
280
303
|
// if optional `confirm` is provided, user will be asked to confirm action
|
|
281
304
|
confirm: 'Are you sure you want to translate selected items?',
|
|
282
305
|
state: 'selected',
|
|
283
|
-
|
|
306
|
+
allowed: (_k) => __awaiter(this, [_k], void 0, function* ({ resource, adminUser, selectedIds, allowedActions }) {
|
|
307
|
+
console.log('allowedActions', JSON.stringify(allowedActions));
|
|
308
|
+
return allowedActions.edit;
|
|
309
|
+
}),
|
|
310
|
+
action: (_l) => __awaiter(this, [_l], void 0, function* ({ selectedIds, tr }) {
|
|
284
311
|
let translatedCount = 0;
|
|
285
312
|
try {
|
|
286
313
|
translatedCount = yield this.bulkTranslate({ selectedIds });
|
|
@@ -398,6 +425,11 @@ ${JSON.stringify(strings.reduce((acc, s) => {
|
|
|
398
425
|
strId: translation[this.primaryKeyFieldName],
|
|
399
426
|
};
|
|
400
427
|
}
|
|
428
|
+
// make sure LLM did not screw up with template params
|
|
429
|
+
if (translation[this.enFieldName].includes('{') && !ensureTemplateHasAllParams(translation[this.enFieldName], translatedStr)) {
|
|
430
|
+
console.warn(`LLM Screwed up with template params mismatch for "${translation[this.enFieldName]}"on language ${lang}, it returned "${translatedStr}"`);
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
401
433
|
updateStrings[translation[this.primaryKeyFieldName]].updates[this.trFieldNames[lang]] = translatedStr;
|
|
402
434
|
}
|
|
403
435
|
}
|
|
@@ -439,7 +471,7 @@ ${JSON.stringify(strings.reduce((acc, s) => {
|
|
|
439
471
|
})));
|
|
440
472
|
yield Promise.all(Object.entries(updateStrings).map((_c) => __awaiter(this, [_c], void 0, function* ([_, { updates, strId }]) {
|
|
441
473
|
// because this will translate all languages, we can set completedLangs to all languages
|
|
442
|
-
const futureCompletedFieldValue = this.
|
|
474
|
+
const futureCompletedFieldValue = yield this.computeCompletedFieldValue(updates);
|
|
443
475
|
yield this.adminforth.resource(this.resourceConfig.resourceId).update(strId, Object.assign(Object.assign({}, updates), { [this.options.completedFieldName]: futureCompletedFieldValue }));
|
|
444
476
|
})));
|
|
445
477
|
for (const lang of langsInvolved) {
|
package/index.ts
CHANGED
|
@@ -49,6 +49,17 @@ class CachingAdapterMemory implements ICachingAdapter {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function ensureTemplateHasAllParams(template, newTemplate) {
|
|
53
|
+
// string ensureTemplateHasAllParams("a {b} c {d}", "я {b} і {d} в") // true
|
|
54
|
+
// string ensureTemplateHasAllParams("a {b} c {d}", "я і {d} в") // false
|
|
55
|
+
// string ensureTemplateHasAllParams("a {b} c {d}", "я {bb} і {d} в") // false
|
|
56
|
+
const existingParams = template.match(/{[^}]+}/g);
|
|
57
|
+
const newParams = newTemplate.match(/{[^}]+}/g);
|
|
58
|
+
const existingParamsSet = new Set(existingParams);
|
|
59
|
+
const newParamsSet = new Set(newParams);
|
|
60
|
+
return existingParamsSet.size === newParamsSet.size && [...existingParamsSet].every(p => newParamsSet.has(p));
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
class AiTranslateError extends Error {
|
|
53
64
|
constructor(message: string) {
|
|
54
65
|
super(message);
|
|
@@ -216,9 +227,24 @@ export default class I18N extends AdminForthPlugin {
|
|
|
216
227
|
// disable create allowedActions for translations
|
|
217
228
|
resourceConfig.options.allowedActions.create = false;
|
|
218
229
|
|
|
230
|
+
// add hook to validate user did not screw up with template params
|
|
231
|
+
resourceConfig.hooks.edit.beforeSave.push(async ({ updates, oldRecord }: { updates: any, oldRecord?: any }): Promise<{ ok: boolean, error?: string }> => {
|
|
232
|
+
for (const lang of this.options.supportedLanguages) {
|
|
233
|
+
if (lang === 'en') {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (updates[this.trFieldNames[lang]]) { // if user set '', it will have '' in updates, then it is fine, we shoudl nto check it
|
|
237
|
+
if (!ensureTemplateHasAllParams(oldRecord[this.enFieldName], updates[this.trFieldNames[lang]])) {
|
|
238
|
+
return { ok: false, error: `Template params mismatch for ${updates[this.enFieldName]}. Template param names should be the same as in original string. E. g. 'Hello {name}', should be 'Hola {name}' and not 'Hola {nombre}'!` };
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return { ok: true };
|
|
243
|
+
});
|
|
219
244
|
|
|
220
245
|
// add hook on edit of any translation
|
|
221
|
-
resourceConfig.hooks.edit.afterSave.push(async ({
|
|
246
|
+
resourceConfig.hooks.edit.afterSave.push(async ({ updates, oldRecord }: { updates: any, oldRecord?: any }): Promise<{ ok: boolean, error?: string }> => {
|
|
247
|
+
console.log('🪲edit.afterSave', JSON.stringify(updates, null, 2),'-----', JSON.stringify(oldRecord, null, 2));
|
|
222
248
|
if (oldRecord) {
|
|
223
249
|
// find lang which changed
|
|
224
250
|
let langsChanged: LanguageCode[] = [];
|
|
@@ -226,9 +252,6 @@ export default class I18N extends AdminForthPlugin {
|
|
|
226
252
|
if (lang === 'en') {
|
|
227
253
|
continue;
|
|
228
254
|
}
|
|
229
|
-
if (record[this.trFieldNames[lang]] !== oldRecord[this.trFieldNames[lang]]) {
|
|
230
|
-
langsChanged.push(lang);
|
|
231
|
-
}
|
|
232
255
|
}
|
|
233
256
|
|
|
234
257
|
// clear frontend cache for all langsChanged
|
|
@@ -241,6 +264,7 @@ export default class I18N extends AdminForthPlugin {
|
|
|
241
264
|
}
|
|
242
265
|
// clear frontend cache for all lan
|
|
243
266
|
|
|
267
|
+
|
|
244
268
|
return { ok: true };
|
|
245
269
|
});
|
|
246
270
|
|
|
@@ -319,11 +343,16 @@ export default class I18N extends AdminForthPlugin {
|
|
|
319
343
|
if (this.options.completeAdapter) {
|
|
320
344
|
resourceConfig.options.bulkActions.push(
|
|
321
345
|
{
|
|
346
|
+
id: 'translate_all',
|
|
322
347
|
label: 'Translate selected',
|
|
323
348
|
icon: 'flowbite:language-outline',
|
|
324
349
|
// if optional `confirm` is provided, user will be asked to confirm action
|
|
325
350
|
confirm: 'Are you sure you want to translate selected items?',
|
|
326
351
|
state: 'selected',
|
|
352
|
+
allowed: async ({ resource, adminUser, selectedIds, allowedActions }) => {
|
|
353
|
+
console.log('allowedActions', JSON.stringify(allowedActions));
|
|
354
|
+
return allowedActions.edit;
|
|
355
|
+
},
|
|
327
356
|
action: async ({ selectedIds, tr }) => {
|
|
328
357
|
let translatedCount = 0;
|
|
329
358
|
try {
|
|
@@ -460,6 +489,11 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
|
|
|
460
489
|
strId: translation[this.primaryKeyFieldName],
|
|
461
490
|
};
|
|
462
491
|
}
|
|
492
|
+
// make sure LLM did not screw up with template params
|
|
493
|
+
if (translation[this.enFieldName].includes('{') && !ensureTemplateHasAllParams(translation[this.enFieldName], translatedStr)) {
|
|
494
|
+
console.warn(`LLM Screwed up with template params mismatch for "${translation[this.enFieldName]}"on language ${lang}, it returned "${translatedStr}"`);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
463
497
|
updateStrings[
|
|
464
498
|
translation[this.primaryKeyFieldName]
|
|
465
499
|
].updates[this.trFieldNames[lang]] = translatedStr;
|
|
@@ -507,7 +541,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
|
|
|
507
541
|
category: string,
|
|
508
542
|
strId: string,
|
|
509
543
|
translatedStr: string
|
|
510
|
-
|
|
544
|
+
}> = {};
|
|
511
545
|
|
|
512
546
|
|
|
513
547
|
const langsInvolved = new Set(Object.keys(needToTranslateByLang));
|
|
@@ -535,7 +569,7 @@ JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object =>
|
|
|
535
569
|
Object.entries(updateStrings).map(
|
|
536
570
|
async ([_, { updates, strId }]: [string, { updates: any, category: string, strId: string }]) => {
|
|
537
571
|
// because this will translate all languages, we can set completedLangs to all languages
|
|
538
|
-
const futureCompletedFieldValue = this.
|
|
572
|
+
const futureCompletedFieldValue = await this.computeCompletedFieldValue(updates);
|
|
539
573
|
|
|
540
574
|
await this.adminforth.resource(this.resourceConfig.resourceId).update(strId, {
|
|
541
575
|
...updates,
|