Skip to content
第 139 / 250 章前端⏱ 10 分钟阅读

第 139 章:国际化 i18n

学习目标

  • 掌握 Vue I18n 在 Vue 3 中的集成
  • 学会多语言切换、消息格式化、复数处理
  • 理解命名空间、懒加载、复数形式
  • 在项目中实现完整的国际化方案

一、为什么需要国际化

二、Vue I18n

2.1 安装

bash
pnpm add vue-i18n@9

2.2 基础配置

typescript
// i18n/index.ts
import { createI18n } from 'vue-i18n';
import zhCN from './locales/zh-CN';
import enUS from './locales/en-US';

const i18n = createI18n({
  legacy: false,  // 使用 Composition API
  locale: 'zh-CN',
  fallbackLocale: 'en-US',
  messages: {
    'zh-CN': zhCN,
    'en-US': enUS
  }
});

export default i18n;

2.3 注入

typescript
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import i18n from './i18n';

const app = createApp(App);
app.use(i18n);
app.mount('#app');

三、消息文件

3.1 简单结构

typescript
// locales/zh-CN.ts
export default {
  hello: '你好',
  welcome: '欢迎,{name}!',
  items: '项 | 项 | 项'
};
typescript
// locales/en-US.ts
export default {
  hello: 'Hello',
  welcome: 'Welcome, {name}!',
  items: 'no items | one item | {count} items'
};

3.2 嵌套结构

typescript
export default {
  common: {
    save: '保存',
    cancel: '取消',
    delete: '删除',
    confirm: '确认'
  },
  user: {
    title: '用户管理',
    list: '用户列表',
    create: '新建用户',
    edit: '编辑用户',
    delete: '删除用户'
  },
  errors: {
    network: '网络错误',
    unauthorized: '未授权',
    serverError: '服务器错误'
  }
};

四、基本用法

4.1 Composition API

vue
<script setup lang="ts">
import { useI18n } from 'vue-i18n';

const { t, locale } = useI18n();

function switchLang() {
  locale.value = locale.value === 'zh-CN' ? 'en-US' : 'zh-CN';
}
</script>

<template>
  <p>{{ t('hello') }}</p>
  <p>{{ t('welcome', { name: 'Tom' }) }}</p>
  <button @click="switchLang">切换语言</button>
</template>

4.2 Options API

vue
<script>
export default {
  methods: {
    greet() {
      return this.$t('hello');
    }
  }
};
</script>

<template>
  <p>{{ $t('hello') }}</p>
</template>

4.3 嵌套 key

vue
<template>
  <button>{{ t('common.save') }}</button>
  <h1>{{ t('user.title') }}</h1>
</template>

五、参数插值

5.1 命名参数

typescript
// messages
{ greet: '你好,{name},你今年 {age} 岁' }
vue
<template>
  <p>{{ t('greet', { name: 'Tom', age: 18 }) }}</p>
</template>

5.2 列表参数

typescript
{ fruits: '我喜欢 {0} 和 {1}' }
vue
<p>{{ t('fruits', ['苹果', '香蕉']) }}</p>

5.3 链接

typescript
{
  policy: '请阅读我们的 <a href="/policy">隐私政策</a>'
}
vue
<p v-html="t('policy')"></p>

六、复数处理

6.1 简单复数

typescript
// zh-CN
{ items: '没有项 | {count} 项' }

// en-US
{ items: 'no items | one item | {count} items' }
vue
<p>{{ t('items', 0) }}</p>
<p>{{ t('items', 1) }}</p>
<p>{{ t('items', 5, { count: 5 }) }}</p>

6.2 复杂复数

typescript
// en-US
{ items: 'no items | one item | {count} items' }
vue
<p>{{ t('items', count, { count }) }}</p>

6.3 named 复数

typescript
{
  car: '{count} {count} car | {count} {count} cars'
  // 实际不这样写,Vue I18n 用 n() 函数
}
typescript
import { useI18n } from 'vue-i18n';
const { n } = useI18n();

n(1, 'car');      // 1 car
n(5, 'car');      // 5 cars

七、数字与日期

7.1 数字格式化

typescript
import { useI18n } from 'vue-i18n';

const { n } = useI18n();

n(1234.56);          // 1,234.56 (en) / 1,234.56 (zh)
n(1234.56, 'currency');  // $1,234.56
n(0.25, 'percent');     // 25%

7.2 日期格式化

typescript
const { d } = useI18n();

