Skip to content
Merged
3 changes: 1 addition & 2 deletions benchmarks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,5 @@
},
"devDependencies": {
"@types/josa": "^3"
},
"version": null
}
}
36 changes: 36 additions & 0 deletions src/pronunciation/standardizePronunciation/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,39 @@ export const 된소리_받침 = [

// 24항, 25항
export const 어간_받침 = ['ㄴ', 'ㄴㅈ', 'ㅁ', 'ㄹㅁ', 'ㄹㅂ', 'ㄹㅌ'] as const;

/**
* @description 'ㄹㅁ'과 같은 복합 받침은 발음 과정에서 단순화될 수 있다.
* @see {@link https://encykorea.aks.ac.kr/Article/E0074523 자음군 단순화 설명 링크}
*/
export const 자음군_단순화 = [
'ㄹㅁ',
'ㄱㅅ',
'ㄹㄱ',
'ㄹㅂ',
'ㄹㅅ',
'ㅂㅅ',
'ㄴㅈ',
'ㄴㅎ',
'ㄹㅌ',
'ㄹㅍ',
'ㄹㅎ',
] as const;

export const 자음군_단순화_결과 = {
ㄱㅅ: 'ㄱ',
ㄴㅈ: 'ㄴ',
ㄴㅎ: 'ㄴ',
ㄹㄱ: 'ㄱ',
ㄹㅁ: 'ㅁ',
ㄹㅂ: 'ㄹ', // 상황에 따라 'ㅂ'이 남기도 하지만, 기본은 'ㄹ'로 정리
ㄹㅅ: 'ㄹ',
ㄹㅌ: 'ㄹ',
ㄹㅍ: 'ㄹ',
ㄹㅎ: 'ㄹ',
ㅂㅅ: 'ㅂ',
} as const;

export const 단일어_예외사항_단어모음: Record<string, string> = {
Copy link
Member

Choose a reason for hiding this comment

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

요 상수가 추가된 맥락을 문서에 정리할 수 있을까요?
이러한 예외사항은 타입스크립트로 판별이 불가능해서 예외로 뺐고, 추가가 필요하면 기여를 해달라고 하면 될 것 같습니다!
이미 PR Description에 잘 작성해주신 내용을 잘 녹여내면 될 것 같아요,.

Copy link
Collaborator Author

@po4tion po4tion May 11, 2025

Choose a reason for hiding this comment

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

image

fix: 단일어 예외사항 상수가 추가된 맥락을 문서에 작성합니다
docs: 표준발음법 영문 문서 추가

예외사항을 문서에 정리해봤습니다. 혹시 문서에 적힌 어투나 개선되어야 할 표현이 있다면 말씀 부탁드립니다!

Copy link
Member

Choose a reason for hiding this comment

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

수도권: '수도꿘'

도 비슷하게 여기에 추가되면 좋을까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

다음과 같은 방법을 제안해봅니다.

배경

  1. '수도권'은 된소리되기 규칙에 따라 '수도꿘'으로 발음되지만, 이는 말하기의 편의성이나 관습적인 사용이 누적되어 굳어진 특이한 사례입니다.
  2. 반면, '전역'처럼 파생어, 합성어인지 구분하지 못해 잘못된 발음이 발생하는 경우는 그 사유가 '수도권'과 다릅니다.

제안 방향

const 파생어_합성어_예외사항 = {
  전역: '저녁'
};

const 관습적_예외사항 = {
  수도권: '수도꿘'
};

위에 선언한 상수들처럼 예외 사유에 따라 단어군을 분류해 정리할 것을 제안합니다.

목적

  1. 예외처리 상수에 여러 사유가 뒤섞여 있으면 예외 단어가 확장될수록 관리와 히스토리 트래킹이 어려워질 것 같다고 판단했습니다.
  2. 예외 사유별로 명확히 분류하여 향후 유지보수성과 확장성을 높이는 것을 목표로 합니다.

전역: '저녁',
};
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,30 @@ describe('transformHardConversion', () => {
},
});
});

