@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.ts
intl/
建议在你的 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 的场景。
1 year ago