@adminforth/i18n 2.4.0 → 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 CHANGED
@@ -11,19 +11,21 @@ const REGION_CODE_RE = /^[A-Z]{2}$/;
11
11
  export const SOURCE_LANGUAGE = 'en';
12
12
  export const PLURAL_SEPARATOR = '|';
13
13
  export const SLAVIC_PLURAL_FORMS = 4;
14
+ export const SLAVIC_PLURAL_FORMS_HINT = 'zero | singular | 2-4 | 5+';
14
15
 
16
+ // forms in SLAVIC_PLURAL_FORMS_HINT order
15
17
  export const SLAVIC_PLURAL_EXAMPLES = {
16
- uk: 'яблук | яблуко | яблука | яблук', // zero | singular | 2-4 | 5+
17
- bg: 'ябълки | ябълка | ябълки | ябълки', // zero | singular | 2-4 | 5+
18
- cs: 'jablek | jablko | jablka | jablek', // zero | singular | 2-4 | 5+
19
- hr: 'jabuka | jabuka | jabuke | jabuka', // zero | singular | 2-4 | 5+
20
- mk: 'јаболка | јаболко | јаболка | јаболка', // zero | singular | 2-4 | 5+
21
- pl: 'jabłek | jabłko | jabłka | jabłek', // zero | singular | 2-4 | 5+
22
- sk: 'jabĺk | jablko | jablká | jabĺk', // zero | singular | 2-4 | 5+
23
- sl: 'jabolk | jabolko | jabolka | jabolk', // zero | singular | 2-4 | 5+
24
- sr: 'јабука | јабука | јабуке | јабука', // zero | singular | 2-4 | 5+
25
- be: 'яблыкаў | яблык | яблыкі | яблыкаў', // zero | singular | 2-4 | 5+
26
- ru: 'яблок | яблоко | яблока | яблок', // zero | singular | 2-4 | 5+
18
+ uk: 'яблук | яблуко | яблука | яблук',
19
+ bg: 'ябълки | ябълка | ябълки | ябълки',
20
+ cs: 'jablek | jablko | jablka | jablek',
21
+ hr: 'jabuka | jabuka | jabuke | jabuka',
22
+ mk: 'јаболка | јаболко | јаболка | јаболка',
23
+ pl: 'jabłek | jabłko | jabłka | jabłek',
24
+ sk: 'jabĺk | jablko | jablká | jabĺk',
25
+ sl: 'jabolk | jabolko | jabolka | jabolk',
26
+ sr: 'јабука | јабука | јабуке | јабука',
27
+ be: 'яблыкаў | яблык | яблыкі | яблыкаў',
28
+ ru: 'яблок | яблоко | яблока | яблок',
27
29
  };
28
30
 
