Vue国际化实现多语言方案

一、目录结构设计

建议将多语言资源集中管理,典型目录结构:

plaintext 复制代码
src/
├── lang/                # 多语言核心目录
│   ├── index.js         # i18n 配置入口
│   ├── zh.js            # 中文
│   ├── en.js            # 英文
│   └── ar.js            # 阿拉伯
├── main.js              # Vue 入口
├── components/          # 业务组件
└── views/               # 页面组件

二、编写语言包

每个语言包导出键值对格式的文本资源,键名保持统一,值为对应语言的文本。

  • src/lang/zh.js(中文):
js 复制代码
export default {
  common: {
    title: 'Vue2 项目',
    welcome: '欢迎 {name} 登录,今日剩余次数:{count}',
    cancel: '取消',
    confirm: '确认',
    placeholder: '请输入内容',
    item: '项目', // 单数 | 复数
    count: '您有 {count} 个项目 | 您有 {count} 个项目',
  },
  login: {
    title: '用户登录',
    user: {
      name: '用户名',
    },
  },
}
  • src/lang/en.js(英文):
js 复制代码
export default {
  common: {
    title: 'Vue2 Lighthouse Demo',
    welcome: 'welcome {name} login, today remaining times: {count}',
    cancel: 'Cancel',
    confirm: 'Confirm',
    placeholder: 'Please enter content',
    item: 'item | items', // 单数 | 复数
    count: 'You have {count} item | You have {count} items',
  },
  login: {
    title: 'User Login',
    user: {
      name: 'Username',
    },
  }
}
  • src/lang/ar.js(阿拉伯):
js 复制代码
// 阿拉伯语语言包 - 注意:阿拉伯语是RTL(从右到左)语言

export default {
  common: {
    title: 'عرض Vue2 لـ Lighthouse',
    welcome: 'مرحبًا بك {name} تسجيل الدخول، المرات المتبقية اليوم: {count}',
    cancel: 'إلغاء',
    confirm: 'تأكيد',
    submit: 'تأكيدتأ تأكيدتأ تأكيدتأ كيدتأكيد',
    placeholder: 'الرجاء إدخال المحتوى',
    item: 'عنصر | عناصر', // مفرد | جمع
    count: 'لديك {count} عنصر | لديك {count} عناصر',
    price: 'السعر: {amount}', // 带变量的文本(变量位置不影响RTL)
    mixedText: "الرقم الطلبي: <span dir='ltr'>#12345</span>", // 混合文本(数字强制LTR)
  },
  login: {
    title: 'تسجيل دخول المستخدم',
    user: {
      name: 'اسم المستخدم',
    },
  },
}

三、配置 vue-i18n

src/lang/index.js 中初始化 i18n 实例:

javascript 复制代码
import Vue from 'vue'
import VueI18n from 'vue-i18n'

Vue.use(VueI18n)

// 按需引入配置
// const messages = {
//   en: () => import('./en'),
//   zh: () => import('./zh'),
// }

import en from './en'
import zh from './zh'
import ar from './ar' // 导入阿拉伯语
const messages = {
  en,
  zh,
  ar, // 注册阿拉伯语
}
// 获取默认语言(优先级:本地存储 > 浏览器语言 > 简体中文)
const getDefaultLang = () => {
  const lang =
    localStorage.getItem('lang') || navigator.language || navigator.userLanguage
  console.log('lang', lang)

  if (lang && messages[lang]) {
    // 设置html根节点的dir和lang属性(核心!)
    document.documentElement.dir = lang === 'ar' ? 'rtl' : 'ltr'
    document.documentElement.lang = lang
    return lang
  } else {
    // 兜底:如果浏览器语言不支持,默认使用简体中文
    return 'zh'
  }
}
const i18n = new VueI18n({
  locale: getDefaultLang(), // 当前语言标识
  fallbackLocale: 'zh', // 语言不存在时的兜底语言
  // 按需加载
  // messages: {},
  // 同步加载
  messages,
  // 防止页面闪烁
  silentTranslationWarn: true,
  dateTimeFormats: {
    zh: {
      short: {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
      },
    },
    en: {
      short: {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
      },
    },
    ar: {
      short: {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit',
      },
    },
  },
  numberFormats: {
    zh: {
      currency: {
        style: 'currency',
        currency: 'CNY',
      },
    },
    en: {
      currency: {
        style: 'currency',
        currency: 'USD',
      },
    },
    ar: {
      currency: {
        style: 'currency',
        currency: 'EGP', // 埃及镑,阿拉伯语国家常用货币
      },
    },
  },
})

// 动态加载默认语言包
// const loadLang = async (lang) => {
//   const langModule = await messages[lang]()
//   console.log('langModule', langModule)
//   i18n.setLocaleMessage(lang, langModule.default)
//   i18n.locale = lang
// }
// loadLang(getDefaultLang())

export default i18n

在 Vue 入口注册 i18n

修改 src/main.js

js 复制代码
import Vue from 'vue'
import App from './App.vue'
import i18n from './lang' // 导入 i18n 实例

Vue.config.productionTip = false

