如何显示日期和时间字段的本地化标签?
使用 Intl.DisplayNames 获取字段标签,并使用 Intl.DateTimeFormat 获取任意语言的月份和星期名称。
介绍
当您构建日期和时间输入表单时,需要为每个字段提供描述性的标签。日期选择器需要像“月”、“年”和“日”这样的标签。时间选择器需要像“时”和“分”这样的标签。这些标签必须以用户的语言显示。
将这些标签硬编码为英文并不适用于国际化应用程序。法国用户期望看到“mois”和“année”,而西班牙用户则期望看到“mes”和“año”。您需要一个能够自动提供这些标签的系统,以支持任何语言。
JavaScript 提供了两个互补的 API 来实现这一点。Intl.DisplayNames API 提供像“月”和“年”这样的字段标签。Intl.DateTimeFormat API 提供这些字段的实际值,例如月份名称和星期名称。
理解日期和时间字段标签
日期和时间界面需要两种类型的标签。字段标签描述每个输入框中需要输入的数据类型。字段值是出现在下拉菜单和选择器中的实际数据。
字段标签包括“年”、“月”、“日”、“时”、“分”和“秒”等词语。这些标签用于标记表单字段本身。
字段值包括像“一月”和“二月”这样的月份名称,像“星期一”和“星期二”这样的星期名称,以及像“上午”和“下午”这样的时间段标签。这些值用于填充下拉菜单和选择列表。
一个完整的日期表单需要这两种类型的标签。
<label>月</label>
<select>
<option>一月</option>
<option>二月</option>
<option>三月</option>
<!-- 更多月份 -->
</select>
<label>年</label>
<input type="number" />
“月”和“一月”都需要本地化,但它们需要不同的方法。
使用 Intl.DisplayNames 获取字段标签
Intl.DisplayNames 构造函数与 type: "dateTimeField" 一起使用时,可以返回日期和时间组件的本地化标签。
const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });
console.log(labels.of('year'));
// "year"
console.log(labels.of('month'));
// "month"
console.log(labels.of('day'));
// "day"
console.log(labels.of('hour'));
// "hour"
console.log(labels.of('minute'));
// "minute"
console.log(labels.of('second'));
// "second"
of() 方法接受一个字段代码并返回其本地化标签。标签符合指定语言环境的约定。
通过更改语言环境,您可以获取任何语言的标签。
// 西班牙语标签
const esLabels = new Intl.DisplayNames('es-ES', { type: 'dateTimeField' });
console.log(esLabels.of('year'));
// "año"
console.log(esLabels.of('month'));
// "mes"
console.log(esLabels.of('day'));
// "día"
console.log(esLabels.of('hour'));
// "hora"
console.log(esLabels.of('minute'));
// "minuto"
// 法语标签
const frLabels = new Intl.DisplayNames('fr-FR', { type: 'dateTimeField' });
console.log(frLabels.of('year'));
// "année"
console.log(frLabels.of('month'));
// "mois"
console.log(frLabels.of('day'));
// "jour"
console.log(frLabels.of('hour'));
// "heure"
console.log(frLabels.of('minute'));
// "minute"
// 日语标签
const jaLabels = new Intl.DisplayNames('ja-JP', { type: 'dateTimeField' });
console.log(jaLabels.of('year'));
// "年"
console.log(jaLabels.of('month'));
// "月"
console.log(jaLabels.of('day'));
// "日"
console.log(jaLabels.of('hour'));
// "時"
console.log(jaLabels.of('minute'));
// "分"
每种语言环境都提供其自身语言和脚本的标签。
可用的日期和时间字段代码
Intl.DisplayNames API 支持以下字段代码。
const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });
console.log(labels.of('era'));
// "era"
console.log(labels.of('year'));
// "year"
console.log(labels.of('quarter'));
// "quarter"
console.log(labels.of('month'));
// "month"
console.log(labels.of('weekOfYear'));
// "week"
console.log(labels.of('weekday'));
// "day of the week"
console.log(labels.of('day'));
// "day"
console.log(labels.of('dayPeriod'));
// "AM/PM"
console.log(labels.of('hour'));
// "hour"
console.log(labels.of('minute'));
// "minute"
console.log(labels.of('second'));
// "second"
console.log(labels.of('timeZoneName'));
// "time zone"
使用这些代码为界面所需的任何日期或时间组件获取标签。
获取本地化的月份名称
Intl.DateTimeFormat API 提供了用于填充下拉菜单和选择列表的月份名称。创建一个格式化器,并将 month 选项设置为 "long",然后格式化表示每个月的日期。
function getMonthNames(locale) {
const formatter = new Intl.DateTimeFormat(locale, {
month: 'long',
timeZone: 'UTC'
});
const months = [];
for (let month = 0; month < 12; month++) {
const date = new Date(Date.UTC(2000, month, 1));
months.push(formatter.format(date));
}
return months;
}
console.log(getMonthNames('en-US'));
// ["January", "February", "March", "April", "May", "June",
// "July", "August", "September", "October", "November", "December"]
该函数为每年的每个月创建日期,并通过格式化提取月份名称。设置 timeZone: 'UTC' 可确保跨时区的一致结果。
您可以获取任何语言的月份名称。
console.log(getMonthNames('es-ES'));
// ["enero", "febrero", "marzo", "abril", "mayo", "junio",
// "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"]
console.log(getMonthNames('fr-FR'));
// ["janvier", "février", "mars", "avril", "mai", "juin",
// "juillet", "août", "septembre", "octobre", "novembre", "décembre"]
console.log(getMonthNames('ja-JP'));
// ["1月", "2月", "3月", "4月", "5月", "6月",
// "7月", "8月", "9月", "10月", "11月", "12月"]
每个语言环境都会根据其自身的约定格式化月份名称。
控制月份名称的长度
month 选项接受不同的值来控制月份名称的长度。
"long" 值返回完整的月份名称。
const longFormatter = new Intl.DateTimeFormat('en-US', {
month: 'long',
timeZone: 'UTC'
});
const date = new Date(Date.UTC(2000, 0, 1));
console.log(longFormatter.format(date));
// "January"
"short" 值返回缩写的月份名称。
const shortFormatter = new Intl.DateTimeFormat('en-US', {
month: 'short',
timeZone: 'UTC'
});
console.log(shortFormatter.format(date));
// "Jan"
"narrow" 值返回最短的月份名称,通常是一个字母。
const narrowFormatter = new Intl.DateTimeFormat('en-US', {
month: 'narrow',
timeZone: 'UTC'
});
console.log(narrowFormatter.format(date));
// "J"
使用 "narrow" 时需谨慎,因为多个月份可能共享相同的字母。在英语中,January、June 和 July 都会生成 "J"。
获取本地化的星期名称
使用相同的模式获取星期名称。设置 weekday 选项来控制格式。
function getWeekdayNames(locale, format = 'long') {
const formatter = new Intl.DateTimeFormat(locale, {
weekday: format,
timeZone: 'UTC'
});
const weekdays = [];
// 从星期天开始(2000年1月2日是星期天)
for (let day = 0; day < 7; day++) {
const date = new Date(Date.UTC(2000, 0, 2 + day));
weekdays.push(formatter.format(date));
}
return weekdays;
}
console.log(getWeekdayNames('en-US'));
// ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
console.log(getWeekdayNames('en-US', 'short'));
// ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
console.log(getWeekdayNames('en-US', 'narrow'));
// ["S", "M", "T", "W", "T", "F", "S"]
您可以获取任何语言的星期名称。
console.log(getWeekdayNames('es-ES'));
// ["domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"]
console.log(getWeekdayNames('fr-FR'));
// ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"]
console.log(getWeekdayNames('ja-JP'));
// ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"]
不同的语言环境可能会从不同的星期开始。这段函数始终按星期天到星期六的顺序返回。
获取本地化的时间段标签
时间段标签是用于 12 小时制时间的上午 (AM) 和下午 (PM) 指示符。使用 formatToParts() 提取这些标签。
function getPeriodLabels(locale) {
const formatter = new Intl.DateTimeFormat(locale, {
hour: 'numeric',
hour12: true,
timeZone: 'UTC'
});
const amDate = new Date(Date.UTC(2000, 0, 1, 0, 0, 0));
const pmDate = new Date(Date.UTC(2000, 0, 1, 12, 0, 0));
const amParts = formatter.formatToParts(amDate);
const pmParts = formatter.formatToParts(pmDate);
const am = amParts.find(part => part.type === 'dayPeriod').value;
const pm = pmParts.find(part => part.type === 'dayPeriod').value;
return { am, pm };
}
console.log(getPeriodLabels('en-US'));
// { am: "AM", pm: "PM" }
console.log(getPeriodLabels('es-ES'));
// { am: "a. m.", pm: "p. m." }
console.log(getPeriodLabels('fr-FR'));
// { am: "AM", pm: "PM" }
console.log(getPeriodLabels('ja-JP'));
// { am: "午前", pm: "午後" }
formatToParts() 方法返回一个对象数组,表示格式化时间的每个部分。具有 type: "dayPeriod" 的对象包含 AM 或 PM 标签。
某些语言环境默认使用 24 小时制,不包括时间段标签。您可以通过设置 hour12: true 强制使用 12 小时制。
构建完整的本地化日期表单
结合所有这些技术,创建一个完全本地化的日期输入表单。
function createDateForm(locale) {
const fieldLabels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });
const monthNames = getMonthNames(locale);
const currentYear = new Date().getFullYear();
return {
monthLabel: fieldLabels.of('month'),
months: monthNames.map((name, index) => ({
value: index + 1,
label: name
})),
dayLabel: fieldLabels.of('day'),
yearLabel: fieldLabels.of('year'),
yearPlaceholder: currentYear
};
}
// 从前面的示例中获取的辅助函数
function getMonthNames(locale) {
const formatter = new Intl.DateTimeFormat(locale, {
month: 'long',
timeZone: 'UTC'
});
const months = [];
for (let month = 0; month < 12; month++) {
const date = new Date(Date.UTC(2000, month, 1));
months.push(formatter.format(date));
}
return months;
}
const enForm = createDateForm('en-US');
console.log(enForm.monthLabel);
// "month"
console.log(enForm.months[0]);
// { value: 1, label: "January" }
console.log(enForm.dayLabel);
// "day"
console.log(enForm.yearLabel);
// "year"
const esForm = createDateForm('es-ES');
console.log(esForm.monthLabel);
// "mes"
console.log(esForm.months[0]);
// { value: 1, label: "enero" }
console.log(esForm.dayLabel);
// "día"
console.log(esForm.yearLabel);
// "año"
此结构提供了在 HTML 中呈现本地化日期表单所需的一切。
function renderDateForm(locale) {
const form = createDateForm(locale);
return `
<div class="date-form">
<div class="form-field">
<label>${form.monthLabel}</label>
<select name="month">
${form.months.map(month =>
`<option value="${month.value}">${month.label}</option>`
).join('')}
</select>
</div>
<div class="form-field">
<label>${form.dayLabel}</label>
<input type="number" name="day" min="1" max="31" />
</div>
<div class="form-field">
<label>${form.yearLabel}</label>
<input type="number" name="year" placeholder="${form.yearPlaceholder}" />
</div>
</div>
`;
}
console.log(renderDateForm('en-US'));
// 使用英文标签和月份名称渲染表单
console.log(renderDateForm('fr-FR'));
// 使用法文标签和月份名称渲染表单
该表单会自动适应您指定的任何语言环境。
构建一个本地化的时间表单
使用相同的方法创建一个包含小时、分钟和时间段选择器的时间输入表单。
function createTimeForm(locale) {
const fieldLabels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });
const periods = getPeriodLabels(locale);
const hours = [];
for (let hour = 1; hour <= 12; hour++) {
hours.push({ value: hour, label: hour.toString() });
}
const minutes = [];
for (let minute = 0; minute < 60; minute += 5) {
minutes.push({
value: minute,
label: minute.toString().padStart(2, '0')
});
}
return {
hourLabel: fieldLabels.of('hour'),
hours: hours,
minuteLabel: fieldLabels.of('minute'),
minutes: minutes,
periodLabel: fieldLabels.of('dayPeriod'),
periods: [
{ value: 'am', label: periods.am },
{ value: 'pm', label: periods.pm }
]
};
}
// 从前面的示例中获取的辅助函数
function getPeriodLabels(locale) {
const formatter = new Intl.DateTimeFormat(locale, {
hour: 'numeric',
hour12: true,
timeZone: 'UTC'
});
const amDate = new Date(Date.UTC(2000, 0, 1, 0, 0, 0));
const pmDate = new Date(Date.UTC(2000, 0, 1, 12, 0, 0));
const amParts = formatter.formatToParts(amDate);
const pmParts = formatter.formatToParts(pmDate);
const am = amParts.find(part => part.type === 'dayPeriod').value;
const pm = pmParts.find(part => part.type === 'dayPeriod').value;
return { am, pm };
}
const enTime = createTimeForm('en-US');
console.log(enTime.hourLabel);
// "hour"
console.log(enTime.minuteLabel);
// "minute"
console.log(enTime.periodLabel);
// "AM/PM"
console.log(enTime.periods);
// [{ value: "am", label: "AM" }, { value: "pm", label: "PM" }]
const jaTime = createTimeForm('ja-JP');
console.log(jaTime.hourLabel);
// "時"
console.log(jaTime.minuteLabel);
// "分"
console.log(jaTime.periodLabel);
// "午前/午後"
console.log(jaTime.periods);
// [{ value: "am", label: "午前" }, { value: "pm", label: "午後" }]
这提供了渲染本地化时间选择器所需的所有数据。
何时使用日期和时间字段标签
日期和时间字段标签出现在多种类型的界面中。
自定义日期和时间选择器
在构建日期选择器、日历小部件或时间选择器时使用本地化标签。
const locale = navigator.language;
const labels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });
const datePicker = {
yearLabel: labels.of('year'),
monthLabel: labels.of('month'),
dayLabel: labels.of('day')
};
表单字段标签
为日期和时间数据的标准表单输入应用标签。
const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });
document.querySelector('#birthdate-month-label').textContent =
labels.of('month');
document.querySelector('#birthdate-year-label').textContent =
labels.of('year');
无障碍标签
为屏幕阅读器和辅助技术提供本地化的 ARIA 标签。
const locale = navigator.language;
const labels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });
const input = document.querySelector('#date-input');
input.setAttribute('aria-label', labels.of('year'));
数据表头
为显示日期和时间组件的表格列添加标签。
const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });
const table = `
<table>
<thead>
<tr>
<th>${labels.of('year')}</th>
<th>${labels.of('month')}</th>
<th>${labels.of('day')}</th>
</tr>
</thead>
</table>
`;
浏览器支持
Intl.DisplayNames API 的 type: "dateTimeField" 自 2022 年 3 月起在主流浏览器中得到支持。
Chrome 和 Edge 从版本 99 开始支持。Firefox 从版本 99 开始支持。Safari 从版本 15.4 开始支持。
在使用之前,您可以检查该功能是否可用。
function supportsDateTimeFieldLabels() {
try {
const labels = new Intl.DisplayNames('en', { type: 'dateTimeField' });
labels.of('year');
return true;
} catch (error) {
return false;
}
}
if (supportsDateTimeFieldLabels()) {
const labels = new Intl.DisplayNames('en-US', { type: 'dateTimeField' });
console.log(labels.of('month'));
} else {
console.log('month'); // 回退到英文
}
对于较旧的浏览器,您需要提供回退标签。为常见字段创建一个简单的映射对象。
const fallbackLabels = {
en: {
year: 'year',
month: 'month',
day: 'day',
hour: 'hour',
minute: 'minute',
second: 'second'
},
es: {
year: 'año',
month: 'mes',
day: 'día',
hour: 'hora',
minute: 'minuto',
second: 'segundo'
},
fr: {
year: 'année',
month: 'mois',
day: 'jour',
hour: 'heure',
minute: 'minute',
second: 'seconde'
}
};
function getFieldLabel(field, locale) {
if (supportsDateTimeFieldLabels()) {
const labels = new Intl.DisplayNames(locale, { type: 'dateTimeField' });
return labels.of(field);
}
const language = locale.split('-')[0];
return fallbackLabels[language]?.[field] || fallbackLabels.en[field];
}
console.log(getFieldLabel('month', 'es-ES'));
// "mes"(如果支持则来自 API,否则来自回退)
这确保了您的表单在所有浏览器中都能正常工作,同时在可用时利用本地化功能。
Intl.DateTimeFormat API 用于获取月份和星期几名称,支持范围更广,早在 Internet Explorer 11 和所有现代浏览器中就已支持。在大多数情况下,您可以直接使用它而无需功能检测。