Merge pull request #1117 from ant-design/feat-locale

Unified component locale support
This commit is contained in:
Benjy Cui 2016-03-10 10:38:53 +08:00
commit e9f837198e
18 changed files with 386 additions and 49 deletions

View File

@ -1,14 +1,23 @@
import React from 'react';
import objectAssign from 'object-assign';
import defaultLocale from './locale/zh_CN';
import DateTimeFormat from 'gregorian-calendar-format';
import GregorianCalendar from 'gregorian-calendar';
export default {
contextTypes: {
antLocale: React.PropTypes.object,
},
getLocale() {
let locale = defaultLocale;
if (this.context.antLocale && this.context.antLocale.DatePicker) {
locale = this.context.antLocale.DatePicker;
}
// Locale
let locale = objectAssign({}, defaultLocale, this.props.locale);
locale.lang = objectAssign({}, defaultLocale.lang, this.props.locale.lang);
return locale;
const result = objectAssign({}, locale, this.props.locale);
result.lang = objectAssign({}, locale.lang, this.props.locale.lang);
return result;
},
getFormatter() {

View File

@ -0,0 +1,131 @@
# 所有组件
- order: 2
此处列出 Ant Design 中需要国际化支持的组件,你可以在演示里切换语言。
---
````jsx
import { LocaleProvider, Pagination, DatePicker, TimePicker,
Popconfirm, Table, Modal, Button, Select, Transfer } from 'antd';
import enUS from 'antd/lib/locale-provider/en_US';
const Option = Select.Option;
const columns = [{
title: 'Name',
dataIndex: 'name',
filters: [{
text: 'filter1',
value: 'filter1',
}],
}, {
title: 'Age',
dataIndex: 'age',
}];
const Page = React.createClass({
getInitialState() {
return {
visible: false,
};
},
showModal() {
this.setState({ visible: true });
},
hideModal() {
this.setState({ visible: false });
},
render() {
const info = () => {
Modal.info({
title: 'some info',
content: 'some info',
});
};
const confirm = () => {
Modal.confirm({
title: 'some info',
content: 'some info',
});
};
return (
<div className="locale-components">
<div className="example">
<Pagination defaultCurrent={1} total={50} showSizeChanger />
</div>
<div className="example">
<DatePicker />
<TimePicker />
<Button type="primary" onClick={this.showModal}>Show Modal</Button>
<Button onClick={info}>Show info</Button>
<Button onClick={confirm}>Show confirm</Button>
<Popconfirm title="Question?">
<a href="#">Click to confirm</a>
</Popconfirm>
</div>
<div className="example">
<Transfer
dataSource={[]}
showSearch
titles={['', '']}
targetKeys={[]}
render={item => item.title} />
</div>
<div className="example">
<Table dataSource={[]} columns={columns} />
</div>
<Modal title="Locale Modal" visible={this.state.visible} onCancel={this.hideModal}>
<p>Locale Modal</p>
</Modal>
</div>
);
}
});
const App = React.createClass({
getInitialState() {
return {
locale: enUS,
};
},
changeLocale(locale) {
this.setState({ locale });
},
render() {
return (
<div>
<div className="change-locale">
<span>Change locale of components: </span>
<Select defaultValue={enUS} onChange={this.changeLocale} dropdownMatchSelectWidth={false}>
<Option value={enUS}>English</Option>
<Option value={null}>中文</Option>
</Select>
</div>
<LocaleProvider locale={this.state.locale}><Page /></LocaleProvider>
</div>
);
}
});
ReactDOM.render(<App />, mountNode);
````
````css
.locale-components {
border-top: 1px solid #d9d9d9;
padding-top: 16px;
}
.example {
margin: 16px 0;
}
.example > * {
margin-right: 8px;
}
.change-locale {
margin-bottom: 16px;
}
````

View File

@ -0,0 +1,28 @@
# 国际化
- order: 1
`LocaleProvider` 包裹你的应用,并引用对应的语言包。
---
````jsx
import { Pagination, LocaleProvider } from 'antd';
import enUS from 'antd/lib/locale-provider/en_US';
const App = React.createClass({
render() {
return (
<div>
<Pagination defaultCurrent={1} total={50} showSizeChanger />
</div>
);
}
});
ReactDOM.render(
<LocaleProvider locale={enUS}>
<App />
</LocaleProvider>
, mountNode);
````

View File

@ -0,0 +1,26 @@
module.exports = {
Pagination: require('rc-pagination/lib/locale/en_US'),
DatePicker: require('../date-picker/locale/en_US'),
TimePicker: require('../time-picker/locale/en_US'),
Table: {
filterTitle: 'Filter Menu',
filterConfirm: 'OK',
filterReset: 'Reset',
emptyText: 'No Data',
},
Modal: {
okText: 'OK',
cancelText: 'Cancel',
justOkText: 'OK',
},
Popconfirm: {
okText: 'OK',
cancelText: 'Cancel',
},
Transfer: {
notFoundContent: 'Not Found',
searchPlaceholder: 'Search here',
itemUnit: 'item',
itemsUnit: 'items',
},
};

View File

@ -0,0 +1,31 @@
import React from 'react';
import { changeConfirmLocale } from '../modal/confirm';
export default class LocaleProvider extends React.Component {
getChildContext() {
return {
antLocale: this.props.locale,
};
}
componentDidMount() {
this.componentDidUpdate();
}
componentDidUpdate() {
const { locale } = this.props;
changeConfirmLocale(locale && locale.Modal);
}
componentWillUnMount() {
changeConfirmLocale();
}
render() {
return React.Children.only(this.props.children);
}
}
LocaleProvider.childContextTypes = {
antLocale: React.PropTypes.object,
};
LocaleProvider.propTypes = {
locale: React.PropTypes.object,
};

View File

@ -0,0 +1,36 @@
# LocaleProvider
- category: Components
- chinese: 国际化
- cols: 1
---
为组件内建文案提供统一的国际化支持。
## 使用
LocaleProvider 使用 React 的 [context](https://facebook.github.io/react/docs/context.html) 特性,只需在应用外围包裹一次即可全局生效。
```jsx
import enUS from 'antd/lib/locale-provider/en_US';
...
return <LocaleProvider locale={enUS}><App /></LocaleProvider>;
```
### Add a language
We supply an English locale package by now. Other language users can custumize your locale package as [en_US](https://github.com/ant-design/ant-design/blob/26b1f37392a278285aec6c573b99c6feea09e218/components/locale-provider/en_US.js) and ask a pull request to us.
### 其他国际化需求
本模块仅用于组件的内建文案,若有业务文案的国际化需求,建议使用 [react-intl](https://github.com/yahoo/react-intl),可参考示例:[Intl demo 1](http://github.com/ant-design/intl-example) 和 [Intl demo 2](http://yiminghe.me/learning-react/examples/react-intl.html?locale=en-US)。
## API
| 参数 | 说明 | 类型 | 默认值 |
|--------|----------------|------------------|--------------|
| locale | 语言包配置,语言包可到 `antd/lib/locale-provider/` 目录下寻找 | Object | - |

View File

@ -14,8 +14,6 @@ let AntModal = React.createClass({
prefixCls: 'ant-modal',
onOk: noop,
onCancel: noop,
okText: '确定',
cancelText: '取消',
width: 520,
transitionName: 'zoom',
maskAnimation: 'fade',
@ -24,6 +22,10 @@ let AntModal = React.createClass({
};
},
contextTypes: {
antLocale: React.PropTypes.object,
},
handleCancel(e) {
this.props.onCancel(e);
},
@ -52,19 +54,26 @@ let AntModal = React.createClass({
render() {
let props = this.props;
let { okText, cancelText } = props;
if (this.context.antLocale && this.context.antLocale.Modal) {
okText = okText || this.context.antLocale.Modal.okText;
cancelText = cancelText || this.context.antLocale.Modal.cancelText;
}
let defaultFooter = [
<Button key="cancel"
type="ghost"
size="large"
onClick={this.handleCancel}>
{props.cancelText}
{cancelText || '取消'}
</Button>,
<Button key="confirm"
type="primary"
size="large"
loading={props.confirmLoading}
onClick={this.handleOk}>
{props.okText}
{okText || '确定'}
</Button>
];
let footer = props.footer || defaultFooter;

View File

@ -5,8 +5,24 @@ import Icon from '../icon';
import Button from '../button';
import objectAssign from 'object-assign';
export default function (config) {
const props = objectAssign({}, config || {});
const defaultLocale = {
okText: '确定',
cancelText: '取消',
justOkText: '知道了',
};
let runtimeLocale = { ...defaultLocale };
export function changeConfirmLocale(newLocale) {
if (newLocale) {
objectAssign(runtimeLocale, newLocale);
} else {
runtimeLocale = { ...defaultLocale };
}
}
export default function confirm(config) {
const props = objectAssign({}, config);
let div = document.createElement('div');
document.body.appendChild(div);
@ -22,12 +38,13 @@ export default function (config) {
props.okCancel = true;
}
props.okText = props.okText || (props.okCancel ? '确定' : '知道了');
props.cancelText = props.cancelText || '取消';
props.okText = props.okText ||
(props.okCancel ? runtimeLocale.okText : runtimeLocale.justOkText);
props.cancelText = props.cancelText || runtimeLocale.cancelText;
function close() {
d.setState({
visible: false
visible: false,
});
ReactDOM.unmountComponentAtNode(div);
div.parentNode.removeChild(div);

View File

@ -1,16 +0,0 @@
# 国际化
- order: 7
通过 `locale` 配置时区、语言等, 默认支持 en_US, zh_CN
---
````jsx
import { Pagination } from 'antd';
import enUS from 'antd/lib/pagination/locale/en_US';
ReactDOM.render(
<Pagination defaultCurrent={1} total={50} locale={enUS} />,
mountNode);
````

View File

@ -16,6 +16,13 @@ class AntPagination extends React.Component {
let className = this.props.className;
let selectComponentClass = Select;
let locale;
if (this.context.antLocale && this.context.antLocale.Pagination) {
locale = this.context.antLocale.Pagination;
} else {
locale = this.props.locale;
}
if (this.props.size === 'small') {
className += ' mini';
selectComponentClass = MiniSelect;
@ -25,6 +32,7 @@ class AntPagination extends React.Component {
<Pagination selectComponentClass={selectComponentClass}
selectPrefixCls="ant-select"
{...this.props}
locale={locale}
className={className} />
);
}
@ -36,4 +44,8 @@ AntPagination.defaultProps = {
prefixCls: 'ant-pagination',
};
AntPagination.contextTypes = {
antLocale: React.PropTypes.object,
};
export default AntPagination;

View File

@ -34,11 +34,12 @@ export default React.createClass({
overlayStyle: {},
onConfirm: noop,
onCancel: noop,
okText: '确定',
cancelText: '取消',
onVisibleChange() {},
};
},
contextTypes: {
antLocale: React.PropTypes.object,
},
componentWillReceiveProps(nextProps) {
if ('visible' in nextProps) {
this.setState({ visible: nextProps.visible });
@ -62,7 +63,12 @@ export default React.createClass({
}
},
render() {
const { title, okText, cancelText, placement, overlayStyle, trigger, ...restProps } = this.props;
const { title, placement, overlayStyle, trigger, ...restProps } = this.props;
let { okText, cancelText } = this.props;
if (this.context.antLocale && this.context.antLocale.Popconfirm) {
okText = okText || this.context.antLocale.Popconfirm.okText;
cancelText = cancelText || this.context.antLocale.Popconfirm.cancelText;
}
const overlay = (
<div>
<div className={`${prefixCls}-content`}>
@ -71,8 +77,8 @@ export default React.createClass({
{title}
</p>
<div className={`${prefixCls}-buttons`}>
<Button onClick={this.cancel} type="ghost" size="small">{cancelText}</Button>
<Button onClick={this.confirm} type="primary" size="small">{okText}</Button>
<Button onClick={this.cancel} type="ghost" size="small">{cancelText || '取消'}</Button>
<Button onClick={this.confirm} type="primary" size="small">{okText || '确定'}</Button>
</div>
</div>
</div>

View File

@ -74,6 +74,10 @@ let AntTable = React.createClass({
locale: React.PropTypes.object,
},
contextTypes: {
antLocale: React.PropTypes.object,
},
getDefaultSelection() {
if (!this.props.rowSelection || !this.props.rowSelection.getCheckboxProps) {
return [];
@ -83,6 +87,14 @@ let AntTable = React.createClass({
.map((record, rowIndex) => this.getRecordKey(record, rowIndex));
},
getLocale() {
let locale = {};
if (this.context.antLocale && this.context.antLocale.Table) {
locale = this.context.antLocale.Table;
}
return objectAssign({}, defaultLocale, locale, this.props.locale);
},
componentWillReceiveProps(nextProps) {
if (('pagination' in nextProps) && nextProps.pagination !== false) {
this.setState({
@ -405,7 +417,7 @@ let AntTable = React.createClass({
},
renderColumnsDropdown(columns) {
let locale = objectAssign({}, defaultLocale, this.props.locale);
const locale = this.getLocale();
return columns.map((originColumn, i) => {
let column = objectAssign({}, originColumn);
let key = this.getColumnKey(column, i);
@ -563,7 +575,7 @@ let AntTable = React.createClass({
const data = this.getCurrentPageData();
let columns = this.renderRowSelection();
const expandIconAsCell = this.props.expandedRowRender && this.props.expandIconAsCell !== false;
const locale = objectAssign({}, defaultLocale, this.props.locale);
const locale = this.getLocale();
const classString = classNames({
[`ant-table-${this.props.size}`]: true,

View File

@ -27,6 +27,10 @@ const AntTimePicker = React.createClass({
};
},
contextTypes: {
antLocale: React.PropTypes.object,
},
getFormatter() {
return new DateTimeFormat(this.props.format);
},
@ -68,14 +72,19 @@ const AntTimePicker = React.createClass({
},
getLocale() {
let locale = defaultLocale;
if (this.context.antLocale && this.context.antLocale.TimePicker) {
locale = this.context.antLocale.TimePicker;
}
// Locale
return objectAssign({}, defaultLocale, this.props.locale);
return objectAssign({}, locale, this.props.locale);
},
render() {
const locale = this.getLocale();
const props = objectAssign({}, this.props);
props.placeholder = ('placeholder' in this.props)
? props.placeholder : this.getLocale().placeholder;
? props.placeholder : locale.placeholder;
if (props.defaultValue) {
props.defaultValue = this.parseTimeFromValue(props.defaultValue);
} else {
@ -99,7 +108,7 @@ const AntTimePicker = React.createClass({
<TimePicker
{...props}
className={className}
locale={this.getLocale()}
locale={locale}
formatter={this.getFormatter()}
onChange={this.handleChange}
/>

View File

@ -223,8 +223,6 @@ Transfer.defaultProps = {
titles: ['源列表', '目的列表'],
operations: [],
showSearch: false,
searchPlaceholder: '请输入搜索内容',
notFoundContent: 'Not Found',
body: noop,
footer: noop,
};

View File

@ -27,6 +27,6 @@
| titles | 标题集合,顺序从左至右 | Array | ['源列表', '目的列表'] |
| operations | 操作文案集合,顺序从上至下 | Array | [] |
| showSearch | 是否显示搜索框 | Boolean | false |
| searchPlaceholder | 搜索框的默认值 | String | 请输入搜索的内容 |
| notFoundContent | 当列表为空时显示的内容 | React.node | 'Not Found' |
| searchPlaceholder | 搜索框的默认值 | String | '请输入搜索内容' |
| notFoundContent | 当列表为空时显示的内容 | React.node | '列表为空' |
| footer | 底部渲染函数 | Function(props) | | |

View File

@ -69,8 +69,10 @@ class TransferList extends Component {
}
render() {
const { prefixCls, dataSource, titleText, filter, checkedKeys, notFoundContent,
checkStatus, body, footer, showSearch, searchPlaceholder } = this.props;
const { prefixCls, dataSource, titleText, filter, checkedKeys,
checkStatus, body, footer, showSearch } = this.props;
let { searchPlaceholder, notFoundContent } = this.props;
// Custom Layout
const footerDom = footer({ ...this.props });
@ -95,6 +97,18 @@ class TransferList extends Component {
);
});
let unit = '条';
if (this.context.antLocale &&
this.context.antLocale.Transfer) {
unit = dataSource.length > 1
? this.context.antLocale.Transfer.itemsUnit
: this.context.antLocale.Transfer.itemUnit;
searchPlaceholder = searchPlaceholder
|| this.context.antLocale.Transfer.searchPlaceholder;
notFoundContent = notFoundContent
|| this.context.antLocale.Transfer.notFoundContent;
}
return (
<div className={listCls} {...this.props}>
<div className={`${prefixCls}-header`}>
@ -103,8 +117,15 @@ class TransferList extends Component {
checked: checkStatus === 'all',
checkPart: checkStatus === 'part',
checkable: <span className={'ant-transfer-checkbox-inner'}></span>
})}<span className={`${prefixCls}-header-selected`}><span>{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + dataSource.length} </span>
<span className={`${prefixCls}-header-title`}>{titleText}</span></span>
})}
<span className={`${prefixCls}-header-selected`}>
<span>
{(checkedKeys.length > 0 ? `${checkedKeys.length}/` : '') + dataSource.length} {unit}
</span>
<span className={`${prefixCls}-header-title`}>
{titleText}
</span>
</span>
</div>
{ bodyDom ||
<div className={ showSearch ? `${prefixCls}-body ${prefixCls}-body-with-search` : `${prefixCls}-body`}>
@ -112,13 +133,15 @@ class TransferList extends Component {
<Search prefixCls={`${prefixCls}-search`}
onChange={this.handleFilter.bind(this)}
handleClear={this.handleClear.bind(this)}
placeholder={searchPlaceholder}
placeholder={searchPlaceholder || '请输入搜索内容'}
value={filter} />
</div> : null }
<Animate component="ul"
transitionName={this.state.mounted ? `${prefixCls}-highlight` : ''}
transitionLeave={false}>
{showItems.length > 0 ? showItems : <div className={`${prefixCls}-body-not-found`}>{notFoundContent}</div>}
{showItems.length > 0
? showItems
: <div className={`${prefixCls}-body-not-found`}>{notFoundContent || '列表为空'}</div>}
</Animate>
</div>}
{ footerDom ? <div className={`${prefixCls}-footer`}>
@ -133,7 +156,6 @@ TransferList.defaultProps = {
dataSource: [],
titleText: '',
showSearch: false,
searchPlaceholder: '',
handleFilter: noop,
handleSelect: noop,
handleSelectAll: noop,
@ -158,4 +180,8 @@ TransferList.propTypes = {
footer: PropTypes.func,
};
TransferList.contextTypes = {
antLocale: React.PropTypes.object,
};
export default TransferList;

View File

@ -46,6 +46,7 @@ const antd = {
Transfer: require('./components/transfer'),
Cascader: require('./components/cascader'),
Card: require('./components/card'),
LocaleProvider: require('./components/locale-provider'),
};
module.exports = antd;

View File

@ -61,6 +61,8 @@ antd.Pagination.locale = {
zh_CN: require('../components/pagination/locale/zh_CN'),
};
antd.LocaleProvider['en_US'] = require('../components/locale-provider/en_US'),
InstantClickChangeFns.push(function () {
// auto complete for components
var Select = antd.Select;