@adminforth/i18n 1.0.18-next.1 → 1.0.18-next.10
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/dist/index.js +103 -77
- package/index.ts +140 -100
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,9 @@ import path from 'path';
|
|
|
13
13
|
import fs from 'fs-extra';
|
|
14
14
|
import chokidar from 'chokidar';
|
|
15
15
|
import { AsyncQueue } from '@sapphire/async-queue';
|
|
16
|
+
console.log = (...args) => {
|
|
17
|
+
process.stdout.write(args.join(" ") + "\n");
|
|
18
|
+
};
|
|
16
19
|
const processFrontendMessagesQueue = new AsyncQueue();
|
|
17
20
|
const SLAVIC_PLURAL_EXAMPLES = {
|
|
18
21
|
uk: 'яблук | Яблуко | Яблука | Яблук', // zero | singular | 2-4 | 5+
|
|
@@ -141,6 +144,7 @@ export default class I18N extends AdminForthPlugin {
|
|
|
141
144
|
if (!enColumn) {
|
|
142
145
|
throw new Error(`Field ${this.enFieldName} not found column to store english original string in resource ${resourceConfig.resourceId}`);
|
|
143
146
|
}
|
|
147
|
+
enColumn.editReadonly = true;
|
|
144
148
|
// if sourceFieldName defined, check it exists
|
|
145
149
|
if (this.options.sourceFieldName) {
|
|
146
150
|
if (!resourceConfig.columns.find(c => c.name === this.options.sourceFieldName)) {
|
|
@@ -282,15 +286,17 @@ export default class I18N extends AdminForthPlugin {
|
|
|
282
286
|
translatedCount = yield this.bulkTranslate({ selectedIds });
|
|
283
287
|
}
|
|
284
288
|
catch (e) {
|
|
289
|
+
process.env.HEAVY_DEBUG && console.error('🪲⛔ bulkTranslate error', e);
|
|
285
290
|
if (e instanceof AiTranslateError) {
|
|
286
|
-
process.env.HEAVY_DEBUG && console.error('🪲⛔ bulkTranslate error', e);
|
|
287
291
|
return { ok: false, error: e.message };
|
|
288
292
|
}
|
|
293
|
+
throw e;
|
|
289
294
|
}
|
|
290
|
-
process.env.HEAVY_DEBUG && console.log('🪲bulkTranslate done',
|
|
295
|
+
process.env.HEAVY_DEBUG && console.log('🪲bulkTranslate done', translatedCount);
|
|
291
296
|
this.updateUntranslatedMenuBadge();
|
|
292
297
|
return {
|
|
293
|
-
ok: true,
|
|
298
|
+
ok: true,
|
|
299
|
+
error: undefined,
|
|
294
300
|
successMessage: yield tr(`Translated {count} items`, 'backend', {
|
|
295
301
|
count: translatedCount,
|
|
296
302
|
}),
|
|
@@ -323,6 +329,84 @@ export default class I18N extends AdminForthPlugin {
|
|
|
323
329
|
});
|
|
324
330
|
});
|
|
325
331
|
}
|
|
332
|
+
translateToLang(langIsoCode_1, strings_1) {
|
|
333
|
+
return __awaiter(this, arguments, void 0, function* (langIsoCode, strings, plurals = false, translations, updateStrings = {}) {
|
|
334
|
+
const maxKeysInOneReq = 10;
|
|
335
|
+
if (strings.length === 0) {
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
if (strings.length > maxKeysInOneReq) {
|
|
339
|
+
let totalTranslated = [];
|
|
340
|
+
for (let i = 0; i < strings.length; i += maxKeysInOneReq) {
|
|
341
|
+
const slicedStrings = strings.slice(i, i + maxKeysInOneReq);
|
|
342
|
+
process.env.HEAVY_DEBUG && console.log('🪲🔪slicedStrings len', slicedStrings.length);
|
|
343
|
+
const madeKeys = yield this.translateToLang(langIsoCode, slicedStrings, plurals, translations, updateStrings);
|
|
344
|
+
totalTranslated = totalTranslated.concat(madeKeys);
|
|
345
|
+
}
|
|
346
|
+
return totalTranslated;
|
|
347
|
+
}
|
|
348
|
+
const lang = langIsoCode;
|
|
349
|
+
const langName = iso6391.getName(lang);
|
|
350
|
+
const requestSlavicPlurals = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(lang) && plurals;
|
|
351
|
+
const prompt = `
|
|
352
|
+
I need to translate strings in JSON to ${lang} (${langName}) language from English for my web app.
|
|
353
|
+
${requestSlavicPlurals ? `You should provide 4 translations (in format zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
|
|
354
|
+
Keep keys, as is, write translation into values! Here are the strings:
|
|
355
|
+
|
|
356
|
+
\`\`\`json
|
|
357
|
+
${JSON.stringify(strings.reduce((acc, s) => {
|
|
358
|
+
acc[s.en_string] = '';
|
|
359
|
+
return acc;
|
|
360
|
+
}, {}), null, 2)}
|
|
361
|
+
\`\`\`
|
|
362
|
+
`;
|
|
363
|
+
// process.env.HEAVY_DEBUG && console.log('🧠 llm prompt', prompt);
|
|
364
|
+
// call OpenAI
|
|
365
|
+
const resp = yield this.options.completeAdapter.complete(prompt, [], 300);
|
|
366
|
+
// process.env.HEAVY_DEBUG && console.log('🧠 llm resp', resp);
|
|
367
|
+
if (resp.error) {
|
|
368
|
+
throw new AiTranslateError(resp.error);
|
|
369
|
+
}
|
|
370
|
+
// parse response like
|
|
371
|
+
// Here are the translations for the strings you provided:
|
|
372
|
+
// ```json
|
|
373
|
+
// [{"live": "canlı"}, {"Table Games": "Masa Oyunları"}]
|
|
374
|
+
// ```
|
|
375
|
+
let res;
|
|
376
|
+
try {
|
|
377
|
+
res = resp.content.split("```json")[1].split("```")[0];
|
|
378
|
+
}
|
|
379
|
+
catch (e) {
|
|
380
|
+
console.error(`Error in parsing LLM resp: ${resp}\n Prompt was: ${prompt}\n Resp was: ${JSON.stringify(resp)}`);
|
|
381
|
+
return [];
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
res = JSON.parse(res);
|
|
385
|
+
}
|
|
386
|
+
catch (e) {
|
|
387
|
+
console.error(`Error in parsing LLM resp json: ${resp}\n Prompt was: ${prompt}\n Resp was: ${JSON.stringify(resp)}`);
|
|
388
|
+
return [];
|
|
389
|
+
}
|
|
390
|
+
for (const [enStr, translatedStr] of Object.entries(res)) {
|
|
391
|
+
const translationsTargeted = translations.filter(t => t[this.enFieldName] === enStr);
|
|
392
|
+
// might be several with same en_string
|
|
393
|
+
for (const translation of translationsTargeted) {
|
|
394
|
+
//translation[this.trFieldNames[lang]] = translatedStr;
|
|
395
|
+
// process.env.HEAVY_DEBUG && console.log(`🪲translated to ${lang} ${translation.en_string}, ${translatedStr}`)
|
|
396
|
+
if (!updateStrings[translation[this.primaryKeyFieldName]]) {
|
|
397
|
+
updateStrings[translation[this.primaryKeyFieldName]] = {
|
|
398
|
+
updates: {},
|
|
399
|
+
translatedStr,
|
|
400
|
+
category: translation[this.options.categoryFieldName],
|
|
401
|
+
strId: translation[this.primaryKeyFieldName],
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
updateStrings[translation[this.primaryKeyFieldName]].updates[this.trFieldNames[lang]] = translatedStr;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return Object.keys(updateStrings);
|
|
408
|
+
});
|
|
409
|
+
}
|
|
326
410
|
// returns translated count
|
|
327
411
|
bulkTranslate(_a) {
|
|
328
412
|
return __awaiter(this, arguments, void 0, function* ({ selectedIds }) {
|
|
@@ -345,96 +429,35 @@ export default class I18N extends AdminForthPlugin {
|
|
|
345
429
|
}
|
|
346
430
|
}
|
|
347
431
|
}
|
|
348
|
-
const maxKeysInOneReq = 10;
|
|
349
432
|
const updateStrings = {};
|
|
350
|
-
const translateToLang = (langIsoCode_1, strings_1, ...args_1) => __awaiter(this, [langIsoCode_1, strings_1, ...args_1], void 0, function* (langIsoCode, strings, plurals = false) {
|
|
351
|
-
if (strings.length === 0) {
|
|
352
|
-
return 0;
|
|
353
|
-
}
|
|
354
|
-
if (strings.length > maxKeysInOneReq) {
|
|
355
|
-
let totalTranslated = 0;
|
|
356
|
-
for (let i = 0; i < strings.length; i += maxKeysInOneReq) {
|
|
357
|
-
const slicedStrings = strings.slice(i, i + maxKeysInOneReq);
|
|
358
|
-
process.env.HEAVY_DEBUG && console.log('🪲🔪slicedStrings len', slicedStrings.length);
|
|
359
|
-
totalTranslated += yield translateToLang(langIsoCode, slicedStrings, plurals);
|
|
360
|
-
}
|
|
361
|
-
return totalTranslated;
|
|
362
|
-
}
|
|
363
|
-
const lang = langIsoCode;
|
|
364
|
-
const requestSlavicPlurals = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(lang) && plurals;
|
|
365
|
-
const prompt = `
|
|
366
|
-
I need to translate strings in JSON to ${lang} language from English for my web app.
|
|
367
|
-
${requestSlavicPlurals ? `You should provide 4 translations (in format zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
|
|
368
|
-
Keep keys, as is, write translation into values! Here are the strings:
|
|
369
|
-
|
|
370
|
-
\`\`\`json
|
|
371
|
-
${JSON.stringify(strings.reduce((acc, s) => {
|
|
372
|
-
acc[s.en_string] = '';
|
|
373
|
-
return acc;
|
|
374
|
-
}, {}), null, 2)}
|
|
375
|
-
\`\`\`
|
|
376
|
-
`;
|
|
377
|
-
process.env.HEAVY_DEBUG && console.log('🧠 llm prompt', prompt);
|
|
378
|
-
// call OpenAI
|
|
379
|
-
const resp = yield this.options.completeAdapter.complete(prompt, [], 300);
|
|
380
|
-
process.env.HEAVY_DEBUG && console.log('🧠 llm resp', resp);
|
|
381
|
-
if (resp.error) {
|
|
382
|
-
throw new AiTranslateError(resp.error);
|
|
383
|
-
}
|
|
384
|
-
// parse response like
|
|
385
|
-
// Here are the translations for the strings you provided:
|
|
386
|
-
// ```json
|
|
387
|
-
// [{"live": "canlı"}, {"Table Games": "Masa Oyunları"}]
|
|
388
|
-
// ```
|
|
389
|
-
let res;
|
|
390
|
-
try {
|
|
391
|
-
res = resp.content.split("```json")[1].split("```")[0];
|
|
392
|
-
}
|
|
393
|
-
catch (e) {
|
|
394
|
-
console.error('error in parsing OpenAI', resp);
|
|
395
|
-
throw new AiTranslateError('Error in parsing OpenAI response');
|
|
396
|
-
}
|
|
397
|
-
res = JSON.parse(res);
|
|
398
|
-
for (const [enStr, translatedStr] of Object.entries(res)) {
|
|
399
|
-
const translationsTargeted = translations.filter(t => t[this.enFieldName] === enStr);
|
|
400
|
-
// might be several with same en_string
|
|
401
|
-
for (const translation of translationsTargeted) {
|
|
402
|
-
//translation[this.trFieldNames[lang]] = translatedStr;
|
|
403
|
-
// process.env.HEAVY_DEBUG && console.log(`🪲translated to ${lang} ${translation.en_string}, ${translatedStr}`)
|
|
404
|
-
if (!updateStrings[translation[this.primaryKeyFieldName]]) {
|
|
405
|
-
updateStrings[translation[this.primaryKeyFieldName]] = {
|
|
406
|
-
updates: {},
|
|
407
|
-
translatedStr,
|
|
408
|
-
category: translation[this.options.categoryFieldName],
|
|
409
|
-
strId: translation[this.primaryKeyFieldName],
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
updateStrings[translation[this.primaryKeyFieldName]].updates[this.trFieldNames[lang]] = translatedStr;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return Object.keys(updateStrings).length;
|
|
416
|
-
});
|
|
417
433
|
const langsInvolved = new Set(Object.keys(needToTranslateByLang));
|
|
418
|
-
let totalTranslated =
|
|
434
|
+
let totalTranslated = [];
|
|
435
|
+
process.env.HEAVY_DEBUG && console.log(' 🐛starting Promise.all Object.entries(needToTranslateByLang)');
|
|
419
436
|
yield Promise.all(Object.entries(needToTranslateByLang).map((_b) => __awaiter(this, [_b], void 0, function* ([lang, strings]) {
|
|
420
437
|
// first translate without plurals
|
|
421
438
|
const stringsWithoutPlurals = strings.filter(s => !s.en_string.includes('|'));
|
|
422
|
-
|
|
439
|
+
process.env.HEAVY_DEBUG && console.log(`🔗 ${lang} noplurals started ${stringsWithoutPlurals.length}`);
|
|
440
|
+
const noPluralKeys = yield this.translateToLang(lang, stringsWithoutPlurals, false, translations, updateStrings);
|
|
441
|
+
process.env.HEAVY_DEBUG && console.log(`🔗 ${lang} noplurals finished`);
|
|
423
442
|
const stringsWithPlurals = strings.filter(s => s.en_string.includes('|'));
|
|
424
|
-
|
|
443
|
+
process.env.HEAVY_DEBUG && console.log(`🔗 ${lang} plurals started ${stringsWithPlurals.length}`);
|
|
444
|
+
const pluralKeys = yield this.translateToLang(lang, stringsWithPlurals, true, translations, updateStrings);
|
|
445
|
+
process.env.HEAVY_DEBUG && console.log(`🔗 ${lang} plurals finished`);
|
|
446
|
+
totalTranslated = totalTranslated.concat(noPluralKeys, pluralKeys);
|
|
425
447
|
})));
|
|
448
|
+
process.env.HEAVY_DEBUG && console.log('✅ updateStrings were formed', (new Set(totalTranslated)));
|
|
426
449
|
yield Promise.all(Object.entries(updateStrings).map((_c) => __awaiter(this, [_c], void 0, function* ([_, { updates, strId }]) {
|
|
427
450
|
// because this will translate all languages, we can set completedLangs to all languages
|
|
428
451
|
const futureCompletedFieldValue = this.fullCompleatedFieldValue;
|
|
429
452
|
yield this.adminforth.resource(this.resourceConfig.resourceId).update(strId, Object.assign(Object.assign({}, updates), { [this.options.completedFieldName]: futureCompletedFieldValue }));
|
|
430
453
|
})));
|
|
431
454
|
for (const lang of langsInvolved) {
|
|
432
|
-
this.cache.clear(`${this.resourceConfig.resourceId}:frontend:${lang}`);
|
|
455
|
+
yield this.cache.clear(`${this.resourceConfig.resourceId}:frontend:${lang}`);
|
|
433
456
|
for (const [enStr, { category }] of Object.entries(updateStrings)) {
|
|
434
|
-
this.cache.clear(`${this.resourceConfig.resourceId}:${category}:${lang}:${enStr}`);
|
|
457
|
+
yield this.cache.clear(`${this.resourceConfig.resourceId}:${category}:${lang}:${enStr}`);
|
|
435
458
|
}
|
|
436
459
|
}
|
|
437
|
-
return totalTranslated;
|
|
460
|
+
return new Set(totalTranslated).size;
|
|
438
461
|
});
|
|
439
462
|
}
|
|
440
463
|
processExtractedMessages(adminforth, filePath) {
|
|
@@ -506,6 +529,9 @@ ${JSON.stringify(strings.reduce((acc, s) => {
|
|
|
506
529
|
validateConfigAfterDiscover(adminforth, resourceConfig) {
|
|
507
530
|
// optional method where you can safely check field types after database discovery was performed
|
|
508
531
|
// ensure each trFieldName (apart from enFieldName) is nullable column of type string
|
|
532
|
+
if (this.options.completeAdapter) {
|
|
533
|
+
this.options.completeAdapter.validate();
|
|
534
|
+
}
|
|
509
535
|
for (const lang of this.options.supportedLanguages) {
|
|
510
536
|
if (lang === 'en') {
|
|
511
537
|
continue;
|
package/index.ts
CHANGED
|
@@ -7,6 +7,11 @@ import fs from 'fs-extra';
|
|
|
7
7
|
import chokidar from 'chokidar';
|
|
8
8
|
import { AsyncQueue } from '@sapphire/async-queue';
|
|
9
9
|
|
|
10
|
+
|
|
11
|
+
console.log = (...args) => {
|
|
12
|
+
process.stdout.write(args.join(" ") + "\n");
|
|
13
|
+
};
|
|
14
|
+
|
|
10
15
|
const processFrontendMessagesQueue = new AsyncQueue();
|
|
11
16
|
|
|
12
17
|
const SLAVIC_PLURAL_EXAMPLES = {
|
|
@@ -156,6 +161,8 @@ export default class I18N extends AdminForthPlugin {
|
|
|
156
161
|
throw new Error(`Field ${this.enFieldName} not found column to store english original string in resource ${resourceConfig.resourceId}`);
|
|
157
162
|
}
|
|
158
163
|
|
|
164
|
+
enColumn.editReadonly = true;
|
|
165
|
+
|
|
159
166
|
// if sourceFieldName defined, check it exists
|
|
160
167
|
if (this.options.sourceFieldName) {
|
|
161
168
|
if (!resourceConfig.columns.find(c => c.name === this.options.sourceFieldName)) {
|
|
@@ -322,15 +329,17 @@ export default class I18N extends AdminForthPlugin {
|
|
|
322
329
|
try {
|
|
323
330
|
translatedCount = await this.bulkTranslate({ selectedIds });
|
|
324
331
|
} catch (e) {
|
|
332
|
+
process.env.HEAVY_DEBUG && console.error('🪲⛔ bulkTranslate error', e);
|
|
325
333
|
if (e instanceof AiTranslateError) {
|
|
326
|
-
process.env.HEAVY_DEBUG && console.error('🪲⛔ bulkTranslate error', e);
|
|
327
334
|
return { ok: false, error: e.message };
|
|
328
|
-
}
|
|
335
|
+
}
|
|
336
|
+
throw e;
|
|
329
337
|
}
|
|
330
|
-
process.env.HEAVY_DEBUG && console.log('🪲bulkTranslate done',
|
|
338
|
+
process.env.HEAVY_DEBUG && console.log('🪲bulkTranslate done', translatedCount);
|
|
331
339
|
this.updateUntranslatedMenuBadge();
|
|
332
340
|
return {
|
|
333
|
-
ok: true,
|
|
341
|
+
ok: true,
|
|
342
|
+
error: undefined,
|
|
334
343
|
successMessage: await tr(`Translated {count} items`, 'backend', {
|
|
335
344
|
count: translatedCount,
|
|
336
345
|
}),
|
|
@@ -364,6 +373,107 @@ export default class I18N extends AdminForthPlugin {
|
|
|
364
373
|
});
|
|
365
374
|
}
|
|
366
375
|
|
|
376
|
+
async translateToLang (
|
|
377
|
+
langIsoCode: LanguageCode,
|
|
378
|
+
strings: { en_string: string, category: string }[],
|
|
379
|
+
plurals=false,
|
|
380
|
+
translations: any[],
|
|
381
|
+
updateStrings: Record<string, { updates: any, category: string, strId: string, translatedStr: string }> = {}
|
|
382
|
+
): Promise<string[]> {
|
|
383
|
+
const maxKeysInOneReq = 10;
|
|
384
|
+
if (strings.length === 0) {
|
|
385
|
+
return [];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (strings.length > maxKeysInOneReq) {
|
|
389
|
+
let totalTranslated = [];
|
|
390
|
+
for (let i = 0; i < strings.length; i += maxKeysInOneReq) {
|
|
391
|
+
const slicedStrings = strings.slice(i, i + maxKeysInOneReq);
|
|
392
|
+
process.env.HEAVY_DEBUG && console.log('🪲🔪slicedStrings len', slicedStrings.length);
|
|
393
|
+
const madeKeys = await this.translateToLang(langIsoCode, slicedStrings, plurals, translations, updateStrings);
|
|
394
|
+
totalTranslated = totalTranslated.concat(madeKeys);
|
|
395
|
+
}
|
|
396
|
+
return totalTranslated;
|
|
397
|
+
}
|
|
398
|
+
const lang = langIsoCode;
|
|
399
|
+
const langName = iso6391.getName(lang);
|
|
400
|
+
const requestSlavicPlurals = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(lang) && plurals;
|
|
401
|
+
|
|
402
|
+
const prompt = `
|
|
403
|
+
I need to translate strings in JSON to ${lang} (${langName}) language from English for my web app.
|
|
404
|
+
${requestSlavicPlurals ? `You should provide 4 translations (in format zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
|
|
405
|
+
Keep keys, as is, write translation into values! Here are the strings:
|
|
406
|
+
|
|
407
|
+
\`\`\`json
|
|
408
|
+
${
|
|
409
|
+
JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object => {
|
|
410
|
+
acc[s.en_string] = '';
|
|
411
|
+
return acc;
|
|
412
|
+
}, {}), null, 2)
|
|
413
|
+
}
|
|
414
|
+
\`\`\`
|
|
415
|
+
`;
|
|
416
|
+
|
|
417
|
+
// process.env.HEAVY_DEBUG && console.log('🧠 llm prompt', prompt);
|
|
418
|
+
|
|
419
|
+
// call OpenAI
|
|
420
|
+
const resp = await this.options.completeAdapter.complete(
|
|
421
|
+
prompt,
|
|
422
|
+
[],
|
|
423
|
+
300,
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
// process.env.HEAVY_DEBUG && console.log('🧠 llm resp', resp);
|
|
427
|
+
|
|
428
|
+
if (resp.error) {
|
|
429
|
+
throw new AiTranslateError(resp.error);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// parse response like
|
|
433
|
+
// Here are the translations for the strings you provided:
|
|
434
|
+
// ```json
|
|
435
|
+
// [{"live": "canlı"}, {"Table Games": "Masa Oyunları"}]
|
|
436
|
+
// ```
|
|
437
|
+
let res;
|
|
438
|
+
try {
|
|
439
|
+
res = resp.content.split("```json")[1].split("```")[0];
|
|
440
|
+
} catch (e) {
|
|
441
|
+
console.error(`Error in parsing LLM resp: ${resp}\n Prompt was: ${prompt}\n Resp was: ${JSON.stringify(resp)}`, );
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
res = JSON.parse(res);
|
|
447
|
+
} catch (e) {
|
|
448
|
+
console.error(`Error in parsing LLM resp json: ${resp}\n Prompt was: ${prompt}\n Resp was: ${JSON.stringify(resp)}`, );
|
|
449
|
+
return [];
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
for (const [enStr, translatedStr] of Object.entries(res) as [string, string][]) {
|
|
454
|
+
const translationsTargeted = translations.filter(t => t[this.enFieldName] === enStr);
|
|
455
|
+
// might be several with same en_string
|
|
456
|
+
for (const translation of translationsTargeted) {
|
|
457
|
+
//translation[this.trFieldNames[lang]] = translatedStr;
|
|
458
|
+
// process.env.HEAVY_DEBUG && console.log(`🪲translated to ${lang} ${translation.en_string}, ${translatedStr}`)
|
|
459
|
+
if (!updateStrings[translation[this.primaryKeyFieldName]]) {
|
|
460
|
+
|
|
461
|
+
updateStrings[translation[this.primaryKeyFieldName]] = {
|
|
462
|
+
updates: {},
|
|
463
|
+
translatedStr,
|
|
464
|
+
category: translation[this.options.categoryFieldName],
|
|
465
|
+
strId: translation[this.primaryKeyFieldName],
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
updateStrings[
|
|
469
|
+
translation[this.primaryKeyFieldName]
|
|
470
|
+
].updates[this.trFieldNames[lang]] = translatedStr;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return Object.keys(updateStrings);
|
|
475
|
+
}
|
|
476
|
+
|
|
367
477
|
// returns translated count
|
|
368
478
|
async bulkTranslate({ selectedIds }: { selectedIds: string[] }): Promise<number> {
|
|
369
479
|
|
|
@@ -397,8 +507,6 @@ export default class I18N extends AdminForthPlugin {
|
|
|
397
507
|
}
|
|
398
508
|
}
|
|
399
509
|
|
|
400
|
-
const maxKeysInOneReq = 10;
|
|
401
|
-
|
|
402
510
|
const updateStrings: Record<string, {
|
|
403
511
|
updates: any,
|
|
404
512
|
category: string,
|
|
@@ -406,104 +514,34 @@ export default class I18N extends AdminForthPlugin {
|
|
|
406
514
|
translatedStr: string
|
|
407
515
|
}> = {};
|
|
408
516
|
|
|
409
|
-
const translateToLang = async (langIsoCode: LanguageCode, strings: { en_string: string, category: string }[], plurals=false): Promise<number> => {
|
|
410
|
-
if (strings.length === 0) {
|
|
411
|
-
return 0;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
if (strings.length > maxKeysInOneReq) {
|
|
415
|
-
let totalTranslated = 0;
|
|
416
|
-
for (let i = 0; i < strings.length; i += maxKeysInOneReq) {
|
|
417
|
-
const slicedStrings = strings.slice(i, i + maxKeysInOneReq);
|
|
418
|
-
process.env.HEAVY_DEBUG && console.log('🪲🔪slicedStrings len', slicedStrings.length);
|
|
419
|
-
totalTranslated += await translateToLang(langIsoCode, slicedStrings, plurals);
|
|
420
|
-
}
|
|
421
|
-
return totalTranslated;
|
|
422
|
-
}
|
|
423
|
-
const lang = langIsoCode;
|
|
424
|
-
|
|
425
|
-
const requestSlavicPlurals = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(lang) && plurals;
|
|
426
|
-
|
|
427
|
-
const prompt = `
|
|
428
|
-
I need to translate strings in JSON to ${lang} language from English for my web app.
|
|
429
|
-
${requestSlavicPlurals ? `You should provide 4 translations (in format zero | singular | 2-4 | 5+) e.g. ${SLAVIC_PLURAL_EXAMPLES[lang]}` : ''}
|
|
430
|
-
Keep keys, as is, write translation into values! Here are the strings:
|
|
431
|
-
|
|
432
|
-
\`\`\`json
|
|
433
|
-
${
|
|
434
|
-
JSON.stringify(strings.reduce((acc: object, s: { en_string: string }): object => {
|
|
435
|
-
acc[s.en_string] = '';
|
|
436
|
-
return acc;
|
|
437
|
-
}, {}), null, 2)
|
|
438
|
-
}
|
|
439
|
-
\`\`\`
|
|
440
|
-
`;
|
|
441
|
-
|
|
442
|
-
process.env.HEAVY_DEBUG && console.log('🧠 llm prompt', prompt);
|
|
443
517
|
|
|
444
|
-
|
|
445
|
-
const resp = await this.options.completeAdapter.complete(
|
|
446
|
-
prompt,
|
|
447
|
-
[],
|
|
448
|
-
300,
|
|
449
|
-
);
|
|
518
|
+
const langsInvolved = new Set(Object.keys(needToTranslateByLang));
|
|
450
519
|
|
|
451
|
-
|
|
520
|
+
let totalTranslated = [];
|
|
521
|
+
process.env.HEAVY_DEBUG && console.log(' 🐛starting Promise.all Object.entries(needToTranslateByLang)');
|
|
452
522
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
523
|
+
await Promise.all(
|
|
524
|
+
Object.entries(needToTranslateByLang).map(
|
|
525
|
+
async ([lang, strings]: [LanguageCode, { en_string: string, category: string }[]]) => {
|
|
526
|
+
// first translate without plurals
|
|
527
|
+
const stringsWithoutPlurals = strings.filter(s => !s.en_string.includes('|'));
|
|
528
|
+
process.env.HEAVY_DEBUG && console.log(`🔗 ${lang} noplurals started ${stringsWithoutPlurals.length}`);
|
|
529
|
+
const noPluralKeys = await this.translateToLang(lang, stringsWithoutPlurals, false, translations, updateStrings);
|
|
530
|
+
process.env.HEAVY_DEBUG && console.log(`🔗 ${lang} noplurals finished`);
|
|
456
531
|
|
|
457
|
-
// parse response like
|
|
458
|
-
// Here are the translations for the strings you provided:
|
|
459
|
-
// ```json
|
|
460
|
-
// [{"live": "canlı"}, {"Table Games": "Masa Oyunları"}]
|
|
461
|
-
// ```
|
|
462
|
-
let res;
|
|
463
|
-
try {
|
|
464
|
-
res = resp.content.split("```json")[1].split("```")[0];
|
|
465
|
-
} catch (e) {
|
|
466
|
-
console.error('error in parsing OpenAI', resp);
|
|
467
|
-
throw new AiTranslateError('Error in parsing OpenAI response');
|
|
468
|
-
}
|
|
469
|
-
res = JSON.parse(res);
|
|
470
532
|
|
|
533
|
+
const stringsWithPlurals = strings.filter(s => s.en_string.includes('|'));
|
|
471
534
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
for (const translation of translationsTargeted) {
|
|
476
|
-
//translation[this.trFieldNames[lang]] = translatedStr;
|
|
477
|
-
// process.env.HEAVY_DEBUG && console.log(`🪲translated to ${lang} ${translation.en_string}, ${translatedStr}`)
|
|
478
|
-
if (!updateStrings[translation[this.primaryKeyFieldName]]) {
|
|
535
|
+
process.env.HEAVY_DEBUG && console.log(`🔗 ${lang} plurals started ${stringsWithPlurals.length}`);
|
|
536
|
+
const pluralKeys = await this.translateToLang(lang, stringsWithPlurals, true, translations, updateStrings);
|
|
537
|
+
process.env.HEAVY_DEBUG && console.log(`🔗 ${lang} plurals finished`);
|
|
479
538
|
|
|
480
|
-
|
|
481
|
-
updates: {},
|
|
482
|
-
translatedStr,
|
|
483
|
-
category: translation[this.options.categoryFieldName],
|
|
484
|
-
strId: translation[this.primaryKeyFieldName],
|
|
485
|
-
};
|
|
486
|
-
}
|
|
487
|
-
updateStrings[
|
|
488
|
-
translation[this.primaryKeyFieldName]
|
|
489
|
-
].updates[this.trFieldNames[lang]] = translatedStr;
|
|
539
|
+
totalTranslated = totalTranslated.concat(noPluralKeys, pluralKeys);
|
|
490
540
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
return Object.keys(updateStrings).length;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const langsInvolved = new Set(Object.keys(needToTranslateByLang));
|
|
497
|
-
|
|
498
|
-
let totalTranslated = 0;
|
|
499
|
-
await Promise.all(Object.entries(needToTranslateByLang).map(async ([lang, strings]: [LanguageCode, { en_string: string, category: string }[]]) => {
|
|
500
|
-
// first translate without plurals
|
|
501
|
-
const stringsWithoutPlurals = strings.filter(s => !s.en_string.includes('|'));
|
|
502
|
-
totalTranslated += await translateToLang(lang, stringsWithoutPlurals, false);
|
|
541
|
+
)
|
|
542
|
+
);
|
|
503
543
|
|
|
504
|
-
|
|
505
|
-
totalTranslated += await translateToLang(lang, stringsWithPlurals, true);
|
|
506
|
-
}));
|
|
544
|
+
process.env.HEAVY_DEBUG && console.log('✅ updateStrings were formed', (new Set(totalTranslated)));
|
|
507
545
|
|
|
508
546
|
await Promise.all(
|
|
509
547
|
Object.entries(updateStrings).map(
|
|
@@ -520,13 +558,13 @@ ${
|
|
|
520
558
|
);
|
|
521
559
|
|
|
522
560
|
for (const lang of langsInvolved) {
|
|
523
|
-
this.cache.clear(`${this.resourceConfig.resourceId}:frontend:${lang}`);
|
|
561
|
+
await this.cache.clear(`${this.resourceConfig.resourceId}:frontend:${lang}`);
|
|
524
562
|
for (const [enStr, { category }] of Object.entries(updateStrings)) {
|
|
525
|
-
this.cache.clear(`${this.resourceConfig.resourceId}:${category}:${lang}:${enStr}`);
|
|
563
|
+
await this.cache.clear(`${this.resourceConfig.resourceId}:${category}:${lang}:${enStr}`);
|
|
526
564
|
}
|
|
527
565
|
}
|
|
528
566
|
|
|
529
|
-
return totalTranslated;
|
|
567
|
+
return new Set(totalTranslated).size;
|
|
530
568
|
|
|
531
569
|
}
|
|
532
570
|
|
|
@@ -593,12 +631,10 @@ ${
|
|
|
593
631
|
});
|
|
594
632
|
w.on('change', () => {
|
|
595
633
|
process.env.HEAVY_DEBUG && console.log('🪲🔔messagesFile change', messagesFile);
|
|
596
|
-
|
|
597
634
|
this.processExtractedMessages(adminforth, messagesFile);
|
|
598
635
|
});
|
|
599
636
|
w.on('add', () => {
|
|
600
637
|
process.env.HEAVY_DEBUG && console.log('🪲🔔messagesFile add', messagesFile);
|
|
601
|
-
|
|
602
638
|
this.processExtractedMessages(adminforth, messagesFile);
|
|
603
639
|
});
|
|
604
640
|
|
|
@@ -607,6 +643,10 @@ ${
|
|
|
607
643
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
|
|
608
644
|
// optional method where you can safely check field types after database discovery was performed
|
|
609
645
|
// ensure each trFieldName (apart from enFieldName) is nullable column of type string
|
|
646
|
+
if (this.options.completeAdapter) {
|
|
647
|
+
this.options.completeAdapter.validate();
|
|
648
|
+
}
|
|
649
|
+
|
|
610
650
|
for (const lang of this.options.supportedLanguages) {
|
|
611
651
|
if (lang === 'en') {
|
|
612
652
|
continue;
|