Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/components/LanguageSwitcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</t-button>
<t-dropdown-menu>
<t-dropdown-item
v-for="(lang, index) in langList"
v-for="(lang, index) in languageList"
:key="index"
:value="lang.value"
@click="(options) => changeLang(options.value as string)"
Expand All @@ -17,7 +17,7 @@
<script setup lang="ts">
import { TranslateIcon } from 'tdesign-icons-vue-next';

import { langList } from '@/locales';
import { languageList } from '@/locales';
import { useLocale } from '@/locales/useLocale';

const { changeLocale } = useLocale();
Expand Down
6 changes: 3 additions & 3 deletions src/components/common-table/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
</t-table>
<t-dialog
v-model:visible="confirmVisible"
header="确认删除当前所选合同?"
:header="t('pages.listBase.deleteConfirm')"
:body="confirmBody"
:on-cancel="onCancel"
@confirm="onConfirmDelete"
Expand Down Expand Up @@ -242,7 +242,7 @@ const deleteIdx = ref(-1);
const confirmBody = computed(() => {
if (deleteIdx.value > -1) {
const { name } = data.value[deleteIdx.value];
return `删除后,${name}的所有合同信息将被清空,且无法恢复`;
return t('pages.listBase.deleteTip', { name });
}
return '';
});
Expand All @@ -256,7 +256,7 @@ const onConfirmDelete = () => {
data.value.splice(deleteIdx.value, 1);
pagination.value.total = data.value.length;
confirmVisible.value = false;
MessagePlugin.success('删除成功');
MessagePlugin.success(t('components.deleteSuccess'));
resetIdx();
};

Expand Down
15 changes: 7 additions & 8 deletions src/layouts/components/Breadcrumb.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@
import { computed } from 'vue';
import { useRoute } from 'vue-router';

import type { LocalizedTitle } from '@/locales';
import { useLocale } from '@/locales/useLocale';

const { locale } = useLocale();
const route = useRoute();

const renderTitle = (title?: LocalizedTitle, fallback?: string) => {
if (!title) return fallback || '';
return title[locale.value as keyof LocalizedTitle] || fallback || '';
};