new Vue({
  i18n, // 注入到 Vue 实例
  render: h => h(App)
}).$mount('#app')

四、在项目中使用多语言

1. 模板中使用(核心用法)

  • 基础用法:$t('键名')
  • 嵌套键名:$t('common.submit')
html 复制代码
<template>
  <div>
    <el-select v-model="value" placeholder="请选择语言" @change="handleChange">
      <el-option label="中文" value="zh"> </el-option>
      <el-option label="英文" value="en"> </el-option>
      <el-option label="阿拉伯" value="ar"> </el-option>
    </el-select>
    <div class="content">
      <p>传参:{{ $t('common.welcome', { name: '生华', count: '10' }) }}</p>
      <p>{{ $t('common.cancel') }}</p>
      <p>{{ $t('common.confirm') }}</p>
      <p>{{ $t('login.title') }}</p>
      <p>三层翻译:{{ $t('login.user.name') }}</p>
      <!-- 基本用法 -->
      <p>单数:{{ $tc('common.item', 1) }}</p>
      <!-- 输出: item -->
      <p>复数:{{ $tc('common.item', 3) }}</p>
      <!-- 输出: items -->

      <!-- 带变量的用法 -->
      <p>{{ $tc('common.count', 1, { count: 1 }) }}</p>
      <!-- 输出: You have 1 item -->
      <p>{{ $tc('common.count', 5, { count: 5 }) }}</p>
      <!-- 输出: You have 5 items -->
      <div>
        水印<el-input
          style="width: 300px"
          v-model="lang"
          :placeholder="$t('common.placeholder')"
        />
      </div>
      <p>
        <span>日期:</span>
        <b>{{ $d(new Date()) }}</b>
      </p>
      <p>
        数字:
        {{ $n(123.22, 'currency') }}
      </p>
      <p>
        箭头图标:
        <i class="arrow-icon el-icon-right"></i>
      </p>
      <!-- 带变量的文本(数字格式化) -->
      <p>{{ $t('common.price', { amount: formatArabicNumber(99.99) }) }}</p>

      <!-- 混合文本(阿拉伯语+数字,需保留 HTML) -->
      <p v-html="$t('common.mixedText')"></p>
    </div>
    <div>组件内自定义文案:<language-private /></div>
    <!-- 适配多语言文本长度的按钮 -->
    <button class="flex-btn">{{ $t('common.submit') }}</button>
  </div>
</template>

2. 脚本中使用

  • 实例内:this.$t('键名')
  • 全局 / 非组件环境:导入 i18n 实例,使用 i18n.t('键名')
javascript 复制代码
// 组件内
export default {
  methods: {
    submit() {
      if (!this.username) {
        alert(this.$t('error.empty'))
      }
    }
  }
}

// 非组件环境(如 router.js)
  // 异步加载语言包路由也要使用异步
  // setTimeout(() => {
  document.title = i18n.t(String(to.meta.title))
  // }, 500)

3. 动态切换语言

实现语言切换按钮,修改 i18n.locale 即可:

js 复制代码
<template>
  <div>
    <el-select v-model="value" placeholder="请选择语言" @change="handleChange">
      <el-option label="中文" value="zh"> </el-option>
      <el-option label="英文" value="en"> </el-option>
      <el-option label="阿拉伯" value="ar"> </el-option>
    </el-select>
  <div>    
<template>
<script>
  methods: {
    handleChange (val) {
      this.$i18n.locale = val
      localStorage.setItem('lang', val)
      // 可选:刷新页面(部分场景需要,如路由、组件内硬编码文本)
      window.location.reload()
    },
    // 格式化数字为阿拉伯-Indic数字(٩٩.٩٩ 而非 99.99)
    formatArabicNumber (num) {
      if (this.$i18n.locale !== 'ar') return num;
      // 阿拉伯数字映射表
      const digitMap = {
        '0': '٠', '1': '١', '2': '٢', '3': '٣', '4': '٤',
        '5': '٥', '6': '٦', '7': '٧', '8': '٨', '9': '٩', '.': '.'
      };
      return num.toString().split('').map(d => digitMap[d] || d).join('');
    }
  },
}
</script>

五、高级用法

1. 带参数的翻译(插值)

如果文本需要动态替换内容,语言包中使用 {变量名},调用时传入参数:

  • 语言包(zh-CN.js):
js 复制代码
export default {
  common: {
    welcome: '欢迎 {name} 登录,今日剩余次数:{count}'
  }
}
  • 使用:
js 复制代码
<!-- 模板中 -->
<div>{{ $t('common.welcome', { name: '张三', count: 5 }) }}</div>

<!-- 脚本中 -->
this.$t('common.welcome', { name: '张三', count: 5 })

2. 复数处理

不同语言的复数规则不同(如英文有单复数,中文无),vue-i18n 支持复数格式化:

  • 语言包(en-US.js):
js 复制代码
export default {
  common: {
    item: 'item | items', // 单数 | 复数
    count: 'You have {count} {count, plural, one {item} other {items}}'
  }
}
  • 使用:
js 复制代码
<div>{{ $t('common.count', { count: 1 }) }}</div> <!-- You have 1 item -->
<div>{{ $t('common.count', { count: 5 }) }}</div> <!-- You have 5 items -->

