mirror of
https://github.com/dreamhunter2333/cloudflare_temp_email.git
synced 2026-09-05 07:27:27 +08:00
feat(i18n): enhance locale handling and routing (#996)
* feat(i18n): enhance locale handling and routing - Implemented dynamic locale aliases in router configuration. - Added support for preferred locale storage in global state. - Improved locale resolution logic in router beforeEach guard. - Created utility functions for locale management and path manipulation. - Added tests for locale matching and message extraction. - Updated Header component to allow language selection. - Refactored getRouterPathWithLang to utilize new locale utilities. - Updated Vite configuration to support aliasing for vue-i18n. - Bumped version numbers across various packages to 1.9.0. * feat(i18n): update version to 1.8.0 and enhance locale handling - Updated version numbers across all package.json files to 1.8.0. - Enhanced locale handling in App.vue by centralizing locale configurations. - Improved Turnstile component to support dynamic language rendering. - Refactored i18n utilities to include initial locale setup and empty locale messages. - Updated i18n.ts to utilize the new locale management structure. - Added naive-locale.ts for better integration with Naive UI's locale handling. - Adjusted Header.vue to streamline language selection and locale changes. - Fixed translations in multiple locale files for consistency and accuracy. * fix(i18n): address review feedback * feat(i18n): update default locale to English and enhance language handling in components * fix(i18n): switch locale selector to dropdown * docs: add topbar language and github order design spec * fix(i18n): 修复 Header 语言切换器相关问题,恢复为独立控件并调整样式 * Refactor locale handling in router and add locale-guard utility functions - Improved locale resolution logic in router by introducing utility functions for better readability and maintainability. - Added `locale-guard.js` to encapsulate locale-related functions such as getting route locale, resolving locale for navigation, and applying locale navigation state. - Updated JWT synchronization logic to streamline the handling of JWT from query parameters. - Modified i18n messages test to check for coverage of registered locale message keys instead of extracting English source messages. * 删除顶部栏语言和GitHub顺序设计文档 * fix: 修复前端设置初始化时未返回 domains 数组导致的 undefined 错误 * refactor(i18n): consolidate locale infrastructure * fix(i18n): stabilize locale route switching * fix(i18n): persist default locale selection * fix(i18n): 修复前端设置初始化时未返回 domains 数组导致的 undefined 错误,统一按空数组兜底处理 feat(i18n): 添加 locale 别名处理,支持默认语言的重定向 test(i18n): 增加对默认语言别名重定向的测试用例 * refactor: replace useAppI18n with useScopedI18n in multiple components for improved localization management * fix(tests): 移除不必要的 URL 断言以简化 Passkey 测试 * fix(i18n): 更新语言切换逻辑,确保使用当前语言设置进行路由导航 * fix(i18n): 强制路由切换以确保语言切换后正确导航 * refactor(i18n): 优化消息注册和路由本地化逻辑,移除冗余代码 * refactor(i18n): 拆分 API 文件以优化路由管理,更新语言处理逻辑 * fix: align i18n release notes and frontend test script
This commit is contained in:
+131
-78
@@ -1,26 +1,30 @@
|
||||
<script setup>
|
||||
import { ref, h, computed, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useScopedI18n } from '@/i18n/app'
|
||||
import { useHead } from '@unhead/vue'
|
||||
import { useRoute, useRouter, RouterLink } from 'vue-router'
|
||||
import { useIsMobile } from '../utils/composables'
|
||||
import {
|
||||
DarkModeFilled, LightModeFilled, MenuFilled,
|
||||
AdminPanelSettingsFilled, MonitorHeartFilled
|
||||
AdminPanelSettingsFilled, MonitorHeartFilled,
|
||||
KeyboardArrowDownOutlined
|
||||
} from '@vicons/material'
|
||||
import { GithubAlt, Language, User, Home } from '@vicons/fa'
|
||||
|
||||
import { useGlobalState } from '../store'
|
||||
import { api } from '../api'
|
||||
import { getRouterPathWithLang, hashPassword } from '../utils'
|
||||
import { DEFAULT_LOCALE, isSupportedLocale, replaceLocaleInFullPath } from '../i18n/utils'
|
||||
import { getLocaleLabel, SUPPORTED_LOCALES } from '../i18n/locale-registry'
|
||||
import Turnstile from '../components/Turnstile.vue'
|
||||
import { NButton, NIcon } from 'naive-ui'
|
||||
|
||||
const message = useMessage()
|
||||
const notification = useNotification()
|
||||
|
||||
const {
|
||||
toggleDark, isDark, isTelegram, showAdminPage,
|
||||
showAuth, auth, loading, openSettings, userSettings
|
||||
showAuth, auth, loading, openSettings, preferredLocale, userSettings
|
||||
} = useGlobalState()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -52,43 +56,52 @@ const authFunc = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
const changeLocale = async (lang) => {
|
||||
if (lang == 'zh') {
|
||||
await router.push(route.fullPath.replace('/en', ''));
|
||||
} else {
|
||||
await router.push(`/${lang}${route.fullPath}`);
|
||||
}
|
||||
}
|
||||
const languageOptions = SUPPORTED_LOCALES.map((locale) => ({
|
||||
label: getLocaleLabel(locale),
|
||||
value: locale,
|
||||
key: locale,
|
||||
}))
|
||||
|
||||
const { locale, t } = useI18n({
|
||||
messages: {
|
||||
en: {
|
||||
title: 'Cloudflare Temp Email',
|
||||
dark: 'Dark',
|
||||
light: 'Light',
|
||||
accessHeader: 'Access Password',
|
||||
accessTip: 'Please enter the correct access password',
|
||||
home: 'Home',
|
||||
menu: 'Menu',
|
||||
user: 'User',
|
||||
status: 'Status',
|
||||
ok: 'OK',
|
||||
},
|
||||
zh: {
|
||||
title: 'Cloudflare 临时邮件',
|
||||
dark: '暗色',
|
||||
light: '亮色',
|
||||
accessHeader: '访问密码',
|
||||
accessTip: '请输入站点访问密码',
|
||||
home: '主页',
|
||||
menu: '菜单',
|
||||
user: '用户',
|
||||
status: '状态',
|
||||
ok: '确定',
|
||||
}
|
||||
}
|
||||
const currentLocaleLabel = computed(() => {
|
||||
return languageOptions.find(opt => opt.value === locale.value)?.label || locale.value;
|
||||
});
|
||||
|
||||
const { t, locale } = useScopedI18n('views.Header')
|
||||
|
||||
const changeLocale = async (lang) => {
|
||||
if (!isSupportedLocale(lang)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentFullPath = route.fullPath;
|
||||
const targetFullPath = replaceLocaleInFullPath(currentFullPath, lang);
|
||||
|
||||
if (lang === locale.value && targetFullPath === currentFullPath) {
|
||||
showMobileMenu.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (lang === DEFAULT_LOCALE) {
|
||||
preferredLocale.value = DEFAULT_LOCALE;
|
||||
}
|
||||
|
||||
let localeSwitched = false;
|
||||
try {
|
||||
await router.push({ path: targetFullPath, force: true });
|
||||
localeSwitched = router.currentRoute.value.fullPath === targetFullPath;
|
||||
if (!localeSwitched) {
|
||||
await router.replace({ path: targetFullPath, force: true });
|
||||
localeSwitched = router.currentRoute.value.fullPath === targetFullPath;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to switch locale', error);
|
||||
} finally {
|
||||
showMobileMenu.value = false;
|
||||
}
|
||||
|
||||
if (localeSwitched) preferredLocale.value = lang;
|
||||
}
|
||||
|
||||
const version = import.meta.env.PACKAGE_VERSION ? `v${import.meta.env.PACKAGE_VERSION}` : "";
|
||||
|
||||
const menuOptions = computed(() => [
|
||||
@@ -172,27 +185,6 @@ const menuOptions = computed(() => [
|
||||
),
|
||||
key: "theme"
|
||||
},
|
||||
{
|
||||
label: () => h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
size: "small",
|
||||
style: "width: 100%",
|
||||
onClick: async () => {
|
||||
locale.value == 'zh' ? await changeLocale('en') : await changeLocale('zh');
|
||||
showMobileMenu.value = false;
|
||||
}
|
||||
},
|
||||
{
|
||||
default: () => locale.value == 'zh' ? "English" : "中文",
|
||||
icon: () => h(
|
||||
NIcon, { component: Language }
|
||||
)
|
||||
}
|
||||
),
|
||||
key: "lang"
|
||||
},
|
||||
{
|
||||
label: () => h(
|
||||
NButton,
|
||||
@@ -211,25 +203,6 @@ const menuOptions = computed(() => [
|
||||
),
|
||||
show: !!openSettings.value?.statusUrl,
|
||||
key: "status"
|
||||
},
|
||||
{
|
||||
label: () => h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
size: "small",
|
||||
style: "width: 100%",
|
||||
tag: "a",
|
||||
target: "_blank",
|
||||
href: "https://github.com/dreamhunter2333/cloudflare_temp_email",
|
||||
},
|
||||
{
|
||||
default: () => version || "Github",
|
||||
icon: () => h(NIcon, { component: GithubAlt })
|
||||
}
|
||||
),
|
||||
show: openSettings.value?.showGithub,
|
||||
key: "github"
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -279,7 +252,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</template>
|
||||
<template #extra>
|
||||
<n-space>
|
||||
<n-space align="center" class="header-extra">
|
||||
<n-menu v-if="!isMobile" mode="horizontal" :options="menuOptions" responsive />
|
||||
<n-button v-else :text="true" @click="showMobileMenu = !showMobileMenu" style="margin-right: 10px;">
|
||||
<template #icon>
|
||||
@@ -287,12 +260,58 @@ onMounted(async () => {
|
||||
</template>
|
||||
{{ t('menu') }}
|
||||
</n-button>
|
||||
<n-dropdown :options="languageOptions" @select="changeLocale" trigger="click" class="header-locale-dropdown">
|
||||
<n-button text size="small" class="header-locale-button" style="padding: 0 10px;">
|
||||
<template #icon>
|
||||
<n-icon :component="Language" />
|
||||
</template>
|
||||
{{ currentLocaleLabel }}
|
||||
<n-icon :component="KeyboardArrowDownOutlined" style="margin-left: 4px;" />
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-button
|
||||
v-if="openSettings.showGithub"
|
||||
text
|
||||
size="small"
|
||||
class="header-version-button"
|
||||
tag="a"
|
||||
target="_blank"
|
||||
href="https://github.com/dreamhunter2333/cloudflare_temp_email"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="GithubAlt" />
|
||||
</template>
|
||||
{{ version || 'Github' }}
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-page-header>
|
||||
<n-drawer v-model:show="showMobileMenu" placement="top" style="height: 100vh;">
|
||||
<n-drawer-content :title="t('menu')" closable>
|
||||
<n-menu :options="menuOptions" />
|
||||
<n-dropdown :options="languageOptions" @select="changeLocale" trigger="click" class="header-locale-dropdown">
|
||||
<n-button text class="header-locale-button" style="margin-top: 12px;">
|
||||
<template #icon>
|
||||
<n-icon :component="Language" />
|
||||
</template>
|
||||
{{ currentLocaleLabel }}
|
||||
<n-icon :component="KeyboardArrowDownOutlined" style="margin-left: 4px;" />
|
||||
</n-button>
|
||||
</n-dropdown>
|
||||
<n-button
|
||||
v-if="openSettings.showGithub"
|
||||
text
|
||||
class="header-version-button"
|
||||
style="margin-top: 12px;"
|
||||
tag="a"
|
||||
target="_blank"
|
||||
href="https://github.com/dreamhunter2333/cloudflare_temp_email"
|
||||
>
|
||||
<template #icon>
|
||||
<n-icon :component="GithubAlt" />
|
||||
</template>
|
||||
{{ version || 'Github' }}
|
||||
</n-button>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
<n-modal v-model:show="showAuth" :closable="false" :closeOnEsc="false" :maskClosable="false" preset="dialog"
|
||||
@@ -316,6 +335,40 @@ onMounted(async () => {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-extra {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-extra :deep(.n-space-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-locale-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-locale-button :deep(.n-button__content) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-locale-button :deep(.n-icon) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-version-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-version-button :deep(.n-button__content) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.n-alert {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
Reference in New Issue
Block a user