关于分享我的Vite 前端国际化方案

背景

相信很多的前端同学在项目中有过做国际化的经验,我来猜猜大家伙做国际化的步骤:是不是先在locale\cn里面定义好中文,然后马上去谷歌/百度翻译查好相应的翻译,然后copy到locale\en中,一开始我也是诸如这样的步骤去实现,久而久之,发现特别低效,如果你有跟我一样的体验和经历,不妨耐心的继续看下去。

目标

如何去优化我们的国际化开发体验,我们先来想象/预设我们的理想工作状态,我们得出了以下的实现目标。

  • 只需要编写locale\cn,也就是说,我只需要关注/维护我的中文内容
  • 每当我打包/dev/locale\cn变化时,自动调用翻译,把locale\en给更新,然后打包反馈给生产/开发模式下

我认为,我只要实现了以上2个点,我的国际化开发体验将会大大提升,因为不需要频繁得去手动翻译了,专注在我愉快的开发之中~

实现

首先,核心是翻译能力,我选取的是百度翻译,没有为什么,因为他开源又免费

这里贴上百度通用文本翻译api的地址百度翻译开放平台

只要你完成自由开发者注册通过后,拿到自己的appid/key就可以按照api接入文档,实现服务调用翻译能力。

所以,我开始对接api

api翻译

首先,实现的目标是翻译locale\cn中定义的所有中文

locale\cn中写好了我们项目需要用到的中文,接着我们开始编写node.js来实现翻译

在项目根目录下新建translate.js,这是我们的node源码

js 复制代码
// 随机生成10位随机数
function randomNum() {
    var num = "";
    for (var i = 0; i < 10; i++) {
        num += Math.floor(Math.random() * 10);
    }
    return num;
}

const axios = require("axios");
const md5 = require('md5');
const appId = "xxx"; // 你的开发者appid
const key = "xxx"; // 你的开发者密钥
const api = "https://fanyi-api.baidu.com/api/trans/vip/translate";
const qs = require("qs");
const get = require("lodash/get");
const trim = require("lodash/trim");

async function translateText2En(txt) {
    const salt = randomNum();

    // 先生成sign
    const strLine = `${appId}${txt}${salt}${key}`;
    const sign = md5(strLine);

    // 设置utf-8
    axios.defaults.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';

    const res = await axios.post(api + "?" + qs.stringify({
        q: txt,
        from: "zh",
        to: "en",
        appid: appId,
        salt,
        sign
    }));

    return get(res.data.trans_result, "[0].dst");
}


function translateLocale() {

    // 读取locale下的cn.ts 内容
    const fs = require("fs");
    const path = require("path");
    const cnPath = path.join(__dirname, "/src/locale/cn.ts");

    fs.readFile(cnPath, "utf-8", (err, data) => {
        if (err) {
            console.log("读取cn.ts文件失败");
            return;
        }

        // 读取成功
        // console.log(data);

        // 正则匹配中文
        const reg = /"(.+)": "(.+)",?/g;
        const res = data.match(reg);

        data = data.replace(/cn/g, "en");

        Promise.all(res.map(async (item, index) => {
            return new Promise(async (resolve, rej) => {
                const [key, value] = item.split(":").map(v => trim(v).replace(/"/g, "").replace(/,/g, ""));

                const enValue = await translateText2En(value);
    
                // 将中文替换成英文
                data = data.replace(value, enValue);
                resolve(data);
            })
        })).then(() => {

            // 写入en.ts文件
            const enPath = path.join(__dirname, "/src/locale/en.ts");
            fs.writeFile(enPath, data, (err) => {
                if (err) {
                    console.log("写入en.ts文件失败");
                    return;
                }
                console.log("写入en.ts文件成功");
            });
        })
    });
}

translateLocale();

实现原理非常简单,正则匹配出对象键值对,然后把中文批量请求百度翻译api

然后把英文和key一起替换cn中的文本内容,最后覆盖locale\en

这一步初步完成了目标1,因为我再也不需要去手动查翻译了

vite插件

接着我们实现目标2,设计vite插件,让这个能力运用到我们的生产开发过程中。

接着我们命名一个translatePlugin.js 这是我们vite插件的源码

javascript 复制代码
const { exec } = require('child_process');
const { watch } = require('chokidar');
const path = require('path');

function executeCommand(command) {
    return new Promise((resolve, reject) => {
        exec(command, (error, stdout, stderr) => {
            if (error) {
                console.error(`Error executing the command: ${error.message}`);
                reject(error);
            }
            if (stderr) {
                console.error(`Command stderr: ${stderr}`);
                reject(stderr);
            }
            console.log(`Command stdout: ${stdout}`);
            resolve(stdout);
        });
    });
}


export default function translatePlugin() {
    let isTranslating = false;

    return {
        name: 'translate-plugin',
        config(config) {
            const filePath = path.join(process.cwd(), 'src/locale/cn.ts');
            const watcher = watch(filePath);

            console.log(filePath)
            watcher.on('change', async () => {

                if (!isTranslating) {
                    isTranslating = true;
                    console.log(`File ${filePath} has changed. Running "node translate" command...`);
                    await executeCommand('node translate');
                    isTranslating = false;
                    console.log('Translation complete. Restarting the Vite server...');
                }
            });
        },
        async buildStart() {
            console.log('Running "node translate" command...');
            await executeCommand('node translate');
        }
    }
}

主要是2个点需要注意的点

  • 监听文件变化
  • buildStart钩子

使用我们的插件

javascript 复制代码
import translatePlugin from './vitePlugin/translatePlugin';

成果展示

这是我的测试页面源码

tsx 复制代码
import "./index.less";
import useI18n from '@/hooks/useI18n';
import { useLanguageStore } from '@/store/useLanguage';


export default function Test() {

    const t = useI18n();
    const { toggleLanguage } = useLanguageStore();

    return <div>
        <div>测试多语言:{t("title")}</div>
        <button onClick={toggleLanguage}>改变语言模式</button>
    </div>
}

这是我的中文 locale

ts 复制代码
const cn = {
    "title": "关于分享我的Vite 前端国际化方案",
    "time": "2024年1月8日",
    "author": "Lemon",
};

export default cn;

这是我的英文locale,啥也没有

ts 复制代码
const en = {
};

export default en;

我们启动项目,一起来看看吧

我们来改动一下locale\cn

总结

目前为止,我们已经初步实现了愉快的国际化开发了

但是优化点还很多

  • 缓存翻译结果,避免重复翻译相同的key
  • 更合理的watch diff 只翻译更新的内容
  • 解决一些语法报错导致的node脚本崩溃问题

如果我的内容有帮助到你,不妨点个赞~

github star or not?

相关推荐
卸任11 小时前
Vibe coding工具使用心得
前端·ai编程
brien912 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance12 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M12 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写12 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李12 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易13 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马13 小时前
前沿视界
前端
kyriewen13 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易13 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员