@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 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((_c) => __awaiter(this, [_c], void 0, function* ({ record, oldRecord }) {
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((_d) => __awaiter(this, [_d], void 0, function* ({ record }) {
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((_e) => __awaiter(this, [_e], void 0, function* ({ response }) {
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((_f) => __awaiter(this, [_f], void 0, function* ({ response }) {
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((_g) => __awaiter(this, [_g], void 0, function* ({ record, oldRecord }) {
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((_h) => __awaiter(this, [_h], void 0, function* ({ query }) {
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
- action: (_j) => __awaiter(this, [_j], void 0, function* ({ selectedIds, tr }) {
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.fullCompleatedFieldValue;
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 ({ record, oldRecord }: { record: any, oldRecord?: any }): Promise<{ ok: boolean, error?: string }> => {
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.fullCompleatedFieldValue;
572
+ const futureCompletedFieldValue = await this.computeCompletedFieldValue(updates);
539
573
 
540
574
  await this.adminforth.resource(this.resourceConfig.resourceId).update(strId, {
541
575
  ...updates,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/i18n",
3
- "version": "1.0.19",
3
+ "version": "1.0.20",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",