From 8a9ba364aa5debae204028b4b1cf1b2568575c0e Mon Sep 17 00:00:00 2001 From: seungrodotlee Date: Sat, 13 Jul 2024 00:02:16 +0900 Subject: [PATCH] =?UTF-8?q?fix.=20=EA=B2=B9=EB=AA=A8=EC=9D=8C=EA=B3=BC=20?= =?UTF-8?q?=EA=B4=80=EB=A0=A8=ED=95=98=EC=97=AC=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EB=A9=94=EC=86=8C=EB=93=9C=EC=97=90=EC=84=9C=20=EC=9E=98?= =?UTF-8?q?=EB=AA=BB=EB=90=9C=20=EB=8F=99=EC=9E=91=EC=9D=84=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=ED=95=A9=EB=8B=88=EB=8B=A4.=20(#156)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix. 겹모음과 관련된 이상 현상들 수정 * fix. fix index * fix. 오타 수정 Co-authored-by: 박찬혁 * fix. 오타 수정 Co-authored-by: 박찬혁 * fix. 업데이트 로직 반영 * test: internal 테스트 코드를 작성합니다 (#137) * test: internal 테스트 코드를 작성합니다 * test: vitest에서 제공해주는 toThrowError로 변경 --------- Co-authored-by: 박찬혁 * docs: josa에 관련된 문서에 잘못된 부분을 수정합니다 - '이에/에' 삭제 Co-authored-by: 박찬혁 * chore: version packages (#143) Co-authored-by: github-actions[bot] * feat: 한글 문자열에 대한 검증, assert, parsing 함수를 구현합니다. (#136) * feat: 한글 문자열인지 boolean 반환 함수 * feat: 한글 문자열인지 assert 함수 * feat: stringify * test: isHangulString, assertHangulString 테스트 코드 * feat: parse 함수 * test: parse 함수 테스트 * refactor: naming 통일 * feat : 문장의 각 단어 중 첫 문자만 뽑는 함수추가 ( #128 이슈에 대한 ) (#133) * feat : 문장의 각 단어 중 첫 문자만 뽑는 함수추가 * test 및 함수 추가 * add : 한글 문장인지 여부 판별 함수 추가 * fix : 한글 문장인지 여부 판별 기저 및 오류 추가 / arg 이름 변경 test 추가 * fix: src/_internal/hangul.ts Co-authored-by: 박찬혁 * fix : rename function getFirstHangulLetters -> getHangulAcronym * fix : rename function export function isHangulOnly로 변경 * fix : lint error * fix : index에 추가 * chore : doc 추가 * fix : 문서화 한글 영어 바뀐거 바로 변경 * fix : isHangul로 대체 #136 으로 * chore : doc수정 * Update docs/src/pages/docs/api/getHangulAcronym.en.mdx Co-authored-by: 박찬혁 * fix : Update hangul.ts 안쓰는 메소드 삭제 * Update getHangulAcronym.ko.mdx * Update getHangulAcronym.en.mdx * Update src/getHangulAcronym.ts Co-authored-by: 박찬혁 * Update src/getHangulAcronym.ts Co-authored-by: 박찬혁 * Update getHangulAcronym.ts * Update getHangulAcronym.spec.ts * Create fair-brooms-drive.md --------- Co-authored-by: 박찬혁 * chore: version packages (#145) Co-authored-by: github-actions[bot] * feat: 문자열에서 한글만 반환하는 extractHangul을 구현합니다. (#130) * feat: parseHangul * fix: parseHangul의 이름을 extractHangul로 수정 및 테스트 코드 보완 * test: 일관된 테스트 코드 작성이 될 수 있도록 describe 설명 수정 * test: 테스트 코드 수정 * docs: extractHangul의 문서 작성 * test: 테스트 문구 수정 * fix: index.ts에 export 추가 * Create fresh-students-sit.md --------- Co-authored-by: 박찬혁 * chore: version packages (#146) Co-authored-by: github-actions[bot] * fix : getHangulacronym함수를 acronymizeHangul 메서드로 대체합니다. (#148) * Update and rename getHangulAcronym.ts to acronymizeHangul.ts * Update and rename getHangulAcronym.spec.ts to acronymizeHangul.spec.ts * Update index.ts * Update and rename getHangulAcronym.en.mdx to acronymizeHangul.en.mdx * Update and rename getHangulAcronym.ko.mdx to acronymizeHangul.ko.mdx * Create odd-squids-sin.md --------- Co-authored-by: 박찬혁 * chore: version packages (#150) Co-authored-by: github-actions[bot] * fix: amountToHangul이 소수점, 숫자도 대응할 수 있도록 수정 (#144) * fix: amountToHangul이 소수점, 숫자도 대응할 수 있도록 수정 * docs: amountToHangul의 영어 문서 작성 * Create famous-cheetahs-sneeze.md * Update famous-cheetahs-sneeze.md --------- Co-authored-by: 박찬혁 * fix: packlint ESLint error (#149) * test: utils 기능들의 테스트 케이스를 개선합니다 (#151) * test: hasBatchim 함수의 테스트 케이스를 추가합니다 * test: hasSingleBatchim의 테스트 케이스를 추가합니다 * fix: nextra theme asset 개선 (#152) * chore: version packages (#153) Co-authored-by: github-actions[bot] * fix: 패키지가 노출하는 인터페이스를 명확히 하기 위해 index.ts를 named export로 수정합니다 (#157) * fix: exposing public apis strictly * Create wild-cows-juggle.md * chore: version packages (#160) Co-authored-by: github-actions[bot] * docs: CONTRIBUTING.md 파일에 메서드 컨벤션을 작성합니다. (#132) * Update CONTRIBUTING.md * fix * write contribution.md Co-authored-by: Jonghyeon Ko <61593290+manudeli@users.noreply.github.com> Co-authored-by: Dongkyu Kim <55759551+po4tion@users.noreply.github.com> * Update .github/CONTRIBUTING.md Co-authored-by: Jonghyeon Ko * Update .github/CONTRIBUTING.md Co-authored-by: Jonghyeon Ko * Update .github/CONTRIBUTING.md Co-authored-by: Jonghyeon Ko --------- Co-authored-by: Jonghyeon Ko <61593290+manudeli@users.noreply.github.com> Co-authored-by: Dongkyu Kim <55759551+po4tion@users.noreply.github.com> Co-authored-by: Jonghyeon Ko * test: amountToHangul 테스트 추가 (#135) * test: amountToHangul 테스트 추가 * test: amountToHangul 테스트 코드 수정 * test: amountToHangul 테스트 코드 스타일 변경 * test: amountToHangul test 개선 * fix: assert.throws대신 vitest toThrow로 메서드 변경 * fix: vitest toThrowError 메서드로 변경 --------- Co-authored-by: kinndohyun Co-authored-by: 박찬혁 * fix: 소수점 추가 후 발생한 '영'읽기 버그 수정 (#159) * fix: 소수점 추가 후 발생한 '영'읽기 버그 수정 * Create big-cups-call.md --------- Co-authored-by: 박찬혁 * docs: acronymizeHangul 리턴 타입 표기 수정 (#161) * chore: version packages (#163) Co-authored-by: github-actions[bot] * fix: 일관된 toThrowError 메서드 사용 (#166) Co-authored-by: kinndohyun * chore(eslint): 의도치 않은 naming을 제거하기 위해 cspell 추가 (#164) * chore(eslint): add cspell to check bad naming * chore: update * Create grumpy-singers-love.md --------- Co-authored-by: 박찬혁 Co-authored-by: Dongkyu Kim Co-authored-by: Jaemin Kim Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] Co-authored-by: seunghee Co-authored-by: taehyun <126179088+KNU-K@users.noreply.github.com> Co-authored-by: SeongMin Kim <86355699+Collection50@users.noreply.github.com> Co-authored-by: Jonghyeon Ko Co-authored-by: Jonghyeon Ko <61593290+manudeli@users.noreply.github.com> Co-authored-by: Dongkyu Kim <55759551+po4tion@users.noreply.github.com> Co-authored-by: 김도현 <101170386+fe-dudu@users.noreply.github.com> Co-authored-by: kinndohyun Co-authored-by: Song Hyo Jin Co-authored-by: wnhlee <40269597+2wheeh@users.noreply.github.com> --- .changeset/grumpy-singers-love.md | 5 +++++ src/_internal/hangul.spec.ts | 12 ++++++++++++ src/_internal/hangul.ts | 9 ++++++++- src/removeLastHangulCharacter.spec.ts | 16 +++++++++++++++- src/removeLastHangulCharacter.ts | 23 ++++++++++++++++++++--- src/utils.spec.ts | 9 +++++++++ src/utils.ts | 3 ++- 7 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 .changeset/grumpy-singers-love.md diff --git a/.changeset/grumpy-singers-love.md b/.changeset/grumpy-singers-love.md new file mode 100644 index 00000000..067c194e --- /dev/null +++ b/.changeset/grumpy-singers-love.md @@ -0,0 +1,5 @@ +--- +"es-hangul": patch +--- + +fix. 겹모음과 관련하여 일부 메소드에서 잘못된 동작을 수정합니다. diff --git a/src/_internal/hangul.spec.ts b/src/_internal/hangul.spec.ts index f09f88b2..150360f7 100644 --- a/src/_internal/hangul.spec.ts +++ b/src/_internal/hangul.spec.ts @@ -89,6 +89,14 @@ describe('binaryAssembleHangulCharacters', () => { expect(binaryAssembleHangulCharacters('고', 'ㅏ')).toEqual('과'); }); + it('초성과 중성(겹모음)이 합쳐진 문자와 자음을 조합', () => { + expect(binaryAssembleHangulCharacters('과', 'ㄱ')).toEqual('곽'); + }); + + it('초성과 중성(겹모음)과 종성이 합쳐진 문자와 자음을 조합하여 겹받침 만들기', () => { + expect(binaryAssembleHangulCharacters('완', 'ㅈ')).toEqual('왅'); + }); + it('모음만 있는 문자와 모음을 조합하여 겹모음 만들기', () => { expect(binaryAssembleHangulCharacters('ㅗ', 'ㅏ')).toEqual('ㅘ'); }); @@ -97,6 +105,10 @@ describe('binaryAssembleHangulCharacters', () => { expect(binaryAssembleHangulCharacters('톳', 'ㅡ')).toEqual('토스'); }); + it('초성과 종성(겹모음)과 종성이 합쳐진 문자의 연음 법칙', () => { + expect(binaryAssembleHangulCharacters('왅', 'ㅓ')).toEqual('완저'); + }); + it('초성과 중성과 종성(겹받침)이 합쳐진 문자의 연음 법칙', () => { expect(binaryAssembleHangulCharacters('닭', 'ㅏ')).toEqual('달가'); expect(binaryAssembleHangulCharacters('깎', 'ㅏ')).toEqual('까까'); diff --git a/src/_internal/hangul.ts b/src/_internal/hangul.ts index 1a746f5b..dcb5c757 100644 --- a/src/_internal/hangul.ts +++ b/src/_internal/hangul.ts @@ -118,6 +118,7 @@ export function binaryAssembleHangulCharacters(source: string, nextCharacter: st } const [restJamos, lastJamo] = excludeLastElement(sourceJamos); + const secondaryLastJamo = excludeLastElement(restJamos)[1]; const needLinking = canBeChosung(lastJamo) && canBeJungsung(nextCharacter); if (needLinking) { @@ -131,12 +132,18 @@ export function binaryAssembleHangulCharacters(source: string, nextCharacter: st return combineJungsung(`${lastJamo}${nextCharacter}`)(); } + if (canBeJungsung(`${secondaryLastJamo}${lastJamo}`) && canBeJongsung(nextCharacter)) { + return combineJungsung(`${secondaryLastJamo}${lastJamo}`)(nextCharacter); + } + if (canBeJungsung(lastJamo) && canBeJongsung(nextCharacter)) { return combineJungsung(lastJamo)(nextCharacter); } const fixVowel = combineJungsung; - const combineJongsung = fixVowel(restJamos[1]); + const combineJongsung = fixVowel( + canBeJungsung(`${restJamos[1]}${restJamos[2]}`) ? `${restJamos[1]}${restJamos[2]}` : restJamos[1] + ); const lastConsonant = lastJamo; diff --git a/src/removeLastHangulCharacter.spec.ts b/src/removeLastHangulCharacter.spec.ts index 0445bc43..49e74fb9 100644 --- a/src/removeLastHangulCharacter.spec.ts +++ b/src/removeLastHangulCharacter.spec.ts @@ -7,15 +7,29 @@ describe('removeLastHangulCharacter', () => { }); it('마지막 문자가 초성과 중성의 조합으로 끝날 경우 초성만 남긴다.', () => { expect(removeLastHangulCharacter('프론트엔드')).toBe('프론트엔ㄷ'); + expect(removeLastHangulCharacter('끓다')).toBe('끓ㄷ'); + expect(removeLastHangulCharacter('관사')).toBe('관ㅅ'); + expect(removeLastHangulCharacter('괴사')).toBe('괴ㅅ'); }); it('마지막 문자가 초성과 중성과 종성의 조합으로 끝날 경우 초성과 중성이 조합된 문자만 남긴다.', () => { expect(removeLastHangulCharacter('일요일')).toBe('일요이'); + expect(removeLastHangulCharacter('완전')).toBe('완저'); + expect(removeLastHangulCharacter('왅전')).toBe('왅저'); expect(removeLastHangulCharacter('깎')).toBe('까'); }); it('마지막 문자가 초성과 중성의 조합으로 끝나며, 중성 입력 시 국제 표준 한글 레이아웃 기준 단일키로 처리되지 않는 이중모음 (ㅗ/ㅜ/ㅡ 계 이중모음) 인 경우 초성과 중성의 시작 모음만 남긴다.', () => { expect(removeLastHangulCharacter('전화')).toBe('전호'); expect(removeLastHangulCharacter('예의')).toBe('예으'); - expect(removeLastHangulCharacter("신세계")).toBe('신세ㄱ'); // 'ㅖ'의 경우 단일키 처리가 가능한 이중모음이므로 모음이 남지 않는다. + expect(removeLastHangulCharacter('신세계')).toBe('신세ㄱ'); // 'ㅖ'의 경우 단일키 처리가 가능한 이중모음이므로 모음이 남지 않는다. + }); + it('마지막 문자가 초성과 중성과 종성의 조합으로 끝나며, 중성 입력 시 국제 표준 한글 레이아웃 기준 단일키로 처리되지 않는 이중모음 (ㅗ/ㅜ/ㅡ 계 이중모음) 인 경우 초성과 중성만 남긴다.', () => { + expect(removeLastHangulCharacter('수확')).toBe('수화'); + }); + it('마지막 문자가 초성과 중성과 종성의 조합으로 끝나며, 종성이 겹자음인 경우 초성과 중성과 종성의 시작 자음만 남긴다.', () => { + expect(removeLastHangulCharacter('끓')).toBe('끌'); + }); + it('마지막 문자가 초성과 중성과 종성의 조합으로 끝나며, 중성 입력 시 국제 표준 한글 레이아웃 기준 단일키로 처리되지 않는 이중모음 (ㅗ/ㅜ/ㅡ 계 이중모음)이고 종성이 겹자음인 경우 초성과 중성과 종성의 시작 자음만 남긴다.', () => { + expect(removeLastHangulCharacter('왅')).toBe('완'); }); it('빈 문자열일 경우 빈 문자열을 반환한다.', () => { expect(removeLastHangulCharacter('')).toBe(''); diff --git a/src/removeLastHangulCharacter.ts b/src/removeLastHangulCharacter.ts index 59838522..5ac9c780 100644 --- a/src/removeLastHangulCharacter.ts +++ b/src/removeLastHangulCharacter.ts @@ -1,6 +1,7 @@ import { combineHangulCharacter } from './combineHangulCharacter'; import { disassembleHangulToGroups } from './disassemble'; import { excludeLastElement } from './_internal'; +import { canBeJungsung } from './utils'; /** * @name removeLastHangulCharacter @@ -24,9 +25,25 @@ export function removeLastHangulCharacter(words: string) { if (lastCharacter == null) { return ''; } - const disassembleLastCharacter = disassembleHangulToGroups(lastCharacter); - const [[first, middle, last]] = excludeLastElement(disassembleLastCharacter[0]); - const result = middle != null ? combineHangulCharacter(first, middle, last) : first; + + const result = (() => { + const disassembleLastCharacter = disassembleHangulToGroups(lastCharacter); + const [lastCharacterWithoutLastAlphabet] = excludeLastElement(disassembleLastCharacter[0]); + if (lastCharacterWithoutLastAlphabet.length <= 3) { + const [first, middle, last] = lastCharacterWithoutLastAlphabet; + if (middle != null) { + return canBeJungsung(last) + ? combineHangulCharacter(first, `${middle}${last}`) + : combineHangulCharacter(first, middle, last); + } + + return first; + } else { + const [first, firstJungsung, secondJungsung, firstJongsung] = lastCharacterWithoutLastAlphabet; + + return combineHangulCharacter(first, `${firstJungsung}${secondJungsung}`, firstJongsung); + } + })(); return [words.substring(0, words.length - 1), result].join(''); } diff --git a/src/utils.spec.ts b/src/utils.spec.ts index 58dc04e4..3d08e2b7 100644 --- a/src/utils.spec.ts +++ b/src/utils.spec.ts @@ -54,17 +54,26 @@ describe('hasSingleBatchim', () => { expect(hasSingleBatchim('핫')).toBe(true); expect(hasSingleBatchim('양')).toBe(true); expect(hasSingleBatchim('신')).toBe(true); + expect(hasSingleBatchim('확')).toBe(true); }); describe('홑받침이 아니라고 판단되는 경우', () => { it('겹받침을 받으면 false를 반환한다.', () => { expect(hasSingleBatchim('값')).toBe(false); expect(hasSingleBatchim('읊')).toBe(false); + expect(hasSingleBatchim('웱')).toBe(false); }); it('받침이 없는 문자를 받으면 false를 반환한다.', () => { expect(hasSingleBatchim('토')).toBe(false); expect(hasSingleBatchim('서')).toBe(false); + expect(hasSingleBatchim('와')).toBe(false); + }); + + it('한글 외의 문자를 입력하면 false를 반환한다.', () => { + expect(hasSingleBatchim('cat')).toBe(false); + expect(hasSingleBatchim('')).toBe(false); + expect(hasSingleBatchim('?')).toBe(false); }); it('한글 외의 문자를 입력하면 false를 반환한다.', () => { diff --git a/src/utils.ts b/src/utils.ts index eb336371..e17b58c6 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -96,7 +96,8 @@ export function hasSingleBatchim(str: string) { * getChosung('띄어 쓰기') // 'ㄸㅇ ㅆㄱ' */ export function getChosung(word: string) { - return word.normalize('NFD') + 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 }