3. 日期 / 数字格式化

vue-i18n 内置日期、数字格式化能力,需结合 $d(日期)、$n(数字)使用:

  • 模板中:
html 复制代码
<!-- 日期格式化 -->
<div>{{ $d(new Date(), 'short') }}</div> <!-- 2025/12/17(中文)| 12/17/2025(英文) -->

<!-- 数字格式化 -->
<div>{{ $n(12345.67, 'currency') }}</div> <!-- ¥12,345.67(中文)| $12,345.67(英文) -->
  • 配置格式化规则(在 lang/index.js 中):
js 复制代码
const i18n = new VueI18n({
  // ... 其他配置
  dateTimeFormats: {
    'zh-CN': {
      short: {
        year: 'numeric', month: '2-digit', day: '2-digit'
      }
    },
    'en-US': {
      short: {
        year: 'numeric', month: '2-digit', day: '2-digit'
      }
    }
  },
  numberFormats: {
    'zh-CN': {
      currency: {
        style: 'currency', currency: 'CNY'
      }
    },
    'en-US': {
      currency: {
        style: 'currency', currency: 'USD'
      }
    }
  }
})

4. 路由国际化

如果某个组件的文本仅在内部使用,可在组件内定义局部语言包:

js 复制代码
  // 异步加载语言包路由也要使用异步
  // setTimeout(() => {
  document.title = i18n.t(String(to.meta.title))
  // }, 500)

5. 动态加载

如果某个组件的文本仅在内部使用,可在组件内定义局部语言包:

js 复制代码
// lang/index.js

// 按需引入配置
const messages = {
  en: () => import('./en'),
  zh: () => import('./zh'),
}

// 动态加载默认语言包  
const loadLang = async (lang) => {
  const langModule = await messages[lang]()
  console.log('langModule', langModule)
  i18n.setLocaleMessage(lang, langModule.default)
  i18n.locale = lang
}
loadLang(getDefaultLang())

5. 阿拉伯语言特殊处理

  1. 切换阿拉伯语后,页面文本从右向左排列,字符无乱码;
  2. 混合文本(如 "السعر: ٩٩.٩٩")中数字保持 LTR 方向,整体排版正常;
  3. 图标、表单、按钮等组件方向适配,交互逻辑自洽。
js 复制代码
// index.html

      /* 全局样式:阿拉伯语字体 + 行高适配 */
      html[lang='ar'] {
        font-family: 'Noto Naskh Arabic', serif;
        line-height: 2.4; /* 阿拉伯字符高度高,避免文字重叠 */
      }
      html[lang='ar'] .arrow-icon {
        transform: scaleX(-1); /* 水平翻转 */
      }
      /* 全局 RTL 布局基础 */
      html[dir='rtl'] {
        direction: rtl;
        unicode-bidi: embed; /* 处理混合文本(阿拉伯+数字/英文) */
        text-align: right; /* 文本默认右对齐 */
      }
      
      <p>
        箭头图标:
        <i class="arrow-icon el-icon-right"></i>
      </p>
      <!-- 带变量的文本(数字格式化) -->
      <p>{{ $t('common.price', { amount: formatArabicNumber(99.99) }) }}</p>  
      
    formatArabicNumber (num) {
      if (this.$i18n.locale !== 'ar') return num;
      // 阿拉伯数字映射表
      const digitMap = {
        '0': '٠', '1': '١', '2': '٢', '3': '٣', '4': '٤',
        '5': '٥', '6': '٦', '7': '٧', '8': '٨', '9': '٩', '.': '.'
      };
      return num.toString().split('').map(d => digitMap[d] || d).join('');
    }      

6. 长度适配

  1. 所有容器「弹性宽度 + 最小 / 最大宽度」替代固定尺寸;
  2. 文本「单行省略 / 多行截断」作为兜底;
  3. 针对不同语言动态调整字体大小 / 内边距;
  4. 关键组件通过 JS 计算文本宽度,精准适配;
  5. 测试覆盖最长 / 最短语言 + 小屏场景。

五、注意事项

  1. 语言标识规范 :建议使用 zh-CNen-US 等 BCP 47 规范标识,避免自定义(如 zhen)导致浏览器语言匹配问题。

  2. 避免硬编码 :所有展示文本必须抽离到语言包,禁止在模板 / 脚本中直接写死(如 alert('提交成功') 需改为 alert(this.$t('common.success')))。

  3. 第三方组件适配 :如果使用 Element UI 等组件库,需单独配置其多语言(Element UI 有内置语言包,可结合 vue-i18n 整合):

js 复制代码
// main.js
import ElementUI from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
import enLocale from 'element-ui/lib/locale/lang/en'
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
import i18n from './lang'

// 整合 Element UI 语言包
ElementUI.i18n((key, value) => i18n.t(key, value))
Vue.use(ElementUI, {
  locale: i18n.locale === 'en-US' ? enLocale : zhLocale
})
相关推荐
恋猫de小郭1 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅7 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60618 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了8 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅8 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅9 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅9 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment9 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅9 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端