날짜 및 시간 필드의 현지화된 레이블을 표시하는 방법은 무엇인가요?

Intl.DisplayNames를 사용하여 필드 레이블을 가져오고 Intl.DateTimeFormat을 사용하여 모든 언어로 월 및 요일 이름을 가져오세요.

소개

날짜 및 시간 입력 양식을 구축할 때 각 필드를 설명하는 레이블이 필요합니다. 날짜 선택기에는 "월", "연도", "일"과 같은 레이블이 필요합니다. 시간 선택기에는 "시간" 및 "분"과 같은 레이블이 필요합니다. 이러한 레이블은 사용자의 언어로 표시되어야 합니다.

이러한 레이블을 영어로 하드코딩하는 것은 국제 애플리케이션에서 작동하지 않습니다. 프랑스 사용자는 "Mois"와 "Année"를 보기를 기대하고, 스페인 사용자는 "Mes"와 "Año"를 찾습니다. 모든 언어로 이러한 레이블을 자동으로 제공하는 시스템이 필요합니다.

JavaScript는 이를 위한 두 가지 보완적인 API를 제공합니다. Intl.DisplayNames API는 "월" 및 "연도"와 같은 필드 레이블을 제공합니다. Intl.DateTimeFormat API는 월 이름 및 요일 이름과 같은 해당 필드의 실제 값을 제공합니다.

날짜 및 시간 필드 레이블 이해하기

날짜 및 시간 인터페이스에는 두 가지 유형의 레이블이 필요합니다. 필드 레이블은 각 입력에 들어가는 데이터 유형을 설명합니다. 필드 값은 드롭다운 및 선택기에 표시되는 실제 데이터입니다.

필드 레이블에는 "연도", "월", "일", "시간", "분", "초"와 같은 단어가 포함됩니다. 이들은 양식 필드 자체에 레이블을 지정합니다.

필드 값에는 "1월" 및 "2월"과 같은 월 이름, "월요일" 및 "화요일"과 같은 요일 이름, "오전" 및 "오후"와 같은 기간 레이블이 포함됩니다. 이들은 드롭다운 및 선택 목록을 채웁니다.

완전한 날짜 양식에는 두 가지 유형의 레이블이 모두 필요합니다.

<label>Month</label>
<select>
  <option>January</option>
  <option>February</option>
  <option>March</option>
  <!-- more months -->
</select>

<label>Year</label>
<input type="number" />

"월"과 "1월" 모두 현지화가 필요하지만 서로 다른 접근 방식이 필요합니다.

Intl.DisplayNames로 필드 레이블 가져오기

type: "dateTimeField"를 사용하는 Intl.DisplayNames 생성자는 날짜 및 시간 구성 요소에 대한 현지화된 레이블을 반환합니다.

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() 메서드는 필드 코드를 받아 현지화된 레이블을 반환합니다. 레이블은 지정된 로케일의 규칙과 일치합니다.

로케일을 변경하여 모든 언어로 레이블을 가져올 수 있습니다.

// Spanish labels
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"

// French labels
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"

// Japanese labels
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 = [];
  // Start from a Sunday (January 2, 2000 was a Sunday)
  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시간 형식에서 사용되는 오전/오후 표시입니다. 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"를 가진 객체에는 오전 또는 오후 레이블이 포함됩니다.

일부 로케일은 기본적으로 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
  };
}

// Helper function from previous example
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'));
// Renders form with English labels and month names

console.log(renderDateForm('fr-FR'));
// Renders form with French labels and month names

양식은 지정한 모든 로케일에 자동으로 적응합니다.

현지화된 시간 양식 구축

같은 접근 방식을 적용하여 시, 분 및 기간 선택기가 있는 시간 입력 양식을 만듭니다.

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 }
    ]
  };
}

// Helper function from previous example
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>
`;

브라우저 지원

type: "dateTimeField"를 사용하는 Intl.DisplayNames API는 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'); // Fallback to English
}

이전 브라우저의 경우 대체 레이블을 제공해야 합니다. 일반적인 필드에 대한 간단한 매핑 객체를 생성하세요.

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" (from API if supported, from fallback otherwise)

이렇게 하면 네이티브 현지화를 사용할 수 있을 때 이를 활용하면서 모든 브라우저에서 양식이 작동하도록 보장합니다.

월 및 요일 이름을 가져오기 위한 Intl.DateTimeFormat API는 Internet Explorer 11과 모든 최신 브라우저까지 거슬러 올라가는 더 광범위한 지원을 제공합니다. 대부분의 경우 기능 감지 없이 사용할 수 있습니다.