tool.xpcool.com/src/i18n/index.ts
夏犀麟 af233e1628
All checks were successful
Build and Deploy (tool.xpcool.com) / build-and-deploy (push) Successful in 1m6s
feat: 视觉重设计 + 中英日韩四语言 + 三态昼夜主题 + 服务端接口对接
视觉体系(核心是「令牌 → 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
2026-09-14 02:05:38 +08:00

98 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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]
/** 语言选择本地存储 keyindex.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 {
// 忽略存储失败
}
}