Refine i18n support (#236)

This commit is contained in:
Germey
2025-07-27 18:57:31 +08:00
committed by GitHub
parent f256e2059e
commit 0c0eea7483
15 changed files with 139 additions and 186 deletions
@@ -0,0 +1,7 @@
{
"type": "patch",
"comment": "refine i18n support",
"packageName": "@acedatacloud/nexior",
"email": "cqc@cuiqingcai.com",
"dependentChangeType": "patch"
}
+3 -3
View File
@@ -12,12 +12,12 @@
"scripts": {
"dev": "vite --host",
"build": "npm run build:web",
"build:web": "cross-env VITE_SURFACE=web vite build",
"build:web": "cross-env VITE_SURFACE=web vue-tsc -b && vite build",
"build:android": "cross-env VITE_SURFACE=android vite build",
"build:ios": "cross-env VITE_SURFACE=ios vite build",
"pack:android": "npm run build:android && npx cap copy android",
"pack:ios": "npm run build:ios && npx cap copy ios",
"serve": "vite preview",
"preview": "vite preview",
"translate": "transmart",
"lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
"prettier": "prettier --write .",
@@ -64,7 +64,7 @@
"uuid": "^8.3.2",
"vue": "^3.5.17",
"vue-codemirror": "^6.1.1",
"vue-i18n": "^9.1.7",
"vue-i18n": "^11.1.11",
"vue-qrcode": "^2.2.0",
"vue-router": "^4.5.1",
"vuex": "^4.1.0",
+1 -1
View File
@@ -10,7 +10,7 @@
import { defineComponent } from 'vue';
import { ElConfigProvider, ElTag } from 'element-plus';
import AuthPanel from './components/common/AuthPanel.vue';
import locale from 'element-plus/dist/locale/zh-cn.mjs';
import locale from 'element-plus/es/locale/lang/en';
import { isTest } from '@/constants/endpoint';
export default defineComponent({
-66
View File
@@ -1,66 +0,0 @@
<template>
<el-dialog :model-value="visible" :width="300" class="text-center" @close="$emit('close')">
<el-select v-model="value" @change="onSelectLocale">
<el-option
v-for="(locale, localeIndex) in locales"
:key="localeIndex"
:label="locale.label"
:value="locale.value"
/>
</el-select>
</el-dialog>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { ElDialog, ElSelect, ElOption } from 'element-plus';
import { SUPPORTED_LOCALES, setI18nLanguage } from '@/i18n';
import { setCookie } from 'typescript-cookie';
import { getDomain } from '@/utils/initializer';
export default defineComponent({
name: 'LocaleSelector',
components: {
ElDialog,
ElSelect,
ElOption
},
props: {
visible: {
type: Boolean,
default: false
}
},
emits: ['close'],
data() {
return {
value: this.$i18n.locale,
locales: SUPPORTED_LOCALES
};
},
methods: {
async onSelectLocale(locale: string) {
// change router
this.$router.push({ query: { ...this.$route.query, locale: undefined } });
await setI18nLanguage(locale);
this.setCookie(locale);
window.location.reload();
},
setCookie(locale: string) {
setCookie('LOCALE', locale, {
path: '/',
domain: getDomain()
});
}
}
});
</script>
<style scoped>
.icon {
cursor: pointer;
font-size: 16px;
margin-top: 4px;
display: inline-block;
}
</style>
+8 -1
View File
@@ -3,7 +3,14 @@
<el-row class="paycodes">
<el-col :span="12">
<div class="paycode wechat">
<qr-code v-if="modelValue?.pay_url" :value="modelValue?.pay_url" :size="200" class="qrcode m-auto" />
<qr-code
v-if="modelValue?.pay_url"
:value="modelValue?.pay_url"
:size="200"
class="qrcode m-auto"
type="image/png"
:color="{ dark: '#000000', light: '#ffffff' }"
/>
<p class="help-text">{{ $t('order.message.wechatPayHelp') }}</p>
</div>
</el-col>
+4 -3
View File
@@ -18,12 +18,13 @@
import { defineComponent } from 'vue';
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElIcon } from 'element-plus';
import { ArrowDown } from '@element-plus/icons-vue';
import { SUPPORTED_LOCALES, setI18nLanguage } from '@/i18n';
import { setCookie } from 'typescript-cookie';
import { getDomain } from '@/utils/initializer';
import { I18N_SUPPORTED_LOCALES } from '@/constants/i18n';
import { setI18nLanguage } from '@/i18n';
export default defineComponent({
name: 'LocaleSelector',
name: 'Locale',
components: {
ElDropdown,
ElDropdownMenu,
@@ -33,7 +34,7 @@ export default defineComponent({
},
data() {
return {
locales: SUPPORTED_LOCALES
locales: I18N_SUPPORTED_LOCALES
};
},
computed: {
+48
View File
@@ -0,0 +1,48 @@
export const I18N_DEFAULT_LOCALE = 'en';
export const I18N_SUPPORTED_LOCALES = [
{ value: 'en', label: 'English' },
{ value: 'de', label: 'Deutsch' },
{ value: 'pt', label: 'Português' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' },
{ value: 'zh-CN', label: '简体中文' },
{ value: 'zh-TW', label: '繁體中文' },
{ value: 'it', label: 'Italiano' },
{ value: 'ko', label: '한국어' },
{ value: 'ja', label: '日本語' },
{ value: 'ru', label: 'Русский' },
{ value: 'pl', label: 'Polski' },
{ value: 'fi', label: 'Suomi' },
{ value: 'sv', label: 'Svenska' },
{ value: 'el', label: 'Ελληνικά' },
{ value: 'uk', label: 'Українська' },
{ value: 'ar', label: 'العربية' },
{ value: 'sr', label: 'Српски' }
];
export const I18N_SCOPES = [
'index',
'api',
'application',
'qrart',
'luma',
'pika',
'kling',
'flux',
'hailuo',
'headshots',
'suno',
'common',
'console',
'credential',
'distribution',
'order',
'proxy',
'service',
'usage',
'user',
'site',
'chat',
'midjourney'
];
+32 -78
View File
@@ -1,56 +1,16 @@
import { createI18n, I18n } from 'vue-i18n';
import { nextTick } from 'vue';
import { createI18n } from 'vue-i18n';
import { I18N_DEFAULT_LOCALE, I18N_SCOPES, I18N_SUPPORTED_LOCALES } from '@/constants/i18n';
import axios from 'axios';
import { nextTick } from 'vue';
declare namespace Intl {
// eslint-disable-next-line no-unused-vars
function getCanonicalLocales(locales: string | string[] | undefined): string[];
}
export const DEFAULT_LOCALE = 'en';
export const SUPPORTED_LOCALES = [
{ value: 'en', label: 'English' },
{ value: 'de', label: 'Deutsch' },
{ value: 'pt', label: 'Português' },
{ value: 'es', label: 'Español' },
{ value: 'fr', label: 'Français' },
{ value: 'zh-CN', label: '简体中文' },
{ value: 'zh-TW', label: '繁體中文' },
{ value: 'it', label: 'Italiano' },
{ value: 'ko', label: '한국어' },
{ value: 'ja', label: '日本語' },
{ value: 'ru', label: 'Русский' },
{ value: 'pl', label: 'Polski' },
{ value: 'fi', label: 'Suomi' },
{ value: 'sv', label: 'Svenska' },
{ value: 'el', label: 'Ελληνικά' },
{ value: 'uk', label: 'Українська' },
{ value: 'ar', label: 'العربية' },
{ value: 'sr', label: 'Српски' }
];
export const setupI18n = () => {
// @ts-ignore
const i18n: I18n = createI18n({});
return i18n;
};
export const i18n = setupI18n();
export const loadLocalResource = async (name: string, locale: string) => {
try {
const module = await import(`./${locale}/${name}.json`);
return module.default;
} catch (error) {
const module = await import(`./${DEFAULT_LOCALE}/${name}.json`);
return module.default;
}
};
export const i18n = createI18n({
legacy: true
});
export const getLocale = (lang?: string): string => {
const canonicalLang = Intl.getCanonicalLocales(lang || navigator.language)?.[0];
const supportedLocales = SUPPORTED_LOCALES.map((locale) => locale.value);
console.debug('canonicalLang', canonicalLang);
const supportedLocales = I18N_SUPPORTED_LOCALES.map((locale) => locale.value);
// if the canonical language is supported, use it
if (canonicalLang && supportedLocales.includes(canonicalLang)) {
console.debug('canonicalLang', canonicalLang);
@@ -63,41 +23,35 @@ export const getLocale = (lang?: string): string => {
return canonicalLangPrefix;
}
}
return DEFAULT_LOCALE;
return I18N_DEFAULT_LOCALE;
};
export const loadLocaleMessages = async (i18n: I18n, locale: string) => {
const names = [
'index',
'api',
'application',
'qrart',
'luma',
'pika',
'kling',
'flux',
'hailuo',
'headshots',
'suno',
'common',
'console',
'credential',
'distribution',
'order',
'proxy',
'service',
'usage',
'user',
'site',
'chat',
'midjourney'
];
const messageLoaders = import.meta.glob('./**/*.json');
const promises = names.map((name) => loadLocalResource(name, locale));
export const loadLocaleResource = async (name: string, locale: string) => {
console.debug('loadLocaleResource', name, locale);
const path = `./${locale}/${name}.json`;
const loader = messageLoaders[path];
if (!loader) {
console.warn(`[i18n] Missing translation file: ${path}`);
return;
}
const module: any = await loader();
console.debug('loaded module', module);
if (!module.default) {
console.warn(`[i18n] Missing default export in translation file: ${path}`);
return;
}
return module.default;
};
export const loadLocaleMessages = async (locale: string) => {
const promises = I18N_SCOPES.map((name) => loadLocaleResource(name, locale));
const resources = await Promise.all(promises);
const messages: any = {};
names.forEach((name, index) => {
I18N_SCOPES.forEach((name, index) => {
const resource = resources[index];
for (const key in resource) {
if (resource.hasOwnProperty(key)) {
@@ -116,7 +70,7 @@ export const loadLocaleMessages = async (i18n: I18n, locale: string) => {
export const setI18nLanguage = async (locale: string) => {
// load locale messages
if (!i18n.global.availableLocales.includes(locale)) {
await loadLocaleMessages(i18n, locale);
await loadLocaleMessages(locale);
}
// set global locale
-1
View File
@@ -305,7 +305,6 @@ export default defineComponent({
},
async onRestoreConversation(id: string) {
console.debug('onRestoreConversation id', id);
this.$router.push({ ...this.$route, params: { id } });
const conversation = this.conversations?.find((conversation: IChatConversation) => conversation.id === id);
// change the model and model group
const model = conversation?.model;
+2
View File
@@ -152,6 +152,8 @@
:size="180"
:margin="2"
class="block mb-2 ml-auto mr-auto"
type="image/png"
:color="{ dark: '#000000', light: '#ffffff' }"
/>
<p class="mt-0">
{{ $t('distribution.message.distributionQrDescription') }}
+1 -3
View File
@@ -41,7 +41,6 @@ import {
} from '@/router';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { getBaseUrlAuth, getBaseUrlPlatform } from '@/utils';
import LocaleSelector from '@/components/common/LocaleSelector.vue';
import HelpDialog from '@/components/common/HelpDialog.vue';
interface ILink {
@@ -58,8 +57,7 @@ export default defineComponent({
components: {
ElImage,
HelpDialog,
FontAwesomeIcon,
LocaleSelector
FontAwesomeIcon
},
data() {
return {
+3 -2
View File
@@ -18,8 +18,9 @@ import site from './site';
import profile from './profile';
import { ROUTE_CHATGPT_CONVERSATION_NEW } from './constants';
import { DEFAULT_LOCALE, setI18nLanguage } from '@/i18n';
import { getCookie } from 'typescript-cookie';
import { I18N_DEFAULT_LOCALE } from '@/constants/i18n';
import { getLocale, setI18nLanguage } from '@/i18n';
const routes = [
{
@@ -51,7 +52,7 @@ const router = createRouter({
});
router.beforeEach(async (_to, _from, next) => {
const locale = getCookie('LOCALE') || DEFAULT_LOCALE;
const locale = getLocale(getCookie('LOCALE') || I18N_DEFAULT_LOCALE);
await setI18nLanguage(locale);
return next();
});
+8 -6
View File
@@ -6,7 +6,7 @@
import { ComponentCustomProperties } from 'vue';
import { Router } from 'vue-router';
import { I18n } from 'vue-i18n';
import { I18n, VueI18n } from 'vue-i18n';
import { Store } from 'vuex';
import { IRootState } from './store/common/models';
import type { Router, RouteLocationNormalizedLoaded } from 'vue-router';
@@ -17,10 +17,7 @@ declare module '*.ico';
declare module '*.css';
declare module '*.scss';
declare module '*.png';
declare module '*.tpl' {
const value: string;
export default value;
}
declare module '*.tpl';
declare module '*.vue' {
import { DefineComponent } from 'vue';
@@ -28,9 +25,14 @@ declare module '*.vue' {
export default component;
}
// declare namespace Intl {
// function getCanonicalLocales(locales: string | string[] | undefined): string[];
// }
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$t: (key: string, ...args: unknown[]) => string;
$t: VueI18n['t'];
$i18n: VueI18n;
$store: Store<IRootState>;
$router: Router;
$route: RouteLocationNormalizedLoaded;
+1 -1
View File
@@ -1,12 +1,12 @@
import { getCookie, setCookie } from 'typescript-cookie';
import favicon from '@/assets/images/favicon.ico';
import { getLocale } from '@/i18n';
import { applyTheme } from './theme';
import store from '@/store';
import { IToken } from '@/models';
import { parse } from 'psl';
import { BASE_HOST_HUB, LOCALE_CURRENCY_MAPPING } from '@/constants';
import { isOfficial, isSubOfficial, isWechatBrowser } from './is';
import { getLocale } from '@/i18n';
export const getDomain = (host: string = window.location.hostname) => {
const parsed = parse(host);
+21 -21
View File
@@ -503,26 +503,26 @@
resolved "https://registry.yarnpkg.com/@icon-park/vue-next/-/vue-next-1.4.2.tgz#818c048100401620e8112beb2636f10171b195cc"
integrity sha512-+QklF255wkfBOabY+xw6FAI0Bwln/RhdwCunNy/9sKdKuChtaU67QZqU67KGAvZUTeeBgsL+yaHHxqfQeGZXEQ==
"@intlify/core-base@9.14.5":
version "9.14.5"
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.14.5.tgz#cfed631e98b34f4f6fe1312e6c02dd8ace180197"
integrity sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==
"@intlify/core-base@11.1.11":
version "11.1.11"
resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-11.1.11.tgz#e36893a7d37a3a75fae30977fc58c1d8cf3c853f"
integrity sha512-1Z0N8jTfkcD2Luq9HNZt+GmjpFe4/4PpZF3AOzoO1u5PTtSuXZcfhwBatywbfE2ieB/B5QHIoOFmCXY2jqVKEQ==
dependencies:
"@intlify/message-compiler" "9.14.5"
"@intlify/shared" "9.14.5"
"@intlify/message-compiler" "11.1.11"
"@intlify/shared" "11.1.11"
"@intlify/message-compiler@9.14.5":
version "9.14.5"
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.14.5.tgz#03b0a358c93cb2eed95936531c8a5871fd3841b0"
integrity sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==
"@intlify/message-compiler@11.1.11":
version "11.1.11"
resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-11.1.11.tgz#ba10641f86af0e991ac9def0385bd345c8f150fb"
integrity sha512-7PC6neomoc/z7a8JRjPBbu0T2TzR2MQuY5kn2e049MP7+o32Ve7O8husylkA7K9fQRe4iNXZWTPnDJ6vZdtS1Q==
dependencies:
"@intlify/shared" "9.14.5"
"@intlify/shared" "11.1.11"
source-map-js "^1.0.2"
"@intlify/shared@9.14.5":
version "9.14.5"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.14.5.tgz#2e42193b8356db20ee28c15d1ee6c8f0fda6c0b9"
integrity sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==
"@intlify/shared@11.1.11":
version "11.1.11"
resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-11.1.11.tgz#6bba3b86617c05767356e4ca939c9e300563a083"
integrity sha512-RIBFTIqxZSsxUqlcyoR7iiC632bq7kkOwYvZlvcVObHfrF4NhuKc4FKvu8iPCrEO+e3XsY7/UVpfgzg+M7ETzA==
"@ionic/cli-framework-output@^2.2.5":
version "2.2.8"
@@ -5541,13 +5541,13 @@ vue-eslint-parser@^8.0.1:
lodash "^4.17.21"
semver "^7.3.5"
vue-i18n@^9.1.7:
version "9.14.5"
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.14.5.tgz#72fbf4384b83a1c59ec9e01ff5d30224bd9150cf"
integrity sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==
vue-i18n@^11.1.11:
version "11.1.11"
resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-11.1.11.tgz#b38ed214896540cf7a68932dfa565d9d4fbbffac"
integrity sha512-LvyteQoXeQiuILbzqv13LbyBna/TEv2Ha+4ZWK2AwGHUzZ8+IBaZS0TJkCgn5izSPLcgZwXy9yyTrewCb2u/MA==
dependencies:
"@intlify/core-base" "9.14.5"
"@intlify/shared" "9.14.5"
"@intlify/core-base" "11.1.11"
"@intlify/shared" "11.1.11"
"@vue/devtools-api" "^6.5.0"
vue-qrcode@^2.2.0: