Skip to content

Commit

Permalink
fix: 초성, 중성, 종성을 사용하도록 통일 (#219)
Browse files Browse the repository at this point in the history
* fix: 초성, 중성, 종성을 사용하도록 통일

* fix: 표기 수정

* fix: 표기 수정

* fix: 표기 수정

* fix: v2 반영

* fix: 불필요한 파일 삭제

* fix: import 수정

* fix: 테스트 코드 수정
  • Loading branch information
Collection50 authored Aug 9, 2024
1 parent 65e2c7c commit 08adfc9
Show file tree
Hide file tree
Showing 33 changed files with 349 additions and 307 deletions.
18 changes: 7 additions & 11 deletions src/canBe.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { hasValueInReadOnlyStringList } from './_internal';
import {
HANGUL_CHARACTERS_BY_FIRST_INDEX,
HANGUL_CHARACTERS_BY_LAST_INDEX,
HANGUL_CHARACTERS_BY_MIDDLE_INDEX,
} from './constants';
import { CHOSEONGS, JONGSEONGS, JUNSEONGS } from './constants';

/**
* @name canBeChoseong
Expand All @@ -22,8 +18,8 @@ import {
* canBeChoseong('ㅏ') // false
* canBeChoseong('가') // false
*/
export function canBeChoseong(character: string): character is (typeof HANGUL_CHARACTERS_BY_FIRST_INDEX)[number] {
return hasValueInReadOnlyStringList(HANGUL_CHARACTERS_BY_FIRST_INDEX, character);
export function canBeChoseong(character: string): character is (typeof CHOSEONGS)[number] {
return hasValueInReadOnlyStringList(CHOSEONGS, character);
}

/**
Expand All @@ -44,8 +40,8 @@ export function canBeChoseong(character: string): character is (typeof HANGUL_CH
* canBeJungseong('ㄱㅅ') // false
* canBeJungseong('가') // false
*/
export function canBeJungseong(character: string): character is (typeof HANGUL_CHARACTERS_BY_MIDDLE_INDEX)[number] {
return hasValueInReadOnlyStringList(HANGUL_CHARACTERS_BY_MIDDLE_INDEX, character);
export function canBeJungseong(character: string): character is (typeof JUNSEONGS)[number] {
return hasValueInReadOnlyStringList(JUNSEONGS, character);
}

/**
Expand All @@ -66,6 +62,6 @@ export function canBeJungseong(character: string): character is (typeof HANGUL_C
* canBeJongseong('ㅏ') // false
* canBeJongseong('ㅗㅏ') // false
*/
export function canBeJongseong(character: string): character is (typeof HANGUL_CHARACTERS_BY_LAST_INDEX)[number] {
return hasValueInReadOnlyStringList(HANGUL_CHARACTERS_BY_LAST_INDEX, character);
export function canBeJongseong(character: string): character is (typeof JONGSEONGS)[number] {
return hasValueInReadOnlyStringList(JONGSEONGS, character);
}
47 changes: 21 additions & 26 deletions src/combineCharacter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { canBeChoseong, canBeJongseong, canBeJungseong } from './canBe';
import {
COMPLETE_HANGUL_START_CHARCODE,
DISASSEMBLED_VOWELS_BY_VOWEL,
HANGUL_CHARACTERS_BY_FIRST_INDEX,
HANGUL_CHARACTERS_BY_LAST_INDEX,
HANGUL_CHARACTERS_BY_MIDDLE_INDEX,
CHOSEONGS,
JONGSEONGS,
JUNSEONGS,
} from './constants';

/**
Expand All @@ -14,38 +14,33 @@ import {
* ```typescript
* combineCharacter(
* // 초성
* firstCharacter: string
* choseong: string
* // 중성
* middleCharacter: string
* jungseong: string
* // 종성
* lastCharacter: string
* jongseong: string
* ): string
* ```
* @example
* combineCharacter('ㄱ', 'ㅏ', 'ㅂㅅ') // '값'
* combineCharacter('ㅌ', 'ㅗ') // '토'
*/
export function combineCharacter(firstCharacter: string, middleCharacter: string, lastCharacter = '') {
if (
canBeChoseong(firstCharacter) === false ||
canBeJungseong(middleCharacter) === false ||
canBeJongseong(lastCharacter) === false
) {
throw new Error(`Invalid hangul Characters: ${firstCharacter}, ${middleCharacter}, ${lastCharacter}`);
export function combineCharacter(choseong: string, jungseong: string, jongseong = '') {
if (canBeChoseong(choseong) === false || canBeJungseong(jungseong) === false || canBeJongseong(jongseong) === false) {
throw new Error(`Invalid hangul Characters: ${choseong}, ${jungseong}, ${jongseong}`);
}

const numOfMiddleCharacters = HANGUL_CHARACTERS_BY_MIDDLE_INDEX.length;
const numOfLastCharacters = HANGUL_CHARACTERS_BY_LAST_INDEX.length;
const numOfJungseongs = JUNSEONGS.length;
const numOfJongseongs = JONGSEONGS.length;

const firstCharacterIndex = HANGUL_CHARACTERS_BY_FIRST_INDEX.indexOf(firstCharacter);
const middleCharacterIndex = HANGUL_CHARACTERS_BY_MIDDLE_INDEX.indexOf(middleCharacter);
const lastCharacterIndex = HANGUL_CHARACTERS_BY_LAST_INDEX.indexOf(lastCharacter);
const choseongIndex = CHOSEONGS.indexOf(choseong as (typeof CHOSEONGS)[number]);
const jungseongIndex = JUNSEONGS.indexOf(jungseong as (typeof JUNSEONGS)[number]);
const jongseongIndex = JONGSEONGS.indexOf(jongseong as (typeof JONGSEONGS)[number]);

const firstIndexOfTargetConsonant = firstCharacterIndex * numOfMiddleCharacters * numOfLastCharacters;
const firstIndexOfTargetVowel = middleCharacterIndex * numOfLastCharacters;
const choseongOfTargetConsonant = choseongIndex * numOfJungseongs * numOfJongseongs;
const choseongOfTargetVowel = jungseongIndex * numOfJongseongs;

const unicode =
COMPLETE_HANGUL_START_CHARCODE + firstIndexOfTargetConsonant + firstIndexOfTargetVowel + lastCharacterIndex;
const unicode = COMPLETE_HANGUL_START_CHARCODE + choseongOfTargetConsonant + choseongOfTargetVowel + jongseongIndex;

return String.fromCharCode(unicode);
}
Expand All @@ -60,10 +55,10 @@ export function combineCharacter(firstCharacter: string, middleCharacter: string
* combineLastHangulCharacter('ㄱ') // '각'
*/
export const curriedCombineCharacter =
(firstCharacter: string) =>
(middleCharacter: string) =>
(lastCharacter = '') =>
combineCharacter(firstCharacter, middleCharacter, lastCharacter);
(choseong: string) =>
(jungseong: string) =>
(jongseong = '') =>
combineCharacter(choseong, jungseong, jongseong);

/**
* @name combineVowels
Expand Down
6 changes: 3 additions & 3 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const DISASSEMBLED_VOWELS_BY_VOWEL = {
/**
* 초성으로 올 수 있는 한글 글자
*/
export const HANGUL_CHARACTERS_BY_FIRST_INDEX = [
export const CHOSEONGS = [
'ㄱ',
'ㄲ',
'ㄴ',
Expand All @@ -104,12 +104,12 @@ export const HANGUL_CHARACTERS_BY_FIRST_INDEX = [
/**
* 중성으로 올 수 있는 한글 글자
*/
export const HANGUL_CHARACTERS_BY_MIDDLE_INDEX = Object.values(DISASSEMBLED_VOWELS_BY_VOWEL);
export const JUNSEONGS = Object.values(DISASSEMBLED_VOWELS_BY_VOWEL);

/**
* 종성으로 올 수 있는 한글 글자
*/
export const HANGUL_CHARACTERS_BY_LAST_INDEX = (
export const JONGSEONGS = (
[
'',
'ㄱ',
Expand Down
6 changes: 5 additions & 1 deletion src/disassemble.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export function disassembleToGroups(str: string) {
const disassembledComplete = disassembleCompleteCharacter(letter);

if (disassembledComplete != null) {
result.push([...disassembledComplete.first, ...disassembledComplete.middle, ...disassembledComplete.last]);
result.push([
...disassembledComplete.choseong,
...disassembledComplete.jungseong,
...disassembledComplete.jongseong,
]);
continue;
}

Expand Down
24 changes: 12 additions & 12 deletions src/disassembleCompleteCharacter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,33 @@ import { disassembleCompleteCharacter } from './disassembleCompleteCharacter';
describe('disassembleCompleteCharacter', () => {
it('값', () => {
expect(disassembleCompleteCharacter('값')).toEqual({
first: 'ㄱ',
middle: 'ㅏ',
last: 'ㅂㅅ',
choseong: 'ㄱ',
jungseong: 'ㅏ',
jongseong: 'ㅂㅅ',
});
});

it('리', () => {
expect(disassembleCompleteCharacter('리')).toEqual({
first: 'ㄹ',
middle: 'ㅣ',
last: '',
choseong: 'ㄹ',
jungseong: 'ㅣ',
jongseong: '',
});
});

it('빚', () => {
expect(disassembleCompleteCharacter('빚')).toEqual({
first: 'ㅂ',
middle: 'ㅣ',
last: 'ㅈ',
choseong: 'ㅂ',
jungseong: 'ㅣ',
jongseong: 'ㅈ',
});
});

it('박', () => {
expect(disassembleCompleteCharacter('박')).toEqual({
first: 'ㅂ',
middle: 'ㅏ',
last: 'ㄱ',
choseong: 'ㅂ',
jungseong: 'ㅏ',
jongseong: 'ㄱ',
});
});

Expand Down
32 changes: 16 additions & 16 deletions src/disassembleCompleteCharacter.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {
COMPLETE_HANGUL_END_CHARCODE,
COMPLETE_HANGUL_START_CHARCODE,
HANGUL_CHARACTERS_BY_FIRST_INDEX,
HANGUL_CHARACTERS_BY_LAST_INDEX,
HANGUL_CHARACTERS_BY_MIDDLE_INDEX,
CHOSEONGS,
JONGSEONGS,
JUNSEONGS,
NUMBER_OF_JONGSEONG,
NUMBER_OF_JUNGSEONG,
} from './constants';

interface ReturnTypeDisassembleCompleteCharacter {
first: (typeof HANGUL_CHARACTERS_BY_FIRST_INDEX)[number];
middle: (typeof HANGUL_CHARACTERS_BY_MIDDLE_INDEX)[number];
last: (typeof HANGUL_CHARACTERS_BY_LAST_INDEX)[number];
choseong: (typeof CHOSEONGS)[number];
jungseong: (typeof JUNSEONGS)[number];
jongseong: (typeof JONGSEONGS)[number];
}

/**
Expand All @@ -22,10 +22,10 @@ interface ReturnTypeDisassembleCompleteCharacter {
* @param {string} letter 분리하고자 하는 완전한 한글 문자열
*
* @example
* disassembleCompleteCharacter('값') // { first: 'ㄱ', middle: 'ㅏ', last: 'ㅂㅅ' }
* disassembleCompleteCharacter('리') // { first: 'ㄹ', middle: 'ㅣ', last: '' }
* disassembleCompleteCharacter('빚') // { first: 'ㅂ', middle: 'ㅣ', last: 'ㅈ' }
* disassembleCompleteCharacter('박') // { first: 'ㅂ', middle: 'ㅏ', last: 'ㄱ' }
* disassembleCompleteCharacter('값') // { choseong: 'ㄱ', jungseong: 'ㅏ', jongseong: 'ㅂㅅ' }
* disassembleCompleteCharacter('리') // { choseong: 'ㄹ', jungseong: 'ㅣ', jongseong: '' }
* disassembleCompleteCharacter('빚') // { choseong: 'ㅂ', jungseong: 'ㅣ', jongseong: 'ㅈ' }
* disassembleCompleteCharacter('박') // { choseong: 'ㅂ', jungseong: 'ㅏ', jongseong: 'ㄱ' }
*/

export function disassembleCompleteCharacter(letter: string): ReturnTypeDisassembleCompleteCharacter | undefined {
Expand All @@ -39,13 +39,13 @@ export function disassembleCompleteCharacter(letter: string): ReturnTypeDisassem

const hangulCode = charCode - COMPLETE_HANGUL_START_CHARCODE;

const lastIndex = hangulCode % NUMBER_OF_JONGSEONG;
const middleIndex = ((hangulCode - lastIndex) / NUMBER_OF_JONGSEONG) % NUMBER_OF_JUNGSEONG;
const firstIndex = Math.floor((hangulCode - lastIndex) / NUMBER_OF_JONGSEONG / NUMBER_OF_JUNGSEONG);
const jongseongIndex = hangulCode % NUMBER_OF_JONGSEONG;
const jungseongIndex = ((hangulCode - jongseongIndex) / NUMBER_OF_JONGSEONG) % NUMBER_OF_JUNGSEONG;
const choseongIndex = Math.floor((hangulCode - jongseongIndex) / NUMBER_OF_JONGSEONG / NUMBER_OF_JUNGSEONG);

return {
first: HANGUL_CHARACTERS_BY_FIRST_INDEX[firstIndex],
middle: HANGUL_CHARACTERS_BY_MIDDLE_INDEX[middleIndex],
last: HANGUL_CHARACTERS_BY_LAST_INDEX[lastIndex],
choseong: CHOSEONGS[choseongIndex],
jungseong: JUNSEONGS[jungseongIndex],
jongseong: JONGSEONGS[jongseongIndex],
} as const;
}
40 changes: 40 additions & 0 deletions src/disassembleCompleteHangulCharacter.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { disassembleCompleteCharacter } from './disassembleCompleteCharacter';

describe('disassembleCompleteCharacter', () => {
it('값', () => {
expect(disassembleCompleteCharacter('값')).toEqual({
choseong: 'ㄱ',
jungseong: 'ㅏ',
jongseong: 'ㅂㅅ',
});
});

it('리', () => {
expect(disassembleCompleteCharacter('리')).toEqual({
choseong: 'ㄹ',
jungseong: 'ㅣ',
jongseong: '',
});
});

it('빚', () => {
expect(disassembleCompleteCharacter('빚')).toEqual({
choseong: 'ㅂ',
jungseong: 'ㅣ',
jongseong: 'ㅈ',
});
});

it('박', () => {
expect(disassembleCompleteCharacter('박')).toEqual({
choseong: 'ㅂ',
jungseong: 'ㅏ',
jongseong: 'ㄱ',
});
});

it('완전한 한글 문자열이 아니면 undefined를 반환해야 합니다.', () => {
expect(disassembleCompleteCharacter('ㄱ')).toBeUndefined;
expect(disassembleCompleteCharacter('ㅏ')).toBeUndefined;
});
});
4 changes: 2 additions & 2 deletions src/getChoseong.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HANGUL_CHARACTERS_BY_FIRST_INDEX, JASO_HANGUL_NFD } from './constants';
import { CHOSEONGS, JASO_HANGUL_NFD } from './constants';

/**
* @name getChoseong
Expand All @@ -18,7 +18,7 @@ export function getChoseong(word: string) {
return word
.normalize('NFD')
.replace(EXTRACT_CHOSEONG_REGEX, '') // NFD ㄱ-ㅎ, NFC ㄱ-ㅎ 외 문자 삭제
.replace(CHOOSE_NFD_CHOSEONG_REGEX, $0 => HANGUL_CHARACTERS_BY_FIRST_INDEX[$0.charCodeAt(0) - 0x1100]); // NFD to NFC
.replace(CHOOSE_NFD_CHOSEONG_REGEX, $0 => CHOSEONGS[$0.charCodeAt(0) - 0x1100]); // NFD to NFC
}

const EXTRACT_CHOSEONG_REGEX = new RegExp(
Expand Down
6 changes: 3 additions & 3 deletions src/hasBatchim.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
COMPLETE_HANGUL_END_CHARCODE,
COMPLETE_HANGUL_START_CHARCODE,
HANGUL_CHARACTERS_BY_LAST_INDEX,
JONGSEONGS,
NUMBER_OF_JONGSEONG,
} from './constants';

Expand Down Expand Up @@ -50,11 +50,11 @@ export function hasBatchim(
const batchimCode = (charCode - COMPLETE_HANGUL_START_CHARCODE) % NUMBER_OF_JONGSEONG;

if (options?.only === 'single') {
return HANGUL_CHARACTERS_BY_LAST_INDEX[batchimCode].length === 1;
return JONGSEONGS[batchimCode].length === 1;
}

if (options?.only === 'double') {
return HANGUL_CHARACTERS_BY_LAST_INDEX[batchimCode].length === 2;
return JONGSEONGS[batchimCode].length === 2;
}

return batchimCode > 0;
Expand Down
2 changes: 1 addition & 1 deletion src/josa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function josaPicker(word: string, josa: JosaOption): string {
const has받침 = hasBatchim(word);
let index = has받침 ? 0 : 1;

const is종성ㄹ = disassembleCompleteCharacter(word[word.length - 1])?.last === 'ㄹ';
const is종성ㄹ = disassembleCompleteCharacter(word[word.length - 1])?.jongseong === 'ㄹ';

const isCaseOf로 = has받침 && is종성ㄹ && 로_조사.includes(josa);

Expand Down
11 changes: 6 additions & 5 deletions src/romanize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@ const romanizeSyllableHangul = (arrayHangul: string[], index: number): string =>
ReturnType<typeof disassembleCompleteCharacter>
>;

let choseong: (typeof 초성_알파벳_발음)[keyof typeof 초성_알파벳_발음] | 'l' = 초성_알파벳_발음[disassemble.first];
const jungseong = 중성_알파벳_발음[assemble([disassemble.middle]) as keyof typeof 중성_알파벳_발음];
const jongseong = 종성_알파벳_발음[disassemble.last as keyof typeof 종성_알파벳_발음];
let choseong: (typeof 초성_알파벳_발음)[keyof typeof 초성_알파벳_발음] | 'l' =
초성_알파벳_발음[disassemble.choseong];
const jungseong = 중성_알파벳_발음[assemble([disassemble.jungseong]) as keyof typeof 중성_알파벳_발음];
const jongseong = 종성_알파벳_발음[disassemble.jongseong as keyof typeof 종성_알파벳_발음];

// 'ㄹ'은 모음 앞에서는 'r'로, 자음 앞이나 어말에서는 'l'로 적는다. 단, 'ㄹㄹ'은 'll'로 적는다. (ex.울릉, 대관령),
if (disassemble.first === 'ㄹ' && index > 0 && isHangulCharacter(arrayHangul[index - 1])) {
if (disassemble.choseong === 'ㄹ' && index > 0 && isHangulCharacter(arrayHangul[index - 1])) {
const prevDisassemble = disassembleCompleteCharacter(arrayHangul[index - 1]);

if (prevDisassemble?.last === 'ㄹ') {
if (prevDisassemble?.jongseong === 'ㄹ') {
choseong = 'l';
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/standardizePronunciation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ function applyRules(params: ApplyParameters): {
function assembleChangedHangul(disassembleHangul: Syllable[], notHangulPhrase: NotHangul[]): string {
const changedSyllables = disassembleHangul
.filter(isNotUndefined)
.map(syllable => combineCharacter(syllable.first, syllable.middle, syllable.last));
.map(syllable => combineCharacter(syllable.choseong, syllable.jungseong, syllable.jongseong));

for (const { index, syllable } of notHangulPhrase) {
changedSyllables.splice(index, 0, syllable);
Expand Down
4 changes: 2 additions & 2 deletions src/standardizePronunciation/rules/rules.utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Syllable } from './rules.types';

export function replace받침ㅎ(currentSyllable: Syllable): Syllable['last'] {
return currentSyllable.last.replace('ㅎ', '') as Syllable['last'];
export function replace받침ㅎ(currentSyllable: Syllable): Syllable['jongseong'] {
return currentSyllable.jongseong.replace('ㅎ', '') as Syllable['jongseong'];
}
Loading

0 comments on commit 08adfc9

Please sign in to comment.