@adminforth/i18n 1.0.9 → 1.0.11

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 ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [v1.0.10]
9
+
10
+ ## Fixed
11
+
12
+ - fix automatic translations for duplicate strings
13
+ - improve slavik pluralization generations by splitting the requests
@@ -54,6 +54,7 @@ const countryISO31661ByLangISO6391 = {
54
54
  ko: 'kr', // Korean → South Korea
55
55
  ja: 'jp', // Japanese → Japan
56
56
  uk: 'ua', // Ukrainian → Ukraine
57
+ ur: 'pk', // Urdu → Pakistan
57
58
  };
58
59
 
59
60
  export function getCountryCodeFromLangCode(langCode) {
@@ -54,6 +54,7 @@ const countryISO31661ByLangISO6391 = {
54
54
  ko: 'kr', // Korean → South Korea
55
55
  ja: 'jp', // Japanese → Japan
56
56
  uk: 'ua', // Ukrainian → Ukraine
57
+ ur: 'pk', // Urdu → Pakistan
57
58
  };
58
59
 
59
60
  export function getCountryCodeFromLangCode(langCode) {
package/dist/index.js CHANGED
@@ -205,29 +205,38 @@ export default class I18N extends AdminForthPlugin {
205
205
  // clear frontend cache for all lan
206
206
  return { ok: true };
207
207
  }));
