本文主要是针对基于umi@3的前端框架的国际化方案

国际化的项目,之前实践的很少,上次踩了一次坑,发现不少问题,在这里总结一下,希望大家能提前感知,避免踩坑。

国际化要做什么事?

国际化要求从产品中抽离所有地域语言国家/地区和文化相关的元素。说白了就是,我们的产品,要给外国人使用,当他们访问我们的站点时,需要展示他们熟悉的语言。

我们在做国际化的时候,主要可以分为以下两类:

  • 静态内容,指的是我们代码中编写的固定文本内容,包括业务代码,组件,组件库等。
  • 动态内容,一般是服务端返回的内容。

如何获取用户的国际化标识?

这个非常简单,规范中有提到,通过navigator.language,可以获取到用户的偏好语言设置。这个大家可以自己去控制台测试一下。(这里可以引申出语言的标识符规范bcp47,不用细看,了解意思就好)

在umi@3中使用获取国际化标识

如何获取

我们可以看到这一篇文档,我们可以通过以下语句,在项目中获得用户的国际化配置信息。

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

当然,getLocale并不是直接获取的navigator.language,文档中描述了getLocale方法的逻辑:

可以看到在你setLocale之后,umi都只会获取localStorage的语言信息。即便是第一次访问,现代浏览器绝大部分都能获取到navigator.language配置。所以default属性,只是在极端情况下可能起作用,且就算你不设置,umi也会保底识别为中文。

这也就是有些同学疑惑,明明设置了locale.default属性,但是似乎没有起作用的一个原因。

配置umi-locale以及antd

// umirc.js OR config/config.ts
export default {locale: {antd: true,},
};
  • 如上所示,开启locale的antd配置,支持自动配置antd组件库的国际化。这样其实就比较满足需求了,更多配置(如标题的配置)还是请通过文档查看使用方式。

定义语言包

要在页面中展示不同的语言,那首先要准备好语言包。我们可以直接在src/locales/目录下新建语言包文件,命名规范如下:

比如下面,我们就建立了“英文-美国”和“中文-中国”两个语言包

每一个语言包,都是一个Object对象,key作为id,value作为内容。语言包的内容,大概如下:

// src/locales/en-US.js
export default {语言: 'language',主页: 'Home',关于: 'about',
}
// src/locales/zh-CN.js
export default {语言: '语言',主页: '主页',关于: '关于',
}

当你的项目比较庞大的时候,语言包可能需要根据页面进行拆分,建议你分别在不同的页面下建立locales目录,并分别建立语言包。下面的例子描述了如何给一个独立页面(首页)语言包:

// src/locales/en-US.js
import home from '@/pages/home/locales/en-US';
export default {语言: 'language',主页: 'Home',关于: 'about',...home,
}
// src/locales/zh-CN.js
import home from '@/pages/home/locales/zh-CN';
export default {语言: '语言',主页: '主页',关于: '关于',...home,
}

home页面下:

// src/pages/home/locales/en-US.js
export default {'home.主标题': 'Main Title','home.副标题': 'Sub Title',
}
// src/pages/home/locales/zh-CN.js
export default {'home.主标题': '主标题','home.副标题': '副标题',
}

语言包的推荐规范

从上面的示例可以看到,可以按如下方式编写语言包:

  • key可以使用中文(可以避免专有名词不易识别,在开发时辨识困难)。
  • 当前页面只能引用本页面或全局的语言包,不应该引用其它页面的语言包。
  • 本页面的语言包,请使用路径作为前缀。(如/home/search页面 ‘home.search.关键字’)。
  • 在src/locales/[language].js文件下,引入其它页面下对应的语言包。(这个操作我想想是否能再简化)

应用到业务代码

页面国际化

通过文档可以发现,umi-locale提供了一个hooks - useIntl给我们使用。下面示例演示了如何在代码中使用语言包

import { useIntl } from 'umi';export default () => {const { formatMessage } = useIntl();return (<div>{formatMessage({ id: 'home.主标题' })}</div>);
}
  • class组件咋办?(其实老办法就可以,但我这里不写,未来还是希望大家更多地使用函数组件)

组件国际化

一般情况,组件本身是不会包含太多文本内容的(大多组件是为了封装结构和功能),如果有带少量文本的内容,比如“加载更多”组件,你可以将它作为一个可选属性,抛出给使用者去处理国际化。

但如果组件中的文本内容过多(比如日历图组件),那最好在组件内部完成国际化。通过为组件添加一个locale属性,并export预设的国际化配置,允许使用者自行设置,或结合组件库(参考antd)实现。

接口数据国际化

接口请求的内容,必须要求服务端本身支持国际化配置参数。通常请求的国际化参数,会通过headers或query传递。但不论是哪种方式,请你在唯一处添加。在项目中常用的@ali-whale/fetch-web,umi-request,axios等,都是可以配置拦截器,为每次请求添加必要的参数。

  • 使用@ali-whale/fetch-web时,添加拦截器(0.8.x)
