国际化(i18n):react、vue、element兼容
i18n应用中,下述 JSON 对象通常会根据不同的语言或功能模块被拆分成多个独立的 JSON 文件,如zh->home.json,en->login.json。从本地化资源文件中获取特定键的字符串,并且可以动态地插入变量。"CN" 则表示该语言的特定区域,即中华人民共和国(China)。(namespace,如下述zh.json中的login),避免了不同组件间状态管理的复杂性和冗余性。包含国际
目录
通用性考虑:location:'位置' vs location:'[位置]'
react-i18next使用上下文API、或者 hook:
1.3设置VueI18n的options中的messages登
src/declare.d.ts:declare module 'VueI18n';
设置语言
页面:html标签的 lang属性
- 英语:
<html lang="en">
- 中文:
<html lang="zh">
或<html lang="zh-CN">
"CN" 则表示该语言的特定区域,即中华人民共和国(China)。
更符合语言标准的规范
- 西班牙语:
<html lang="es">
- 法语:
<html lang="fr">
- 日语:
<html lang="ja">
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Your Webpage Title</title>
</head>
<body>
<!-- Your webpage content goes here -->
</body>
</html>
浏览器->设置->语言
判断语言
场景:若法国、日韩本地语言不是“en”,但需要用“en”,
所以不能靠“en”判断,而是非“zh”时就显示英文
const appLanguage = window.app && window.app.appLocaleInfo
? window.app.appLocaleInfo.appLanguage
: "zh";
const isZhLocal = (appLanguage === "zh");
命名由来
国际化(i18n):
- i 表示单词 "internationalization" 的首字母。
- 18 表示在 "i" 和 "n" 之间有 18 个字母。
本地化(l10n):
- l 表示单词 "localization" 的首字母。
- 10 表示在 "l" 和 "n" 之间有 10 个字母。
- react-i18next:i18n+extend(推测)
注意
通用性考虑:location:'位置' vs location:'[位置]'
`[${i18n.t('message.location')}]`
标题(含列表):都大写,除了 冠、连、介词(除非首)
如冠词(a, an, the)、连词(and, but, or)和介词(in, on, with)通常不大写,除非它们是标题的第一个或最后一个词。
"The Quick Brown Fox Jumps Over the Lazy Dog"
"Software Update"
原理
从本地化资源文件中获取特定键的字符串,并且可以动态地插入变量。本地资源一般是.json,.xml,.ts存储对象
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Translation Example</title>
</head>
<body>
<script>
const translationContext = {
t: (key) => {
// 获取浏览器当前语言
const userLanguage = navigator.language || navigator.userLanguage;
// 默认翻译
const defaultTranslations = {
"No modifications at present.": "No modifications at present.",
"Time Error: Current creation time is earlier than the server time, unable to submit.": "Time Error: Current creation time is earlier than the server time, unable to submit.",
// 添加其他英文翻译
};
// 按语言选择翻译
const zhCNTranslations = {
"No modifications at present.": "当前无修改",
"Time Error: Current creation time is earlier than the server time, unable to submit.": "时间异常,当前创建时间早于服务器端时间,无法提交",
// 添加其他中文翻译
};
const currentTranslations = ['zh', 'zh-CN', 'cn'].includes(userLanguage) ? zhCNTranslations : defaultTranslations;
// 示例翻译
const translatedText = currentTranslations[key] || defaultTranslations[key] || key;
// 在控制台输出翻译结果
console.log(translatedText);
console.log(userLanguage);
// 可以将翻译结果插入到页面中
// document.body.innerHTML = translatedText;
return translatedText;
},
};
// 示例使用
translationContext.t("No modifications at present.");
translationContext.t("Time Error: Current creation time is earlier than the server time, unable to submit.");
</script>
</body>
</html>
tc参数:复数翻译
apple: 'no apples | one apple | {count} apples'
因为tc函数可以用|分割,而t不能
count、n、可以随意命名
<p>{{ $tc('apple', 0) }}</p>
<p>{{ $tc('apple', 1) }}</p>
<p>{{ $tc('apple', 10, { count: 10 }) }}</p>
覆盖预定义的命名参数
<p>{{ $tc('apple', 100, { count: 'too many' }) }}</p>
<p>no apples</p>
<p>one apple</p>
<p>10 apples</p>
<p>too many apples</p>
t参数:单一翻译
t
(translate) 函数: 这个函数用于翻译文本,传入翻译文件(如下述home.json)或命名空间(namespace,如下述zh.json中的login)
i18n
对象: 包含国际化设置的对象,判断和切换语言
i18n.language
的默认值是浏览器的语言首选项(navigator.language)
i18n.language; // 当前语言环境
i18n.changeLanguage('en'); // 切换到英语
变量替换 (variables
)
- 用法:通过一个对象来传递需要替换的变量。
- 示例:
t('welcomeMessage', { username: 'John' })
- 说明:这种方式允许你将动态变量插入到翻译文本中,类似占位符的功能。
vue-i18n
9.x 版本(与Vue 3.x 兼容)
选项对象 (options
)
可选,包含配置选项,如 locale
, pluralization
等。
this.$t('greeting', { name: 'Alice', locale: 'fr' }) // 使用 'fr' 语言环境
格式化 (format
)
- 用法:指定特定的格式化选项,例如日期、数字格式化等。
- 示例:
t('date', { date: new Date(), format: 'longDate' })
- 说明:在需要根据语言环境格式化日期、数字或其他类型的内容时特别有用。
数字格式化:1,000
复数形式 (count
)
- 用法:指定一个用于选择适当复数形式的数值。
- 示例:
t('items', { count: 5 })
- 说明:根据给定的数值选择文本的复数形式,如单数、复数等。
上下文 (context
)
- 用法:指定一个上下文参数,用于选择文本的特定变体。
- 示例:
t('open', { context: 'verb' })
- 说明:在需要根据不同语境下选择不同翻译版本时使用,例如动词形式和名词形式的不同翻译。
默认值 (defaultValue
)
- 用法:指定在找不到翻译键时返回的默认文本。
- 示例:
t('nonExistentKey', { defaultValue: 'Default Text' })
- 说明:确保在没有对应翻译键的情况下,能够提供一个合适的默认文本,避免出现空白或错误。
返回对象:
{ returnObjects: true }
选项来告诉 t
函数返回一个包含多个条目的对象
适用于:
- 本身就需要对象
- 遍历对象
- 对象层级深,避免名字太长
zh.json
{
"home": {
"welcomeMessage": "欢迎,{{username}}!",
"items": {
"one": "一个项目",
"other": "{{count}} 个项目"
},
"date": {
"longDate": "{{date, YYYY年MM月DD日}}",
"shortDate": "{{date, YYYY-MM-DD}}",
"fullDateTime": "{{date, YYYY年MM月DD日Ah点mm分ss秒}}"
}
"open": {
"verb": "打开",
"noun": "开放"
},
"features": {
"free": {
"title": "免费",
"description": [
"免费注册",
"免费试用"
]
},
},
"login": {
"title": "登录",
"username": "用户名",
"password": "密码",
"loginButton": "登录"
}
}
- 长日期 (
longDate
):例如 2024年07月13日 - 短日期 (
shortDate
):例如 2024-07-13 - 完整日期时间 (
fullDateTime
):例如 2024年07月13日下午02点30分00秒
en.json
{
"home": {
"welcomeMessage": "Welcome, {{username}}!",
"items": {
"one": "One item",
"other": "{{count}} items"
},
"date": {
"longDate": "{{date, MMMM Do, YYYY}}",
"shortDate": "{{date, MM/DD/YYYY}}",
"isoDate": "{{date, YYYY-MM-DD}}",
"fullDateTime": "{{date, MMMM Do YYYY, h:mm:ss a}}"
"features": {
"free": {
"title": "Free",
"description": [
"Free registration",
"Free trial"
]
},
}
"open": {
"verb": "Open",
"noun": "Openness"
}
},
"login": {
"title": "Login",
"username": "Username",
"password": "Password",
"loginButton": "Login"
}
}
- 长日期 (
longDate
):例如 July 13th, 2024 - 短日期 (
shortDate
):例如 07/13/2024 - ISO 8601 格式 (
isoDate
):例如 2024-07-13 - 完整日期时间 (
fullDateTime
):例如 July 13th 2024, 2:30:00 pm
react组件
//不传参的话加载所有的本地资源
const { t, i18n } = useTranslation(['home', 'login']);
import React from 'react';
import { useTranslation } from 'react-i18next';
function WelcomeComponent({ username, itemCount, date }) {
//只加载home.json,就不用带前缀home,只在home内查找
const { t } = useTranslation('home');
return (
<div>
<p>{t('welcomeMessage', { username: username })}</p>
<p>{t('items', { count: itemCount })}</p>
<p>{t('date', { date: date, format: 'longDate' })}</p>
<p>{t('open', { context: 'verb' })}</p>
<p>{t('nonExistentKey', { defaultValue: 'Default Text' })}</p>
<ul>
{t('features.free.description', { returnObjects: true }).map((desc, index) =>
(<li key={index}>{desc}</li>))}
</ul>
</div>
);
}
export default WelcomeComponent;
传参和效果
<WelcomeComponent
username="张三"
itemCount={3}
date={new Date()}
/>
<div>
<p>Welcome, 张三!</p>
<p>3 items</p>
<p>July 12th, 2024</p>
<p>Open</p>
<p>Default Text</p>
...
</div>
<div>
<p>欢迎,张三!</p>
<p>3 个项目</p>
<p>2024年7月12日</p>
<p>打开</p>
<p>Default Text</p>
...
</div>
本地资源:语言包对象
zh.ts
export default {
languageName: '简体中文',
common: { //通用
you: '你',
}
}
en.ts
export default {
languageName: 'English',
common: { //通用
you: 'you',
},
}
开发
staticData.ts:直接返回对象效率更高
-
适用于静态语言
-
若固定顺序,可遍历对象属性
例子可见下文的react-i18next
export function getFeatures(lang: string){
if(lang==='cn'){
return [
{
title: '免费',
description: [
'你好',
...
],
},
{
...
}
]
}else{
return [
{
title: 'Free',
description: [
'hi~~~😄',
....
],
},
{
....
],
}
]
}
}
public->locales->en->模块名.json
public->locales->zh->模块名.json
-
适用于动态语言,非固定顺序,零散重复使用
-
returnObjects: true,可遍历对象属性
react-i18next库:hook、组件级
安装
npm i i18next react-i18next
- i18next 提供了翻译的基本能力。
- react-i18next 是 i18next 的一个插件,用来降低 react 的使用成本。
可选:
npm i i18next-browser-languagedetector i18next-http-backend
- i18next-browser-languagedetector 检测浏览器语言的插件。
- i18next-http-backend从服务器加载翻译资源文件
react-i18next使用上下文API、或者 hook:
React Context API实现状态和数据在组件树中的共享。它允许在组件之间共享数据,而无需手动通过props一层层地传递数据。尤其适用于全局数据、主题设置、用户身份验证等在整个应用程序中需要访问的数据。
hook只能在函数组件中
import { useTranslation } from 'react-i18next';
import { getFeatures } from './staticData';
export default function HomeContent() {
//解构变量,根据本地的home和login文件设置i18n
const { t, i18n } = useTranslation(['home', 'login']);
const features = getFeatures(['zh', 'zh-CN', 'cn'].includes(i18n.language) ? 'cn' : 'en')
getFeatures见上文中的staticData.ts
<h1 className="font-bold text-2xl">{t('Sign in/Sign up')}</h1>
{features?.map((item, index) => (
<div className='flex flex-col items-center p-4 md:w-1/2' key={item.title}>
<div className=' min-w-full h-full bg-[#2A2935] rounded-lg p-4'>
<div className=' text-2xl leading-loose md:leading-tight'>{item.title}
{item.subtitle && <span className=' text-xs text-gray-400 ml-2'>( {item.subtitle} )</span>}
</div>
{item.description.map((item, index) => (
<div className=' text-xs text-gray-400 leading-relaxed' key={index}>- {item}</div>
))}
</div>
</div>
))}
vue-i18n库:原型链、全局
0.安装
npm install vue-i18n
1.配置语言包(src\i18n或language)
整合语言包对象和创建 VueI18n 实例并配置
1.1注册vue-i18n
import Vue from "vue"
import VueI18n from "vue-i18n"
Vue.use(VueI18n) // 将 VueI18n 注入到 Vue 实例中的所有子组件
在 Vue.js 中,插件通常通过
Vue.use()
方法来注册和安装。
vue-i18n
注册后,将i18n
实例挂载到原型链上。
全局可访问:任何 Vue 组件中都可以通过
this.$i18n
访问到国际化实例一致性:保证所有组件使用同一个
i18n
实例,避免了不同组件间状态管理的复杂性和冗余性。
1.2动态加载语言文件
let langFileds = require.context('./locales', false, /\.ts$/)
let dateTimeFormatsFileds = require.context('./dateTimeFormats', false, /\.ts$/)
let regExp = /\.\/([^\.\/]+)\.([^\.]+)$/
require.context
是 webpack 提供的一种函数,用于在编译时动态地引入模块。它接受三个参数:require.context(path,deep,regExp)
- path(目录路径): 表示需要搜索的目录路径。
- deep(是否搜索子目录): 表示是否搜索该目录下的子目录。
- regExp(匹配文件的正则表达式): 表示匹配文件的正则表达式。
日期时间格式文件(zh.ts+en.ts一致)
export default { shortMonth: { month: 'short', }, numberMonth: { month: 'narrow', }, shortYear: { year: 'numeric' } }
shortMonth:
month: 'short'
表示月份的简短格式。具体格式(比如 'Jan'、'Feb')取决于具体的本地化设置,通常是缩写形式的月份名称。numberMonth:
month: 'narrow'
表示月份的窄格式。这种格式可能比 'short' 更加紧凑,通常用于较窄的显示空间或者日历视图中。shortYear:
year: 'numeric'
表示年份的数字格式。这种格式通常是完整的四位数字年份(例如 2023)。
1.3设置VueI18n的options中的messages登
langFileds.keys().forEach((key:any) => {
let prop = regExp.exec(key)//正则匹配en|zh这样的值
if(prop){
//messages[prop]相当于 messages['en'] = {table:{...}}
messages[prop[1]] = langFileds(key).default
//对于 ./en.ts,prop 将会是 ['en', 'ts']
//.default 表示 ES6 默认导出的内容,因为 webpack 在处理 ES6 模块时会将其包装为一个包含 default 属性的对象。
}
})
dateTimeFormatsFileds.keys().forEach((key:any) => {
let prop = regExp.exec(key)//正则匹配en|zh这样的值
if(prop){
dateTimeFormats[prop[1]] = dateTimeFormatsFileds(key).default
}
})
messages
对象:{
en: {
greeting: 'Hello!',
farewell: 'Goodbye!'
},
zh: {
greeting: '你好!',
farewell: '再见!'
}
}
1.4设置VueI18n的options中的locale
let locale = window.xxx.appLocaleInfo.appLanguage || "zh" //从localstorag中获取
1.5创建 VueI18n 实例并导出
export default new VueI18n({
locale, // 指定当前的语言环境
messages, // 语言包对象,包含不同语言的翻译
dateTimeFormats // 日期时间格式对象,包含不同语言的日期时间格式配置
})
完整代码
import Vue from "vue"
import VueI18n from "vue-i18n"
Vue.use(VueI18n) //注入到所有的子组件
let langFileds = require.context('./locales', false, /\.ts$/)
let dateTimeFormatsFileds = require.context('./dateTimeFormats', false, /\.ts$/)
let regExp = /\.\/([^\.\/]+)\.([^\.]+)$/ //正则用于匹配 ./en.js中的'en'
let messages = {} //声明一个数据模型,对应i18n中的message属性
let dateTimeFormats = {}
langFileds.keys().forEach((key:any) => {
let prop = regExp.exec(key)//正则匹配en|zh这样的值
if(prop){
//messages[prop]相当于 messages['en'] = {table:{...}}
messages[prop[1]] = langFileds(key).default
}
})
dateTimeFormatsFileds.keys().forEach((key:any) => {
let prop = regExp.exec(key)//正则匹配en|zh这样的值
if(prop){
dateTimeFormats[prop[1]] = dateTimeFormatsFileds(key).default
}
})
let locale = window.xxx.appLocaleInfo.appLanguage || "zh" //从localstorag中获取
export default new VueI18n({
locale,//指定语言字段
messages,//定义语言字段
dateTimeFormats
})
2.main.js引入,实例化Vue
import i18n from "./i18n"
//渲染函数 (render):动态地生成虚拟 DOM,适合复杂的逻辑和定制化需求。
new Vue({
i18n,
render: h => h(App)
}).$mount('#app')
//模板字符串 (template):适合基本的应用程序结构和布局,易读和维护。
new Vue({
i18n, // 语言国际化配置
template: '<App/>', // 模板字符串,指定根组件
components: { App } // 注册根组件
}).$mount('#app')
3.使用
选项式 API .vue
JS对象方式定义组件,Vue 在此对象中注入了上下文,包括
this.$t
方法
//标签文本内容
<span>{{$t("workplace.updateRecurringSchedule")}}</span>
//标签属性
:placeholder="$t('chartSend.addScheduleTheme')"
//js
this.$t("chartSend.waitBotReply")
//键值对:removeMsg:'确定将 {nickname} 移除群聊吗?',
const item.nickname={nickname:'M7'}
this.$t("chartSend.removeMsg", {nickname: item.nickname})
extent.js : Vue 的原型中挂载 $t 方法
// 把 VueI18n 对象实例的方法都注入到 Vue 实例上
Vue.prototype.$t = function (key: Path, ...values: any): TranslateResult {
const i18n = this.$i18n
// 代理模式的使用
return i18n._t(key, i18n.locale, i18n._getMessages(), this, ...values)
}
调用 index.js 中的 $t 的方法
// $t 最后调用的方法
_t (key: Path, _locale: Locale, messages: LocaleMessages, host: any, ...values: any): any {
if (!key) { return '' }
const parsedArgs = parseArgs(...values)
// 如果 escapeParameterHtml 被配置为 true,那么插值参数将在转换消息之前被转义。
if(this._escapeParameterHtml) {
parsedArgs.params = escapeParams(parsedArgs.params)
}
const locale: Locale = parsedArgs.locale || _locale
// 翻译
let ret: any = this._translate(
messages, locale, this.fallbackLocale, key,
host, 'string', parsedArgs.params
)
}
a
类式 API .vue、工具函数.ts
import i18n from '@/i18n'
class IMConListUtil {
i18n.t('common.private'),
}
如果类式 API 用 this.$t会报错
原因:ES6 类来定义组件,需要使用 vue-class-component
插件来处理 Vue 的特性
[warn]:不会阻断运行逻辑,但error会
TypeError: Cannot read properties of undefined (reading '_t')
at Vue.$t (todo.common.js:20045:1)
at VueComponent.selectPractitioners (todo.common.js:25065:1)
at click (todo.common.js:17124:2746)
export default class CreateBacklogTask extends Vue {
this.searchSelectPerson = params == this.$t('todo.add.subscriberss') ? this.followersList : this.executorList;
}
ts
src/declare.d.ts:declare module 'VueI18n';
TypeScript项目中,当第三方库或模块不是由 TypeScript 编写的,因此可能没有原生的 TypeScript 类型定义文件。
会创建一个 declare.d.ts
文件,声明模块的类型信息,以便在编译时进行类型检查和提供类型提示。
declare module 'element-ui';
declare module 'vuex';
declare module 'vue';
declare module 'VueI18n';
declare module 'axios';
eclare module 'lodash';
declare module 'vue-router';
Element:UI组件库
若用main.ts,
记得声明
declare.d.ts
declare module 'element-ui/lib/locale';
0.全局加载
main.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import Element from 'element-ui'
import enLocale from 'element-ui/lib/locale/lang/en'
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
Vue.use(VueI18n)
Vue.use(Element)
//设置默认语言
Vue.config.lang = 'zh-cn'
//注册了中文(简体)和英文的本地化资源
Vue.locale('zh-cn', zhLocale)
Vue.locale('en', enLocale)
Tip:
全局安装Element组件配置相应locale:
Vue.use(Element)
Vue.use(ElementUI, { locale })
按需安装Element组件配置相应locale:
Vue.use(DatePicker)
ElementLocale.i18n((key, value) => i18n.t(key, value))
若用Vue.use(Element)配ElementLocale.i18n((key, value) => i18n.t(key, value))会报错:
Vue.use(Element)
默认没有指定语言环境,Element UI将使用它内置的默认语言设置。此时用
ElementLocale.i18n()
去改变其语言处理函数可能会导致冲突或重复配置,从而引发错误。
0.按需加载
main.js
import Vue from 'vue'
import DatePicker from 'element/lib/date-picker'
import VueI18n from 'vue-i18n'
import enLocale from 'element-ui/lib/locale/lang/en'
import zhLocale from 'element-ui/lib/locale/lang/zh-CN'
import ElementLocale from 'element-ui/lib/locale'
Vue.use(VueI18n)
Vue.use(DatePicker)
const messages = {
en: {
message: 'hello',
...enLocale
},
zh: {
message: '你好',
...zhLocale
}
}
// Create VueI18n instance with options
const i18n = new VueI18n({
locale: 'en', // set locale
messages, // set locale messages
})
ElementLocale.i18n((key, value) => i18n.t(key, value))
0.无法兼容/按需修改
vs:command+p 查找文件
en.js
'use strict';
exports.__esModule = true;
exports.default = {
el: {
colorpicker: {
confirm: 'OK',
clear: 'Clear'
},
datepicker: {
now: 'Now',
today: 'Today',
cancel: 'Cancel',
clear: 'Clear',
...
将'element-ui/lib/locale/lang/en','zh-CN'中相关内容粘贴合并到本地语言包文件中即可
靠el-识别
el: {
colorpicker: {
confirm: 'OK',
clear: 'Clear'
},
datepicker: {
now: 'Now',
today: 'Today',
cancel: 'Cancel',
clear: 'Clear',
...
main.ts
//全局加载
import locale_en from 'element-ui/lib/locale/lang/en'
import locale_zh from 'element-ui/lib/locale/lang/zh-CN'
const appLanguage:string = window.appName.appLocaleInfo ? window.appName.appLocaleInfo.appLanguage : "zh"
const locale = appLanguage === 'zh' ? locale_zh : locale_en;
Vue.use(ElementUI, { locale });
1.重启主服务/项目
不重启的话会报错/配置不生效
CDN加载
性能提升:就近
CDN 分布在全球多个节点,通过就近的服务器提供文件,减少了用户的加载时间,提高了性能。
缓存优化
CDN 会缓存文件,减轻了源服务器的负担,并使文件的访问速度更快。
可靠性增强:多节点备份
CDN 提供高可用性和容错性,确保文件在多个节点上都有备份,即使某些节点出现问题,用户仍然可以从其他节点获取文件。
带宽节省:分担请求
通过 CDN,减少了直接从源服务器的带宽使用,因为文件请求会分担到 CDN 网络上。
自动更新
CDN 提供的文件可以自动更新,确保用户获取到的是最新版本的语言文件。
源码分析
更多推荐
所有评论(0)