208
+ // add hook on delete of any translation to reset cache
209
+ resourceConfig.hooks.delete.afterSave.push((_d) => __awaiter(this, [_d], void 0, function* ({ record }) {
210
+ for (const lang of this.options.supportedLanguages) {
211
+ this.cache.clear(`${this.resourceConfig.resourceId}:frontend:${lang}`);
212
+ this.cache.clear(`${this.resourceConfig.resourceId}:${record[this.options.categoryFieldName]}:${lang}:${record[this.enFieldName]}`);
213
+ }
214
+ this.updateUntranslatedMenuBadge();
215
+ return { ok: true };
216
+ }));
208
217
  if (this.options.completedFieldName) {
209
218
  // on show and list add a list hook which will add incomplete field to record if translation is missing for at least one language
210
219
  const addIncompleteField = (record) => {
211
220
  // form list of all langs, sorted by alphabet, without en, to get 'al|ro|uk'
212
221
  record.fully_translated = this.fullCompleatedFieldValue === record[this.options.completedFieldName];
213
222
  };
214
- resourceConfig.hooks.list.afterDatasourceResponse.push((_d) => __awaiter(this, [_d], void 0, function* ({ response }) {
223
+ resourceConfig.hooks.list.afterDatasourceResponse.push((_e) => __awaiter(this, [_e], void 0, function* ({ response }) {
215
224
  response.forEach(addIncompleteField);
216
225
  return { ok: true };
217
226
  }));
218
- resourceConfig.hooks.show.afterDatasourceResponse.push((_e) => __awaiter(this, [_e], void 0, function* ({ response }) {
227
+ resourceConfig.hooks.show.afterDatasourceResponse.push((_f) => __awaiter(this, [_f], void 0, function* ({ response }) {
219
228
  addIncompleteField(response.length && response[0]);
220
229
  return { ok: true };
221
230
  }));
222
231
  // also add edit hook beforeSave to update completedFieldName
223
- resourceConfig.hooks.edit.beforeSave.push((_f) => __awaiter(this, [_f], void 0, function* ({ record, oldRecord }) {
232
+ resourceConfig.hooks.edit.beforeSave.push((_g) => __awaiter(this, [_g], void 0, function* ({ record, oldRecord }) {
224
233
  const futureRecord = Object.assign(Object.assign({}, oldRecord), record);
225
234
  const futureCompletedFieldValue = yield this.computeCompletedFieldValue(futureRecord);
226
235
  record[this.options.completedFieldName] = futureCompletedFieldValue;
227
236
  return { ok: true };
228
237
  }));
229
238
  // add list hook to support filtering by fully_translated virtual field
230
- resourceConfig.hooks.list.beforeDatasourceRequest.push((_g) => __awaiter(this, [_g], void 0, function* ({ query }) {
239
+ resourceConfig.hooks.list.beforeDatasourceRequest.push((_h) => __awaiter(this, [_h], void 0, function* ({ query }) {
231
240
  if (!query.filters || query.filters.length === 0) {
232
241
  query.filters = [];
233
242
  }
@@ -265,7 +274,7 @@ export default class I18N extends AdminForthPlugin {
265
274
  // if optional `confirm` is provided, user will be asked to confirm action
266
275
  confirm: 'Are you sure you want to translate selected items?',
267
276
  state: 'selected',
268
- action: (_h) => __awaiter(this, [_h], void 0, function* ({ selectedIds, tr }) {
277
+ action: (_j) => __awaiter(this, [_j], void 0, function* ({ selectedIds, tr }) {
269
278
  try {
270
279
  yield this.bulkTranslate({ selectedIds });
271
280
  }
@@ -308,6 +317,7 @@ export default class I18N extends AdminForthPlugin {
308
317
  });
309
318
  });
310
319
  }
320
+ // returns translated count
311
321
  bulkTranslate(_a) {
312
322
  return __awaiter(this, arguments, void 0, function* ({ selectedIds }) {
313
323
  const needToTranslateByLang = {};
@@ -331,19 +341,24 @@ export default class I18N extends AdminForthPlugin {
331
341
  }
332
342
  const maxKeysInOneReq = 10;
333
343
  const updateStrings = {};
334
- const translateToLang = (langIsoCode, strings) => __awaiter(this, void 0, void 0, function* () {
344
+ const translateToLang = (langIsoCode_1, strings_1, ...args_1) => __awaiter(this, [langIsoCode_1, strings_1, ...args_1], void 0, function* (langIsoCode, strings, plurals = false) {
345
+ if (strings.length === 0) {
346
+ return 0;
347
+ }
335
348
  if (strings.length > maxKeysInOneReq) {
349
+ let totalTranslated = 0;
336
350
  for (let i = 0; i < strings.length; i += maxKeysInOneReq) {
337
351
  const slicedStrings = strings.slice(i, i + maxKeysInOneReq);
338
- yield translateToLang(langIsoCode, slicedStrings);
352
+ console.log('🪲🔪slicedStrings ', slicedStrings);
353
+ totalTranslated += yield translateToLang(langIsoCode, slicedStrings, plurals);
339
354
  }
340
- return;
355
+ return totalTranslated;
341
356
  }
342
357
  const lang = langIsoCode;
343
- const isSlavikPlural = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(lang);
358
+ const requestSlavicPlurals = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(lang) && plurals;
344
359
  const prompt = `
345
360
  I need to translate strings in JSON to ${lang} language from English for my web app.
346
- ${isSlavikPlural ? `If string contains '|' it means it is plural form, you should provide 4 translations (zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
361
+ ${requestSlavicPlurals ? `You should provide 4 translations (in format zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
347
362
  Keep keys, as is, write translation into values! Here are the strings:
348
363
 
349
364
  \`\`\`json
@@ -378,22 +393,29 @@ ${JSON.stringify(strings.reduce((acc, s) => {
378
393
  const translationsTargeted = translations.filter(t => t[this.enFieldName] === enStr);
379
394
  // might be several with same en_string
380
395
  for (const translation of translationsTargeted) {
381
- translation[this.trFieldNames[lang]] = translatedStr;
396
+ //translation[this.trFieldNames[lang]] = translatedStr;
382
397
  // process.env.HEAVY_DEBUG && console.log(`🪲translated to ${lang} ${translation.en_string}, ${translatedStr}`)
383
- if (!updateStrings[enStr]) {
384
- updateStrings[enStr] = {
398
+ if (!updateStrings[translation[this.primaryKeyFieldName]]) {
399
+ updateStrings[translation[this.primaryKeyFieldName]] = {
385
400
  updates: {},
401
+ translatedStr,
386
402
  category: translation[this.options.categoryFieldName],
387
403
  strId: translation[this.primaryKeyFieldName],
388
404
  };
389
405
  }
390
- updateStrings[enStr].updates[this.trFieldNames[lang]] = translatedStr;
406
+ updateStrings[translation[this.primaryKeyFieldName]].updates[this.trFieldNames[lang]] = translatedStr;
391
407
  }
392
408
  }
409
+ return res.length;
393
410
  });
394
411
  const langsInvolved = new Set(Object.keys(needToTranslateByLang));
412
+ let totalTranslated = 0;
395
413
  yield Promise.all(Object.entries(needToTranslateByLang).map((_b) => __awaiter(this, [_b], void 0, function* ([lang, strings]) {
396
- yield translateToLang(lang, strings);
414
+ // first translate without plurals
415
+ const stringsWithoutPlurals = strings.filter(s => !s.en_string.includes('|'));
416
+ totalTranslated += yield translateToLang(lang, stringsWithoutPlurals, false);
417
+ const stringsWithPlurals = strings.filter(s => s.en_string.includes('|'));
418
+ totalTranslated += yield translateToLang(lang, stringsWithPlurals, true);
397
419
  })));
398
420
  yield Promise.all(Object.entries(updateStrings).map((_c) => __awaiter(this, [_c], void 0, function* ([_, { updates, strId }]) {
399
421
  // because this will translate all languages, we can set completedLangs to all languages
@@ -406,6 +428,7 @@ ${JSON.stringify(strings.reduce((acc, s) => {
406
428
  this.cache.clear(`${this.resourceConfig.resourceId}:${category}:${lang}:${enStr}`);
407
429
  }
408
430
  }
431
+ return totalTranslated;
409
432
  });
410
433
  }
411
434
  processExtractedMessages(adminforth, filePath) {
package/index.ts CHANGED
@@ -234,6 +234,16 @@ export default class I18N extends AdminForthPlugin {
234
234
  return { ok: true };
235
235
  });
236
236
 
237
+ // add hook on delete of any translation to reset cache
238
+ resourceConfig.hooks.delete.afterSave.push(async ({ record }: { record: any }): Promise<{ ok: boolean, error?: string }> => {
239
+ for (const lang of this.options.supportedLanguages) {
240
+ this.cache.clear(`${this.resourceConfig.resourceId}:frontend:${lang}`);
241
+ this.cache.clear(`${this.resourceConfig.resourceId}:${record[this.options.categoryFieldName]}:${lang}:${record[this.enFieldName]}`);
242
+ }
243
+ this.updateUntranslatedMenuBadge();
244
+ return { ok: true };
245
+ });
246
+
237
247
  if (this.options.completedFieldName) {
238
248
  // on show and list add a list hook which will add incomplete field to record if translation is missing for at least one language
239
249
  const addIncompleteField = (record: any) => {
@@ -347,7 +357,8 @@ export default class I18N extends AdminForthPlugin {
347
357
  });
348
358
  }
349
359
 
350
- async bulkTranslate({ selectedIds }: { selectedIds: string[] }) {
360
+ // returns translated count
361
+ async bulkTranslate({ selectedIds }: { selectedIds: string[] }): Promise<number> {
351
362
 
352
363
  const needToTranslateByLang : Partial<
353
364
  Record<
@@ -382,26 +393,33 @@ export default class I18N extends AdminForthPlugin {
382
393
  const maxKeysInOneReq = 10;
383
394
 
384
395
  const updateStrings: Record<string, {
385
- updates: any, category: string, strId: string
396
+ updates: any,
397
+ category: string,
398
+ strId: string,
399
+ translatedStr: string
386
400
  }> = {};
387
401
 
388
- const translateToLang = async (langIsoCode: LanguageCode, strings: { en_string: string, category: string }[]) => {
389
-
402
+ const translateToLang = async (langIsoCode: LanguageCode, strings: { en_string: string, category: string }[], plurals=false): Promise<number> => {
403
+ if (strings.length === 0) {
404
+ return 0;
405
+ }
390
406
 
391
407
  if (strings.length > maxKeysInOneReq) {
408
+ let totalTranslated = 0;
392
409
  for (let i = 0; i < strings.length; i += maxKeysInOneReq) {
393
410
  const slicedStrings = strings.slice(i, i + maxKeysInOneReq);
394
- await translateToLang(langIsoCode, slicedStrings);
411
+ console.log('🪲🔪slicedStrings ', slicedStrings);
412
+ totalTranslated += await translateToLang(langIsoCode, slicedStrings, plurals);
395
413
  }
396
- return;
414
+ return totalTranslated;
397
415
  }
398
416
  const lang = langIsoCode;
399
417
 
400
- const isSlavikPlural = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(lang);
418
+ const requestSlavicPlurals = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(lang) && plurals;
401
419
 
402
420
  const prompt = `
403
421
  I need to translate strings in JSON to ${lang} language from English for my web app.
404
- ${isSlavikPlural ? `If string contains '|' it means it is plural form, you should provide 4 translations (zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
422
+ ${requestSlavicPlurals ? `You should provide 4 translations (in format zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
405
423
  Keep keys, as is, write translation into values! Here are the strings:
406
424
 
407
425
  \`\`\`json
@@ -442,29 +460,42 @@ ${
442
460
  return;
443
461
  }
444
462
  res = JSON.parse(res);
445
- for (const [enStr, translatedStr] of Object.entries(res)) {
463
+
464
+
465
+ for (const [enStr, translatedStr] of Object.entries(res) as [string, string][]) {
446
466
  const translationsTargeted = translations.filter(t => t[this.enFieldName] === enStr);
447
467
  // might be several with same en_string
448
468
  for (const translation of translationsTargeted) {
449
- translation[this.trFieldNames[lang]] = translatedStr;
469
+ //translation[this.trFieldNames[lang]] = translatedStr;
450
470
  // process.env.HEAVY_DEBUG && console.log(`🪲translated to ${lang} ${translation.en_string}, ${translatedStr}`)
451
- if (!updateStrings[enStr]) {
452
- updateStrings[enStr] = {
471
+ if (!updateStrings[translation[this.primaryKeyFieldName]]) {
472
+
473
+ updateStrings[translation[this.primaryKeyFieldName]] = {
453
474
  updates: {},
475
+ translatedStr,
454
476
  category: translation[this.options.categoryFieldName],
455
477
  strId: translation[this.primaryKeyFieldName],
456
478
  };
457
479
  }
458
- updateStrings[enStr].updates[this.trFieldNames[lang]] = translatedStr;
480
+ updateStrings[
481
+ translation[this.primaryKeyFieldName]
482
+ ].updates[this.trFieldNames[lang]] = translatedStr;
459
483
  }
460
484
  }
461
485
 
486
+ return res.length;
462
487
  }
463
488
 
464
489
  const langsInvolved = new Set(Object.keys(needToTranslateByLang));
465
490
 
491
+ let totalTranslated = 0;
466
492
  await Promise.all(Object.entries(needToTranslateByLang).map(async ([lang, strings]: [LanguageCode, { en_string: string, category: string }[]]) => {
467
- await translateToLang(lang, strings);
493
+ // first translate without plurals
494
+ const stringsWithoutPlurals = strings.filter(s => !s.en_string.includes('|'));
495
+ totalTranslated += await translateToLang(lang, stringsWithoutPlurals, false);
496
+
497
+ const stringsWithPlurals = strings.filter(s => s.en_string.includes('|'));
498
+ totalTranslated += await translateToLang(lang, stringsWithPlurals, true);
468
499
  }));
469
500
 
470
501
  await Promise.all(
@@ -488,6 +519,8 @@ ${
488
519
  }
489
520
  }
490
521
 
522
+ return totalTranslated;
523
+
491
524
  }
492
525
 
493
526
  async processExtractedMessages(adminforth: IAdminForth, filePath: string) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/i18n",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",