it('예외사항 - 현재 음절이 경음화를 적용할 수 있는 받침에 해당하지만, 다음 음절의 받침이 "자음군 단순화" 음운현상이 생긴다면 된소리로 발음하지 않는다', () => {
const current = defined(disassembleCompleteCharacter('힘'));
const next = defined(disassembleCompleteCharacter('듦'));

expect(transformHardConversion(current, next)).toEqual({
next: {
choseong: 'ㄷ',
jungseong: 'ㅡ',
jongseong: 'ㄹㅁ',
},
});
});

it('번외 - 현재 음절의 받침이 "자음군 단순화" 대상에 해당하지만, 다음 음절의 초성이 "음가가 없는 자음"일 경우에는 된소리로 발음하지 않는다', () => {
const current = defined(disassembleCompleteCharacter('삶'));
const next = defined(disassembleCompleteCharacter('은'));

expect(transformHardConversion(current, next)).toEqual({
next: {
choseong: 'ㅇ',
jungseong: 'ㅡ',
jongseong: 'ㄴ',
},
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { arrayIncludes, hasProperty } from '@/_internal';
import { 된소리, 된소리_받침, 어간_받침 } from '../constants';
import { 된소리, 된소리_받침, 어간_받침, 자음군_단순화 } from '../constants';
import type { ReturnSyllables, Syllable } from './rules.types';

/**
Expand All @@ -17,6 +17,15 @@ export function transformHardConversion(
const next = { ...nextSyllable };

if (hasProperty(된소리, next.choseong)) {
// [예외 처리]
// 다음 글자의 종성(받침)이 자음군 단순화 대상에 해당할 경우,
// 자음군이 단순화되면서 남은 받침이 비음화되거나 연음되어,
// 일반적인 된소리 현상이 발생하지 않는다.
// 따라서 이 경우에는 된소리 변화를 적용하지 않고 그대로 반환한다.
if (arrayIncludes(자음군_단순화, next.jongseong)) {
return { next };
}

const 제23항조건 = arrayIncludes(된소리_받침, currentSyllable.jongseong);
const 제24_25항조건 = arrayIncludes(어간_받침, currentSyllable.jongseong) && next.choseong !== 'ㅂ';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('transformNLAssimilation', () => {
});
});

it('ㄴ/ㄹ이 되기 위한 조건이지만 현재 음절의 중성의 ∙(아래아)가 하나가 아닐 경우에는 덧나지 않고 연음규칙이 적용된다', () => {
it('예외사항 - ㄴ/ㄹ이 되기 위한 조건이지만 현재 음절의 중성의 ∙(아래아)가 하나가 아닐 경우에는 덧나지 않고 연음규칙이 적용된다', () => {
const current = defined(disassembleCompleteCharacter('양'));
const next = defined(disassembleCompleteCharacter('이'));

Expand All @@ -56,4 +56,22 @@ describe('transformNLAssimilation', () => {
},
});
});

it('예외사항 - ㄴ/ㄹ이 되기 위한 조건이면서 현재 음절의 중성의 ∙(아래아)가 하나가 아닐 경우지만, 현재 종성이 "자음군 단순화"의 대상이라면 연음규칙이 적용되지 않고 둘 중 하나의 자음만 남고 나머지 자음은 탈락한다', () => {
const current = defined(disassembleCompleteCharacter('듦'));
const next = defined(disassembleCompleteCharacter('이'));

expect(transformNLAssimilation(current, next)).toEqual({
current: {
choseong: 'ㄷ',
jungseong: 'ㅡ',
jongseong: 'ㅁ',
},
next: {
choseong: 'ㅇ',
jungseong: 'ㅣ',
jongseong: '',
},
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import {
ㄴㄹ이_덧나는_후속음절_모음,
ㄴㄹ이_덧나서_받침_ㄴ_변환,
ㄴㄹ이_덧나서_받침_ㄹ_변환,
자음군_단순화,
자음군_단순화_결과,
} from '../constants';
import type { ReturnSyllables, Syllable } from './rules.types';

/**
* 'ㄴ,ㄹ'이 덧나는 경우(동화작용)를 적용합니다.
* @description 합성어에서 둘째 요소가 '야, 여, 요, 유, 얘, 예' 등으로 시작되는 말이면 'ㄴ, ㄹ'이 덧난다
* @description 합성어에서 둘째 요소가 '야, 여, 요, 유, 이, 얘, 예' 등으로 시작되는 말이면 'ㄴ, ㄹ'이 덧난다
* @link https://www.youtube.com/watch?v=Mm2JX2naqWk
* @link http://contents2.kocw.or.kr/KOCW/data/document/2020/seowon/choiyungon0805/12.pdf
* @param currentSyllable 현재 음절을 입력합니다.
Expand Down Expand Up @@ -51,8 +53,13 @@ function applyㄴㄹ덧남(current: Syllable, next: Syllable): ReturnSyllables {
updatedNext.choseong = 'ㄹ';
}
} else {
// ㄴ/ㄹ이 되기 위한 조건이지만 현재 음절의 중성의 ∙(아래아)가 하나가 아닐 경우에는 덧나지 않고 연음규칙이 적용된다
updatedNext.choseong = updatedCurrent.jongseong as typeof updatedNext.choseong;
if (arrayIncludes(자음군_단순화, updatedCurrent.jongseong)) {
// ㄴ/ㄹ이 되기 위한 조건이면서 현재 음절의 중성의 ∙(아래아)가 하나가 아닐 경우지만, 현재 종성이 "자음군 단순화"의 대상이라면 연음규칙이 적용되지 않고 둘 중 하나의 자음만 남고 나머지 자음은 탈락한다
updatedCurrent.jongseong = 자음군_단순화_결과[updatedCurrent.jongseong];
} else {
// ㄴ/ㄹ이 되기 위한 조건이지만 현재 음절의 중성의 ∙(아래아)가 하나가 아닐 경우에는 덧나지 않고 연음규칙이 적용된다
updatedNext.choseong = updatedCurrent.jongseong as typeof updatedNext.choseong;
}
}

return { current: updatedCurrent, next: updatedNext };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ describe('standardizePronunciation', () => {
expect(standardizePronunciation('고양이')).toBe('고양이');
expect(standardizePronunciation('윤여정')).toBe('윤녀정');
});

it('ㄴ/ㄹ이 되기 위한 조건이면서 현재 음절의 중성의 ∙(아래아)가 하나가 아닐 경우지만, 현재 종성이 "자음군 단순화"의 대상이라면 연음규칙이 적용되지 않고 둘 중 하나의 자음만 남고 나머지 자음은 탈락한다', () => {
expect(standardizePronunciation('힘듦이 있다')).toBe('힘드미 읻따');
});
});

describe('19항', () => {
Expand Down Expand Up @@ -351,4 +355,10 @@ describe('standardizePronunciation', () => {
).toBe('널게');
});
});

describe('단일어 예외사항 단어는 단어모음에서 바로 반환한다', () => {
it('전역', () => {
expect(standardizePronunciation('전역')).toBe('저녁');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
type Nullable,
type Syllable,
} from './rules';
import { 단일어_예외사항_단어모음 } from './constants';

type Options = {
hardConversion: boolean;
Expand All @@ -38,6 +39,10 @@ export function standardizePronunciation(hangul: string, options: Options = { ha
return '';
}

if (hangul in 단일어_예외사항_단어모음) {
return 단일어_예외사항_단어모음[hangul];
}

const processSyllables = (syllables: Syllable[], phrase: string, options: Options) =>
syllables.map((currentSyllable, index, array) => {
const nextSyllable = index < array.length - 1 ? array[index + 1] : null;
Expand Down Expand Up @@ -125,6 +130,7 @@ function applyRules(params: ApplyParameters): {
({ current, next } = transform17th(current, next));
({ next } = transform19th(current, next));
({ current, next } = transformNLAssimilation(current, next));

({ current } = transform18th(current, next));
({ current, next } = transform20th(current, next));
}
Expand Down
Loading