Skip to content

Commit 8636a03

Browse files
committed
translation progress
1 parent cf95d89 commit 8636a03

File tree

9 files changed

+207
-48
lines changed

9 files changed

+207
-48
lines changed

adminforth/modules/restApi.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
394394
server.endpoint({
395395
method: 'POST',
396396
path: '/get_resource',
397-
handler: async ({ body, adminUser }): Promise<{ resource?: AdminForthResourceCommon, error?: string }> => {
397+
handler: async ({ body, adminUser, tr }): Promise<{ resource?: AdminForthResourceCommon, error?: string }> => {
398398
const { resourceId } = body;
399399
if (!this.adminforth.statuses.dbDiscover) {
400400
return { error: 'Database discovery not started' };
@@ -425,6 +425,14 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
425425

426426
const toReturn = {
427427
...resource,
428+
columns: await Promise.all(
429+
resource.columns.map(async (col) => {
430+
return {
431+
...col,
432+
label: await tr(col.label, `resource.${resource.resourceId}`),
433+
}
434+
})
435+
),
428436
options: {
429437
...resource.options,
430438
bulkActions: allowedBulkActions,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<template>
2+
<p class="text-gray-500 dark:text-gray-400 font-sm text-left mt-3 flex items-center justify-center">
3+
<Select
4+
v-model="selectedLanguage"
5+
:options="options"
6+
:placeholder="$t('Select language')"
7+
@change="changeLanguage"
8+
>
9+
<template #item="{ option }">
10+
<span class="mr-1">
11+
<span class="flag-icon"
12+
:class="`flag-icon-${getCountryCodeFromLangCode(option.value)}`"
13+
></span>
14+
15+
</span>
16+
<span>{{ option.label }}</span>
17+
</template>
18+
19+
<template #selected-item="{option}">
20+
<span class="mr-1">
21+
<span class="flag-icon"
22+
:class="`flag-icon-${getCountryCodeFromLangCode(option.value)}`"
23+
></span>
24+
</span>
25+
<span>{{ option.label }}</span>
26+
</template>
27+
</Select>
28+
</p>
29+
</template>
30+
31+
<script setup>
32+
import Select from '@/afcl/Select.vue';
33+
import 'flag-icon-css/css/flag-icons.min.css';
34+
import { setLang, getCountryCodeFromLangCode, getLocalLang } from './langCommon';
35+
36+
import { computed, ref, onMounted, watch } from 'vue';
37+
import { useI18n } from 'vue-i18n';
38+
39+
const { setLocaleMessage, locale } = useI18n();
40+
41+
42+
const props = defineProps(['meta', 'resource']);
43+
44+
const selectedLanguage = ref('');
45+
46+
47+
watch(() => selectedLanguage.value, (newVal) => {
48+
setLang({ setLocaleMessage, locale }, props.meta.pluginInstanceId, newVal);
49+
});
50+
51+
52+
const options = computed(() => {
53+
return props.meta.supportedLanguages.map((lang) => {
54+
return {
55+
value: lang.code,
56+
label: lang.name,
57+
};
58+
});
59+
});
60+
61+
62+
onMounted(() => {
63+
console.log('Language In user menu mounted', props.meta.supportedLanguages);
64+
selectedLanguage.value = getLocalLang(props.meta.supportedLanguages);
65+
setLang({ setLocaleMessage, locale }, props.meta.pluginInstanceId, selectedLanguage.value);
66+
// todo this mounted executed only on this component mount, f5 from another page apart login will not read it
67+
});
68+
69+
70+
71+
72+
73+
74+
75+
</script>

adminforth/plugins/i18n/custom/LanguageUnderLogin.vue

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
<script setup>
3232
import Select from '@/afcl/Select.vue';
3333
import 'flag-icon-css/css/flag-icons.min.css';
34-
import { setLang } from './langCommon';
34+
import { setLang, getCountryCodeFromLangCode, getLocalLang } from './langCommon';
3535
3636
import { computed, ref, onMounted, watch } from 'vue';
3737
import { useI18n } from 'vue-i18n';
@@ -43,34 +43,11 @@ const props = defineProps(['meta', 'resource']);
4343
4444
const selectedLanguage = ref('');
4545
46-
47-
4846
watch(() => selectedLanguage.value, (newVal) => {
49-
localStorage.setItem(LS_LANG_KEY, newVal);
50-
51-
52-
5347
setLang({ setLocaleMessage, locale }, props.meta.pluginInstanceId, newVal);
5448
});
5549
5650
57-
// only remap the country code for the languages where language code is different from the country code
58-
// don't include es: es, fr: fr, etc, only include the ones where language code is different from the country code
59-
const countryISO31661ByLangISO6391 = {
60-
en: 'us', // English → United States
61-
zh: 'cn', // Chinese → China
62-
hi: 'in', // Hindi → India
63-
ar: 'sa', // Arabic → Saudi Arabia
64-
ko: 'kr', // Korean → South Korea
65-
ja: 'jp', // Japanese → Japan
66-
uk: 'ua', // Ukrainian → Ukraine
67-
};
68-
69-
function getCountryCodeFromLangCode(langCode) {
70-
return countryISO31661ByLangISO6391[langCode] || langCode;
71-
}
72-
73-
7451
const options = computed(() => {
7552
return props.meta.supportedLanguages.map((lang) => {
7653
return {
@@ -80,11 +57,17 @@ const options = computed(() => {
8057
});
8158
});
8259
83-
const LS_LANG_KEY = `${props.meta.brandSlug}-lang`;
84-
8560
onMounted(() => {
8661
console.log('LanguageUnderLogin mounted', props.meta.supportedLanguages);
87-
selectedLanguage.value = localStorage.getItem(LS_LANG_KEY) || props.meta.supportedLanguages[0].code;
62+
selectedLanguage.value = getLocalLang(props.meta.supportedLanguages);
63+
setLang({ setLocaleMessage, locale }, props.meta.pluginInstanceId, selectedLanguage.value);
64+
// todo this mounted executed only on this component mount, f5 from another page apart login will not read it
8865
});
8966
67+
68+
69+
70+
71+
72+
9073
</script>

adminforth/plugins/i18n/custom/langCommon.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,48 @@ export async function setLang({ setLocaleMessage, locale }: any, pluginInstanceI
4242
locale.value = langIso;
4343

4444
document.querySelector('html').setAttribute('lang', langIso);
45-
}
45+
setLocalLang(langIso);
46+
}
47+
48+
// only remap the country code for the languages where language code is different from the country code
49+
// don't include es: es, fr: fr, etc, only include the ones where language code is different from the country code
50+
const countryISO31661ByLangISO6391 = {
51+
en: 'us', // English → United States
52+
zh: 'cn', // Chinese → China
53+
hi: 'in', // Hindi → India
54+
ar: 'sa', // Arabic → Saudi Arabia
55+
ko: 'kr', // Korean → South Korea
56+
ja: 'jp', // Japanese → Japan
57+
uk: 'ua', // Ukrainian → Ukraine
58+
};
59+
60+
export function getCountryCodeFromLangCode(langCode) {
61+
return countryISO31661ByLangISO6391[langCode] || langCode;
62+
}
63+
64+
65+
const LS_LANG_KEY = `afLanguage`;
66+
67+
export function getLocalLang(supportedLanguages: {code}[]): string {
68+
let lsLang = localStorage.getItem(LS_LANG_KEY);
69+
// if someone screwed up the local storage or we stopped language support, lets check if it is in supported languages
70+
if (lsLang && !supportedLanguages.find((l) => l.code == lsLang)) {
71+
lsLang = null;
72+
}
73+
if (lsLang) {
74+
return lsLang;
75+
}
76+
// read lang from navigator and try find what we have in supported languages
77+
const lang = navigator.language.split('-')[0];
78+
const foundLang = supportedLanguages.find((l) => l.code == lang);
79+
if (foundLang) {
80+
return foundLang.code;
81+
}
82+
return supportedLanguages[0].code;
83+
}
84+
85+
export function setLocalLang(lang: string) {
86+
localStorage.setItem(LS_LANG_KEY, lang);
87+
}
88+
89+

adminforth/plugins/i18n/index.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -156,20 +156,26 @@ export default class I18N extends AdminForthPlugin {
156156
});
157157
}
158158

159+
const compMeta = {
160+
brandSlug: adminforth.config.customization.brandNameSlug,
161+
pluginInstanceId: this.pluginInstanceId,
162+
supportedLanguages: this.options.supportedLanguages.map(lang => (
163+
{
164+
code: lang,
165+
// lang name on on language native name
166+
name: iso6391.getNativeName(lang),
167+
}
168+
))
169+
};
159170
// add underLogin component
160-
(adminforth.config.customization.loginPageInjections.underInputs as AdminForthComponentDeclaration[]).push({
171+
(adminforth.config.customization.loginPageInjections.underInputs).push({
161172
file: this.componentPath('LanguageUnderLogin.vue'),
162-
meta: {
163-
brandSlug: adminforth.config.customization.brandNameSlug,
164-
pluginInstanceId: this.pluginInstanceId,
165-
supportedLanguages: this.options.supportedLanguages.map(lang => (
166-
{
167-
code: lang,
168-
// lang name on on language native name
169-
name: iso6391.getNativeName(lang),
170-
}
171-
))
172-
}
173+
meta: compMeta
174+
});
175+
176+
(adminforth.config.customization.globalInjections.userMenu).push({
177+
file: this.componentPath('LanguageInUserMenu.vue'),
178+
meta: compMeta
173179
});
174180

175181
// disable create allowedActions for translations
@@ -301,8 +307,6 @@ export default class I18N extends AdminForthPlugin {
301307

302308
const translations = await this.adminforth.resource(this.resourceConfig.resourceId).list(Filters.IN(this.primaryKeyFieldName, selectedIds));
303309

304-
console.log('🪲translations', translations);
305-
306310
for (const lang of this.options.supportedLanguages) {
307311
if (lang === 'en') {
308312
// all strings are in English, no need to translate
@@ -321,8 +325,6 @@ export default class I18N extends AdminForthPlugin {
321325
}
322326
}
323327

324-
console.log('🪲needToTranslateByLang', needToTranslateByLang);
325-
326328
const maxKeysInOneReq = 10;
327329

328330
const updateStrings: Record<string, {
@@ -521,6 +523,12 @@ ${
521523

522524
adminforth.tr = async (msg: string, category: string, lang: string): Promise<string> => {
523525
console.log('🪲tr', msg, category, lang);
526+
527+
// if lang is not supported , throw
528+
if (!this.options.supportedLanguages.includes(lang as LanguageCode)) {
529+
throw new Error(`Language ${lang} is not entered to be supported by requested by browser in request headers accept-language`);
530+
}
531+
524532
// try to get translation from cache
525533
const cacheKey = `${resourceConfig.resourceId}:${category}:${lang}:${msg}`;
526534
const cached = await this.cache.get(cacheKey);
@@ -536,7 +544,18 @@ ${
536544
});
537545
return msg;
538546
}
547+
548+
// do this check here, to faster register missing translations
549+
// also not cache it - no sense to cache english strings
550+
if (lang === 'en') {
551+
return msg;
552+
}
553+
539554
const result = translation[this.trFieldNames[lang]];
555+
if (!result) {
556+
// return english
557+
return msg;
558+
}
540559
await this.cache.set(cacheKey, result);
541560
return result;
542561
}

adminforth/spa/src/components/ResourceListTable.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
<!-- Help text -->
234234
<span class="text-sm text-gray-700 dark:text-gray-400">
235235
<span v-if="((page || 1) - 1) * pageSize + 1 > totalRows">{{ $t('Wrong Page') }} </span>
236-
<span v-else>{{ $t('Showing') }} </span>
236+
<span v-else>{{ $t('Showing') }}&nbsp;</span>
237237
<span class="font-semibold text-gray-900 dark:text-white">
238238
{{ ((page || 1) - 1) * pageSize + 1 }}
239239
</span> {{ $t('to') }} <span class="font-semibold text-gray-900 dark:text-white">

adminforth/spa/src/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { useCoreStore } from './stores/core';
88
import { useUserStore } from './stores/user';
99
import { Dropdown } from 'flowbite';
1010

11+
const LS_LANG_KEY = `afLanguage`;
1112

1213
export async function callApi({path, method, body=undefined}: {
1314
path: string, method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
@@ -17,6 +18,7 @@ export async function callApi({path, method, body=undefined}: {
1718
method,
1819
headers: {
1920
'Content-Type': 'application/json',
21+
'accept-language': localStorage.getItem(LS_LANG_KEY) || 'en',
2022
},
2123
body: JSON.stringify(body),
2224
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
Warnings:
3+
4+
- Made the column `category` on table `translations` required. This step will fail if there are existing NULL values in that column.
5+
6+
*/
7+
-- RedefineTables
8+
PRAGMA defer_foreign_keys=ON;
9+
PRAGMA foreign_keys=OFF;
10+
CREATE TABLE "new_translations" (
11+
"id" TEXT NOT NULL PRIMARY KEY,
12+
"en_string" TEXT NOT NULL,
13+
"created_at" DATETIME NOT NULL,
14+
"uk_string" TEXT,
15+
"ja_string" TEXT,
16+
"fr_string" TEXT,
17+
"es_string" TEXT,
18+
"category" TEXT NOT NULL,
19+
"source" TEXT,
20+
"completedLangs" TEXT
21+
);
22+
INSERT INTO "new_translations" ("category", "completedLangs", "created_at", "en_string", "es_string", "fr_string", "id", "ja_string", "source", "uk_string") SELECT "category", "completedLangs", "created_at", "en_string", "es_string", "fr_string", "id", "ja_string", "source", "uk_string" FROM "translations";
23+
DROP TABLE "translations";
24+
ALTER TABLE "new_translations" RENAME TO "translations";
25+
CREATE INDEX "translations_en_string_category_idx" ON "translations"("en_string", "category");
26+
CREATE INDEX "translations_category_idx" ON "translations"("category");
27+
PRAGMA foreign_keys=ON;
28+
PRAGMA defer_foreign_keys=OFF;

dev-demo/schema.prisma

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ model translations {
6161
ja_string String?
6262
fr_string String?
6363
es_string String?
64-
category String?
65-
source String
64+
category String
65+
source String?
6666
completedLangs String?
6767
6868
// we need both indexes on en_string+category and separately on category

0 commit comments

Comments
 (0)