From 286003e6ca3537899b6cfba2bd299e7ecfa2d1d4 Mon Sep 17 00:00:00 2001 From: Maxim Kudryavtsev Date: Tue, 7 May 2019 11:06:55 +0300 Subject: [PATCH] feat(react-scheduler): support scheduler localization capability (#1985) --- .../material-ui/custom-formatting.jsx | 61 +++++++ .../material-ui/locale.jsx | 108 ++++++++++++ .../api/dx-react-scheduler-material-ui.api.ts | 1 + .../templates/appointment-tooltip/layout.jsx | 12 +- .../appointment-tooltip/layout.test.jsx | 17 +- .../appointment/appointment-content.test.jsx | 1 + .../appointment/horizontal-appointment.jsx | 3 + .../appointment/vertical-appointment.jsx | 8 +- .../appointment/vertical-appointment.test.jsx | 20 ++- .../date-navigator/calendar/navigator.jsx | 4 +- .../calendar/navigator.test.jsx | 5 +- .../date-navigator/calendar/root.jsx | 4 + .../date-navigator/calendar/root.test.jsx | 12 +- .../date-navigator/calendar/table.jsx | 7 +- .../date-navigator/calendar/table.test.jsx | 1 + .../date-navigator/calendar/text.jsx | 6 +- .../date-navigator/calendar/text.test.jsx | 17 +- .../views/common/day-scale/layout.jsx | 3 + .../views/horizontal/day-scale/cell.jsx | 27 ++- .../views/horizontal/day-scale/cell.test.jsx | 13 ++ .../views/horizontal/time-table/cell.jsx | 8 +- .../views/horizontal/time-table/cell.test.jsx | 13 ++ .../views/horizontal/time-table/layout.jsx | 3 + .../horizontal/time-table/layout.test.jsx | 1 + .../views/vertical/day-scale/cell.jsx | 51 +++--- .../views/vertical/day-scale/cell.test.jsx | 17 ++ .../views/vertical/time-scale/cell.jsx | 27 ++- .../views/vertical/time-scale/cell.test.jsx | 13 ++ .../views/vertical/time-scale/layout.jsx | 3 + .../views/vertical/time-scale/layout.test.jsx | 1 + .../views/vertical/time-table/layout.jsx | 2 + .../api/dx-react-scheduler.api.ts | 17 ++ .../docs/guides/localization.md | 24 +++ .../docs/reference/appointment-tooltip.md | 1 + .../docs/reference/appointments.md | 1 + .../docs/reference/day-view.md | 5 + .../docs/reference/month-view.md | 4 + .../docs/reference/scheduler.md | 1 + .../docs/reference/week-view.md | 5 + .../src/plugins/appointment-tooltip.jsx | 5 +- .../src/plugins/appointment-tooltip.test.jsx | 5 + .../src/plugins/appointments.jsx | 33 ++-- .../src/plugins/appointments.test.jsx | 7 +- .../src/plugins/date-navigator.jsx | 3 + .../src/plugins/date-navigator.test.jsx | 37 ++-- .../src/plugins/day-view.jsx | 9 +- .../src/plugins/day-view.test.jsx | 26 ++- .../src/plugins/month-view.jsx | 6 +- .../src/plugins/month-view.test.jsx | 9 +- .../src/plugins/scheduler-core.jsx | 5 +- .../src/plugins/scheduler-core.test.jsx | 19 +- .../src/plugins/week-view.jsx | 9 +- .../src/plugins/week-view.test.jsx | 13 +- packages/dx-react-scheduler/src/scheduler.jsx | 4 + packages/dx-scheduler-core/src/constants.ts | 16 ++ .../plugins/date-navigator/helpers.test.ts | 37 ++-- .../src/plugins/date-navigator/helpers.ts | 63 +++++-- .../plugins/scheduler-core/computeds.test.ts | 165 +++++++++++++----- .../src/plugins/scheduler-core/computeds.ts | 21 ++- .../plugins/scheduler-core/helpers.test.ts | 14 ++ .../src/plugins/scheduler-core/helpers.ts | 5 + .../src/types/date-navigator.types.ts | 3 +- .../src/types/scheduler-core.types.ts | 10 ++ site/_data/docs/navigation.yml | 2 + 64 files changed, 836 insertions(+), 217 deletions(-) create mode 100644 packages/dx-react-scheduler-demos/src/demo-sources/scheduler-localization/material-ui/custom-formatting.jsx create mode 100644 packages/dx-react-scheduler-demos/src/demo-sources/scheduler-localization/material-ui/locale.jsx create mode 100644 packages/dx-react-scheduler/docs/guides/localization.md create mode 100644 packages/dx-scheduler-core/src/plugins/scheduler-core/helpers.test.ts create mode 100644 packages/dx-scheduler-core/src/plugins/scheduler-core/helpers.ts diff --git a/packages/dx-react-scheduler-demos/src/demo-sources/scheduler-localization/material-ui/custom-formatting.jsx b/packages/dx-react-scheduler-demos/src/demo-sources/scheduler-localization/material-ui/custom-formatting.jsx new file mode 100644 index 0000000000..a027883338 --- /dev/null +++ b/packages/dx-react-scheduler-demos/src/demo-sources/scheduler-localization/material-ui/custom-formatting.jsx @@ -0,0 +1,61 @@ +import * as React from 'react'; +import Paper from '@material-ui/core/Paper'; +import moment from 'moment'; +import { ViewState } from '@devexpress/dx-react-scheduler'; +import { + Scheduler, + WeekView, + Toolbar, + Appointments, +} from '@devexpress/dx-react-scheduler-material-ui'; +import { appointments } from '../../../demo-data/appointments'; + +const formatDayScaleDate = (date, options) => { + const momentDate = moment(date); + const { weekday } = options; + return momentDate.format(weekday ? 'dddd' : 'D'); +}; +const formatTimeScaleDate = date => moment(date).format('hh:mm:ss'); + +const DayScaleCell = ( + { formatDate, ...restProps }, +) => ; +const TimeScaleCell = ( + { formatDate, ...restProps }, +) => ; + +export default class Demo extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + data: appointments, + currentDate: '2018-06-27', + }; + } + + render() { + const { data, currentDate, locale } = this.state; + + return ( + + + + + + + + + ); + } +} diff --git a/packages/dx-react-scheduler-demos/src/demo-sources/scheduler-localization/material-ui/locale.jsx b/packages/dx-react-scheduler-demos/src/demo-sources/scheduler-localization/material-ui/locale.jsx new file mode 100644 index 0000000000..2221b7f211 --- /dev/null +++ b/packages/dx-react-scheduler-demos/src/demo-sources/scheduler-localization/material-ui/locale.jsx @@ -0,0 +1,108 @@ +import * as React from 'react'; +import Paper from '@material-ui/core/Paper'; +import TextField from '@material-ui/core/TextField'; +import MenuItem from '@material-ui/core/MenuItem'; +import { ViewState } from '@devexpress/dx-react-scheduler'; +import { withStyles } from '@material-ui/core/styles'; +import { + Scheduler, + WeekView, + Toolbar, + DateNavigator, + Appointments, + AllDayPanel, +} from '@devexpress/dx-react-scheduler-material-ui'; + +import { appointments } from '../../../demo-data/appointments'; + +const allDayLocalizationMessages = { + 'fr-FR': { + allDay: "Autour de l'horloge", + }, + 'de-GR': { + allDay: 'Rund um die Uhr', + }, + 'en-US': { + allDay: 'All Day', + }, +}; + +const getAllDayMessages = locale => allDayLocalizationMessages[locale]; + +const styles = theme => ({ + container: { + display: 'flex', + marginBottom: theme.spacing.unit * 2, + justifyContent: 'flex-end', + }, + text: { + ...theme.typography.h6, + marginRight: theme.spacing.unit * 2, + }, +}); + +const LocaleSwitcher = withStyles(styles, { name: 'LocaleSwitcher' })( + ({ onLocaleChange, currentLocale, classes }) => ( +
+
+ Locale: +
+ + Le français (French) + Deutsch (German) + English (United States) + +
+ ), +); + +export default class Demo extends React.PureComponent { + constructor(props) { + super(props); + + this.state = { + data: appointments, + currentDate: '2018-06-27', + locale: 'fr-FR', + }; + + this.changeLocale = event => this.setState({ locale: event.target.value }); + } + + render() { + const { data, currentDate, locale } = this.state; + + return ( +
+ + + + + + + + + + + +
+ ); + } +} diff --git a/packages/dx-react-scheduler-material-ui/api/dx-react-scheduler-material-ui.api.ts b/packages/dx-react-scheduler-material-ui/api/dx-react-scheduler-material-ui.api.ts index 885468ee0e..5c3a1f41f5 100644 --- a/packages/dx-react-scheduler-material-ui/api/dx-react-scheduler-material-ui.api.ts +++ b/packages/dx-react-scheduler-material-ui/api/dx-react-scheduler-material-ui.api.ts @@ -551,6 +551,7 @@ declare const Scheduler: React.ComponentType & { // @public (undocumented) interface SchedulerProps { data?: Array; + locale?: string; rootComponent?: React.ComponentType; } diff --git a/packages/dx-react-scheduler-material-ui/src/templates/appointment-tooltip/layout.jsx b/packages/dx-react-scheduler-material-ui/src/templates/appointment-tooltip/layout.jsx index 72a7b86ca4..86f7adbdf3 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/appointment-tooltip/layout.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/appointment-tooltip/layout.jsx @@ -3,8 +3,10 @@ import * as PropTypes from 'prop-types'; import Popover from '@material-ui/core/Popover'; import AccessTime from '@material-ui/icons/AccessTime'; import Grid from '@material-ui/core/Grid'; -import moment from 'moment'; import { withStyles } from '@material-ui/core/styles'; +import { HOUR_MINUTE_OPTIONS } from '@devexpress/dx-scheduler-core'; + +const verticalTopHorizontalCenterOptions = { vertical: 'top', horizontal: 'center' }; const styles = theme => ({ text: { @@ -47,6 +49,7 @@ const LayoutBase = ({ commandButtonIds, onOpenButtonClick, onDeleteButtonClick, + formatDate, classes, ...restProps }) => { @@ -61,8 +64,8 @@ const LayoutBase = ({ open={visible} anchorEl={target} onClose={onHide} - anchorOrigin={{ vertical: 'top', horizontal: 'center' }} - transformOrigin={{ vertical: 'top', horizontal: 'center' }} + anchorOrigin={verticalTopHorizontalCenterOptions} + transformOrigin={verticalTopHorizontalCenterOptions} {...restProps} >
@@ -92,7 +95,7 @@ const LayoutBase = ({
- {`${moment(data.startDate).format('h:mm A')} - ${moment(data.endDate).format('h:mm A')}`} + {`${formatDate(data.startDate, HOUR_MINUTE_OPTIONS)} - ${formatDate(data.endDate, HOUR_MINUTE_OPTIONS)}`}
@@ -110,6 +113,7 @@ LayoutBase.propTypes = { showDeleteButton: PropTypes.bool.isRequired, commandButtonIds: PropTypes.object.isRequired, classes: PropTypes.object.isRequired, + formatDate: PropTypes.func.isRequired, onOpenButtonClick: PropTypes.func, onDeleteButtonClick: PropTypes.func, appointmentMeta: PropTypes.shape({ diff --git a/packages/dx-react-scheduler-material-ui/src/templates/appointment-tooltip/layout.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/appointment-tooltip/layout.test.jsx index f0fa4ccf41..aaa07625ce 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/appointment-tooltip/layout.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/appointment-tooltip/layout.test.jsx @@ -24,6 +24,7 @@ describe('Appointment Tooltip', () => { close: 'close', delete: 'delete', }, + formatDate: () => undefined, }; beforeAll(() => { classes = getClasses(); @@ -121,14 +122,20 @@ describe('Appointment Tooltip', () => { .toBeTruthy(); }); - it('should render appointment dates', () => { + it('should call dates format function', () => { + const dateTimeFormat = jest.fn(); const tree = shallow(( - + )); - const text = tree.find(defaultProps.contentComponent).find(`.${classes.text}`).text(); - expect(text) - .toEqual('10:00 AM - 11:00 AM'); + expect(dateTimeFormat) + .toBeCalledTimes(2); + expect(dateTimeFormat) + .toHaveBeenCalledWith(defaultProps.appointmentMeta.data.startDate, { hour: 'numeric', minute: 'numeric' }); + expect(dateTimeFormat) + .toHaveBeenCalledWith(defaultProps.appointmentMeta.data.endDate, { hour: 'numeric', minute: 'numeric' }); + expect(tree.find(`.${classes.text}`).props().children) + .toBeTruthy(); }); }); }); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/appointment/appointment-content.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/appointment/appointment-content.test.jsx index 34d31f44c7..ff80a3dce5 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/appointment/appointment-content.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/appointment/appointment-content.test.jsx @@ -11,6 +11,7 @@ describe('AppointmentContent', () => { data: {}, type: 'horizontal', recurringIconComponent: () =>
, + formatDate: () => undefined, }; beforeAll(() => { shallow = createShallow({ dive: true }); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/appointment/horizontal-appointment.jsx b/packages/dx-react-scheduler-material-ui/src/templates/appointment/horizontal-appointment.jsx index 2fc0ba9294..100da65f92 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/appointment/horizontal-appointment.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/appointment/horizontal-appointment.jsx @@ -39,6 +39,7 @@ const HorizontalAppointmentBase = ({ children, className, recurringIconComponent: RecurringIcon, + formatDate, ...restProps }) => { const repeat = !!data.rRule; @@ -69,9 +70,11 @@ HorizontalAppointmentBase.propTypes = { data: PropTypes.object.isRequired, children: PropTypes.node, className: PropTypes.string, + formatDate: PropTypes.func, }; HorizontalAppointmentBase.defaultProps = { + formatDate: () => '', children: undefined, className: undefined, }; diff --git a/packages/dx-react-scheduler-material-ui/src/templates/appointment/vertical-appointment.jsx b/packages/dx-react-scheduler-material-ui/src/templates/appointment/vertical-appointment.jsx index f351360af9..e1f1bc1d32 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/appointment/vertical-appointment.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/appointment/vertical-appointment.jsx @@ -1,8 +1,8 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; -import moment from 'moment'; import { withStyles } from '@material-ui/core/styles'; import classNames from 'classnames'; +import { HOUR_MINUTE_OPTIONS } from '@devexpress/dx-scheduler-core'; const styles = ({ palette, spacing }) => ({ title: { @@ -51,6 +51,7 @@ const VerticalAppointmentBase = ({ data, children, className, + formatDate, recurringIconComponent: RecurringIcon, ...restProps }) => { @@ -64,13 +65,13 @@ const VerticalAppointmentBase = ({
- {moment(data.startDate).format('h:mm A')} + {formatDate(data.startDate, HOUR_MINUTE_OPTIONS)}
{' - '}
- {moment(data.endDate).format('h:mm A')} + {formatDate(data.endDate, HOUR_MINUTE_OPTIONS)}
@@ -88,6 +89,7 @@ VerticalAppointmentBase.propTypes = { recurringIconComponent: PropTypes.func.isRequired, classes: PropTypes.object.isRequired, data: PropTypes.object.isRequired, + formatDate: PropTypes.func.isRequired, children: PropTypes.node, className: PropTypes.string, }; diff --git a/packages/dx-react-scheduler-material-ui/src/templates/appointment/vertical-appointment.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/appointment/vertical-appointment.test.jsx index 67b867aed9..163f2756b8 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/appointment/vertical-appointment.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/appointment/vertical-appointment.test.jsx @@ -10,6 +10,7 @@ describe('VerticalAppointment', () => { endDate: new Date('2018-07-27 17:10'), }, recurringIconComponent: () =>
, + formatDate: () => undefined, }; let classes; @@ -30,19 +31,24 @@ describe('VerticalAppointment', () => { .toBe('title'); }); - it('should render appointment times', () => { + it('should call time format function', () => { + const formatDate = jest.fn(); + formatDate.mockImplementation(() => 'time'); const tree = mount(( )); - expect(tree.find(`.${classes.time}`).at(0).text()) - .toBe('1:10 PM'); - expect(tree.find(`.${classes.time}`).at(1).text()) - .toBe(' - '); - expect(tree.find(`.${classes.time}`).at(2).text()) - .toBe('5:10 PM'); + expect(formatDate) + .toHaveBeenCalledWith(defaultProps.data.startDate, { hour: 'numeric', minute: 'numeric' }); + expect(formatDate) + .toHaveBeenCalledWith(defaultProps.data.startDate, { hour: 'numeric', minute: 'numeric' }); + expect(tree.find(`.${classes.time}`).at(0).props().children) + .toBeTruthy(); + expect(tree.find(`.${classes.time}`).at(2).props().children) + .toBeTruthy(); }); it('should render children', () => { diff --git a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/navigator.jsx b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/navigator.jsx index 111d03640e..44175d53e5 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/navigator.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/navigator.jsx @@ -18,6 +18,7 @@ const NavigatorBase = ({ textComponent: Text, navigationButtonComponent: NavigationButton, onNavigate, + formatDate, ...restProps }) => ( { onNavigate({ back: true }); }} /> - + { onNavigate({ back: false }); }} @@ -45,6 +46,7 @@ NavigatorBase.propTypes = { PropTypes.string, PropTypes.instanceOf(Date), ]).isRequired, + formatDate: PropTypes.func.isRequired, className: PropTypes.string, onNavigate: PropTypes.func, }; diff --git a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/navigator.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/navigator.test.jsx index bec580c49c..b0eb28edc2 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/navigator.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/navigator.test.jsx @@ -36,14 +36,17 @@ describe('Calendar', () => { .toMatchObject({ a: 1 }); }); it('should render text', () => { + const formatDate = jest.fn(); const text = shallow(( - + )).find(Text); expect(text.exists()) .toBeTruthy(); expect(text.prop('currentDate')) .toBe('2018-07-12'); + expect(text.prop('formatDate')) + .toBe(formatDate); }); it('should render navigation buttons', () => { const buttons = shallow(( diff --git a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/root.jsx b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/root.jsx index 0c0b56b46b..1434f48114 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/root.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/root.jsx @@ -38,6 +38,7 @@ export class Root extends React.PureComponent { headerRowComponent: HeaderRow, headerCellComponent: HeaderCell, onSelectedDateChange, + formatDate, ...restProps } = this.props; const { selectedDate: selectedDateState, currentDate } = this.state; @@ -51,6 +52,7 @@ export class Root extends React.PureComponent { textComponent={Text} navigationButtonComponent={NavigationButton} onNavigate={this.onNavigate} + formatDate={formatDate} /> ); @@ -81,6 +84,7 @@ Root.propTypes = { PropTypes.string, PropTypes.instanceOf(Date), ]).isRequired, + formatDate: PropTypes.func.isRequired, firstDayOfWeek: PropTypes.number, onSelectedDateChange: PropTypes.func, }; diff --git a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/root.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/root.test.jsx index 61a911467b..7c68a5cf4e 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/root.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/root.test.jsx @@ -19,6 +19,7 @@ describe('Calendar', () => { selectedDate: '2018-07-16', firstDayOfWeek: 1, getCells: () => [], + formatDate: () => undefined, }; describe('Root', () => { let mount; @@ -46,20 +47,23 @@ describe('Calendar', () => { .toMatchObject({ a: 1 }); }); it('should render navigator', () => { + const defaultFormatDate = jest.fn(); const navigator = mount(( - + )).find(defaultProps.navigatorComponent); const { currentDate, textComponent, navigationButtonComponent, + formatDate, } = navigator.props(); expect(navigator.exists()).toBeTruthy(); expect(currentDate).toBe('2018-07-16'); expect(textComponent).toBe(defaultProps.textComponent); expect(navigationButtonComponent).toBe(defaultProps.navigationButtonComponent); + expect(formatDate).toBe(defaultFormatDate); }); it('should navigate to the prev and next month', () => { const tree = mount(( @@ -80,13 +84,15 @@ describe('Calendar', () => { .toBe(defaultProps.selectedDate); }); it('should render calendar table', () => { - const tree = mount(); + const defaultFormatDate = jest.fn(); + const tree = mount(); const { cells, rowComponent, cellComponent, headerRowComponent, headerCellComponent, + formatDate, } = Table.mock.calls[0][0]; expect(tree.find('.table').exists()) @@ -101,6 +107,8 @@ describe('Calendar', () => { .toBe(defaultProps.headerRowComponent); expect(headerCellComponent) .toBe(defaultProps.headerCellComponent); + expect(formatDate) + .toBe(defaultFormatDate); }); it('should handle table cell click', () => { const onSelectedDateChangeMock = jest.fn(); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/table.jsx b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/table.jsx index aa57f9c04f..ef2780d6e1 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/table.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/table.jsx @@ -6,6 +6,7 @@ import TableMUI from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableHead from '@material-ui/core/TableHead'; import { withStyles } from '@material-ui/core/styles'; +import { WEEK_DAY_OPTIONS, DAY_OPTIONS } from '@devexpress/dx-scheduler-core'; const styles = { table: { @@ -25,6 +26,7 @@ const TableBase = ({ headerCells, selectedDate, onCellClick, + formatDate, ...restProps }) => { const comparableSelectedDate = moment(selectedDate); @@ -36,7 +38,7 @@ const TableBase = ({ {headerCells.map((cell) => { - const key = moment(cell.startDate).format('ddd'); + const key = formatDate(cell.startDate, WEEK_DAY_OPTIONS); return ( - {moment(startDate).format('D')} + {formatDate(startDate, DAY_OPTIONS)} ); })} @@ -91,6 +93,7 @@ TableBase.propTypes = { PropTypes.string, PropTypes.number, ]), + formatDate: PropTypes.func.isRequired, headerCells: PropTypes.array, className: PropTypes.string, onCellClick: PropTypes.func, diff --git a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/table.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/table.test.jsx index 16d3c59e61..4f515e7278 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/table.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/table.test.jsx @@ -19,6 +19,7 @@ describe('Calendar', () => { cellComponent: () =>
, headerCellComponent: () => , cells: [], + formatDate: () => '', }; let classes; let shallow; diff --git a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/text.jsx b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/text.jsx index 85a9ce2179..3955b5d0be 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/text.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/text.jsx @@ -3,7 +3,7 @@ import * as PropTypes from 'prop-types'; import { withStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import classNames from 'classnames'; -import moment from 'moment'; +import { MONTH_YEAR_OPTIONS } from '@devexpress/dx-scheduler-core'; const styles = { text: { @@ -16,6 +16,7 @@ const TextBase = ({ classes, className, currentDate, + formatDate, ...restProps }) => ( - {moment(currentDate).format('MMMM YYYY')} + {formatDate(currentDate, MONTH_YEAR_OPTIONS)} ); @@ -36,6 +37,7 @@ TextBase.propTypes = { PropTypes.string, PropTypes.instanceOf(Date), ]).isRequired, + formatDate: PropTypes.func.isRequired, className: PropTypes.string, }; diff --git a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/text.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/text.test.jsx index 8c15b8b327..355b3890f5 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/text.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/date-navigator/calendar/text.test.jsx @@ -9,6 +9,7 @@ describe('Calendar', () => { let shallow; const defaultProps = { currentDate: '2018-07-12', + formatDate: () => '', }; beforeAll(() => { classes = getClasses(); @@ -36,13 +37,17 @@ describe('Calendar', () => { expect(tree.props().data) .toMatchObject({ a: 1 }); }); - it('should render current date', () => { - const currentDate = shallow(( - - )).dive().dive().text(); + it('should call date format function', () => { + const formatDate = jest.fn(); + formatDate.mockImplementation(() => 'time'); + const tree = shallow(( + + )); - expect(currentDate) - .toBe('July 2018'); + expect(formatDate) + .toHaveBeenCalledWith(defaultProps.currentDate, { month: 'long', year: 'numeric' }); + expect(tree.props().children) + .toBeTruthy(); }); }); }); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/common/day-scale/layout.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/common/day-scale/layout.jsx index d232bdaa29..f0ad6153f0 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/common/day-scale/layout.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/common/day-scale/layout.jsx @@ -17,6 +17,7 @@ const LayoutBase = ({ cellsData, className, classes, + formatDate, ...restProps }) => ( ))} @@ -47,6 +49,7 @@ LayoutBase.propTypes = { cellsData: PropTypes.arrayOf(Array).isRequired, cellComponent: PropTypes.func.isRequired, rowComponent: PropTypes.func.isRequired, + formatDate: PropTypes.func.isRequired, className: PropTypes.string, }; LayoutBase.defaultProps = { diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/day-scale/cell.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/day-scale/cell.jsx index 84758f56c7..c8f47ffd9d 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/day-scale/cell.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/day-scale/cell.jsx @@ -1,9 +1,9 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; -import moment from 'moment'; import classNames from 'classnames'; import TableCell from '@material-ui/core/TableCell'; import { withStyles } from '@material-ui/core/styles'; +import { WEEK_DAY_OPTIONS } from '@devexpress/dx-scheduler-core'; import { getBorder } from '../../../utils'; const styles = theme => ({ @@ -24,23 +24,22 @@ const CellBase = ({ startDate, endDate, today, + formatDate, ...restProps -}) => { - const currentDate = moment(startDate); - return ( - -
- {currentDate.format('ddd')} -
-
- ); -}; +}) => ( + +
+ {formatDate(startDate, WEEK_DAY_OPTIONS)} +
+
+); CellBase.propTypes = { classes: PropTypes.object.isRequired, + formatDate: PropTypes.func.isRequired, startDate: PropTypes.instanceOf(Date).isRequired, endDate: PropTypes.instanceOf(Date), className: PropTypes.string, diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/day-scale/cell.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/day-scale/cell.test.jsx index 0a76291141..41cb053d1a 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/day-scale/cell.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/day-scale/cell.test.jsx @@ -5,6 +5,7 @@ import { Cell } from './cell'; describe('Horizontal view DayScale', () => { const defaultProps = { startDate: new Date(2018, 6, 7, 16, 20), + formatDate: () => undefined, }; let classes; let shallow; @@ -31,5 +32,17 @@ describe('Horizontal view DayScale', () => { expect(tree.props().data) .toMatchObject({ a: 1 }); }); + it('should call formatDate function', () => { + const formatDate = jest.fn(); + formatDate.mockImplementation(() => 'time'); + const tree = shallow(( + + )); + + expect(formatDate) + .toHaveBeenCalledWith(defaultProps.startDate, { weekday: 'short' }); + expect(tree.find(`.${classes.dayOfWeek}`).props().children) + .toBeTruthy(); + }); }); }); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/cell.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/cell.jsx index 77a85dcea8..384f01c94d 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/cell.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/cell.jsx @@ -1,10 +1,10 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; import classNames from 'classnames'; -import moment from 'moment'; import TableCell from '@material-ui/core/TableCell'; import { fade } from '@material-ui/core/styles/colorManipulator'; import { withStyles } from '@material-ui/core/styles'; +import { DAY_OPTIONS } from '@devexpress/dx-scheduler-core'; import { getBorder } from '../../../utils'; const styles = theme => ({ @@ -48,6 +48,7 @@ const CellBase = ({ endDate, today, otherMonth, + formatDate, ...restProps }) => ( - {moment(startDate).format('D')} + {formatDate(startDate, DAY_OPTIONS)} ); CellBase.propTypes = { classes: PropTypes.object.isRequired, - className: PropTypes.string, + formatDate: PropTypes.func.isRequired, startDate: PropTypes.instanceOf(Date).isRequired, endDate: PropTypes.instanceOf(Date), + className: PropTypes.string, today: PropTypes.bool, otherMonth: PropTypes.bool, }; diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/cell.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/cell.test.jsx index 6a77bf7482..9930209f32 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/cell.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/cell.test.jsx @@ -5,6 +5,7 @@ import { Cell } from './cell'; describe('Horizontal view TimeTable', () => { const defaultProps = { startDate: new Date(2018, 6, 7, 16), + formatDate: () => undefined, }; let classes; let shallow; @@ -66,4 +67,16 @@ describe('Horizontal view TimeTable', () => { expect(tree.find(`.${classes.otherMonth}`)) .toHaveLength(1); }); + it('should call date format function', () => { + const formatDate = jest.fn(); + formatDate.mockImplementation(() => 'time'); + const tree = shallow(( + + )); + + expect(formatDate) + .toHaveBeenCalledWith(defaultProps.startDate, { day: 'numeric' }); + expect(tree.find(`.${classes.text}`).props().children) + .toBeTruthy(); + }); }); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/layout.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/layout.jsx index 57013ebc5c..ef3feb1e83 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/layout.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/layout.jsx @@ -19,6 +19,7 @@ const LayoutBase = ({ tableRef, className, cellsData, + formatDate, ...restProps }) => ( @@ -41,6 +42,7 @@ const LayoutBase = ({ endDate={endDate} today={today} otherMonth={otherMonth} + formatDate={formatDate} /> ))} @@ -56,6 +58,7 @@ LayoutBase.propTypes = { classes: PropTypes.object.isRequired, cellComponent: PropTypes.func.isRequired, rowComponent: PropTypes.func.isRequired, + formatDate: PropTypes.func.isRequired, className: PropTypes.string, }; LayoutBase.defaultProps = { diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/layout.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/layout.test.jsx index 7325b1ebad..730658cd5d 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/layout.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/horizontal/time-table/layout.test.jsx @@ -17,6 +17,7 @@ describe('Horizontal view TimeTable', () => { ], cellComponent: () => undefined, rowComponent: () => undefined, + formatDate: () => undefined, }; let classes; let shallow; diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/day-scale/cell.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/day-scale/cell.jsx index 3a62a35ed4..54fd25f6bd 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/day-scale/cell.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/day-scale/cell.jsx @@ -1,9 +1,9 @@ import * as React from 'react'; import * as PropTypes from 'prop-types'; -import moment from 'moment'; import classNames from 'classnames'; import TableCell from '@material-ui/core/TableCell'; import { withStyles } from '@material-ui/core/styles'; +import { WEEK_DAY_OPTIONS, DAY_OPTIONS } from '@devexpress/dx-scheduler-core'; import { getBorder } from '../../../utils'; const styles = theme => ({ @@ -29,36 +29,35 @@ const CellBase = ({ startDate, endDate, today, + formatDate, ...restProps -}) => { - const currentDate = moment(startDate); - return ( - ( + +

-

- {currentDate.format('ddd')} -

- - {currentDate.format('D')} - -
- ); -}; + {formatDate(startDate, WEEK_DAY_OPTIONS)} +

+ + {formatDate(startDate, DAY_OPTIONS)} + +
+); CellBase.propTypes = { classes: PropTypes.object.isRequired, + formatDate: PropTypes.func.isRequired, startDate: PropTypes.instanceOf(Date).isRequired, endDate: PropTypes.instanceOf(Date), className: PropTypes.string, diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/day-scale/cell.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/day-scale/cell.test.jsx index 07b85878e6..cbfcbe2e7c 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/day-scale/cell.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/day-scale/cell.test.jsx @@ -5,6 +5,7 @@ import { Cell } from './cell'; describe('Vertical view DayPanel', () => { const defaultProps = { startDate: new Date(2018, 6, 7, 16, 20), + formatDate: () => undefined, }; let classes; let shallow; @@ -41,5 +42,21 @@ describe('Vertical view DayPanel', () => { expect(tree.find(`span.${classes.highlightCell}`).exists()) .toBeTruthy(); }); + it('should call formatDate function', () => { + const formatDate = jest.fn(); + formatDate.mockImplementation(() => 'time'); + const tree = shallow(( + + )); + + expect(formatDate) + .toHaveBeenCalledWith(defaultProps.startDate, { weekday: 'short' }); + expect(formatDate) + .toHaveBeenCalledWith(defaultProps.startDate, { day: 'numeric' }); + expect(tree.find(`.${classes.dayOfWeek}`).props().children) + .toBeTruthy(); + expect(tree.find(`.${classes.dayOfMonth}`).props().children) + .toBeTruthy(); + }); }); }); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/cell.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/cell.jsx index 0b899282ef..c0a73ac5d7 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/cell.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/cell.jsx @@ -3,7 +3,7 @@ import * as PropTypes from 'prop-types'; import classNames from 'classnames'; import TableCell from '@material-ui/core/TableCell'; import { withStyles } from '@material-ui/core/styles'; -import moment from 'moment'; +import { HOUR_MINUTE_OPTIONS } from '@devexpress/dx-scheduler-core'; const styles = theme => ({ cell: { @@ -29,22 +29,21 @@ const CellBase = ({ className, startDate, endDate, + formatDate, ...restProps -}) => { - const currentTime = moment(endDate); - return ( - - - {currentTime.format('h:mm A')} - - - ); -}; +}) => ( + + + {formatDate(endDate, HOUR_MINUTE_OPTIONS)} + + +); CellBase.propTypes = { + formatDate: PropTypes.func.isRequired, endDate: PropTypes.instanceOf(Date).isRequired, startDate: PropTypes.instanceOf(Date), classes: PropTypes.object.isRequired, diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/cell.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/cell.test.jsx index ffd1d31123..743ce9c3f7 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/cell.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/cell.test.jsx @@ -5,6 +5,7 @@ import { Cell } from './cell'; describe('Vertical view TimePanel', () => { const defaultProps = { endDate: new Date(2018, 6, 7, 16, 20), + formatDate: () => undefined, }; let classes; let shallow; @@ -31,5 +32,17 @@ describe('Vertical view TimePanel', () => { expect(tree.props().data) .toMatchObject({ a: 1 }); }); + it('should call date format function', () => { + const formatDate = jest.fn(); + formatDate.mockImplementation(() => 'time'); + const tree = shallow(( + + )); + + expect(formatDate) + .toHaveBeenCalledWith(defaultProps.endDate, { hour: 'numeric', minute: 'numeric' }); + expect(tree.find(`.${classes.text}`).props().children) + .toBeTruthy(); + }); }); }); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/layout.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/layout.jsx index a40c12db73..6d437ca84d 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/layout.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/layout.jsx @@ -17,6 +17,7 @@ const LayoutBase = ({ cellsData, classes, className, + formatDate, ...restProps }) => ( @@ -30,6 +31,7 @@ const LayoutBase = ({ rowSpan="2" startDate={days[0].startDate} endDate={days[0].endDate} + formatDate={formatDate} /> )} @@ -43,6 +45,7 @@ LayoutBase.propTypes = { cellsData: PropTypes.arrayOf(Array).isRequired, cellComponent: PropTypes.func.isRequired, rowComponent: PropTypes.func.isRequired, + formatDate: PropTypes.func.isRequired, className: PropTypes.string, }; LayoutBase.defaultProps = { diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/layout.test.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/layout.test.jsx index 476fdf77bd..01506fcb14 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/layout.test.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-scale/layout.test.jsx @@ -18,6 +18,7 @@ describe('Vertical view TimePanel', () => { ], cellComponent: () => undefined, rowComponent: () => undefined, + formatDate: () => undefined, }; beforeAll(() => { classes = getClasses(); diff --git a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-table/layout.jsx b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-table/layout.jsx index fccfb2e28d..e7ba787682 100644 --- a/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-table/layout.jsx +++ b/packages/dx-react-scheduler-material-ui/src/templates/views/vertical/time-table/layout.jsx @@ -18,6 +18,7 @@ const LayoutBase = ({ cellComponent: Cell, rowComponent: Row, cellsData, + formatDate, ...restProps }) => ( @@ -48,6 +49,7 @@ LayoutBase.propTypes = { cellsData: PropTypes.arrayOf(Array).isRequired, cellComponent: PropTypes.func.isRequired, rowComponent: PropTypes.func.isRequired, + formatDate: PropTypes.func.isRequired, className: PropTypes.string, }; LayoutBase.defaultProps = { diff --git a/packages/dx-react-scheduler/api/dx-react-scheduler.api.ts b/packages/dx-react-scheduler/api/dx-react-scheduler.api.ts index 7425e95e51..cfd694b2bf 100644 --- a/packages/dx-react-scheduler/api/dx-react-scheduler.api.ts +++ b/packages/dx-react-scheduler/api/dx-react-scheduler.api.ts @@ -167,6 +167,7 @@ namespace Appointments { interface AppointmentContentProps { children: React.ReactNode; data: object; + formatDate: (date: Date, options: any) => string; recurringIconComponent: React.ComponentType; } } @@ -205,6 +206,7 @@ namespace AppointmentTooltip { commandButtonComponent: React.ComponentType; commandButtonIds: Array; contentComponent: React.ComponentType; + formatDate: (date: Date, options: any) => string; headerComponent: React.ComponentType; onDeleteButtonClick?: () => void; onHide?: () => void; @@ -339,6 +341,7 @@ namespace DayView { interface TimeScaleLayoutProps { cellComponent: React.ComponentType; cellsData: Array>; + formatDate: (date: Date, options: any) => string; rowComponent: React.ComponentType; } } @@ -347,6 +350,7 @@ namespace DayView { namespace DayView { interface TimeScaleCellProps { endDate: Date; + formatDate: (date: Date, options: any) => string; startDate?: Date; } } @@ -356,6 +360,7 @@ namespace DayView { interface DayScaleLayoutProps { cellComponent: React.ComponentType; cellsData: Array>; + formatDate: (date: Date, options: any) => string; rowComponent: React.ComponentType; } } @@ -364,6 +369,7 @@ namespace DayView { namespace DayView { interface DayScaleCellProps { endDate?: Date; + formatDate: (date: Date, options: any) => string; startDate: Date; today?: boolean; } @@ -381,6 +387,7 @@ namespace DayView { interface TimeTableLayoutProps { cellComponent: React.ComponentType; cellsData: Array>; + formatDate: (date: Date, options: any) => string; rowComponent: React.ComponentType; tableRef: (ref: React.ReactInstance) => void; } @@ -523,6 +530,7 @@ namespace MonthView { interface DayScaleLayoutProps { cellComponent: React.ComponentType; cellsData: Array>; + formatDate: (date: Date, options: any) => string; rowComponent: React.ComponentType; } } @@ -531,6 +539,7 @@ namespace MonthView { namespace MonthView { interface DayScaleCellProps { endDate?: Date; + formatDate: (date: Date, options: any) => string; startDate: Date; } } @@ -540,6 +549,7 @@ namespace MonthView { interface TimeTableLayoutProps { cellComponent: React.ComponentType; cellsData: Array>; + formatDate: (date: Date, options: any) => string; rowComponent: React.ComponentType; tableRef: (ref: React.ReactInstance) => void; } @@ -549,6 +559,7 @@ namespace MonthView { namespace MonthView { interface TimeTableCellProps { endDate?: Date; + formatDate: (date: Date, options: any) => string; otherMonth?: boolean; startDate: Date; today?: boolean; @@ -600,6 +611,7 @@ declare const Scheduler: React.ComponentType; // @public (undocumented) interface SchedulerProps { data: Array; + locale: string; rootComponent: React.ComponentType; } @@ -680,6 +692,7 @@ namespace WeekView { interface TimeScaleLayoutProps { cellComponent: React.ComponentType; cellsData: Array>; + formatDate: (date: Date, options: any) => string; rowComponent: React.ComponentType; } } @@ -688,6 +701,7 @@ namespace WeekView { namespace WeekView { interface TimeScaleCellProps { endDate: Date; + formatDate: (date: Date, options: any) => string; startDate?: Date; } } @@ -697,6 +711,7 @@ namespace WeekView { interface DayScaleLayoutProps { cellComponent: React.ComponentType; cellsData: Array>; + formatDate: (date: Date, options: any) => string; rowComponent: React.ComponentType; } } @@ -705,6 +720,7 @@ namespace WeekView { namespace WeekView { interface DayScaleCellProps { endDate?: Date; + formatDate: (date: Date, options: any) => string; startDate: Date; today?: boolean; } @@ -722,6 +738,7 @@ namespace WeekView { interface TimeTableLayoutProps { cellComponent: React.ComponentType; cellsData: Array>; + formatDate: (date: Date, options: any) => string; rowComponent: React.ComponentType; tableRef: (ref: React.ReactInstance) => void; } diff --git a/packages/dx-react-scheduler/docs/guides/localization.md b/packages/dx-react-scheduler/docs/guides/localization.md new file mode 100644 index 0000000000..762cedc1d0 --- /dev/null +++ b/packages/dx-react-scheduler/docs/guides/localization.md @@ -0,0 +1,24 @@ +# React Scheduler - Localization + +The React Scheduler provides an API that allows you to format dates and localize messages. + +**Format Dates** + +Scheduler UI plugins can display dates that are formatted depending on the locale. To specify the locale, assign one of the [supported formats](https://tools.ietf.org/html/rfc5646) to the [Scheduler](../reference/scheduler.md) component's `locale` property. + +**Localize Messages** + +Most UI plugins have the `messages` option that accepts localized messages. + +## Basic Setup + +The following example demonstrates how to localize the Scheduler by specifying the `messages` and `locale` properties. + +.embedded-demo({ "path": "scheduler-localization/locale", "showThemeSelector": true }) + +## Customize Date Formatting + +To customize how dates are formatted in a component, [override the component](./fundamentals.md#customize-the-appearance) and set the `formatDate` property to a function that formats dates. The following example illustrates these actions. Note that in this example, the [Moment.js](https://momentjs.com/) library is used to work with dates. + +.embedded-demo({ "path": "scheduler-localization/custom-formatting", "showThemeSelector": true }) + diff --git a/packages/dx-react-scheduler/docs/reference/appointment-tooltip.md b/packages/dx-react-scheduler/docs/reference/appointment-tooltip.md index 133b0c476d..3305e71ad7 100644 --- a/packages/dx-react-scheduler/docs/reference/appointment-tooltip.md +++ b/packages/dx-react-scheduler/docs/reference/appointment-tooltip.md @@ -60,6 +60,7 @@ showOpenButton | boolean | Specifies the Open button's visibility. showCloseButton | boolean | Specifies the Close button's visibility. showDeleteButton | boolean | Specifies the Delete button's visibility. commandButtonIds | Array<string> | A command button's identifier list. +formatDate | (date: Date, options: any) => string | A function that formats dates according to the locale. onOpenButtonClick? | () => void | An event raised when the Open button is clicked. The event handler should open the appointment form. onDeleteButtonClick? | () => void | An event raised when the Open button is clicked. The event handler should delete an appointment. appointmentMeta? | [AppointmentMeta](#appointmentmeta) | The appointment's displayed metadata. diff --git a/packages/dx-react-scheduler/docs/reference/appointments.md b/packages/dx-react-scheduler/docs/reference/appointments.md index aaa9e4487b..a5d1711230 100644 --- a/packages/dx-react-scheduler/docs/reference/appointments.md +++ b/packages/dx-react-scheduler/docs/reference/appointments.md @@ -57,6 +57,7 @@ Field | Type | Description ------|------|------------ children | ReactNode | A React node used to render the appointment content. data | object | An object that represents appointment data. +formatDate | (date: Date, options: any) => string | A function that formats dates according to the locale. recurringIconComponent | ComponentType<object> | A component that renders an icon for recurring appointments. ### Appointments.SplitIndicatorProps diff --git a/packages/dx-react-scheduler/docs/reference/day-view.md b/packages/dx-react-scheduler/docs/reference/day-view.md index bba024f5dd..5f5de375f4 100644 --- a/packages/dx-react-scheduler/docs/reference/day-view.md +++ b/packages/dx-react-scheduler/docs/reference/day-view.md @@ -76,6 +76,7 @@ Field | Type | Description cellsData | Array<Array<[DayView.CellData](#dayviewcelldata)>> | Specifies the cells meta data. cellComponent | ComponentType<[DayView.TimeScaleCellProps](#dayviewtimescalecellprops)> | A component that renders a time scale cell. rowComponent | ComponentType<[DayView.RowProps](#dayviewrowprops)> | A component that renders a time scale row. +formatDate | (date: Date, options: any) => string | A function that formats dates according to the locale. ### DayView.TimeScaleCellProps @@ -85,6 +86,7 @@ Field | Type | Description ------|------|------------ endDate | Date | Specifies the cell end time. startDate? | Date | Specifies the cell start time. +formatDate | (date: Date, options: any) => string | A function that formats dates according to the locale. ### DayView.DayScaleLayoutProps @@ -95,6 +97,7 @@ Field | Type | Description cellsData | Array<Array<[DayView.CellData](#dayviewcelldata)>> | Specifies the cells meta data. cellComponent | ComponentType<[DayView.DayScaleCellProps](#dayviewdayscalecellprops)> | A component that renders a day scale cell. rowComponent | ComponentType<[DayView.RowProps](#dayviewrowprops)> | A component that renders a day scale row. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### DayView.DayScaleCellProps @@ -105,6 +108,7 @@ Field | Type | Description startDate | Date | Specifies the cell end time. endDate? | Date | Specifies the cell start time. today? | boolean | Indicates whether the cell's date is today. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### DayView.DayScaleEmptyCellProps @@ -124,6 +128,7 @@ cellsData | Array<Array<[DayView.CellData](#dayviewcelldata)>> | Spe tableRef | (ref: ReactInstance) => void | A function that accepts the table root React element. cellComponent | ComponentType<[DayView.TimeTableCellProps](#dayviewtimetablecellprops)> | A component that renders a time table cell. rowComponent | ComponentType<[DayView.RowProps](#dayviewrowprops)> | A component that renders a time table row. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### DayView.TimeTableCellProps diff --git a/packages/dx-react-scheduler/docs/reference/month-view.md b/packages/dx-react-scheduler/docs/reference/month-view.md index 6a0c915c83..21bdd38302 100644 --- a/packages/dx-react-scheduler/docs/reference/month-view.md +++ b/packages/dx-react-scheduler/docs/reference/month-view.md @@ -69,6 +69,7 @@ Field | Type | Description cellsData | Array<Array<[MonthView.CellData](#monthviewcelldata)>> | Specifies the cells meta data. cellComponent | ComponentType<[MonthView.DayScaleCellProps](#monthviewdayscalecellprops)> | A component that renders a day scale cell. rowComponent | ComponentType<[MonthView.RowProps](#monthviewrowprops)> | A component that renders a day scale row. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### MonthView.DayScaleCellProps @@ -78,6 +79,7 @@ Field | Type | Description ------|------|------------ startDate | Date | Specifies the cell start time. endDate? | Date | Specifies the cell end time. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### MonthView.TimeTableLayoutProps @@ -89,6 +91,7 @@ cellsData | Array<Array<[MonthView.CellData](#monthviewcelldata)>> | tableRef | (ref: ReactInstance) => void | A function that accepts the table's root React element. cellComponent | ComponentType<[MonthView.TimeTableCellProps](#monthviewtimetablecellprops)> | A component that renders a time table cell. rowComponent | ComponentType<[MonthView.RowProps](#monthviewrowprops)> | A component that renders a time table row. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### MonthView.TimeTableCellProps @@ -100,6 +103,7 @@ startDate | Date | Specifies the cell start time. endDate? | Date | Specifies the cell end time. otherMonth? | boolean | Indicates whether the cell's date is not in the current month. today? | boolean | Indicates whether the cell's date is today. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### MonthView.AppointmentLayerProps diff --git a/packages/dx-react-scheduler/docs/reference/scheduler.md b/packages/dx-react-scheduler/docs/reference/scheduler.md index 4c8e29faf2..0271bab4e0 100644 --- a/packages/dx-react-scheduler/docs/reference/scheduler.md +++ b/packages/dx-react-scheduler/docs/reference/scheduler.md @@ -24,6 +24,7 @@ Name | Type | Default | Description -----|------|---------|------------ data | Array<[AppointmentModel](#appointmentmodel)> | | An array of appointment data objects. rootComponent | ComponentType<[Scheduler.RootProps](#schedulerrootprops)> | | A component that renders the root layout. +locale | string | 'en-US' | The locale according to which dates should be formatted. ## Interfaces diff --git a/packages/dx-react-scheduler/docs/reference/week-view.md b/packages/dx-react-scheduler/docs/reference/week-view.md index a4a70d56c5..9ee29c1e10 100644 --- a/packages/dx-react-scheduler/docs/reference/week-view.md +++ b/packages/dx-react-scheduler/docs/reference/week-view.md @@ -78,6 +78,7 @@ Field | Type | Description cellsData | Array<Array<[WeekView.CellData](#weekviewcelldata)>> | Specifies the cells meta data. cellComponent | ComponentType<[WeekView.TimeScaleCellProps](#weekviewtimescalecellprops)> | A component that renders a time scale cell. rowComponent | ComponentType<[WeekView.RowProps](#weekviewrowprops)> | A component that renders a time scale row. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### WeekView.TimeScaleCellProps @@ -87,6 +88,7 @@ Field | Type | Description ------|------|------------ endDate | Date | Specifies the cell end time. startDate? | Date | Specifies the cell start time. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### WeekView.DayScaleLayoutProps @@ -97,6 +99,7 @@ Field | Type | Description cellsData | Array<Array<[WeekView.CellData](#weekviewcelldata)>> | Specifies the cells meta data. cellComponent | ComponentType<[WeekView.DayScaleCellProps](#weekviewdayscalecellprops)> | A component that renders a day scale cell. rowComponent | ComponentType<[WeekView.RowProps](#weekviewrowprops)> | A component that renders a day scale row. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### WeekView.DayScaleCellProps @@ -107,6 +110,7 @@ Field | Type | Description startDate | Date | Specifies the cell start time. endDate? | Date | Specifies the cell end time. today? | boolean | Indicates whether the cell's date is today. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### WeekView.DayScaleEmptyCellProps @@ -126,6 +130,7 @@ cellsData | Array<Array<[WeekView.CellData](#weekviewcelldata)>> | S tableRef | (ref: ReactInstance) => void | A function that accepts the table root React element. cellComponent | ComponentType<[WeekView.TimeTableCellProps](#weekviewtimetablecellprops)> | A component that renders a time table cell. rowComponent | ComponentType<[WeekView.RowProps](#weekviewrowprops)> | A component that renders a time table row. +formatDate | (date: Date, options: any) => string | A function that format a date by locale. ### WeekView.TimeTableCellProps diff --git a/packages/dx-react-scheduler/src/plugins/appointment-tooltip.jsx b/packages/dx-react-scheduler/src/plugins/appointment-tooltip.jsx index 1f928aa5a2..cfb0e14130 100644 --- a/packages/dx-react-scheduler/src/plugins/appointment-tooltip.jsx +++ b/packages/dx-react-scheduler/src/plugins/appointment-tooltip.jsx @@ -93,7 +93,9 @@ export class AppointmentTooltip extends React.PureComponent { diff --git a/packages/dx-react-scheduler/src/plugins/appointments.test.jsx b/packages/dx-react-scheduler/src/plugins/appointments.test.jsx index a3b2e87d80..bf0a25a8e2 100644 --- a/packages/dx-react-scheduler/src/plugins/appointments.test.jsx +++ b/packages/dx-react-scheduler/src/plugins/appointments.test.jsx @@ -28,6 +28,9 @@ jest.mock('@devexpress/dx-core', () => ({ })); const defaultDeps = { + getter: { + formatDate: jest.fn(), + }, template: { appointment: { type: 'horizontal', @@ -95,7 +98,8 @@ describe('Appointments', () => { const appointmentContent = tree.find(AppointmentContent); const { data: appointmentData } = appointment.props(); const { - type, data: appointmentContentData, recurringIconComponent, + type, data: appointmentContentData, + recurringIconComponent, formatDate, } = appointmentContent.props(); expect(appointment).toHaveLength(1); @@ -113,6 +117,7 @@ describe('Appointments', () => { }); expect(recurringIconComponent).toBe(defaultProps.recurringIconComponent); + expect(formatDate).toBe(defaultDeps.getter.formatDate); }); it('should pass correct event handlers', () => { const appointment = mount(( diff --git a/packages/dx-react-scheduler/src/plugins/date-navigator.jsx b/packages/dx-react-scheduler/src/plugins/date-navigator.jsx index 0815308be6..fa3757b63a 100644 --- a/packages/dx-react-scheduler/src/plugins/date-navigator.jsx +++ b/packages/dx-react-scheduler/src/plugins/date-navigator.jsx @@ -80,6 +80,7 @@ export class DateNavigator extends React.PureComponent { firstDayOfWeek, currentView, intervalCount, + formatDate, }, { changeCurrentDate, }) => { @@ -94,6 +95,7 @@ export class DateNavigator extends React.PureComponent { currentView, currentDate, intervalCount, + formatDate, ); return ( @@ -122,6 +124,7 @@ export class DateNavigator extends React.PureComponent { headerCellComponent={CalendarHeaderCell} navigatorComponent={CalendarNavigator} onSelectedDateChange={calendarDateChanged} + formatDate={formatDate} /> diff --git a/packages/dx-react-scheduler/src/plugins/date-navigator.test.jsx b/packages/dx-react-scheduler/src/plugins/date-navigator.test.jsx index ee0c2ff895..4af4ca246f 100644 --- a/packages/dx-react-scheduler/src/plugins/date-navigator.test.jsx +++ b/packages/dx-react-scheduler/src/plugins/date-navigator.test.jsx @@ -16,6 +16,7 @@ const defaultDeps = { firstDayOfWeek: 1, currentView: { type: 'month' }, intervalCount: 3, + formatDate: jest.fn(), }, action: { changeCurrentDate: jest.fn(), @@ -131,33 +132,27 @@ describe('DateNavigator', () => { const calendar = tree.find(CalendarComponent); const dateNavigator = tree.find(DateNavigator); dateNavigator.instance().setState({ visible: true }); - const { - selectedDate, - firstDayOfWeek, - textComponent, - navigationButtonComponent, - rowComponent, - cellComponent, - headerRowComponent, - headerCellComponent, - navigatorComponent, - onSelectedDateChange, - } = calendar.props(); + const { onSelectedDateChange } = calendar.props(); onSelectedDateChange(); expect(calendar.exists()).toBeTruthy(); - expect(selectedDate).toBe('2018-07-05'); - expect(firstDayOfWeek).toBe(1); - expect(textComponent).toBe(CalendarTextComponent); - expect(navigationButtonComponent).toBe(CalendarNavigationButtonComponent); - expect(rowComponent).toBe(CalendarRow); - expect(cellComponent).toBe(CalendarCell); - expect(headerRowComponent).toBe(CalendarHeaderRow); - expect(headerCellComponent).toBe(CalendarHeaderCell); - expect(navigatorComponent).toBe(CalendarNavigatorComponent); expect(defaultDeps.action.changeCurrentDate).toHaveBeenCalled(); expect(dateNavigator.instance().state.visible).toBeFalsy(); + + expect(calendar.props()) + .toMatchObject({ + selectedDate: '2018-07-05', + firstDayOfWeek: 1, + formatDate: defaultDeps.getter.formatDate, + textComponent: CalendarTextComponent, + navigationButtonComponent: CalendarNavigationButtonComponent, + rowComponent: CalendarRow, + cellComponent: CalendarCell, + headerRowComponent: CalendarHeaderRow, + headerCellComponent: CalendarHeaderCell, + navigatorComponent: CalendarNavigatorComponent, + }); }); it('should calculate calendar cells via the "monthCells" computed', () => { diff --git a/packages/dx-react-scheduler/src/plugins/day-view.jsx b/packages/dx-react-scheduler/src/plugins/day-view.jsx index 8065e1f409..61c61777ff 100644 --- a/packages/dx-react-scheduler/src/plugins/day-view.jsx +++ b/packages/dx-react-scheduler/src/plugins/day-view.jsx @@ -162,13 +162,14 @@ export class DayView extends React.PureComponent {