1st, 2nd, 3rd와 같은 서수 숫자 포맷 방법

JavaScript를 사용하여 지역에 맞는 접미사와 포맷으로 서수 숫자 표시하기

소개

서수는 순서나 순위를 나타냅니다. 영어에서는 경주의 순위나 목록의 항목을 설명할 때 1st, 2nd, 3rd, 4th와 같이 표기합니다. 이러한 접미사는 서수를 일반 계수 숫자와 구별하는 데 도움이 됩니다.

다양한 언어에서는 서수를 표현하는 완전히 다른 규칙을 사용합니다. 영어는 st, nd, rd, th와 같은 접미사를 추가합니다. 프랑스어는 1er, 2e와 같은 위 첨자 문자를 사용합니다. 독일어는 1.과 2.처럼 숫자 뒤에 마침표를 추가합니다. 일본어는 숫자 앞에 第 문자를 붙입니다. 영어 서수 접미사를 하드코딩하면 모든 사용자가 동일한 규칙을 따른다고 가정하는 것입니다.

자바스크립트는 Intl.PluralRules API와 서수 유형을 제공하여 이러한 차이를 자동으로 처리합니다. 이 강의에서는 서수가 무엇인지, 언어마다 형식이 어떻게 다른지, 그리고 모든 로케일에 맞게 올바르게 형식을 지정하는 방법을 설명합니다.

서수란 무엇인가

서수는 순서, 순위 또는 연속된 순서를 표현합니다. 서수는 "몇 개"가 아닌 "몇 번째"라는 질문에 답합니다. 1st, 2nd, 3rd는 경주에서의 위치를 설명합니다. First, second, third는 목록의 항목을 설명합니다.

기수는 양이나 수량을 표현합니다. 기수는 "몇 개"라는 질문에 답합니다. 1, 2, 3은 물체의 수를 설명합니다. One, two, three는 양을 설명합니다.

같은 숫자 값이라도 문맥에 따라 두 가지 목적으로 사용됩니다. "5 apples"에서 5는 기수이지만 "5th place"에서는 서수입니다. 많은 언어에서 서수와 기수의 형식이 다르기 때문에 이 구분은 중요합니다.

영어에서 10 미만의 서수는 고유한 단어 형태를 가집니다. First, second, third, fourth, fifth는 구별되는 단어입니다. 10 이상에서 영어는 접미사를 추가하여 서수를 형성합니다. Eleventh, twelfth, twentieth, twenty-first는 패턴을 따르지만 접미사가 필요합니다.

단어가 아닌 숫자로 서수를 표기할 때, 영어는 st, nd, rd 또는 th 접미사를 추가합니다. 이러한 접미사는 숫자의 마지막 자릿수에 따라 특정 규칙을 따릅니다.

서수 형식이 로케일에 따라 다른 이유

서로 다른 언어들은 서수를 표현하는 다양한 시스템을 발전시켰습니다. 이러한 관례는 각 언어에 고유한 문법 규칙, 표기 체계 및 문화적 관행을 반영합니다.

영어에서는 서수에 네 가지 다른 접미사를 사용합니다. 1로 끝나는 숫자는 st, 2로 끝나는 숫자는 nd, 3으로 끝나는 숫자는 rd, 그리고 다른 모든 숫자는 th를 사용합니다. 그러나 11, 12, 13으로 끝나는 숫자는 모두 th를 사용합니다. 이렇게 1st, 2nd, 3rd, 4th, 11th, 12th, 13th, 21st, 22nd, 23rd가 됩니다.

프랑스어에서는 서수에 위첨자 약어를 사용합니다. 첫 번째 항목은 남성형으로 1er 또는 여성형으로 1re를 사용합니다. 다른 모든 서수는 2e, 3e, 4e와 같이 위첨자 e를 사용합니다. 이 형식에는 접미사 글자뿐만 아니라 위첨자 타이포그래피도 포함됩니다.

독일어에서는 서수에 숫자 뒤에 마침표를 사용합니다. 1., 2., 3.은 첫 번째, 두 번째, 세 번째를 나타냅니다. 이 마침표는 독자가 소리 내어 읽을 때 적절한 문법적 어미를 정신적으로 추가해야 함을 알려줍니다.

스페인어에서는 서수에 성별에 따른 위첨자 표시를 사용합니다. 남성형 서수는 1.º, 2.º, 3.º를 사용하고 여성형 서수는 1.ª, 2.ª, 3.ª를 사용합니다. 마침표는 숫자와 표시를 구분합니다.

