Skip to content

Commit f54fe98

Browse files
authored
feat: support partial i18n for date time picker (#10125)
1 parent 0351925 commit f54fe98

File tree

5 files changed

+150
-97
lines changed

5 files changed

+150
-97
lines changed

packages/date-time-picker/src/vaadin-date-time-picker-mixin.d.ts

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import type { Constructor } from '@open-wc/dedupe-mixin';
77
import type { DisabledMixinClass } from '@vaadin/a11y-base/src/disabled-mixin.js';
88
import type { FocusMixinClass } from '@vaadin/a11y-base/src/focus-mixin.js';
9+
import type { I18nMixinClass } from '@vaadin/component-base/src/i18n-mixin.js';
910
import type { DatePickerI18n } from '@vaadin/date-picker/src/vaadin-date-picker.js';
1011
import type { FieldMixinClass } from '@vaadin/field-base/src/field-mixin.js';
1112
import type { LabelMixinClass } from '@vaadin/field-base/src/label-mixin.js';
@@ -19,15 +20,15 @@ export interface DateTimePickerI18n extends DatePickerI18n, TimePickerI18n {
1920
* If both properties are defined, then accessibleName takes precedence.
2021
* Then, the dateLabel value is concatenated with it.
2122
*/
22-
dateLabel: string | null | undefined;
23+
dateLabel?: string;
2324

2425
/**
2526
* Accessible label to the time picker.
2627
* The property works in conjunction with label and accessibleName defined on the field.
2728
* If both properties are defined, then accessibleName takes precedence.
2829
* Then, the dateLabel value is concatenated with it.
2930
*/
30-
timeLabel: string | null | undefined;
31+
timeLabel?: string;
3132
}
3233

3334
/**
@@ -39,6 +40,7 @@ export declare function DateTimePickerMixin<T extends Constructor<HTMLElement>>(
3940
Constructor<DisabledMixinClass> &
4041
Constructor<FieldMixinClass> &
4142
Constructor<FocusMixinClass> &
43+
Constructor<I18nMixinClass<DateTimePickerI18n>> &
4244
Constructor<LabelMixinClass> &
4345
Constructor<ValidateMixinClass> &
4446
T;
@@ -142,13 +144,31 @@ export declare class DateTimePickerMixinClass {
142144
autofocus: boolean;
143145

144146
/**
145-
* The object used to localize this component.
146-
* To change the default localization, replace the entire
147-
* `i18n` object or just the properties you want to modify.
147+
* The object used to localize this component. To change the default
148+
* localization, replace this with an object that provides all properties, or
149+
* just the individual properties you want to change.
148150
*
149-
* The object is a combination of the i18n properties supported by
151+
* The object has the following structure and default values:
152+
*
153+
* ```
154+
* {
155+
* // Accessible label to the date picker.
156+
* // The property works in conjunction with label and accessibleName defined on the field.
157+
* // If both properties are defined, then accessibleName takes precedence.
158+
* // Then, the dateLabel value is concatenated with it.
159+
* dateLabel: undefined;
160+
*
161+
* // Accessible label to the time picker.
162+
* // The property works in conjunction with label and accessibleName defined on the field.
163+
* // If both properties are defined, then accessibleName takes precedence.
164+
* // Then, the dateLabel value is concatenated with it.
165+
* timeLabel: undefined;
166+
* }
167+
* ```
168+
*
169+
* Additionally, all i18n properties from
150170
* [`<vaadin-date-picker>`](#/elements/vaadin-date-picker) and
151-
* [`<vaadin-time-picker>`](#/elements/vaadin-time-picker).
171+
* [`<vaadin-time-picker>`](#/elements/vaadin-time-picker) are supported.
152172
*/
153173
i18n: DateTimePickerI18n;
154174
}

packages/date-time-picker/src/vaadin-date-time-picker-mixin.js

