Vue 3 项目实现国际化指南 i18n
引言
在开发现代 Web 应用时,国际化(Internationalization,简称 i18n)已经成为一个不可或缺的功能。无论是面向全球用户的商业网站,还是需要支持多语言的企业应用,良好的国际化支持都能显著提升用户体验。本文将深入介绍如何在 Vue 3 项目中实现国际化,从基础概念到实践细节,帮助你构建一个真正的多语言应用。
基础概念
什么是国际化(i18n)?
国际化(i18n)是指设计和开发软件时,使其能够适应不同语言和地区的过程。"i18n" 这个缩写来源于 "internationalization" 这个词,其中 18 表示首字母 'i' 和末字母 'n' 之间有 18 个字母。
为什么需要 JSON 文件?
在 Vue 3 的国际化实现中,我们使用 JSON 文件来存储不同语言的翻译文本。选择 JSON 格式有以下几个原因:
- 结构化数据:JSON 提供了清晰的层次结构,便于组织和管理翻译文本
- 易于维护:可以方便地添加、修改和删除翻译内容
- 跨平台兼容:JSON 是一种通用的数据格式,可以被各种工具和平台处理
- 支持嵌套:可以创建层次化的翻译结构,更好地组织大型应用的翻译
JSON 文件的来源
翻译文件(JSON)可以通过以下几种方式获得:
- 手动创建:
- 适合小型项目或初始开发阶段
- 开发者直接编写翻译文本
- 示例
{"nav": {"home": "首页","about": "关于"}}
2.翻译工具生成:
- 使用专业的翻译管理系统(TMS)
- 支持批量翻译和导出
- 常用工具:
- POEditor
- Lokalise
- Crowdin
3.自动化脚本生成:
- 使用脚本从其他格式转换
- 从数据库导出
详细安装步骤
1. 创建 Vue 3 项目
# 使用 Vite 创建项目
npm create vite@latest my-vue-app -- --template vue-ts# 进入项目目录
cd my-vue-app# 安装依赖
npm install
2. 安装 vue-i18n
npm install vue-i18n@9
3. 项目结构设置
src/
├── locales/ # 翻译文件目录
│ ├── en.json # 英文翻译
│ └── zh.json # 中文翻译
├── i18n/ # i18n 配置目录
│ ├── index.ts # 主配置文件
│ └── messages.ts # 消息加载器
├── components/ # 组件目录
└── App.vue # 根组件
4. 创建基础翻译文件
src/locales/zh.json:
{"nav": {"home": "首页","blog": "博客","about": "关于","contact": "联系"},"home": {"welcome": "欢迎来到我的网站","description": "这是一个使用 Vue 3 和 i18n 构建的多语言网站","features": {"title": "主要特点","list": {"1": "支持多语言切换","2": "响应式设计","3": "用户友好界面"}}},"common": {"loading": "加载中...","error": "发生错误","success": "操作成功","buttons": {"submit": "提交","cancel": "取消","save": "保存"}}
}
src/locales/en.json:
{"nav": {"home": "Home","blog": "Blog","about": "About","contact": "Contact"},"home": {"welcome": "Welcome to my website","description": "This is a multilingual website built with Vue 3 and i18n","features": {"title": "Key Features","list": {"1": "Multi-language support","2": "Responsive design","3": "User-friendly interface"}}},"common": {"loading": "Loading...","error": "An error occurred","success": "Operation successful","buttons": {"submit": "Submit","cancel": "Cancel","save": "Save"}}
}
5. 配置 i18n
src/i18n/messages.ts:
import en from '../locales/en.json'
import zh from '../locales/zh.json'export const messages = {en,zh
}// 类型定义
export type MessageSchema = typeof zh
src/i18n/index.ts:
import { createI18n } from 'vue-i18n'
import { messages } from './messages'
import type { MessageSchema } from './messages'// 获取浏览器语言设置
const getBrowserLanguage = (): string => {const lang = navigator.languagereturn lang.toLowerCase().startsWith('zh') ? 'zh' : 'en'
}// 获取存储的语言设置
const getSavedLanguage = (): string => {return localStorage.getItem('language') || getBrowserLanguage()
}export const i18n = createI18n<[MessageSchema], 'en' | 'zh'>({legacy: false, // 启用 Composition API 模式locale: getSavedLanguage(),fallbackLocale: 'en',messages,// 数字格式化选项numberFormats: {en: {currency: {style: 'currency',currency: 'USD'}},zh: {currency: {style: 'currency',currency: 'CNY'}}},// 日期格式化选项datetimeFormats: {en: {short: {year: 'numeric',month: 'short',day: 'numeric'}},zh: {short: {year: 'numeric',month: 'long',day: 'numeric'}}}
})
实际使用示例
1. 基础组件使用
src/components/LanguageSwitcher.vue:
<template><div class="language-switcher"><select v-model="currentLocale" @change="handleLanguageChange"><option value="zh">中文</option><option value="en">English</option></select></div>
</template><script setup lang="ts">
import { ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'const { locale } = useI18n()
const currentLocale = ref(locale.value)const handleLanguageChange = () => {// 更新语言设置locale.value = currentLocale.value// 保存到本地存储localStorage.setItem('language', currentLocale.value)// 可选:刷新页面以应用新语言// window.location.reload()
}// 监听语言变化
watch(locale, (newLocale) => {document.documentElement.setAttribute('lang', newLocale)
})
</script><style scoped>
.language-switcher {padding: 8px;
}select {padding: 4px 8px;border-radius: 4px;border: 1px solid #ddd;
}
</style>
2. 在页面中使用翻译
src/components/HomePage.vue:
<template><div class="home"><h1>{{ t('home.welcome') }}</h1><p>{{ t('home.description') }}</p><div class="features"><h2>{{ t('home.features.title') }}</h2><ul><li v-for="(feature, index) in features" :key="index">{{ t(`home.features.list.${index + 1}`) }}</li></ul></div><!-- 数字格式化示例 --><div class="price">{{ n(1234.56, 'currency') }}</div><!-- 日期格式化示例 --><div class="date">{{ d(new Date(), 'short') }}</div></div>
</template><script setup lang="ts">
import { useI18n } from 'vue-i18n'const { t, n, d } = useI18n()const features = [1, 2, 3] // 对应 features.list 中的键
</script>
3. 动态加载翻译
src/utils/i18n-loader.ts:
import { nextTick } from 'vue'
import { i18n } from '../i18n'export async function loadLanguageAsync(locale: string) {// 动态导入语言文件const messages = await import(`../locales/${locale}.json`)// 设置语言包i18n.global.setLocaleMessage(locale, messages.default)// 切换语言i18n.global.locale.value = locale// 设置 html lang 属性document.documentElement.setAttribute('lang', locale)return nextTick()
}
最佳实践与进阶技巧
1. 翻译文件管理
模块化组织
对于大型项目,建议按模块组织翻译文件:
locales/
├── zh/
│ ├── common.json
│ ├── auth.json
│ └── dashboard.json
└── en/├── common.json├── auth.json└── dashboard.json
自动合并翻译文件
创建一个脚本来合并翻译文件:
// scripts/merge-translations.ts
import * as fs from 'fs'
import * as path from 'path'const LOCALES_DIR = path.join(__dirname, '../src/locales')function mergeTranslations(locale: string) {const localeDir = path.join(LOCALES_DIR, locale)const files = fs.readdirSync(localeDir)const merged = files.reduce((acc, file) => {if (file.endsWith('.json')) {const content = JSON.parse(fs.readFileSync(path.join(localeDir, file), 'utf-8'))return { ...acc, ...content }}return acc}, {})fs.writeFileSync(path.join(LOCALES_DIR, `${locale}.json`),JSON.stringify(merged, null, 2))
}['en', 'zh'].forEach(mergeTranslations)
2. 类型安全
使用 TypeScript 类型来确保翻译键的类型安全
// types/i18n.d.ts
import { MessageSchema } from '@/i18n/messages'declare module 'vue-i18n' {export interface DefineLocaleMessage extends MessageSchema {}
}
3. 翻译缺失检查
创建一个工具函数来检查翻译是否完整:
// utils/check-translations.ts
import en from '../locales/en.json'
import zh from '../locales/zh.json'function findMissingKeys(obj1: any, obj2: any, path: string[] = []): string[] {const missing: string[] = []Object.keys(obj1).forEach(key => {const currentPath = [...path, key]if (!(key in obj2)) {missing.push(currentPath.join('.'))} else if (typeof obj1[key] === 'object' && typeof obj2[key] === 'object') {missing.push(...findMissingKeys(obj1[key], obj2[key], currentPath))}})return missing
}// 检查中文翻译是否完整
const missingInZh = findMissingKeys(en, zh)
console.log('Missing in zh:', missingInZh)// 检查英文翻译是否完整
const missingInEn = findMissingKeys(zh, en)
console.log('Missing in en:', missingInEn)
4. 性能优化
按需加载语言包
const loadedLanguages = ['zh'] // 默认加载的语言async function loadLanguage(lang: string) {// 如果语言已经加载,直接返回if (loadedLanguages.includes(lang)) {return Promise.resolve()}// 动态导入语言包const messages = await import(`./locales/${lang}.json`)i18n.global.setLocaleMessage(lang, messages.default)loadedLanguages.push(lang)return messages
}
5.缓存翻译结果
const loadedLanguages = ['zh'] // 默认加载的语言async function loadLanguage(lang: string) {// 如果语言已经加载,直接返回if (loadedLanguages.includes(lang)) {return Promise.resolve()}// 动态导入语言包const messages = await import(`./locales/${lang}.json`)i18n.global.setLocaleMessage(lang, messages.default)loadedLanguages.push(lang)return messages
}
常见问题与解决方案
1. 翻译未更新
问题:切换语言后,某些组件的翻译没有更新
解决方案
<script setup>
import { watch } from 'vue'
import { useI18n } from 'vue-i18n'const { locale } = useI18n()// 监听语言变化,强制更新组件
watch(locale, () => {nextTick(() => {// 触发组件重新渲染})
})
</script>
2. 数字格式化
问题:不同地区的数字格式不一致
解决方案:使用 numberFormats 配置:
const i18n = createI18n({numberFormats: {zh: {currency: {style: 'currency',currency: 'CNY',notation: 'standard'},decimal: {style: 'decimal',minimumFractionDigits: 2,maximumFractionDigits: 2}},en: {currency: {style: 'currency',currency: 'USD',notation: 'standard'},decimal: {style: 'decimal',minimumFractionDigits: 2,maximumFractionDigits: 2}}}
})
3. 日期本地化
问题:日期格式因地区而异
解决方案:使用 datetimeFormats 配置
const i18n = createI18n({datetimeFormats: {zh: {short: {year: 'numeric',month: 'short',day: 'numeric'},long: {year: 'numeric',month: 'long',day: 'numeric',weekday: 'long',hour: 'numeric',minute: 'numeric'}},en: {short: {year: 'numeric',month: 'short',day: 'numeric'},long: {year: 'numeric',month: 'long',day: 'numeric',weekday: 'long',hour: 'numeric',minute: 'numeric',hour12: true}}}
})
总结
实现 Vue 3 的国际化需要注意以下几个关键点:
- 配置管理
- 合理组织翻译文件结构
- 使用类型系统确保翻译键的安全
- 实现动态语言包加载
- 用户体验
- 保存用户语言偏好
- 提供平滑的语言切换体验
- 确保所有内容都正确翻译
- 维护性
- 使用工具检查翻译完整性
- 实现自动化的翻译文件管理
- 保持良好的代码组织
- 性能优化
- 实现按需加载
- 使用缓存优化翻译性能
- 避免不必要的组件重渲染
通过遵循这些最佳实践,我们可以构建一个高质量的多语言 Vue 3 应用。
好的国际化实现不仅仅是简单的文本替换,还包括对数字、日期、货币等的本地化处理,以及对用户体验的全面考虑。
相关文章:
Vue 3 项目实现国际化指南 i18n
引言 在开发现代 Web 应用时,国际化(Internationalization,简称 i18n)已经成为一个不可或缺的功能。无论是面向全球用户的商业网站,还是需要支持多语言的企业应用,良好的国际化支持都能显著提升用户体验。本…...
元音辅音及其字母组合发音
文章目录 单元音长元音/ɑː//ɔ://u://i://ɜː/// 短元音/ʌ//ɒ//ʊ//ɪ//ə//e/ 双元音/eɪ//aɪ//ɔɪ//ɪə//eə//ʊə//əʊ//aʊ/ 辅音3个鼻辅音m n ŋ 5个独立浊辅音w j r l h 20个清浊相对的辅音s zʃ ʒf vθ p bt dk gts dztʃ dʒtr dr 以下是列举的部分字母组合…...
【Vitis AIE】FPGA图像处理 11 双线性插值 Bilinear Interpolation
双线性插值 https://github.com/Xilinx/Vitis-Tutorials/tree/2024.2/AI_Engine_Development/AIE/Design_Tutorials/11-Bilinear_Interpolation 简介 双线性插值是一种使用重复线性插值来插值两个变量函数的方法。它通常用于以下应用: 图像处理和计算机视觉&…...
Linux | 安装 Samba将ubuntu 的存储空间指定为windows 上的一个磁盘
01 安装 samba 文件来实现。比如把我们 ubuntu 的存储空间指定为我们 windows 上的一个磁盘,然后我们在这个磁盘里面创建 .c 文件,进行我们代码的修改和编写,可以安装 samba 文件来实现。 samba 是一种网络共享服务,可以通过网络访问我们指定的文件夹 02 第一步:下…...
一文说清预训练与微调:AI的双重训练法则
什么是预训练? 预训练是大型语言模型训练的第一步。它在资金和计算能力的支持下,通过深入分析大量的文本数据,使模型建立起语言的基本构架。在这一阶段,模型通过学习海量的书籍、文章和网页,识别出语言的语法、句法和…...
solana增加流动性和删除流动性
在 Solana 区块链上增加和删除流动性通常通过去中心化交易所(DEX)实现,例如 Raydium 或 Orca。以下是详细的操作流程和注意事项: 一、增加流动性 步骤: 1. 连接钱包 使用支持 Solana 的钱包(如 Phantom、…...
996引擎-接口测试:音效测试NPC
996引擎-接口测试:音效测试NPC 参考资料local offset = 1 -- 默认偏移量function main(player, newOffset)offset = newOffset or offset -- 更新偏移量local buttonWidth =...
javabean类,测试类,工具类都是什么?
JavaBean类 用来描述一类事物的类。比如Student、Teacher、Dog、Cat 例如下面的这个就是JavaBean类 package com.hong.static01demo;public class Student {//姓名,年龄,性别private String name;private int age;private String gender;public stati…...
基于C8051F020单片机的液晶显示,LCD1602并口驱动,单片机并口驱动LCD1602
一、前言 LCD1602是一种广泛使用的字符型液晶显示模块,有8根数据线和3根控制线E,RS和R/W,8根数据线与单片机P6连接,3根控制线与使用P1口的P1.4、P1.5、P1.6连接,VO连接了P1.7,通过给P1.7赋值0或1ÿ…...
miniconda安装保姆级教程|win11|深度学习环境配置
一、官网安装miniconda miniconda官网:Miniconda - Anaconda 点击Download按钮 在红框位置输入邮箱并点击submit,下载链接将会发到邮箱中 邮箱中将会收到如图所示邮件,点击下载 选择windows对应的miniconda安装包 miniconda安装包安装完成如…...
算力100问☞第92问:为什么各地热衷建设算力中心?
目录 1、宏观分析 2、政府角度分析 3、投资者角度分析 在数字化浪潮中,各地对算力中心建设的热情高涨,这一现象背后潜藏着诸多深层次的原因,涵盖了经济、科技、社会等多个维度,且彼此交织,共同驱动着这一发展趋势。 1、宏观分析 从经济结构转型的底层逻辑来看,全球经…...
HTML字符实体笔记
一、概述 在HTML中,某些字符具有特殊含义,不能直接用于网页内容显示,需要使用字符实体来代替。字符实体分为两类:字符实体名称和字符实体编号。字符实体名称由&开头,后跟实体名称,以分号;结束…...
Linux shell脚本-概述、语法定义、自定义变量、环境变量、预设变量、变量的特殊用法(转义字符、单双引号、大小括号)的验证
目录 1.shell概述 1.1作为应用程序: 1.2 shell 作为一门语言 2.shell 语法 2.1 shell脚本的定义与执行 (1)新建文件 (2)程序开头第一行 必须写shell的类型 (3)程序编写完后,…...
数据驱动进化:AI Agent如何重构手机交互范式?
如果说AIGC拉开了内容生成的序幕,那么AI Agent则标志着AI从“工具”向“助手”的跨越式进化。它不再是简单的问答机器,而是一个能够感知环境、规划任务并自主执行的智能体,更像是虚拟世界中的“全能员工”。 正如行业所热议的:“大…...
DL学习笔记:穿戴设备上的轻量级人体活动识别方法
Hello,大家好!这里是《Dream 的深度学习笔记》,本系列将聚焦三个学习方面: 论文解读:拆解经典论文与最新突破 技术实现:从模型搭建到实际部署 应用案例:涵盖图像识别、深度学习、人工智能等热门方向 让…...
拓展知识三:编码学及密码学
编码和密码的区别 研究密码变化的客观规律,应用于编制密码以保守通信秘密的,称为编码学;应用于破译密码以获取通信情报的,称为破译学,总称密码学。 编码和密码是两个不同的概念,它们的区别如下:…...
windows安装配置FFmpeg教程
1.先访问官网:https://www.gyan.dev/ffmpeg/builds/ 2.选择安装包Windows builds from gyan.dev 3. 下滑找到release bulids部分,选择ffmpeg-7.0.2-essentials_build.zip 4. 然后解压将bin目录添加path系统变量:\ffmpeg-7.0.2-essentials_bui…...
Qt/C++项目积累:4.远程升级工具 - 4.1 项目设想
背景: 桌面程序一般都支持远程升级,也是比较常用的场景设计。如酷狗音乐的升级,会提供两个选项,自动帮助安装或是新版本提醒,由用户来决定是否升级,都属于远程升级的应用及策略。 看看经过这块的功能了解及…...
同旺科技USB to SPI 适配器 ---- 指令循环发送功能
所需设备: 内附链接 1、同旺科技USB to SPI 适配器 1、周期性的指令一次输入,即可以使用 “单次发送” 功能,也可以使用 “循环发送” 功能,大大减轻发送指令的编辑效率; 2、 “单次发送” 功能,“发送数据…...
用 Pinia 点燃 Vue 3 应用:状态管理革新之旅
一、状态管理的范式转移:从 Flux 到 Composition ### 1.1 Vuex 的辉煌与局限 - **核心架构**:基于Flux模式的state/mutations/actions三件套 - **痛点显现**: - 类型推导困难:TypeScript支持需复杂配置 - 模块嵌套陷阱&#…...
单表达式倒计时工具:datetime的极度优雅(Kimi)
一个简单表达式,也可以优雅自成工具。 笔记模板由python脚本于2025-03-22 20:25:49创建,本篇笔记适合任意喜欢学习的coder翻阅。 【学习的细节是欢悦的历程】 博客的核心价值:在于输出思考与经验,而不仅仅是知识的简单复述。 Pyth…...
Linux:基础IO---文件描述符
文章目录 1. 前言1.1 C语言文件知识回顾 2. 文件2.1 文件基础知识 3. 被打开的文件3.1 以C语言为主,先回忆一下C文件接口3.2 过渡到系统,认识文件系统调用3.3 访问文件的本质3.4 重定向&&缓冲区 序:在深入了解了进程的内容后…...
15:视图
1. 什么是视图? 视图是sql查询的虚拟表,他并不直接存储数据,而是基于单表/多表查询的结果创建的一张虚拟表,主要是为了提高查询速率、简化复杂查询。 视图的数据基于原始表,如果原始表中的数据发生了改变,…...
附——教6
审核较严格!审核较严格!审核较严格! 完整内容参见: https://zhuanlan.zhihu.com/p/32050040665 注解: 1. 投机本质论:市场本质是投机,投资仅是表象; 2. 安全G点法则:精…...
使用 CA 证书进行签名的步骤
使用 CA 证书进行签名通常涉及以下步骤: 生成私钥和证书签名请求(CSR):你需要生成一个私钥,并基于该私钥创建一个 CSR 文件。使用 CA 证书签名 CSR:使用 CA 的私钥对 CSR 进行签名,生成最终的证…...
LINUX基础 [二] - 进程概念
目录 前言 什么是进程 如何管理进程 描述进程 组织进程 如何查看进程 通过 ps 命令查看进程 通过 ls / proc 命令查看进程 通过系统调用 获取进程标示符 前言 在学习了【Linux系统编程】中的 操作系统 和 冯诺依曼体系结构 之后,我们已经对系统应该有…...
浏览器自动携带cookie注意事项
文章目录 浏览器自动携带与目标域相关的 cookie 是由 HTTP 协议规范和浏览器设计共同决定的一、Cookie 携带的基本规则同源策略下的自动携带跨域请求的受限携带一、服务器端配置二、客户端配置三、 常见错误及注意事项 二、Cookie 属性的筛选逻辑 三、浏览器携带cookie的准则1.…...
同旺科技USB to I2C 适配器 ---- 指令循环发送功能
所需设备: 内附链接 1、同旺科技USB to I2C 适配器 1、周期性的指令一次输入,即可以使用 “单次发送” 功能,也可以使用 “循环发送” 功能,大大减轻发送指令的编辑效率; 2、 “单次发送” 功能,“发送数据…...
算法及数据结构系列 - 滑动窗口
系列文章目录 算法及数据结构系列 - 二分查找 算法及数据结构系列 - BFS算法 算法及数据结构系列 - 动态规划 算法及数据结构系列 - 双指针 算法及数据结构系列 - 回溯算法 算法及数据结构系列 - 树 文章目录 滑动窗口框架思路经典题型76. 最小覆盖子串567. 字符串的排列438. …...
AI密码学
嗯,用户给了一个需要破译的密码文档:“Uif qjh jt po uif usff.”,提示是用字母往前推移1的凯撒密码。首先,我得确认自己是否正确理解提示。凯撒密码通常是将字母按照一定位移来替换,这里的提示是往前推1位,…...