일본어에서는 서수에 숫자 앞에 접두사 第를 추가합니다. 첫 번째, 두 번째, 세 번째는 第一, 第二, 第三으로 나타납니다. 이 접두사는 기수에서 서수로 의미를 변경합니다.

하드코딩된 접미사로 숫자를 연결하여 서수 문자열을 구성하면 모든 사용자가 영어 관례를 해석하도록 강요하게 됩니다. 이는 다른 형식을 기대하는 사람들에게 애플리케이션 사용을 더 어렵게 만듭니다.

서수 유형의 Intl.PluralRules 이해하기

'Intl.PluralRules' API는 주어진 로케일에 대해 숫자가 어떤 복수 카테고리에 속하는지 결정합니다. 이 API는 일반적으로 단수와 복수 단어 형태 중에서 선택하는 데 사용되지만, 서수도 처리합니다.

생성자는 로케일 식별자와 옵션 객체를 받습니다. 기수 대신 서수로 작업하려면 'type' 옵션을 '"ordinal"'로 설정하세요.

const rules = new Intl.PluralRules('en-US', { type: 'ordinal' });

이렇게 하면 영어 서수 패턴을 이해하는 규칙 객체가 생성됩니다. 'select()' 메서드는 전달하는 모든 숫자에 대한 카테고리 이름을 반환합니다.

const rules = new Intl.PluralRules('en-US', { type: 'ordinal' });

console.log(rules.select(1));
// 출력: "one"

console.log(rules.select(2));
// 출력: "two"

console.log(rules.select(3));
// 출력: "few"

console.log(rules.select(4));
// 출력: "other"

반환되는 카테고리는 실제 접미사가 아닌 언어학적 용어입니다. "one" 카테고리는 영어에서 st 접미사를 취하는 숫자에 해당합니다. "two" 카테고리는 nd 접미사에 해당합니다. "few" 카테고리는 rd 접미사에 해당합니다. "other" 카테고리는 th 접미사에 해당합니다.

이러한 카테고리 이름을 로케일에 맞는 적절한 접미사에 매핑합니다. API는 각 숫자가 어떤 카테고리에 속하는지 결정하는 복잡한 규칙을 처리합니다.

서수 포맷터 함수 구축하기

서수를 포맷하기 위해서는 Intl.PluralRules와 복수 카테고리에서 접미사로의 매핑을 결합합니다. 숫자를 받아 포맷된 문자열을 반환하는 포맷터 함수를 만들어 보겠습니다.

function formatOrdinal(number, locale) {
  const rules = new Intl.PluralRules(locale, { type: 'ordinal' });
  const category = rules.select(number);

  const suffixes = {
    one: 'st',
    two: 'nd',
    few: 'rd',
    other: 'th'
  };

  const suffix = suffixes[category];
  return `${number}${suffix}`;
}

console.log(formatOrdinal(1, 'en-US'));
// 출력: "1st"

console.log(formatOrdinal(2, 'en-US'));
// 출력: "2nd"

console.log(formatOrdinal(3, 'en-US'));
// 출력: "3rd"

console.log(formatOrdinal(4, 'en-US'));
// 출력: "4th"

이 함수는 실행될 때마다 새로운 PluralRules 인스턴스를 생성합니다. 더 나은 성능을 위해 rules 객체를 한 번 생성하고 여러 숫자에 재사용하는 것이 좋습니다.

const rules = new Intl.PluralRules('en-US', { type: 'ordinal' });

const suffixes = {
  one: 'st',
  two: 'nd',
  few: 'rd',
  other: 'th'
};

function formatOrdinal(number) {
  const category = rules.select(number);
  const suffix = suffixes[category];
  return `${number}${suffix}`;
}

console.log(formatOrdinal(1));
// 출력: "1st"

console.log(formatOrdinal(21));
// 출력: "21st"

console.log(formatOrdinal(22));
// 출력: "22nd"

console.log(formatOrdinal(23));
// 출력: "23rd"

이 API는 마지막 숫자에도 불구하고 모두 th를 사용하는 11, 12, 13과 같은 숫자를 올바르게 처리합니다.

console.log(formatOrdinal(11));
// 출력: "11th"

console.log(formatOrdinal(12));
// 출력: "12th"

console.log(formatOrdinal(13));
// 출력: "13th"

복수 규칙은 로케일에 대한 모든 특수 케이스와 예외를 인코딩합니다. 이러한 엣지 케이스를 처리하기 위한 조건부 로직을 작성할 필요가 없습니다.

