diff --git a/.vscode/settings.json b/.vscode/settings.json index f89ed5f1d..6a30c1d58 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "editor.formatOnSave": true + "editor.formatOnSave": true, + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/examples/components/App.jsx b/examples/components/App.jsx index d98c9bc75..fada1e56d 100644 --- a/examples/components/App.jsx +++ b/examples/components/App.jsx @@ -4,6 +4,7 @@ import Layout from '../../src/components/Layout'; import AsideNav from '../../src/components/AsideNav'; import {AlertComponent, ToastComponent} from '../../src/components/index'; import {mapTree} from '../../src/utils/helper'; +import '../../src/locale/en'; import { Router, Route, @@ -560,6 +561,18 @@ const themes = [ } ]; +const locales = [ + { + label: '默认', + value: 'zh-cn' + }, + + { + label: 'English', + value: 'en' + } +]; + @withRouter export class App extends React.PureComponent { state = { @@ -568,7 +581,8 @@ export class App extends React.PureComponent { headerVisible: true, themeIndex: 0, themes: themes, - theme: themes[localStorage.getItem('themeIndex') || 0] + theme: themes[localStorage.getItem('themeIndex') || 0], + locale: localStorage.getItem('locale') || '' }; constructor(props) { @@ -767,10 +781,27 @@ export class App extends React.PureComponent { +
+ 语言: + { + - - + + {React.cloneElement(this.props.children, { ...this.props.children.props, setAsideFolded: this.setAsideFolded, setHeaderVisible: this.setHeaderVisible, theme: theme.value, - classPrefix: theme.ns + classPrefix: theme.ns, + locale: this.state.locale })} ); diff --git a/examples/components/SchemaRender.jsx b/examples/components/SchemaRender.jsx index e7e0a39c5..5509f1b2a 100644 --- a/examples/components/SchemaRender.jsx +++ b/examples/components/SchemaRender.jsx @@ -161,13 +161,14 @@ export default function (schema) { } renderSchema() { - const {router, location, theme} = this.props; + const {router, location, theme, locale} = this.props; return render( schema, { location, - theme + theme, + locale }, this.env ); diff --git a/package.json b/package.json index 5a549dfcc..8cce53e1b 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,7 @@ "@types/classnames": "^2.2.3", "@types/dom-helpers": "^3.4.1", "@types/history": "^4.6.0", - "@types/hoist-non-react-statics": "^3.0.1", + "@types/hoist-non-react-statics": "^3.3.1", "@types/jest": "^24.0.11", "@types/jquery": "^3.3.1", "@types/lodash": "^4.14.76", @@ -89,7 +89,7 @@ "@types/pretty-bytes": "^4.0.0", "@types/prop-types": "^15.5.2", "@types/qs": "^6.5.1", - "@types/react": "^16.8.1", + "@types/react": "^16.9.35", "@types/react-addons-update": "^0.14.19", "@types/react-color": "^2.13.3", "@types/react-cropper": "^0.10.1", @@ -139,7 +139,7 @@ "react-testing-library": "6.0.4", "strip-json-comments": "^2.0.1", "ts-jest": "^24.0.0", - "typescript": "^3.9.2" + "typescript": "^3.9.3" }, "jest": { "testEnvironment": "jsdom", diff --git a/publish.sh b/publish.sh index c8f1bdcfc..7e641f4a6 100644 --- a/publish.sh +++ b/publish.sh @@ -11,7 +11,7 @@ rm -rf lib/node_modules rm -rf sdk && fis3 release publish-sdk -c # 生成 .d.ts 文件 -tsc --allowJs --declaration +./node_modules/.bin/tsc --allowJs --declaration cd output diff --git a/src/components/Alert.tsx b/src/components/Alert.tsx index 958efd428..3b4ba396d 100644 --- a/src/components/Alert.tsx +++ b/src/components/Alert.tsx @@ -7,18 +7,16 @@ import React from 'react'; import {render} from 'react-dom'; import Modal from './Modal'; import Button from './Button'; -import {ClassNamesFn, themeable} from '../theme'; +import {ClassNamesFn, themeable, ThemeProps} from '../theme'; +import {LocaleProps, localeable} from '../locale'; -export interface AlertProps { +export interface AlertProps extends ThemeProps, LocaleProps { container?: any; confirmText?: string; cancelText?: string; title?: string; confirmBtnLevel?: string; alertBtnLevel?: string; - classPrefix: string; - classnames: ClassNamesFn; - theme?: string; } export interface AlertState { @@ -37,7 +35,7 @@ export class Alert extends React.Component { const container = document.body; const div = document.createElement('div'); container.appendChild(div); - render(, div); + render(, div); } return Alert.instance; @@ -150,6 +148,8 @@ export class Alert extends React.Component { classnames: cx, classPrefix } = this.props; + const __ = this.props.translate; + return ( { ref={this.modalRef} >
-
{this.state.title || title}
+
+ {__(this.state.title || title)} +
{this.state.confirm ? ( - + ) : null}
@@ -189,5 +191,5 @@ export const confirm: ( confirmText?: string ) => Promise = (content, title, confirmText) => Alert.getInstance().confirm(content, title, confirmText); -export const ThemedAlert = themeable(Alert); -export default ThemedAlert; +export const FinnalAlert = themeable(localeable(Alert)); +export default FinnalAlert; diff --git a/src/components/AssociatedCheckboxes.tsx b/src/components/AssociatedCheckboxes.tsx index cfb524efd..df336a873 100644 --- a/src/components/AssociatedCheckboxes.tsx +++ b/src/components/AssociatedCheckboxes.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; -import {CheckboxesProps, Checkboxes} from './Checkboxes'; +import {BaseCheckboxesProps, BaseCheckboxes} from './Checkboxes'; import {Options, Option} from './Select'; import ListMenu from './ListMenu'; import {autobind} from '../utils/helper'; @@ -19,8 +19,9 @@ import ChainedCheckboxes from './ChainedCheckboxes'; import Spinner from './Spinner'; import TreeRadios from './TreeRadios'; import {Icon} from './icons'; +import {localeable} from '../locale'; -export interface AssociatedCheckboxesProps extends CheckboxesProps { +export interface AssociatedCheckboxesProps extends BaseCheckboxesProps { leftOptions: Options; leftDefaultValue?: any; leftMode?: 'tree' | 'list'; @@ -42,7 +43,7 @@ export interface AssociatedCheckboxesState { leftValue?: Option; } -export class AssociatedCheckboxes extends Checkboxes< +export class AssociatedCheckboxes extends BaseCheckboxes< AssociatedCheckboxesProps, AssociatedCheckboxesState > { @@ -58,7 +59,7 @@ export class AssociatedCheckboxes extends Checkboxes< const selectdOption = ListRadios.resolveSelected( leftValue, options, - option => option.ref + (option: Option) => option.ref ); if (selectdOption && onDeferLoad && selectdOption.defer) { @@ -80,7 +81,7 @@ export class AssociatedCheckboxes extends Checkboxes< const selectdOption = ListRadios.resolveSelected( value, options, - option => option.ref + (option: Option) => option.ref ); if (selectdOption && onDeferLoad && selectdOption.defer) { @@ -111,8 +112,9 @@ export class AssociatedCheckboxes extends Checkboxes< const selectdOption = ListRadios.resolveSelected( this.state.leftValue, options, - option => option.ref + (option: Option) => option.ref ); + const __ = this.props.translate; return (
@@ -155,9 +157,9 @@ export class AssociatedCheckboxes extends Checkboxes<
{selectdOption.loading ? ( -

加载中

+

{__('加载中')}

) : ( -

点击刷新重新加载

+

{__('点击刷新重新加载')}

)}
) : rightMode === 'table' ? ( @@ -193,12 +195,12 @@ export class AssociatedCheckboxes extends Checkboxes< ) ) : (
- 配置错误,选项无法与左侧选项对应 + {__('配置错误,选项无法与左侧选项对应')}
) ) : (
- 请先选择左侧数据 + {__('请先选择左侧数据')}
)}
@@ -208,7 +210,9 @@ export class AssociatedCheckboxes extends Checkboxes< } export default themeable( - uncontrollable(AssociatedCheckboxes, { - value: 'onChange' - }) + localeable( + uncontrollable(AssociatedCheckboxes, { + value: 'onChange' + }) + ) ); diff --git a/src/components/ChainedCheckboxes.tsx b/src/components/ChainedCheckboxes.tsx index 1a75915f0..0e4b3c7e7 100644 --- a/src/components/ChainedCheckboxes.tsx +++ b/src/components/ChainedCheckboxes.tsx @@ -1,7 +1,7 @@ /** * 级联多选框,支持无限极。从左侧到右侧一层层点选。 */ -import {Checkboxes, CheckboxesProps} from './Checkboxes'; +import {BaseCheckboxes, BaseCheckboxesProps} from './Checkboxes'; import {themeable} from '../theme'; import React from 'react'; import uncontrollable from 'uncontrollable'; @@ -10,8 +10,9 @@ import {Option} from './Select'; import {getTreeDepth} from '../utils/helper'; import times from 'lodash/times'; import Spinner from './Spinner'; +import {localeable} from '../locale'; -export interface ChainedCheckboxesProps extends CheckboxesProps { +export interface ChainedCheckboxesProps extends BaseCheckboxesProps { defaultSelectedIndex?: string; } @@ -19,7 +20,7 @@ export interface ChainedCheckboxesState { selected: Array; } -export class ChainedCheckboxes extends Checkboxes< +export class ChainedCheckboxes extends BaseCheckboxes< ChainedCheckboxesProps, ChainedCheckboxesState > { @@ -122,7 +123,7 @@ export class ChainedCheckboxes extends Checkboxes< itemRender } = this.props; - this.valueArray = Checkboxes.value2array(value, options, option2value); + this.valueArray = BaseCheckboxes.value2array(value, options, option2value); let body: Array = []; if (Array.isArray(options) && options.length) { @@ -188,13 +189,15 @@ export class ChainedCheckboxes extends Checkboxes< ); } + const __ = this.props.translate; + return (
{body && body.length ? ( body ) : (
- {placeholder} + {__(placeholder)}
)}
@@ -203,7 +206,9 @@ export class ChainedCheckboxes extends Checkboxes< } export default themeable( - uncontrollable(ChainedCheckboxes, { - value: 'onChange' - }) + localeable( + uncontrollable(ChainedCheckboxes, { + value: 'onChange' + }) + ) ); diff --git a/src/components/Checkboxes.tsx b/src/components/Checkboxes.tsx index 41b46ebab..d4c039124 100644 --- a/src/components/Checkboxes.tsx +++ b/src/components/Checkboxes.tsx @@ -13,9 +13,10 @@ import {Option, value2array, Options} from './Select'; import find from 'lodash/find'; import {autobind, findTree} from '../utils/helper'; import isEqual from 'lodash/isEqual'; +import {LocaleProps, localeable} from '../locale'; // import isPlainObject from 'lodash/isPlainObject'; -export interface CheckboxesProps extends ThemeProps { +export interface BaseCheckboxesProps extends ThemeProps, LocaleProps { options: Options; className?: string; placeholder?: string; @@ -31,8 +32,8 @@ export interface CheckboxesProps extends ThemeProps { disabled?: boolean; } -export class Checkboxes< - T extends CheckboxesProps = CheckboxesProps, +export class BaseCheckboxes< + T extends BaseCheckboxesProps = BaseCheckboxesProps, S = any > extends React.Component { static defaultProps = { @@ -68,7 +69,7 @@ export class Checkboxes< return; } - let valueArray = Checkboxes.value2array(value, options, option2value); + let valueArray = BaseCheckboxes.value2array(value, options, option2value); let idx = valueArray.indexOf(option); if (~idx) { @@ -115,7 +116,9 @@ export class Checkboxes< itemRender } = this.props; - let valueArray = Checkboxes.value2array(value, options, option2value); + const __ = this.props.translate; + + let valueArray = BaseCheckboxes.value2array(value, options, option2value); let body: Array = []; if (Array.isArray(options) && options.length) { @@ -142,14 +145,18 @@ export class Checkboxes< inline ? 'Checkboxes--inline' : '' )} > - {body && body.length ? body :
{placeholder}
} + {body && body.length ? body :
{__(placeholder)}
} ); } } +export class Checkboxes extends BaseCheckboxes {} + export default themeable( - uncontrollable(Checkboxes, { - value: 'onChange' - }) + localeable( + uncontrollable(Checkboxes, { + value: 'onChange' + }) + ) ); diff --git a/src/components/ColorPicker.tsx b/src/components/ColorPicker.tsx index 5528db2f9..b36c31da4 100644 --- a/src/components/ColorPicker.tsx +++ b/src/components/ColorPicker.tsx @@ -12,10 +12,11 @@ import {Icon} from './icons'; import Overlay from './Overlay'; import uncontrollable from 'uncontrollable'; import PopOver from './PopOver'; -import {ClassNamesFn, themeable} from '../theme'; +import {ClassNamesFn, themeable, ThemeProps} from '../theme'; import {autobind} from '../utils/helper'; +import {localeable, LocaleProps} from '../locale'; -export interface ColorProps { +export interface ColorProps extends LocaleProps, ThemeProps { placeholder?: string; format: string; // closeOnSelect:boolean; @@ -25,8 +26,6 @@ export interface ColorProps { popOverContainer?: any; placement?: string; value: any; - classPrefix: string; - classnames: ClassNamesFn; onChange: (value: any) => void; presetColors?: string[]; resetValue?: string; @@ -219,6 +218,7 @@ export class ColorControl extends React.PureComponent< allowCustomColor } = this.props; + const __ = this.props.translate; const isOpened = this.state.isOpened; const isFocused = this.state.isFocused; @@ -240,7 +240,7 @@ export class ColorControl extends React.PureComponent< size={10} className={cx('ColorPicker-input')} value={this.state.inputValue || ''} - placeholder={placeholder} + placeholder={__(placeholder)} disabled={disabled} onChange={this.handleInputChange} onFocus={this.handleFocus} @@ -300,7 +300,9 @@ export class ColorControl extends React.PureComponent< } export default themeable( - uncontrollable(ColorControl, { - value: 'onChange' - }) + localeable( + uncontrollable(ColorControl, { + value: 'onChange' + }) + ) ); diff --git a/src/components/DatePicker.tsx b/src/components/DatePicker.tsx index 16e4531d7..ac68e160d 100644 --- a/src/components/DatePicker.tsx +++ b/src/components/DatePicker.tsx @@ -11,10 +11,11 @@ import 'moment/locale/zh-cn'; import {Icon} from './icons'; import PopOver from './PopOver'; import Overlay from './Overlay'; -import {ClassNamesFn, themeable} from '../theme'; +import {ClassNamesFn, themeable, ThemeProps} from '../theme'; import {PlainObject} from '../types'; import Calendar from './calendar/Calendar'; import 'react-datetime/css/react-datetime.css'; +import {localeable, LocaleProps, TranslateFn} from '../locale'; const availableShortcuts: {[propName: string]: any} = { now: { @@ -97,9 +98,9 @@ const availableShortcuts: {[propName: string]: any} = { const advancedShortcuts = [ { regexp: /^(\d+)hoursago$/, - resolve: (_: string, hours: string) => { + resolve: (__: TranslateFn, _: string, hours: string) => { return { - label: `${hours}小时前`, + label: __('{{hours}}小时前', {hours}), date: (now: moment.Moment) => { return now.subtract(hours, 'hours'); } @@ -108,9 +109,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)hourslater$/, - resolve: (_: string, hours: string) => { + resolve: (__: TranslateFn, _: string, hours: string) => { return { - label: `${hours}小时后`, + label: __('{{hours}}小时后', {hours}), date: (now: moment.Moment) => { return now.add(hours, 'hours'); } @@ -119,9 +120,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)daysago$/, - resolve: (_: string, days: string) => { + resolve: (__: TranslateFn, _: string, days: string) => { return { - label: `${days}天前`, + label: __('{{days}}天前', {days}), date: (now: moment.Moment) => { return now.subtract(days, 'days'); } @@ -130,9 +131,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)dayslater$/, - resolve: (_: string, days: string) => { + resolve: (__: TranslateFn, _: string, days: string) => { return { - label: `${days}天后`, + label: __('{{days}}天后', {days}), date: (now: moment.Moment) => { return now.add(days, 'days'); } @@ -141,9 +142,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)weeksago$/, - resolve: (_: string, weeks: string) => { + resolve: (__: TranslateFn, _: string, weeks: string) => { return { - label: `${weeks}周前`, + label: __('{{weeks}}周前', {weeks}), date: (now: moment.Moment) => { return now.subtract(weeks, 'weeks'); } @@ -152,9 +153,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)weekslater$/, - resolve: (_: string, weeks: string) => { + resolve: (__: TranslateFn, _: string, weeks: string) => { return { - label: `${weeks}周后`, + label: __('{{weeks}}周后', {weeks}), date: (now: moment.Moment) => { return now.add(weeks, 'weeks'); } @@ -163,9 +164,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)monthsago$/, - resolve: (_: string, months: string) => { + resolve: (__: TranslateFn, _: string, months: string) => { return { - label: `${months}月前`, + label: __('{{months}}月前', {months}), date: (now: moment.Moment) => { return now.subtract(months, 'months'); } @@ -174,9 +175,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)monthslater$/, - resolve: (_: string, months: string) => { + resolve: (__: TranslateFn, _: string, months: string) => { return { - label: `${months}月后`, + label: __('{{months}}月后', {months}), date: (now: moment.Moment) => { return now.add(months, 'months'); } @@ -185,9 +186,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)quartersago$/, - resolve: (_: string, quarters: string) => { + resolve: (__: TranslateFn, _: string, quarters: string) => { return { - label: `${quarters}季度前`, + label: __('{{quarters}}季度前', {quarters}), date: (now: moment.Moment) => { return now.subtract(quarters, 'quarters'); } @@ -196,9 +197,9 @@ const advancedShortcuts = [ }, { regexp: /^(\d+)quarterslater$/, - resolve: (_: string, quarters: string) => { + resolve: (__: TranslateFn, _: string, quarters: string) => { return { - label: `${quarters}季度后`, + label: __('{{quarters}}季度后', {quarters}), date: (now: moment.Moment) => { return now.add(quarters, 'quarters'); } @@ -226,17 +227,15 @@ export type ShortCuts = | ShortCutDate | ShortCutDateRange; -export interface DateProps { +export interface DateProps extends LocaleProps, ThemeProps { viewMode: 'years' | 'months' | 'days' | 'time'; className?: string; - classPrefix: string; - classnames: ClassNamesFn; placeholder?: string; inputFormat?: string; timeFormat?: string; format?: string; timeConstrainst?: object; - closeOnSelect?: boolean; + closeOnSelect: boolean; disabled?: boolean; minDate?: moment.Moment; maxDate?: moment.Moment; @@ -247,7 +246,14 @@ export interface DateProps { value: any; shortcuts: string | Array; overlayPlacement: string; - [propName: string]: any; + minTime?: moment.Moment; + maxTime?: moment.Moment; + dateFormat?: string; + timeConstraints?: any; + popOverContainer?: any; + + // 下面那个千万不要写,写了就会导致 keyof DateProps 得到的结果是 string | number; + // [propName: string]: any; } export interface DatePickerState { @@ -257,11 +263,8 @@ export interface DatePickerState { } export class DatePicker extends React.Component { - static defaultProps: Pick< - DateProps, - 'viewMode' | 'shortcuts' | 'closeOnSelect' | 'overlayPlacement' - > = { - viewMode: 'days', + static defaultProps = { + viewMode: 'days' as 'years' | 'months' | 'days' | 'time', shortcuts: '', closeOnSelect: true, overlayPlacement: 'auto' @@ -429,12 +432,14 @@ export class DatePicker extends React.Component { return availableShortcuts[key]; } + const __ = this.props.translate; + for (let i = 0, len = advancedShortcuts.length; i < len; i++) { let item = advancedShortcuts[i]; const m = item.regexp.exec(key); if (m) { - return item.resolve.apply(item, m); + return item.resolve.apply(item, [__, ...m]); } } @@ -452,6 +457,8 @@ export class DatePicker extends React.Component { } else { shortcutArr = shortcuts; } + + const __ = this.props.translate; return (