Umi 小白纪实(五)—— 结合有道翻译 API 实现 i18n 多语言功能


多语言(国际化)是一个很常见的需求,Umi 对多语言也有很好的支持

 

一、简单实现

Umi 基于 react-intl 封装了多语言插件 @umijs/plugin-locale

不过并不需要单独引入,只需要在配置文件(.umirc.js 或 config/config.js)中配置 locale

export default { locale: { // 默认语言
    default: 'zh-CN', // antd 启用国际化
    antd: true, // 浏览器页面标题支持国际化
    title: true, // 浏览器语言检测
    baseNavigator: true, }, };

然后在 src/locales 目录下创建所需要的语言包

src ├── locales │ ├── en-US.js │ ├── zh-CN.js │ └── zh-TW.js └── pages
// zh-CN.js
export default { HOME_PAGE: '首页', PAGE_NOT_FOUND: '页面不可见', }; // en-US.js
export default { HOME_PAGE: 'Home page', PAGE_NOT_FOUND: 'Page not found', };

然后在页面中通过 useIntl 拿到 formatMessage 方法,处理需要国际化的文本

import React from 'react'; import { useIntl } from 'umi'; const Demo= () => { const { formatMessage } = useIntl(); return <div>{formatMessage({ id: 'HOME_PAGE' })}</div>; };

最后在切换语言时调用 setLocale,实现多语言切换

import React from 'react'; import { setLocale } from 'umi'; const Demo = () =><button onClick={() => setLocale('en-US')}>English</buttn>;

 

 

二、更完善的多语言切换

上面是使用 setLocale 实现的多语言切换,这样的实现并不完善

比如:<htm /> 标签上的 lang 属性并没有改变,第三方库( moment.js )并没有切换语言

这些功能实现的前提,是知道当前项目是哪一种语言,这可以通过 getLocale 来获取

import { getLocale } from 'umi'; console.log(getLocale()); // en-US | zh-CN

也可以通过 localStorage 中的 umi_locale 字段来获取,因为 setLocale 会更新 umi_locale

最终就能实现一个简易的自定义 hooks

// useChangeLocale.js
 import { useMemoizedFn } from 'ahooks'; import { setLocale, getAllLocales } from 'umi'; import moment from 'moment'; export const STORAGE_LOCALES_KEY = 'umi_locale'; export const Locales = { ZH_CN: 'zh-CN', ZH_TW: 'zh-TW', EN_US: 'en-US', }; // 获取默认语言
export function getDefaultLocale() { const allLocales = getAllLocales(); const systemLanguage = navigator.language; return ( // 本地缓存
    localStorage.getItem(STORAGE_LOCALES_KEY) ||
    // 系统默认语言
    (allLocales.includes(systemLanguage) && systemLanguage) ||
    // 中文
 Locales.ZH_CN ); } // 修改 <html /> 标签中的 lang 属性
export function setLocaleToHTMLLang(locale = getDefaultLocale()) { const html = document.querySelector('html'); html && (html.lang = locale); } // 切换语言
export const useChangeLocale = () => { const changeLocale = useMemoizedFn((locale) => { localStorage.setItem(STORAGE_LOCALES_KEY, locale); setLocaleToHTMLLang(locale); setLocale(locale); moment.locale(locale); }); return changeLocale; };

然后在切换语言的时候,将原本的 setLocale 改为 useChangeLocale

import React from 'react'; import { useChangeLocale } from '@/hooks/useChangeLocale'; const Demo = () => { const changeLocale = useChangeLocale(); return <button onClick={() => changeLocale('en-US')}>English</buttn>; };

 

 

三、通过脚本翻译语言包

现在已经实现了多语言功能,但多语言功能最大的工作量并不是“实现”,而是语言包

如果需要精准的翻译,还是需要请专业的翻译人员来维护语言包

如果要求不是很严格,能接受机翻的话,就很有多的操作空间了

 

1. 繁体中文

node.js 环境有一个 chinese-conv 插件可以实现简繁转换

基于此,可以写一个翻译脚本,将 zh-CN.js 翻译为 zh-TW.js


在根目录创建脚本文件 scripts/translateCNToTW.js 

