diff --git a/.changeset/modern-trains-change.md b/.changeset/modern-trains-change.md new file mode 100644 index 00000000..4540d1bb --- /dev/null +++ b/.changeset/modern-trains-change.md @@ -0,0 +1,5 @@ +--- +"es-hangul": patch +--- + +feat: 숫자를 순 우리말 서수사로 변환하는 함수 추가 diff --git a/docs/src/pages/docs/api/seosusa.en.mdx b/docs/src/pages/docs/api/seosusa.en.mdx new file mode 100644 index 00000000..b2187e65 --- /dev/null +++ b/docs/src/pages/docs/api/seosusa.en.mdx @@ -0,0 +1,48 @@ +--- +title: seosusa +--- + +import { Sandpack } from '@/components/Sandpack'; + +# susa + +Convert numbers to Korean ordinal words. +Integers from 1 to 99 are converted to native Korean ordinal words. +Integers greater than 100 are converted to Sino-Korean ordinal words. + +```typescript +function seosusa( + // Number to convert + num: number +): string; +``` + +## Examples + +```typescript +seosusa(1); // '첫째' +seosusa(2); // '둘째' +seosusa(3); // '셋째' +seosusa(10); // '열째' +seosusa(11); // '열한째' +seosusa(12); // '열두째' +seosusa(13); // '열셋째' +seosusa(20); // '스무째' +seosusa(21); // '스물한째' +seosusa(99); // '아흔아홉째' +seosusa(100); // '백째' +``` + +## Demo + +
+ + + +```ts index.ts +import { seosusa } from 'es-hangul'; + +console.log(seosusa(1)); +``` + + diff --git a/docs/src/pages/docs/api/seosusa.ko.mdx b/docs/src/pages/docs/api/seosusa.ko.mdx new file mode 100644 index 00000000..35ead58f --- /dev/null +++ b/docs/src/pages/docs/api/seosusa.ko.mdx @@ -0,0 +1,48 @@ +--- +title: seosusa +--- + +import { Sandpack } from '@/components/Sandpack'; + +# seosusa + +숫자를 한글 서수사로 변환합니다. +1부터 99까지의 정수는 순우리말 서수사 문자열로 변환합니다. +100 이상의 정수는 한자어 서수사 문자열로 변환합니다. + +```typescript +function seosusa( + // 변환할 숫자 + num: number +): string; +``` + +## Examples + +```typescript +seosusa(1); // '첫째' +seosusa(2); // '둘째' +seosusa(3); // '셋째' +seosusa(10); // '열째' +seosusa(11); // '열한째' +seosusa(12); // '열두째' +seosusa(13); // '열셋째' +seosusa(20); // '스무째' +seosusa(21); // '스물한째' +seosusa(99); // '아흔아홉째' +seosusa(100); // '백째' +``` + +## 사용해보기 + +
+ + + +```ts index.ts +import { seosusa } from 'es-hangul'; + +console.log(seosusa(1)); +``` + + diff --git a/src/seosusa/constants.ts b/src/seosusa/constants.ts new file mode 100644 index 00000000..ab797ab5 --- /dev/null +++ b/src/seosusa/constants.ts @@ -0,0 +1,27 @@ +export const SEOSUSA_MAP = { + 1: '한', + 2: '두', + 3: '셋', + 4: '넷', + 5: '다섯', + 6: '여섯', + 7: '일곱', + 8: '여덟', + 9: '아홉', + 10: '열', + 20: '스물', + 30: '서른', + 40: '마흔', + 50: '쉰', + 60: '예순', + 70: '일흔', + 80: '여든', + 90: '아흔', + 100: '백', +}; + +export const SEOSUSA_SPECIAL_CASE_MAP = { + 1: '첫', + 2: '둘', + 20: '스무', +}; diff --git a/src/seosusa/index.ts b/src/seosusa/index.ts new file mode 100644 index 00000000..161aab56 --- /dev/null +++ b/src/seosusa/index.ts @@ -0,0 +1 @@ +export * from './seosusa'; diff --git a/src/seosusa/seosusa.spec.ts b/src/seosusa/seosusa.spec.ts new file mode 100644 index 00000000..5c31c27b --- /dev/null +++ b/src/seosusa/seosusa.spec.ts @@ -0,0 +1,44 @@ +import { seosusa } from './seosusa'; + +describe('seosusa', () => { + const validNumbers = [ + { num: 1, word: '첫째' }, + { num: 2, word: '둘째' }, + { num: 3, word: '셋째' }, // '셋째'가 표준어이고 '세째'는 비표준어이다.(표준어 사정 원칙 제6항) + { num: 4, word: '넷째' }, // '넷째'가 표준어이고 '네째'는 비표준어이다.(표준어 사정 원칙 제6항) + { num: 5, word: '다섯째' }, + { num: 6, word: '여섯째' }, + { num: 7, word: '일곱째' }, + { num: 8, word: '여덟째' }, + { num: 9, word: '아홉째' }, + { num: 10, word: '열째' }, + { num: 11, word: '열한째' }, + { num: 12, word: '열두째' }, // '둘째'는 십 단위 이상의 서수사에 쓰일 때에 '두째'로 한다.(표준어 사정 원칙 제6항) + { num: 13, word: '열셋째' }, + { num: 14, word: '열넷째' }, + { num: 15, word: '열다섯째' }, + { num: 20, word: '스무째' }, + { num: 21, word: '스물한째' }, + { num: 22, word: '스물두째' }, + { num: 30, word: '서른째' }, + { num: 40, word: '마흔째' }, + { num: 90, word: '아흔째' }, + { num: 99, word: '아흔아홉째' }, + { num: 100, word: '백째' }, + { num: 101, word: '백일째' }, + ]; + + const invalidNumbers = [0, -1, 1.1, -1.1, Infinity, -Infinity, NaN]; + + validNumbers.forEach(({ num, word }) => { + it(`${num} - 순 우리말 서수사로 변환한다.`, () => { + expect(seosusa(num)).toBe(word); + }); + }); + + invalidNumbers.forEach(num => { + it(`${num} - 유효하지 않은 숫자에 대해 오류를 발생시켜야 한다.`, () => { + expect(() => seosusa(num)).toThrow('유효하지 않은 입력입니다. 1이상의 정수만 지원합니다.'); + }); + }); +}); diff --git a/src/seosusa/seosusa.ts b/src/seosusa/seosusa.ts new file mode 100644 index 00000000..d32d7cf7 --- /dev/null +++ b/src/seosusa/seosusa.ts @@ -0,0 +1,62 @@ +import { numberToHangul } from '@/numberToHangul'; +import { hasProperty } from '../_internal'; +import { SEOSUSA_MAP, SEOSUSA_SPECIAL_CASE_MAP } from './constants'; + +/** + * 숫자를 한글 서수사로 변환합니다. + * + * @remarks + * - **서수사**는 순서를 나타내는 단어입니다. + * - 1부터 99까지의 정수는 순우리말 서수사 문자열로 변환합니다. + * - 100 이상의 정수는 한자어 서수사 문자열로 변환합니다. + * + * @param num - 변환할 숫자 + * @return 변환된 서수사 문자열 + * @throws {Error} 지원하지 않는 숫자인 경우 + * + * @example + * seosusa(1); // '첫째' + * seosusa(2); // '둘째' + * seosusa(3); // '셋째' + * seosusa(10); // '열째' + * seosusa(11); // '열한째' + * seosusa(12); // '열두째' + * seosusa(13); // '열셋째' + * seosusa(20); // '스무째' + * seosusa(21); // '스물한째' + * seosusa(30); // '서른째' + * seosusa(40); // '마흔째' + * seosusa(99); // '아흔아홉째' + * seosusa(100); // '백째' + * + * @see https://es-hangul.slash.page/docs/api/seosusa + */ +export function seosusa(num: number): string { + if (num === 0 || !Number.isInteger(num)) { + throw new Error('유효하지 않은 입력입니다. 1이상의 정수만 지원합니다.'); + } + + if (num >= 1 && num <= 99) { + return `${getOrdinalWord(num)}째`; + } + + try { + return `${numberToHangul(num)}째`; + } catch (error) { + throw new Error('유효하지 않은 입력입니다. 1이상의 정수만 지원합니다.'); + } +} + +function getOrdinalWord(num: number): string { + if (hasProperty(SEOSUSA_SPECIAL_CASE_MAP, num)) { + return SEOSUSA_SPECIAL_CASE_MAP[num]; + } + + const tens = Math.floor(num / 10) * 10; + const ones = num % 10; + + const tensWord = hasProperty(SEOSUSA_MAP, tens) ? SEOSUSA_MAP[tens] : ''; + const onesWord = hasProperty(SEOSUSA_MAP, ones) ? SEOSUSA_MAP[ones] : ''; + + return `${tensWord}${onesWord}`; +}