다양한 로케일에 대한 서수 포맷팅

복수 카테고리와 그 의미는 로케일에 따라 변경됩니다. 일부 언어는 영어보다 카테고리가 적습니다. 다른 언어는 각 카테고리에 속하는 숫자에 대해 다른 규칙을 가지고 있습니다.

웨일스어는 다른 분류 시스템을 사용합니다. 규칙은 더 많은 카테고리를 식별하며, 각각은 웨일스어의 다른 서수 형태에 해당합니다.

const enRules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const cyRules = new Intl.PluralRules('cy', { type: 'ordinal' });

console.log(enRules.select(1));
// 출력: "one"

console.log(cyRules.select(1));
// 출력: "one"

console.log(enRules.select(2));
// 출력: "two"

console.log(cyRules.select(2));
// 출력: "two"

console.log(enRules.select(5));
// 출력: "other"

console.log(cyRules.select(5));
// 출력: "many"

여러 로케일을 지원하려면 각 로케일에 대해 다른 접미사 매핑이 필요합니다. 카테고리는 동일하게 유지되지만 접미사는 변경됩니다.

const ordinalSuffixes = {
  'en-US': {
    one: 'st',
    two: 'nd',
    few: 'rd',
    other: 'th'
  },
  'fr-FR': {
    one: 'er',
    other: 'e'
  }
};

function formatOrdinal(number, locale) {
  const rules = new Intl.PluralRules(locale, { type: 'ordinal' });
  const category = rules.select(number);
  const suffixes = ordinalSuffixes[locale];
  const suffix = suffixes[category] || suffixes.other;
  return `${number}${suffix}`;
}

console.log(formatOrdinal(1, 'en-US'));
// 출력: "1st"

console.log(formatOrdinal(1, 'fr-FR'));
// 출력: "1er"

console.log(formatOrdinal(2, 'en-US'));
// 출력: "2nd"

console.log(formatOrdinal(2, 'fr-FR'));
// 출력: "2e"

이 접근 방식은 각 로케일에 대한 접미사 문자열을 제어할 때 잘 작동합니다. 그러나 지원하는 모든 로케일에 대한 접미사 데이터를 유지해야 합니다.

서수 복수 카테고리 이해하기

Intl.PluralRules API는 여섯 가지 가능한 카테고리 이름을 사용합니다. 다양한 로케일은 이러한 카테고리의 서로 다른 하위 집합을 사용합니다.

카테고리는 zero, one, two, few, many, other입니다. 모든 언어가 이 여섯 가지 카테고리를 모두 구분하지는 않습니다. 영어 서수는 one, two, few, other 네 가지만 사용합니다.

카테고리 이름은 숫자 값과 직접적으로 일치하지 않습니다. "one" 카테고리에는 11을 제외하고 1로 끝나는 모든 숫자인 1, 21, 31, 41 등이 포함됩니다. "two" 카테고리에는 12를 제외하고 2로 끝나는 모든 숫자인 2, 22, 32, 42 등이 포함됩니다.

resolvedOptions() 메서드를 호출하고 pluralCategories 속성을 검사하여 로케일이 어떤 카테고리를 사용하는지 확인할 수 있습니다.

const rules = new Intl.PluralRules('en-US', { type: 'ordinal' });
const options = rules.resolvedOptions();

console.log(options.pluralCategories);
// 출력: ["one", "two", "few", "other"]

이는 영어 서수가 네 가지 카테고리를 사용한다는 것을 보여줍니다. 다른 로케일은 다른 카테고리 집합을 사용합니다.

const rules = new Intl.PluralRules('fr-FR', { type: 'ordinal' });
const options = rules.resolvedOptions();

console.log(options.pluralCategories);
// 출력: ["one", "other"]

프랑스어 서수는 one과 other만 구분합니다. 이러한 더 단순한 분류는 프랑스어의 더 단순한 접미사 규칙을 반영합니다.

사용자 로케일에 맞는 서수 숫자 포맷팅

특정 로케일을 하드코딩하는 대신, 브라우저에서 사용자가 선호하는 언어를 사용할 수 있습니다. navigator.language 속성은 사용자의 최상위 언어 선호도를 반환합니다.

const userLocale = navigator.language;
const rules = new Intl.PluralRules(userLocale, { type: 'ordinal' });

const suffixes = {
  one: 'st',
  two: 'nd',
  few: 'rd',
  other: 'th'
};

function formatOrdinal(number) {
  const category = rules.select(number);
  const suffix = suffixes[category] || suffixes.other;
  return `${number}${suffix}`;
}