d(new Date());                       // 2026/8/13
d(new Date(), 'short');              // 2026/8/13
d(new Date(), 'long');               // 2026年8月13日
d(new Date(), {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

7.3 自定义格式

typescript
const i18n = createI18n({
  // ...
  numberFormats: {
    'zh-CN': {
      currency: { style: 'currency', currency: 'CNY' }
    },
    'en-US': {
      currency: { style: 'currency', currency: 'USD' }
    }
  },
  datetimeFormats: {
    'zh-CN': {
      short: { year: 'numeric', month: '2-digit', day: '2-digit' }
    }
  }
});
vue
<p>{{ n(price, 'currency') }}</p>
<p>{{ d(date, 'short') }}</p>

八、命名空间

8.1 创建

typescript
// composables/useI18n.ts
import { useI18n } from 'vue-i18n';

export function useModuleI18n() {
  return useI18n({
    messages: {
      'zh-CN': {
        local: {
          hello: '模块内你好'
        }
      },
      'en-US': {
        local: {
          hello: 'Module Hello'
        }
      }
    }
  });
}

8.2 使用

vue
<script setup lang="ts">
const { t } = useModuleI18n();
</script>

<template>
  <p>{{ t('local.hello') }}</p>
</template>

九、动态切换语言

9.1 切换函数

typescript
// composables/useLocale.ts
import { useI18n } from 'vue-i18n';

export function useLocale() {
  const { locale } = useI18n();

  function setLocale(lang: string) {
    locale.value = lang;
    localStorage.setItem('locale', lang);
    document.documentElement.lang = lang;
  }

  function initLocale() {
    const saved = localStorage.getItem('locale');
    if (saved) {
      locale.value = saved;
    }
  }

  return { locale, setLocale, initLocale };
}

9.2 持久化

typescript
// main.ts
const savedLocale = localStorage.getItem('locale') || 'zh-CN';

const i18n = createI18n({
  locale: savedLocale,
  // ...
});

9.3 切换组件

vue
<template>
  <select :value="locale" @change="handleChange">
    <option value="zh-CN">中文</option>
    <option value="en-US">English</option>
    <option value="ja-JP">日本語</option>
  </select>
</template>

<script setup lang="ts">
import { useLocale } from '@/composables/useLocale';

const { locale, setLocale } = useLocale();

function handleChange(e: Event) {
  setLocale((e.target as HTMLSelectElement).value);
}
</script>

十、懒加载语言包

10.1 动态导入

typescript
// i18n/index.ts
import { createI18n } from 'vue-i18n';

const i18n = createI18n({
  locale: 'zh-CN',
  messages: {}  // 初始为空
});

// 懒加载语言包
export async function loadLocale(lang: string) {
  const messages = await import(`./locales/${lang}.ts`);
  i18n.global.setLocaleMessage(lang, messages.default);
  i18n.global.locale.value = lang;
}

export default i18n;

10.2 使用

typescript
async function switchLang(lang: string) {
  await loadLocale(lang);
}

10.3 预加载

typescript
// 入口预加载默认语言
await loadLocale('zh-CN');

十一、组件库集成

11.1 Element Plus

typescript
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import en from 'element-plus/es/locale/lang/en';
import { useLocale } from 'element-plus';

const i18n = createI18n({
  // ...
  legacy: false
});

// 切换
const { locale: elLocale } = useLocale();
watch(locale, (val) => {
  elLocale.value = val === 'zh-CN' ? zhCn : en;
});

11.2 Naive UI

typescript
import { zhCN, enUS, dateZhCN, dateEnUS } from 'naive-ui';

// 在 n-config-provider 中
<n-config-provider :locale="naiveLocale" :date-locale="naiveDateLocale">
  <app />
</n-config-provider>

十二、复数规则

typescript
// i18n/index.ts
const i18n = createI18n({
  // ...
  pluralRules: {
    'ru': (choice, choicesLength) => {
      // 俄语复杂复数
      if (choice === 0) return 0;
      const teen = choice > 10 && choice < 20;
      const endsWithOne = choice % 10 === 1;
      if (!teen && endsWithOne) return 1;
      if (!teen && choice % 10 >= 2 && choice % 10 <= 4) return 2;
      return choicesLength < 4 ? 2 : 3;
    }
  }
});

十三、TypeScript 增强

13.1 模式定义

typescript
// types/i18n.d.ts
import 'vue-i18n';

declare module 'vue-i18n' {
  interface DefineLocaleMessage {
    hello: string;
    welcome: string;
    common: {
      save: string;
      cancel: string;
    };
  }
}

13.2 类型化 t

typescript
const { t } = useI18n();
t('hello');    // ✅ 强类型
t('unknown');  // ❌ 报错

十四、消息同步

14.1 服务端同步

typescript
async function loadMessagesFromServer(lang: string) {
  const res = await http.get(`/api/i18n/${lang}`);
  return res.data;
}

export async function loadLocale(lang: string) {
  const messages = await loadMessagesFromServer(lang);
  i18n.global.setLocaleMessage(lang, messages);
  i18n.global.locale.value = lang;
}

14.2 缓存

typescript
const messageCache = new Map<string, any>();

export async function loadLocale(lang: string) {
  if (messageCache.has(lang)) {
    i18n.global.setLocaleMessage(lang, messageCache.get(lang));
    return;
  }

  const messages = await loadMessagesFromServer(lang);
  messageCache.set(lang, messages);
  i18n.global.setLocaleMessage(lang, messages);
}

十五、SEO 适配

15.1 html lang

typescript
function setHtmlLang(lang: string) {
  document.documentElement.lang = lang;
}

15.2 标题

typescript
const route = useRoute();
const { t, locale } = useI18n();

watchEffect(() => {
  document.title = t(route.meta.title as string);
});

十六、翻译管理工具

工具特点
i18next业界标准
i18n-allyVSCode 插件
Crowdin在线协作
Lokalise翻译管理
locize实时翻译

十七、测试

typescript
import { mount } from '@vue/test-utils';
import { createI18n } from 'vue-i18n';

const i18n = createI18n({
  locale: 'zh-CN',
  messages: {
    'zh-CN': { hello: '你好' },
    'en-US': { hello: 'Hello' }
  }
});

it('renders', () => {
  const wrapper = mount(MyComponent, {
    global: { plugins: [i18n] }
  });
  expect(wrapper.text()).toContain('你好');
});

十八、本章小结

概念关键
基础createI18n + useI18n
消息key + 嵌套结构
复数n() + 语法
格式化n() / d()
切换setLocaleMessage
懒加载动态 import
命名空间composable 包裹
TypeScriptDefineLocaleMessage

动手练习

  1. 基础集成:配置 vue-i18n,实现中英文切换
  2. 消息文件:抽取所有文案到语言文件
  3. 参数化:实现带参数的国际化文案
  4. 复数:实现 items 数量的复数处理
  5. 懒加载:异步加载语言包,优化首屏

推荐阅读


下一章第 140 章:主题与暗色模式

本站基于 VitePress 构建 · 由 Codebook 团队维护