国际化利器 Intl Messageformat
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。
本文作者:霜序
Formats ICU Message strings with number, date, plural, and select placeholders to create localized messages.
ICU 信息语法
ICU 是 International Components for Unicode 的简称。处理多语言和复杂文本模板提供了一种标准化的语法。它的主要用途是通过模板描述消息内容,并根据上下文(如语言、复数规则、性别等)动态生成格式化的字符串。
核心功能
动态插值
在模板中插入变量值,格式为{key, type, format}
Hello, {name}!
I have {workNum, number} things to do
Almost {pctBlack, number, percent} of them are black.
变量{key}
会被实际值替换,例如:Hello, FBB!
复数规则(Plurals)
处理与数量相关的消息变化,格式为{key, plural, matches}
{count, plural, =0{no items} one {1 item} other {# items}}
会根据传入的 count 会动态处理成合适的文本
count = 0 ===> no items
count = 1 ===> 1 item
count = 4 ===> 4 items
条件选择(Select)
基于条件选择消息内容,格式为{key, select, matches}
{gender, select, male {He} female {She} other {They}} liked your post.
会根据 gender 的传入动态输出
gender = male ===> He liked your post.
gender = female ===> She liked your post.
gender = other ===> They liked your post.
日期与时间格式化
格式化日期和时间值
The event is scheduled on {date, date, long}.
输出格式依赖于区域设置,如: January 1, 2024
总结
- 强大的模版支持,提供了多种模式供用户选择
- 跨平台支持,可以用于不同语言
Intl Messageformat
Intl MessageFormat 是一个基于 JavaScript 实现的库,用于处理多语言国际化场景,主要功能是动态格式化文本消息。
pnpm add intl-messageformat
基础使用
import { IntlMessageFormat } from "intl-messageformat";
const formatter = new IntlMessageFormat("Hello, {name}!");
const message = formatter.format({ name: "World" });
const formatter = new IntlMessageFormat(
"{count, plural, =0{no items} one {1 item} other {# items}}"
);
const message = formatter.format({ count: 0 });
const message1 = formatter.format({ count: 1 });
const message2 = formatter.format({ count: 10 });
console.log(message, message1, message2); // no items 1 items 10 items
发现了一个小小的问题,为什么在{ count: 1 }
的时候,输出的是1 items
?
IntlMessageFormat
还接受其他的参数,第二个参数为locales
用于指定当前的语言。如果不特殊指定会通过new Intl.NumberFormat().resolvedOptions().locale
获取默认值,此时的默认值为zh-CN
,无法处理one
这条规则,因此匹配到了other
这条规则。在locale
为en
的语言下能够成为我们的期望值。
那么我们只能更改传入的message
const formatter = new IntlMessageFormat(
"{count, plural, =0{no items} =1{1 item} other {# items}}"
);
const message = formatter.format({ count: 0 });
const message1 = formatter.format({ count: 1 });
const message2 = formatter.format({ count: 10 });
console.log(message, message1, message2); // no items 1 item 10 items
在我们的产品中会有这样的场景,每月 x 号,每周星期 x 的情况,当我们做国际化的时候就可以使用 ICU 信息来做。
const message = `The {day, selectordinal,
one {#st}
two {#nd}
few {#rd}
other {#th}
} day of month`;
const formatter = new IntlMessageFormat(message, "en");
console.log(formatter.format({ day: 22 })); // The 22nd day of month
console.log(formatter.format({ day: 26 })); // The 26th day of month
这里又出来一种新的类型selectordinal
,主要用于处理序数词,适用于描述顺序或排名。
one/two/few/other
分别对应着不同的序数形式。one
通常用于以1结尾的数字除了11),two
用于以2结尾的数字除了12),few
用于以3结尾的数字(除了13),而other
则用于所有其他情况。
嵌套 dom 的情况
常见例子,我们产品中需要使用 showTotal 中的 total 需要用 包裹做样式更改,最后可能提出的文本为
showTotal={(total) => (
<span>
共<span className="text-primary">{total}</span>
条数据,每页显示{pagination.pageSize}条
</span>
)}
{
W: '共',
X: '条数据,每页显示',
Y: '条',
}
一句完整的话被切分的乱七八糟,翻译成英文的时候也会出现语序问题。
这个时候就需要更改当前的方法,用 IntlMessageFormat 的 tag 模式来支持
共 <BlueText>{val1}</BlueText> 条数据<Fragment>,每页显示 {val2} 条</Fragment>
自定义 formatter
在intl-messageformat
中,您可以为消息模板中的自定义类型定义自己的格式化逻辑。例如,默认支持number
、date
和time
类型,但通过自定义formatters
,您可以添加自己的格式化类型或扩展现有类型。
const customFormatters = {
getDateTimeFormat(locales, options) {
const originalFormatter = new Intl.DateTimeFormat(locales, options);
return {
format: (value) => ` ${originalFormatter.format(value)}`,
};
},
};
const message = new IntlMessageFormat(
"The event is on {eventDate, date}",
"en",
{},
{ formatters: customFormatters }
);
const msg = message.format({ eventDate: new Date("2024-01-01") });
console.log(msg); // The event is on 1/1/2024
在formatters
中只能定义getNumberFormat/getDateTimeFormat/getPluralRules
三种类型方法
在前面的时候我们说到{pctBlack, number, percent}/{date, date, long}
,最后一个称为style
可以用于扩展内置的格式化功能
const customFormatters = {
getDateTimeFormat(locales, style) {
if (style === "customDate") {
const originalFormatter = new Intl.DateTimeFormat(locales, style);
return {
format: (value) => ` ${originalFormatter.format(value)} `,
};
}
return Intl.DateTimeFormat(locales, style);
},
};
const message = new IntlMessageFormat(
"The event is on {eventDate, date, customDate}",
"en",
{ date: { customDate: "customDate" } },
{ formatters: customFormatters }
);
const msg = message.format({ eventDate: new Date("2024-01-01") });
console.log(msg); // The event is on 1/1/2024
总结
在我们后续如果要去国际化的时候,遇到一些需要做单复数/数词的时候,应该去修改我们的英文 JSON,最后在子产品内部调用I18N.get
方法即可。因为在get
方法使用了IntlMessageFormat
去做转换
Intl Messageformat 的实现原理
Intl Messageformat 需要将传入的message
做一个拆解,获取到对应{}
中的数据,在 format
的时候通过传入的数据做填充。
icu-messageformat-parser
针对于icu message string
官方提供了对应的parser
来解析,和我们生成AST
一样,会生成固定类型的数据。
import { parse } from "@formatjs/icu-messageformat-parser";
const ast = parse(
"Hello, {name}! You have {count, plural, one {# message} other {# messages}}."
);
// [
// { type: 0, value: "Hello, " },
// { type: 1, value: "name" },
// { type: 0, value: "! You have " },
// {
// type: 6,
// value: "count",
// options: {
// "=0": { value: [{ type: 0, value: "no items" }] },
// one: { value: [{ type: 0, value: "1 item" }] },
// other: { value: [{ type: 7 }, { type: 0, value: " items" }] },
// },
// offset: 0,
// pluralType: "cardinal",
// },
// { type: 0, value: "." },
// ]
format 方法
const message = "Hello, {name}! You have {count, plural, =0{no items} one {1 item} other {# items}}.";
const formatter = new IntlMessageFormat(message, "en");
formatter.format({ name: "World", count: 0 });
当我们调用format
的时候,其实就是遍历上述的AST
,针对于不同的type
使用不同的formatter
处理好数据。
// 处理普通文本
if (isArgumentElement(el)) {
if (!value || typeof value === 'string' || typeof value === 'number') {
value =
typeof value === 'string' || typeof value === 'number'
? String(value)
: ''
}
result.push({
type: typeof value === 'string' ? PART_TYPE.literal : PART_TYPE.object,
value,
} as ObjectPart<T>)
}
// 处理 Date
if (isDateElement(el)) {
const style =
typeof el.style === 'string'
? formats.date[el.style]
: isDateTimeSkeleton(el.style)
? el.style.parsedOptions
: undefined
result.push({
type: PART_TYPE.literal,
value: formatters.getDateTimeFormat(locales, style).format(value as number),
})
}
总结
Intl MessageFormat
是一个功能强大且成熟的国际化工具。通过结合ICU
信息语法和JS的Intl API
,它能够为多语言应用提供高效、灵活的消息格式化解决方案
最后
欢迎关注【袋鼠云数栈UED团队】~
袋鼠云数栈 UED 团队持续为广大开发者分享技术成果,相继参与开源了欢迎 star
- 大数据分布式任务调度系统——Taier
- 轻量级的 Web IDE UI 框架——Molecule
- 针对大数据领域的 SQL Parser 项目——dt-sql-parser
- 袋鼠云数栈前端团队代码评审工程实践文档——code-review-practices
- 一个速度更快、配置更灵活、使用更简单的模块打包器——ko
- 一个针对 antd 的组件测试工具库——ant-design-testing
国际化利器 Intl Messageformat的更多相关文章
- 监听器和web国际化
一.监听器 1.监听器:监听器就是一个java程序,功能是监听另一个java对象变化(方法调用.属性变更) 2.监听器监听过程:事件源.事件对象.监听器对象.操作事件源 1).存在被监听对象(事件源) ...
- day12 EL 表达式和国际化开发
day12 EL 表达式和国际化开发 1. EL(Expression Language) 表达式简介 1.1 执行运算 1.2 获取web开发常用对象(el 中定义了11个隐式对象) 1.3 使用 ...
- 02 基于umi搭建React快速开发框架(国际化)
前言 之前写过一篇关于React的国际化文章,主要是用react-intl库,雅虎开源的.react-intl是用高阶组件包装一层来做国际化. 基于组件化会有一些问题,比如在一些工具方法中需要国际化, ...
- react-intl 实现 React 国际化多语言
效果预览 React Intl 国际化步骤 创建国际化资源文件 根据语言获取国际化资源 引入 react-intl 的 local data 创建 LocaleProvider 国际化上下文组件 创建 ...
- 常用 API
运行 Java 程序的参数.使用 Scanner 获取键盘输入.使用 BufferedReader 获取键盘输入.System类.Runtime类.Object类.Java 7新增的 Objects ...
- java大框架
本文章,列出了一些程序员需要学习的技术和知识点,有些技术和知识点没有写道,欢迎大家进行修改和补充,有些技术公司用到,大家需要先学习,有些技术和知识点过时,大家可以了解.本人笔记连接[[http://2 ...
- JavaScript 标准内置对象
JavaScript 标准内置对象或称全局的对象(global objects)不要和 全局对象(global object)混淆.这里说的全局的对象是说在全局作用域里的对象,全局作用域包含了全局对象 ...
- [原创]java WEB学习笔记51:国际化 概述,API 之 locale类,dataFormat类,numberFormat类, MessageFormat类,ResourceBundle 类
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- javaWEB国际化:DateFormat,NumberFormat,MessageFormat,ResourceBundle的使用
DateFormat:格式化日期的工具类,本身是一个抽象类: NumberFormat:格式化 数字 到 数字字符串,或货币字符串的字符类; MessageFormat: 可以格式化模式字符串,模式字 ...
- 国际化之MessageFormat与占位符
如果一个字符串文本中包含了多个与国际化相关的数据,可以使用MessageFormat类对这些数据进行批量处理. 例如: 在2016年1月9日的时候,一场台风导致了500间房屋的摧毁和¥1000000元 ...
随机推荐
- 保持Android Service在手机休眠后继续运行的方法
保持Android Service在手机休眠后继续运行的方法 下面小编就为大家分享一篇保持Android Service在手机休眠后继续运行的方法,具有很好的参考价值,希望对大家有所帮助.一起跟随 ...
- C#枚举帮助EnumHelper
1 public class EnumHelper 2 { 3 #region 获取枚举 4 public static List<EnumValue> GetEnumList(Type ...
- 视频笔记软件JumpVideo技术解析一:Electron案例-调用VLC播放器
大家好,我是TheGodOfKing,是 最强考研学习神器,免费视频笔记应用JumpVideo,可以快速添加截图时间戳,支持所有笔记软件,学习效率MAX!的开发者之一,分享技术的目的是想找到更多志同道 ...
- ssh: connect to host github.com port 22: Connection timed out----git问题记录
今天使用git命令提交代码,git add .,git commit -m '',git push 一顿操作猛如虎啊,嘴角一勾,邪魅一笑像往常一样期待着等着进度条100%,然后直接出现ssh: con ...
- PDF转换:从Word到Excel
一.引言 在数字化的浪潮中,PDF文件格式以其稳定性和兼容性成为了信息交流的宠儿.然而,当我们需要编辑这些PDF文件时,往往会遇到各种难题.今天,我要和大家分享的,是如何将PDF文件轻松转换成Word ...
- chrome播放webRTC的H265视频方法
需求描述 最近有需求实现浏览器直接播放摄像头视频 鉴于Camera本身支持了rtsp流,本想web直接播放rtsp,但是还不行,搜了一下webRTC实现的效果和延迟会好一些.于是就使用了mediaMT ...
- linux命令:lsof命令
lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接和硬件.所以如传输控制协议 ...
- Delphi 模糊查询和字段查询
procedure TFrmain.scGPEdit1Change(Sender: TObject); var ASql, AKey: string; //模糊查询和字段查询 const vsql1: ...
- 深入理解Hadoop读书笔记-2
背景 公司的物流业务系统目前实现了使用storm集群进行过门事件的实时计算处理,但是还有一个需求,我们需要存储每个标签上传的每条明细数据,然后进行定期的标签报表统计,这个是目前的实时计算框架无法满足的 ...
- 内部类--匿名内部类--java进阶day03
1.匿名内部类 在介绍匿名内部类前,先引用一段代码材料,通过这段代码来理解匿名内部类 如下图,我们定义了接口和一个方法,方法中调用该接口的抽象方法,这时我们要调用use方法,但是该怎么传参呢? 我们将 ...