All checks were successful
Build and Deploy (tool.xpcool.com) / build-and-deploy (push) Successful in 1m6s
视觉体系(核心是「令牌 → TDesign 桥接」) - 新增 tokens.scss:--tk-* 设计令牌(青玉 Jade 单一强调色、冷调中性色阶、 圆角/间距/字号/z-index/动效规范、按语言切换字体栈) - 新增 tdesign-theme.scss:--tk-* → --td-* 桥接(含完整冷调灰阶 1-14) → 60+ 个未改动的存量工具页零成本继承新配色与圆角 - index.scss 重写:只过渡颜色、统一 focus-visible、reduced-motion 降级 - 新增 v-reveal 滚动进入指令 布局与交互 - MainLayout 重写:毛玻璃 sticky 顶栏 + 三区布局 + 页脚(含备案号) - 新增 CommandPalette(⌘K/Ctrl+K)、LocaleSwitch(显示语言代码不用国旗)、 ThemeSwitch(三态;图标显示生效 mode、菜单勾选用户偏好 preference) - SideNav 重写:内联筛选、可折叠分组持久化、活动态指示条、需联网标记 - HomeView 重写:左对齐 Hero + 真实可用搜索 + 概览数字条 + 服务端工具专区 国际化与主题 - vue-i18n 11 + zh-CN/en-US/ja-JP/ko-KR 完整语言包(58 个工具名与描述全译) - helpers 取词带中文兜底;index.html 防闪烁内联脚本与 store 三处同步 - 主题三态 light/dark/auto,preference 与 mode 分离 服务端对接(service.xpcool.com /api/service/open/tools/*) - 新增 openTools.ts:接口元数据与调用函数共用同一份路径定义 - 改造/新建 6 页:ip-info、uuid、hash(服务端 MD5 两端口径对比)、 timestamp(服务端时钟 + 时钟偏移)、random-string、api-status - 均为「本机 / 服务端」双模式,默认本机、不自动打网络请求 - 后端未实现的 5 个接口用 BackendPending 如实标注,不伪装可用 修复 - 空分类渲染缺陷(health/study/travel 无工具却渲染空区块),分类 9 → 6 - UnoCSS panel 快捷类补内边距;IpInfoView 漏 onBeforeUnmount; SideNav 末尾 import;HomeView 错误导入 SUPPORTED_LOCALES 其他 - 新增 src/config.ts 集中站点常量,页脚展示备案号并链工信部核验 - docs/api-contract.md 按线上实际重写;PROJECT_STATE.md 全面更新 - 构建通过:vue-tsc --noEmit + vite build
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
import { createI18n } from 'vue-i18n'
|
||
import zhCN from './locales/zh-CN'
|
||
import enUS from './locales/en-US'
|
||
import jaJP from './locales/ja-JP'
|
||
import koKR from './locales/ko-KR'
|
||
|
||
/**
|
||
* 国际化(i18n)基础设施。
|
||
*
|
||
* 支持语言:简体中文 / 英语 / 日语 / 韩语。
|
||
* 语言判定优先级:本地存储(用户手动选择) > 浏览器语言 > 默认简体中文。
|
||
*
|
||
* 使用方式:
|
||
* 模板中 {{ $t('tool.uuid.name') }}
|
||
* 脚本中 const { t } = useI18n() → t('tool.uuid.name')
|
||
*
|
||
* 新增工具/页面文案时,请同时补齐 4 个语言包的同一个 key,
|
||
* 否则缺失语言会回退到 fallbackLocale(简体中文),不会报错但会出现中英混排。
|
||
*/
|
||
|
||
/** 站点支持的语言列表 */
|
||
export const SUPPORTED_LOCALES = ['zh-CN', 'en-US', 'ja-JP', 'ko-KR'] as const
|
||
|
||
export type AppLocale = (typeof SUPPORTED_LOCALES)[number]
|
||
|
||
/** 语言选择本地存储 key(index.html 内联脚本会读取它,用于首屏防闪烁) */
|
||
export const LOCALE_STORAGE_KEY = 'tool-locale'
|
||
|
||
/** 兜底语言:简体中文(语言包最完整,作为所有缺失 key 的回退) */
|
||
export const FALLBACK_LOCALE: AppLocale = 'zh-CN'
|
||
|
||
const messages = {
|
||
'zh-CN': zhCN,
|
||
'en-US': enUS,
|
||
'ja-JP': jaJP,
|
||
'ko-KR': koKR,
|
||
}
|
||
|
||
/** 判断给定值是否为受支持的语言代码 */
|
||
export function isAppLocale(value: unknown): value is AppLocale {
|
||
return typeof value === 'string' && (SUPPORTED_LOCALES as readonly string[]).includes(value)
|
||
}
|
||
|
||
/**
|
||
* 把浏览器语言标签(如 zh-Hant-TW、en-GB)归一化到站点支持的语言。
|
||
* 匹配规则:先精确匹配,再按主语言前缀匹配,最后回退默认语言。
|
||
*/
|
||
export function normalizeLocale(raw: string | null | undefined): AppLocale {
|
||
if (!raw) return FALLBACK_LOCALE
|
||
if (isAppLocale(raw)) return raw
|
||
const primary = raw.toLowerCase().split('-')[0]
|
||
const matched = SUPPORTED_LOCALES.find((l) => l.toLowerCase().split('-')[0] === primary)
|
||
return matched ?? FALLBACK_LOCALE
|
||
}
|
||
|
||
/** 解析初始语言:本地存储优先,其次浏览器语言 */
|
||
export function resolveInitialLocale(): AppLocale {
|
||
try {
|
||
const saved = localStorage.getItem(LOCALE_STORAGE_KEY)
|
||
if (isAppLocale(saved)) return saved
|
||
} catch {
|
||
// 隐私模式等场景下 localStorage 不可用,忽略即可
|
||
}
|
||
return normalizeLocale(typeof navigator !== 'undefined' ? navigator.language : null)
|
||
}
|
||
|
||
export const i18n = createI18n({
|
||
// Composition API 模式(Vue 3 推荐),同时开启全局注入让模板可直接用 $t
|
||
legacy: false,
|
||
globalInjection: true,
|
||
locale: resolveInitialLocale(),
|
||
fallbackLocale: FALLBACK_LOCALE,
|
||
// 缺失 key 时不在控制台刷警告(开发期可临时改回 true 排查)
|
||
missingWarn: false,
|
||
fallbackWarn: false,
|
||
messages,
|
||
})
|
||
|
||
/**
|
||
* 同步语言到 DOM:写入 html[lang]。
|
||
* html[lang] 会影响字体栈选择(见 tokens.scss)与搜索引擎/读屏软件的语言判断,
|
||
* 因此切语言时必须一并更新。
|
||
*/
|
||
export function applyLocaleToDocument(locale: AppLocale) {
|
||
document.documentElement.setAttribute('lang', locale)
|
||
}
|
||
|
||
/** 切换语言:更新 i18n 实例、DOM 属性与本地存储 */
|
||
export function setAppLocale(locale: AppLocale) {
|
||
i18n.global.locale.value = locale
|
||
applyLocaleToDocument(locale)
|
||
try {
|
||
localStorage.setItem(LOCALE_STORAGE_KEY, locale)
|
||
} catch {
|
||
// 忽略存储失败
|
||
}
|
||
}
|