Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[fix]: 초성, 중성, 종성을 사용하도록 통일 #219

Merged
merged 9 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Comment on lines 16 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

변수명이 변함에 따라 명확해져서 오해의 소지가 없어진 것 같아서 초성 중성 종성 주석은 제거되어도 될 것 같아요!

* ): 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