kanabarum 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Kanabarum
2
+
3
+ 일본어 가나 문자열을 한국어 발음 표기로 바꿔주는 TypeScript 라이브러리입니다.
4
+ `kuromoji` 품사 분석을 이용해 は/へ 같은 조사를 안전하게 치환합니다.
5
+
6
+ ## 설치
7
+
8
+ ```bash
9
+ pnpm add kanabarum
10
+ # 또는
11
+ npm install kanabarum
12
+ ```
13
+
14
+ ## 사용법
15
+
16
+ ### 간편 호출
17
+
18
+ 초기화 없이 즉시 사용도 가능합니다.
19
+
20
+ ```ts
21
+ import { kanaToHangul } from "kanabarum";
22
+
23
+ const text = await kanaToHangul("さようなら");
24
+ // => "사요나라"
25
+ ```
26
+
27
+ ### 초기화 후 호출
28
+
29
+ ```ts
30
+ import { KanaBarum } from "kanabarum";
31
+
32
+ const converter = await KanaBarum.init();
33
+
34
+ // 인사말
35
+ converter("おはよう"); // => "오하요"
36
+ converter("こんにちは"); // => "콘니치와"
37
+ converter("こんばんは"); // => "콤방와"
38
+ converter("ありがとう"); // => "아리가토"
39
+ converter("すみません"); // => "스미마셍"
40
+
41
+ // 요음
42
+ converter("きゃく"); // => "캬쿠"
43
+ converter("しゅくだい"); // => "슈쿠다이"
44
+ converter("ちょっと"); // => "춋토"
45
+ converter("きゅう"); // => "큐"
46
+ converter("りょこう"); // => "료코"
47
+
48
+ // 촉음
49
+ converter("きって"); // => "킷테"
50
+ converter("がっこう"); // => "각코"
51
+ converter("けっこん"); // => "켓콘"
52
+ converter("ざっし"); // => "잣시"
53
+ converter("やっちゃった"); // => "얏챳타"
54
+
55
+ // ん 규칙
56
+ converter("にゃんこ"); // => "냥코"
57
+ converter("さんぽ"); // => "삼포"
58
+ converter("しんぶん"); // => "심분"
59
+ converter("りんご"); // => "링고"
60
+ converter("まんいち"); // => "만이치"
61
+
62
+ // 조사치환 は → わ
63
+ converter("わたしはがくせいです"); // => "와타시와가쿠세데스"
64
+ converter("これはペンです"); // => "코레와펜데스"
65
+ converter("きょうはあつい"); // => "쿄와아츠이"
66
+
67
+ // 조사치환 へ → え
68
+ converter("がっこうへいく"); // => "각코에이쿠"
69
+ converter("うちへかえる"); // => "우치에카에루"
70
+
71
+ // 장모음(おう/よう/えい) 축약
72
+ converter("さようなら"); // => "사요나라"
73
+ converter("せんせい"); // => "센세"
74
+ converter("おおさか"); // => "오사카"
75
+
76
+ // 가타카나
77
+ converter("カタカナ"); // => "카타카나"
78
+ converter("コーヒー"); // => "코히"
79
+ converter("アイドル"); // => "아이도루"
80
+
81
+ // 장음 기호 변형(ー variants)
82
+ converter("コーヒー"); // => "코히"
83
+ converter("パーティー"); // => "파티"
84
+ converter("ゲーム"); // => "게무"
85
+
86
+ // 커스텀 사전
87
+ converter("とうきょう"); // => "도쿄"
88
+ converter("すみません"); // => "스미마셍"
89
+ converter("こんばんは"); // => "콤방와"
90
+
91
+ // 한자포함
92
+ converter("誕生日(たんじょうび)"); // => "誕生日(탄죠비)"
93
+ converter("第3回(だいさんかい)"); // => "第3回(다이상카이)"
94
+ converter("京都(きょうと)"); // => "京都(쿄토)"
95
+
96
+ // 특수문자, 마침표
97
+ converter("コーヒー, ください。"); // => "코히, 쿠다사이。"
98
+ converter("「きょう」"); // => "「쿄」"
99
+ converter("(がっこう)"); // => "(각코)"
100
+
101
+ // 전각/반각
102
+ converter("ハングル"); // => "한구루"
103
+ converter("カタカナ"); // => "카타카나"
104
+ converter("ト-キョ-"); // => "토ー쿄ー"
105
+
106
+ // NFC 합성
107
+ converter("がくせい"); // => "가쿠세"
108
+ converter("ぱーてぃー"); // => "파티"
109
+ converter("べんごし"); // => "벵고시"
110
+
111
+
112
+ ```
@@ -0,0 +1,9 @@
1
+ type KanaToHangul = (input: string) => string;
2
+
3
+ declare function initKanaToHangul(): Promise<KanaToHangul>;
4
+ declare function kanaToHangul(input: string): Promise<string>;
5
+ declare const KanaBarum: Readonly<{
6
+ init: typeof initKanaToHangul;
7
+ }>;
8
+
9
+ export { KanaBarum, type KanaToHangul, KanaBarum as KanaToHangulMaker, kanaToHangul };
package/dist/index.js ADDED
@@ -0,0 +1,650 @@
1
+ import kuromoji from 'kuromoji';
2
+ import path from 'path';
3
+ import { createRequire } from 'module';
4
+
5
+ // src/normalizer.ts
6
+ function normalizeInputText(input) {
7
+ let normalized = input.normalize("NFC");
8
+ normalized = normalizeHalfwidthKatakanaOnly(normalized);
9
+ normalized = normalized.replace(/[\u2015\u2500]/g, "\u30FC");
10
+ normalized = normalized.replace(/(?<=([\u30A0-\u30FF]))-/g, "\u30FC").replace(/-(?=[\u30A0-\u30FF])/g, "\u30FC");
11
+ return normalized;
12
+ }
13
+ function normalizeHalfwidthKatakanaOnly(s) {
14
+ return s.replace(
15
+ /[\uFF66-\uFF9F\uFF70]+/g,
16
+ (chunk) => chunk.normalize("NFKC")
17
+ );
18
+ }
19
+ function toHiragana(input) {
20
+ let out = "";
21
+ for (const ch of input) {
22
+ const code = ch.codePointAt(0);
23
+ if (code >= 12449 && code <= 12534) {
24
+ out += String.fromCodePoint(code - 96);
25
+ continue;
26
+ }
27
+ if (ch === "\u30FC") {
28
+ out += ch;
29
+ continue;
30
+ }
31
+ out += ch;
32
+ }
33
+ return out;
34
+ }
35
+
36
+ // src/particleRewriter.ts
37
+ var HARD_BOUNDARY_SURF = /* @__PURE__ */ new Set([
38
+ "\u3002",
39
+ "\u3001",
40
+ "\uFF01",
41
+ "\uFF1F",
42
+ "!",
43
+ "?",
44
+ " ",
45
+ "\u3000"
46
+ ]);
47
+ var HARD_BOUNDARY_DETAIL1 = /* @__PURE__ */ new Set([
48
+ "\u53E5\u70B9",
49
+ "\u8AAD\u70B9",
50
+ "\u62EC\u5F27\u958B",
51
+ "\u62EC\u5F27\u9589",
52
+ "\u7A7A\u767D"
53
+ ]);
54
+ var LEXICAL_HE_ENDINGS = [
55
+ "\u3044\u306B\u3057\u3078",
56
+ "\u304A\u304D\u3078",
57
+ "\u3082\u3068\u3078",
58
+ "\u3059\u3048\u3078",
59
+ "\u3059\u3091\u3078",
60
+ "\u304B\u307F\u3078",
61
+ "\u304F\u306B\u3078",
62
+ "\u304D\u3057\u3078"
63
+ ];
64
+ var isSingleKana = (x) => x.length === 1 && /^[\u3040-\u309F\u30A0-\u30FF]$/.test(x);
65
+ function rewriteParticlesWithKuromoji(text, tokenizer) {
66
+ const tokens = tokenizer.tokenize(text);
67
+ let out = "";
68
+ for (let i = 0; i < tokens.length; i += 1) {
69
+ const token = tokens[i];
70
+ const surf = token.surface_form;
71
+ let replaced = surf;
72
+ if (token.pos === "\u52A9\u8A5E" && surf === "\u306F") {
73
+ if (i > 0 && tokens[i - 1].surface_form === "\u306F") {
74
+ out += surf;
75
+ continue;
76
+ }
77
+ if (i + 1 < tokens.length && tokens[i + 1].surface_form === "\u306F") {
78
+ out += surf;
79
+ continue;
80
+ }
81
+ const prevIdx = prevContentIdx(tokens, i);
82
+ if (prevIdx >= 0) {
83
+ const nextIdx = nextContentIdx(tokens, i);
84
+ const hasNextContent = nextIdx >= 0;
85
+ const isEndOrPunct = nextBoundaryOrEnd(tokens, i);
86
+ const prev = tokens[prevIdx];
87
+ if (!prev.surface_form.includes("\u3063") && token.pos_detail_1 === "\u4FC2\u52A9\u8A5E" && (hasNextContent || isEndOrPunct) && prev.pos !== "\u52A9\u8A5E") {
88
+ out += "\u308F";
89
+ continue;
90
+ }
91
+ }
92
+ out += surf;
93
+ continue;
94
+ }
95
+ if (token.pos === "\u52A9\u8A5E" && surf === "\u3078") {
96
+ const prevIdx = prevContentIdx(tokens, i);
97
+ if (prevIdx >= 0) {
98
+ const nextIdx = nextContentIdx(tokens, i);
99
+ const hasNextContent = nextIdx >= 0;
100
+ const isEndOrPunct = nextBoundaryOrEnd(tokens, i);
101
+ const prevSurf = tokens[prevIdx].surface_form;
102
+ if (LEXICAL_HE_ENDINGS.some((w) => (prevSurf + "\u3078").endsWith(w))) ; else if (prevSurf.endsWith("\u306E")) ; else if (token.pos_detail_1 === "\u683C\u52A9\u8A5E") {
103
+ const nextPos = hasNextContent ? tokens[nextIdx].pos : "";
104
+ const looksDirectionalByVerb = nextPos === "\u52D5\u8A5E" || nextPos === "\u52A9\u52D5\u8A5E";
105
+ const nextSurf = hasNextContent ? tokens[nextIdx].surface_form : "";
106
+ const nextIsSingleKana = hasNextContent && isSingleKana(nextSurf);
107
+ if (!nextIsSingleKana && (looksDirectionalByVerb || isEndOrPunct)) {
108
+ replaced = "\u3048";
109
+ }
110
+ }
111
+ }
112
+ }
113
+ out += replaced;
114
+ }
115
+ return out;
116
+ }
117
+ function isHardBoundaryToken(t) {
118
+ if (t.pos !== "\u8A18\u53F7")
119
+ return false;
120
+ if (HARD_BOUNDARY_SURF.has(t.surface_form))
121
+ return true;
122
+ return HARD_BOUNDARY_DETAIL1.has(t.pos_detail_1 ?? "");
123
+ }
124
+ function isContentToken(t) {
125
+ if (t.pos === "\u8A18\u53F7")
126
+ return !isHardBoundaryToken(t);
127
+ return true;
128
+ }
129
+ function prevContentIdx(tokens, i) {
130
+ for (let j = i - 1; j >= 0; j -= 1)
131
+ if (isContentToken(tokens[j]))
132
+ return j;
133
+ return -1;
134
+ }
135
+ function nextContentIdx(tokens, i) {
136
+ for (let j = i + 1; j < tokens.length; j += 1)
137
+ if (isContentToken(tokens[j]))
138
+ return j;
139
+ return -1;
140
+ }
141
+ function nextBoundaryOrEnd(tokens, i) {
142
+ for (let j = i + 1; j < tokens.length; j += 1) {
143
+ if (isHardBoundaryToken(tokens[j]))
144
+ continue;
145
+ return false;
146
+ }
147
+ return true;
148
+ }
149
+
150
+ // src/coreConverter.ts
151
+ function coreKanaToHangulConvert(s) {
152
+ const SPECIAL = [
153
+ ["\u3068\u3046\u304D\u3087\u3046", "\uB3C4\uCFC4"],
154
+ ["\u3044\u3044\u3067\u3057\u3087\u3046\u304B", "\uC774\uB370\uC1FC\uCE74"],
155
+ ["\u3044\u3044\u3067\u3057\u3087\u3046", "\uC774\uB370\uC1FC"],
156
+ ["\u3053\u3093\u306B\u3061\u306F", "\uCF58\uB2C8\uCE58\uC640"],
157
+ ["\u3053\u3093\u3070\u3093\u306F", "\uCF64\uBC29\uC640"],
158
+ ["\u3059\u307F\u307E\u305B\u3093", "\uC2A4\uBBF8\uB9C8\uC14D"]
159
+ ];
160
+ const HANGUL_BASE = 44032;
161
+ const HANGUL_END = 55203;
162
+ function isHangulSyllable(ch) {
163
+ const c = ch.codePointAt(0);
164
+ return c >= HANGUL_BASE && c <= HANGUL_END;
165
+ }
166
+ const JONG = {
167
+ G: 1,
168
+ // ㄱ
169
+ N: 4,
170
+ // ㄴ
171
+ M: 16,
172
+ // ㅁ
173
+ B: 17,
174
+ // ㅂ
175
+ S: 19,
176
+ // ㅅ
177
+ NG: 21
178
+ // ㅇ
179
+ };
180
+ function addFinal(syl, jong) {
181
+ if (!isHangulSyllable(syl))
182
+ return syl;
183
+ const code = syl.codePointAt(0) - HANGUL_BASE;
184
+ const cho = Math.floor(code / 588);
185
+ const jung = Math.floor(code % 588 / 28);
186
+ return String.fromCodePoint(HANGUL_BASE + cho * 588 + jung * 28 + jong);
187
+ }
188
+ function replaceLastHangul(out2, jong) {
189
+ if (!out2)
190
+ return out2;
191
+ const last = out2[out2.length - 1];
192
+ if (!isHangulSyllable(last))
193
+ return out2;
194
+ return out2.slice(0, -1) + addFinal(last, jong);
195
+ }
196
+ function isHiragana(ch) {
197
+ const c = ch.codePointAt(0);
198
+ return c >= 12352 && c <= 12447;
199
+ }
200
+ function isKana(ch) {
201
+ return isHiragana(ch) || ch === "\u30FC";
202
+ }
203
+ const SINGLE = {
204
+ \u3042: { out: "\uC544", vowelMain: "a", consClass: "vowel", vowelOnly: true },
205
+ \u3044: { out: "\uC774", vowelMain: "i", consClass: "vowel", vowelOnly: true },
206
+ \u3046: { out: "\uC6B0", vowelMain: "u", consClass: "vowel", vowelOnly: true },
207
+ \u3048: { out: "\uC5D0", vowelMain: "e", consClass: "vowel", vowelOnly: true },
208
+ \u304A: { out: "\uC624", vowelMain: "o", consClass: "vowel", vowelOnly: true },
209
+ \u304B: { out: "\uCE74", vowelMain: "a", consClass: "k" },
210
+ \u304D: { out: "\uD0A4", vowelMain: "i", consClass: "k" },
211
+ \u304F: { out: "\uCFE0", vowelMain: "u", consClass: "k" },
212
+ \u3051: { out: "\uCF00", vowelMain: "e", consClass: "k" },
213
+ \u3053: { out: "\uCF54", vowelMain: "o", consClass: "k" },
214
+ \u3055: { out: "\uC0AC", vowelMain: "a", consClass: "s" },
215
+ \u3057: { out: "\uC2DC", vowelMain: "i", consClass: "s" },
216
+ \u3059: { out: "\uC2A4", vowelMain: "u", consClass: "s" },
217
+ \u305B: { out: "\uC138", vowelMain: "e", consClass: "s" },
218
+ \u305D: { out: "\uC18C", vowelMain: "o", consClass: "s" },
219
+ \u305F: { out: "\uD0C0", vowelMain: "a", consClass: "t" },
220
+ \u3061: { out: "\uCE58", vowelMain: "i", consClass: "t" },
221
+ \u3064: { out: "\uCE20", vowelMain: "u", consClass: "t" },
222
+ \u3066: { out: "\uD14C", vowelMain: "e", consClass: "t" },
223
+ \u3068: { out: "\uD1A0", vowelMain: "o", consClass: "t" },
224
+ \u306A: { out: "\uB098", vowelMain: "a", consClass: "n" },
225
+ \u306B: { out: "\uB2C8", vowelMain: "i", consClass: "n" },
226
+ \u306C: { out: "\uB204", vowelMain: "u", consClass: "n" },
227
+ \u306D: { out: "\uB124", vowelMain: "e", consClass: "n" },
228
+ \u306E: { out: "\uB178", vowelMain: "o", consClass: "n" },
229
+ \u306F: { out: "\uD558", vowelMain: "a", consClass: "h" },
230
+ \u3072: { out: "\uD788", vowelMain: "i", consClass: "h" },
231
+ \u3075: { out: "\uD6C4", vowelMain: "u", consClass: "h" },
232
+ \u3078: { out: "\uD5E4", vowelMain: "e", consClass: "h" },
233
+ \u307B: { out: "\uD638", vowelMain: "o", consClass: "h" },
234
+ \u307E: { out: "\uB9C8", vowelMain: "a", consClass: "m" },
235
+ \u307F: { out: "\uBBF8", vowelMain: "i", consClass: "m" },
236
+ \u3080: { out: "\uBB34", vowelMain: "u", consClass: "m" },
237
+ \u3081: { out: "\uBA54", vowelMain: "e", consClass: "m" },
238
+ \u3082: { out: "\uBAA8", vowelMain: "o", consClass: "m" },
239
+ \u3084: { out: "\uC57C", vowelMain: "a", consClass: "y" },
240
+ \u3086: { out: "\uC720", vowelMain: "u", consClass: "y" },
241
+ \u3088: { out: "\uC694", vowelMain: "o", consClass: "y" },
242
+ \u3089: { out: "\uB77C", vowelMain: "a", consClass: "r" },
243
+ \u308A: { out: "\uB9AC", vowelMain: "i", consClass: "r" },
244
+ \u308B: { out: "\uB8E8", vowelMain: "u", consClass: "r" },
245
+ \u308C: { out: "\uB808", vowelMain: "e", consClass: "r" },
246
+ \u308D: { out: "\uB85C", vowelMain: "o", consClass: "r" },
247
+ \u308F: { out: "\uC640", vowelMain: "a", consClass: "w" },
248
+ \u3092: { out: "\uC624", vowelMain: "o", consClass: "w" },
249
+ \u304C: { out: "\uAC00", vowelMain: "a", consClass: "g" },
250
+ \u304E: { out: "\uAE30", vowelMain: "i", consClass: "g" },
251
+ \u3050: { out: "\uAD6C", vowelMain: "u", consClass: "g" },
252
+ \u3052: { out: "\uAC8C", vowelMain: "e", consClass: "g" },
253
+ \u3054: { out: "\uACE0", vowelMain: "o", consClass: "g" },
254
+ \u3056: { out: "\uC790", vowelMain: "a", consClass: "z" },
255
+ \u3058: { out: "\uC9C0", vowelMain: "i", consClass: "z" },
256
+ \u305A: { out: "\uC988", vowelMain: "u", consClass: "z" },
257
+ \u305C: { out: "\uC81C", vowelMain: "e", consClass: "z" },
258
+ \u305E: { out: "\uC870", vowelMain: "o", consClass: "z" },
259
+ \u3060: { out: "\uB2E4", vowelMain: "a", consClass: "d" },
260
+ \u3062: { out: "\uC9C0", vowelMain: "i", consClass: "d" },
261
+ \u3065: { out: "\uC988", vowelMain: "u", consClass: "d" },
262
+ \u3067: { out: "\uB370", vowelMain: "e", consClass: "d" },
263
+ \u3069: { out: "\uB3C4", vowelMain: "o", consClass: "d" },
264
+ \u3070: { out: "\uBC14", vowelMain: "a", consClass: "b" },
265
+ \u3073: { out: "\uBE44", vowelMain: "i", consClass: "b" },
266
+ \u3076: { out: "\uBD80", vowelMain: "u", consClass: "b" },
267
+ \u3079: { out: "\uBCA0", vowelMain: "e", consClass: "b" },
268
+ \u307C: { out: "\uBCF4", vowelMain: "o", consClass: "b" },
269
+ \u3071: { out: "\uD30C", vowelMain: "a", consClass: "p" },
270
+ \u3074: { out: "\uD53C", vowelMain: "i", consClass: "p" },
271
+ \u3077: { out: "\uD478", vowelMain: "u", consClass: "p" },
272
+ \u307A: { out: "\uD398", vowelMain: "e", consClass: "p" },
273
+ \u307D: { out: "\uD3EC", vowelMain: "o", consClass: "p" },
274
+ // 이거는 う에 탁점 붙인 유니코드임.
275
+ \u3094: { out: "\uBD80", vowelMain: "u", consClass: "b" }
276
+ };
277
+ const YOUON = {
278
+ \u304D\u3083: { out: "\uCEAC", vowelMain: "a", consClass: "k", wasYouon: true },
279
+ \u304D\u3085: { out: "\uD050", vowelMain: "u", consClass: "k", wasYouon: true },
280
+ \u304D\u3087: { out: "\uCFC4", vowelMain: "o", consClass: "k", wasYouon: true },
281
+ \u3057\u3083: { out: "\uC0E4", vowelMain: "a", consClass: "s", wasYouon: true },
282
+ \u3057\u3085: { out: "\uC288", vowelMain: "u", consClass: "s", wasYouon: true },
283
+ \u3057\u3087: { out: "\uC1FC", vowelMain: "o", consClass: "s", wasYouon: true },
284
+ \u3061\u3083: { out: "\uCC60", vowelMain: "a", consClass: "t", wasYouon: true },
285
+ \u3061\u3085: { out: "\uCE04", vowelMain: "u", consClass: "t", wasYouon: true },
286
+ \u3061\u3087: { out: "\uCD78", vowelMain: "o", consClass: "t", wasYouon: true },
287
+ \u3066\u3085: { out: "\uD29C", vowelMain: "u", consClass: "t", wasYouon: true },
288
+ \u3067\u3085: { out: "\uB4C0", vowelMain: "u", consClass: "d", wasYouon: true },
289
+ \u306B\u3083: { out: "\uB0D0", vowelMain: "a", consClass: "n", wasYouon: true },
290
+ \u306B\u3085: { out: "\uB274", vowelMain: "u", consClass: "n", wasYouon: true },
291
+ \u306B\u3087: { out: "\uB1E8", vowelMain: "o", consClass: "n", wasYouon: true },
292
+ \u3072\u3083: { out: "\uD590", vowelMain: "a", consClass: "h", wasYouon: true },
293
+ \u3072\u3085: { out: "\uD734", vowelMain: "u", consClass: "h", wasYouon: true },
294
+ \u3072\u3087: { out: "\uD6A8", vowelMain: "o", consClass: "h", wasYouon: true },
295
+ \u3075\u3083: { out: "\uD344", vowelMain: "a", consClass: "p", wasYouon: true },
296
+ \u3075\u3085: { out: "\uD4E8", vowelMain: "u", consClass: "p", wasYouon: true },
297
+ \u3075\u3087: { out: "\uD45C", vowelMain: "o", consClass: "p", wasYouon: true },
298
+ \u307F\u3083: { out: "\uBA00", vowelMain: "a", consClass: "m", wasYouon: true },
299
+ \u307F\u3085: { out: "\uBBA4", vowelMain: "u", consClass: "m", wasYouon: true },
300
+ \u307F\u3087: { out: "\uBB18", vowelMain: "o", consClass: "m", wasYouon: true },
301
+ \u308A\u3083: { out: "\uB7B4", vowelMain: "a", consClass: "r", wasYouon: true },
302
+ \u308A\u3085: { out: "\uB958", vowelMain: "u", consClass: "r", wasYouon: true },
303
+ \u308A\u3087: { out: "\uB8CC", vowelMain: "o", consClass: "r", wasYouon: true },
304
+ \u304E\u3083: { out: "\uAC38", vowelMain: "a", consClass: "g", wasYouon: true },
305
+ \u304E\u3085: { out: "\uADDC", vowelMain: "u", consClass: "g", wasYouon: true },
306
+ \u304E\u3087: { out: "\uAD50", vowelMain: "o", consClass: "g", wasYouon: true },
307
+ \u3058\u3083: { out: "\uC7C8", vowelMain: "a", consClass: "z", wasYouon: true },
308
+ \u3058\u3085: { out: "\uC96C", vowelMain: "u", consClass: "z", wasYouon: true },
309
+ \u3058\u3087: { out: "\uC8E0", vowelMain: "o", consClass: "z", wasYouon: true },
310
+ \u3073\u3083: { out: "\uBC4C", vowelMain: "a", consClass: "b", wasYouon: true },
311
+ \u3073\u3085: { out: "\uBDF0", vowelMain: "u", consClass: "b", wasYouon: true },
312
+ \u3073\u3087: { out: "\uBD64", vowelMain: "o", consClass: "b", wasYouon: true },
313
+ \u3074\u3083: { out: "\uD344", vowelMain: "a", consClass: "p", wasYouon: true },
314
+ \u3074\u3085: { out: "\uD4E8", vowelMain: "u", consClass: "p", wasYouon: true },
315
+ \u3074\u3087: { out: "\uD45C", vowelMain: "o", consClass: "p", wasYouon: true }
316
+ };
317
+ const LOAN = {
318
+ \u3066\u3043: { out: "\uD2F0", vowelMain: "i", consClass: "t" },
319
+ \u3067\u3043: { out: "\uB514", vowelMain: "i", consClass: "d" },
320
+ \u3061\u3047: { out: "\uCCB4", vowelMain: "e", consClass: "t" },
321
+ \u3057\u3047: { out: "\uC170", vowelMain: "e", consClass: "s" },
322
+ \u3058\u3047: { out: "\uC81C", vowelMain: "e", consClass: "z" },
323
+ \u3064\u3041: { out: "\uCC28", vowelMain: "a", consClass: "t" },
324
+ \u3064\u3043: { out: "\uCE58", vowelMain: "i", consClass: "t" },
325
+ \u3064\u3047: { out: "\uCCB4", vowelMain: "e", consClass: "t" },
326
+ \u3064\u3049: { out: "\uCD08", vowelMain: "o", consClass: "t" },
327
+ \u3075\u3041: { out: "\uD30C", vowelMain: "a", consClass: "p" },
328
+ \u3075\u3043: { out: "\uD53C", vowelMain: "i", consClass: "p" },
329
+ \u3075\u3047: { out: "\uD398", vowelMain: "e", consClass: "p" },
330
+ \u3075\u3049: { out: "\uD3EC", vowelMain: "o", consClass: "p" },
331
+ \u3050\u3041: { out: "\uACFC", vowelMain: "a", consClass: "g" },
332
+ \u3050\u3043: { out: "\uADC0", vowelMain: "i", consClass: "g" },
333
+ \u3050\u3047: { out: "\uADA4", vowelMain: "e", consClass: "g" },
334
+ \u3050\u3049: { out: "\uAD88", vowelMain: "o", consClass: "g" },
335
+ \u304F\u3041: { out: "\uCF70", vowelMain: "a", consClass: "k" },
336
+ \u304F\u3043: { out: "\uD034", vowelMain: "i", consClass: "k" },
337
+ \u304F\u3047: { out: "\uD018", vowelMain: "e", consClass: "k" },
338
+ \u304F\u3049: { out: "\uCFFC", vowelMain: "o", consClass: "k" },
339
+ \u3069\u3041: { out: "\uB3E0", vowelMain: "a", consClass: "d" },
340
+ \u3069\u3045: { out: "\uB450", vowelMain: "u", consClass: "d" },
341
+ \u3069\u3049: { out: "\uB46C", vowelMain: "o", consClass: "d" },
342
+ \u3094\u3041: { out: "\uBC14", vowelMain: "a", consClass: "b" },
343
+ \u3094\u3043: { out: "\uBE44", vowelMain: "i", consClass: "b" },
344
+ \u3094\u3047: { out: "\uBCA0", vowelMain: "e", consClass: "b" },
345
+ \u3094\u3049: { out: "\uBCF4", vowelMain: "o", consClass: "b" }
346
+ };
347
+ const SMALL_Y = /* @__PURE__ */ new Set(["\u3083", "\u3085", "\u3087"]);
348
+ const SMALL_V = /* @__PURE__ */ new Set(["\u3041", "\u3043", "\u3045", "\u3047", "\u3049"]);
349
+ const U_DROP_KEYS = /* @__PURE__ */ new Set([
350
+ "\u3086",
351
+ "\u304D\u3085",
352
+ "\u3057\u3085",
353
+ "\u3061\u3085",
354
+ "\u306B\u3085",
355
+ "\u3072\u3085",
356
+ "\u307F\u3085",
357
+ "\u308A\u3085",
358
+ "\u304E\u3085",
359
+ "\u3058\u3085",
360
+ "\u3073\u3085",
361
+ "\u3074\u3085"
362
+ ]);
363
+ function readMoraAt(idx) {
364
+ if (idx >= s.length)
365
+ return null;
366
+ const c0 = s[idx];
367
+ const c1 = s[idx + 1];
368
+ if (c1 && SMALL_V.has(c1)) {
369
+ const key2 = c0 + c1;
370
+ const info2 = LOAN[key2];
371
+ if (info2)
372
+ return { key: key2, len: 2, info: info2 };
373
+ }
374
+ if (c1 && SMALL_Y.has(c1)) {
375
+ const key2 = c0 + c1;
376
+ const info2 = YOUON[key2];
377
+ if (info2)
378
+ return { key: key2, len: 2, info: info2 };
379
+ }
380
+ const info = SINGLE[c0];
381
+ if (info)
382
+ return { key: c0, len: 1, info };
383
+ return { key: c0, len: 1, info: void 0 };
384
+ }
385
+ function isLabialStart(cons) {
386
+ return cons === "m" || cons === "b" || cons === "p";
387
+ }
388
+ const isBoundary = (ch) => {
389
+ if (!ch)
390
+ return true;
391
+ return /\s|[、。!?!?\(\)\[\]{}「」『』()【】]/.test(ch);
392
+ };
393
+ let out = "";
394
+ let i = 0;
395
+ let lastMora = null;
396
+ let leadingSokuon = false;
397
+ while (i < s.length) {
398
+ let matchedSpecial = false;
399
+ for (const [k, v] of SPECIAL) {
400
+ if (s.startsWith(k, i)) {
401
+ out += v;
402
+ i += k.length;
403
+ lastMora = null;
404
+ matchedSpecial = true;
405
+ break;
406
+ }
407
+ }
408
+ if (matchedSpecial)
409
+ continue;
410
+ if (s.startsWith("\u3061\u3083\u3093", i)) {
411
+ out += "\uCA29";
412
+ i += 3;
413
+ lastMora = { out: "\uCA29", vowelMain: "a", consClass: "t", wasYouon: true };
414
+ continue;
415
+ }
416
+ const ch = s[i];
417
+ if (ch === "\u30FC") {
418
+ i += 1;
419
+ continue;
420
+ }
421
+ if (leadingSokuon) {
422
+ if (isHiragana(ch)) {
423
+ out += hiraToKata(ch);
424
+ i += 1;
425
+ leadingSokuon = false;
426
+ lastMora = null;
427
+ continue;
428
+ } else {
429
+ leadingSokuon = false;
430
+ }
431
+ }
432
+ if (ch === "\u3063") {
433
+ if (!out || !isHangulSyllable(out[out.length - 1])) {
434
+ out += "\u30C3";
435
+ i += 1;
436
+ leadingSokuon = true;
437
+ lastMora = null;
438
+ continue;
439
+ }
440
+ const next = readMoraAt(i + 1);
441
+ if (lastMora && lastMora.out === "\uC9C0" && next && next.key === "\u304F") {
442
+ i += 1;
443
+ continue;
444
+ }
445
+ const prevV = lastMora?.vowelMain ?? "a";
446
+ const nextInfo = next?.info;
447
+ const nextCons = nextInfo?.consClass ?? "t";
448
+ let jong = JONG.S;
449
+ if (nextCons === "p" || nextCons === "b")
450
+ jong = JONG.B;
451
+ else if (nextCons === "k" || nextCons === "g") {
452
+ jong = prevV === "e" || prevV === "i" ? JONG.S : JONG.G;
453
+ } else {
454
+ jong = JONG.S;
455
+ }
456
+ out = replaceLastHangul(out, jong);
457
+ i += 1;
458
+ continue;
459
+ }
460
+ if (!isKana(ch)) {
461
+ out += ch;
462
+ i += 1;
463
+ lastMora = null;
464
+ continue;
465
+ }
466
+ if (ch === "\u304A" && s[i + 1] === "\u304A") {
467
+ let j = i;
468
+ while (s[j] === "\u304A")
469
+ j++;
470
+ out += "\uC624";
471
+ i = j;
472
+ lastMora = {
473
+ out: "\uC624",
474
+ vowelMain: "o",
475
+ consClass: "vowel",
476
+ vowelOnly: true
477
+ };
478
+ continue;
479
+ }
480
+ const mora = readMoraAt(i);
481
+ if (!mora) {
482
+ out += ch;
483
+ i += 1;
484
+ lastMora = null;
485
+ continue;
486
+ }
487
+ if (mora.key === "\u3093") {
488
+ const next = readMoraAt(i + 1);
489
+ const nextInfo = next?.info;
490
+ let jong = JONG.N;
491
+ const hasPrevHangul = out.length > 0 && isHangulSyllable(out[out.length - 1]);
492
+ if (!hasPrevHangul) {
493
+ out += "\u3134";
494
+ i += 1;
495
+ lastMora = null;
496
+ continue;
497
+ }
498
+ if (lastMora?.out === "\uC0AC") {
499
+ const nextCh = s[i + 1];
500
+ const isBoundaryOrEnd = !nextCh || /\s|[、。!?!?\(\)\[\]{}「」『』()【】]/.test(nextCh);
501
+ const isParticleAfterSan = nextCh === "\u306F" || nextCh === "\u3078" || nextCh === "\u3092" || nextCh === "\u308F" || nextCh === "\u3048" || nextCh === "\u304A";
502
+ const hasPrefixBeforeSan = out.length >= 2;
503
+ if (hasPrefixBeforeSan && (isBoundaryOrEnd || isParticleAfterSan)) {
504
+ out = replaceLastHangul(out, JONG.NG);
505
+ i += 1;
506
+ continue;
507
+ }
508
+ }
509
+ if (!next || !nextInfo || !isKana(next.key[0])) {
510
+ jong = lastMora?.wasYouon ? JONG.NG : JONG.N;
511
+ } else {
512
+ const nc = nextInfo.consClass;
513
+ if (nc === "k" || nc === "g") {
514
+ jong = JONG.NG;
515
+ } else if (nc === "vowel" || nc === "y" || nc === "w") {
516
+ jong = JONG.N;
517
+ } else if (isLabialStart(nc)) {
518
+ if (lastMora?.vowelOnly)
519
+ jong = JONG.N;
520
+ else
521
+ jong = JONG.M;
522
+ } else {
523
+ jong = JONG.N;
524
+ }
525
+ }
526
+ out = replaceLastHangul(out, jong);
527
+ i += 1;
528
+ continue;
529
+ }
530
+ const info = mora.info;
531
+ if (!info) {
532
+ out += mora.key;
533
+ i += mora.len;
534
+ lastMora = null;
535
+ continue;
536
+ }
537
+ out += info.out;
538
+ lastMora = info;
539
+ const next1 = s[i + mora.len];
540
+ const afterLen = s[i + mora.len + 1];
541
+ if (next1 === "\u3046" && info.vowelMain === "o") {
542
+ i += mora.len + 1;
543
+ continue;
544
+ }
545
+ if (next1 === "\u3046" && (mora.key === "\u3086" || U_DROP_KEYS.has(mora.key))) {
546
+ i += mora.len + 1;
547
+ continue;
548
+ }
549
+ if (next1 === "\u3044") {
550
+ if (mora.key === "\u305B") {
551
+ if (afterLen !== "\u306A" && afterLen !== "\u304B") {
552
+ i += mora.len + 1;
553
+ continue;
554
+ }
555
+ } else if (mora.key === "\u3051") {
556
+ if (afterLen !== "\u3068") {
557
+ i += mora.len + 1;
558
+ continue;
559
+ }
560
+ } else if (mora.key === "\u3048") {
561
+ if (afterLen !== "\u3053" && afterLen !== "\u304F" && afterLen !== "\u304D") {
562
+ i += mora.len + 1;
563
+ continue;
564
+ }
565
+ } else if (mora.key === "\u3058") {
566
+ i += mora.len + 1;
567
+ continue;
568
+ } else if (mora.key === "\u304D") {
569
+ if (isBoundary(afterLen) || !afterLen) {
570
+ i += mora.len + 1;
571
+ continue;
572
+ }
573
+ } else if (mora.key === "\u3057") {
574
+ i += mora.len + 1;
575
+ continue;
576
+ }
577
+ }
578
+ if (next1 === "\u3048" && mora.key === "\u306D") {
579
+ i += mora.len + 1;
580
+ continue;
581
+ }
582
+ i += mora.len;
583
+ }
584
+ return out;
585
+ }
586
+ function hiraToKata(hira) {
587
+ const c = hira.codePointAt(0);
588
+ if (c >= 12353 && c <= 12438) {
589
+ return String.fromCodePoint(c + 96);
590
+ }
591
+ return hira;
592
+ }
593
+
594
+ // src/kanaToHangul.ts
595
+ function createKanaToHangul(tokenizer) {
596
+ return (input) => convertWithTokenizer(input, tokenizer);
597
+ }
598
+ function convertWithTokenizer(input, tokenizer) {
599
+ const normalized = normalizeInputText(input);
600
+ const hiragana = toHiragana(normalized);
601
+ const rewritten = rewriteParticlesWithKuromoji(hiragana, tokenizer);
602
+ return coreKanaToHangulConvert(rewritten);
603
+ }
604
+ var require2 = createRequire(import.meta.url);
605
+ async function buildTokenizer() {
606
+ return new Promise((resolve, reject) => {
607
+ const dicPath = path.join(require2.resolve("kuromoji"), "..", "..", "dict");
608
+ kuromoji.builder({ dicPath }).build((err, tk) => {
609
+ if (err || !tk)
610
+ reject(err);
611
+ else
612
+ resolve(tk);
613
+ });
614
+ });
615
+ }
616
+ var tokenizerPromise = null;
617
+ async function getTokenizer() {
618
+ if (!tokenizerPromise) {
619
+ tokenizerPromise = buildTokenizer();
620
+ }
621
+ return tokenizerPromise;
622
+ }
623
+
624
+ // src/kanaBarum.ts
625
+ var cachedConverter = null;
626
+ var pendingInit = null;
627
+ async function initKanaToHangul() {
628
+ if (cachedConverter)
629
+ return cachedConverter;
630
+ if (pendingInit)
631
+ return pendingInit;
632
+ pendingInit = (async () => {
633
+ const tokenizer = await getTokenizer();
634
+ const converter = createKanaToHangul(tokenizer);
635
+ cachedConverter = converter;
636
+ return converter;
637
+ })();
638
+ return pendingInit;
639
+ }
640
+ async function kanaToHangul(input) {
641
+ const converter = await initKanaToHangul();
642
+ return converter(input);
643
+ }
644
+ var KanaBarum = Object.freeze({
645
+ init: initKanaToHangul
646
+ });
647
+
648
+ export { KanaBarum, KanaBarum as KanaToHangulMaker, kanaToHangul };
649
+ //# sourceMappingURL=out.js.map
650
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/normalizer.ts","../src/particleRewriter.ts","../src/coreConverter.ts","../src/kanaToHangul.ts","../src/tokenizer.ts","../src/kanaBarum.ts"],"names":["out","info","require"],"mappings":"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,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,IAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA;AAAA,IAG9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAChD;AAEA,QAAM,QAAkC;AAAA,IACtC,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,IAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,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","sourcesContent":["export function normalizeInputText(input: string): string {\n // 1) NFD 결합문자(が/ぱ 등) 합성\n let normalized = input.normalize(\"NFC\");\n\n // 2) 반각 가타카나만 전각으로 (구두점/특수문자 최대한 보존)\n normalized = normalizeHalfwidthKatakanaOnly(normalized);\n\n // 3) 장음 기호 변종 최소 치환\n // - U+2015 HORIZONTAL BAR\n // - U+2500 BOX DRAWINGS LIGHT HORIZONTAL\n normalized = normalized.replace(/[\\u2015\\u2500]/g, \"ー\");\n\n // 4) ASCII hyphen이 가타카나 사이에 있을 때 장음 처리\n normalized = normalized\n .replace(/(?<=([\\u30A0-\\u30FF]))-/g, \"ー\")\n .replace(/-(?=[\\u30A0-\\u30FF])/g, \"ー\");\n\n return normalized;\n}\n\nfunction normalizeHalfwidthKatakanaOnly(s: string): string {\n // ✅ 반각 가타카나 + 탁점/반탁점(゙゚) + 반각 장음(ー)까지 함께 NFKC\n return s.replace(/[\\uFF66-\\uFF9F\\uFF70]+/g, (chunk) =>\n chunk.normalize(\"NFKC\"),\n );\n}\n\nexport function toHiragana(input: string): string {\n let out = \"\";\n for (const ch of input) {\n const code = ch.codePointAt(0)!;\n // カタカナ → ひらがな\n if (code >= 0x30a1 && code <= 0x30f6) {\n out += String.fromCodePoint(code - 0x60);\n continue;\n }\n if (ch === \"ー\") {\n out += ch;\n continue;\n }\n out += ch;\n }\n return out;\n}\n","import type kuromoji from \"kuromoji\";\nimport type { Tokenizer } from \"./tokenizer\";\n\nconst HARD_BOUNDARY_SURF = new Set([\n \"。\",\n \"、\",\n \"!\",\n \"?\",\n \"!\",\n \"?\",\n \" \",\n \" \",\n]);\nconst HARD_BOUNDARY_DETAIL1 = new Set([\n \"句点\",\n \"読点\",\n \"括弧開\",\n \"括弧閉\",\n \"空白\",\n]);\n\nconst LEXICAL_HE_ENDINGS = [\n \"いにしへ\",\n \"おきへ\",\n \"もとへ\",\n \"すえへ\",\n \"すゑへ\",\n \"かみへ\",\n \"くにへ\",\n \"きしへ\",\n] as const;\n\nconst isSingleKana = (x: string) =>\n x.length === 1 && /^[\\u3040-\\u309F\\u30A0-\\u30FF]$/.test(x);\n\nexport function rewriteParticlesWithKuromoji(\n text: string,\n tokenizer: Tokenizer,\n): string {\n const tokens = tokenizer.tokenize(text);\n let out = \"\";\n\n for (let i = 0; i < tokens.length; i += 1) {\n const token = tokens[i];\n const surf = token.surface_form;\n let replaced = surf;\n\n if (token.pos === \"助詞\" && surf === \"は\") {\n if (i > 0 && tokens[i - 1].surface_form === \"は\") {\n out += surf;\n continue;\n }\n if (i + 1 < tokens.length && tokens[i + 1].surface_form === \"は\") {\n out += surf;\n continue;\n }\n\n const prevIdx = prevContentIdx(tokens, i);\n if (prevIdx >= 0) {\n const nextIdx = nextContentIdx(tokens, i);\n const hasNextContent = nextIdx >= 0;\n const isEndOrPunct = nextBoundaryOrEnd(tokens, i);\n\n const prev = tokens[prevIdx];\n if (\n !prev.surface_form.includes(\"っ\") &&\n token.pos_detail_1 === \"係助詞\" &&\n (hasNextContent || isEndOrPunct) &&\n prev.pos !== \"助詞\"\n ) {\n out += \"わ\";\n continue;\n }\n }\n out += surf;\n continue;\n }\n\n if (token.pos === \"助詞\" && surf === \"へ\") {\n const prevIdx = prevContentIdx(tokens, i);\n if (prevIdx >= 0) {\n const nextIdx = nextContentIdx(tokens, i);\n const hasNextContent = nextIdx >= 0;\n const isEndOrPunct = nextBoundaryOrEnd(tokens, i);\n\n const prevSurf = tokens[prevIdx].surface_form;\n if (LEXICAL_HE_ENDINGS.some((w) => (prevSurf + \"へ\").endsWith(w))) {\n // keep lexical endings\n } else if (prevSurf.endsWith(\"の\")) {\n // \"...のへ\" pattern\n } else if (token.pos_detail_1 === \"格助詞\") {\n const nextPos = hasNextContent ? tokens[nextIdx].pos : \"\";\n const looksDirectionalByVerb =\n nextPos === \"動詞\" || nextPos === \"助動詞\";\n\n const nextSurf = hasNextContent ? tokens[nextIdx].surface_form : \"\";\n const nextIsSingleKana = hasNextContent && isSingleKana(nextSurf);\n\n if (!nextIsSingleKana && (looksDirectionalByVerb || isEndOrPunct)) {\n replaced = \"え\";\n }\n }\n }\n }\n\n out += replaced;\n }\n\n return out;\n}\n\nfunction isHardBoundaryToken(t: kuromoji.IpadicFeatures): boolean {\n if (t.pos !== \"記号\") return false;\n if (HARD_BOUNDARY_SURF.has(t.surface_form)) return true;\n return HARD_BOUNDARY_DETAIL1.has(t.pos_detail_1 ?? \"\");\n}\n\nfunction isContentToken(t: kuromoji.IpadicFeatures): boolean {\n if (t.pos === \"記号\") return !isHardBoundaryToken(t);\n return true;\n}\n\nfunction prevContentIdx(\n tokens: kuromoji.IpadicFeatures[],\n i: number,\n): number {\n for (let j = i - 1; j >= 0; j -= 1) if (isContentToken(tokens[j])) return j;\n return -1;\n}\n\nfunction nextContentIdx(\n tokens: kuromoji.IpadicFeatures[],\n i: number,\n): number {\n for (let j = i + 1; j < tokens.length; j += 1)\n if (isContentToken(tokens[j])) return j;\n return -1;\n}\n\nfunction nextBoundaryOrEnd(\n tokens: kuromoji.IpadicFeatures[],\n i: number,\n): boolean {\n for (let j = i + 1; j < tokens.length; j += 1) {\n if (isHardBoundaryToken(tokens[j])) continue;\n return false;\n }\n return true;\n}\n","export function coreKanaToHangulConvert(s: string): string {\n // 특별 사전 매핑: 한국인이 익숙한 발음\n const SPECIAL: Array<[string, string]> = [\n [\"とうきょう\", \"도쿄\"],\n [\"いいでしょうか\", \"이데쇼카\"],\n [\"いいでしょう\", \"이데쇼\"],\n [\"こんにちは\", \"콘니치와\"],\n [\"こんばんは\", \"콤방와\"],\n [\"すみません\", \"스미마셍\"],\n ];\n\n // --- Hangul utilities ---\n const HANGUL_BASE = 0xac00;\n const HANGUL_END = 0xd7a3;\n\n function isHangulSyllable(ch: string): boolean {\n const c = ch.codePointAt(0)!;\n return c >= HANGUL_BASE && c <= HANGUL_END;\n }\n\n const JONG = {\n NONE: 0,\n G: 1, // ㄱ\n N: 4, // ㄴ\n M: 16, // ㅁ\n B: 17, // ㅂ\n S: 19, // ㅅ\n NG: 21, // ㅇ\n } as const;\n\n function addFinal(syl: string, jong: number): string {\n if (!isHangulSyllable(syl)) return syl;\n const code = syl.codePointAt(0)! - HANGUL_BASE;\n const cho = Math.floor(code / 588);\n const jung = Math.floor((code % 588) / 28);\n return String.fromCodePoint(HANGUL_BASE + cho * 588 + jung * 28 + jong);\n }\n\n function replaceLastHangul(out: string, jong: number): string {\n if (!out) return out;\n const last = out[out.length - 1];\n if (!isHangulSyllable(last)) return out;\n return out.slice(0, -1) + addFinal(last, jong);\n }\n\n // --- Kana classification ---\n function isHiragana(ch: string): boolean {\n const c = ch.codePointAt(0)!;\n return c >= 0x3040 && c <= 0x309f;\n }\n function isKana(ch: string): boolean {\n return isHiragana(ch) || ch === \"ー\";\n }\n\n // --- Tables ---\n type VowelMain = \"a\" | \"i\" | \"u\" | \"e\" | \"o\";\n type ConsClass =\n | \"vowel\"\n | \"k\"\n | \"s\"\n | \"t\"\n | \"n\"\n | \"h\"\n | \"m\"\n | \"y\"\n | \"r\"\n | \"w\"\n | \"g\"\n | \"z\"\n | \"d\"\n | \"b\"\n | \"p\";\n\n type MoraInfo = {\n out: string;\n vowelMain: VowelMain;\n consClass: ConsClass;\n vowelOnly?: boolean;\n wasYouon?: boolean;\n };\n\n const SINGLE: Record<string, MoraInfo> = {\n あ: { out: \"아\", vowelMain: \"a\", consClass: \"vowel\", vowelOnly: true },\n い: { out: \"이\", vowelMain: \"i\", consClass: \"vowel\", vowelOnly: true },\n う: { out: \"우\", vowelMain: \"u\", consClass: \"vowel\", vowelOnly: true },\n え: { out: \"에\", vowelMain: \"e\", consClass: \"vowel\", vowelOnly: true },\n お: { out: \"오\", vowelMain: \"o\", consClass: \"vowel\", vowelOnly: true },\n\n か: { out: \"카\", vowelMain: \"a\", consClass: \"k\" },\n き: { out: \"키\", vowelMain: \"i\", consClass: \"k\" },\n く: { out: \"쿠\", vowelMain: \"u\", consClass: \"k\" },\n け: { out: \"케\", vowelMain: \"e\", consClass: \"k\" },\n こ: { out: \"코\", vowelMain: \"o\", consClass: \"k\" },\n\n さ: { out: \"사\", vowelMain: \"a\", consClass: \"s\" },\n し: { out: \"시\", vowelMain: \"i\", consClass: \"s\" },\n す: { out: \"스\", vowelMain: \"u\", consClass: \"s\" },\n せ: { out: \"세\", vowelMain: \"e\", consClass: \"s\" },\n そ: { out: \"소\", vowelMain: \"o\", consClass: \"s\" },\n\n た: { out: \"타\", vowelMain: \"a\", consClass: \"t\" },\n ち: { out: \"치\", vowelMain: \"i\", consClass: \"t\" },\n つ: { out: \"츠\", vowelMain: \"u\", consClass: \"t\" },\n て: { out: \"테\", vowelMain: \"e\", consClass: \"t\" },\n と: { out: \"토\", vowelMain: \"o\", consClass: \"t\" },\n\n な: { out: \"나\", vowelMain: \"a\", consClass: \"n\" },\n に: { out: \"니\", vowelMain: \"i\", consClass: \"n\" },\n ぬ: { out: \"누\", vowelMain: \"u\", consClass: \"n\" },\n ね: { out: \"네\", vowelMain: \"e\", consClass: \"n\" },\n の: { out: \"노\", vowelMain: \"o\", consClass: \"n\" },\n\n は: { out: \"하\", vowelMain: \"a\", consClass: \"h\" },\n ひ: { out: \"히\", vowelMain: \"i\", consClass: \"h\" },\n ふ: { out: \"후\", vowelMain: \"u\", consClass: \"h\" },\n へ: { out: \"헤\", vowelMain: \"e\", consClass: \"h\" },\n ほ: { out: \"호\", vowelMain: \"o\", consClass: \"h\" },\n\n ま: { out: \"마\", vowelMain: \"a\", consClass: \"m\" },\n み: { out: \"미\", vowelMain: \"i\", consClass: \"m\" },\n む: { out: \"무\", vowelMain: \"u\", consClass: \"m\" },\n め: { out: \"메\", vowelMain: \"e\", consClass: \"m\" },\n も: { out: \"모\", vowelMain: \"o\", consClass: \"m\" },\n\n や: { out: \"야\", vowelMain: \"a\", consClass: \"y\" },\n ゆ: { out: \"유\", vowelMain: \"u\", consClass: \"y\" },\n よ: { out: \"요\", vowelMain: \"o\", consClass: \"y\" },\n\n ら: { out: \"라\", vowelMain: \"a\", consClass: \"r\" },\n り: { out: \"리\", vowelMain: \"i\", consClass: \"r\" },\n る: { out: \"루\", vowelMain: \"u\", consClass: \"r\" },\n れ: { out: \"레\", vowelMain: \"e\", consClass: \"r\" },\n ろ: { out: \"로\", vowelMain: \"o\", consClass: \"r\" },\n\n わ: { out: \"와\", vowelMain: \"a\", consClass: \"w\" },\n を: { out: \"오\", vowelMain: \"o\", consClass: \"w\" },\n\n が: { out: \"가\", vowelMain: \"a\", consClass: \"g\" },\n ぎ: { out: \"기\", vowelMain: \"i\", consClass: \"g\" },\n ぐ: { out: \"구\", vowelMain: \"u\", consClass: \"g\" },\n げ: { out: \"게\", vowelMain: \"e\", consClass: \"g\" },\n ご: { out: \"고\", vowelMain: \"o\", consClass: \"g\" },\n\n ざ: { out: \"자\", vowelMain: \"a\", consClass: \"z\" },\n じ: { out: \"지\", vowelMain: \"i\", consClass: \"z\" },\n ず: { out: \"즈\", vowelMain: \"u\", consClass: \"z\" },\n ぜ: { out: \"제\", vowelMain: \"e\", consClass: \"z\" },\n ぞ: { out: \"조\", vowelMain: \"o\", consClass: \"z\" },\n\n だ: { out: \"다\", vowelMain: \"a\", consClass: \"d\" },\n ぢ: { out: \"지\", vowelMain: \"i\", consClass: \"d\" },\n づ: { out: \"즈\", vowelMain: \"u\", consClass: \"d\" },\n で: { out: \"데\", vowelMain: \"e\", consClass: \"d\" },\n ど: { out: \"도\", vowelMain: \"o\", consClass: \"d\" },\n\n ば: { out: \"바\", vowelMain: \"a\", consClass: \"b\" },\n び: { out: \"비\", vowelMain: \"i\", consClass: \"b\" },\n ぶ: { out: \"부\", vowelMain: \"u\", consClass: \"b\" },\n べ: { out: \"베\", vowelMain: \"e\", consClass: \"b\" },\n ぼ: { out: \"보\", vowelMain: \"o\", consClass: \"b\" },\n\n ぱ: { out: \"파\", vowelMain: \"a\", consClass: \"p\" },\n ぴ: { out: \"피\", vowelMain: \"i\", consClass: \"p\" },\n ぷ: { out: \"푸\", vowelMain: \"u\", consClass: \"p\" },\n ぺ: { out: \"페\", vowelMain: \"e\", consClass: \"p\" },\n ぽ: { out: \"포\", vowelMain: \"o\", consClass: \"p\" },\n\n // 이거는 う에 탁점 붙인 유니코드임.\n ゔ: { out: \"부\", vowelMain: \"u\", consClass: \"b\" },\n };\n\n const YOUON: Record<string, MoraInfo> = {\n きゃ: { out: \"캬\", vowelMain: \"a\", consClass: \"k\", wasYouon: true },\n きゅ: { out: \"큐\", vowelMain: \"u\", consClass: \"k\", wasYouon: true },\n きょ: { out: \"쿄\", vowelMain: \"o\", consClass: \"k\", wasYouon: true },\n\n しゃ: { out: \"샤\", vowelMain: \"a\", consClass: \"s\", wasYouon: true },\n しゅ: { out: \"슈\", vowelMain: \"u\", consClass: \"s\", wasYouon: true },\n しょ: { out: \"쇼\", vowelMain: \"o\", consClass: \"s\", wasYouon: true },\n\n ちゃ: { out: \"챠\", vowelMain: \"a\", consClass: \"t\", wasYouon: true },\n ちゅ: { out: \"츄\", vowelMain: \"u\", consClass: \"t\", wasYouon: true },\n ちょ: { out: \"쵸\", vowelMain: \"o\", consClass: \"t\", wasYouon: true },\n てゅ: { out: \"튜\", vowelMain: \"u\", consClass: \"t\", wasYouon: true },\n でゅ: { out: \"듀\", vowelMain: \"u\", consClass: \"d\", wasYouon: true },\n\n にゃ: { out: \"냐\", vowelMain: \"a\", consClass: \"n\", wasYouon: true },\n にゅ: { out: \"뉴\", vowelMain: \"u\", consClass: \"n\", wasYouon: true },\n にょ: { out: \"뇨\", vowelMain: \"o\", consClass: \"n\", wasYouon: true },\n\n ひゃ: { out: \"햐\", vowelMain: \"a\", consClass: \"h\", wasYouon: true },\n ひゅ: { out: \"휴\", vowelMain: \"u\", consClass: \"h\", wasYouon: true },\n ひょ: { out: \"효\", vowelMain: \"o\", consClass: \"h\", wasYouon: true },\n ふゃ: { out: \"퍄\", vowelMain: \"a\", consClass: \"p\", wasYouon: true },\n ふゅ: { out: \"퓨\", vowelMain: \"u\", consClass: \"p\", wasYouon: true },\n ふょ: { out: \"표\", vowelMain: \"o\", consClass: \"p\", wasYouon: true },\n\n みゃ: { out: \"먀\", vowelMain: \"a\", consClass: \"m\", wasYouon: true },\n みゅ: { out: \"뮤\", vowelMain: \"u\", consClass: \"m\", wasYouon: true },\n みょ: { out: \"묘\", vowelMain: \"o\", consClass: \"m\", wasYouon: true },\n\n りゃ: { out: \"랴\", vowelMain: \"a\", consClass: \"r\", wasYouon: true },\n りゅ: { out: \"류\", vowelMain: \"u\", consClass: \"r\", wasYouon: true },\n りょ: { out: \"료\", vowelMain: \"o\", consClass: \"r\", wasYouon: true },\n\n ぎゃ: { out: \"갸\", vowelMain: \"a\", consClass: \"g\", wasYouon: true },\n ぎゅ: { out: \"규\", vowelMain: \"u\", consClass: \"g\", wasYouon: true },\n ぎょ: { out: \"교\", vowelMain: \"o\", consClass: \"g\", wasYouon: true },\n\n じゃ: { out: \"쟈\", vowelMain: \"a\", consClass: \"z\", wasYouon: true },\n じゅ: { out: \"쥬\", vowelMain: \"u\", consClass: \"z\", wasYouon: true },\n じょ: { out: \"죠\", vowelMain: \"o\", consClass: \"z\", wasYouon: true },\n\n びゃ: { out: \"뱌\", vowelMain: \"a\", consClass: \"b\", wasYouon: true },\n びゅ: { out: \"뷰\", vowelMain: \"u\", consClass: \"b\", wasYouon: true },\n びょ: { out: \"뵤\", vowelMain: \"o\", consClass: \"b\", wasYouon: true },\n\n ぴゃ: { out: \"퍄\", vowelMain: \"a\", consClass: \"p\", wasYouon: true },\n ぴゅ: { out: \"퓨\", vowelMain: \"u\", consClass: \"p\", wasYouon: true },\n ぴょ: { out: \"표\", vowelMain: \"o\", consClass: \"p\", wasYouon: true },\n };\n\n const LOAN: Record<string, MoraInfo> = {\n てぃ: { out: \"티\", vowelMain: \"i\", consClass: \"t\" },\n でぃ: { out: \"디\", vowelMain: \"i\", consClass: \"d\" },\n ちぇ: { out: \"체\", vowelMain: \"e\", consClass: \"t\" },\n しぇ: { out: \"셰\", vowelMain: \"e\", consClass: \"s\" },\n じぇ: { out: \"제\", vowelMain: \"e\", consClass: \"z\" },\n つぁ: { out: \"차\", vowelMain: \"a\", consClass: \"t\" },\n つぃ: { out: \"치\", vowelMain: \"i\", consClass: \"t\" },\n つぇ: { out: \"체\", vowelMain: \"e\", consClass: \"t\" },\n つぉ: { out: \"초\", vowelMain: \"o\", consClass: \"t\" },\n ふぁ: { out: \"파\", vowelMain: \"a\", consClass: \"p\" },\n ふぃ: { out: \"피\", vowelMain: \"i\", consClass: \"p\" },\n ふぇ: { out: \"페\", vowelMain: \"e\", consClass: \"p\" },\n ふぉ: { out: \"포\", vowelMain: \"o\", consClass: \"p\" },\n ぐぁ: { out: \"과\", vowelMain: \"a\", consClass: \"g\" },\n ぐぃ: { out: \"귀\", vowelMain: \"i\", consClass: \"g\" },\n ぐぇ: { out: \"궤\", vowelMain: \"e\", consClass: \"g\" },\n ぐぉ: { out: \"궈\", vowelMain: \"o\", consClass: \"g\" },\n くぁ: { out: \"콰\", vowelMain: \"a\", consClass: \"k\" },\n くぃ: { out: \"퀴\", vowelMain: \"i\", consClass: \"k\" },\n くぇ: { out: \"퀘\", vowelMain: \"e\", consClass: \"k\" },\n くぉ: { out: \"쿼\", vowelMain: \"o\", consClass: \"k\" },\n どぁ: { out: \"돠\", vowelMain: \"a\", consClass: \"d\" },\n どぅ: { out: \"두\", vowelMain: \"u\", consClass: \"d\" },\n どぉ: { out: \"둬\", vowelMain: \"o\", consClass: \"d\" },\n ゔぁ: { out: \"바\", vowelMain: \"a\", consClass: \"b\" },\n ゔぃ: { out: \"비\", vowelMain: \"i\", consClass: \"b\" },\n ゔぇ: { out: \"베\", vowelMain: \"e\", consClass: \"b\" },\n ゔぉ: { out: \"보\", vowelMain: \"o\", consClass: \"b\" },\n };\n\n const SMALL_Y = new Set([\"ゃ\", \"ゅ\", \"ょ\"]);\n const SMALL_V = new Set([\"ぁ\", \"ぃ\", \"ぅ\", \"ぇ\", \"ぉ\"]);\n\n const U_DROP_KEYS = new Set([\n \"ゆ\",\n \"きゅ\",\n \"しゅ\",\n \"ちゅ\",\n \"にゅ\",\n \"ひゅ\",\n \"みゅ\",\n \"りゅ\",\n \"ぎゅ\",\n \"じゅ\",\n \"びゅ\",\n \"ぴゅ\",\n ]);\n\n type ReadMora = { key: string; len: number; info?: MoraInfo } | null;\n\n function readMoraAt(idx: number): ReadMora {\n if (idx >= s.length) return null;\n\n const c0 = s[idx];\n const c1 = s[idx + 1];\n\n if (c1 && SMALL_V.has(c1)) {\n const key2 = c0 + c1;\n const info = LOAN[key2];\n if (info) return { key: key2, len: 2, info };\n }\n\n if (c1 && SMALL_Y.has(c1)) {\n const key2 = c0 + c1;\n const info = YOUON[key2];\n if (info) return { key: key2, len: 2, info };\n }\n\n const info = SINGLE[c0];\n if (info) return { key: c0, len: 1, info };\n\n return { key: c0, len: 1, info: undefined };\n }\n\n function isLabialStart(cons: ConsClass): boolean {\n return cons === \"m\" || cons === \"b\" || cons === \"p\";\n }\n\n const isBoundary = (ch: string | undefined): boolean => {\n if (!ch) return true;\n return /\\s|[、。!?!?\\(\\)\\[\\]{}「」『』()【】]/.test(ch);\n };\n\n let out = \"\";\n let i = 0;\n\n let lastMora: MoraInfo | null = null;\n let leadingSokuon = false;\n\n while (i < s.length) {\n let matchedSpecial = false;\n for (const [k, v] of SPECIAL) {\n if (s.startsWith(k, i)) {\n // SPECIAL 값도 \"가나\" 형태로 들어와야 테이블이 자연스럽게 이어짐.\n // 여기서는 그대로 한글로 박는 기존 정책 유지.\n out += v\n i += k.length;\n lastMora = null;\n matchedSpecial = true;\n break;\n }\n }\n if (matchedSpecial) continue;\n\n if (s.startsWith(\"ちゃん\", i)) {\n out += \"쨩\";\n i += 3;\n lastMora = { out: \"쨩\", vowelMain: \"a\", consClass: \"t\", wasYouon: true };\n continue;\n }\n\n const ch = s[i];\n\n if (ch === \"ー\") {\n i += 1;\n continue;\n }\n\n if (leadingSokuon) {\n if (isHiragana(ch)) {\n out += hiraToKata(ch);\n i += 1;\n leadingSokuon = false;\n lastMora = null;\n continue;\n } else {\n leadingSokuon = false;\n }\n }\n\n if (ch === \"っ\") {\n if (!out || !isHangulSyllable(out[out.length - 1])) {\n out += \"ッ\";\n i += 1;\n leadingSokuon = true;\n lastMora = null;\n continue;\n }\n\n const next = readMoraAt(i + 1);\n if (lastMora && lastMora.out === \"지\" && next && next.key === \"く\") {\n i += 1;\n continue;\n }\n\n const prevV = lastMora?.vowelMain ?? \"a\";\n const nextInfo = next?.info;\n const nextCons: ConsClass = nextInfo?.consClass ?? \"t\";\n\n let jong: number = JONG.S;\n if (nextCons === \"p\" || nextCons === \"b\") jong = JONG.B;\n else if (nextCons === \"k\" || nextCons === \"g\") {\n jong = prevV === \"e\" || prevV === \"i\" ? JONG.S : JONG.G;\n } else {\n jong = JONG.S;\n }\n\n out = replaceLastHangul(out, jong);\n i += 1;\n continue;\n }\n\n // 비가나: 그대로\n if (!isKana(ch)) {\n out += ch;\n i += 1;\n lastMora = null;\n continue;\n }\n\n if (ch === \"お\" && s[i + 1] === \"お\") {\n let j = i;\n while (s[j] === \"お\") j++;\n out += \"오\";\n i = j;\n lastMora = {\n out: \"오\",\n vowelMain: \"o\",\n consClass: \"vowel\",\n vowelOnly: true,\n };\n continue;\n }\n\n const mora = readMoraAt(i);\n if (!mora) {\n out += ch;\n i += 1;\n lastMora = null;\n continue;\n }\n\n if (mora.key === \"ん\") {\n const next = readMoraAt(i + 1);\n const nextInfo = next?.info;\n\n let jong: number = JONG.N;\n const hasPrevHangul =\n out.length > 0 && isHangulSyllable(out[out.length - 1]);\n if (!hasPrevHangul) {\n out += \"ㄴ\";\n i += 1;\n lastMora = null;\n continue;\n }\n\n // ✅ \"さん\"(호칭)일 때만 '상'(받침 ㅇ)\n // 주의: 조사 리라이트가 먼저라서 다음 글자가 'は'가 아니라 'わ'일 수 있음!\n if (lastMora?.out === \"사\") {\n const nextCh = s[i + 1];\n\n const isBoundaryOrEnd =\n !nextCh || /\\s|[、。!?!?\\(\\)\\[\\]{}「」『』()【】]/.test(nextCh);\n\n // 원문 조사 + 리라이트된 조사까지 모두 허용\n const isParticleAfterSan =\n nextCh === \"は\" ||\n nextCh === \"へ\" ||\n nextCh === \"を\" ||\n nextCh === \"わ\" ||\n nextCh === \"え\" ||\n nextCh === \"お\";\n\n // ✅ 핵심: \"사\" 앞에 뭔가가 있어야(-san) 인정.\n // out는 지금 \"...사\" 까지 찍힌 상태.\n // \"さんは\"는 out === \"사\"라서 여기서 걸러져야 함.\n const hasPrefixBeforeSan = out.length >= 2;\n\n // 숫자/로마자 앞도 허용해야 \"3さん\" => 3상 유지됨\n if (hasPrefixBeforeSan && (isBoundaryOrEnd || isParticleAfterSan)) {\n out = replaceLastHangul(out, JONG.NG); // 사 + ん => 상\n i += 1;\n continue;\n }\n }\n\n // --- 기존 ん 규칙 ---\n if (!next || !nextInfo || !isKana(next.key[0])) {\n jong = lastMora?.wasYouon ? JONG.NG : JONG.N;\n } else {\n const nc = nextInfo.consClass;\n if (nc === \"k\" || nc === \"g\") {\n jong = JONG.NG;\n } else if (nc === \"vowel\" || nc === \"y\" || nc === \"w\") {\n jong = JONG.N;\n } else if (isLabialStart(nc)) {\n if (lastMora?.vowelOnly) jong = JONG.N;\n else jong = JONG.M;\n } else {\n jong = JONG.N;\n }\n }\n\n out = replaceLastHangul(out, jong);\n i += 1;\n continue;\n }\n\n const info = mora.info;\n if (!info) {\n out += mora.key;\n i += mora.len;\n lastMora = null;\n continue;\n }\n\n out += info.out;\n lastMora = info;\n\n const next1 = s[i + mora.len];\n const afterLen = s[i + mora.len + 1];\n\n if (next1 === \"う\" && info.vowelMain === \"o\") {\n i += mora.len + 1;\n continue;\n }\n\n if (next1 === \"う\" && (mora.key === \"ゆ\" || U_DROP_KEYS.has(mora.key))) {\n i += mora.len + 1;\n continue;\n }\n\n if (next1 === \"い\") {\n if (mora.key === \"せ\") {\n if (afterLen !== \"な\" && afterLen !== \"か\") {\n i += mora.len + 1;\n continue;\n }\n } else if (mora.key === \"け\") {\n if (afterLen !== \"と\") {\n i += mora.len + 1;\n continue;\n }\n } else if (mora.key === \"え\") {\n if (afterLen !== \"こ\" && afterLen !== \"く\" && afterLen !== \"き\") {\n i += mora.len + 1;\n continue;\n }\n } else if (mora.key === \"じ\") {\n i += mora.len + 1;\n continue;\n } else if (mora.key === \"き\") {\n // \"おおきい\" 줄임 정책 유지: 뒤에 이어지면 keep\n if (isBoundary(afterLen) || !afterLen) {\n i += mora.len + 1;\n continue;\n }\n } else if (mora.key === \"し\") {\n // しい adjectives pronounce as '시'\n i += mora.len + 1;\n continue;\n }\n }\n\n if (next1 === \"え\" && mora.key === \"ね\") {\n i += mora.len + 1;\n continue;\n }\n\n i += mora.len;\n }\n\n return out;\n}\n\nfunction hiraToKata(hira: string): string {\n const c = hira.codePointAt(0)!;\n if (c >= 0x3041 && c <= 0x3096) {\n return String.fromCodePoint(c + 0x60);\n }\n return hira;\n}\n","// lib/kanaToHangul.ts\n// 전체 변환 파이프라인을 orchestration만 담당하도록 정리했습니다.\nimport type { Tokenizer } from \"./tokenizer\";\nimport { normalizeInputText, toHiragana } from \"./normalizer\";\nimport { rewriteParticlesWithKuromoji } from \"./particleRewriter\";\nimport { coreKanaToHangulConvert } from \"./coreConverter\";\n\nexport type KanaToHangul = (input: string) => string;\n\nexport function createKanaToHangul(tokenizer: Tokenizer): KanaToHangul {\n return (input: string) => convertWithTokenizer(input, tokenizer);\n}\n\nfunction convertWithTokenizer(input: string, tokenizer: Tokenizer): string {\n const normalized = normalizeInputText(input);\n const hiragana = toHiragana(normalized);\n const rewritten = rewriteParticlesWithKuromoji(hiragana, tokenizer);\n return coreKanaToHangulConvert(rewritten);\n}\n","import kuromoji from \"kuromoji\";\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\n\nexport type Tokenizer = kuromoji.Tokenizer<kuromoji.IpadicFeatures>;\n\nasync function buildTokenizer(): Promise<Tokenizer> {\n return new Promise((resolve, reject) => {\n const dicPath = path.join(require.resolve(\"kuromoji\"), \"..\", \"..\", \"dict\");\n\n kuromoji.builder({ dicPath }).build((err, tk) => {\n if (err || !tk) reject(err);\n else resolve(tk);\n });\n });\n}\n\nlet tokenizerPromise: Promise<Tokenizer> | null = null;\n\nexport async function getTokenizer(): Promise<Tokenizer> {\n if (!tokenizerPromise) {\n tokenizerPromise = buildTokenizer();\n }\n return tokenizerPromise;\n}\n","import type { KanaToHangul } from \"./kanaToHangul\";\nimport { createKanaToHangul } from \"./kanaToHangul\";\nimport { getTokenizer } from \"./tokenizer\";\n\n/**\n * Lazy-initialized public API wrapper.\n * 토크나이저를 빌드/캐시하고, 외부에서는 await kanaToHangul(...)만 호출하면 됩니다.\n */\n\nlet cachedConverter: KanaToHangul | null = null;\nlet pendingInit: Promise<KanaToHangul> | null = null;\n\nasync function initKanaToHangul(): Promise<KanaToHangul> {\n if (cachedConverter) return cachedConverter;\n if (pendingInit) return pendingInit;\n\n pendingInit = (async () => {\n const tokenizer = await getTokenizer();\n const converter = createKanaToHangul(tokenizer);\n cachedConverter = converter;\n return converter;\n })();\n\n return pendingInit;\n}\n\nexport async function kanaToHangul(input: string): Promise<string> {\n const converter = await initKanaToHangul();\n return converter(input);\n}\n\nexport const KanaBarum = Object.freeze({\n init: initKanaToHangul,\n});\n\nexport type { KanaToHangul };\n"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "kanabarum",
3
+ "version": "0.1.0",
4
+ "description": "일본어 가나를 한국어 발음으로 바꿔주는 변환기",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "kana",
20
+ "hangul",
21
+ "japanese",
22
+ "kuromoji",
23
+ "transliteration"
24
+ ],
25
+ "author": "oyc0401",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/oyc0401/kanabarum"
29
+ },
30
+ "bugs": {
31
+ "url": "https://github.com/oyc0401/kanabarum/issues"
32
+ },
33
+ "homepage": "https://github.com/oyc0401/kanabarum#readme",
34
+ "license": "MIT",
35
+ "dependencies": {
36
+ "kuromoji": "^0.1.2"
37
+ },
38
+ "devDependencies": {
39
+ "@biomejs/biome": "^1.5.0",
40
+ "@types/kuromoji": "^0.1.3",
41
+ "@types/node": "^20.11.5",
42
+ "tsup": "^7.2.0",
43
+ "vitest": "^1.2.2",
44
+ "typescript": "^5.3.3"
45
+ },
46
+ "scripts": {
47
+ "build": "tsup --config tsup.config.ts",
48
+ "clean": "rm -rf dist",
49
+ "lint": "biome check .",
50
+ "format": "biome format --write .",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest"
53
+ }
54
+ }