29
31
  const countryISO31661ByLangISO6391 = {
@@ -36,7 +38,142 @@ const countryISO31661ByLangISO6391 = {
36
38
  uk: 'ua', // Ukrainian → Ukraine
37
39
  ur: 'pk', // Urdu → Pakistan
38
40
  sr: 'rs', // Serbian → Serbia
39
- da: 'dk' // Danish → Denmark
41
+ da: 'dk', // Danish → Denmark
42
+ sv: 'se', // Swedish → Sweden
43
+ he: 'il', // Hebrew → Israel
44
+ el: 'gr', // Greek → Greece
45
+ cs: 'cz', // Czech → Czech Republic
46
+ vi: 'vn', // Vietnamese → Vietnam
47
+ aa: 'et', // Afar → Ethiopia
48
+ ab: 'ge', // Abkhaz → Georgia
49
+ ae: 'ir', // Avestan → Iran
50
+ af: 'za', // Afrikaans → South Africa
51
+ ak: 'gh', // Akan → Ghana
52
+ am: 'et', // Amharic → Ethiopia
53
+ an: 'es', // Aragonese → Spain
54
+ as: 'in', // Assamese → India
55
+ av: 'ru', // Avaric → Russia
56
+ ay: 'bo', // Aymara → Bolivia
57
+ ba: 'ru', // Bashkir → Russia
58
+ be: 'by', // Belarusian → Belarus
59
+ bi: 'vu', // Bislama → Vanuatu
60
+ bm: 'ml', // Bambara → Mali
61
+ bn: 'bd', // Bengali → Bangladesh
62
+ bo: 'cn', // Tibetan → China
63
+ br: 'fr', // Breton → France
64
+ bs: 'ba', // Bosnian → Bosnia & Herzegovina
65
+ ca: 'es', // Catalan → Spain
66
+ ce: 'ru', // Chechen → Russia
67
+ ch: 'gu', // Chamorro → Guam
68
+ co: 'fr', // Corsican → France
69
+ cr: 'ca', // Cree → Canada
70
+ cu: 'ru', // Old Church Slavonic → Russia
71
+ cv: 'ru', // Chuvash → Russia
72
+ cy: 'gb', // Welsh → United Kingdom
73
+ dv: 'mv', // Divehi → Maldives
74
+ dz: 'bt', // Dzongkha → Bhutan
75
+ ee: 'gh', // Ewe → Ghana
76
+ et: 'ee', // Estonian → Estonia
77
+ eu: 'es', // Basque → Spain
78
+ fa: 'ir', // Persian → Iran
79
+ ff: 'sn', // Fula → Senegal
80
+ fy: 'nl', // Western Frisian → Netherlands
81
+ ga: 'ie', // Irish → Ireland
82
+ gd: 'gb', // Scottish Gaelic → United Kingdom
83
+ gl: 'es', // Galician → Spain
84
+ gn: 'py', // Guaraní → Paraguay
85
+ gu: 'in', // Gujarati → India
86
+ gv: 'im', // Manx → Isle of Man
87
+ ha: 'ng', // Hausa → Nigeria
88
+ ho: 'pg', // Hiri Motu → Papua New Guinea
89
+ hy: 'am', // Armenian → Armenia
90
+ hz: 'na', // Herero → Namibia
91
+ ie: 'ee', // Interlingue → Estonia
92
+ ig: 'ng', // Igbo → Nigeria
93
+ ii: 'cn', // Nuosu → China
94
+ ik: 'us', // Inupiaq → United States
95
+ iu: 'ca', // Inuktitut → Canada
96
+ jv: 'id', // Javanese → Indonesia
97
+ ka: 'ge', // Georgian → Georgia
98
+ kg: 'cd', // Kongo → Congo - Kinshasa
99
+ ki: 'ke', // Kikuyu → Kenya
100
+ kj: 'na', // Kwanyama → Namibia
101
+ kk: 'kz', // Kazakh → Kazakhstan
102
+ kl: 'gl', // Kalaallisut → Greenland
103
+ km: 'kh', // Khmer → Cambodia
104
+ kn: 'in', // Kannada → India
105
+ kr: 'ng', // Kanuri → Nigeria
106
+ ks: 'in', // Kashmiri → India
107
+ ku: 'tr', // Kurdish → Türkiye
108
+ kv: 'ru', // Komi → Russia
109
+ kw: 'gb', // Cornish → United Kingdom
110
+ ky: 'kg', // Kyrgyz → Kyrgyzstan
111
+ la: 'va', // Latin → Vatican City
112
+ lb: 'lu', // Luxembourgish → Luxembourg
113
+ lg: 'ug', // Ganda → Uganda
114
+ li: 'nl', // Limburgish → Netherlands
115
+ ln: 'cd', // Lingala → Congo - Kinshasa
116
+ lo: 'la', // Lao → Laos
117
+ lu: 'cd', // Luba-Katanga → Congo - Kinshasa
118
+ mi: 'nz', // Māori → New Zealand
119
+ ml: 'in', // Malayalam → India
120
+ mr: 'in', // Marathi → India
121
+ ms: 'my', // Malay → Malaysia
122
+ my: 'mm', // Burmese → Myanmar (Burma)
123
+ na: 'nr', // Nauru → Nauru
124
+ nb: 'no', // Norwegian Bokmål → Norway
125
+ nd: 'zw', // Northern Ndebele → Zimbabwe
126
+ ne: 'np', // Nepali → Nepal
127
+ ng: 'na', // Ndonga → Namibia
128
+ nn: 'no', // Norwegian Nynorsk → Norway
129
+ nr: 'za', // Southern Ndebele → South Africa
130
+ nv: 'us', // Navajo → United States
131
+ ny: 'mw', // Chichewa → Malawi
132
+ oc: 'fr', // Occitan → France
133
+ oj: 'ca', // Ojibwe → Canada
134
+ om: 'et', // Oromo → Ethiopia
135
+ or: 'in', // Oriya → India
136
+ os: 'ge', // Ossetian → Georgia
137
+ pa: 'in', // Panjabi → India
138
+ pi: 'gb', // Pāli → United Kingdom
139
+ ps: 'af', // Pashto → Afghanistan
140
+ qu: 'pe', // Quechua → Peru
141
+ rm: 'ch', // Romansh → Switzerland
142
+ rn: 'bi', // Kirundi → Burundi
143
+ sa: 'in', // Sanskrit → India
144
+ sc: 'it', // Sardinian → Italy
145
+ sd: 'pk', // Sindhi → Pakistan
146
+ se: 'no', // Northern Sami → Norway
147
+ sg: 'cf', // Sango → Central African Republic
148
+ si: 'lk', // Sinhala → Sri Lanka
149
+ sl: 'si', // Slovenian → Slovenia
150
+ sm: 'ws', // Samoan → Samoa
151
+ sn: 'zw', // Shona → Zimbabwe
152
+ sq: 'al', // Albanian → Albania
153
+ ss: 'za', // Swati → South Africa
154
+ st: 'za', // Southern Sotho → South Africa
155
+ su: 'id', // Sundanese → Indonesia
156
+ sw: 'tz', // Swahili → Tanzania
157
+ ta: 'in', // Tamil → India
158
+ te: 'in', // Telugu → India
159
+ tg: 'tj', // Tajik → Tajikistan
160
+ ti: 'et', // Tigrinya → Ethiopia
161
+ tk: 'tm', // Turkmen → Turkmenistan
162
+ tl: 'ph', // Tagalog → Philippines
163
+ tn: 'za', // Tswana → South Africa
164
+ ts: 'za', // Tsonga → South Africa
165
+ tt: 'ru', // Tatar → Russia
166
+ tw: 'gh', // Twi → Ghana
167
+ ty: 'pf', // Tahitian → French Polynesia
168
+ ug: 'cn', // Uyghur → China
169
+ ve: 'za', // Venda → South Africa
170
+ wa: 'be', // Walloon → Belgium
171
+ wo: 'sn', // Wolof → Senegal
172
+ xh: 'za', // Xhosa → South Africa
173
+ yi: 'ua', // Yiddish → Ukraine
174
+ yo: 'ng', // Yoruba → Nigeria
175
+ za: 'cn', // Zhuang → China
176
+ zu: 'za', // Zulu → South Africa
40
177
  };
41
178
 
42
179
  export function getCountryCodeFromLangCode(lang: SupportedLanguage) {
@@ -98,6 +235,31 @@ export abstract class I18nBase<O extends I18nBaseOptions = I18nBaseOptions> exte
98
235
 
99
236
  abstract getCategoryTranslations(category: string, lang: string): Promise<Record<string, string>>;
100
237
 
238
+ // languages translations are written for: strings in source language are the keys themselves
239
+ targetLanguages(): SupportedLanguage[] {
240
+ return this.options.supportedLanguages.filter((lang) => lang !== SOURCE_LANGUAGE);
241
+ }
242
+
243
+ // empty translation means "not translated yet", original string is shown instead
244
+ fallbackToOriginal(messages: Record<string, string>): Record<string, string> {
245
+ return Object.fromEntries(Object.entries(messages).map(([key, value]) => [key, value || key]));
246
+ }
247
+
248
+ /**
249
+ * Translates backend strings (menu, resource labels, errors) with adminforth.tr,
250
+ * subclass provides only translation lookup in its storage for already resolved language
251
+ */
252
+ installBackendTranslator(adminforth: IAdminForth, lookup: (msg: string, category: string, lang: string) => Promise<string | undefined>) {
253
+ adminforth.tr = async (msg, category, lang, params, pluralizationNumber) => {
254
+ if (!msg) {
255
+ return msg;
256
+ }
257
+ const resolvedLang = this.resolveLanguage(lang);
258
+ const translation = await lookup(msg, category, resolvedLang);
259
+ return this.formatTranslation(msg, translation || msg, resolvedLang, params, pluralizationNumber);
260
+ };
261
+ }
262
+
101
263
  // validate each supported language: ISO 639-1 or BCP-47 with region (e.g., en-GB)
102
264
  validateSupportedLanguages() {
103
265
  this.options.supportedLanguages.forEach((lang) => {
@@ -118,6 +280,7 @@ export abstract class I18nBase<O extends I18nBaseOptions = I18nBaseOptions> exte
118
280
  code: lang,
119
281
  // lang name on on language native name
120
282
  name: iso6391.getNativeName(getPrimaryLanguageCode(lang)),
283
+ countryCode: getCountryCodeFromLangCode(lang),
121
284
  }
122
285
  ))
123
286
  };
package/build.log CHANGED
@@ -20,5 +20,5 @@ custom/package.json
20
20
  custom/pnpm-lock.yaml
21
21
  custom/tsconfig.json
22
22
 
23
- sent 48,201 bytes received 305 bytes 32,337.33 bytes/sec
24
- total size is 47,033 speedup is 0.97
23
+ sent 47,235 bytes received 305 bytes 95,080.00 bytes/sec
24
+ total size is 46,083 speedup is 0.97
@@ -41,7 +41,7 @@
41
41
  <div class="group hover:bg-gray-100 dark:hover:bg-gray-600 px-2 py-2 flex items-center cursor-pointer" v-for="(index, lang) in checkedLanguages" :key="lang" @click="toggleLanguage(lang)">
42
42
  <Checkbox v-model="checkedLanguages[lang]" />
43
43
  <span class="flag-icon mr-2"
44
- :class="`flag-icon-${getCountryCodeFromLangCode(lang)}`"
44
+ :class="`flag-icon-${props.meta.countryCodes[lang]}`"
45
45
  ></span>
46
46
  <span class="group-hover:text-gray-900 text-gray-600 dark:text-white dark:group-hover:text-gray-200">{{ ISO6391.getName(lang.slice(0,2)) }} ({{ lang }})</span>
47
47
  </div>
@@ -58,7 +58,6 @@
58
58
  import { computed, onMounted, ref, onUnmounted } from 'vue';
59
59
  import { callAdminForthApi } from '@/utils';
60
60
  import { useAdminforth } from '@/adminforth';
61
- import { getCountryCodeFromLangCode } from './langCommon';
62
61
  import { getName, overwrite } from 'country-list';
63
62
  import ISO6391 from 'iso-639-1';
64
63
  import 'flag-icon-css/css/flag-icons.min.css';
@@ -82,6 +81,7 @@
82
81
  adminUser: Record<string, any>;
83
82
  meta: {
84
83
  supportedLanguages: string[];
84
+ countryCodes: Record<string, string>;
85
85
  pluginInstanceId: string;
86
86
  };
87
87
  clearCheckboxes: () => void;
@@ -10,7 +10,7 @@
10
10
  <div>
11
11
  <span class="mr-1">
12
12
  <span class="flag-icon"
13
- :class="`flag-icon-${getCountryCodeFromLangCode(selectedOption.value)}`"
13
+ :class="`flag-icon-${selectedOption.countryCode}`"
14
14
  ></span>
15
15
  </span>
16
16
  <span>{{ selectedOption.label }}</span>
@@ -32,7 +32,7 @@
32
32
  >
33
33
  <span class="mr-1">
34
34
  <span class="flag-icon"
35
- :class="`flag-icon-${getCountryCodeFromLangCode(option.value)}`"
35
+ :class="`flag-icon-${option.countryCode}`"
36
36
  ></span>
37
37
  </span>
38
38
  <span>{{ option.label }}</span>
@@ -48,7 +48,7 @@
48
48
  import 'flag-icon-css/css/flag-icons.min.css';
49
49
  import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
50
50
 
51
- import { setLang, getCountryCodeFromLangCode, getLocalLang, setLocalLang } from './langCommon';
51
+ import { setLang, getLocalLang, setLocalLang, getLanguageOptions } from './langCommon';
52
52
 
53
53
  import { computed, ref, onMounted, watch } from 'vue';
54
54
  import { useI18n } from 'vue-i18n';
@@ -68,15 +68,7 @@ function doChangeLang(lang) {
68
68
  }
69
69
 
70
70
 
71
- const options = computed(() => {
72
- return props.meta.supportedLanguages.map((lang) => {
73
- const region = String(lang.code).split('-')[1]?.toUpperCase();
74
- return {
75
- value: lang.code,
76
- label: region ? `${lang.name} (${region})` : lang.name,
77
- };
78
- });
79
- });
71
+ const options = computed(() => getLanguageOptions(props.meta.supportedLanguages));
80
72
 
81
73
  const selectedOption = computed(() => {
82
74
  const val = options.value.find((option) => option.value === selectedLanguage.value);
@@ -9,7 +9,7 @@
9
9
  <template #item="{ option }">
10
10
  <span class="mr-1">
11
11
  <span class="flag-icon"
12
- :class="`flag-icon-${getCountryCodeFromLangCode(option.value)}`"
12
+ :class="`flag-icon-${option.countryCode}`"
13
13
  ></span>
14
14
 
15
15
  </span>
@@ -19,7 +19,7 @@
19
19
  <template #selected-item="{option}">
20
20
  <span class="mr-1">
21
21
  <span class="flag-icon"
22
- :class="`flag-icon-${getCountryCodeFromLangCode(option.value)}`"
22
+ :class="`flag-icon-${option.countryCode}`"
23
23
  ></span>
24
24
  </span>
25
25
  <span>{{ option.label }}</span>
@@ -31,7 +31,7 @@
31
31
  <script setup>
32
32
  import Select from '@/afcl/Select.vue';
33
33
  import 'flag-icon-css/css/flag-icons.min.css';
34
- import { setLang, getCountryCodeFromLangCode, getLocalLang } from './langCommon';
34
+ import { setLang, getLocalLang, getLanguageOptions } from './langCommon';
35
35
  import { useCoreStore } from '@/stores/core';
36
36
 
37
37
  import { computed, ref, onMounted, watch } from 'vue';
@@ -52,15 +52,7 @@ watch(() => selectedLanguage.value, async (newVal) => {
52
52
  });
53
53
 
54
54
 
55
- const options = computed(() => {
56
- return props.meta.supportedLanguages.map((lang) => {
57
- const region = String(lang.code).split('-')[1]?.toUpperCase();
58
- return {
59
- value: lang.code,
60
- label: region ? `${lang.name} (${region})` : lang.name,
61
- };
62
- });
63
- });
55
+ const options = computed(() => getLanguageOptions(props.meta.supportedLanguages));
64
56
 
65
57
  onMounted(() => {
66
58
  selectedLanguage.value = getLocalLang(props.meta.supportedLanguages, props.meta.primaryLanguage);
@@ -22,7 +22,7 @@
22
22
  </span>
23
23
  <div class="flex items-center gap-2 shrink-0">
24
24
  <span class="flag-icon"
25
- :class="`flag-icon-${getCountryCodeFromLangCode(task.state?.lang)}`"
25
+ :class="`flag-icon-${task.state?.countryCode}`"
26
26
  ></span>
27
27
  <component
28
28
  :is="getCustomComponent({file: '@@/plugins/BackgroundJobsPlugin/StateToIcon.vue'})"
@@ -61,7 +61,6 @@
61
61
  import { useI18n } from 'vue-i18n'
62
62
  import { computed, onMounted, onUnmounted, ref } from 'vue';
63
63
  import websocket from '@/websocket';
64
- import { getCountryCodeFromLangCode } from './langCommon';
65
64
  import { getCustomComponent } from '@/utils';
66
65
  import { Button, Spinner } from '@/afcl';
67
66
  import { IconAngleLeftOutline, IconAngleRightOutline } from '@iconify-prerendered/vue-flowbite';
@@ -106,7 +105,7 @@ async function loadPage(targetPage: number) {
106
105
  try {
107
106
  for (let retry = 0; retry < retries; retry++) {
108
107
  try {
109
- const res = await props.getJobTasks(PAGE_SIZE, targetPage * PAGE_SIZE, ['lang', 'error', 'taskName', 'failedToTranslate']);
108
+ const res = await props.getJobTasks(PAGE_SIZE, targetPage * PAGE_SIZE, ['lang', 'countryCode', 'error', 'taskName', 'failedToTranslate']);
110
109
  if (res && res.length > 0) {
111
110
  translationTasks.value = res;
112
111
  page.value = targetPage;
@@ -65,32 +65,20 @@ export async function setLang({ setLocaleMessage, locale }: any, pluginInstanceI
65
65
  setLocalLang(langIso);
66
66
  }
67
67
 
68
- // only remap the country code for the languages where language code is different from the country code
69
- // don't include es: es, fr: fr, etc, only include the ones where language code is different from the country code
70
- const countryISO31661ByLangISO6391 = {
71
- en: 'us', // English → United States
72
- zh: 'cn', // Chinese → China
73
- hi: 'in', // Hindi → India
74
- ar: 'sa', // Arabic → Saudi Arabia
75
- ko: 'kr', // Korean → South Korea
76
- ja: 'jp', // Japanese → Japan
77
- uk: 'ua', // Ukrainian → Ukraine
78
- ur: 'pk', // Urdu → Pakistan
79
- sr: 'rs', // Serbian → Serbia
80
- da: 'dk' // Danish → Denmark
81
- };
82
-
83
- export function getCountryCodeFromLangCode(langCode) {
84
- const [primary, region] = String(langCode).split('-');
85
- if (region && /^[A-Za-z]{2}$/.test(region)) {
86
- return region.toLowerCase();
87
- }
88
- return countryISO31661ByLangISO6391[primary] || primary;
89
- }
90
-
91
-
92
68
  const LS_LANG_KEY = `afLanguage`;
93
69
 
70
+ // options for language selectors, region of BCP-47 tag is added to native language name, e.g. "Português (BR)"
71
+ export function getLanguageOptions(supportedLanguages: { code: string, name: string, countryCode: string }[]) {
72
+ return supportedLanguages.map((lang) => {
73
+ const region = String(lang.code).split('-')[1]?.toUpperCase();
74
+ return {
75
+ value: lang.code,
76
+ countryCode: lang.countryCode,
77
+ label: region ? `${lang.name} (${region})` : lang.name,
78
+ };
79
+ });
80
+ }
81
+
94
82
  export function getLocalLang(supportedLanguages: {code}[], primaryLanguage?: SupportedLanguage): string {
95
83
  let lsLang = localStorage.getItem(LS_LANG_KEY);
96
84
  // if someone screwed up the local storage or we stopped language support, lets check if it is in supported languages
package/dist/base.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { I18nBaseOptions, SupportedLanguage } from './types.js';
4
4
  export declare const SOURCE_LANGUAGE = "en";
5
5
  export declare const PLURAL_SEPARATOR = "|";
6
6
  export declare const SLAVIC_PLURAL_FORMS = 4;
7
+ export declare const SLAVIC_PLURAL_FORMS_HINT = "zero | singular | 2-4 | 5+";
7
8
  export declare const SLAVIC_PLURAL_EXAMPLES: {
8
9
  uk: string;
9
10
  bg: string;
@@ -34,6 +35,13 @@ export declare abstract class I18nBase<O extends I18nBaseOptions = I18nBaseOptio
34
35
  constructor(options: O);
35
36
  instanceUniqueRepresentation(): string;
36
37
  abstract getCategoryTranslations(category: string, lang: string): Promise<Record<string, string>>;
38
+ targetLanguages(): SupportedLanguage[];
39
+ fallbackToOriginal(messages: Record<string, string>): Record<string, string>;
40
+ /**
41
+ * Translates backend strings (menu, resource labels, errors) with adminforth.tr,
42
+ * subclass provides only translation lookup in its storage for already resolved language
43
+ */
44
+ installBackendTranslator(adminforth: IAdminForth, lookup: (msg: string, category: string, lang: string) => Promise<string | undefined>): void;
37
45
  validateSupportedLanguages(): void;
38
46
  injectLanguageSelectors(adminforth: IAdminForth): void;
39
47
  resolveLanguage(lang: string): string;
package/dist/base.js CHANGED
@@ -16,18 +16,20 @@ const REGION_CODE_RE = /^[A-Z]{2}$/;
16
16
  export const SOURCE_LANGUAGE = 'en';
17
17
  export const PLURAL_SEPARATOR = '|';
18
18
  export const SLAVIC_PLURAL_FORMS = 4;
19
+ export const SLAVIC_PLURAL_FORMS_HINT = 'zero | singular | 2-4 | 5+';
20
+ // forms in SLAVIC_PLURAL_FORMS_HINT order
19
21
  export const SLAVIC_PLURAL_EXAMPLES = {
20
- uk: 'яблук | яблуко | яблука | яблук', // zero | singular | 2-4 | 5+
21
- bg: 'ябълки | ябълка | ябълки | ябълки', // zero | singular | 2-4 | 5+
22
- cs: 'jablek | jablko | jablka | jablek', // zero | singular | 2-4 | 5+
23
- hr: 'jabuka | jabuka | jabuke | jabuka', // zero | singular | 2-4 | 5+
24
- mk: 'јаболка | јаболко | јаболка | јаболка', // zero | singular | 2-4 | 5+
25
- pl: 'jabłek | jabłko | jabłka | jabłek', // zero | singular | 2-4 | 5+
26
- sk: 'jabĺk | jablko | jablká | jabĺk', // zero | singular | 2-4 | 5+
27
- sl: 'jabolk | jabolko | jabolka | jabolk', // zero | singular | 2-4 | 5+
28
- sr: 'јабука | јабука | јабуке | јабука', // zero | singular | 2-4 | 5+
29
- be: 'яблыкаў | яблык | яблыкі | яблыкаў', // zero | singular | 2-4 | 5+
30
- ru: 'яблок | яблоко | яблока | яблок', // zero | singular | 2-4 | 5+
22
+ uk: 'яблук | яблуко | яблука | яблук',
23
+ bg: 'ябълки | ябълка | ябълки | ябълки',
24
+ cs: 'jablek | jablko | jablka | jablek',
25
+ hr: 'jabuka | jabuka | jabuke | jabuka',
26
+ mk: 'јаболка | јаболко | јаболка | јаболка',
27
+ pl: 'jabłek | jabłko | jabłka | jabłek',
28
+ sk: 'jabĺk | jablko | jablká | jabĺk',
29
+ sl: 'jabolk | jabolko | jabolka | jabolk',
30
+ sr: 'јабука | јабука | јабуке | јабука',
31
+ be: 'яблыкаў | яблык | яблыкі | яблыкаў',
32
+ ru: 'яблок | яблоко | яблока | яблок',
31
33
  };
32
34
  const countryISO31661ByLangISO6391 = {
33
35
  en: 'us', // English → United States
@@ -39,7 +41,142 @@ const countryISO31661ByLangISO6391 = {
39
41
  uk: 'ua', // Ukrainian → Ukraine
40
42
  ur: 'pk', // Urdu → Pakistan
41
43
  sr: 'rs', // Serbian → Serbia
42
- da: 'dk' // Danish → Denmark
44
+ da: 'dk', // Danish → Denmark
45
+ sv: 'se', // Swedish → Sweden
46
+ he: 'il', // Hebrew → Israel
47
+ el: 'gr', // Greek → Greece
48
+ cs: 'cz', // Czech → Czech Republic
49
+ vi: 'vn', // Vietnamese → Vietnam
50
+ aa: 'et', // Afar → Ethiopia
51
+ ab: 'ge', // Abkhaz → Georgia
52
+ ae: 'ir', // Avestan → Iran
53
+ af: 'za', // Afrikaans → South Africa
54
+ ak: 'gh', // Akan → Ghana
55
+ am: 'et', // Amharic → Ethiopia
56
+ an: 'es', // Aragonese → Spain
57
+ as: 'in', // Assamese → India
58
+ av: 'ru', // Avaric → Russia
59
+ ay: 'bo', // Aymara → Bolivia
60
+ ba: 'ru', // Bashkir → Russia
61
+ be: 'by', // Belarusian → Belarus
62
+ bi: 'vu', // Bislama → Vanuatu
63
+ bm: 'ml', // Bambara → Mali
64
+ bn: 'bd', // Bengali → Bangladesh
65
+ bo: 'cn', // Tibetan → China
66
+ br: 'fr', // Breton → France
67
+ bs: 'ba', // Bosnian → Bosnia & Herzegovina
68
+ ca: 'es', // Catalan → Spain
69
+ ce: 'ru', // Chechen → Russia
70
+ ch: 'gu', // Chamorro → Guam
71
+ co: 'fr', // Corsican → France
72
+ cr: 'ca', // Cree → Canada
73
+ cu: 'ru', // Old Church Slavonic → Russia
74
+ cv: 'ru', // Chuvash → Russia
75
+ cy: 'gb', // Welsh → United Kingdom
76
+ dv: 'mv', // Divehi → Maldives
77
+ dz: 'bt', // Dzongkha → Bhutan
78
+ ee: 'gh', // Ewe → Ghana
79
+ et: 'ee', // Estonian → Estonia
80
+ eu: 'es', // Basque → Spain
81
+ fa: 'ir', // Persian → Iran
82
+ ff: 'sn', // Fula → Senegal
83
+ fy: 'nl', // Western Frisian → Netherlands
84
+ ga: 'ie', // Irish → Ireland
85
+ gd: 'gb', // Scottish Gaelic → United Kingdom
86
+ gl: 'es', // Galician → Spain
87
+ gn: 'py', // Guaraní → Paraguay
88
+ gu: 'in', // Gujarati → India
89
+ gv: 'im', // Manx → Isle of Man
90
+ ha: 'ng', // Hausa → Nigeria
91
+ ho: 'pg', // Hiri Motu → Papua New Guinea
92
+ hy: 'am', // Armenian → Armenia
93
+ hz: 'na', // Herero → Namibia
94
+ ie: 'ee', // Interlingue → Estonia
95
+ ig: 'ng', // Igbo → Nigeria
96
+ ii: 'cn', // Nuosu → China
97
+ ik: 'us', // Inupiaq → United States
98
+ iu: 'ca', // Inuktitut → Canada
99
+ jv: 'id', // Javanese → Indonesia
100
+ ka: 'ge', // Georgian → Georgia
101
+ kg: 'cd', // Kongo → Congo - Kinshasa
102
+ ki: 'ke', // Kikuyu → Kenya
103
+ kj: 'na', // Kwanyama → Namibia
104
+ kk: 'kz', // Kazakh → Kazakhstan
105
+ kl: 'gl', // Kalaallisut → Greenland
106
+ km: 'kh', // Khmer → Cambodia
107
+ kn: 'in', // Kannada → India
108
+ kr: 'ng', // Kanuri → Nigeria
109
+ ks: 'in', // Kashmiri → India
110
+ ku: 'tr', // Kurdish → Türkiye
111
+ kv: 'ru', // Komi → Russia
112
+ kw: 'gb', // Cornish → United Kingdom
113
+ ky: 'kg', // Kyrgyz → Kyrgyzstan
114
+ la: 'va', // Latin → Vatican City
115
+ lb: 'lu', // Luxembourgish → Luxembourg
116
+ lg: 'ug', // Ganda → Uganda
117
+ li: 'nl', // Limburgish → Netherlands
118
+ ln: 'cd', // Lingala → Congo - Kinshasa
119
+ lo: 'la', // Lao → Laos
120
+ lu: 'cd', // Luba-Katanga → Congo - Kinshasa
121
+ mi: 'nz', // Māori → New Zealand
122
+ ml: 'in', // Malayalam → India
123
+ mr: 'in', // Marathi → India
124
+ ms: 'my', // Malay → Malaysia
125
+ my: 'mm', // Burmese → Myanmar (Burma)
126
+ na: 'nr', // Nauru → Nauru
127
+ nb: 'no', // Norwegian Bokmål → Norway
128
+ nd: 'zw', // Northern Ndebele → Zimbabwe
129
+ ne: 'np', // Nepali → Nepal
130
+ ng: 'na', // Ndonga → Namibia
131
+ nn: 'no', // Norwegian Nynorsk → Norway
132
+ nr: 'za', // Southern Ndebele → South Africa
133
+ nv: 'us', // Navajo → United States
134
+ ny: 'mw', // Chichewa → Malawi
135
+ oc: 'fr', // Occitan → France
136
+ oj: 'ca', // Ojibwe → Canada
137
+ om: 'et', // Oromo → Ethiopia
138
+ or: 'in', // Oriya → India
139
+ os: 'ge', // Ossetian → Georgia
140
+ pa: 'in', // Panjabi → India
141
+ pi: 'gb', // Pāli → United Kingdom
142
+ ps: 'af', // Pashto → Afghanistan
143
+ qu: 'pe', // Quechua → Peru
144
+ rm: 'ch', // Romansh → Switzerland
145
+ rn: 'bi', // Kirundi → Burundi
146
+ sa: 'in', // Sanskrit → India
147
+ sc: 'it', // Sardinian → Italy
148
+ sd: 'pk', // Sindhi → Pakistan
149
+ se: 'no', // Northern Sami → Norway
150
+ sg: 'cf', // Sango → Central African Republic
151
+ si: 'lk', // Sinhala → Sri Lanka
152
+ sl: 'si', // Slovenian → Slovenia
153
+ sm: 'ws', // Samoan → Samoa
154
+ sn: 'zw', // Shona → Zimbabwe
155
+ sq: 'al', // Albanian → Albania
156
+ ss: 'za', // Swati → South Africa
157
+ st: 'za', // Southern Sotho → South Africa
158
+ su: 'id', // Sundanese → Indonesia
159
+ sw: 'tz', // Swahili → Tanzania
160
+ ta: 'in', // Tamil → India
161
+ te: 'in', // Telugu → India
162
+ tg: 'tj', // Tajik → Tajikistan
163
+ ti: 'et', // Tigrinya → Ethiopia
164
+ tk: 'tm', // Turkmen → Turkmenistan
165
+ tl: 'ph', // Tagalog → Philippines
166
+ tn: 'za', // Tswana → South Africa
167
+ ts: 'za', // Tsonga → South Africa
168
+ tt: 'ru', // Tatar → Russia
169
+ tw: 'gh', // Twi → Ghana
170
+ ty: 'pf', // Tahitian → French Polynesia
171
+ ug: 'cn', // Uyghur → China
172
+ ve: 'za', // Venda → South Africa
173
+ wa: 'be', // Walloon → Belgium
174
+ wo: 'sn', // Wolof → Senegal
175
+ xh: 'za', // Xhosa → South Africa
176
+ yi: 'ua', // Yiddish → Ukraine
177
+ yo: 'ng', // Yoruba → Nigeria
178
+ za: 'cn', // Zhuang → China
179
+ zu: 'za', // Zulu → South Africa
43
180
  };
44
181
  export function getCountryCodeFromLangCode(lang) {
45
182
  const [langCode, region] = String(lang).split('-');
@@ -87,6 +224,28 @@ export class I18nBase extends AdminForthPlugin {
87
224
  instanceUniqueRepresentation() {
88
225
  return 'single';
89
226
  }
227
+ // languages translations are written for: strings in source language are the keys themselves
228
+ targetLanguages() {
229
+ return this.options.supportedLanguages.filter((lang) => lang !== SOURCE_LANGUAGE);
230
+ }
231
+ // empty translation means "not translated yet", original string is shown instead
232
+ fallbackToOriginal(messages) {
233
+ return Object.fromEntries(Object.entries(messages).map(([key, value]) => [key, value || key]));
234
+ }
235
+ /**
236
+ * Translates backend strings (menu, resource labels, errors) with adminforth.tr,
237
+ * subclass provides only translation lookup in its storage for already resolved language
238
+ */
239
+ installBackendTranslator(adminforth, lookup) {
240
+ adminforth.tr = (msg, category, lang, params, pluralizationNumber) => __awaiter(this, void 0, void 0, function* () {
241
+ if (!msg) {
242
+ return msg;
243
+ }
244
+ const resolvedLang = this.resolveLanguage(lang);
245
+ const translation = yield lookup(msg, category, resolvedLang);
246
+ return this.formatTranslation(msg, translation || msg, resolvedLang, params, pluralizationNumber);
247
+ });
248
+ }
90
249
  // validate each supported language: ISO 639-1 or BCP-47 with region (e.g., en-GB)
91
250
  validateSupportedLanguages() {
92
251
  this.options.supportedLanguages.forEach((lang) => {
@@ -105,6 +264,7 @@ export class I18nBase extends AdminForthPlugin {
105
264
  code: lang,
106
265
  // lang name on on language native name
107
266
  name: iso6391.getNativeName(getPrimaryLanguageCode(lang)),
267
+ countryCode: getCountryCodeFromLangCode(lang),
108
268
  }))
109
269
  };
110
270
  adminforth.config.customization.loginPageInjections.underLoginButton.push({