# @alicloud/xconsole-intl-extended

> 强化 Intl，以最佳实践和规范，统一样式，减少代码量

Latest version **1.0.0** (published 2023-03-24) · MIT license · 0 weekly downloads

## Install

```sh
npm install @alicloud/xconsole-intl-extended
pnpm add @alicloud/xconsole-intl-extended
yarn add @alicloud/xconsole-intl-extended
bun add @alicloud/xconsole-intl-extended
```

## Health

**Score 30/100 (F)** — status: abandoned.

Positive: has types; esm support; no vulnerabilities; high quality score.

Warnings: low downloads.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.0.0 |
| Published | 2023-03-24 |
| First published | 2023-03-24 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 4 |
| Unpacked size | 473.2 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | jianchun.wjc |
| Maintainers | jacksontian, fengmk2, pagecao, aliyunsdkteam, console-fe |

## Links

- npm: https://www.npmjs.com/package/@alicloud/xconsole-intl-extended
- npm.io page: https://npm.io/package/@alicloud/xconsole-intl-extended

## Dependencies (4)

- [lodash-es](https://npm.io/package/lodash-es.md) ^4.17.21
- [@alicloud/mere-dom](https://npm.io/package/@alicloud/mere-dom.md) ^1.6.10
- [@alicloud/console-base-rc-intl](https://npm.io/package/@alicloud/console-base-rc-intl.md) ^1.4.9
- [@alicloud/console-components-intl](https://npm.io/package/@alicloud/console-components-intl.md) 2.0.1

## Recent versions

- 1.0.0 (latest) — 2023-03-24

## README

# @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 做兼容                              | 对大小和连字符等做了较强的兼容                                                          |

## 安装

```shell
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` 下建一个目录，并创建如下文件（也可以按照你的个人喜好）：

```text
src/
 └─ intl/
     ├─ locales/
     │   ├─ en-us.json
     │   ├─ ...
     │   └─ zh-cn.json
     ├─ index.ts
     └─ messages.ts
```

## `intl/`

建议在你的 webpack 配置下，为 `src` 目录配置 alias `:`（个人习惯，Xconsole 下是 `~`），比如在 `webpack.config.base.json` 里：

```javascript
const path = require('path');
// ...

module.exports = {
  // ...
  resolve: {
    alias: {
      ':': path.resolve(__dirname, '../src')
    }
    // ...
  }
  // ...
}
```

如果你用 TS，修改 `tsconfig.json`：

```json
{
  "compilerOptions": {
    // ...
    "paths": {
      ":/*": ["src/*"]
    }
    // ...
  }
}
```

### `intl/locales/[locale].json`

`locales` 下只是对 [美杜莎](http://mcms-portal.alibaba-inc.com) 上的一个导出和备份，主要是为了在本地仓库有个参考，可以快速定位到正确的 key。

### `intl/message.ts`

```js
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`

```typescript
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`。

```jsx
import intl, {
  // other stuff
} from ':/intl';

// 静态调用
intl(...);

// 在 JSX 中
<div>{intl(...)}</div>
```

## 关于返回类型

`intl` 方法虽然返回的类型只有 `string`，但实际可能返回 `JSX.Element`（在指定 `!lines` 或 `!html` 的情况下），这个非常不好写，又不适合用泛型（不知怎么回事会返回 `any`）。

好在大多数情况下，`intl` 仅用于展示端，`string` 或 `JSX.Element` 可以无感。

如果要强行指定 `JSX.Element` 的话，就强行转换类型吧：

```typescript
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` 进行聚合，比如：

```typescript
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': '已注销',
};
```

聚合后，可以是这样：

```typescript
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 级别的元素，可以用 `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` 即可：

```typescript
windXIntl(messages, LOCALE, { // LOCALE 视你应用的具体情况而定，你也可以通过 options 做一些定制
  extraValues: LINKS
});
```

以上，`LINKS` 是你的应用在 viper 配置的渠道链接的输出。

而你在定义你的文案时，这么定义：

```json
{
  "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`：

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

### 如何避免传错 key？

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

---
_Source: https://npm.io/package/@alicloud/xconsole-intl-extended · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
