diff --git a/i18n-guide.md b/i18n-guide.md new file mode 100644 index 00000000..13828e53 --- /dev/null +++ b/i18n-guide.md @@ -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
{formatMessage({ id: 'Key' })}
; +}; +``` + +### 2. withLocale 包裹普通组件 +```javascript +import withLocale from './withLocale'; +import { useIntl } from '@kne/react-intl'; + +const Component = withLocale(({ ...props }) => { + const { formatMessage } = useIntl(); + // 将所有中文替换为 formatMessage({ id: 'Key' }) + return
{formatMessage({ id: 'Key' })}
; +}); + +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 +``` diff --git a/package.json b/package.json index fe60d31f..146d202e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne-components/components-core", - "version": "0.4.50", + "version": "0.4.51", "files": [ "build" ], @@ -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", diff --git a/src/components/Table/ColumnsControlContent.js b/src/components/Table/ColumnsControlContent.js index d52c380c..42f51226 100644 --- a/src/components/Table/ColumnsControlContent.js +++ b/src/components/Table/ColumnsControlContent.js @@ -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(""); @@ -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("-")})` : ""} @@ -90,9 +93,9 @@ const ColumnsControlContent = ({
- 编辑表格 + {formatMessage({id: 'EditTable'})} - + } @@ -111,7 +114,7 @@ const ColumnsControlContent = ({ ghost={true} bordered > - + {leftFixedColumns.map((item, index) => ( - {item.titleText || item.title || "未命名列"} + {item.titleText || item.title || formatMessage({id: 'UnnamedColumn'})} ))} @@ -195,7 +198,7 @@ const ColumnsControlContent = ({ key="un-active" header={ - 隐藏的信息 + {formatMessage({id: 'HiddenInfo'})} { e.stopPropagation(); @@ -204,7 +207,7 @@ const ColumnsControlContent = ({ > } - placeholder="搜索" + placeholder={formatMessage({id: 'Search'})} onSearch={(value) => { setSearchText(value); }} @@ -241,7 +244,7 @@ const ColumnsControlContent = ({ onChange(newConfig); }} > - {item.titleText || item.title || "未命名列"} + {item.titleText || item.title || formatMessage({id: 'UnnamedColumn'})} ); @@ -263,7 +266,7 @@ const ColumnsControlContent = ({ close(); }} > - 取消 + {formatMessage({id: 'Cancel'})} @@ -275,12 +278,12 @@ const ColumnsControlContent = ({ close(); }} > - 确定 + {formatMessage({id: 'Confirm'})}
); -}; +}); export default ColumnsControlContent; diff --git a/src/components/Table/HideInfoComponent.js b/src/components/Table/HideInfoComponent.js index 0495ac19..e48e82f0 100644 --- a/src/components/Table/HideInfoComponent.js +++ b/src/components/Table/HideInfoComponent.js @@ -3,6 +3,8 @@ 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, @@ -10,7 +12,7 @@ const DisplayInfo = createWithFetch({ return children(data); }); -const HideInfoComponent = ({ +const HideInfoComponent = withLocale(({ api, expand, onExpand, @@ -20,6 +22,7 @@ const HideInfoComponent = ({ emptyRender, isEmpty, }) => { + const {formatMessage} = useIntl(); const targetApi = Object.assign({}, api); if (expand) { return ( @@ -48,10 +51,10 @@ const HideInfoComponent = ({ return ( ); -}; +}); export default HideInfoComponent; diff --git a/src/components/Table/TablePage.js b/src/components/Table/TablePage.js index 3b0f0fb5..d47d6f31 100644 --- a/src/components/Table/TablePage.js +++ b/src/components/Table/TablePage.js @@ -6,10 +6,10 @@ import get from "lodash/get"; import useRefCallback from "@kne/use-ref-callback"; import {forwardRef, useMemo, useState} from "react"; import style from "./style.module.scss"; -import importMessages from "./locale"; -import {FormattedMessage, IntlProvider} from "@components/Intl"; import localStorage from "@common/utils/localStorage"; import {getScrollEl} from "@common/utils/importantContainer"; +import {useIntl} from '@kne/react-intl'; +import withLocale from './withLocale'; const FeaturesColumnsConfig = ({id, columns, children}) => { if (id) { @@ -30,7 +30,7 @@ const FeaturesColumnsConfig = ({id, columns, children}) => { return children({columns}); }; -const TablePageInner = withFetch(({ +const TablePageInnerContent = withLocale(({ data, refresh, reload, @@ -55,6 +55,7 @@ const TablePageInner = withFetch(({ sticky = true, ...props }) => { + const {formatMessage} = useIntl(); const handlerDataFormat = useRefCallback(dataFormat); const formatData = useMemo(() => { return handlerDataFormat(data); @@ -64,17 +65,10 @@ const TablePageInner = withFetch(({ dataSource: formatData.list, pagination: pagination.open ? { total: formatData.total, showTotal: (total) => (<> - + {formatMessage({id: 'TotalText'})}  {total} - +   + {formatMessage({id: 'ItemText'})} ), current: get(requestParams, [pagination.paramsType, pagination.currentName], 1), pageSize: get(requestParams, [pagination.paramsType, pagination.pageSizeName], 20), @@ -104,28 +98,28 @@ const TablePageInner = withFetch(({ } : false, }; - return ( - - {({columns}) => ( { - return summary(Object.assign({}, { - data, fetchProps, requestParams, refresh, reload, loadMore, send, dataFormat, pagination, - }, ...args)); - } : null} - />)} - - ); + return ( + {({columns}) => (
{ + return summary(Object.assign({}, { + data, fetchProps, requestParams, refresh, reload, loadMore, send, dataFormat, pagination, + }, ...args)); + } : null} + />)} + ); }); +const TablePageInner = withFetch(TablePageInnerContent); + const TablePage = forwardRef(({pagination, ...props}, ref) => { pagination = Object.assign({}, { showSizeChanger: true, diff --git a/src/components/Table/locale/en-US.js b/src/components/Table/locale/en-US.js index ecb8ee92..a8d46d79 100644 --- a/src/components/Table/locale/en-US.js +++ b/src/components/Table/locale/en-US.js @@ -1,6 +1,15 @@ const message = { - Page_Total: "Total", - Page_TotalCount: " Items", + TotalText: "Total", + ItemText: "items", + UnnamedColumn: "Unnamed", + EditTable: "Edit Table", + RestoreDefault: "Restore Default", + VisibleInfo: "Visible Columns", + HiddenInfo: "Hidden Columns", + Search: "Search", + Cancel: "Cancel", + Confirm: "Confirm", + View: "View" }; export default message; diff --git a/src/components/Table/locale/index.js b/src/components/Table/locale/index.js deleted file mode 100644 index 1a49770b..00000000 --- a/src/components/Table/locale/index.js +++ /dev/null @@ -1,8 +0,0 @@ -const importMessages = (locale) => { - return { - "en-US": () => import("./en-US"), - "zh-CN": () => import("./zh-CN"), - }[locale](); -}; - -export default importMessages; diff --git a/src/components/Table/locale/zh-CN.js b/src/components/Table/locale/zh-CN.js index 98e45c88..64da39b8 100644 --- a/src/components/Table/locale/zh-CN.js +++ b/src/components/Table/locale/zh-CN.js @@ -1,6 +1,15 @@ const message = { - Page_Total: "共", - Page_TotalCount: "条", + TotalText: "共", + ItemText: "条", + UnnamedColumn: "未命名列", + EditTable: "编辑表格", + RestoreDefault: "恢复默认", + VisibleInfo: "显示的信息", + HiddenInfo: "隐藏的信息", + Search: "搜索", + Cancel: "取消", + Confirm: "确定", + View: "查看" }; export default message; diff --git a/src/components/Table/withLocale.js b/src/components/Table/withLocale.js new file mode 100644 index 00000000..2f394257 --- /dev/null +++ b/src/components/Table/withLocale.js @@ -0,0 +1,11 @@ +import {createWithIntlProvider} from '@kne/react-intl'; +import zhCN from './locale/zh-CN'; +import enUS from './locale/en-US'; + +const withLocale = createWithIntlProvider({ + defaultLocale: 'zh-CN', messages: { + 'zh-CN': zhCN, 'en-US': enUS + }, namespace: 'table' +}); + +export default withLocale;