@adminforth/i18n 2.3.2 → 2.5.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/base.ts +402 -0
- package/build.log +2 -2
- package/custom/BulkActionButton.vue +2 -2
- package/custom/LanguageInUserMenu.vue +4 -12
- package/custom/LanguageUnderLogin.vue +4 -12
- package/custom/TranslationJobViewComponent.vue +2 -3
- package/custom/langCommon.ts +12 -24
- package/dist/base.d.ts +59 -0
- package/dist/base.js +363 -0
- package/dist/custom/BulkActionButton.vue +2 -2
- package/dist/custom/LanguageInUserMenu.vue +4 -12
- package/dist/custom/LanguageUnderLogin.vue +4 -12
- package/dist/custom/TranslationJobViewComponent.vue +2 -3
- package/dist/custom/langCommon.ts +12 -24
- package/dist/index.d.ts +9 -17
- package/dist/index.js +58 -263
- package/dist/types.d.ts +17 -12
- package/index.ts +67 -294
- package/package.json +1 -1
- package/types.ts +20 -14
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
7
7
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
|
-
import {
|
|
10
|
+
import { Filters, suggestIfTypo, AdminForthDataTypes, RAMLock, filtersTools, AdminForthFilterOperators } from "adminforth";
|
|
11
|
+
import { I18nBase, getCountryCodeFromLangCode, getPrimaryLanguageCode, requiresSlavicPlurals, SLAVIC_PLURAL_EXAMPLES, SLAVIC_PLURAL_FORMS, SLAVIC_PLURAL_FORMS_HINT, SOURCE_LANGUAGE } from './base.js';
|
|
11
12
|
import { z } from "zod";
|
|
12
13
|
import iso6391 from 'iso-639-1';
|
|
13
14
|
const updateFieldBodySchema = z.object({
|
|
@@ -28,61 +29,10 @@ import path from 'path';
|
|
|
28
29
|
import fs from 'fs-extra';
|
|
29
30
|
import chokidar from 'chokidar';
|
|
30
31
|
import { AsyncQueue } from '@sapphire/async-queue';
|
|
31
|
-
import getFlagEmoji from 'country-flag-svg';
|
|
32
32
|
import { parse } from 'bcp-47';
|
|
33
33
|
import pLimit from 'p-limit';
|
|
34
34
|
import { afLogger } from "adminforth";
|
|
35
35
|
const processFrontendMessagesQueue = new AsyncQueue();
|
|
36
|
-
const SLAVIC_PLURAL_EXAMPLES = {
|
|
37
|
-
uk: 'яблук | Яблуко | Яблука | Яблук', // zero | singular | 2-4 | 5+
|
|
38
|
-
bg: 'ябълки | ябълка | ябълки | ябълки', // zero | singular | 2-4 | 5+
|
|
39
|
-
cs: 'jablek | jablko | jablka | jablek', // zero | singular | 2-4 | 5+
|
|
40
|
-
hr: 'jabuka | jabuka | jabuke | jabuka', // zero | singular | 2-4 | 5+
|
|
41
|
-
mk: 'јаболка | јаболко | јаболка | јаболка', // zero | singular | 2-4 | 5+
|
|
42
|
-
pl: 'jabłek | jabłko | jabłka | jabłek', // zero | singular | 2-4 | 5+
|
|
43
|
-
sk: 'jabĺk | jablko | jablká | jabĺk', // zero | singular | 2-4 | 5+
|
|
44
|
-
sl: 'jabolk | jabolko | jabolka | jabolk', // zero | singular | 2-4 | 5+
|
|
45
|
-
sr: 'јабука | јабука | јабуке | јабука', // zero | singular | 2-4 | 5+
|
|
46
|
-
be: 'яблыкаў | яблык | яблыкі | яблыкаў', // zero | singular | 2-4 | 5+
|
|
47
|
-
ru: 'яблок | яблоко | яблока | яблок', // zero | singular | 2-4 | 5+
|
|
48
|
-
};
|
|
49
|
-
const countryISO31661ByLangISO6391 = {
|
|
50
|
-
en: 'us', // English → United States
|
|
51
|
-
zh: 'cn', // Chinese → China
|
|
52
|
-
hi: 'in', // Hindi → India
|
|
53
|
-
ar: 'sa', // Arabic → Saudi Arabia
|
|
54
|
-
ko: 'kr', // Korean → South Korea
|
|
55
|
-
ja: 'jp', // Japanese → Japan
|
|
56
|
-
uk: 'ua', // Ukrainian → Ukraine
|
|
57
|
-
ur: 'pk', // Urdu → Pakistan
|
|
58
|
-
sr: 'rs', // Serbian → Serbia
|
|
59
|
-
da: 'dk' // Danish → Denmark
|
|
60
|
-
};
|
|
61
|
-
function getCountryCodeFromLangCode(lang) {
|
|
62
|
-
const [langCode, region] = String(lang).split('-');
|
|
63
|
-
if (region && /^[A-Z]{2}$/.test(region)) {
|
|
64
|
-
return region.toLowerCase();
|
|
65
|
-
}
|
|
66
|
-
return countryISO31661ByLangISO6391[langCode] || langCode;
|
|
67
|
-
}
|
|
68
|
-
function getPrimaryLanguageCode(langCode) {
|
|
69
|
-
return String(langCode).split('-')[0];
|
|
70
|
-
}
|
|
71
|
-
function isValidSupportedLanguageTag(langCode) {
|
|
72
|
-
try {
|
|
73
|
-
const schema = parse(String(langCode), {
|
|
74
|
-
normalize: true,
|
|
75
|
-
warning: (reason, code, offset) => {
|
|
76
|
-
console.warn(`Warning in validating language tag ${langCode}: reason=${reason}, code=${code}, offset=${offset}`);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
const isValid = schema.language.length === 2;
|
|
80
|
-
return isValid;
|
|
81
|
-
}
|
|
82
|
-
catch (e) {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
36
|
class CachingAdapterMemory {
|
|
87
37
|
constructor() {
|
|
88
38
|
this.cache = {};
|
|
@@ -121,29 +71,30 @@ class AiTranslateError extends Error {
|
|
|
121
71
|
this.name = 'AiTranslateError';
|
|
122
72
|
}
|
|
123
73
|
}
|
|
124
|
-
export default class I18nPlugin extends
|
|
74
|
+
export default class I18nPlugin extends I18nBase {
|
|
125
75
|
constructor(options) {
|
|
126
|
-
super(options
|
|
127
|
-
this.options = options;
|
|
76
|
+
super(options);
|
|
128
77
|
this.cache = new CachingAdapterMemory();
|
|
129
78
|
this.trFieldNames = {};
|
|
130
|
-
|
|
79
|
+
}
|
|
80
|
+
// translated languages joined by '|', e.g. 'uk|ja', stored in completedFieldName to filter not fully translated strings
|
|
81
|
+
completedLangsValue(isTranslated) {
|
|
82
|
+
return this.targetLanguages().filter(isTranslated).join('|');
|
|
131
83
|
}
|
|
132
84
|
computeCompletedFieldValue(record) {
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
});
|
|
85
|
+
return this.completedLangsValue((lang) => Boolean(record[this.trFieldNames[lang]]));
|
|
86
|
+
}
|
|
87
|
+
// all translations of category for frontend_messages
|
|
88
|
+
categoryCacheKey(category, lang) {
|
|
89
|
+
return `${this.resourceConfig.resourceId}:${category}:${lang}`;
|
|
90
|
+
}
|
|
91
|
+
// one string translated by backend tr
|
|
92
|
+
messageCacheKey(category, lang, enString) {
|
|
93
|
+
return `${this.categoryCacheKey(category, lang)}:${enString}`;
|
|
94
|
+
}
|
|
95
|
+
clearTranslationCache(category, lang, enString) {
|
|
96
|
+
this.cache.clear(this.categoryCacheKey(category, lang));
|
|
97
|
+
this.cache.clear(this.messageCacheKey(category, lang, enString));
|
|
147
98
|
}
|
|
148
99
|
updateUntranslatedMenuBadge() {
|
|
149
100
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -169,12 +120,6 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
169
120
|
this.adminforth.config.componentsToExplicitRegister.push({
|
|
170
121
|
file: this.componentPath('TranslationJobViewComponent.vue')
|
|
171
122
|
});
|
|
172
|
-
// validate each supported language: ISO 639-1 or BCP-47 with region (e.g., en-GB)
|
|
173
|
-
this.options.supportedLanguages.forEach((lang) => {
|
|
174
|
-
if (!isValidSupportedLanguageTag(lang)) {
|
|
175
|
-
throw new Error(`Invalid language code ${lang}. Use ISO 639-1 (e.g., 'en') or BCP-47 with region (e.g., 'en-GB').`);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
123
|
if (this.options.translateLangAsBCP47Code) {
|
|
179
124
|
for (const [lang, bcp47] of Object.entries(this.options.translateLangAsBCP47Code)) {
|
|
180
125
|
if (!this.options.supportedLanguages.includes(lang)) {
|
|
@@ -189,10 +134,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
189
134
|
throw new Error(`Primary key field not found in resource ${resourceConfig.resourceId}`);
|
|
190
135
|
}
|
|
191
136
|
// parse trFieldNames
|
|
192
|
-
for (const lang of this.
|
|
193
|
-
if (lang === 'en') {
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
137
|
+
for (const lang of this.targetLanguages()) {
|
|
196
138
|
if ((_b = this.options.translationFieldNames) === null || _b === void 0 ? void 0 : _b[lang]) {
|
|
197
139
|
this.trFieldNames[lang] = this.options.translationFieldNames[lang];
|
|
198
140
|
}
|
|
@@ -224,17 +166,8 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
224
166
|
},
|
|
225
167
|
};
|
|
226
168
|
}
|
|
227
|
-
this.enFieldName = this.trFieldNames[
|
|
228
|
-
this.fullCompleatedFieldValue = this.
|
|
229
|
-
if (lang === 'en') {
|
|
230
|
-
return acc;
|
|
231
|
-
}
|
|
232
|
-
if (acc !== '') {
|
|
233
|
-
acc += '|';
|
|
234
|
-
}
|
|
235
|
-
acc += lang;
|
|
236
|
-
return acc;
|
|
237
|
-
}, '');
|
|
169
|
+
this.enFieldName = this.trFieldNames[SOURCE_LANGUAGE] || `${SOURCE_LANGUAGE}_string`;
|
|
170
|
+
this.fullCompleatedFieldValue = this.completedLangsValue(() => true);
|
|
238
171
|
// if not enFieldName column is not found, throw error
|
|
239
172
|
const enColumn = resourceConfig.columns.find(c => c.name === this.enFieldName);
|
|
240
173
|
if (!enColumn) {
|
|
@@ -293,40 +226,15 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
293
226
|
type: AdminForthDataTypes.BOOLEAN,
|
|
294
227
|
});
|
|
295
228
|
}
|
|
296
|
-
const compMeta = {
|
|
297
|
-
brandSlug: adminforth.config.customization.brandNameSlug,
|
|
298
|
-
pluginInstanceId: this.pluginInstanceId,
|
|
299
|
-
primaryLanguage: this.primaryLanguage,
|
|
300
|
-
afOrder: this.options.loginPageLanguageSelectorOrder || 0,
|
|
301
|
-
supportedLanguages: this.options.supportedLanguages.map(lang => ({
|
|
302
|
-
code: lang,
|
|
303
|
-
// lang name on on language native name
|
|
304
|
-
name: iso6391.getNativeName(getPrimaryLanguageCode(lang)),
|
|
305
|
-
}))
|
|
306
|
-
};
|
|
307
229
|
// add underLogin component
|
|
308
230
|
if (!this.externalAppOnly) {
|
|
309
|
-
|
|
310
|
-
file: this.componentPath('LanguageUnderLogin.vue'),
|
|
311
|
-
meta: compMeta
|
|
312
|
-
});
|
|
313
|
-
(adminforth.config.customization.globalInjections.userMenu).push({
|
|
314
|
-
file: this.componentPath('LanguageInUserMenu.vue'),
|
|
315
|
-
meta: compMeta
|
|
316
|
-
});
|
|
317
|
-
adminforth.config.customization.globalInjections.everyPageBottom.push({
|
|
318
|
-
file: this.componentPath('LanguageEveryPageLoader.vue'),
|
|
319
|
-
meta: compMeta
|
|
320
|
-
});
|
|
231
|
+
this.injectLanguageSelectors(adminforth);
|
|
321
232
|
}
|
|
322
233
|
// disable create allowedActions for translations
|
|
323
234
|
resourceConfig.options.allowedActions.create = false;
|
|
324
235
|
// add hook to validate user did not screw up with template params
|
|
325
236
|
resourceConfig.hooks.edit.beforeSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ updates, oldRecord }) {
|
|
326
|
-
for (const lang of this.
|
|
327
|
-
if (lang === 'en') {
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
237
|
+
for (const lang of this.targetLanguages()) {
|
|
330
238
|
if (updates[this.trFieldNames[lang]]) { // if user set '', it will have '' in updates, then it is fine, we shoudl nto check it
|
|
331
239
|
if (!ensureTemplateHasAllParams(oldRecord[this.enFieldName], updates[this.trFieldNames[lang]])) {
|
|
332
240
|
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}'!` };
|
|
@@ -339,19 +247,10 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
339
247
|
resourceConfig.hooks.edit.afterSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ updates, oldRecord }) {
|
|
340
248
|
if (oldRecord) {
|
|
341
249
|
// find lang which changed
|
|
342
|
-
|
|
343
|
-
for (const lang of this.options.supportedLanguages) {
|
|
344
|
-
if (lang === 'en') {
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
if (updates[this.trFieldNames[lang]] !== undefined) {
|
|
348
|
-
langsChanged.push(lang);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
250
|
+
const langsChanged = this.targetLanguages().filter((lang) => updates[this.trFieldNames[lang]] !== undefined);
|
|
351
251
|
// clear frontend cache for all langsChanged
|
|
352
252
|
for (const lang of langsChanged) {
|
|
353
|
-
this.
|
|
354
|
-
this.cache.clear(`${this.resourceConfig.resourceId}:${oldRecord[this.options.categoryFieldName]}:${lang}:${oldRecord[this.enFieldName]}`);
|
|
253
|
+
this.clearTranslationCache(oldRecord[this.options.categoryFieldName], lang, oldRecord[this.enFieldName]);
|
|
355
254
|
}
|
|
356
255
|
this.updateUntranslatedMenuBadge();
|
|
357
256
|
}
|
|
@@ -361,9 +260,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
361
260
|
// add hook on delete of any translation to reset cache
|
|
362
261
|
resourceConfig.hooks.delete.afterSave.push((_a) => __awaiter(this, [_a], void 0, function* ({ record }) {
|
|
363
262
|
for (const lang of this.options.supportedLanguages) {
|
|
364
|
-
|
|
365
|
-
this.cache.clear(`${this.resourceConfig.resourceId}:${record[this.options.categoryFieldName]}:${lang}`);
|
|
366
|
-
this.cache.clear(`${this.resourceConfig.resourceId}:${record[this.options.categoryFieldName]}:${lang}:${record[this.enFieldName]}`);
|
|
263
|
+
this.clearTranslationCache(record[this.options.categoryFieldName], lang, record[this.enFieldName]);
|
|
367
264
|
}
|
|
368
265
|
this.updateUntranslatedMenuBadge();
|
|
369
266
|
return { ok: true };
|
|
@@ -422,6 +319,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
422
319
|
file: this.componentPath('BulkActionButton.vue'),
|
|
423
320
|
meta: {
|
|
424
321
|
supportedLanguages: this.options.supportedLanguages,
|
|
322
|
+
countryCodes: Object.fromEntries(this.options.supportedLanguages.map((lang) => [lang, getCountryCodeFromLangCode(lang)])),
|
|
425
323
|
pluginInstanceId: this.pluginInstanceId,
|
|
426
324
|
}
|
|
427
325
|
};
|
|
@@ -568,13 +466,8 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
568
466
|
yield this.adminforth.resource(this.resourceConfig.resourceId).update(strId, Object.assign(Object.assign({}, updates), { [this.options.completedFieldName]: futureCompletedFieldValue }));
|
|
569
467
|
})));
|
|
570
468
|
for (const lang of langsInvolved) {
|
|
571
|
-
const categoriesInvolved = new Set();
|
|
572
469
|
for (const { enStr, category } of Object.values(updateStrings)) {
|
|
573
|
-
|
|
574
|
-
yield this.cache.clear(`${this.resourceConfig.resourceId}:${category}:${lang}:${enStr}`);
|
|
575
|
-
}
|
|
576
|
-
for (const category of categoriesInvolved) {
|
|
577
|
-
yield this.cache.clear(`${this.resourceConfig.resourceId}:${category}:${lang}`);
|
|
470
|
+
this.clearTranslationCache(category, lang, enStr);
|
|
578
471
|
}
|
|
579
472
|
}
|
|
580
473
|
return { ok: true };
|
|
@@ -628,14 +521,14 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
628
521
|
const lang = langIsoCode;
|
|
629
522
|
const primaryLang = getPrimaryLanguageCode(lang);
|
|
630
523
|
const langName = iso6391.getName(primaryLang);
|
|
631
|
-
const requestSlavicPlurals =
|
|
524
|
+
const requestSlavicPlurals = requiresSlavicPlurals(lang) && plurals;
|
|
632
525
|
const region = langSchema.region;
|
|
633
526
|
const basePrompt = `
|
|
634
527
|
I need to translate strings in JSON to ${langName} language ${replacedLanguageCodeForTranslations || lang.length > 2 ? `BCP-47 code ${langCode}` : `ISO 639-1 code ${langIsoCode}`} from English for my web app.
|
|
635
528
|
|
|
636
529
|
|
|
637
530
|
${region ? `Use the regional conventions for ${langCode} (region ${region}), including spelling, punctuation, and formatting.` : ''}
|
|
638
|
-
${requestSlavicPlurals ? `You should provide
|
|
531
|
+
${requestSlavicPlurals ? `You should provide ${SLAVIC_PLURAL_FORMS} slavic forms (in format "${SLAVIC_PLURAL_FORMS_HINT}") e.g. "apple | apples" should become "${SLAVIC_PLURAL_EXAMPLES[primaryLang]}"` : ''}
|
|
639
532
|
JSON keys are internal identifiers. Keep keys as is and write translations into values. If values have variables (in curly brackets), then translated strings should have them as well (variables itself should not be translated). Here are the strings:
|
|
640
533
|
\`\`\`json
|
|
641
534
|
{
|
|
@@ -702,6 +595,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
702
595
|
translations: translations.filter(t => stringBanchCopy.includes(t.en_string)),
|
|
703
596
|
updateStrings,
|
|
704
597
|
lang,
|
|
598
|
+
countryCode: getCountryCodeFromLangCode(lang),
|
|
705
599
|
failedToTranslate,
|
|
706
600
|
needToTranslateByLang,
|
|
707
601
|
promptCost: basePromptTokenLength + banchTokens,
|
|
@@ -718,8 +612,8 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
718
612
|
const translations = yield this.adminforth.resource(this.resourceConfig.resourceId).list(Filters.IN(this.primaryKeyFieldName, selectedIds));
|
|
719
613
|
const languagesToProcess = selectedLanguages || this.options.supportedLanguages;
|
|
720
614
|
for (const lang of languagesToProcess) {
|
|
721
|
-
if (lang ===
|
|
722
|
-
// all strings are in
|
|
615
|
+
if (lang === SOURCE_LANGUAGE) {
|
|
616
|
+
// all strings are in source language, no need to translate
|
|
723
617
|
continue;
|
|
724
618
|
}
|
|
725
619
|
for (const translation of translations) {
|
|
@@ -849,10 +743,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
849
743
|
if (this.options.completeAdapter) {
|
|
850
744
|
this.options.completeAdapter.validate();
|
|
851
745
|
}
|
|
852
|
-
for (const lang of this.
|
|
853
|
-
if (lang === 'en') {
|
|
854
|
-
continue;
|
|
855
|
-
}
|
|
746
|
+
for (const lang of this.targetLanguages()) {
|
|
856
747
|
const column = resourceConfig.columns.find(c => c.name === this.trFieldNames[lang]);
|
|
857
748
|
if (!column) {
|
|
858
749
|
throw new Error(`Field ${this.trFieldNames[lang]} not found for storing translation for language ${lang}
|
|
@@ -887,134 +778,47 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
887
778
|
// in this plugin we will use plugin to fill the database with missing language messages
|
|
888
779
|
if (!this.externalAppOnly) {
|
|
889
780
|
this.tryProcessAndWatch(adminforth);
|
|
890
|
-
adminforth
|
|
891
|
-
if (!msg) {
|
|
892
|
-
return msg;
|
|
893
|
-
}
|
|
781
|
+
this.installBackendTranslator(adminforth, (msg, category, lang) => __awaiter(this, void 0, void 0, function* () {
|
|
894
782
|
if (category === 'frontend') {
|
|
895
783
|
throw new Error(`Category 'frontend' is reserved for frontend messages, use any other category for backend messages`);
|
|
896
784
|
}
|
|
897
|
-
// console.log('🪲tr', msg, category, lang);
|
|
898
|
-
// if lang is not supported, fallback to primaryLanguage, then to english
|
|
899
|
-
if (!this.options.supportedLanguages.includes(lang)) {
|
|
900
|
-
lang = this.primaryLanguage; // fallback to primary language first
|
|
901
|
-
if (!this.options.supportedLanguages.includes(lang)) {
|
|
902
|
-
lang = 'en'; // final fallback to english
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
let result;
|
|
906
785
|
// try to get translation from cache
|
|
907
|
-
const cacheKey =
|
|
786
|
+
const cacheKey = this.messageCacheKey(category, lang, msg);
|
|
908
787
|
const cached = yield this.cache.get(cacheKey);
|
|
909
788
|
if (cached) {
|
|
910
|
-
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
this.updateUntranslatedMenuBadge();
|
|
921
|
-
}
|
|
922
|
-
// do this check here, to faster register missing translations
|
|
923
|
-
// also not cache it - no sense to cache english strings
|
|
924
|
-
if (lang === 'en') {
|
|
925
|
-
// set to cache to return faster next time
|
|
926
|
-
result = msg;
|
|
927
|
-
}
|
|
928
|
-
else {
|
|
929
|
-
result = translation === null || translation === void 0 ? void 0 : translation[this.trFieldNames[lang]];
|
|
930
|
-
if (!result) {
|
|
931
|
-
// return english
|
|
932
|
-
result = msg;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
// cache so even if key does not exist, we will not hit database
|
|
936
|
-
yield this.cache.set(cacheKey, result);
|
|
937
|
-
}
|
|
938
|
-
// if msg has '|' in it, then we need to aplly pluralization
|
|
939
|
-
if (msg.includes('|') && pluralizationNumber !== undefined) {
|
|
940
|
-
result = this.applyPluralization(result, pluralizationNumber, lang);
|
|
941
|
-
}
|
|
942
|
-
if (params) {
|
|
943
|
-
for (const [key, value] of Object.entries(params)) {
|
|
944
|
-
if (result) {
|
|
945
|
-
result = result.replace(`{${key}}`, value);
|
|
946
|
-
}
|
|
947
|
-
}
|
|
789
|
+
return cached;
|
|
790
|
+
}
|
|
791
|
+
const resource = adminforth.resource(resourceConfig.resourceId);
|
|
792
|
+
const translation = yield resource.get([Filters.EQ(this.enFieldName, msg), Filters.EQ(this.options.categoryFieldName, category)]);
|
|
793
|
+
if (!translation) {
|
|
794
|
+
yield resource.create({
|
|
795
|
+
[this.enFieldName]: msg,
|
|
796
|
+
[this.options.categoryFieldName]: category,
|
|
797
|
+
});
|
|
798
|
+
this.updateUntranslatedMenuBadge();
|
|
948
799
|
}
|
|
800
|
+
// do this check here, to faster register missing translations
|
|
801
|
+
const result = lang === SOURCE_LANGUAGE ? msg : (translation === null || translation === void 0 ? void 0 : translation[this.trFieldNames[lang]]) || msg;
|
|
802
|
+
// cache so even if key does not exist, we will not hit database
|
|
803
|
+
yield this.cache.set(cacheKey, result);
|
|
949
804
|
return result;
|
|
950
|
-
});
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
applyPluralization(str, number, lang) {
|
|
954
|
-
const choices = str.split('|');
|
|
955
|
-
const choicesLength = choices.length;
|
|
956
|
-
if (choicesLength > 2) {
|
|
957
|
-
// this is slavic pluralization
|
|
958
|
-
return choices[this.slavicPluralRule(number, choicesLength)];
|
|
959
|
-
}
|
|
960
|
-
else {
|
|
961
|
-
return number === 1 ? choices[0] : choices[1];
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
// taken from here https://vue-i18n.intlify.dev/guide/essentials/pluralization.html#custom-pluralization
|
|
965
|
-
slavicPluralRule(choice, choicesLength) {
|
|
966
|
-
if (choice === 0) {
|
|
967
|
-
return 0;
|
|
968
|
-
}
|
|
969
|
-
const teen = choice > 10 && choice < 20;
|
|
970
|
-
const endsWithOne = choice % 10 === 1;
|
|
971
|
-
if (!teen && endsWithOne) {
|
|
972
|
-
return 1;
|
|
973
|
-
}
|
|
974
|
-
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
|
|
975
|
-
return 2;
|
|
805
|
+
}));
|
|
976
806
|
}
|
|
977
|
-
return choicesLength < 4 ? 2 : 3;
|
|
978
|
-
}
|
|
979
|
-
instanceUniqueRepresentation(pluginOptions) {
|
|
980
|
-
// optional method to return unique string representation of plugin instance.
|
|
981
|
-
// Needed if plugin can have multiple instances on one resource
|
|
982
|
-
return `single`;
|
|
983
807
|
}
|
|
984
808
|
getCategoryTranslations(category, lang) {
|
|
985
809
|
return __awaiter(this, void 0, void 0, function* () {
|
|
986
810
|
const resource = this.adminforth.resource(this.resourceConfig.resourceId);
|
|
987
|
-
const cacheKey =
|
|
811
|
+
const cacheKey = this.categoryCacheKey(category, lang);
|
|
988
812
|
const cached = yield this.cache.get(cacheKey);
|
|
989
813
|
if (cached) {
|
|
990
814
|
return cached;
|
|
991
815
|
}
|
|
992
|
-
const translations = {};
|
|
993
816
|
const allTranslations = yield resource.list([Filters.EQ(this.options.categoryFieldName, category)]);
|
|
994
|
-
|
|
995
|
-
const translatedValue = tr[this.trFieldNames[lang]];
|
|
996
|
-
translations[tr[this.enFieldName]] = translatedValue || tr[this.enFieldName];
|
|
997
|
-
}
|
|
817
|
+
const translations = this.fallbackToOriginal(Object.fromEntries(allTranslations.map((tr) => [tr[this.enFieldName], tr[this.trFieldNames[lang]]])));
|
|
998
818
|
yield this.cache.set(cacheKey, translations);
|
|
999
819
|
return translations;
|
|
1000
820
|
});
|
|
1001
821
|
}
|
|
1002
|
-
languagesList() {
|
|
1003
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
1004
|
-
return Promise.all(this.options.supportedLanguages.map((lang) => __awaiter(this, void 0, void 0, function* () {
|
|
1005
|
-
const flagEmoji = typeof getFlagEmoji === 'function'
|
|
1006
|
-
? getFlagEmoji
|
|
1007
|
-
: getFlagEmoji.default;
|
|
1008
|
-
return {
|
|
1009
|
-
code: lang,
|
|
1010
|
-
nameOnNative: iso6391.getNativeName(getPrimaryLanguageCode(lang)),
|
|
1011
|
-
nameEnglish: iso6391.getName(getPrimaryLanguageCode(lang)),
|
|
1012
|
-
emojiFlag: getCountryCodeFromLangCode(lang).toUpperCase().replace(/./g, char => String.fromCodePoint(char.charCodeAt(0) + 127397)),
|
|
1013
|
-
svgFlagB64: yield flagEmoji(getCountryCodeFromLangCode(lang)),
|
|
1014
|
-
};
|
|
1015
|
-
})));
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
822
|
feedCategoryTranslations(messages, category) {
|
|
1019
823
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1020
824
|
const adminforth = this.adminforth;
|
|
@@ -1049,17 +853,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
1049
853
|
});
|
|
1050
854
|
}
|
|
1051
855
|
setupEndpoints(server) {
|
|
1052
|
-
|
|
1053
|
-
method: 'GET',
|
|
1054
|
-
path: `/plugin/${this.pluginInstanceId}/frontend_messages`,
|
|
1055
|
-
noAuth: true,
|
|
1056
|
-
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ query }) {
|
|
1057
|
-
const lang = query.lang;
|
|
1058
|
-
// form map of translations
|
|
1059
|
-
const translations = yield this.getCategoryTranslations('frontend', lang);
|
|
1060
|
-
return translations;
|
|
1061
|
-
})
|
|
1062
|
-
});
|
|
856
|
+
super.setupEndpoints(server);
|
|
1063
857
|
const lock = new RAMLock();
|
|
1064
858
|
server.endpoint({
|
|
1065
859
|
method: 'POST',
|
|
@@ -1203,3 +997,4 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
1203
997
|
});
|
|
1204
998
|
}
|
|
1205
999
|
}
|
|
1000
|
+
export { I18nBase, getCountryCodeFromLangCode, getPrimaryLanguageCode, isValidSupportedLanguageTag, requiresSlavicPlurals, SOURCE_LANGUAGE, PLURAL_SEPARATOR, SLAVIC_PLURAL_FORMS, SLAVIC_PLURAL_FORMS_HINT } from './base.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -5,8 +5,24 @@ import { type PluginsCommonOptions } from "adminforth";
|
|
|
5
5
|
type Alpha2Code = keyof typeof iso31661Alpha2ToAlpha3;
|
|
6
6
|
type Bcp47LanguageTag = `${LanguageCode}-${Alpha2Code}`;
|
|
7
7
|
export type SupportedLanguage = LanguageCode | Bcp47LanguageTag;
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Options shared by all i18n plugins built on top of I18nBase
|
|
10
|
+
*/
|
|
11
|
+
export interface I18nBaseOptions extends PluginsCommonOptions {
|
|
9
12
|
supportedLanguages: SupportedLanguage[];
|
|
13
|
+
/**
|
|
14
|
+
* Language selector on login page order
|
|
15
|
+
*/
|
|
16
|
+
loginPageLanguageSelectorOrder?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Primary language for the application. This is the default language shown to users.
|
|
19
|
+
* English is always used as the source language for translations, even if primaryLanguage is different.
|
|
20
|
+
* When a translation is missing for the primaryLanguage, English will be shown as fallback.
|
|
21
|
+
* Defaults to 'en' if not specified.
|
|
22
|
+
*/
|
|
23
|
+
primaryLanguage?: SupportedLanguage;
|
|
24
|
+
}
|
|
25
|
+
export interface PluginOptions extends I18nBaseOptions {
|
|
10
26
|
/**
|
|
11
27
|
* Each translation string will be stored in a separate field, you can remap it to existing columns using this option
|
|
12
28
|
* By default it will assume field are named like `${lang_code}_string` (e.g. 'en_string', 'uk_string', 'ja_string', 'fr_string')
|
|
@@ -18,10 +34,6 @@ export interface PluginOptions extends PluginsCommonOptions {
|
|
|
18
34
|
* We recommend to put index on this column
|
|
19
35
|
*/
|
|
20
36
|
categoryFieldName: string;
|
|
21
|
-
/**
|
|
22
|
-
* Language selector on login page order
|
|
23
|
-
*/
|
|
24
|
-
loginPageLanguageSelectorOrder?: number;
|
|
25
37
|
/**
|
|
26
38
|
* Optional source field to store e.g. file name where it first was captured
|
|
27
39
|
*/
|
|
@@ -46,13 +58,6 @@ export interface PluginOptions extends PluginsCommonOptions {
|
|
|
46
58
|
* it should be a JSON field (underlyng database type should be TEXT or JSON)
|
|
47
59
|
*/
|
|
48
60
|
reviewedCheckboxesFieldName?: string;
|
|
49
|
-
/**
|
|
50
|
-
* Primary language for the application. This is the default language shown to users.
|
|
51
|
-
* English is always used as the source language for translations, even if primaryLanguage is different.
|
|
52
|
-
* When a translation is missing for the primaryLanguage, English will be shown as fallback.
|
|
53
|
-
* Defaults to 'en' if not specified.
|
|
54
|
-
*/
|
|
55
|
-
primaryLanguage?: SupportedLanguage;
|
|
56
61
|
/**
|
|
57
62
|
* Ask translator to treat some code from supportedLanguages as exact BCP47 tag. Read docs for details.
|
|
58
63
|
* key - one of the values form supportedLanguages, value -BCP47 tag
|