Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions i18n-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# 组件国际化指南

## 概述

本指南用于将组件完成国际化。

## 一、创建的文件

1. **`[组件目录]/withLocale.js`** - HOC 包裹组件,提供国际化上下文
2. **`[组件目录]/locale/zh-CN.js`** - 中文语言包
3. **`[组件目录]/locale/en-US.js`** - 英文语言包

## 二、需要修改的文件类型

### 主组件文件
- 添加 `useIntl` Hook
- 用 `withLocale` 包裹导出

### FormInner 表单组件
- 添加 `useIntl` Hook
- 用 `withLocale` 包裹导出
- 表单 label 国际化

### getColumns 等工具函数
- 通过参数接收 `formatMessage`
- 移除内部的 `useIntl` 和 `withLocale` 引入

### Action 操作组件
- 添加 `useIntl` Hook
- 用 `withLocale` 包裹导出

## 三、国际化的关键模式

### 1. useIntl Hook 使用
```javascript
import { useIntl } from '@kne/react-intl';

const Component = () => {
const { formatMessage } = useIntl();
return <div>{formatMessage({ id: 'Key' })}</div>;
};
```

### 2. withLocale 包裹普通组件
```javascript
import withLocale from './withLocale';
import { useIntl } from '@kne/react-intl';

const Component = withLocale(({ ...props }) => {
const { formatMessage } = useIntl();
// 将所有中文替换为 formatMessage({ id: 'Key' })
return <div>{formatMessage({ id: 'Key' })}</div>;
});

export default Component;
```

### 3. createWithRemoteLoader 组件(推荐格式)
```javascript
import withLocale from '../withLocale';
import { useIntl } from '@kne/react-intl';

const Component = createWithRemoteLoader({...})(withLocale(({ remoteModules, ...props }) => {
const { formatMessage } = useIntl();
// ...
}));

export default Component;
```

**注意:** 对于 `createWithRemoteLoader` 创建的组件,必须使用 `createWithRemoteLoader({...})(withLocale(...))` 这种链式调用格式,不要先定义中间变量再用 withLocale 包裹。

### 4. getColumns 等工具函数(formatMessage 从父组件传入)
```javascript
const getColumns = ({formatMessage}) => {
return [
{
name: 'name',
title: formatMessage({ id: 'Key' })
}
];
};

// 父组件中使用
const columns = getColumns({formatMessage});
```

### 5. 带参数的翻译
```javascript
formatMessage({ id: 'KeyWithParam' }, { name: value })
```

## 四、注意事项

1. **所有使用 `useIntl` 的组件必须用 `withLocale` 包裹**
2. **`getColumns` 等工具函数通过参数接收 `formatMessage`,不使用 `useIntl`**
3. **语言包中避免重复的 key**,命名规则:`模块名 + 功能名`,如 `UserName`、`UserRole`
4. **`createWithRemoteLoader` 创建的组件内部使用 useIntl 时,外层需要重命名并用 withLocale 包裹**
5. 注意检查 `withLocale`文件的引用地址

---

# 组件国际化操作步骤

## 步骤

### 1. 创建国际化文件
- 创建 `[组件目录]/withLocale.js`(参考已有组件的 withLocale.js)
- 创建 `[组件目录]/locale/zh-CN.js` 中文语言包
- 创建 `[组件目录]/locale/en-US.js` 英文语言包

### 2. 修改组件文件

#### 主组件修改模式:
```javascript
import withLocale from './withLocale';
import { useIntl } from '@kne/react-intl';

const Component = withLocale(({ ...props }) => {
const { formatMessage } = useIntl();
// 将所有中文替换为 formatMessage({ id: 'Key' })
return (
// ...
);
});

export default Component;
```

#### FormInner 修改模式:
```javascript
import withLocale from '../withLocale';
import { useIntl } from '@kne/react-intl';

const FormInner = createWithRemoteLoader({...})(withLocale(({ remoteModules, ...props }) => {
const { formatMessage } = useIntl();
// label={formatMessage({ id: 'Key' })}
// ...
}));

export default FormInner;
```

#### Action 操作组件修改模式:
```javascript
import withLocale from '../withLocale';
import { useIntl } from '@kne/react-intl';

const ActionComponent = createWithRemoteLoader({...})(withLocale(({ remoteModules, ...props }) => {
const { formatMessage } = useIntl();
// ...
}));

export default ActionComponent;
```

#### getColumns 修改模式:
```javascript
// 移除 useIntl 和 withLocale 引入
const getColumns = ({formatMessage}) => {
return [
{
name: 'xxx',
title: formatMessage({ id: 'Key' })
}
];
};
```

父组件中调用:`getColumns({formatMessage})`

### 3. 语言包 key 命名规范
- 避免重复,使用 `模块名+功能名` 格式,如 `UserName`、`UserRole`、`SettingType`
- 中文和英文语言包保持完全一致的 key 结构

### 4. 检查要点
- [ ] 所有使用 `useIntl` 的组件都用 `withLocale` 包裹
- [ ] `getColumns` 等工具函数通过参数接收 `formatMessage`
- [ ] 语言包中无重复 key
- [ ] `createWithRemoteLoader` 组件必须使用 `createWithRemoteLoader({...})(withLocale(...))` 链式调用格式,**禁止**先定义中间变量再包裹

### 5. 最后检查
运行命令找到所有使用 useIntl 的文件,确保都已正确包裹:
```bash
grep -r "useIntl" [组件目录] --include="*.js" -l
```
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kne-components/components-core",
"version": "0.4.50",
"version": "0.4.51",
"files": [
"build"
],
Expand Down Expand Up @@ -34,7 +34,7 @@
"@kne/react-form-antd": "^4.0.3",
"@kne/react-form-plus": "^0.1.5",
"@kne/react-icon": "^0.1.3",
"@kne/react-intl": "^0.1.6",
"@kne/react-intl": "^0.1.12",
"@kne/react-text-escape": "^0.1.4",
"@kne/remote-loader": "^1.2.2",
"@kne/scroll-loader": "^0.1.13",
Expand Down
27 changes: 15 additions & 12 deletions src/components/Table/ColumnsControlContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ import transform from "lodash/transform";
import get from "lodash/get";
import set from "lodash/set";
import cloneDeep from "lodash/cloneDeep";
import {useIntl} from '@kne/react-intl';
import withLocale from './withLocale';

const { Panel } = Collapse;

const ColumnsControlContent = ({
const ColumnsControlContent = withLocale(({
close,
onConfirm,
columns,
config: defaultValue,
}) => {
const {formatMessage} = useIntl();
const [config, onChange] = useState(defaultValue || {});

const [searchText, setSearchText] = useState("");
Expand Down Expand Up @@ -78,7 +81,7 @@ const ColumnsControlContent = ({
const renderColumn = (item) => {
return (
<>
{item.titleText || item.title || "未命名列"}
{item.titleText || item.title || formatMessage({id: 'UnnamedColumn'})}
{item.groupHeader && item.groupHeader.length > 0
? `(${item.groupHeader.map(({ title }) => title).join("-")})`
: ""}
Expand All @@ -90,9 +93,9 @@ const ColumnsControlContent = ({
<div className={style["columns-control-content"]}>
<div className={style["columns-control-content-title"]}>
<Row align="middle" justify="space-between">
<Col>编辑表格</Col>
<Col>{formatMessage({id: 'EditTable'})}</Col>
<Col>
<Tooltip title="恢复默认">
<Tooltip title={formatMessage({id: 'RestoreDefault'})}>
<LoadingButton
type="text"
icon={<Icon type="icon-huifumorenshezhi" />}
Expand All @@ -111,7 +114,7 @@ const ColumnsControlContent = ({
ghost={true}
bordered
>
<Panel key="active" header="显示的信息">
<Panel key="active" header={formatMessage({id: 'VisibleInfo'})}>
<List className={style["columns-control-content-list"]}>
{leftFixedColumns.map((item, index) => (
<List.Item
Expand Down Expand Up @@ -185,7 +188,7 @@ const ColumnsControlContent = ({
key={item.name || `right-${index}`}
>
<Checkbox checked disabled>
{item.titleText || item.title || "未命名列"}
{item.titleText || item.title || formatMessage({id: 'UnnamedColumn'})}
</Checkbox>
</List.Item>
))}
Expand All @@ -195,7 +198,7 @@ const ColumnsControlContent = ({
key="un-active"
header={
<Row wrap={false} justify="space-between">
<Col>隐藏的信息</Col>
<Col>{formatMessage({id: 'HiddenInfo'})}</Col>
<Col
onClick={(e) => {
e.stopPropagation();
Expand All @@ -204,7 +207,7 @@ const ColumnsControlContent = ({
>
<SearchInput
prefix={<Icon type="icon-sousuo" size={12} />}
placeholder="搜索"
placeholder={formatMessage({id: 'Search'})}
onSearch={(value) => {
setSearchText(value);
}}
Expand Down Expand Up @@ -241,7 +244,7 @@ const ColumnsControlContent = ({
onChange(newConfig);
}}
>
{item.titleText || item.title || "未命名列"}
{item.titleText || item.title || formatMessage({id: 'UnnamedColumn'})}
</Checkbox>
</List.Item>
);
Expand All @@ -263,7 +266,7 @@ const ColumnsControlContent = ({
close();
}}
>
取消
{formatMessage({id: 'Cancel'})}
</Button>
</Col>
<Col>
Expand All @@ -275,12 +278,12 @@ const ColumnsControlContent = ({
close();
}}
>
确定
{formatMessage({id: 'Confirm'})}
</LoadingButton>
</Col>
</Row>
</div>
);
};
});

export default ColumnsControlContent;
9 changes: 6 additions & 3 deletions src/components/Table/HideInfoComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import Ellipsis from "./Ellipsis";
import { Button } from "antd";
import ColItem from "./ColItem";
import isColValueEmpty from "./isColValueEmpty";
import {useIntl} from '@kne/react-intl';
import withLocale from './withLocale';

const DisplayInfo = createWithFetch({
loading: null,
})(({ data, children }) => {
return children(data);
});

const HideInfoComponent = ({
const HideInfoComponent = withLocale(({
api,
expand,
onExpand,
Expand All @@ -20,6 +22,7 @@ const HideInfoComponent = ({
emptyRender,
isEmpty,
}) => {
const {formatMessage} = useIntl();
const targetApi = Object.assign({}, api);
if (expand) {
return (
Expand Down Expand Up @@ -48,10 +51,10 @@ const HideInfoComponent = ({
return (
<ColItem type="hide-info" primary emptyRender={emptyRender}>
<Button className="btn-no-padding" type="link" onClick={onExpand}>
查看
{formatMessage({id: 'View'})}
</Button>
</ColItem>
);
};
});

export default HideInfoComponent;
Loading