// translateCNToTW.js

const fs = require('fs'); const path = require('path'); const chineseConv = require('chinese-conv'); const LOCALES_PATH = path.join(__dirname, '../src/locales'); console.log('同步 zh-CN.js 到 zh-TW.js 中...'); const text = fs.readFileSync(path.join(LOCALES_PATH, 'zh-CN.js'), { encoding: 'utf-8', }); fs.writeFileSync( path.join(LOCALES_PATH, 'zh-TW.js'), // 读取 value 部分,并转换为繁体中文
 text.replace( /'([^']*)',$/gm,
    (_, origin) => `'${chineseConv.tify(origin)}',`, ), { encoding: 'utf-8', }, ); console.log('同步完成');

 

2. 英文

简繁转换有 chinese-conv,翻译为英文可以使用第三方 API

我使用的是有道智云,然后通过 youdao-node 进行翻译

const { default: youdao } = require('youdao-node'); youdao.config({ appKey: 'your appKey', appSecret: 'your appSecret', }); async function translateToEN(content) { try { console.log(`翻译中....${content}`); const res = await youdao.translate({ content, from: 'zh-CHS', to: 'EN', }); return res.translation[0] || content; } catch (e) { console.log(e); return content; } }

第三方翻译 API 通常会存在次数限制,为了节省资源,可以对翻译的结果做一个缓存 enCache.json

在执行脚本的时候,首先跳过已有 en-US 中已有的结果,然后先从 enCache.json 中读取,都没有的情况下再调翻译 API

// translateCNToEN.js

const fs = require('fs'); const path = require('path'); const pMap = require('p-map'); function readFile(filePath) { return fs.readFileSync(path.join(__dirname, filePath), { encoding: 'utf-8', }); } function writeFile(filePath, data) { return fs.writeFileSync(path.join(__dirname, filePath), data, { encoding: 'utf-8', }); } // 通过正则提取语言包中的 key-value
function fromEntries(textFile) { const list = []; const map = {}; textFile.replace(/(\S+):\s+'([^']*)',/gm, (_, $1, $2) => {
    map[$1] = $2; list.push({ key: $1, word: $2, }); return ''; }); return { list, map }; } console.log('同步 zh-CN.js 到 en-US.js 中...'); // 需要提前创建 enCache.json
const cacheData = JSON.parse(readFile('./enCache.json')); // 获取所有的中文 [{ key, word }]
const zhCNText = readFile('../src/locales/zh-CN.js'); const { list: zhList } = fromEntries(zhCNText); // 获取所有的英文 [{ key:value }]
const enUSText = readFile('../src/locales/en-US.js'); const { map: enMap } = fromEntries(enUSText); (async function () { // 使用 pMap 开启异步遍历任务
  const jsonDictEntries = await pMap( zhList, async (item, index) => { const { key, word } = item; // 优先调用缓存中的数据,若无再调用有道接口
      let value = enMap[key] || cacheData[word]; if (!value) { try { value = await translateToEN(word); } catch (e) { console.log(`翻译「${word}」出错`); console.log(e); } } return value ? [word, value] : []; }, { concurrency: 5 }, ); const jsonDict = Object.fromEntries(jsonDictEntries); // 写入缓存
  writeFile('./enCache.json', JSON.stringify(jsonDict, null, '  ')); // 将翻译结果同步到 en-US.js
 writeFile( '../src/locales/en-US.js', zhCNText.replace( /'([^']*)',$/gm,
      (_, origin) => `'${jsonDict[origin] || origin}',`, ), ); console.log('同步完成'); })();

 

 

四、自动执行脚本

翻译脚本创建完毕,接下来可以在 package.json 中增一个命令

{ "scripts": { "locales": "node scripts/translateCNToTW.js && node scripts/translateCNToEN.js" } }

然后在终端执行 npm run locales 就能执行翻译脚本

但每次都手动执行也太不智能了,这时候可以引入 husky + lint-staged,在每次 commit 的时候执行 npm run locales

// . lintstagedrc

{
  "src/locales/zh-CN.js": [
    "npm run locales",
    "git add src/locales/*",
    "git add scripts/*"
  ]
}

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM