@alicloud/xconsole-intl-extended v1.0.0
@alicloud/xconsole-intl-extended
对 @alicloud/console-components-intl 的封装,使其更易用。
该版本依赖
@alicloud/console-components-intl版本2.x,如果是1.x可以使用@ali/wind-x-intl。
吐槽 @alicloud/console-components-intl
| 问题 | 改进 |
|---|---|
对 @alicloud/console-components-intl 的使用有加载顺序的要求 | 不需要 |
| 没有样式规范 | 定义了一套方便用户的规则,可以按照 key 的后缀指令选择是返回字符串还是带标准样式的 JSX,既保证样式标准性,又可以避免断句、断章等问题 |
没有类型保护,编译期无法检测到某个 key 不存在,从而导致线上问题 | 通过 TS 泛型,既保证代码的完整性,又能够避免线上问题 |
| 很难单独使用在 npm 包中 | 因为是工厂方法,可以创建多个实例 |
| 没有对 locale 的 key 做兼容 | 对大小和连字符等做了较强的兼容 |
安装
tnpm i -S react @alicloud/console-components @alicloud/console-components-intl styled-components @alicloud/xconsole-intl-extended@alicloud/xconsole-intl-extended 之前的是它的 peer dependency,必须由使用者安装。
设置
首先请非常冷酷无情地删掉狗屎的
initializer.js。
在你项目的 src 下建一个目录,并创建如下文件(也可以按照你的个人喜好):
src/
└─ intl/
├─ locales/
│ ├─ en-us.json
│ ├─ ...
│ └─ zh-cn.json
├─ index.ts
└─ messages.tsintl/
建议在你的 webpack 配置下,为 src 目录配置 alias :(个人习惯,Xconsole 下是 ~),比如在 webpack.config.base.json 里:
const path = require('path');
// ...
module.exports = {
// ...
resolve: {
alias: {
':': path.resolve(__dirname, '../src')
}
// ...
}
// ...
}如果你用 TS,修改 tsconfig.json:
{
"compilerOptions": {
// ...
"paths": {
":/*": ["src/*"]
}
// ...
}
}intl/locales/[locale].json
locales 下只是对 美杜莎 上的一个导出和备份,主要是为了在本地仓库有个参考,可以快速定位到正确的 key。
intl/message.ts
import fallbackMessages from './locales/en-us.json'; // locales 下的 json 文件最多就是个参考,但还是会打一个到包里做兜底
export default {
...fallbackMessages,
// ↓ 开发期间「增加」新的文案,发布前必须提交到 medusa,并删除之间的文案 - BEGIN
// ↑ 开发期间「增加」新的文案,发布前必须提交到 medusa,并删除之间的文案 - END
...window.SOME_GLOBAL_MESSAGES // window 或哪里可以直接拿到的 medusa 输出的对象
// ↓ 开发期间「修改」已有的文案,发布前必须提交到 medusa,并删除之间的文案 - BEGIN
// ↑ 开发期间「修改」已有的文案,发布前必须提交到 medusa,并删除之间的文案 - END
};注意:SOME_GLOBAL_MESSAGES 视你的具体应用而定。
intl/index.ts
import createIntl from '@alicloud/xconsole-intl-extended';
import messages from './messages';
const intl = createIntl(messages, LOCALE, { // LOCALE 视你应用的具体情况而定,你也可以通过 options 做一些定制
extraValues
});
export default intl;
export const { // 你也可以不必输出它们,而直接使用 intl.xx 进行引用
getLocale,
intlDate,
intlNumber,
intlPercentage,
intlPercentageTuple,
intlByte,
intlByteTuple,
intlCurrency,
intlConst,
intlChoices
} = intl;如何使用
记住:在你的项目里,你永远只跟 :/intl 打交道,绝对不要染指 @alicloud/console-components-intl。
import intl, {
// other stuff
} from ':/intl';
// 静态调用
intl(...);
// 在 JSX 中
<div>{intl(...)}</div>关于返回类型
intl 方法虽然返回的类型只有 string,但实际可能返回 JSX.Element(在指定 !lines 或 !html 的情况下),这个非常不好写,又不适合用泛型(不知怎么回事会返回 any)。
好在大多数情况下,intl 仅用于展示端,string 或 JSX.Element 可以无感。
如果要强行指定 JSX.Element 的话,就强行转换类型吧:
const message = intl('xxx!html') as unknown as JSX.Element;国际化规范(最佳实践)
关于 key
- 全小写,单词之间以
_分隔 - 建议以
:做分隔(.也行),该分隔符一般 - 一般是
对象:类型约定:含义?参数!长相- 对象:表示功能对象,但在某些包下面,有明确的上下文的时候可以不需要
- 类型约定:文案的类型
- 含义:表示文案的意思,需要让读者看到它就知道大致的意思,而不需要进一步看文案的内容,如果有插值则以
{val1,val2}的形式挂上 - 参数:如果内部有
select语句则带上参数,如?attr、?op、?status、?type - 长相:用
!html表示内容里边有 HTML 元素,!lines表示内容里边的换行需要解析成<p>、<ul>、<ol>
- 尽可能聚合,如用户的属性
user?attr,然后内部使用select - 通用的文案以
_打头,如_?op
关于 value
- 不断句,即不能有
我有+个女朋友两条,而是应该用插值我有 {n, number} 个女朋友 - 不断章,相关联的文案放一个 key 里,不可一行文案对一个 key,见过
xx1、xx2... 然后在代码里把它们拼成一个段落(可能是列表)的行为,恶心至极 - 注意标点,中文下不允许有英文标点,英文下不允许有中文标点
- 英文、数字、HTML inline 元素,若和中文贴着,两者之间加空格
- 仅允许
<a>、<em>、<code>、<strong>、<kbd>这几个 inline 元素 <ul>的<li>以*␣打头(注意有空格)<ol>的<li>以1.␣、2.␣...打头(注意有空格)- 用作例子的部分,以
<code>包裹
关于 key 的「类型约定」建议
我发现很多人对「类型」一点都不感冒,什么是「属性」,什么是「操作」一点都没有概念,导致写出来的文案虽然在用户看来没有问题,但定义的却一塌糊涂。
一般我会用 一个 单词来表示文案的类型,每种类型在含义、书写上都会有一定的特点,在一个项目下,不可能有太多的类型,以下是我在项目中常用的类型:
| 单词 | 简写 | 含义 | 场景 | 文案规范 |
|---|---|---|---|---|
attr | a | 属性 | 标题 | 名词,英文遵循首每个单词(除介词、冠词等)字母大写 |
op | o | 属性 | 按钮 | 动词,英文遵循首每个单词(除介词、冠词等)字母大写 |
type | t | 类型 | 字段展示 | 名词,英文遵循首每个单词(除介词、冠词等)字母大写 |
status | s | 状态 | 字段展示 | 形容词,英文遵循首每个单词(除介词、冠词等)字母大写 |
title | - | 标题 | 标题 | 名词,英文遵循首每个单词(除介词、冠词等)字母大写 |
label | l | 标题 | 按钮、字段展示 | 名词/动词,英文遵循首每个单词(除介词、冠词等)字母大写 |
message | m | 长句子 | 标题 | 必须有结束标点,不可断句,不可断章 |
phrase | p | 短句子 | 标题 | 没有结束标点的句子(不建议) |
以上,我们可以将在特定对象的 attr、op、type、status 进行聚合,比如:
export default {
'user:attr:id': '用户 ID',
'user:attr:name': '用户名',
'user:op:create': '创建用户',
'user:op:edit': '编辑用户',
'user:op:delete': '删除用户',
'user:type:normal': '普通',
'user:type:vip': 'VIP',
'user:type:svip': '超级 VIP',
'user:status:new': '未认证',
'user:status:verified': '已认证',
'user:status:unregstered': '已注销',
};聚合后,可以是这样:
export default {
'user?attr': `{attr, select,
ID {用户 ID}
NAME {用户名}
other {{attr}}}`,
'user?op': `{op, select,
CREATE {创建用户}
EDIT {编辑用户}
DELETE {删除用户}
other {{op}}}`,
'user?type': `{type, select,
NORMAL {普通}
VIP {VIP}
SVIP {超级 VIP}
other {{type}}}`,
'user?status': `{status, select,
NEW {未认证}
VERIFIED {已认证}
UNREGSTERED {已注销}
other {{status}}}`
};我更推荐这种聚合的定义方式,它有以下好处:
- 更内聚,相关的内容一定会定义在一个地方
- 可以用
intlChoices搭配常量枚举快速生成选项列表 - 可以用
intlConst搭配常量枚举快速转化常量为文案
FAQ
可以直接用 @alicloud/console-components-intl 么?
可以,但一万个不推荐。你可以在你的 intl/index.ts 的最末把 @alicloud/console-components-intl export 出去再用,但千万不要直接用 @alicloud/console-components-intl,
原因是你在别人 import 的 @alicloud/console-components-intl 有可能是没有初始化完毕的——这就是那个臭烘烘的 initializer.js 的来由。
文案中可否有 HTML?
可以,首先你的 key 在末尾要带上 HTML 指令 !html(你可以自定义)。
其次,不要有 block 级别的元素,可以用 strong、em、code、kbd、a,这些元素会有一定的样式。
如何转义花括号?
This '{isn''t}' obvious → This {isn't} obvious
参考 https://unicode-org.github.io/icu/userguide/format_parse/messages/#quotingescaping
文案如何不断章?
不断句和不断章,这个在国际化的场景下非常重要。不断句可以利用插值,那么不断章(即一个文案中可能有多个段落)怎么处理呢?
你的 key 需要有 !lines 指令(同样可以自定义),你写文案的时候,该文案是带换行(\n)的。
文案中的每一行,会被解析成 p,ul > li(该行以 1.␣、2.␣ 等打头)或 ol > li(该行以 *␣ 打头)。
!html 和 !lines 是可以合起来用的,比如 !html!lines 或 !lines!html(建议选择一种风格,不要都用)。
文案中的链接怎么跟渠道链接配置结合?
你只需要在调用工厂方法的时候,第三个 options 参数中传入 extraValues 即可:
windXIntl(messages, LOCALE, { // LOCALE 视你应用的具体情况而定,你也可以通过 options 做一些定制
extraValues: LINKS
});以上,LINKS 是你的应用在 viper 配置的渠道链接的输出。
而你在定义你的文案时,这么定义:
{
"x:message:about_xx_with_some_link!html": "...,详情可以查看 <a href=\"{help:xxx}\" target=\"_blank\">帮助文档</a>"
}注意 help:xxx 不可定义成 help.xxx,会报错(至少我当时用的时候是这样)。
于是,当你 intl('x:message:about_xx_with_some_link!html') 的时候,其中的链接自然而然地被替换成了渠道链接中对应的配置。
但是,如果渠道链接是需要参数的,就无法这么做了。
文案中有 HTML 和换行,但 key 末尾没有指令,如何正常展示?
首先,强烈推荐 key 末尾带上 !html 和 !lines 指令,它的好处在于看到 key 就知到它长什么样,而且你不需要额外的编码就可以自动展示成你期望的样式。
但如果你一定不加,你可以用 intl 的第三个参数,html 和 lines 可以同时存在,也可以只有一个为 true:
intl('x:message:with_html_but_no_indicators', undefined, {
html: true,
lines: true
})如何避免传错 key?
你必须本地有一份「完整」的兜底文案,这很重要,一是保证代码的完整性,二是,在使用 TS 的场景下,可以帮你避免输错 key 的场景。
3 years ago