const crumbs = computed(() => {
const pathArray = route.path.split('/');
pathArray.shift();
Expand All @@ -24,14 +30,7 @@ const crumbs = computed(() => {
if (meta?.hiddenBreadcrumb || Object.values(route.params).includes(path)) {
return breadcrumbArray;
}
let title = path;
if (meta?.title) {
if (typeof meta.title === 'string') {
title = meta.title;
} else {
title = meta.title[locale.value];
}
}
const title = renderTitle(meta?.title as LocalizedTitle, path);
breadcrumbArray.push({
path,
to: breadcrumbArray[idx - 1] ? `${breadcrumbArray[idx - 1].to}/${path}` : `/${path}`,
Expand Down
7 changes: 4 additions & 3 deletions src/layouts/components/LayoutContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import { computed, nextTick, ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';

import { prefix } from '@/config/global';
import type { LocalizedTitle } from '@/locales';
import { t } from '@/locales';
import { useLocale } from '@/locales/useLocale';
import { useSettingStore, useTabsRouterStore } from '@/store';
Expand Down Expand Up @@ -108,9 +109,9 @@ const handleRemove = (options: TTabRemoveOptions) => {
if ((options.value as string) === route.path) router.push({ path: nextRouter.path, query: nextRouter.query });
};

const renderTitle = (title: string | Record<string, string>) => {
if (typeof title === 'string') return title;
return title[locale.value];
const renderTitle = (title?: LocalizedTitle) => {
if (!title) return '';
return title[locale.value as keyof LocalizedTitle] || '';
};
const handleRefresh = (route: TRouterInfo, routeIdx: number) => {
tabsRouterStore.toggleTabRouterAlive(routeIdx);
Expand Down
16 changes: 9 additions & 7 deletions src/layouts/components/MenuContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import type { PropType } from 'vue';
import { computed } from 'vue';

import type { LocalizedTitle } from '@/locales';
import { useLocale } from '@/locales/useLocale';
import { getActive } from '@/router';
import type { MenuRoute } from '@/types/interface';
Expand All @@ -44,6 +45,7 @@ const { navData } = defineProps({
const active = computed(() => getActive());

const { locale } = useLocale();

const list = computed(() => {
return getMenuList(navData);
});
Expand All @@ -54,12 +56,12 @@ const menuIcon = (item: ListItemType) => {
return RenderIcon;
};

const renderMenuTitle = (title: string | Record<string, string>) => {
if (typeof title === 'string') return title;
return title[locale.value];
const renderMenuTitle = (title?: LocalizedTitle) => {
if (!title) return '';
return title[locale.value as keyof LocalizedTitle] || '';
};

const getMenuList = (list: MenuRoute[], basePath?: string): ListItemType[] => {
function getMenuList(list: MenuRoute[], basePath?: string): MenuRoute[] {
if (!list || list.length === 0) {
return [];
}
Expand All @@ -73,15 +75,15 @@ const getMenuList = (list: MenuRoute[], basePath?: string): ListItemType[] => {

return {
path,
title: item.meta?.title,
title: item.meta?.title as LocalizedTitle | undefined,
icon: item.meta?.icon,
children: getMenuList(item.children, path),
meta: item.meta,
redirect: item.redirect,
};
} as MenuRoute;
})
.filter((item) => item.meta && item.meta.hidden !== true);
};
}

const getHref = (item: MenuRoute) => {
const { frameSrc, frameBlank } = item.meta;
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/components/Search.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
v-model="searchData"
class="header-search"
:class="[{ 'width-zero': !isSearchFocus }]"
placeholder="输入要搜索内容"
:placeholder="t('layout.search.placeholder')"
:autofocus="isSearchFocus"
@blur="changeSearchFocus(false)"
>
Expand Down
3 changes: 2 additions & 1 deletion src/layouts/components/SideNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
</template>
<menu-content :nav-data="menu" />
<template #operations>
<span :class="versionCls"> {{ !collapsed ? 'TDesign Starter' : '' }} {{ pgk.version }} </span>
<span :class="versionCls"> {{ !collapsed ? t('common.appName') : '' }} {{ pgk.version }} </span>
</template>
</t-menu>
<div :class="`${prefix}-side-nav-placeholder${collapsed ? '-hidden' : ''}`"></div>
Expand All @@ -32,6 +32,7 @@ import { useRouter } from 'vue-router';
import AssetLogoFull from '@/assets/assets-logo-full.svg?component';
import AssetLogo from '@/assets/assets-t-logo.svg?component';
import { prefix } from '@/config/global';
import { t } from '@/locales';
import { getActive } from '@/router';
import { useSettingStore } from '@/store';
import type { MenuRoute, ModeType } from '@/types/interface';
Expand Down
3 changes: 2 additions & 1 deletion src/layouts/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ const appendNewRoute = () => {
meta: { title },
name,
} = route;
tabsRouterStore.appendTabRouterList({ path, query, title: title as string, name, isAlive: true, meta: route.meta });
const titleObj = typeof title === 'string' ? { zh_CN: title, en_US: title } : title;
tabsRouterStore.appendTabRouterList({ path, query, title: titleObj, name, isAlive: true, meta: route.meta });
};

onMounted(() => {
Expand Down
8 changes: 4 additions & 4 deletions src/layouts/setting.vue
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ const settingStore = useSettingStore();

const LAYOUT_OPTION = ['side', 'top', 'mix'];

const MODE_OPTIONS = [
const MODE_OPTIONS = computed(() => [
{ type: 'light', text: t('layout.setting.theme.options.light') },
{ type: 'dark', text: t('layout.setting.theme.options.dark') },
{ type: 'auto', text: t('layout.setting.theme.options.auto') },
];
]);

const initStyleConfig = () => {
const styleConfig = STYLE_CONFIG;
Expand Down Expand Up @@ -181,11 +181,11 @@ const handleCopy = () => {
copy()
.then(() => {
MessagePlugin.closeAll();
MessagePlugin.success('复制成功');
MessagePlugin.success(t('components.copySuccess'));
})
.catch(() => {
MessagePlugin.closeAll();
MessagePlugin.error('复制失败');
MessagePlugin.error(t('components.copyFail'));
});
};
const getModeIcon = (mode: string) => {
Expand Down
85 changes: 39 additions & 46 deletions src/locales/index.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,60 @@
import { useLocalStorage, usePreferredLanguages } from '@vueuse/core';
import type { DropdownOption } from 'tdesign-vue-next';
import en_US from 'tdesign-vue-next/es/locale/en_US';
import zh_CN from 'tdesign-vue-next/es/locale/zh_CN';
import { computed } from 'vue';
import type { I18nOptions } from 'vue-i18n';
import { createI18n } from 'vue-i18n';

// 导入语言文件
const langModules = import.meta.glob('./lang/*/index.ts', { eager: true });

const langModuleMap = new Map<string, unknown>();
export const localeConfigKey = 'tdesign-starter-locale';

export const langCode: Array<string> = [];
// 定义支持的语言列表,添加新语言时只需在此处添加
export const supportedLocales = ['zh_CN', 'en_US'] as const;
export type SupportedLocale = (typeof supportedLocales)[number];

export const localeConfigKey = 'tdesign-starter-locale';
// 多语言标题类型,用于路由 meta.title 等场景
export type LocalizedTitle = Record<SupportedLocale, string>;

// 获取浏览器默认语言环境
const languages = usePreferredLanguages();
const tdesignLocaleMap: Record<SupportedLocale, typeof zh_CN | typeof en_US> = { zh_CN, en_US };

// 生成语言模块列表
const generateLangModuleMap = () => {
const fullPaths = Object.keys(langModules);
fullPaths.forEach((fullPath) => {
const k = fullPath.replace('./lang', '');
const startIndex = 1;
const lastIndex = k.lastIndexOf('/');
const code = k.substring(startIndex, lastIndex);
langCode.push(code);
langModuleMap.set(code, langModules[fullPath]);
});
};
const langModules = import.meta.glob<{ default: Record<string, unknown> }>('./lang/*.json', { eager: true });

// 导出 Message
const importMessages = computed(() => {
generateLangModuleMap();
const langCode: SupportedLocale[] = [];
const messages: I18nOptions['messages'] = {};
const langList: DropdownOption[] = [];

const message: I18nOptions['messages'] = {};
langModuleMap.forEach((value: any, key) => {
message[key] = value.default;
});
return message;
Object.entries(langModules).forEach(([path, module]) => {
const code = path.match(/\.\/lang\/([^.]+)\.json$/)?.[1] as SupportedLocale | undefined;
if (!code || !supportedLocales.includes(code)) return;
langCode.push(code);
messages[code] = { ...module.default, componentsLocale: tdesignLocaleMap[code] };
langList.push({ content: module.default.lang as string, value: code });
});

export { langCode };

// 获取初始语言:优先本地存储,其次浏览器偏好,最后默认中文
const getInitialLocale = (): SupportedLocale => {
const stored = localStorage.getItem(localeConfigKey);
if (stored && supportedLocales.includes(stored as SupportedLocale)) {
return stored as SupportedLocale;
}
const preferred = navigator.languages?.[0]?.replace(/-/g, '_');
if (preferred && supportedLocales.includes(preferred as SupportedLocale)) {
return preferred as SupportedLocale;
}
return 'zh_CN';
};

const initialLocale = getInitialLocale();

export const i18n = createI18n({
legacy: false,
locale: useLocalStorage(localeConfigKey, 'zh_CN').value || languages.value[0] || 'zh_CN',
locale: initialLocale,
fallbackLocale: 'zh_CN',
Comment thread
RSS1102 marked this conversation as resolved.
messages: importMessages.value,
messages,
Comment thread
RSS1102 marked this conversation as resolved.
globalInjection: true,
});

export const langList = computed(() => {
if (langModuleMap.size === 0) generateLangModuleMap();

const list: DropdownOption[] = [];
langModuleMap.forEach((value: any, key) => {
list.push({
content: value.default.lang,
value: key,
});
});

return list;
});

export const languageList = computed(() => langList);
export const { t } = i18n.global;

export default i18n;
Loading