@adminforth/i18n 1.8.0 → 1.9.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/build.log +3 -2
- package/custom/BulkActionButton.vue +132 -0
- package/custom/langCommon.ts +2 -0
- package/custom/package-lock.json +9 -0
- package/custom/package.json +3 -0
- package/dist/custom/BulkActionButton.vue +132 -0
- package/dist/custom/langCommon.ts +2 -0
- package/dist/custom/package-lock.json +9 -0
- package/dist/custom/package.json +3 -0
- package/dist/index.js +90 -52
- package/index.ts +99 -53
- package/package.json +2 -1
- package/types.ts +7 -1
package/build.log
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
sending incremental file list
|
|
6
6
|
custom/
|
|
7
|
+
custom/BulkActionButton.vue
|
|
7
8
|
custom/LanguageEveryPageLoader.vue
|
|
8
9
|
custom/LanguageInUserMenu.vue
|
|
9
10
|
custom/LanguageUnderLogin.vue
|
|
@@ -17,5 +18,5 @@ custom/package-lock.json
|
|
|
17
18
|
custom/package.json
|
|
18
19
|
custom/tsconfig.json
|
|
19
20
|
|
|
20
|
-
sent
|
|
21
|
-
total size is
|
|
21
|
+
sent 38,848 bytes received 267 bytes 78,230.00 bytes/sec
|
|
22
|
+
total size is 37,840 speedup is 0.97
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Dialog
|
|
3
|
+
class="w-[500px]"
|
|
4
|
+
:buttons="[
|
|
5
|
+
{
|
|
6
|
+
label: 'Translate',
|
|
7
|
+
onclick: (dialog) => { runTranslation(); dialog.hide(); } ,
|
|
8
|
+
options: {
|
|
9
|
+
disabled: noneChecked
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'Close',
|
|
14
|
+
options: {
|
|
15
|
+
class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200 dark:!border-gray-600'
|
|
16
|
+
},
|
|
17
|
+
onclick: (dialog) => dialog.hide()
|
|
18
|
+
},
|
|
19
|
+
]"
|
|
20
|
+
>
|
|
21
|
+
<template #trigger>
|
|
22
|
+
<button
|
|
23
|
+
v-if="checkboxes.length > 0"
|
|
24
|
+
class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded-default border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover"
|
|
25
|
+
>
|
|
26
|
+
<IconLanguageOutline class="w-5 h-5" />
|
|
27
|
+
{{ t('Translate Selected') }} {{ `(${checkboxes.length})` }}
|
|
28
|
+
<div class="text-white bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800
|
|
29
|
+
font-medium rounded-sm text-xs px-1 ml-1 text-center ">
|
|
30
|
+
AI
|
|
31
|
+
</div>
|
|
32
|
+
</button>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<div class="grid grid-cols-2 gap-4 w-full">
|
|
36
|
+
<Button @click="selectAll" :disabled="allChecked">{{ t('Select All') }}</Button>
|
|
37
|
+
<Button @click="uncheckAll" :disabled="noneChecked">{{ t('Uncheck All') }}</Button>
|
|
38
|
+
<div class="col-span-2 grid grid-cols-3 gap-4 ">
|
|
39
|
+
<div class="group flex items-center justify-between cursor-pointer" v-for="(index, lang) in checkedLanguages" :key="index" @click="toggleLanguage(lang)">
|
|
40
|
+
<div class="flex items-center gap-2">
|
|
41
|
+
<Checkbox v-model="checkedLanguages[lang]" />
|
|
42
|
+
<span class="flag-icon"
|
|
43
|
+
:class="`flag-icon-${getCountryCodeFromLangCode(lang)}`"
|
|
44
|
+
></span>
|
|
45
|
+
<span class="group-hover:underline">{{ getName(getCountryCodeFromLangCode(lang)) }}</span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</Dialog>
|
|
51
|
+
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import { IconLanguageOutline } from '@iconify-prerendered/vue-flowbite';
|
|
56
|
+
import { useI18n } from 'vue-i18n';
|
|
57
|
+
import { Dialog, Button, Checkbox } from '@/afcl';
|
|
58
|
+
import { computed, onMounted, ref, watch } from 'vue';
|
|
59
|
+
import { callAdminForthApi } from '@/utils';
|
|
60
|
+
import { useAdminforth } from '@/adminforth';
|
|
61
|
+
import { getCountryCodeFromLangCode } from './langCommon';
|
|
62
|
+
import { getName, overwrite } from 'country-list';
|
|
63
|
+
|
|
64
|
+
const { t } = useI18n();
|
|
65
|
+
const adminforth = useAdminforth();
|
|
66
|
+
|
|
67
|
+
overwrite([{
|
|
68
|
+
code: 'US',
|
|
69
|
+
name: 'USA'
|
|
70
|
+
}]);
|
|
71
|
+
const props = defineProps<{
|
|
72
|
+
resource: Record<string, any>;
|
|
73
|
+
checkboxes: string[];
|
|
74
|
+
adminUser: Record<string, any>;
|
|
75
|
+
meta: {
|
|
76
|
+
supportedLanguages: string[];
|
|
77
|
+
pluginInstanceId: string;
|
|
78
|
+
};
|
|
79
|
+
clearCheckboxes: () => void;
|
|
80
|
+
}>();
|
|
81
|
+
|
|
82
|
+
const checkedLanguages = ref<Record<string, boolean>>({});
|
|
83
|
+
const allChecked = computed(() => Object.values(checkedLanguages.value).every(Boolean));
|
|
84
|
+
const noneChecked = computed(() => Object.values(checkedLanguages.value).every(value => !value));
|
|
85
|
+
|
|
86
|
+
onMounted(() => {
|
|
87
|
+
for (const lang of props.meta.supportedLanguages) {
|
|
88
|
+
checkedLanguages.value[lang] = true;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function selectAll() {
|
|
93
|
+
for (const lang of props.meta.supportedLanguages) {
|
|
94
|
+
checkedLanguages.value[lang] = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function uncheckAll() {
|
|
99
|
+
for (const lang of props.meta.supportedLanguages) {
|
|
100
|
+
checkedLanguages.value[lang] = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function toggleLanguage(lang: string) {
|
|
105
|
+
checkedLanguages.value[lang] = !checkedLanguages.value[lang];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function runTranslation() {
|
|
109
|
+
try {
|
|
110
|
+
const res = await callAdminForthApi({
|
|
111
|
+
path: `/plugin/${props.meta.pluginInstanceId}/translate-selected-to-languages`,
|
|
112
|
+
method: 'POST',
|
|
113
|
+
body: {
|
|
114
|
+
selectedIds: props.checkboxes,
|
|
115
|
+
selectedLanguages: Object.keys(checkedLanguages.value).filter(lang => checkedLanguages.value[lang]),
|
|
116
|
+
},
|
|
117
|
+
silentError: true,
|
|
118
|
+
});
|
|
119
|
+
adminforth.list.refresh();
|
|
120
|
+
props.clearCheckboxes();
|
|
121
|
+
if (res.ok) {
|
|
122
|
+
adminforth.alert({ message: res.successMessage, variant: 'success' });
|
|
123
|
+
} else {
|
|
124
|
+
adminforth.alert({ message: res.errorMessage || t('Failed to translate selected items. Please, try again.'), variant: 'danger' });
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
console.error('Failed to translate selected items:', e);
|
|
128
|
+
adminforth.alert({ message: t('Failed to translate selected items. Please, try again.'), variant: 'danger' });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
</script>
|
package/custom/langCommon.ts
CHANGED
|
@@ -76,6 +76,8 @@ const countryISO31661ByLangISO6391 = {
|
|
|
76
76
|
ja: 'jp', // Japanese → Japan
|
|
77
77
|
uk: 'ua', // Ukrainian → Ukraine
|
|
78
78
|
ur: 'pk', // Urdu → Pakistan
|
|
79
|
+
sr: 'rs', // Serbian → Serbia
|
|
80
|
+
da: 'dk' // Danish → Denmark
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
export function getCountryCodeFromLangCode(langCode) {
|
package/custom/package-lock.json
CHANGED
|
@@ -8,10 +8,19 @@
|
|
|
8
8
|
"name": "custom",
|
|
9
9
|
"version": "1.0.0",
|
|
10
10
|
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"country-list": "^2.4.1"
|
|
13
|
+
},
|
|
11
14
|
"devDependencies": {
|
|
12
15
|
"flag-icon-css": "^4.1.7"
|
|
13
16
|
}
|
|
14
17
|
},
|
|
18
|
+
"node_modules/country-list": {
|
|
19
|
+
"version": "2.4.1",
|
|
20
|
+
"resolved": "https://registry.npmjs.org/country-list/-/country-list-2.4.1.tgz",
|
|
21
|
+
"integrity": "sha512-KhVV/UfUV3dSNpsWIqHTQxLpYDKPKz1UwkRjadt+YbX2PRhyCEihEoS5XgB7J7AMXpkicvl+tRHvkNI5wbji/g==",
|
|
22
|
+
"license": "MIT"
|
|
23
|
+
},
|
|
15
24
|
"node_modules/flag-icon-css": {
|
|
16
25
|
"version": "4.1.7",
|
|
17
26
|
"resolved": "https://registry.npmjs.org/flag-icon-css/-/flag-icon-css-4.1.7.tgz",
|
package/custom/package.json
CHANGED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Dialog
|
|
3
|
+
class="w-[500px]"
|
|
4
|
+
:buttons="[
|
|
5
|
+
{
|
|
6
|
+
label: 'Translate',
|
|
7
|
+
onclick: (dialog) => { runTranslation(); dialog.hide(); } ,
|
|
8
|
+
options: {
|
|
9
|
+
disabled: noneChecked
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'Close',
|
|
14
|
+
options: {
|
|
15
|
+
class: 'bg-white hover:!bg-gray-100 !text-gray-900 hover:!text-gray-800 dark:!bg-gray-800 dark:!text-gray-100 dark:hover:!bg-gray-700 !border-gray-200 dark:!border-gray-600'
|
|
16
|
+
},
|
|
17
|
+
onclick: (dialog) => dialog.hide()
|
|
18
|
+
},
|
|
19
|
+
]"
|
|
20
|
+
>
|
|
21
|
+
<template #trigger>
|
|
22
|
+
<button
|
|
23
|
+
v-if="checkboxes.length > 0"
|
|
24
|
+
class="flex gap-1 items-center py-1 px-3 text-sm font-medium text-lightListViewButtonText focus:outline-none bg-lightListViewButtonBackground rounded-default border border-lightListViewButtonBorder hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover focus:z-10 focus:ring-4 focus:ring-lightListViewButtonFocusRing dark:focus:ring-darkListViewButtonFocusRing dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder dark:hover:text-darkListViewButtonTextHover dark:hover:bg-darkListViewButtonBackgroundHover"
|
|
25
|
+
>
|
|
26
|
+
<IconLanguageOutline class="w-5 h-5" />
|
|
27
|
+
{{ t('Translate Selected') }} {{ `(${checkboxes.length})` }}
|
|
28
|
+
<div class="text-white bg-gradient-to-r from-purple-500 via-purple-600 to-purple-700 hover:bg-gradient-to-br focus:ring-4 focus:outline-none focus:ring-purple-300 dark:focus:ring-purple-800
|
|
29
|
+
font-medium rounded-sm text-xs px-1 ml-1 text-center ">
|
|
30
|
+
AI
|
|
31
|
+
</div>
|
|
32
|
+
</button>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<div class="grid grid-cols-2 gap-4 w-full">
|
|
36
|
+
<Button @click="selectAll" :disabled="allChecked">{{ t('Select All') }}</Button>
|
|
37
|
+
<Button @click="uncheckAll" :disabled="noneChecked">{{ t('Uncheck All') }}</Button>
|
|
38
|
+
<div class="col-span-2 grid grid-cols-3 gap-4 ">
|
|
39
|
+
<div class="group flex items-center justify-between cursor-pointer" v-for="(index, lang) in checkedLanguages" :key="index" @click="toggleLanguage(lang)">
|
|
40
|
+
<div class="flex items-center gap-2">
|
|
41
|
+
<Checkbox v-model="checkedLanguages[lang]" />
|
|
42
|
+
<span class="flag-icon"
|
|
43
|
+
:class="`flag-icon-${getCountryCodeFromLangCode(lang)}`"
|
|
44
|
+
></span>
|
|
45
|
+
<span class="group-hover:underline">{{ getName(getCountryCodeFromLangCode(lang)) }}</span>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</Dialog>
|
|
51
|
+
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script setup lang="ts">
|
|
55
|
+
import { IconLanguageOutline } from '@iconify-prerendered/vue-flowbite';
|
|
56
|
+
import { useI18n } from 'vue-i18n';
|
|
57
|
+
import { Dialog, Button, Checkbox } from '@/afcl';
|
|
58
|
+
import { computed, onMounted, ref, watch } from 'vue';
|
|
59
|
+
import { callAdminForthApi } from '@/utils';
|
|
60
|
+
import { useAdminforth } from '@/adminforth';
|
|
61
|
+
import { getCountryCodeFromLangCode } from './langCommon';
|
|
62
|
+
import { getName, overwrite } from 'country-list';
|
|
63
|
+
|
|
64
|
+
const { t } = useI18n();
|
|
65
|
+
const adminforth = useAdminforth();
|
|
66
|
+
|
|
67
|
+
overwrite([{
|
|
68
|
+
code: 'US',
|
|
69
|
+
name: 'USA'
|
|
70
|
+
}]);
|
|
71
|
+
const props = defineProps<{
|
|
72
|
+
resource: Record<string, any>;
|
|
73
|
+
checkboxes: string[];
|
|
74
|
+
adminUser: Record<string, any>;
|
|
75
|
+
meta: {
|
|
76
|
+
supportedLanguages: string[];
|
|
77
|
+
pluginInstanceId: string;
|
|
78
|
+
};
|
|
79
|
+
clearCheckboxes: () => void;
|
|
80
|
+
}>();
|
|
81
|
+
|
|
82
|
+
const checkedLanguages = ref<Record<string, boolean>>({});
|
|
83
|
+
const allChecked = computed(() => Object.values(checkedLanguages.value).every(Boolean));
|
|
84
|
+
const noneChecked = computed(() => Object.values(checkedLanguages.value).every(value => !value));
|
|
85
|
+
|
|
86
|
+
onMounted(() => {
|
|
87
|
+
for (const lang of props.meta.supportedLanguages) {
|
|
88
|
+
checkedLanguages.value[lang] = true;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
function selectAll() {
|
|
93
|
+
for (const lang of props.meta.supportedLanguages) {
|
|
94
|
+
checkedLanguages.value[lang] = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function uncheckAll() {
|
|
99
|
+
for (const lang of props.meta.supportedLanguages) {
|
|
100
|
+
checkedLanguages.value[lang] = false;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function toggleLanguage(lang: string) {
|
|
105
|
+
checkedLanguages.value[lang] = !checkedLanguages.value[lang];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function runTranslation() {
|
|
109
|
+
try {
|
|
110
|
+
const res = await callAdminForthApi({
|
|
111
|
+
path: `/plugin/${props.meta.pluginInstanceId}/translate-selected-to-languages`,
|
|
112
|
+
method: 'POST',
|
|
113
|
+
body: {
|
|
114
|
+
selectedIds: props.checkboxes,
|
|
115
|
+
selectedLanguages: Object.keys(checkedLanguages.value).filter(lang => checkedLanguages.value[lang]),
|
|
116
|
+
},
|
|
117
|
+
silentError: true,
|
|
118
|
+
});
|
|
119
|
+
adminforth.list.refresh();
|
|
120
|
+
props.clearCheckboxes();
|
|
121
|
+
if (res.ok) {
|
|
122
|
+
adminforth.alert({ message: res.successMessage, variant: 'success' });
|
|
123
|
+
} else {
|
|
124
|
+
adminforth.alert({ message: res.errorMessage || t('Failed to translate selected items. Please, try again.'), variant: 'danger' });
|
|
125
|
+
}
|
|
126
|
+
} catch (e) {
|
|
127
|
+
console.error('Failed to translate selected items:', e);
|
|
128
|
+
adminforth.alert({ message: t('Failed to translate selected items. Please, try again.'), variant: 'danger' });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
</script>
|
|
@@ -76,6 +76,8 @@ const countryISO31661ByLangISO6391 = {
|
|
|
76
76
|
ja: 'jp', // Japanese → Japan
|
|
77
77
|
uk: 'ua', // Ukrainian → Ukraine
|
|
78
78
|
ur: 'pk', // Urdu → Pakistan
|
|
79
|
+
sr: 'rs', // Serbian → Serbia
|
|
80
|
+
da: 'dk' // Danish → Denmark
|
|
79
81
|
};
|
|
80
82
|
|
|
81
83
|
export function getCountryCodeFromLangCode(langCode) {
|
|
@@ -8,10 +8,19 @@
|
|
|
8
8
|
"name": "custom",
|
|
9
9
|
"version": "1.0.0",
|
|
10
10
|
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"country-list": "^2.4.1"
|
|
13
|
+
},
|
|
11
14
|
"devDependencies": {
|
|
12
15
|
"flag-icon-css": "^4.1.7"
|
|
13
16
|
}
|
|
14
17
|
},
|
|
18
|
+
"node_modules/country-list": {
|
|
19
|
+
"version": "2.4.1",
|
|
20
|
+
"resolved": "https://registry.npmjs.org/country-list/-/country-list-2.4.1.tgz",
|
|
21
|
+
"integrity": "sha512-KhVV/UfUV3dSNpsWIqHTQxLpYDKPKz1UwkRjadt+YbX2PRhyCEihEoS5XgB7J7AMXpkicvl+tRHvkNI5wbji/g==",
|
|
22
|
+
"license": "MIT"
|
|
23
|
+
},
|
|
15
24
|
"node_modules/flag-icon-css": {
|
|
16
25
|
"version": "4.1.7",
|
|
17
26
|
"resolved": "https://registry.npmjs.org/flag-icon-css/-/flag-icon-css-4.1.7.tgz",
|
package/dist/custom/package.json
CHANGED
package/dist/index.js
CHANGED
|
@@ -9,12 +9,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import { AdminForthPlugin, Filters, suggestIfTypo, AdminForthDataTypes, RAMLock } from "adminforth";
|
|
11
11
|
import iso6391 from 'iso-639-1';
|
|
12
|
-
import { iso31661Alpha2ToAlpha3 } from 'iso-3166';
|
|
13
12
|
import path from 'path';
|
|
14
13
|
import fs from 'fs-extra';
|
|
15
14
|
import chokidar from 'chokidar';
|
|
16
15
|
import { AsyncQueue } from '@sapphire/async-queue';
|
|
17
16
|
import getFlagEmoji from 'country-flag-svg';
|
|
17
|
+
import { parse } from 'bcp-47';
|
|
18
18
|
const processFrontendMessagesQueue = new AsyncQueue();
|
|
19
19
|
const SLAVIC_PLURAL_EXAMPLES = {
|
|
20
20
|
uk: 'яблук | Яблуко | Яблука | Яблук', // zero | singular | 2-4 | 5+
|
|
@@ -52,15 +52,19 @@ function getPrimaryLanguageCode(langCode) {
|
|
|
52
52
|
return String(langCode).split('-')[0];
|
|
53
53
|
}
|
|
54
54
|
function isValidSupportedLanguageTag(langCode) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
try {
|
|
56
|
+
const schema = parse(String(langCode), {
|
|
57
|
+
normalize: true,
|
|
58
|
+
warning: (reason, code, offset) => {
|
|
59
|
+
console.warn(`Warning in validating language tag ${langCode}: reason=${reason}, code=${code}, offset=${offset}`);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const isValid = schema && schema.language && schema.language.length === 2;
|
|
63
|
+
return !!isValid;
|
|
58
64
|
}
|
|
59
|
-
|
|
60
|
-
return
|
|
65
|
+
catch (e) {
|
|
66
|
+
return false;
|
|
61
67
|
}
|
|
62
|
-
const regionUpper = region.toUpperCase();
|
|
63
|
-
return /^[A-Z]{2}$/.test(regionUpper) && (regionUpper in iso31661Alpha2ToAlpha3);
|
|
64
68
|
}
|
|
65
69
|
class CachingAdapterMemory {
|
|
66
70
|
constructor() {
|
|
@@ -148,6 +152,13 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
148
152
|
throw new Error(`Invalid language code ${lang}. Use ISO 639-1 (e.g., 'en') or BCP-47 with region (e.g., 'en-GB').`);
|
|
149
153
|
}
|
|
150
154
|
});
|
|
155
|
+
if (this.options.translateLangAsBCP47Code) {
|
|
156
|
+
for (const [lang, bcp47] of Object.entries(this.options.translateLangAsBCP47Code)) {
|
|
157
|
+
if (!this.options.supportedLanguages.includes(lang)) {
|
|
158
|
+
throw new Error(`Invalid language code ${lang} in translateLangAsBCP47Code. It must be one of the supportedLanguages.`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
151
162
|
this.externalAppOnly = this.options.externalAppOnly === true;
|
|
152
163
|
// find primary key field
|
|
153
164
|
this.primaryKeyFieldName = (_a = resourceConfig.columns.find(c => c.primaryKey)) === null || _a === void 0 ? void 0 : _a.name;
|
|
@@ -384,45 +395,23 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
384
395
|
}));
|
|
385
396
|
}
|
|
386
397
|
// add bulk action
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
action: (_a) => __awaiter(this, [_a], void 0, function* ({ selectedIds, tr }) {
|
|
403
|
-
let translatedCount = 0;
|
|
404
|
-
try {
|
|
405
|
-
translatedCount = yield this.bulkTranslate({ selectedIds });
|
|
406
|
-
}
|
|
407
|
-
catch (e) {
|
|
408
|
-
process.env.HEAVY_DEBUG && console.error('🪲⛔ bulkTranslate error', e);
|
|
409
|
-
if (e instanceof AiTranslateError) {
|
|
410
|
-
return { ok: false, error: e.message };
|
|
411
|
-
}
|
|
412
|
-
throw e;
|
|
413
|
-
}
|
|
414
|
-
this.updateUntranslatedMenuBadge();
|
|
415
|
-
return {
|
|
416
|
-
ok: true,
|
|
417
|
-
error: undefined,
|
|
418
|
-
successMessage: yield tr(`Translated {count} items`, 'backend', {
|
|
419
|
-
count: translatedCount,
|
|
420
|
-
}),
|
|
421
|
-
};
|
|
422
|
-
})
|
|
423
|
-
});
|
|
398
|
+
const pageInjection = {
|
|
399
|
+
file: this.componentPath('BulkActionButton.vue'),
|
|
400
|
+
meta: {
|
|
401
|
+
supportedLanguages: this.options.supportedLanguages,
|
|
402
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
if (!resourceConfig.options.pageInjections) {
|
|
406
|
+
resourceConfig.options.pageInjections = {};
|
|
407
|
+
}
|
|
408
|
+
if (!resourceConfig.options.pageInjections.list) {
|
|
409
|
+
resourceConfig.options.pageInjections.list = {};
|
|
410
|
+
}
|
|
411
|
+
if (!resourceConfig.options.pageInjections.list.beforeActionButtons) {
|
|
412
|
+
resourceConfig.options.pageInjections.list.beforeActionButtons = [];
|
|
424
413
|
}
|
|
425
|
-
;
|
|
414
|
+
resourceConfig.options.pageInjections.list.beforeActionButtons.push(pageInjection);
|
|
426
415
|
// if there is menu item with resourceId, add .badge function showing number of untranslated strings
|
|
427
416
|
const addBadgeCountToMenuItem = (menuItem) => {
|
|
428
417
|
this.menuItemWithBadgeId = menuItem.itemId;
|
|
@@ -454,6 +443,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
454
443
|
if (strings.length === 0) {
|
|
455
444
|
return [];
|
|
456
445
|
}
|
|
446
|
+
const replacedLanguageCodeForTranslations = this.options.translateLangAsBCP47Code && langIsoCode.length === 2 ? this.options.translateLangAsBCP47Code[langIsoCode] : null;
|
|
457
447
|
if (strings.length > maxKeysInOneReq) {
|
|
458
448
|
let totalTranslated = [];
|
|
459
449
|
for (let i = 0; i < strings.length; i += maxKeysInOneReq) {
|
|
@@ -464,14 +454,15 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
464
454
|
}
|
|
465
455
|
return totalTranslated;
|
|
466
456
|
}
|
|
457
|
+
const langCode = replacedLanguageCodeForTranslations ? replacedLanguageCodeForTranslations : langIsoCode;
|
|
467
458
|
const lang = langIsoCode;
|
|
468
459
|
const primaryLang = getPrimaryLanguageCode(lang);
|
|
469
460
|
const langName = iso6391.getName(primaryLang);
|
|
470
461
|
const requestSlavicPlurals = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(primaryLang) && plurals;
|
|
471
462
|
const region = ((_a = String(lang).split('-')[1]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || '';
|
|
472
463
|
const prompt = `
|
|
473
|
-
I need to translate strings in JSON to ${langName} language
|
|
474
|
-
${region ? `Use the regional conventions for ${
|
|
464
|
+
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.
|
|
465
|
+
${region ? `Use the regional conventions for ${langCode} (region ${region}), including spelling, punctuation, and formatting.` : ''}
|
|
475
466
|
${requestSlavicPlurals ? `You should provide 4 slavic forms (in format "zero count | singular count | 2-4 | 5+") e.g. "apple | apples" should become "${SLAVIC_PLURAL_EXAMPLES[lang]}"` : ''}
|
|
476
467
|
Keep keys, as is, write translation into values! If keys have variables (in curly brackets), then translated strings should have them as well (variables itself should not be translated). Here are the strings:
|
|
477
468
|
|
|
@@ -482,8 +473,25 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
482
473
|
}, {}), null, 2)}
|
|
483
474
|
\`\`\`
|
|
484
475
|
`;
|
|
476
|
+
const jsonSchemaProperties = {};
|
|
477
|
+
strings.forEach(s => {
|
|
478
|
+
jsonSchemaProperties[s.en_string] = {
|
|
479
|
+
type: 'string',
|
|
480
|
+
minLength: 1,
|
|
481
|
+
};
|
|
482
|
+
});
|
|
483
|
+
const jsonSchemaRequired = strings.map(s => s.en_string);
|
|
485
484
|
// call OpenAI
|
|
486
|
-
const resp = yield this.options.completeAdapter.complete(prompt, [], prompt.length * 2
|
|
485
|
+
const resp = yield this.options.completeAdapter.complete(prompt, [], prompt.length * 2, {
|
|
486
|
+
json_schema: {
|
|
487
|
+
name: "translation_response",
|
|
488
|
+
schema: {
|
|
489
|
+
type: "object",
|
|
490
|
+
properties: jsonSchemaProperties,
|
|
491
|
+
required: jsonSchemaRequired,
|
|
492
|
+
},
|
|
493
|
+
},
|
|
494
|
+
});
|
|
487
495
|
process.env.HEAVY_DEBUG && console.log(`🪲🔪LLM resp >> ${prompt.length}, <<${resp.content.length} :\n\n`, JSON.stringify(resp));
|
|
488
496
|
if (resp.error) {
|
|
489
497
|
throw new AiTranslateError(resp.error);
|
|
@@ -495,7 +503,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
495
503
|
// ```
|
|
496
504
|
let res;
|
|
497
505
|
try {
|
|
498
|
-
res = resp.content
|
|
506
|
+
res = resp.content; //.split("```json")[1].split("```")[0];
|
|
499
507
|
}
|
|
500
508
|
catch (e) {
|
|
501
509
|
console.error(`Error in parsing LLM resp: ${resp}\n Prompt was: ${prompt}\n Resp was: ${JSON.stringify(resp)}`);
|
|
@@ -536,10 +544,11 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
536
544
|
}
|
|
537
545
|
// returns translated count
|
|
538
546
|
bulkTranslate(_a) {
|
|
539
|
-
return __awaiter(this, arguments, void 0, function* ({ selectedIds }) {
|
|
547
|
+
return __awaiter(this, arguments, void 0, function* ({ selectedIds, selectedLanguages }) {
|
|
540
548
|
const needToTranslateByLang = {};
|
|
541
549
|
const translations = yield this.adminforth.resource(this.resourceConfig.resourceId).list(Filters.IN(this.primaryKeyFieldName, selectedIds));
|
|
542
|
-
|
|
550
|
+
const languagesToProcess = selectedLanguages || this.options.supportedLanguages;
|
|
551
|
+
for (const lang of languagesToProcess) {
|
|
543
552
|
if (lang === 'en') {
|
|
544
553
|
// all strings are in English, no need to translate
|
|
545
554
|
continue;
|
|
@@ -893,5 +902,34 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
893
902
|
return { record: updatedRecord };
|
|
894
903
|
})
|
|
895
904
|
});
|
|
905
|
+
server.endpoint({
|
|
906
|
+
method: 'POST',
|
|
907
|
+
path: `/plugin/${this.pluginInstanceId}/translate-selected-to-languages`,
|
|
908
|
+
noAuth: false,
|
|
909
|
+
handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, tr }) {
|
|
910
|
+
const selectedLanguages = body.selectedLanguages;
|
|
911
|
+
const selectedIds = body.selectedIds;
|
|
912
|
+
let translatedCount = 0;
|
|
913
|
+
try {
|
|
914
|
+
console.log('🪲translate-selected-to-languages', { selectedLanguages, selectedIds });
|
|
915
|
+
translatedCount = yield this.bulkTranslate({ selectedIds, selectedLanguages });
|
|
916
|
+
}
|
|
917
|
+
catch (e) {
|
|
918
|
+
process.env.HEAVY_DEBUG && console.error('🪲⛔ bulkTranslate error', e);
|
|
919
|
+
if (e instanceof AiTranslateError) {
|
|
920
|
+
return { ok: false, error: e.message };
|
|
921
|
+
}
|
|
922
|
+
throw e;
|
|
923
|
+
}
|
|
924
|
+
this.updateUntranslatedMenuBadge();
|
|
925
|
+
return {
|
|
926
|
+
ok: true,
|
|
927
|
+
error: undefined,
|
|
928
|
+
successMessage: yield tr(`Translated {count} items`, 'backend', {
|
|
929
|
+
count: translatedCount,
|
|
930
|
+
}),
|
|
931
|
+
};
|
|
932
|
+
})
|
|
933
|
+
});
|
|
896
934
|
}
|
|
897
935
|
}
|
package/index.ts
CHANGED
|
@@ -8,7 +8,7 @@ import fs from 'fs-extra';
|
|
|
8
8
|
import chokidar from 'chokidar';
|
|
9
9
|
import { AsyncQueue } from '@sapphire/async-queue';
|
|
10
10
|
import getFlagEmoji from 'country-flag-svg';
|
|
11
|
-
|
|
11
|
+
import { parse } from 'bcp-47';
|
|
12
12
|
|
|
13
13
|
const processFrontendMessagesQueue = new AsyncQueue();
|
|
14
14
|
|
|
@@ -52,15 +52,20 @@ function getPrimaryLanguageCode(langCode: SupportedLanguage): string {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function isValidSupportedLanguageTag(langCode: SupportedLanguage): boolean {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
try {
|
|
56
|
+
const schema = parse(String(langCode),
|
|
57
|
+
{
|
|
58
|
+
normalize: true,
|
|
59
|
+
warning: (reason, code, offset) => {
|
|
60
|
+
console.warn(`Warning in validating language tag ${langCode}: reason=${reason}, code=${code}, offset=${offset}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
const isValid = schema && schema.language && schema.language.length === 2;
|
|
65
|
+
return !!isValid;
|
|
66
|
+
} catch (e) {
|
|
57
67
|
return false;
|
|
58
68
|
}
|
|
59
|
-
if (!region) {
|
|
60
|
-
return true;
|
|
61
|
-
}
|
|
62
|
-
const regionUpper = region.toUpperCase();
|
|
63
|
-
return /^[A-Z]{2}$/.test(regionUpper) && (regionUpper in iso31661Alpha2ToAlpha3);
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
|
|
@@ -167,6 +172,14 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
167
172
|
}
|
|
168
173
|
});
|
|
169
174
|
|
|
175
|
+
if (this.options.translateLangAsBCP47Code) {
|
|
176
|
+
for (const [lang, bcp47] of Object.entries(this.options.translateLangAsBCP47Code)) {
|
|
177
|
+
if (!this.options.supportedLanguages.includes(lang as SupportedLanguage)) {
|
|
178
|
+
throw new Error(`Invalid language code ${lang} in translateLangAsBCP47Code. It must be one of the supportedLanguages.`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
this.externalAppOnly = this.options.externalAppOnly === true;
|
|
171
184
|
|
|
172
185
|
// find primary key field
|
|
@@ -435,46 +448,27 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
435
448
|
}
|
|
436
449
|
|
|
437
450
|
// add bulk action
|
|
438
|
-
|
|
439
|
-
|
|
451
|
+
|
|
452
|
+
const pageInjection = {
|
|
453
|
+
file: this.componentPath('BulkActionButton.vue'),
|
|
454
|
+
meta: {
|
|
455
|
+
supportedLanguages: this.options.supportedLanguages,
|
|
456
|
+
pluginInstanceId: this.pluginInstanceId,
|
|
457
|
+
}
|
|
440
458
|
}
|
|
441
|
-
|
|
442
|
-
if (
|
|
443
|
-
resourceConfig.options.
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
},
|
|
455
|
-
action: async ({ selectedIds, tr }) => {
|
|
456
|
-
let translatedCount = 0;
|
|
457
|
-
try {
|
|
458
|
-
translatedCount = await this.bulkTranslate({ selectedIds });
|
|
459
|
-
} catch (e) {
|
|
460
|
-
process.env.HEAVY_DEBUG && console.error('🪲⛔ bulkTranslate error', e);
|
|
461
|
-
if (e instanceof AiTranslateError) {
|
|
462
|
-
return { ok: false, error: e.message };
|
|
463
|
-
}
|
|
464
|
-
throw e;
|
|
465
|
-
}
|
|
466
|
-
this.updateUntranslatedMenuBadge();
|
|
467
|
-
return {
|
|
468
|
-
ok: true,
|
|
469
|
-
error: undefined,
|
|
470
|
-
successMessage: await tr(`Translated {count} items`, 'backend', {
|
|
471
|
-
count: translatedCount,
|
|
472
|
-
}),
|
|
473
|
-
};
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
);
|
|
477
|
-
};
|
|
459
|
+
|
|
460
|
+
if (!resourceConfig.options.pageInjections) {
|
|
461
|
+
resourceConfig.options.pageInjections = {};
|
|
462
|
+
}
|
|
463
|
+
if (!resourceConfig.options.pageInjections.list) {
|
|
464
|
+
resourceConfig.options.pageInjections.list = {};
|
|
465
|
+
}
|
|
466
|
+
if (!resourceConfig.options.pageInjections.list.beforeActionButtons) {
|
|
467
|
+
resourceConfig.options.pageInjections.list.beforeActionButtons = [];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
(resourceConfig.options.pageInjections.list.beforeActionButtons as AdminForthComponentDeclaration[]).push(pageInjection);
|
|
471
|
+
|
|
478
472
|
|
|
479
473
|
// if there is menu item with resourceId, add .badge function showing number of untranslated strings
|
|
480
474
|
const addBadgeCountToMenuItem = (menuItem: AdminForthConfigMenuItem) => {
|
|
@@ -512,6 +506,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
512
506
|
return [];
|
|
513
507
|
}
|
|
514
508
|
|
|
509
|
+
const replacedLanguageCodeForTranslations = this.options.translateLangAsBCP47Code && langIsoCode.length === 2 ? this.options.translateLangAsBCP47Code[langIsoCode as any] : null;
|
|
515
510
|
if (strings.length > maxKeysInOneReq) {
|
|
516
511
|
let totalTranslated = [];
|
|
517
512
|
for (let i = 0; i < strings.length; i += maxKeysInOneReq) {
|
|
@@ -522,14 +517,15 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
522
517
|
}
|
|
523
518
|
return totalTranslated;
|
|
524
519
|
}
|
|
520
|
+
const langCode = replacedLanguageCodeForTranslations ? replacedLanguageCodeForTranslations : langIsoCode;
|
|
525
521
|
const lang = langIsoCode;
|
|
526
522
|
const primaryLang = getPrimaryLanguageCode(lang);
|
|
527
523
|
const langName = iso6391.getName(primaryLang);
|
|
528
524
|
const requestSlavicPlurals = Object.keys(SLAVIC_PLURAL_EXAMPLES).includes(primaryLang) && plurals;
|
|
529
525
|
const region = String(lang).split('-')[1]?.toUpperCase() || '';
|
|
530
526
|
const prompt = `
|
|
531
|
-
I need to translate strings in JSON to ${langName} language
|
|
532
|
-
${region ? `Use the regional conventions for ${
|
|
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.
|
|
528
|
+
${region ? `Use the regional conventions for ${langCode} (region ${region}), including spelling, punctuation, and formatting.` : ''}
|
|
533
529
|
${requestSlavicPlurals ? `You should provide 4 slavic forms (in format "zero count | singular count | 2-4 | 5+") e.g. "apple | apples" should become "${SLAVIC_PLURAL_EXAMPLES[lang]}"` : ''}
|
|
534
530
|
Keep keys, as is, write translation into values! If keys have variables (in curly brackets), then translated strings should have them as well (variables itself should not be translated). Here are the strings:
|
|
535
531
|
|
|
@@ -543,11 +539,31 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
543
539
|
\`\`\`
|
|
544
540
|
`;
|
|
545
541
|
|
|
542
|
+
const jsonSchemaProperties = {};
|
|
543
|
+
strings.forEach(s => {
|
|
544
|
+
jsonSchemaProperties[s.en_string] = {
|
|
545
|
+
type: 'string',
|
|
546
|
+
minLength: 1,
|
|
547
|
+
};
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const jsonSchemaRequired = strings.map(s => s.en_string);
|
|
551
|
+
|
|
546
552
|
// call OpenAI
|
|
547
553
|
const resp = await this.options.completeAdapter.complete(
|
|
548
554
|
prompt,
|
|
549
555
|
[],
|
|
550
556
|
prompt.length * 2,
|
|
557
|
+
{
|
|
558
|
+
json_schema: {
|
|
559
|
+
name: "translation_response",
|
|
560
|
+
schema: {
|
|
561
|
+
type: "object",
|
|
562
|
+
properties: jsonSchemaProperties,
|
|
563
|
+
required: jsonSchemaRequired,
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
}
|
|
551
567
|
);
|
|
552
568
|
|
|
553
569
|
process.env.HEAVY_DEBUG && console.log(`🪲🔪LLM resp >> ${prompt.length}, <<${resp.content.length} :\n\n`, JSON.stringify(resp));
|
|
@@ -563,7 +579,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
563
579
|
// ```
|
|
564
580
|
let res;
|
|
565
581
|
try {
|
|
566
|
-
res = resp.content
|
|
582
|
+
res = resp.content//.split("```json")[1].split("```")[0];
|
|
567
583
|
} catch (e) {
|
|
568
584
|
console.error(`Error in parsing LLM resp: ${resp}\n Prompt was: ${prompt}\n Resp was: ${JSON.stringify(resp)}`, );
|
|
569
585
|
return [];
|
|
@@ -608,7 +624,7 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
608
624
|
}
|
|
609
625
|
|
|
610
626
|
// returns translated count
|
|
611
|
-
async bulkTranslate({ selectedIds }: { selectedIds: string[] }): Promise<number> {
|
|
627
|
+
async bulkTranslate({ selectedIds, selectedLanguages }: { selectedIds: string[], selectedLanguages?: SupportedLanguage[] }): Promise<number> {
|
|
612
628
|
|
|
613
629
|
const needToTranslateByLang : Partial<
|
|
614
630
|
Record<
|
|
@@ -621,8 +637,8 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
621
637
|
> = {};
|
|
622
638
|
|
|
623
639
|
const translations = await this.adminforth.resource(this.resourceConfig.resourceId).list(Filters.IN(this.primaryKeyFieldName, selectedIds));
|
|
624
|
-
|
|
625
|
-
for (const lang of
|
|
640
|
+
const languagesToProcess = selectedLanguages || this.options.supportedLanguages;
|
|
641
|
+
for (const lang of languagesToProcess) {
|
|
626
642
|
if (lang === 'en') {
|
|
627
643
|
// all strings are in English, no need to translate
|
|
628
644
|
continue;
|
|
@@ -1052,6 +1068,36 @@ export default class I18nPlugin extends AdminForthPlugin {
|
|
|
1052
1068
|
}
|
|
1053
1069
|
});
|
|
1054
1070
|
|
|
1071
|
+
server.endpoint({
|
|
1072
|
+
method: 'POST',
|
|
1073
|
+
path: `/plugin/${this.pluginInstanceId}/translate-selected-to-languages`,
|
|
1074
|
+
noAuth: false,
|
|
1075
|
+
handler: async ({ body, tr }) => {
|
|
1076
|
+
const selectedLanguages = body.selectedLanguages;
|
|
1077
|
+
const selectedIds = body.selectedIds;
|
|
1078
|
+
|
|
1079
|
+
let translatedCount = 0;
|
|
1080
|
+
try {
|
|
1081
|
+
console.log('🪲translate-selected-to-languages', { selectedLanguages, selectedIds });
|
|
1082
|
+
translatedCount = await this.bulkTranslate({ selectedIds, selectedLanguages });
|
|
1083
|
+
} catch (e) {
|
|
1084
|
+
process.env.HEAVY_DEBUG && console.error('🪲⛔ bulkTranslate error', e);
|
|
1085
|
+
if (e instanceof AiTranslateError) {
|
|
1086
|
+
return { ok: false, error: e.message };
|
|
1087
|
+
}
|
|
1088
|
+
throw e;
|
|
1089
|
+
}
|
|
1090
|
+
this.updateUntranslatedMenuBadge();
|
|
1091
|
+
return {
|
|
1092
|
+
ok: true,
|
|
1093
|
+
error: undefined,
|
|
1094
|
+
successMessage: await tr(`Translated {count} items`, 'backend', {
|
|
1095
|
+
count: translatedCount,
|
|
1096
|
+
}),
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
});
|
|
1100
|
+
|
|
1055
1101
|
}
|
|
1056
1102
|
|
|
1057
1103
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adminforth/i18n",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@aws-sdk/client-ses": "^3.654.0",
|
|
32
32
|
"@sapphire/async-queue": "^1.5.5",
|
|
33
|
+
"bcp-47": "^2.1.0",
|
|
33
34
|
"chokidar": "^4.0.1",
|
|
34
35
|
"country-flag-svg": "^1.0.19",
|
|
35
36
|
"fs-extra": "^11.3.2",
|
package/types.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type SupportedLanguage = LanguageCode | Bcp47LanguageTag;
|
|
|
10
10
|
|
|
11
11
|
export interface PluginOptions {
|
|
12
12
|
|
|
13
|
-
/* List of ISO 639-1 language codes
|
|
13
|
+
/* List of language codes which you want to support. Can be either short ISO 639-1 language codes or/and BCP47 tags */
|
|
14
14
|
supportedLanguages: SupportedLanguage[];
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -68,4 +68,10 @@ export interface PluginOptions {
|
|
|
68
68
|
* Defaults to 'en' if not specified.
|
|
69
69
|
*/
|
|
70
70
|
primaryLanguage?: SupportedLanguage;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Ask translator to treat some code from supportedLanguages as exact BCP47 tag. Read docs for details.
|
|
74
|
+
* key - one of the values form supportedLanguages, value -BCP47 tag
|
|
75
|
+
*/
|
|
76
|
+
translateLangAsBCP47Code?: Partial<Record<LanguageCode, Bcp47LanguageTag>>;
|
|
71
77
|
}
|