1.0.0 • Published 1 year ago

@alicloud/xconsole-intl-extended v1.0.0

Weekly downloads
-
License
MIT
Repository
-
Last release
1 year ago

@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 仅用于展示端,stringJSX.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,见过 xx1xx2... 然后在代码里把它们拼成一个段落(可能是列表)的行为,恶心至极
  • 注意标点,中文下不允许有英文标点,英文下不允许有中文标点
  • 英文、数字、HTML inline 元素,若和中文贴着,两者之间加空格
  • 仅允许 <a><em><code><strong><kbd>这几个 inline 元素
  • <ul><li>*␣ 打头(注意有空格)
  • <ol><li>1.␣2.␣...打头(注意有空格)
  • 用作例子的部分,以 <code> 包裹

关于 key 的「类型约定」建议

我发现很多人对「类型」一点都不感冒,什么是「属性」,什么是「操作」一点都没有概念,导致写出来的文案虽然在用户看来没有问题,但定义的却一塌糊涂。

一般我会用 一个 单词来表示文案的类型,每种类型在含义、书写上都会有一定的特点,在一个项目下,不可能有太多的类型,以下是我在项目中常用的类型:

单词简写含义场景文案规范
attra属性标题名词,英文遵循首每个单词(除介词、冠词等)字母大写
opo属性按钮动词,英文遵循首每个单词(除介词、冠词等)字母大写
typet类型字段展示名词,英文遵循首每个单词(除介词、冠词等)字母大写
statuss状态字段展示形容词,英文遵循首每个单词(除介词、冠词等)字母大写
title-标题标题名词,英文遵循首每个单词(除介词、冠词等)字母大写
labell标题按钮、字段展示名词/动词,英文遵循首每个单词(除介词、冠词等)字母大写
messagem长句子标题必须有结束标点,不可断句,不可断章
phrasep短句子标题没有结束标点的句子(不建议)

以上,我们可以将在特定对象的 attroptypestatus 进行聚合,比如:

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}}}`
};

我更推荐这种聚合的定义方式,它有以下好处:

  1. 更内聚,相关的内容一定会定义在一个地方
  2. 可以用 intlChoices 搭配常量枚举快速生成选项列表
  3. 可以用 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 级别的元素,可以用 strongemcodekbda,这些元素会有一定的样式。

如何转义花括号?

This '{isn''t}' obviousThis {isn't} obvious

参考 https://unicode-org.github.io/icu/userguide/format_parse/messages/#quotingescaping

文案如何不断章?

不断句和不断章,这个在国际化的场景下非常重要。不断句可以利用插值,那么不断章(即一个文案中可能有多个段落)怎么处理呢?

你的 key 需要有 !lines 指令(同样可以自定义),你写文案的时候,该文案是带换行(\n)的。

文案中的每一行,会被解析成 pul > 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 的第三个参数,htmllines 可以同时存在,也可以只有一个为 true

intl('x:message:with_html_but_no_indicators', undefined, {
  html: true,
  lines: true
})

如何避免传错 key?

你必须本地有一份「完整」的兜底文案,这很重要,一是保证代码的完整性,二是,在使用 TS 的场景下,可以帮你避免输错 key 的场景。