console.log(formatOrdinal(1));
// 출력은 사용자의 로케일에 따라 다름

이 접근 방식은 사용자의 언어 선호도에 자동으로 적응합니다. 그러나 애플리케이션이 지원하는 각 로케일에 대한 적절한 접미사 매핑을 제공해야 합니다.

특정 접미사 매핑이 없는 로케일의 경우, 기본 동작으로 대체하거나 접미사 없이 숫자를 표시할 수 있습니다.

function formatOrdinal(number, locale = navigator.language) {
  const rules = new Intl.PluralRules(locale, { type: 'ordinal' });
  const category = rules.select(number);

  const localeMapping = ordinalSuffixes[locale];

  if (!localeMapping) {
    return String(number);
  }

  const suffix = localeMapping[category] || localeMapping.other || '';
  return `${number}${suffix}`;
}

이 함수는 로케일에 대한 접미사 매핑이 존재하지 않을 때 숫자만 반환합니다.

서수의 일반적인 사용 사례

서수는 사용자 인터페이스에서 여러 일반적인 상황에서 나타납니다. 이러한 사용 사례를 이해하면 숫자를 서수로 포맷팅해야 할 때를 결정하는 데 도움이 됩니다.

랭킹과 리더보드는 사용자 위치를 표시합니다. 게임 애플리케이션은 "place 1", "place 2", "place 3" 대신 "1st place", "2nd place", "3rd place"를 표시합니다.

function formatRanking(position) {
  const rules = new Intl.PluralRules('en-US', { type: 'ordinal' });
  const category = rules.select(position);

  const suffixes = {
    one: 'st',
    two: 'nd',
    few: 'rd',
    other: 'th'
  };

  const suffix = suffixes[category];
  return `${position}${suffix} place`;
}

console.log(formatRanking(1));
// Output: "1st place"

console.log(formatRanking(42));
// Output: "42nd place"

날짜 형식은 때때로 월의 일자를 표시할 때 서수를 사용합니다. 일부 로케일에서는 "January 1" 대신 "January 1st"로 표기합니다.

단계별 지침은 각 단계를 번호로 표시할 때 서수를 사용합니다. 튜토리얼에서는 "1st step: 소프트웨어 설치", "2nd step: 설정 구성", "3rd step: 애플리케이션 시작"과 같이 표시합니다.

긴 시퀀스의 목록 항목은 단순한 열거보다 위치 강조가 더 중요할 때 서수 형식을 사용하면 유용합니다.

성능을 위한 규칙 객체 재사용

새로운 Intl.PluralRules 인스턴스를 생성하는 것은 로케일 데이터를 로드하고 옵션을 처리하는 작업을 포함합니다. 동일한 로케일로 여러 서수를 포맷팅할 때는 규칙 객체를 한 번 생성하고 재사용하세요.

const rules = new Intl.PluralRules('en-US', { type: 'ordinal' });

const suffixes = {
  one: 'st',
  two: 'nd',
  few: 'rd',
  other: 'th'
};

function formatOrdinal(number) {
  const category = rules.select(number);
  const suffix = suffixes[category];
  return `${number}${suffix}`;
}

const positions = [1, 2, 3, 4, 5];

positions.forEach(position => {
  console.log(formatOrdinal(position));
});
// Output:
// "1st"
// "2nd"
// "3rd"
// "4th"
// "5th"

이 접근 방식은 각 숫자마다 새로운 규칙 객체를 생성하는 것보다 더 효율적입니다. 수백 또는 수천 개의 값이 있는 배열을 포맷팅할 때 성능 차이가 크게 나타납니다.

특정 로케일에 맞게 구성된 함수를 반환하는 포맷터 팩토리를 만들 수도 있습니다.

function createOrdinalFormatter(locale, suffixMapping) {
  const rules = new Intl.PluralRules(locale, { type: 'ordinal' });

  return function(number) {
    const category = rules.select(number);
    const suffix = suffixMapping[category] || suffixMapping.other || '';
    return `${number}${suffix}`;
  };
}

const formatEnglishOrdinal = createOrdinalFormatter('en-US', {
  one: 'st',
  two: 'nd',
  few: 'rd',
  other: 'th'
});

console.log(formatEnglishOrdinal(1));
// Output: "1st"

console.log(formatEnglishOrdinal(2));
// Output: "2nd"

이 패턴은 규칙 객체와 접미사 매핑을 함께 캡슐화하여 애플리케이션 전체에서 포맷터를 쉽게 재사용할 수 있게 합니다.