Lines changed: 57 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
import { DisabledMixin } from '@vaadin/a11y-base/src/disabled-mixin.js';
77
import { FocusMixin } from '@vaadin/a11y-base/src/focus-mixin.js';
8+
import { I18nMixin } from '@vaadin/component-base/src/i18n-mixin.js';
89
import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
910
import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js';
1011
import {
@@ -21,6 +22,8 @@ import { timePickerI18nDefaults } from '@vaadin/time-picker/src/vaadin-time-pick
2122
const datePickerI18nProps = Object.keys(datePickerI18nDefaults);
2223
const timePickerI18nProps = Object.keys(timePickerI18nDefaults);
2324

25+
const DEFAULT_I18N = { ...datePickerI18nDefaults, ...timePickerI18nDefaults };
26+
2427
/**
2528
* A controller to initialize slotted picker.
2629
*
@@ -46,7 +49,7 @@ class PickerSlotController extends SlotController {
4649
* @mixes FocusMixin
4750
*/
4851
export const DateTimePickerMixin = (superClass) =>
49-
class DateTimePickerMixinClass extends FieldMixin(FocusMixin(DisabledMixin(superClass))) {
52+
class DateTimePickerMixinClass extends I18nMixin(DEFAULT_I18N, FieldMixin(FocusMixin(DisabledMixin(superClass)))) {
5053
static get properties() {
5154
return {
5255
/**
@@ -224,22 +227,6 @@ export const DateTimePickerMixin = (superClass) =>
224227
sync: true,
225228
},
226229

227-
/**
228-
* The object used to localize this component.
229-
* To change the default localization, replace the entire
230-
* `i18n` object or just the properties you want to modify.
231-
*
232-
* The object is a combination of the i18n properties supported by
233-
* [`<vaadin-date-picker>`](#/elements/vaadin-date-picker) and
234-
* [`<vaadin-time-picker>`](#/elements/vaadin-time-picker).
235-
* @type {!DateTimePickerI18n}
236-
*/
237-
i18n: {
238-
type: Object,
239-
sync: true,
240-
value: () => ({ ...datePickerI18nDefaults, ...timePickerI18nDefaults }),
241-
},
242-
243230
/**
244231
* The current slotted date picker.
245232
* @private
@@ -274,11 +261,11 @@ export const DateTimePickerMixin = (superClass) =>
274261
'__invalidChanged(invalid, __datePicker, __timePicker)',
275262
'__disabledChanged(disabled, __datePicker, __timePicker)',
276263
'__readonlyChanged(readonly, __datePicker, __timePicker)',
277-
'__i18nChanged(i18n, __datePicker, __timePicker)',
264+
'__i18nChanged(__effectiveI18n, __datePicker, __timePicker)',
278265
'__autoOpenDisabledChanged(autoOpenDisabled, __datePicker, __timePicker)',
279266
'__themeChanged(_theme, __datePicker, __timePicker)',
280267
'__pickersChanged(__datePicker, __timePicker)',
281-
'__labelOrAccessibleNameChanged(label, accessibleName, i18n, __datePicker, __timePicker)',
268+
'__labelOrAccessibleNameChanged(label, accessibleName, __effectiveI18n, __datePicker, __timePicker)',
282269
];
283270
}
284271

@@ -297,6 +284,43 @@ export const DateTimePickerMixin = (superClass) =>
297284
this.__openedChangedEventHandler = this.__openedChangedEventHandler.bind(this);
298285
}
299286

287+
/**
288+
* The object used to localize this component. To change the default
289+
* localization, replace this with an object that provides all properties, or
290+
* just the individual properties you want to change.
291+
*
292+
* The object has the following structure and default values:
293+
*
294+
* ```
295+
* {
296+
* // Accessible label to the date picker.
297+
* // The property works in conjunction with label and accessibleName defined on the field.
298+
* // If both properties are defined, then accessibleName takes precedence.
299+
* // Then, the dateLabel value is concatenated with it.
300+
* dateLabel: undefined;
301+
*
302+
* // Accessible label to the time picker.
303+
* // The property works in conjunction with label and accessibleName defined on the field.
304+
* // If both properties are defined, then accessibleName takes precedence.
305+
* // Then, the dateLabel value is concatenated with it.
306+
* timeLabel: undefined;
307+
* }
308+
* ```
309+
*
310+
* Additionally, all i18n properties from
311+
* [`<vaadin-date-picker>`](#/elements/vaadin-date-picker) and
312+
* [`<vaadin-time-picker>`](#/elements/vaadin-time-picker) are supported.
313+
*
314+
* @type {!DateTimePickerI18n}
315+
*/
316+
get i18n() {
317+
return super.i18n;
318+
}
319+
320+
set i18n(value) {
321+
super.i18n = value;
322+
}
323+
300324
/** @private */
301325
get __pickers() {
302326
return [this.__datePicker, this.__timePicker];
@@ -448,15 +472,15 @@ export const DateTimePickerMixin = (superClass) =>
448472
}
449473

450474
/** @private */
451-
__syncI18n(target, source, props = Object.keys(source.i18n)) {
452-
const i18n = { ...target.i18n };
475+
__syncI18n(target, i18n, props) {
476+
const targetI18n = {};
453477
props.forEach((prop) => {
454478
// eslint-disable-next-line no-prototype-builtins
455-
if (source.i18n && source.i18n.hasOwnProperty(prop)) {
456-
i18n[prop] = source.i18n[prop];
479+
if (i18n && i18n.hasOwnProperty(prop)) {
480+
targetI18n[prop] = i18n[prop];
457481
}
458482
});
459-
target.i18n = i18n;
483+
target.i18n = targetI18n;
460484
}
461485

462486
/** @private */
@@ -528,7 +552,6 @@ export const DateTimePickerMixin = (superClass) =>
528552
this.datePlaceholder = newDatePicker.placeholder;
529553
this.initialPosition = newDatePicker.initialPosition;
530554
this.showWeekNumbers = newDatePicker.showWeekNumbers;
531-
this.__syncI18n(this, newDatePicker, datePickerI18nProps);
532555
}
533556

534557
// Min and max are always synchronized from date time picker (host) to inner fields because time picker
@@ -557,7 +580,6 @@ export const DateTimePickerMixin = (superClass) =>
557580
// Synchronize properties from slotted time picker
558581
this.timePlaceholder = newTimePicker.placeholder;
559582
this.step = newTimePicker.step;
560-
this.__syncI18n(this, newTimePicker, timePickerI18nProps);
561583
}
562584

563585
// Min and max are always synchronized from parent to slotted because time picker min and max
@@ -589,26 +611,27 @@ export const DateTimePickerMixin = (superClass) =>
589611
}
590612

591613
/** @private */
592-
__i18nChanged(_i18n, datePicker, timePicker) {
593-
if (datePicker) {
594-
this.__syncI18n(datePicker, this, datePickerI18nProps);
614+
__i18nChanged(effectiveI18n, datePicker, timePicker) {
615+
// Only propagate i18n to default pickers
616+
if (datePicker && this.__isDefaultPicker(datePicker, 'date')) {
617+
this.__syncI18n(datePicker, effectiveI18n, datePickerI18nProps);
595618
}
596619

597-
if (timePicker) {
598-
this.__syncI18n(timePicker, this, timePickerI18nProps);
620+
if (timePicker && this.__isDefaultPicker(timePicker, 'time')) {
621+
this.__syncI18n(timePicker, effectiveI18n, timePickerI18nProps);
599622
}
600623
}
601624

602625
/** @private */
603-
__labelOrAccessibleNameChanged(label, accessibleName, i18n, datePicker, timePicker) {
626+
__labelOrAccessibleNameChanged(label, accessibleName, effectiveI18n, datePicker, timePicker) {
604627
const name = accessibleName || label || '';
605628

606629
if (datePicker) {
607-
datePicker.accessibleName = `${name} ${i18n.dateLabel || ''}`.trim();
630+
datePicker.accessibleName = `${name} ${effectiveI18n.dateLabel || ''}`.trim();
608631
}
609632

610633
if (timePicker) {
611-
timePicker.accessibleName = `${name} ${i18n.timeLabel || ''}`.trim();
634+
timePicker.accessibleName = `${name} ${effectiveI18n.timeLabel || ''}`.trim();
612635
}
613636
}
614637

packages/date-time-picker/test/i18n.test.js

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,74 @@
11
import { expect } from '@vaadin/chai-plugins';
2-
import { aTimeout, fixtureSync, nextFrame, nextRender } from '@vaadin/testing-helpers';
2+
import { fixtureSync, nextFrame, nextRender } from '@vaadin/testing-helpers';
33
import '../src/vaadin-date-time-picker.js';
44

5-
['default', 'slotted'].forEach((set) => {
6-
describe(`i18n property (${set})`, () => {
7-
let dateTimePicker, datePicker;
5+
describe('i18n property', () => {
6+
let dateTimePicker, datePicker, timePicker;
87

9-
const CUSTOM_I18N = { cancel: 'Peruuta' };
8+
beforeEach(async () => {
9+
dateTimePicker = fixtureSync(`<vaadin-date-time-picker></vaadin-date-time-picker>`);
10+
await nextRender();
11+
datePicker = dateTimePicker.querySelector('[slot="date-picker"]');
12+
timePicker = dateTimePicker.querySelector('[slot="time-picker"]');
13+
});
1014

11-
beforeEach(async () => {
12-
const i18nProp = JSON.stringify(CUSTOM_I18N);
13-
14-
if (set === 'default') {
15-
dateTimePicker = fixtureSync(`<vaadin-date-time-picker i18n='${i18nProp}'></vaadin-date-time-picker>`);
16-
} else {
17-
dateTimePicker = fixtureSync(`
18-
<vaadin-date-time-picker>
19-
<vaadin-date-picker slot="date-picker" i18n='${i18nProp}'></vaadin-date-picker>
20-
<vaadin-time-picker slot="time-picker"></vaadin-time-picker>
21-
</vaadin-date-time-picker>
22-
`);
23-
}
24-
await nextRender();
25-
datePicker = dateTimePicker.querySelector('[slot="date-picker"]');
26-
});
15+
it('should have default i18n properties coming from date and time pickers', () => {
16+
// From date picker
17+
expect(dateTimePicker.i18n).to.have.property('formatDate').that.is.a('function');
18+
expect(dateTimePicker.i18n).to.have.property('parseDate').that.is.a('function');
19+
expect(dateTimePicker.i18n).to.have.property('cancel').that.is.a('string');
20+
// From time picker
21+
expect(dateTimePicker.i18n).to.have.property('formatTime').that.is.a('function');
22+
expect(dateTimePicker.i18n).to.have.property('parseTime').that.is.a('function');
23+
});
2724

28-
it('should have initial value for i18n', () => {
29-
expect(dateTimePicker.i18n).to.have.property('cancel', CUSTOM_I18N.cancel);
30-
expect(datePicker.i18n).to.have.property('cancel', CUSTOM_I18N.cancel);
31-
});
25+
it('should propagate relevant properties to sub-components', () => {
26+
dateTimePicker.i18n = { cancel: 'Peruuta' };
27+
28+
expect(datePicker.i18n).to.have.property('cancel', 'Peruuta');
29+
expect(timePicker.i18n).to.not.have.property('cancel');
30+
31+
const parseTime = () => {};
32+
dateTimePicker.i18n = { parseTime };
33+
34+
expect(timePicker.i18n).to.have.property('parseTime', parseTime);
35+
expect(datePicker.i18n).to.not.have.property('parseTime');
36+
});
37+
38+
it('should fall back to default values', () => {
39+
dateTimePicker.i18n = {};
40+
41+
expect(datePicker.i18n.cancel).to.equal('Cancel');
42+
expect(timePicker.i18n).to.have.property('formatTime').that.is.a('function');
3243
});
33-
});
3444

35-
describe('setting i18n on a slotted picker before connected to the DOM', () => {
36-
let dateTimePicker, datePicker;
45+
it('should initialize property from JSON string', () => {
46+
const i18nJson = JSON.stringify({ cancel: 'Peruuta' });
47+
dateTimePicker = fixtureSync(`<vaadin-date-time-picker i18n='${i18nJson}'></vaadin-date-time-picker>`);
48+
datePicker = dateTimePicker.querySelector('[slot="date-picker"]');
3749

38-
beforeEach(() => {
39-
dateTimePicker = document.createElement('vaadin-date-time-picker');
50+
expect(dateTimePicker.i18n).to.have.property('cancel', 'Peruuta');
51+
expect(datePicker.i18n).to.have.property('cancel', 'Peruuta');
4052
});
4153

42-
describe('date-picker', () => {
54+
describe('slotted date and time pickers', () => {
4355
beforeEach(() => {
44-
datePicker = document.createElement('vaadin-date-picker');
45-
datePicker.slot = 'date-picker';
46-
datePicker.i18n = { ...datePicker.i18n, cancel: 'Peruuta' };
47-
dateTimePicker.appendChild(datePicker);
56+
dateTimePicker = fixtureSync(`
57+
<vaadin-date-time-picker>
58+
<vaadin-date-picker slot="date-picker"></vaadin-date-picker>
59+
<vaadin-time-picker slot="time-picker"></vaadin-time-picker>
60+
</vaadin-date-time-picker>
61+
`);
62+
datePicker = dateTimePicker.querySelector('[slot="date-picker"]');
63+
timePicker = dateTimePicker.querySelector('[slot="time-picker"]');
4864
});
4965

50-
it('should not have i18n overridden', async () => {
51-
await aTimeout(0);
52-
document.body.appendChild(dateTimePicker);
53-
await nextRender();
54-
expect(datePicker.i18n.cancel).to.equal('Peruuta');
66+
it('should not override i18n of slotted date and time pickers', () => {
67+
const i18n = { cancel: 'Peruuta', formatDate: () => 'formatted-date' };
68+
dateTimePicker.i18n = i18n;
69+
70+
expect(datePicker.i18n.cancel).to.not.equal(i18n.cancel);
71+
expect(datePicker.i18n.formatDate).to.not.equal(i18n.formatDate);
5572
});
5673
});
5774
});

packages/date-time-picker/test/properties.test.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -244,21 +244,6 @@ function getTimePicker(dateTimePicker) {
244244
expect(datePicker.autoOpenDisabled).to.be.true;
245245
expect(timePicker.autoOpenDisabled).to.be.true;
246246
});
247-
248-
it('should have default i18n properties coming from date and time pickers', () => {
249-
// From date picker
250-
expect(dateTimePicker.i18n).to.have.property('formatDate').that.is.a('function');
251-
expect(dateTimePicker.i18n).to.have.property('parseDate').that.is.a('function');
252-
expect(dateTimePicker.i18n).to.have.property('cancel').that.is.a('string');
253-
// From time picker
254-
expect(dateTimePicker.i18n).to.have.property('formatTime').that.is.a('function');
255-
expect(dateTimePicker.i18n).to.have.property('parseTime').that.is.a('function');
256-
});
257-
258-
it('should propagate i18n properties to the date picker', () => {
259-
dateTimePicker.i18n = { ...dateTimePicker.i18n, cancel: 'Peruuta' };
260-
expect(datePicker.i18n.cancel).to.equal('Peruuta');
261-
});
262247
});
263248

264249
describe(`Initial property values (${set})`, () => {

0 commit comments

Comments
 (0)