diff --git a/src/blockHeader/__tests__/blockHeader.test.tsx b/src/blockHeader/__tests__/blockHeader.test.tsx index 6ca4e836c..955c1665e 100644 --- a/src/blockHeader/__tests__/blockHeader.test.tsx +++ b/src/blockHeader/__tests__/blockHeader.test.tsx @@ -37,7 +37,7 @@ describe('test BlockHeader render', () => { }); test('should render BlockHeader default BlockHeader render', () => { const { getByText, rerender } = render(); - expect(getByText(props.title)).toBeTruthy(); + expect(getByText(props.title as any)).toBeTruthy(); rerender(); expect(getByText('标题2')).toBeTruthy(); }); diff --git a/src/blockHeader/demos/title.tsx b/src/blockHeader/demos/title.tsx new file mode 100644 index 000000000..5bffd5008 --- /dev/null +++ b/src/blockHeader/demos/title.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { BlockHeader, EllipsisText } from 'dt-react-component'; + +export default () => { + return ( + + } + /> + ); +}; diff --git a/src/blockHeader/index.md b/src/blockHeader/index.md index 42b713b11..5fcdff475 100644 --- a/src/blockHeader/index.md +++ b/src/blockHeader/index.md @@ -20,6 +20,7 @@ demo: 自定义 icon 带提示信息的标题 支持 `children` 做为内容展示 +标题超长 ## API @@ -38,8 +39,8 @@ demo: | contentClassName | 展示内容的样式类名 | `string` | - | | contentStyle | 展示内容的样式 | `React.CSSProperties` | - | | background | 是否显示背景 | `boolean` | `true` | -| defaultExpand | 是否默认展开内容 | `boolean` | `-` | +| defaultExpand | 是否默认展开内容 | `boolean` | `-` | | expand | 当前展开状态 | `boolean` | | -| spaceBottom | 自定义下边距,优先级高于 hasBottom | `number` | `16` | +| spaceBottom | 自定义下边距,优先级高于 hasBottom | `number` | `16` | | children | 展开/收起的内容 | `React.ReactNode` | - | | onExpand | 展开/收起时的回调 | `(expand: boolean) => void` | - | diff --git a/src/blockHeader/index.tsx b/src/blockHeader/index.tsx index 7ad787ed4..256abe11c 100644 --- a/src/blockHeader/index.tsx +++ b/src/blockHeader/index.tsx @@ -3,6 +3,7 @@ import { QuestionOutlined, UpOutlined } from '@dtinsight/react-icons'; import { Tooltip } from 'antd'; import classNames from 'classnames'; +import useLocale from '../locale/useLocale'; import { LabelTooltipType, toTooltipProps } from '../utils'; import './style.scss'; @@ -14,7 +15,7 @@ function isControlled(props: IBlockHeaderProps) { export interface IBlockHeaderProps { /** 标题 */ - title: string; + title: ReactNode; /** 标题前的图标,默认是一个色块 */ addonBefore?: ReactNode; /** 标题后的提示说明文字 */ @@ -76,6 +77,7 @@ const BlockHeader: React.FC = function (props) { } = props; const [internalExpand, setInternalExpand] = useState(defaultExpand); + const locale = useLocale('BlockHeader'); const currentExpand = isControlled(props) ? expand : internalExpand; @@ -124,7 +126,9 @@ const BlockHeader: React.FC = function (props) { {addonAfter &&
{addonAfter}
} {showCollapse && (
-
{currentExpand ? '收起' : '展开'}
+
+ {currentExpand ? locale.collapse : locale.expand} +
{ + return {children}; +}; + +export default ConfigProvider; diff --git a/src/copy/index.tsx b/src/copy/index.tsx index c572439f8..d4993040a 100644 --- a/src/copy/index.tsx +++ b/src/copy/index.tsx @@ -4,6 +4,7 @@ import { CopyOutlined } from '@dtinsight/react-icons'; import { message, Tooltip } from 'antd'; import classNames from 'classnames'; +import useLocale from '../locale/useLocale'; import { LabelTooltipType, toTooltipProps } from '../utils'; import './style.scss'; @@ -18,14 +19,15 @@ export interface ICopyProps { } const Copy: React.FC = (props) => { + const locale = useLocale('Copy'); const { button = , text, - tooltip = '复制', + tooltip = locale.copy, style, className, disabled = false, - onCopy = () => message.success('复制成功'), + onCopy = () => message.success(locale.copied), } = props; const handleCopy = () => { diff --git a/src/dropdown/select.tsx b/src/dropdown/select.tsx index 52ab6847d..bbfa09b5a 100644 --- a/src/dropdown/select.tsx +++ b/src/dropdown/select.tsx @@ -10,6 +10,7 @@ import classNames from 'classnames'; import { isEqual } from 'lodash-es'; import List from 'rc-virtual-list'; +import useLocale from '../locale/useLocale'; import './style.scss'; interface IDropdownSelectProps @@ -35,6 +36,8 @@ export default function Select({ const [visible, setVisible] = useState(false); const [selected, setSelected] = useState(value || defaultValue || []); + const locale = useLocale('Dropdown'); + const handleCheckedAll = (e: CheckboxChangeEvent) => { if (e.target.checked) { setSelected(options?.map((i) => i.value) || []); @@ -130,7 +133,7 @@ export default function Select({ checked={checkAll} indeterminate={indeterminate} > - 全选 + {locale.selectAll} @@ -169,10 +172,10 @@ export default function Select({ diff --git a/src/ellipsisText/index.tsx b/src/ellipsisText/index.tsx index 65662dfd4..280bb67fb 100644 --- a/src/ellipsisText/index.tsx +++ b/src/ellipsisText/index.tsx @@ -78,6 +78,9 @@ const EllipsisText = (props: IEllipsisTextProps) => { * @return {*} */ const getStyle = (dom: NewHTMLElement, attr: string) => { + if (!dom) { + return null; + } // Compatible width IE8 // @ts-ignore return window.getComputedStyle(dom)[attr] || dom.currentStyle[attr]; @@ -203,7 +206,11 @@ const EllipsisText = (props: IEllipsisTextProps) => { * @return {*} */ const onResize = () => { - const ellipsisNode = ellipsisRef.current!; + if (!ellipsisRef.current) { + return; + } + + const ellipsisNode = ellipsisRef.current; const parentElement = ellipsisNode.parentElement!; const rangeWidth = getRangeWidth(ellipsisNode); const containerWidth = getContainerWidth(parentElement); diff --git a/src/errorBoundary/loadError.tsx b/src/errorBoundary/loadError.tsx index 8d75380d2..b10fbd491 100644 --- a/src/errorBoundary/loadError.tsx +++ b/src/errorBoundary/loadError.tsx @@ -1,22 +1,24 @@ import React from 'react'; +import useLocale from '../locale/useLocale'; + const LoadError: React.FC = function () { + const locale = useLocale('LoadError'); return (
- 

- 发现新版本,请 + {locale.please} { location.reload(); }} > - 刷新 + {locale.refresh} - 获取新版本。 + {locale.get}

-

若该提示长时间存在,请联系管理员。

+

{locale.title}

); diff --git a/src/fullscreen/index.tsx b/src/fullscreen/index.tsx index ece3b7502..97b5ea09f 100644 --- a/src/fullscreen/index.tsx +++ b/src/fullscreen/index.tsx @@ -2,6 +2,7 @@ import React, { CSSProperties, HTMLAttributes, ReactNode, useEffect, useState } import { Button } from 'antd'; import KeyEventListener from '../keyEventListener'; +import useLocale from '../locale/useLocale'; import MyIcon from './icon'; const { KeyCombiner } = KeyEventListener; @@ -44,7 +45,11 @@ export default function Fullscreen({ ...other }: IFullscreenProps) { const [isFullScreen, setIsFullScreen] = useState(false); + + const locale = useLocale('Fullscreen'); + const customIcon = isFullScreen ? exitFullIcon : fullIcon; + useEffect(() => { const propsDom = document.getElementById(target); const domEle = propsDom || document.body; @@ -188,7 +193,7 @@ export default function Fullscreen({ ) : ( )} diff --git a/src/globalLoading/index.tsx b/src/globalLoading/index.tsx index 0324e2d01..989d3a92a 100644 --- a/src/globalLoading/index.tsx +++ b/src/globalLoading/index.tsx @@ -1,6 +1,7 @@ import React from 'react'; import classNames from 'classnames'; +import useLocale from '../locale/useLocale'; import './style.scss'; export interface IGlobalLoadingProps { @@ -13,8 +14,10 @@ export interface IGlobalLoadingProps { } const GlobalLoading: React.FC = function (props) { + const locale = useLocale('GlobalLoading'); + const { - loadingTitle = '应用加载中,请等候~', + loadingTitle = locale.loading, mainBackground = '#F2F7FA', circleBackground = '#1D78FF', titleColor = '#3D446E', diff --git a/src/index.ts b/src/index.ts index 613a09772..34da49f40 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ export { default as Button } from './button'; export { default as Catalogue } from './catalogue'; export { default as Chat } from './chat'; export { default as CollapsibleActionItems } from './collapsibleActionItems'; +export { default as ConfigProvider } from './configProvider'; export { default as ContentLayout } from './contentLayout'; export { default as ContextMenu } from './contextMenu'; export { default as Copy } from './copy'; @@ -24,6 +25,8 @@ export { default as GlobalLoading } from './globalLoading'; export { default as Image } from './image'; export { default as Input } from './input'; export { default as KeyEventListener } from './keyEventListener'; +export { default as enUS } from './locale/en-US'; +export { default as zhCN } from './locale/zh-CN'; export { default as MarkdownRender } from './markdownRender'; export { default as Modal } from './modal/modal'; export { default as NotFound } from './notFound'; diff --git a/src/input/match.tsx b/src/input/match.tsx index a13c1c176..020ee844e 100644 --- a/src/input/match.tsx +++ b/src/input/match.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Input, type InputProps, Tooltip } from 'antd'; import classNames from 'classnames'; +import useLocale from '../locale/useLocale'; import { CaseSensitiveIcon, FrontIcon, PreciseIcon, TailIcon } from './icons'; import './match.scss'; @@ -29,30 +30,11 @@ interface IMatchProps extends Omit { onSearch?: (value: string, searchType: SearchType) => void; } -const searchTypeList = [ - { - key: 'caseSensitive', - tip: '区分大小写匹配', - }, - { - key: 'precise', - tip: '精确匹配', - }, - { - key: 'front', - tip: '头部匹配', - }, - { - key: 'tail', - tip: '尾部匹配', - }, -] as const; - export default function Match({ className, value, searchType, - filterOptions = searchTypeList.map((i) => i.key), + filterOptions: propFilterOptions, onTypeChange, onSearch, onChange, @@ -62,12 +44,35 @@ export default function Match({ const [internalValue, setValue] = useState(''); const [internalSearchType, setSearchType] = useState('fuzzy'); + const locale = useLocale('Input'); + const handleTypeChange = (key: SearchType) => { const next = realSearchType === key ? 'fuzzy' : key; onTypeChange?.(next); setSearchType(next); }; + const searchTypeList = [ + { + key: 'caseSensitive', + tip: locale.case, + }, + { + key: 'precise', + tip: locale.precise, + }, + { + key: 'front', + tip: locale.front, + }, + { + key: 'tail', + tip: locale.tail, + }, + ] as const; + + const filterOptions = propFilterOptions || searchTypeList.map((i) => i.key); + const options = searchTypeList.filter((i) => filterOptions.includes(i.key)); const realSearchType = searchType || internalSearchType; diff --git a/src/locale/en-US.ts b/src/locale/en-US.ts new file mode 100644 index 000000000..19b569508 --- /dev/null +++ b/src/locale/en-US.ts @@ -0,0 +1,57 @@ +import { Locale } from './useLocale'; + +const localeValues: Locale = { + locale: 'en-US', + BlockHeader: { + expand: 'Expand', + collapse: 'Collapse', + }, + + Copy: { + copied: 'Copied', + + copy: 'Copy', + }, + Fullscreen: { + exitFull: 'Exit Full Screen', + full: 'Full Screen', + }, + GlobalLoading: { + loading: 'The application is loading, please wait~', + }, + LoadError: { + please: 'A new version has been found. Please', + get: 'to get the new version.', + refresh: ' refresh ', + title: 'If this prompt persists for a long time, please contact the administrator.', + }, + Modal: { + okText: 'Ok', + cancelText: 'Cancel', + }, + Dropdown: { + resetText: 'Cancel', + okText: 'Ok', + selectAll: 'Select All', + }, + Input: { + case: 'Case-sensitive match', + precise: 'Exact match', + front: 'Head match', + tail: 'Tail match', + }, + MxGraph: { + newNode: 'New node', + }, + NotFound: { + description: 'Sorry, the page you visited does not exist', + }, + SpreadSheet: { + description: 'No Data', + copy: 'Copy values', + copyCol: 'Copy column names', + copyAll: 'Copy values ​​and column names', + }, +}; + +export default localeValues; diff --git a/src/locale/useLocale.tsx b/src/locale/useLocale.tsx new file mode 100644 index 000000000..d9ecc517f --- /dev/null +++ b/src/locale/useLocale.tsx @@ -0,0 +1,63 @@ +import { createContext, useContext, useMemo } from 'react'; + +import defaultLocaleData from './zh-CN'; + +export interface Locale { + locale: string; + BlockHeader: { expand: string; collapse: string }; + Copy: { copied: string; copy: string }; + Dropdown: { selectAll: string; resetText: string; okText: string }; + Fullscreen: { exitFull: string; full: string }; + GlobalLoading: { loading: string }; + Input: { + case: string; + precise: string; + front: string; + tail: string; + }; + LoadError: { + please: string; + get: string; + refresh: string; + title: string; + }; + Modal: { + okText: string; + cancelText: string; + }; + MxGraph: { newNode: string }; + NotFound: { + description: string; + }; + SpreadSheet: { + description: string; + copy: string; + copyCol: string; + copyAll: string; + }; +} + +export interface LocaleContextProps { + locale: Locale; +} + +export const LocaleContext = createContext({ locale: {} as Locale }); + +export type LocaleComponentName = keyof Locale; + +const useLocale = ( + componentName: C +): NonNullable => { + const fullLocale = useContext(LocaleContext); + + const getLocale = useMemo(() => { + const locale = defaultLocaleData[componentName] ?? {}; + const localeFromContext = fullLocale?.locale[componentName] ?? {}; + + return Object.assign({}, locale, localeFromContext) as NonNullable; + }, [componentName, fullLocale]); + + return getLocale; +}; + +export default useLocale; diff --git a/src/locale/zh-CN.ts b/src/locale/zh-CN.ts new file mode 100644 index 000000000..465a17fce --- /dev/null +++ b/src/locale/zh-CN.ts @@ -0,0 +1,51 @@ +import { Locale } from './useLocale'; + +const localeValues: Locale = { + locale: 'zh-CN', + BlockHeader: { + expand: '展开', + collapse: '收起', + }, + Copy: { + copied: '复制成功', + copy: '复制', + }, + Dropdown: { selectAll: '全选', resetText: '重置', okText: '确定' }, + Fullscreen: { + exitFull: '退出全屏', + full: '全屏', + }, + GlobalLoading: { + loading: '应用加载中,请等候~', + }, + LoadError: { + please: '发现新版本,请', + get: '获取新版本。', + refresh: '刷新', + title: '若该提示长时间存在,请联系管理员。', + }, + Input: { + case: '区分大小写匹配', + precise: '精确匹配', + front: '头部匹配', + tail: '尾部匹配', + }, + Modal: { + okText: '确定', + cancelText: '取消', + }, + MxGraph: { + newNode: '新节点', + }, + NotFound: { + description: '抱歉,您访问的页面不存在', + }, + SpreadSheet: { + description: '暂无数据', + copy: '复制值', + copyCol: '复制列名', + copyAll: '复制列名和值', + }, +}; + +export default localeValues; diff --git a/src/notFound/index.tsx b/src/notFound/index.tsx index b453851ff..2571e706e 100644 --- a/src/notFound/index.tsx +++ b/src/notFound/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import Empty from '../empty'; +import useLocale from '../locale/useLocale'; import './style.scss'; interface INotFoundProps { @@ -10,11 +11,12 @@ interface INotFoundProps { } const NotFound: React.FC = function ({ className, style }) { + const locale = useLocale('NotFound'); return (
diff --git a/src/spreadSheet/index.tsx b/src/spreadSheet/index.tsx index 85e26d77c..cad53b4ef 100644 --- a/src/spreadSheet/index.tsx +++ b/src/spreadSheet/index.tsx @@ -5,6 +5,7 @@ import { HotTable } from '@handsontable/react'; import classNames from 'classnames'; import 'handsontable/languages/zh-CN.js'; +import useLocale from '../locale/useLocale'; import 'handsontable/dist/handsontable.full.css'; import './style.scss'; @@ -36,7 +37,10 @@ const SpreadSheet: React.FC = forwardRef( const copyUtils = new CopyUtils(); const _timer = useRef(); const { copyTypes = [], ...restProps } = options || {}; + useImperativeHandle(ref, () => tableRef.current); + const locale = useLocale('SpreadSheet'); + useEffect(() => { if (tableRef.current) { removeRenderClock(); @@ -57,7 +61,7 @@ const SpreadSheet: React.FC = forwardRef( let showData = data; if (!showData?.length) { const emptyArr = new Array(columns.length).fill('', 0, columns.length); - emptyArr[0] = '暂无数据'; + emptyArr[0] = locale.description; showData = [emptyArr]; } return showData; @@ -128,21 +132,21 @@ const SpreadSheet: React.FC = forwardRef( }; const copyDataItem = { - name: '复制值', + name: locale.copy, callback: function (_key: string) { const copyDataArr = getCopyData(); beforeCopy(copyDataArr); }, }; const copyHeadersItem = { - name: '复制列名', + name: locale.copyCol, callback: function (_key: string, selection: Array) { const copyHeaders = getCopyHeaders(selection); beforeCopy([copyHeaders]); }, }; const copyHeadersAndDataItem = { - name: '复制列名和值', + name: locale.copyAll, callback: function (_key: string, selection: Array) { const copyDataArr = getCopyData(); const copyHeaders = getCopyHeaders(selection);