Skip to content

【大前端】50行代码,教你实现轻量级 i18n 翻译插件

你还在使用 i18n 实现网站国际化吗?那你也 太OUT了

今天教你 50行 代码实现轻量级i18n 插件

第一步

首先你需要清楚i18n 插件主要用来干了什么事,它的原理是什么

答案:就是把代码中的文本翻译成我们想要的语言,而要想实现翻译成对应的语言,就需要有一个语言映射表,所以我们首先需要设置一个语言映射表,代码如下:

typescript
import type { I18nTranslateRawOptions } from "..";

export default [
    {
        source: "$nbsp",
        translate: " ",
    },
    {
        source: "邮编",
        translate: "Postal Code",
    },
    {
        source: "地址",
        translate: "Address",
    },
] as Array<I18nTranslateRawOptions>;

点上面代码就是映射了代码中对应的中文映射对应的英文

第二步

有了映射表,我们就需要解决如何实现映射

答案:传统的i18n插件都是在代码中写入一个key,然后通过key去获取当前语言的文本,但是这不是我想要的,因为太麻烦了
我想要的就是,我只需要在需要翻译的地方将需要翻译的文本传给插件,插件给我翻译成对应的语言,如果匹配不到就不翻译,我不需要关心他当前是哪个key
这样是不是就简单很多,那么问题来了,如何实现呢?
代码如下:

typescript
import zhCn from "./lang/zh-cn";
import en from "./lang/en";

import CryptoJS from "crypto-js";
import { shallowRef } from "vue";

export interface I18nTranslateRawOptions {
    source: string;
    translate: string;
}

export interface I18nLang {
    name: string;
    list: I18nTranslateRawOptions[];
}

// 编码需要翻译的文本,确保匹配到对应的翻译结果
const encodeSource = (source: string) => {
    return CryptoJS.MD5(source).toString();
};

const _langs: Record<string, I18nLang> = {
    zhCn: {
        name: "简体中文",
        list: zhCn,
    },
    en: {
        name: "英文",
        list: en,
    },
};

const I18N_STORE_KEY = "PORTAL_I18N";

class I18n {
    private _activeLang = shallowRef<Record<string, string>>({});
    private _langMap: Record<string, Record<string, string>> = {};

    constructor(langMap: Record<string, I18nLang>, defaultLang: string) {
		
		// 转换语言数据,为key:value 形式 的映射报表
        Object.keys(langMap).forEach((langKey) => {
            const _lang = langMap[langKey].list.reduce(
                (map: Record<string, string>, item) => {
                	// 储存编码后的key,方便数据检索
                    map[encodeSource(item.source)] = item.translate;
                    return map;
                },
                {}
            );
            this._langMap[langKey] = _lang;
        });

		// 切换到浏览器中缓存的语言
        this.change(localStorage.getItem(I18N_STORE_KEY) || defaultLang);
    }

	// 更换语言
    change(lang: string) {
		
		// 设置当前语言标识,方便代码中根据不同语言设置不同的样式
        document.body.classList.remove(
            `lang-${localStorage.getItem(I18N_STORE_KEY)}`
        );
        document.body.classList.add(`lang-${lang}`);

		// 切换当前语言映射表,并缓存当前语言
        this._activeLang.value = this._langMap[lang] || {};
        localStorage.setItem(I18N_STORE_KEY, lang);
    }

	// 翻译
    translate(source: string) {
        const _res = this._activeLang.value[encodeSource(source)];
        // 匹配不到,则返回原始文本,匹配成功,返回翻译结果
        return _res === undefined ? source : _res;
    }
}

// 创建实例
const i18n = new I18n(_langs, localStorage.getItem(I18N_STORE_KEY) || "zhCn");

// 当前配置的语言列表,方便渲染语言切换列表
export const i18nLangList = Object.keys(_langs).map((langKey) => {
    return {
        name: _langs[langKey].name,
        key: langKey,
    };
});

// 通过hook调用实例,方便支持react、vue
export function useI18n() {
    return i18n;
}

最后一步:翻译

以下使用 vue 框架示例:

html
<template>
    <title-card>
        <template #title>
            {{ useI18n().translate("近期") }}
            <span v-html="useI18n().translate('$nbsp')"></span>
            <span style="color: #1e50ae">{{
                useI18n().translate("热点")
            }}</span>
        </template>
    </title-card>
</template>

<script setup lang="ts">
import { useI18n } from "@/plugins/i18n";
useI18n().change('en')
</script>

<style scoped lang="scss">

</style>

总结

1️⃣ 支持翻译异步数据, 2️⃣ 轻量级,50行代码