扫描二维码关注或者微信搜一搜:编程智域 前端至全栈交流与成长
发现1000+提升效率与开发的AI工具和实用程序:https://tools.cmdragon.cn/zh/apps?category=ai_chat
一、先搞明白我们要干啥
i18n是"国际化"(Internationalization)的缩写——那个18是因为I和n之间有18个字母,程序员就是这么懒,能缩写绝不写全。
咱们要做的就是一个翻译插件:你给它一个key,它帮你找到对应的翻译文本。比如:
<h1>{{ $translate('greetings.hello') }}</h1>
<!-- 输出:<h1>Bonjour!</h1> -->这个$translate函数,我们希望它在任何组件的模板里都能直接用,不用每个组件单独导入。这就是插件的活儿了。
二、第一步:创建插件文件
建议把插件放在单独的文件里,方便管理:
// plugins/i18n.js
exportdefault {
install(app, options) {
// 插件代码写在这里
},
};就这么简单——一个对象,里面有个install方法。app是Vue应用实例,options是安装时传进来的配置。
三、第二步:实现翻译函数
咱们要实现的功能是:给一个greetings.hello这样的key,去翻译字典里找到对应的值。
翻译字典长这样:
{
greetings: {
hello: '你好',
goodbye: '再见'
},
menu: {
home: '首页',
about: '关于'
}
}那greetings.hello怎么找到你好呢?把key按.拆开,一层层往里找:
// 'greetings.hello'.split('.') → ['greetings', 'hello']
// 先找 obj['greetings'],再找 obj['greetings']['hello']
key.split(".").reduce((obj, k) => {
if (obj) return obj[k];
}, translations);这个reduce可能看着有点绕,来拆解一下:
// 假设 key = 'greetings.hello', translations = { greetings: { hello: '你好' } }
// 第一轮:obj = translations, k = 'greetings'
// obj['greetings'] → { hello: '你好' }
// 第二轮:obj = { hello: '你好' }, k = 'hello'
// obj['hello'] → '你好'
// 最终结果:'你好'四、第三步:把翻译函数挂到globalProperties上
// plugins/i18n.js
exportdefault {
install(app, options) {
app.config.globalProperties.$translate = (key) => {
return key.split(".").reduce((obj, k) => {
if (obj) return obj[k];
}, options);
};
},
};app.config.globalProperties是Vue提供的一个对象,往上面挂的东西,所有组件都能通过this访问到。所以挂了$translate之后,任何组件里都能用this.$translate()。
按惯例,全局属性名以$开头,这样一眼就能看出这是个Vue注入的全局方法,跟你自己定义的data/methods区分开。
五、第四步:在main.js中安装插件
// main.js
import { createApp } from"vue";
importAppfrom"./App.vue";
import i18nPlugin from"./plugins/i18n.js";
const app = createApp(App);
app.use(i18nPlugin, {
greetings: {
hello: "你好",
goodbye: "再见",
},
menu: {
home: "首页",
about: "关于",
},
});
app.mount("#app");注意app.use()的第二个参数——这就是传给插件install方法的options。翻译字典就是通过这个参数传进去的。
六、在组件中使用
选项式API
<template>
<h1>{{ $translate("greetings.hello") }}</h1>
<p>{{ $translate("menu.home") }}</p>
</template>在选项式API的模板中,$translate可以直接用,因为模板会自动从this上找属性。
在JS中:
exportdefault {
mounted() {
const hello = this.$translate("greetings.hello");
console.log(hello); // '你好'
},
};组合式API(script setup)
在<script setup>中没有this,所以不能直接用this.$translate。需要通过getCurrentInstance来访问:
<script setup>
import { getCurrentInstance } from "vue";
const { proxy } = getCurrentInstance();
const hello = proxy.$translate("greetings.hello");
console.log(hello); // '你好'
</script>
<template>
<!-- 模板中还是可以直接用 -->
<h1>{{ $translate("greetings.hello") }}</h1>
</template>不过说实话,在组合式API中用getCurrentInstance来访问globalProperties有点别扭。后面我们会讲更优雅的方式——用app.provide() + inject()。
七、让翻译函数更健壮
上面的$translate还有几个问题,咱们来修一修:
问题一:key找不到时返回undefined
$translate("not.exist.key"); // undefined加个默认值:
app.config.globalProperties.$translate = (key, defaultValue = "") => {
const result = key.split(".").reduce((obj, k) => {
if (obj) return obj[k];
}, options);
return result !== undefined ? result : defaultValue;
};问题二:key不是字符串时会报错
$translate(123); // split不是数字的方法,报错加个类型检查:
app.config.globalProperties.$translate = (key, defaultValue = "") => {
if (typeof key !== "string") return defaultValue;
const result = key.split(".").reduce((obj, k) => {
if (obj) return obj[k];
}, options);
return result !== undefined ? result : defaultValue;
};问题三:支持动态切换语言
现在的翻译字典是安装时写死的,不能动态切换。咱们加个响应式的语言切换功能:
// plugins/i18n.js
import { ref, computed } from"vue";
exportdefault {
install(app, options) {
const locale = ref("zh");
const messages = options;
const$translate = (key, defaultValue = "") => {
if (typeof key !== "string") return defaultValue;
const currentMessages = messages[locale.value] || {};
const result = key.split(".").reduce((obj, k) => {
if (obj) return obj[k];
}, currentMessages);
return result !== undefined ? result : defaultValue;
};
app.config.globalProperties.$translate = $translate;
app.config.globalProperties.$locale = locale;
app.provide("i18n", {
locale,
translate: $translate,
setLocale(lang) {
if (messages[lang]) {
locale.value = lang;
}
},
});
},
};安装时传入多语言字典:
app.use(i18nPlugin, {
zh: {
greetings: { hello: "你好", goodbye: "再见" },
menu: { home: "首页", about: "关于" },
},
en: {
greetings: { hello: "Hello", goodbye: "Goodbye" },
menu: { home: "Home", about: "About" },
},
});组件里切换语言:
<script setup>
import { inject } from "vue";
const { locale, translate, setLocale } = inject("i18n");
function switchToEnglish() {
setLocale("en");
}
function switchToChinese() {
setLocale("zh");
}
</script>
<template>
<h1>{{ translate("greetings.hello") }}</h1>
<button @click="switchToEnglish">English</button>
<button @click="switchToChinese">中文</button>
</template>八、完整的插件代码
// plugins/i18n.js
import { ref } from"vue";
exportdefault {
install(app, options) {
const locale = ref("zh");
const messages = options;
function$translate(key, defaultValue = "") {
if (typeof key !== "string") return defaultValue;
const currentMessages = messages[locale.value] || {};
const result = key.split(".").reduce((obj, k) => {
if (obj) return obj[k];
}, currentMessages);
return result !== undefined ? result : defaultValue;
}
app.config.globalProperties.$translate = $translate;
app.config.globalProperties.$locale = locale;
app.provide("i18n", {
locale,
translate: $translate,
setLocale(lang) {
if (messages[lang]) {
locale.value = lang;
}
},
availableLocales: Object.keys(messages),
});
},
};课后 Quiz
问题 1
app.config.globalProperties上挂的方法,在<script setup>中怎么访问?
答案解析
<script setup>中没有this,不能直接访问globalProperties。有两种方式:
1. 通过 getCurrentInstance()获取组件实例,再通过proxy访问2. 更推荐的方式是用 app.provide()把功能注入,组件中用inject()获取
第二种方式更符合组合式API的风格,也更好做类型推断。
问题 2
为什么全局属性名建议以$开头?
答案解析
这是Vue的约定,以$开头的属性表示它是Vue或插件注入的全局方法/属性,跟组件自己定义的data、methods区分开。比如$router、$route、$translate——看到$开头就知道这不是你自己写的,是框架或插件提供的。
问题 3
app.use(plugin, options)中的options是怎么传到插件里的?
答案解析
app.use内部调用插件的install方法时,会把app实例作为第一个参数,把options作为第二个参数传进去。所以install(app, options)中的options就是你调用app.use时传的第二个参数。
常见报错解决方案
报错 1:this.$translate is not a function
错误场景:
<script setup>
const msg = this.$translate("greetings.hello"); // 💥 setup中没有this
</script>报错原因:<script setup>中没有this对象,globalProperties上的方法无法通过this访问。
解决方案:
在模板中可以直接用$translate,在JS中用getCurrentInstance或inject:
// 方式一:getCurrentInstance
import { getCurrentInstance } from"vue";
const { proxy } = getCurrentInstance();
const msg = proxy.$translate("greetings.hello");
// 方式二:inject(推荐)
const { translate } = inject("i18n");
const msg = translate("greetings.hello");报错 2:Cannot read property 'split' of undefined
错误场景:
$translate(undefined); // 💥 undefined没有split方法报错原因:
翻译函数没有对key做类型检查,传入了非字符串类型的值。
解决方案:
在函数开头加类型检查:
function$translate(key, defaultValue = "") {
if (typeof key !== "string") return defaultValue;
// ...
}报错 3:翻译字典key写错了,返回undefined
错误场景:
$translate("greeting.hello"); // 拼写错误:greeting少了个s
// 返回undefined,页面上显示空白报错原因:
key拼写错误,在字典中找不到对应的翻译。
解决方案:
给$translate加默认值参数,找不到翻译时显示默认文本而不是空白:
$translate("greeting.hello", "翻译缺失"); // 找不到时显示"翻译缺失"同时在开发环境下可以加个警告:
function$translate(key, defaultValue = "") {
if (typeof key !== "string") return defaultValue;
const result = key.split(".").reduce((obj, k) => {
if (obj) return obj[k];
}, messages[locale.value]);
if (result === undefined && import.meta.env.DEV) {
console.warn(`[i18n] Missing translation: ${key}`);
}
return result !== undefined ? result : defaultValue;
}
夜雨聆风