// 这个文件通常是src/utils/fetch.ts
import { get, post, interceptor } from '@ali-whale/fetch-web';
import { getLocale } from 'umi';// 注:0.8.x的fetch-web暂时只支持单例(umi-request支持创建多实例)
// 添加请求拦截器(支持async函数)
// 在请求拦截器中,你将得到包含三个内容的对象:
// @param url 当前发起请求的url(如果是get,则是拼装之后的),用作fetch的第一个参数
// @param options fetch的第二个参数,详情请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch
// @param config fetch-web工具中,get/post/put/del的第三个参数,具体格式请查看文档。
interceptor.request.use(({ url, options, config }) => {if(options.method === 'GET'){// 为所有GET请求添加国际化配置// 返回包含options的对象,表示对options做出修改return {options: {...options,headers:{...options.headers,'request-locale': getLocale(), // 此处的'request-locale'只是示意}}}}// 不作修改return undefined;
});
  • 使用umi-request添加拦截器
// 这个文件通常是src/utils/fetch.ts
import { extend } from 'umi-request';
import { getLocale } from 'umi';// 创建请求实例
const request = extend({// 一些request的配置
});// 为request实例添加请求拦截器
// 注:与@ali-whale/fetch-web的请求拦截器不同,这里有两个参数(相比没有config)
request.interceptors.request.use((url, options) => {if(options.method === 'GET'){// 为所有GET请求添加国际化配置// 返回修改后的url, optionsreturn {url,options: {...options,headers:{...options.headers,'request-locale': getLocale(), // 此处的'request-locale'只是示意}}}}// 不作修改return undefined;
});

我们遇到的一个大坑及解决方案

上述使用的方式看似没有什么问题,但其实隐含着一个大坑等着你,而且很可能是在临近上线或已经上线的时候才会爆发。原因如下:

处于成本等因素考虑,我们国际化支持的语言数量是很有限的,很多时候就只有中英双语。但通过getLocale获得的用户设备的语言配置,却五花八门。有标准,也有非标准,有可能获得的是各种语言和地区。当我们使用useIntl的时候,react-intl会自行匹配各种语言,并最终映射到我们预设的某种语言包上。但其他用到国际化的地方(如发送请求,组件等)可能就不会那么像react-intl一样进行匹配,最终的呈现的结果就是,页面上存在多种语言混杂显示甚至会抛出异常

解决这个问题的关键就在于:我们需要getLocale方法得到的是我们映射之后可用的语言包。而@umijs/plugin-locale允许我们在src/app.ts文件对getLocale方法进行覆盖:

// src/app.ts
import qs from 'qs';
import { getAllLocales } from 'umi';/* 自动将用户国际化配置转换为当前可用配置 */
function parseLanguage(lang = '') {const availableLanguages = getAllLocales();try {// 完全匹配if (availableLanguages.includes(lang)) {return lang;}// 前缀匹配const matchedLang = availableLanguages.find((aLang = '') => {return aLang.split('-')[0] === lang.split('-')[0];});if (matchedLang) {return matchedLang;}} catch (e) {console.error("Error while parse user's locale");console.error(e);}return availableLanguages[0] || 'zh-CN';
}export const locale = {/* 覆盖 @umijs/plugin-locale 的getLocale逻辑,保证取得的国际化配置,都是当前可用的 */getLocale() {// 1. 判断urlQuery上是否存在显示指定的locale属性const { search } = window.location;const { locale: localeFromUrl } = qs.parse(search, { ignoreQueryPrefix: true });let lang: string | null = localeFromUrl as string;if (lang) {const parsedLanguage = parseLanguage(lang);// 将locale信息暂存window.localStorage.setItem('umi_locale', parsedLanguage);return parsedLanguage;}// 2. 判断localStorage中是否存在国际化配置lang = window.localStorage.getItem('umi_locale');if (lang) {return parseLanguage(lang);}// 3. 从navigator中获取国际化配置lang = window.navigator.language;return parseLanguage(lang);},
};

逻辑解读:

  1. 优先从url获取,比如设置了?locale=zh-CN
  2. 在不满足1的情况下,locale的来源与plugin类似:先从localStorage获取,再从navigator获取,默认值为zh-CN
  3. 不论从哪里获得的locale配置,都会通过parseLanguage进行过滤,得到有效的语言包。

如此一来,我们就可以比较放心地在业务代码中直接使用getLocale了。

切换语言

支持国际化的项目中,切换语言一定是必要的。如果没有修改上述getLocale的逻辑,直接调用setLocale就可以了。但由于我们使用上述修改,获得了更安全的语言包配置,且加入了url的配置优先的逻辑,那么在存在url配置的时候,直接setLocale就无法生效了。为此,我的建议如下:

  • 清除url上的配置 或 为url添加?locale=新的语言。
  • 切换语言时,调用setLocale方法(用于触发react-intl),当然如果你上一步如果进行了页面刷新,可以不做。

总结

  • 注意语言包的key命名规范,还有存放位置。
  • 业务代码中,直接使用useIntl的方式。
  • 组件中尽量少存放文本信息,尽量通过属性控制。
  • 接口的国际化参数,通过拦截器统一配置。
  • 调整app.ts,避免getLocale获得无效语言。
  • 切换语言时,因为app.ts中对url配置的获取逻辑,需要移除或修改url中的locale配置。

如何为你的项目添加国际化配置(umi@3的国际化实践)相关推荐

  1. 在Idea中为项目添加Tomcat配置

    1.配置JDK 1).点击右上角的 按钮,调出project structure页面 2)选择Project,进行配置. 2.配置Artifacts 1)在Project Structure页面,点击 ...

  2. SpringBoot 项目 添加 redis配置

    一.新建一个springboot 项目,springboot项目创建过程详见:传送门 二.pom.xml依赖配置 <!-- redis 配置 --> <dependency>& ...

  3. SpringBoot2.1.5(18)--- 国际化配置,SpringBoot Locale 国际化使用方法

    在项目中,很多时候需要国际化的支持,这篇文章要介绍一下springboot项目中多语言国际化的使用. 本文项目结构如图: springboot默认就支持国际化的,而且不需要你过多的做什么配置,只需要在 ...

  4. idea为web项目添加tomcat并配置Artifacts

    背景 有一天聪明的小峰从网上找了一个web项目,但是只有代码没有启动步骤,作为小白的我解决这个问题也不太顺利特此记录一下. 主要解决的问题: 为项目添加tomact 配置Artifacts 引入 ja ...

  5. vue-cli构建的项目手动添加eslint配置

    一.package.json里配置添加 1.scripts里添加快捷eslint检查命令 "lint": "eslint --ext .js,.vue src" ...

  6. Yii2语言国际化配置Twig翻译解决方案

    转载 Yii2语言国际化配置Twig翻译解决方案 我自己在写项目的时候,不喜欢使用php自身的模板,主要是各种PHP标签让我烦,而且对Html的标签兼容也不够友好,所以我后面采用了twig模板,配置之 ...

  7. 【SpringMVC 笔记】SpringMVC 原理 + 入门项目(xml 配置版 vs 注解版)

    SpringMVC 入门项目 什么是 SpringMVC? 中心控制器 SpringMVC 执行原理 执行流程 xml 配置版 1.创建一个 Web 项目 2.pom.xml 中导入 SpringMV ...

  8. vue-i18n及ElementUI国际化配置步骤

    1.vue-I18n使用 1.1.下载依赖 注意:vue2.0要用8版本的,使用9版本的会报错 npm install vue-i18n // 默认安装最新版本npm i vue-i18n@8.27. ...

  9. js 前端实现国际化配置

    最近项目中需求配置国际化,再网上找了相关资料,最后选择了i18n来处理国际化,可是在使用过程中碰到了兼容性的问题,后来想想就自己用js实现了一个简单的国际化配置,具体实现步骤如下: 1,参考i18n的 ...

最新文章

  1. matlab207a,MATLAB教程R2012a课后习题答案
  2. 运行快捷指令无法连接服务器失败,快捷指令打不开怎么回事?iPhone快捷指令无法载入的解决办法...
  3. 数据库基础知识——DQL语言(二)
  4. 西南交大计算机辅助制造a卷,计算机辅助制造 西南交大作业.doc
  5. 特殊时期,找工作的 9 点建议!
  6. 基于麻雀算法的投影寻踪模型 - 附代码
  7. TensorFlow 安装教程
  8. 淘淘商城项目mysql,idea搭建淘淘商城项目
  9. Win7 蓝牙耳机无法使用
  10. lldp协议代码阅读_LLDP协议、STP协议 笔记
  11. 营销工具-优惠券相关设计思路
  12. 快递电子面单打印接口对接demo-JAVA
  13. RocketMQ(十)RocketMQ事务消息
  14. Excel常用电子表格公式大全【汇总篇】
  15. Jframe任务栏图标隐藏
  16. [005量化交易] python收盘价绘图
  17. msp430f149最小核心板和bsl下载器连线
  18. Handler同步屏障
  19. 程序员公众号用什么工具写?
  20. Android新闻客户端

热门文章

  1. 高级的验证码识别软件都能对图片验证码进行识别
  2. 字符串处理--去除首尾双引号
  3. 海航陈峰带领海航艰难起飞
  4. 宝塔面板部署django项目
  5. USB转多串口/单串口方案
  6. mysql数据类型--[整数类型]--smallint类型
  7. python画图显示中文_Python的matplotlib库画图不能显示中文问题解决
  8. 比起30岁一事无成更可怕的是:你还在虚度光阴。
  9. 分布式锁主动续期的入门级实现-自省 | 简约而不简单
  10. 在右键新建菜单中添加新项目