Skip to content

前端语言文件结构

本文档引用的文件

目录

  1. 项目结构分析
  2. 语言文件组织结构
  3. i18n 初始化与配置
  4. 国际化使用方式
  5. 动态加载与缓存机制
  6. 类型安全与开发调试

项目结构分析

项目 pc 模块下的 src/locales 目录是前端多语言实现的核心区域,包含以下关键文件:

  • zh-cn.ts:简体中文语言包
  • en.ts:英文语言包
  • i18n.ts:国际化逻辑处理与 API 调用封装
  • index.ts:语言环境初始化与默认语言选择
  • main.ts:Vue 应用入口,未直接集成 Vue I18n,而是采用自定义方案

该结构表明项目未使用标准的 vue-i18n 插件进行静态语言包管理,而是构建了一套基于服务器动态获取、本地缓存和按需加载的混合式国际化方案。

Section sources

语言文件组织结构

中文与英文语言文件

当前 zh-cn.tsen.ts 文件内容为空对象:

ts
export default {
  
};

这表明所有用户界面文本并非通过静态语言包预定义,而是从后端数据库动态获取。语言键(code)不以内联方式存储于前端,而是通过 API 请求按需加载。

嵌套对象结构设计

虽然当前文件为空,但根据 index.ts 的合并逻辑,其设计支持嵌套对象结构:

ts
import elementZhLocale from "element-plus/es/locale/lang/zh-cn";
import zhLocale from "./zh-cn";

const messages = {
  [elementZhLocale.name]: {
    ...zhLocale,
    ...elementZhLocale,
  },
};

此模式允许:

  • 模块化管理:将业务语言资源与第三方组件库(如 Element Plus)语言资源分离
  • 动态扩展:运行时可合并多个语言源
  • 覆盖机制:自定义语言可覆盖组件库默认文本

Section sources

i18n 初始化与配置

语言环境检测

getLocale() 函数实现智能语言检测:

ts
export function getLocale(): string {
  let usr = { lang: undefined };
  const usrStr = localStorage.getItem("usr");
  if (usrStr) {
    try {
      usr = JSON.parse(usrStr);
    } catch (e) { /* empty */ }
  }
  let lang = usr?.lang;
  if (lang) return lang;

  lang = navigator.language;
  if (["zh", "zh-cn", "zh-hans", "zh-hans-cn"].indexOf(lang.toLowerCase()) > -1) {
    lang = "zh-CN";
  }
  return lang;
}

检测优先级:

  1. 用户登录状态中保存的语言偏好
  2. 浏览器语言自动识别
  3. 默认 fallback 为 zh-CN

默认语言设置

通过 lang 常量导出当前语言,并在 messages 中查找对应语言包。若未找到则 fallback 到 zh-cn

Section sources

国际化使用方式

useI18n 组合式函数

useI18n() 是核心 API,返回一组翻译方法:

ts
export function useI18n(routePath?: string | null) {
  const usrStore = useUsrStore();
  const lang = usrStore.lang;
  return {
    n(code: string, ...args: any[]) { /* 路由绑定翻译 */ },
    nAsync(code: string, ...args: any[]) { /* 异步翻译 */ },
    initI18ns(codes: string[]) { /* 预加载多个code */ },
    ns(code: string, ...args: any[]) { /* 全局系统级翻译 */ },
    nsAsync(code: string, ...args: any[]) { /* 异步全局翻译 */ },
    initSysI18ns(codes: string[]) { /* 预加载系统级语言 */ }
  };
}

方法说明

方法名用途绑定路径
n()同步翻译,绑定当前路由route.path + code
nAsync()异步翻译route.path + code
ns()系统级同步翻译"" + code
nsAsync()系统级异步翻译"" + code

参数替换支持

支持两种占位符语法:

  • {0}{1}:按索引替换参数
  • {name}:按对象属性名替换
ts
// 示例
this.$t('Hello {0}, welcome to {site}', 'Alice', { site: 'OurApp' })

Section sources

动态加载与缓存机制

本地缓存管理

initI18nLblsLang() 负责初始化语言缓存:

ts
function initI18nLblsLang() {
  const i18nsLangStr = localStorage.getItem(`i18nLblsLang`);
  if (i18nsLangStr) {
    try {
      i18nLblsLang = JSON.parse(i18nsLangStr);
    } catch (e) { }
  }
  // 版本校验
  const __version = localStorage.getItem("__i18n_version");
  if (i18nLblsLang?.__version !== __version) {
    i18nLblsLang = undefined;
  }
  // 初始化当前语言空间
  if (!i18nLblsLang) {
    i18nLblsLang = { [lang]: {} };
  }
}

缓存策略特点:

  • 使用 localStorage 持久化存储
  • 支持版本控制(__i18n_version
  • 按语言分桶存储(i18nLblsLang[lang]

懒加载机制

getLbl() 实现按需加载:

ts
function getLbl(lang, code, routePath, ...args) {
  const key = `${routePath} ${code}`;
  let i18nLbl = i18nLbls[key];
  if (i18nLbl) {
    return setLblArgs(i18nLbl, args);
  }
  // 缓存不存在,异步请求
  (async function() {
    i18nLbls[key] = await n0(lang, routePath, code, { notLoading: true });
    localStorage.setItem(`i18nLblsLang`, JSON.stringify(i18nLblsLang));
  })();
  return setLblArgs(code, args); // 返回code作为临时文本
}

行为逻辑:

  1. 先查本地缓存
  2. 缓存命中 → 返回翻译文本
  3. 缓存未命中 → 后台异步请求,立即返回原始 code 作为占位符
  4. 请求完成后更新缓存并刷新响应式引用

流程图

Diagram sources

Section sources

类型安全与开发调试

类型安全方案

当前实现使用 any 类型,缺乏编译期类型检查:

ts
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...args: any[]

建议改进方向:

  • 定义 I18nKey 联合类型枚举所有语言键
  • 创建类型守卫函数确保 key 合法性
  • 使用模块声明合并增强 useI18n 返回类型

开发环境调试技巧

启用/禁用动态加载

通过环境变量控制行为:

ts
if (import.meta.env.VITE_SERVER_I18N_ENABLE === "false") {
  return setLblArgs(code, args); // 直接返回code
}

开发时可设置 VITE_SERVER_I18N_ENABLE=false 快速定位未翻译文本。

缓存清理

手动清除缓存以测试加载逻辑:

js
localStorage.removeItem('i18nLblsLang');
localStorage.removeItem('__i18n_version');

热更新支持

当前方案天然支持热更新:

  • 服务端更新语言文本
  • 客户端下次访问时因版本号变化自动重新加载
  • 无需重新构建前端

Section sources