kanabarum 0.3.0 → 0.3.2

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/dist/index.cjs CHANGED
@@ -1,11 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  var kuromoji = require('kuromoji');
4
+ var path = require('path');
5
+ var module$1 = require('module');
4
6
 
5
7
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
8
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
9
 
8
10
  var kuromoji__default = /*#__PURE__*/_interopDefault(kuromoji);
11
+ var path__default = /*#__PURE__*/_interopDefault(path);
9
12
 
10
13
  // src/normalizer.ts
11
14
  function normalizeInputText(input) {
@@ -866,18 +869,10 @@ function convertWithTokenizer(input, tokenizer) {
866
869
  original: rewrittenOriginal
867
870
  });
868
871
  }
872
+ var req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('out.js', document.baseURI).href)));
869
873
  async function buildTokenizer() {
870
- const isNode = typeof process !== "undefined" && typeof process.versions?.node === "string";
871
- let dicPath;
872
- if (isNode) {
873
- const { default: path } = await import('path');
874
- const { createRequire } = await import('module');
875
- const req = createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('out.js', document.baseURI).href)));
876
- dicPath = path.join(req.resolve("kuromoji"), "..", "..", "dict");
877
- } else {
878
- dicPath = "https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/";
879
- }
880
874
  return new Promise((resolve, reject) => {
875
+ const dicPath = path__default.default.join(req.resolve("kuromoji"), "..", "..", "dict");
881
876
  kuromoji__default.default.builder({ dicPath }).build((err, tk) => {
882
877
  if (err || !tk)
883
878
  reject(err);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/normalizer.ts","../src/dictionary.ts","../src/particleRewriter.ts","../src/mora.ts","../src/coreConverter.ts","../src/kanaToHangul.ts","../src/tokenizer.ts","../src/kanaBarum.ts"],"names":["toHiragana","outCpStart","isKatakanaChar","out","info"],"mappings":"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C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAChD;AAEO,IAAM,QAAkC;AAAA,EAC7C,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D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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","// dictionary.ts\n// 한국인이 익숙한 발음을 담은 특수 사전\nexport interface SpecialDictionaryEntry {\n word: string;\n answer: string;\n hira?: boolean; // true면 히라가나 입력에도 적용\n kata?: boolean; // true면 카타카나 입력에도 적용\n}\n\nexport const SpecialDictionary: SpecialDictionaryEntry[] = [\n // [\"とうきょう\", \"도쿄\"],\n { word: \"こんにちは\", answer: \"곤니치와\", hira: true, kata: true },\n { word: \"こんばんは\", answer: \"곰방와\" },\n { word: \"すみません\", answer: \"스미마셍\" },\n { word: \"はひふへほ\", answer: \"하히후헤호\" },\n { word: \"かわいい\", answer: \"카와이\" },\n { word: \"つなみ\", answer: \"쓰나미\" },\n { word: \"ゆうり\", answer: \"유우리\" },\n { word: \"ミュージック\", answer: \"뮤지쿠\" },\n { word: \"ちゃん\", answer: \"쨩\" },\n];\n","// particleRewriter.ts\nimport type kuromoji from \"kuromoji\";\nimport type { Tokenizer } from \"./tokenizer\";\nimport { SpecialDictionary, SpecialDictionaryEntry } from \"./dictionary\";\n\n// --------------------------\n// local helper: toHiragana (protectedRanges는 hiraganaText 기준)\n// --------------------------\nfunction isKatakanaChar(ch: string) {\n const c = ch.codePointAt(0)!;\n return c >= 0x30a0 && c <= 0x30ff;\n}\n\nfunction containsKanji(s: string): boolean {\n for (const ch of s) {\n const c = ch.codePointAt(0)!;\n // CJK Unified Ideographs (U+4E00-U+9FFF) + Extension A (U+3400-U+4DBF)\n if ((c >= 0x4e00 && c <= 0x9fff) || (c >= 0x3400 && c <= 0x4dbf)) {\n return true;\n }\n }\n return false;\n}\nfunction toHiragana(s: string): string {\n const n = s.normalize(\"NFKC\");\n return Array.from(n)\n .map((ch) => {\n // 장음은 드랍\n if (ch === \"ー\") return \"\";\n if (!isKatakanaChar(ch)) return ch;\n const code = ch.codePointAt(0)!;\n // カタカナ 문자 범위 (ァ~ヶ)만 변환\n if (code >= 0x30a1 && code <= 0x30f6) {\n return String.fromCodePoint(code - 0x60);\n }\n return ch;\n })\n .join(\"\");\n}\n\nfunction dictKeysForHiraganaText(dict: SpecialDictionaryEntry[]): string[] {\n // hiraganaText에서 실제로 등장할 수 있는 키만 모으기:\n // - entry.word 자체는 넣어도 되고(못 찾으면 무해)\n // - hira:true면 toHiragana(word)를 추가로 넣는다\n const keys: string[] = [];\n for (const e of dict) {\n keys.push(e.word);\n if (e.hira) keys.push(toHiragana(e.word));\n }\n // 중복 제거\n return [...new Set(keys)].filter(Boolean);\n}\n\ntype Range = { start: number; end: number }; // [start, end)\n\nfunction rangesOverlap(a: Range, b: Range): boolean {\n return a.start < b.end && b.start < a.end;\n}\n\nfunction buildProtectedRanges(text: string, keys: string[]): Range[] {\n const sorted = [...new Set(keys)].sort((a, b) => b.length - a.length);\n const ranges: Range[] = [];\n\n for (const key of sorted) {\n if (!key) continue;\n let from = 0;\n while (true) {\n const idx = text.indexOf(key, from);\n if (idx === -1) break;\n\n const cand: Range = { start: idx, end: idx + key.length };\n if (!ranges.some((r) => rangesOverlap(r, cand))) {\n ranges.push(cand);\n }\n from = idx + 1;\n }\n }\n\n ranges.sort((a, b) => a.start - b.start);\n return ranges;\n}\n\nfunction isProtectedSpan(protectedRanges: Range[], start: number, end: number) {\n for (const r of protectedRanges) {\n if (start < r.end && end > r.start) return true;\n }\n return false;\n}\n\nexport const 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\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}\nfunction isContentToken(t: kuromoji.IpadicFeatures): boolean {\n if (t.pos === \"記号\") return !isHardBoundaryToken(t);\n return true;\n}\n\nfunction prevContentIdx(tokens: kuromoji.IpadicFeatures[], i: number): number {\n for (let j = i - 1; j >= 0; j -= 1) if (isContentToken(tokens[j])) return j;\n return -1;\n}\nfunction nextContentIdx(tokens: kuromoji.IpadicFeatures[], i: number): number {\n for (let j = i + 1; j < tokens.length; j += 1)\n if (isContentToken(tokens[j])) return j;\n return -1;\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\nexport type TokenSpan = {\n start: number; // rewritten 기준\n end: number; // rewritten 기준\n surface: string;\n\n pos?: string;\n pos1?: string;\n pos2?: string;\n pos3?: string;\n\n // ✅ 원문 기반 힌트: katakana 포함 여부(노ート 같은 케이스 차단용)\n originHadKatakana?: boolean;\n};\n\n/**\n * ✅ 핵심:\n * - 토큰화는 \"prewrite 이전\"에 수행 (원문/정규화 기준)\n * - prewrite는 \"토큰 품사\"를 쓰되, 실제 replace는 hiraganaText slice로 수행\n * - 결과로 rewrittenText + rewrittenTokenSpans를 만들어 core로 넘김\n *\n * 가정: hiraganaText와 originalText는 길이가 동일 (toHiragana는 1:1 치환)\n */\nexport function rewriteParticlesFromTokenization(\n originalText: string,\n hiraganaText: string,\n tokenizerTokens: kuromoji.IpadicFeatures[],\n): { rewritten: string; spans: TokenSpan[]; rewrittenOriginal: string } {\n // 코드포인트 배열로 변환 (kuromoji word_position이 코드포인트 기준)\n const hiraChars = Array.from(hiraganaText);\n const originChars = Array.from(originalText);\n\n // entry 기반\n const protectedRanges = buildProtectedRanges(\n hiraganaText,\n dictKeysForHiraganaText(SpecialDictionary),\n );\n\n let out = \"\";\n let origOut = \"\"; // 한자→pronunciation 변환된 원본\n const spans: TokenSpan[] = [];\n\n let cursorInText = 0;\n\n for (let i = 0; i < tokenizerTokens.length; i += 1) {\n const tok = tokenizerTokens[i];\n const wp = (tok as any).word_position as number | undefined;\n const surf = tok.surface_form;\n const surfCpLen = [...surf].length; // 코드포인트 길이\n\n // kuromoji word_position은 코드포인트 기준 (1-based)\n const start = typeof wp === \"number\" ? wp - 1 : cursorInText;\n const end = start + surfCpLen;\n cursorInText = end;\n\n // 한자가 포함된 경우 pronunciation을 사용\n const hasKanji = containsKanji(surf) && tok.pronunciation;\n const hiraSurf = hasKanji\n ? toHiragana(tok.pronunciation!)\n : hiraChars.slice(start, end).join(\"\");\n // 원본도 한자면 pronunciation에서 장음 제거 (길이 맞추기)\n const origSurfForOut = hasKanji\n ? tok.pronunciation!.replace(/ー/g, \"\")\n : originChars.slice(start, end).join(\"\");\n const originSurf = originChars.slice(start, end).join(\"\");\n\n const originHadKatakana = /[\\u30A0-\\u30FF]/.test(originSurf);\n\n // ✅ 불필요하고 위험한 isProtected(튜플 기반 + includes 난사) 제거\n // protectedRanges 기반으로만 판단\n if (isProtectedSpan(protectedRanges, start, end)) {\n const outCpStart = [...out].length;\n out += hiraSurf;\n origOut += origSurfForOut;\n spans.push({\n start: outCpStart,\n end: [...out].length,\n surface: hiraSurf,\n pos: tok.pos,\n pos1: tok.pos_detail_1 ?? undefined,\n pos2: tok.pos_detail_2 ?? undefined,\n pos3: tok.pos_detail_3 ?? undefined,\n originHadKatakana,\n });\n continue;\n }\n\n let replaced = hiraSurf;\n\n // --- は -> わ (계조사) ---\n if (tok.pos === \"助詞\" && hiraSurf === \"は\") {\n if (i > 0 && tokenizerTokens[i - 1].surface_form === \"は\") {\n // keep\n } else if (\n i + 1 < tokenizerTokens.length &&\n tokenizerTokens[i + 1].surface_form === \"は\"\n ) {\n // keep\n } else {\n const prevIdx = prevContentIdx(tokenizerTokens, i);\n if (prevIdx >= 0) {\n const nextIdx = nextContentIdx(tokenizerTokens, i);\n const hasNextContent = nextIdx >= 0;\n const isEndOrPunct = nextBoundaryOrEnd(tokenizerTokens, i);\n\n const prevTok = tokenizerTokens[prevIdx];\n const prevWpHa = (prevTok as any).word_position as number | undefined;\n const prevStartHa = typeof prevWpHa === \"number\" ? prevWpHa - 1 : 0;\n const prevEndHa = prevStartHa + [...prevTok.surface_form].length;\n const prevHira = hiraChars.slice(prevStartHa, prevEndHa).join(\"\");\n\n if (\n !prevHira.includes(\"っ\") &&\n tok.pos_detail_1 === \"係助詞\" &&\n (hasNextContent || isEndOrPunct) &&\n prevTok.pos !== \"助詞\"\n ) {\n replaced = \"わ\";\n }\n }\n }\n }\n\n // --- へ -> え (격조사) ---\n if (tok.pos === \"助詞\" && hiraSurf === \"へ\") {\n // 바로 왼쪽이 공백이면 keep (코드포인트 배열 사용)\n if (start > 0) {\n const left = hiraChars[start - 1];\n if (\n left === \" \" ||\n left === \" \" ||\n left === \"\\t\" ||\n left === \"\\n\" ||\n left === \"\\r\"\n ) {\n // keep\n } else {\n const prevIdx = prevContentIdx(tokenizerTokens, i);\n if (prevIdx >= 0) {\n const prevTok = tokenizerTokens[prevIdx];\n const prevWp = (prevTok as any).word_position as number | undefined;\n const prevStart = typeof prevWp === \"number\" ? prevWp - 1 : 0;\n const prevEnd = prevStart + [...prevTok.surface_form].length;\n\n const prevHiraSurf = hiraChars.slice(prevStart, prevEnd).join(\"\");\n\n if (\n LEXICAL_HE_ENDINGS.some((w) => (prevHiraSurf + \"へ\").endsWith(w))\n ) {\n // keep lexical endings\n } else if (prevHiraSurf.endsWith(\"の\")) {\n // keep \"...のへ\"\n } else if (tok.pos_detail_1 === \"格助詞\") {\n replaced = \"え\";\n }\n }\n }\n }\n }\n\n const outCpStart = [...out].length;\n out += replaced;\n origOut += origSurfForOut;\n\n spans.push({\n start: outCpStart,\n end: [...out].length,\n surface: replaced,\n pos: tok.pos,\n pos1: tok.pos_detail_1 ?? undefined,\n pos2: tok.pos_detail_2 ?? undefined,\n pos3: tok.pos_detail_3 ?? undefined,\n originHadKatakana,\n });\n }\n\n return { rewritten: out, spans, rewrittenOriginal: origOut };\n}\n\n/**\n * 외부에서 쓰기 편한 래퍼:\n * - originalText를 tokenizer로 먼저 tokenize\n * - hiraganaText는 호출자가 넘겨줌(길이 동일 가정)\n */\nexport function tokenizeAndRewriteParticles(\n originalText: string,\n hiraganaText: string,\n tokenizer: Tokenizer,\n): {\n rewritten: string;\n spans: TokenSpan[];\n rewrittenOriginal: string;\n rawTokens: kuromoji.IpadicFeatures[];\n} {\n const rawTokens = tokenizer.tokenize(originalText);\n // console.log(rawTokens);\n const { rewritten, spans, rewrittenOriginal } =\n rewriteParticlesFromTokenization(originalText, hiraganaText, rawTokens);\n return { rewritten, spans, rewrittenOriginal, rawTokens };\n}\n","// --- Tables (당신 코드 그대로) ---\nexport type VowelMain = \"a\" | \"i\" | \"u\" | \"e\" | \"o\";\nexport 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\nexport type MoraInfo = {\n out: string;\n vowelMain: VowelMain;\n consClass: ConsClass;\n vowelOnly?: boolean;\n wasYouon?: boolean;\n};\n\nexport 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 ゔ: { out: \"부\", vowelMain: \"u\", consClass: \"b\" },\n};\n\nexport 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\nexport 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\nexport const SMALL_Y = new Set([\"ゃ\", \"ゅ\", \"ょ\"]);\nexport const SMALL_V = new Set([\"ぁ\", \"ぃ\", \"ぅ\", \"ぇ\", \"ぉ\"]);\n\nexport const U_DROP_KEYS = new Set([\n \"ゆ\",\n \"きゅ\",\n \"しゅ\",\n \"ちゅ\",\n \"にゅ\",\n \"ひゅ\",\n \"みゅ\",\n \"りゅ\",\n \"ぎゅ\",\n \"じゅ\",\n \"びゅ\",\n \"ぴゅ\",\n]);\n","// coreConverter.ts\nimport { SpecialDictionary, type SpecialDictionaryEntry } from \"./dictionary\";\nimport { HARD_BOUNDARY_SURF, type TokenSpan } from \"./particleRewriter\";\nimport {\n type ConsClass,\n type MoraInfo,\n SINGLE,\n YOUON,\n LOAN,\n SMALL_Y,\n SMALL_V,\n U_DROP_KEYS,\n} from \"./mora\";\n\n// --------------------------\n// Kana normalize helpers\n// --------------------------\nfunction isHiraganaChar(ch: string) {\n const c = ch.codePointAt(0)!;\n return c >= 0x3040 && c <= 0x309f;\n}\nfunction isKatakanaChar(ch: string) {\n const c = ch.codePointAt(0)!;\n return c >= 0x30a0 && c <= 0x30ff;\n}\nfunction toHiraganaKey(s: string): string {\n const n = s.normalize(\"NFKC\");\n return Array.from(n)\n .map((ch) => {\n // 장음은 그대로 유지\n if (ch === \"ー\") return ch;\n if (!isKatakanaChar(ch)) return ch;\n const code = ch.codePointAt(0)!;\n // カタカナ 문자 범위 (ァ~ヶ)만 변환\n if (code >= 0x30a1 && code <= 0x30f6) {\n return String.fromCodePoint(code - 0x60);\n }\n return ch;\n })\n .join(\"\");\n}\nfunction toKatakanaKey(s: string): string {\n const n = s.normalize(\"NFKC\");\n return Array.from(n)\n .map((ch) =>\n isHiraganaChar(ch) ? String.fromCodePoint(ch.codePointAt(0)! + 0x60) : ch,\n )\n .join(\"\");\n}\n\ntype DictStream = \"orig\" | \"rewritten\";\ntype CompiledDictItem = {\n keyChars: string[];\n answer: string;\n stream: DictStream;\n};\n\nfunction compileSpecialDictionary(\n dict: SpecialDictionaryEntry[],\n): CompiledDictItem[] {\n const items: CompiledDictItem[] = [];\n\n for (const e of dict) {\n // 기본: exact word는 원본에서만\n items.push({\n keyChars: Array.from(e.word),\n answer: e.answer,\n stream: \"orig\",\n });\n\n // hira:true => hiragana 스트림에서만 (입력 전체가 히라로 바뀌는 파이프라인이기 때문)\n if (e.hira) {\n const k = toHiraganaKey(e.word);\n items.push({\n keyChars: Array.from(k),\n answer: e.answer,\n stream: \"rewritten\",\n });\n }\n\n // kata:true => 원본에서만\n if (e.kata) {\n const k = toKatakanaKey(e.word);\n items.push({ keyChars: Array.from(k), answer: e.answer, stream: \"orig\" });\n }\n }\n\n // 긴 키 우선\n items.sort((a, b) => b.keyChars.length - a.keyChars.length);\n return items;\n}\n\nconst COMPILED_SPECIAL_DICT = compileSpecialDictionary(SpecialDictionary);\n\nfunction isHiragana(ch: string): boolean {\n const c = ch.codePointAt(0)!;\n return c >= 0x3040 && c <= 0x309f;\n}\n\nfunction isKana(ch: string): boolean {\n return isHiragana(ch) || ch === \"ー\";\n}\n\nexport function coreKanaToHangulConvert(\n s: string,\n opts: { tokens: TokenSpan[]; original: string },\n): string {\n // 코드포인트 배열로 변환 (surrogate pair 문제 해결)\n const chars = Array.from(s);\n const origChars = Array.from(opts?.original ?? s);\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 type ReadMora = { key: string; len: number; info?: MoraInfo } | null;\n\n function readMoraAt(idx: number): ReadMora {\n if (idx >= chars.length) return null;\n\n const c0 = chars[idx];\n const c1 = chars[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 readOriginalMoraAt(idx: number): ReadMora {\n if (idx >= origChars.length) return null;\n\n const c0 = origChars[idx];\n const c1 = origChars[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 // 토큰 컨텍스트 탐색용\n const tokens = opts?.tokens ?? null;\n let tokIdx = 0;\n\n function syncTokenIndex(charIndex: number) {\n if (!tokens) return;\n while (tokIdx + 1 < tokens.length && tokens[tokIdx].end <= charIndex) {\n tokIdx++;\n }\n }\n\n function curToken(charIndex: number): TokenSpan | null {\n if (!tokens) return null;\n syncTokenIndex(charIndex);\n const t = tokens[tokIdx];\n if (t && t.start <= charIndex && charIndex < t.end) return t;\n return null;\n }\n\n function prevToken(): TokenSpan | null {\n if (!tokens) return null;\n return tokIdx - 1 >= 0 ? tokens[tokIdx - 1] : null;\n }\n function nextToken(): TokenSpan | null {\n if (!tokens) return null;\n return tokIdx + 1 < tokens.length ? tokens[tokIdx + 1] : null;\n }\n\n // ✅ 유성화 차단 next\n const INITIAL_VOICING_BLOCK_NEXT = new Set([\"い\", \"ひ\", \"ん\", \"て\"]);\n\n function peekNextMoraKeySkippingChoonpu(fromIdx: number): string | null {\n let j = fromIdx;\n while (j < chars.length && chars[j] === \"ー\") j++;\n const m = readMoraAt(j);\n return m?.key ?? null;\n }\n\n // ✅ \"-san\" 판별 (코드포인트 인덱스 기준)\n const SAN_PARTICLES = new Set([\"は\", \"わ\", \"へ\", \"え\", \"を\", \"お\"]);\n function isSanHonorificAt(cpIdx: number): boolean {\n const t = curToken(cpIdx);\n if (!t) return false;\n if (cpIdx < 1 || chars[cpIdx - 1] !== \"さ\") return false;\n\n // t.start는 코드포인트 인덱스, chars.slice 사용\n const local = chars.slice(t.start, cpIdx + 1).join(\"\");\n if (!local.endsWith(\"さん\")) return false;\n\n const hasPrefixInsideToken = cpIdx - 1 > t.start;\n const p = prevToken();\n const prevIsAttachable =\n !!p &&\n p.end === t.start &&\n p.surface.length > 0 &&\n !HARD_BOUNDARY_SURF.has(p.surface);\n\n if (!hasPrefixInsideToken && !prevIsAttachable) return false;\n\n const n = nextToken();\n if (!n) return true;\n if (n.pos === \"記号\" && HARD_BOUNDARY_SURF.has(n.surface)) return true;\n if (n.pos === \"助詞\" && SAN_PARTICLES.has(n.surface)) return true;\n return false;\n }\n\n let out = \"\";\n let i = 0;\n\n let lastMora: MoraInfo | null = null;\n\n while (i < chars.length) {\n // 토큰 기반 \"단어 시작\" 정의: i가 content 토큰 start면 true\n let atTokenStart = false;\n let tokForI: TokenSpan | null = null;\n\n if (tokens) {\n tokForI = curToken(i);\n // ✅ 토큰 시작이면 일단 단어 시작 후보로 인정\n atTokenStart = !!tokForI && tokForI.start === i;\n\n // ✅ 유성화/단어시작 판정에서 \"기호\"와 \"원문 카타카나 토큰\"만 컷\n if (tokForI?.pos === \"記号\") atTokenStart = false;\n } else {\n atTokenStart = i === 0;\n }\n\n // --------------------------\n // ✅ SpecialDictionary (entry 기반 + hira/kata 옵션)\n // --------------------------\n let matchedSpecial = false;\n\n // 긴 키부터 순회하므로, 앞에서 걸리면 끝\n for (const it of COMPILED_SPECIAL_DICT) {\n const src = it.stream === \"orig\" ? origChars : chars; // chars=rewritten\n const len = it.keyChars.length;\n if (i + len > src.length) continue;\n\n let ok = true;\n for (let k = 0; k < len; k++) {\n if (src[i + k] !== it.keyChars[k]) {\n ok = false;\n break;\n }\n }\n if (!ok) continue;\n\n out += it.answer;\n i += len;\n\n // 사전 치환은 단어 단위 => 상태 초기화\n lastMora = null;\n\n matchedSpecial = true;\n break;\n }\n if (matchedSpecial) continue;\n\n const ch = chars[i];\n\n /**\n * ー 표시는 그냥 드랍\n */\n if (ch === \"ー\") {\n i += 1;\n continue;\n }\n\n /**\n * 비가나: 그대로\n */\n if (!isKana(ch)) {\n out += ch;\n i += 1;\n lastMora = null;\n continue;\n }\n\n /**\n * 촉음 규칙\n */\n if (ch === \"っ\") {\n if (!out || !isHangulSyllable(out[out.length - 1])) {\n // \"ッ\"으로 바꾸기\n out += \"ッ\";\n i += 1;\n lastMora = null;\n continue;\n }\n\n const next = readMoraAt(i + 1);\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 * おお 장음 규칙\n */\n if (ch === \"お\" && chars[i + 1] === \"お\") {\n let j = i;\n while (chars[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 const originalMora = readOriginalMoraAt(i);\n if (!mora) {\n out += chars[i];\n i += 1;\n lastMora = null;\n continue;\n }\n // 에러\n if (!originalMora) {\n throw Error(\"원본 모라 손실\");\n }\n\n /**\n * ん 규칙\n */\n if (mora.key === \"ん\") {\n const next = readMoraAt(i + 1);\n const nextInfo = next?.info;\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 // 토큰 컨텍스트 기반 \"-san\" → '상'\n if (lastMora?.out === \"사\" && isSanHonorificAt(i)) {\n out = replaceLastHangul(out, JONG.NG);\n i += 1;\n continue;\n }\n\n // --- 기존 ん 동화 규칙 ---\n let jong: number = JONG.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 /**\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 // ✅ 단어(토큰) 시작 유성화: と/こ만 + 예외(이/히/ん/테) + (앞이 っ이면 금지)\n let outSyl = info.out;\n if (atTokenStart && (mora.key === \"と\" || mora.key === \"こ\")) {\n const prevIsSokuon = i > 0 && chars[i - 1] === \"っ\";\n\n const nextKey = peekNextMoraKeySkippingChoonpu(i + mora.len);\n const blockedByNext =\n !!nextKey && INITIAL_VOICING_BLOCK_NEXT.has(nextKey);\n\n const isKou = mora.key === \"こ\" && chars[i + mora.len] === \"う\";\n\n // ✅ 추가: \"진짜 조사 と/こ\"로 쓰인 경우만 유성화 차단\n // - 현재 토큰이 1글자 'と'/'こ'이고,\n // - 이전 토큰이 내용어(명사/동사/형용사 등)면 => 조사로 판단 => 유성화 금지\n let blockedByParticleUsage = false;\n if (tokens && tokForI) {\n const isSingleCharToken = tokForI.surface.length === 1;\n const tokenMatchesMora = tokForI.surface === mora.key;\n\n if (isSingleCharToken && tokenMatchesMora) {\n const p = prevToken(); // curToken(i) 호출로 tokIdx는 sync된 상태\n const prevLooksLikeContent =\n !!p &&\n p.pos !== \"記号\" &&\n p.pos !== \"助詞\" &&\n p.pos !== \"助動詞\" &&\n !HARD_BOUNDARY_SURF.has(p.surface);\n\n if (prevLooksLikeContent) blockedByParticleUsage = true;\n }\n }\n\n const allowVoicing =\n !prevIsSokuon && !blockedByNext && !isKou && !blockedByParticleUsage;\n\n if (allowVoicing) {\n if (mora.key === \"と\") outSyl = \"도\";\n else if (mora.key === \"こ\") outSyl = \"고\";\n }\n }\n\n out += outSyl;\n lastMora = { ...info, out: outSyl };\n\n /**\n * 연음 드랍\n */\n const next1 = chars[i + mora.len]; // 다음 언어\n const afterLen = chars[i + mora.len + 1]; // 다다음언어\n\n // o + う 드랍\n if (next1 === \"う\" && info.vowelMain === \"o\") {\n i += mora.len + 1;\n continue;\n }\n\n // ゅう 드랍 (きゅう) // ゆう는 드랍할까말까? (유우리) 일단 ゆう도 드랍함!\n if (next1 === \"う\" && U_DROP_KEYS.has(mora.key)) {\n i += mora.len + 1;\n continue;\n }\n\n // い 드랍\n if (next1 === \"い\") {\n // せんせい -> 센세\n if (mora.key === \"せ\") {\n i += mora.len + 1;\n continue;\n }\n // 케도 장음인데, 케이사츠라고 검색하는 경우가 더 많으려나? 어떻게 할까...\n else if (mora.key === \"け\") {\n i += mora.len + 1;\n continue;\n }\n // えいご -> 에고\n else if (mora.key === \"え\") {\n // 조사 へ 감지\n if (originalMora.key !== \"へ\") {\n i += mora.len + 1;\n continue;\n }\n }\n // 오이시, 야사시 대응\n else if (mora.key === \"し\") {\n i += mora.len + 1;\n continue;\n }\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","// kanaToHangul.ts\n// 전체 변환 파이프라인을 orchestration만 담당하도록 정리했습니다.\nimport type { Tokenizer } from \"./tokenizer\";\nimport { normalizeInputText, toHiragana } from \"./normalizer\";\nimport { tokenizeAndRewriteParticles } 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\nexport function convertWithTokenizer(\n input: string,\n tokenizer: Tokenizer,\n): string {\n const normalized = normalizeInputText(input);\n\n // ✅ 길이 1:1 보장되는 kana 변환을 먼저 수행(스팬 유지)\n const hiragana = toHiragana(normalized);\n\n // ✅ 핵심: prewrite 이전에 토큰화(=normalized 기준), prewrite는 토큰(pos)을 사용하되 slice는 hiragana 기준\n const { rewritten, spans, rewrittenOriginal } = tokenizeAndRewriteParticles(\n normalized,\n hiragana,\n tokenizer,\n );\n\n // ✅ rewritten + rewritten spans 로 core\n // 한자가 pronunciation으로 변환된 경우 rewrittenOriginal도 같은 길이로 변환됨\n return coreKanaToHangulConvert(rewritten, {\n tokens: spans,\n original: rewrittenOriginal,\n });\n}\n","import kuromoji from \"kuromoji\";\n\nexport type Tokenizer = kuromoji.Tokenizer<kuromoji.IpadicFeatures>;\n\nasync function buildTokenizer(): Promise<Tokenizer> {\n const isNode =\n typeof process !== \"undefined\" && typeof process.versions?.node === \"string\";\n\n let dicPath: string;\n if (isNode) {\n const { default: path } = await import(\"node:path\");\n const { createRequire } = await import(\"node:module\");\n const req = createRequire(import.meta.url);\n dicPath = path.join(req.resolve(\"kuromoji\"), \"..\", \"..\", \"dict\");\n } else {\n dicPath = \"https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/\";\n }\n\n return new Promise((resolve, reject) => {\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 * Kanabarum - 일본어 가나를 한글로 변환하는 클래스\n *\n * @example\n * const kanabarum = new Kanabarum();\n * await kanabarum.init();\n * kanabarum.kanaToHangul(\"こんにちは\"); // \"곤니치와\"\n */\nexport class Kanabarum {\n private converter: KanaToHangul | null = null;\n\n /**\n * 토크나이저를 초기화합니다. 변환 전에 반드시 호출해야 합니다.\n */\n async init(): Promise<void> {\n const tokenizer = await getTokenizer();\n this.converter = createKanaToHangul(tokenizer);\n }\n\n /**\n * 일본어 가나를 한글로 변환합니다.\n * @param input 변환할 일본어 문자열\n * @returns 한글로 변환된 문자열\n * @throws init()이 호출되지 않은 경우 에러\n */\n kanaToHangul(input: string): string {\n if (!this.converter) {\n throw new Error(\"Kanabarum is not initialized. Call init() first.\");\n }\n return this.converter(input);\n }\n}\n\n// ============================================\n// Async helper (기존 API 호환)\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\n/**\n * 일본어 가나를 한글로 변환합니다. (async helper)\n * init 없이 바로 호출 가능합니다.\n *\n * @example\n * await kanaToHangul(\"こんにちは\"); // \"곤니치와\"\n */\nexport async function kanaToHangul(input: string): Promise<string> {\n const converter = await initKanaToHangul();\n return converter(input);\n}\n\nexport type { KanaToHangul };\n"]}
1
+ {"version":3,"sources":["../src/normalizer.ts","../src/dictionary.ts","../src/particleRewriter.ts","../src/mora.ts","../src/coreConverter.ts","../src/kanaToHangul.ts","../src/tokenizer.ts","../src/kanaBarum.ts"],"names":["toHiragana","outCpStart","isKatakanaChar","out","info"],"mappings":"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C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAC9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAAA,EAE9C,QAAG,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,IAAI;AAChD;AAEO,IAAM,QAAkC;AAAA,EAC7C,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAE/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D,cAAI,EAAE,KAAK,UAAK,WAAW,KAAK,WAAW,KAAK,UAAU,KAAK;AAAA,EAC/D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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","// dictionary.ts\n// 한국인이 익숙한 발음을 담은 특수 사전\nexport interface SpecialDictionaryEntry {\n word: string;\n answer: string;\n hira?: boolean; // true면 히라가나 입력에도 적용\n kata?: boolean; // true면 카타카나 입력에도 적용\n}\n\nexport const SpecialDictionary: SpecialDictionaryEntry[] = [\n // [\"とうきょう\", \"도쿄\"],\n { word: \"こんにちは\", answer: \"곤니치와\", hira: true, kata: true },\n { word: \"こんばんは\", answer: \"곰방와\" },\n { word: \"すみません\", answer: \"스미마셍\" },\n { word: \"はひふへほ\", answer: \"하히후헤호\" },\n { word: \"かわいい\", answer: \"카와이\" },\n { word: \"つなみ\", answer: \"쓰나미\" },\n { word: \"ゆうり\", answer: \"유우리\" },\n { word: \"ミュージック\", answer: \"뮤지쿠\" },\n { word: \"ちゃん\", answer: \"쨩\" },\n];\n","// particleRewriter.ts\nimport type kuromoji from \"kuromoji\";\nimport type { Tokenizer } from \"./tokenizer\";\nimport { SpecialDictionary, SpecialDictionaryEntry } from \"./dictionary\";\n\n// --------------------------\n// local helper: toHiragana (protectedRanges는 hiraganaText 기준)\n// --------------------------\nfunction isKatakanaChar(ch: string) {\n const c = ch.codePointAt(0)!;\n return c >= 0x30a0 && c <= 0x30ff;\n}\n\nfunction containsKanji(s: string): boolean {\n for (const ch of s) {\n const c = ch.codePointAt(0)!;\n // CJK Unified Ideographs (U+4E00-U+9FFF) + Extension A (U+3400-U+4DBF)\n if ((c >= 0x4e00 && c <= 0x9fff) || (c >= 0x3400 && c <= 0x4dbf)) {\n return true;\n }\n }\n return false;\n}\nfunction toHiragana(s: string): string {\n const n = s.normalize(\"NFKC\");\n return Array.from(n)\n .map((ch) => {\n // 장음은 드랍\n if (ch === \"ー\") return \"\";\n if (!isKatakanaChar(ch)) return ch;\n const code = ch.codePointAt(0)!;\n // カタカナ 문자 범위 (ァ~ヶ)만 변환\n if (code >= 0x30a1 && code <= 0x30f6) {\n return String.fromCodePoint(code - 0x60);\n }\n return ch;\n })\n .join(\"\");\n}\n\nfunction dictKeysForHiraganaText(dict: SpecialDictionaryEntry[]): string[] {\n // hiraganaText에서 실제로 등장할 수 있는 키만 모으기:\n // - entry.word 자체는 넣어도 되고(못 찾으면 무해)\n // - hira:true면 toHiragana(word)를 추가로 넣는다\n const keys: string[] = [];\n for (const e of dict) {\n keys.push(e.word);\n if (e.hira) keys.push(toHiragana(e.word));\n }\n // 중복 제거\n return [...new Set(keys)].filter(Boolean);\n}\n\ntype Range = { start: number; end: number }; // [start, end)\n\nfunction rangesOverlap(a: Range, b: Range): boolean {\n return a.start < b.end && b.start < a.end;\n}\n\nfunction buildProtectedRanges(text: string, keys: string[]): Range[] {\n const sorted = [...new Set(keys)].sort((a, b) => b.length - a.length);\n const ranges: Range[] = [];\n\n for (const key of sorted) {\n if (!key) continue;\n let from = 0;\n while (true) {\n const idx = text.indexOf(key, from);\n if (idx === -1) break;\n\n const cand: Range = { start: idx, end: idx + key.length };\n if (!ranges.some((r) => rangesOverlap(r, cand))) {\n ranges.push(cand);\n }\n from = idx + 1;\n }\n }\n\n ranges.sort((a, b) => a.start - b.start);\n return ranges;\n}\n\nfunction isProtectedSpan(protectedRanges: Range[], start: number, end: number) {\n for (const r of protectedRanges) {\n if (start < r.end && end > r.start) return true;\n }\n return false;\n}\n\nexport const 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\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}\nfunction isContentToken(t: kuromoji.IpadicFeatures): boolean {\n if (t.pos === \"記号\") return !isHardBoundaryToken(t);\n return true;\n}\n\nfunction prevContentIdx(tokens: kuromoji.IpadicFeatures[], i: number): number {\n for (let j = i - 1; j >= 0; j -= 1) if (isContentToken(tokens[j])) return j;\n return -1;\n}\nfunction nextContentIdx(tokens: kuromoji.IpadicFeatures[], i: number): number {\n for (let j = i + 1; j < tokens.length; j += 1)\n if (isContentToken(tokens[j])) return j;\n return -1;\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\nexport type TokenSpan = {\n start: number; // rewritten 기준\n end: number; // rewritten 기준\n surface: string;\n\n pos?: string;\n pos1?: string;\n pos2?: string;\n pos3?: string;\n\n // ✅ 원문 기반 힌트: katakana 포함 여부(노ート 같은 케이스 차단용)\n originHadKatakana?: boolean;\n};\n\n/**\n * ✅ 핵심:\n * - 토큰화는 \"prewrite 이전\"에 수행 (원문/정규화 기준)\n * - prewrite는 \"토큰 품사\"를 쓰되, 실제 replace는 hiraganaText slice로 수행\n * - 결과로 rewrittenText + rewrittenTokenSpans를 만들어 core로 넘김\n *\n * 가정: hiraganaText와 originalText는 길이가 동일 (toHiragana는 1:1 치환)\n */\nexport function rewriteParticlesFromTokenization(\n originalText: string,\n hiraganaText: string,\n tokenizerTokens: kuromoji.IpadicFeatures[],\n): { rewritten: string; spans: TokenSpan[]; rewrittenOriginal: string } {\n // 코드포인트 배열로 변환 (kuromoji word_position이 코드포인트 기준)\n const hiraChars = Array.from(hiraganaText);\n const originChars = Array.from(originalText);\n\n // entry 기반\n const protectedRanges = buildProtectedRanges(\n hiraganaText,\n dictKeysForHiraganaText(SpecialDictionary),\n );\n\n let out = \"\";\n let origOut = \"\"; // 한자→pronunciation 변환된 원본\n const spans: TokenSpan[] = [];\n\n let cursorInText = 0;\n\n for (let i = 0; i < tokenizerTokens.length; i += 1) {\n const tok = tokenizerTokens[i];\n const wp = (tok as any).word_position as number | undefined;\n const surf = tok.surface_form;\n const surfCpLen = [...surf].length; // 코드포인트 길이\n\n // kuromoji word_position은 코드포인트 기준 (1-based)\n const start = typeof wp === \"number\" ? wp - 1 : cursorInText;\n const end = start + surfCpLen;\n cursorInText = end;\n\n // 한자가 포함된 경우 pronunciation을 사용\n const hasKanji = containsKanji(surf) && tok.pronunciation;\n const hiraSurf = hasKanji\n ? toHiragana(tok.pronunciation!)\n : hiraChars.slice(start, end).join(\"\");\n // 원본도 한자면 pronunciation에서 장음 제거 (길이 맞추기)\n const origSurfForOut = hasKanji\n ? tok.pronunciation!.replace(/ー/g, \"\")\n : originChars.slice(start, end).join(\"\");\n const originSurf = originChars.slice(start, end).join(\"\");\n\n const originHadKatakana = /[\\u30A0-\\u30FF]/.test(originSurf);\n\n // ✅ 불필요하고 위험한 isProtected(튜플 기반 + includes 난사) 제거\n // protectedRanges 기반으로만 판단\n if (isProtectedSpan(protectedRanges, start, end)) {\n const outCpStart = [...out].length;\n out += hiraSurf;\n origOut += origSurfForOut;\n spans.push({\n start: outCpStart,\n end: [...out].length,\n surface: hiraSurf,\n pos: tok.pos,\n pos1: tok.pos_detail_1 ?? undefined,\n pos2: tok.pos_detail_2 ?? undefined,\n pos3: tok.pos_detail_3 ?? undefined,\n originHadKatakana,\n });\n continue;\n }\n\n let replaced = hiraSurf;\n\n // --- は -> わ (계조사) ---\n if (tok.pos === \"助詞\" && hiraSurf === \"は\") {\n if (i > 0 && tokenizerTokens[i - 1].surface_form === \"は\") {\n // keep\n } else if (\n i + 1 < tokenizerTokens.length &&\n tokenizerTokens[i + 1].surface_form === \"は\"\n ) {\n // keep\n } else {\n const prevIdx = prevContentIdx(tokenizerTokens, i);\n if (prevIdx >= 0) {\n const nextIdx = nextContentIdx(tokenizerTokens, i);\n const hasNextContent = nextIdx >= 0;\n const isEndOrPunct = nextBoundaryOrEnd(tokenizerTokens, i);\n\n const prevTok = tokenizerTokens[prevIdx];\n const prevWpHa = (prevTok as any).word_position as number | undefined;\n const prevStartHa = typeof prevWpHa === \"number\" ? prevWpHa - 1 : 0;\n const prevEndHa = prevStartHa + [...prevTok.surface_form].length;\n const prevHira = hiraChars.slice(prevStartHa, prevEndHa).join(\"\");\n\n if (\n !prevHira.includes(\"っ\") &&\n tok.pos_detail_1 === \"係助詞\" &&\n (hasNextContent || isEndOrPunct) &&\n prevTok.pos !== \"助詞\"\n ) {\n replaced = \"わ\";\n }\n }\n }\n }\n\n // --- へ -> え (격조사) ---\n if (tok.pos === \"助詞\" && hiraSurf === \"へ\") {\n // 바로 왼쪽이 공백이면 keep (코드포인트 배열 사용)\n if (start > 0) {\n const left = hiraChars[start - 1];\n if (\n left === \" \" ||\n left === \" \" ||\n left === \"\\t\" ||\n left === \"\\n\" ||\n left === \"\\r\"\n ) {\n // keep\n } else {\n const prevIdx = prevContentIdx(tokenizerTokens, i);\n if (prevIdx >= 0) {\n const prevTok = tokenizerTokens[prevIdx];\n const prevWp = (prevTok as any).word_position as number | undefined;\n const prevStart = typeof prevWp === \"number\" ? prevWp - 1 : 0;\n const prevEnd = prevStart + [...prevTok.surface_form].length;\n\n const prevHiraSurf = hiraChars.slice(prevStart, prevEnd).join(\"\");\n\n if (\n LEXICAL_HE_ENDINGS.some((w) => (prevHiraSurf + \"へ\").endsWith(w))\n ) {\n // keep lexical endings\n } else if (prevHiraSurf.endsWith(\"の\")) {\n // keep \"...のへ\"\n } else if (tok.pos_detail_1 === \"格助詞\") {\n replaced = \"え\";\n }\n }\n }\n }\n }\n\n const outCpStart = [...out].length;\n out += replaced;\n origOut += origSurfForOut;\n\n spans.push({\n start: outCpStart,\n end: [...out].length,\n surface: replaced,\n pos: tok.pos,\n pos1: tok.pos_detail_1 ?? undefined,\n pos2: tok.pos_detail_2 ?? undefined,\n pos3: tok.pos_detail_3 ?? undefined,\n originHadKatakana,\n });\n }\n\n return { rewritten: out, spans, rewrittenOriginal: origOut };\n}\n\n/**\n * 외부에서 쓰기 편한 래퍼:\n * - originalText를 tokenizer로 먼저 tokenize\n * - hiraganaText는 호출자가 넘겨줌(길이 동일 가정)\n */\nexport function tokenizeAndRewriteParticles(\n originalText: string,\n hiraganaText: string,\n tokenizer: Tokenizer,\n): {\n rewritten: string;\n spans: TokenSpan[];\n rewrittenOriginal: string;\n rawTokens: kuromoji.IpadicFeatures[];\n} {\n const rawTokens = tokenizer.tokenize(originalText);\n // console.log(rawTokens);\n const { rewritten, spans, rewrittenOriginal } =\n rewriteParticlesFromTokenization(originalText, hiraganaText, rawTokens);\n return { rewritten, spans, rewrittenOriginal, rawTokens };\n}\n","// --- Tables (당신 코드 그대로) ---\nexport type VowelMain = \"a\" | \"i\" | \"u\" | \"e\" | \"o\";\nexport 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\nexport type MoraInfo = {\n out: string;\n vowelMain: VowelMain;\n consClass: ConsClass;\n vowelOnly?: boolean;\n wasYouon?: boolean;\n};\n\nexport 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 ゔ: { out: \"부\", vowelMain: \"u\", consClass: \"b\" },\n};\n\nexport 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\nexport 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\nexport const SMALL_Y = new Set([\"ゃ\", \"ゅ\", \"ょ\"]);\nexport const SMALL_V = new Set([\"ぁ\", \"ぃ\", \"ぅ\", \"ぇ\", \"ぉ\"]);\n\nexport const U_DROP_KEYS = new Set([\n \"ゆ\",\n \"きゅ\",\n \"しゅ\",\n \"ちゅ\",\n \"にゅ\",\n \"ひゅ\",\n \"みゅ\",\n \"りゅ\",\n \"ぎゅ\",\n \"じゅ\",\n \"びゅ\",\n \"ぴゅ\",\n]);\n","// coreConverter.ts\nimport { SpecialDictionary, type SpecialDictionaryEntry } from \"./dictionary\";\nimport { HARD_BOUNDARY_SURF, type TokenSpan } from \"./particleRewriter\";\nimport {\n type ConsClass,\n type MoraInfo,\n SINGLE,\n YOUON,\n LOAN,\n SMALL_Y,\n SMALL_V,\n U_DROP_KEYS,\n} from \"./mora\";\n\n// --------------------------\n// Kana normalize helpers\n// --------------------------\nfunction isHiraganaChar(ch: string) {\n const c = ch.codePointAt(0)!;\n return c >= 0x3040 && c <= 0x309f;\n}\nfunction isKatakanaChar(ch: string) {\n const c = ch.codePointAt(0)!;\n return c >= 0x30a0 && c <= 0x30ff;\n}\nfunction toHiraganaKey(s: string): string {\n const n = s.normalize(\"NFKC\");\n return Array.from(n)\n .map((ch) => {\n // 장음은 그대로 유지\n if (ch === \"ー\") return ch;\n if (!isKatakanaChar(ch)) return ch;\n const code = ch.codePointAt(0)!;\n // カタカナ 문자 범위 (ァ~ヶ)만 변환\n if (code >= 0x30a1 && code <= 0x30f6) {\n return String.fromCodePoint(code - 0x60);\n }\n return ch;\n })\n .join(\"\");\n}\nfunction toKatakanaKey(s: string): string {\n const n = s.normalize(\"NFKC\");\n return Array.from(n)\n .map((ch) =>\n isHiraganaChar(ch) ? String.fromCodePoint(ch.codePointAt(0)! + 0x60) : ch,\n )\n .join(\"\");\n}\n\ntype DictStream = \"orig\" | \"rewritten\";\ntype CompiledDictItem = {\n keyChars: string[];\n answer: string;\n stream: DictStream;\n};\n\nfunction compileSpecialDictionary(\n dict: SpecialDictionaryEntry[],\n): CompiledDictItem[] {\n const items: CompiledDictItem[] = [];\n\n for (const e of dict) {\n // 기본: exact word는 원본에서만\n items.push({\n keyChars: Array.from(e.word),\n answer: e.answer,\n stream: \"orig\",\n });\n\n // hira:true => hiragana 스트림에서만 (입력 전체가 히라로 바뀌는 파이프라인이기 때문)\n if (e.hira) {\n const k = toHiraganaKey(e.word);\n items.push({\n keyChars: Array.from(k),\n answer: e.answer,\n stream: \"rewritten\",\n });\n }\n\n // kata:true => 원본에서만\n if (e.kata) {\n const k = toKatakanaKey(e.word);\n items.push({ keyChars: Array.from(k), answer: e.answer, stream: \"orig\" });\n }\n }\n\n // 긴 키 우선\n items.sort((a, b) => b.keyChars.length - a.keyChars.length);\n return items;\n}\n\nconst COMPILED_SPECIAL_DICT = compileSpecialDictionary(SpecialDictionary);\n\nfunction isHiragana(ch: string): boolean {\n const c = ch.codePointAt(0)!;\n return c >= 0x3040 && c <= 0x309f;\n}\n\nfunction isKana(ch: string): boolean {\n return isHiragana(ch) || ch === \"ー\";\n}\n\nexport function coreKanaToHangulConvert(\n s: string,\n opts: { tokens: TokenSpan[]; original: string },\n): string {\n // 코드포인트 배열로 변환 (surrogate pair 문제 해결)\n const chars = Array.from(s);\n const origChars = Array.from(opts?.original ?? s);\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 type ReadMora = { key: string; len: number; info?: MoraInfo } | null;\n\n function readMoraAt(idx: number): ReadMora {\n if (idx >= chars.length) return null;\n\n const c0 = chars[idx];\n const c1 = chars[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 readOriginalMoraAt(idx: number): ReadMora {\n if (idx >= origChars.length) return null;\n\n const c0 = origChars[idx];\n const c1 = origChars[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 // 토큰 컨텍스트 탐색용\n const tokens = opts?.tokens ?? null;\n let tokIdx = 0;\n\n function syncTokenIndex(charIndex: number) {\n if (!tokens) return;\n while (tokIdx + 1 < tokens.length && tokens[tokIdx].end <= charIndex) {\n tokIdx++;\n }\n }\n\n function curToken(charIndex: number): TokenSpan | null {\n if (!tokens) return null;\n syncTokenIndex(charIndex);\n const t = tokens[tokIdx];\n if (t && t.start <= charIndex && charIndex < t.end) return t;\n return null;\n }\n\n function prevToken(): TokenSpan | null {\n if (!tokens) return null;\n return tokIdx - 1 >= 0 ? tokens[tokIdx - 1] : null;\n }\n function nextToken(): TokenSpan | null {\n if (!tokens) return null;\n return tokIdx + 1 < tokens.length ? tokens[tokIdx + 1] : null;\n }\n\n // ✅ 유성화 차단 next\n const INITIAL_VOICING_BLOCK_NEXT = new Set([\"い\", \"ひ\", \"ん\", \"て\"]);\n\n function peekNextMoraKeySkippingChoonpu(fromIdx: number): string | null {\n let j = fromIdx;\n while (j < chars.length && chars[j] === \"ー\") j++;\n const m = readMoraAt(j);\n return m?.key ?? null;\n }\n\n // ✅ \"-san\" 판별 (코드포인트 인덱스 기준)\n const SAN_PARTICLES = new Set([\"は\", \"わ\", \"へ\", \"え\", \"を\", \"お\"]);\n function isSanHonorificAt(cpIdx: number): boolean {\n const t = curToken(cpIdx);\n if (!t) return false;\n if (cpIdx < 1 || chars[cpIdx - 1] !== \"さ\") return false;\n\n // t.start는 코드포인트 인덱스, chars.slice 사용\n const local = chars.slice(t.start, cpIdx + 1).join(\"\");\n if (!local.endsWith(\"さん\")) return false;\n\n const hasPrefixInsideToken = cpIdx - 1 > t.start;\n const p = prevToken();\n const prevIsAttachable =\n !!p &&\n p.end === t.start &&\n p.surface.length > 0 &&\n !HARD_BOUNDARY_SURF.has(p.surface);\n\n if (!hasPrefixInsideToken && !prevIsAttachable) return false;\n\n const n = nextToken();\n if (!n) return true;\n if (n.pos === \"記号\" && HARD_BOUNDARY_SURF.has(n.surface)) return true;\n if (n.pos === \"助詞\" && SAN_PARTICLES.has(n.surface)) return true;\n return false;\n }\n\n let out = \"\";\n let i = 0;\n\n let lastMora: MoraInfo | null = null;\n\n while (i < chars.length) {\n // 토큰 기반 \"단어 시작\" 정의: i가 content 토큰 start면 true\n let atTokenStart = false;\n let tokForI: TokenSpan | null = null;\n\n if (tokens) {\n tokForI = curToken(i);\n // ✅ 토큰 시작이면 일단 단어 시작 후보로 인정\n atTokenStart = !!tokForI && tokForI.start === i;\n\n // ✅ 유성화/단어시작 판정에서 \"기호\"와 \"원문 카타카나 토큰\"만 컷\n if (tokForI?.pos === \"記号\") atTokenStart = false;\n } else {\n atTokenStart = i === 0;\n }\n\n // --------------------------\n // ✅ SpecialDictionary (entry 기반 + hira/kata 옵션)\n // --------------------------\n let matchedSpecial = false;\n\n // 긴 키부터 순회하므로, 앞에서 걸리면 끝\n for (const it of COMPILED_SPECIAL_DICT) {\n const src = it.stream === \"orig\" ? origChars : chars; // chars=rewritten\n const len = it.keyChars.length;\n if (i + len > src.length) continue;\n\n let ok = true;\n for (let k = 0; k < len; k++) {\n if (src[i + k] !== it.keyChars[k]) {\n ok = false;\n break;\n }\n }\n if (!ok) continue;\n\n out += it.answer;\n i += len;\n\n // 사전 치환은 단어 단위 => 상태 초기화\n lastMora = null;\n\n matchedSpecial = true;\n break;\n }\n if (matchedSpecial) continue;\n\n const ch = chars[i];\n\n /**\n * ー 표시는 그냥 드랍\n */\n if (ch === \"ー\") {\n i += 1;\n continue;\n }\n\n /**\n * 비가나: 그대로\n */\n if (!isKana(ch)) {\n out += ch;\n i += 1;\n lastMora = null;\n continue;\n }\n\n /**\n * 촉음 규칙\n */\n if (ch === \"っ\") {\n if (!out || !isHangulSyllable(out[out.length - 1])) {\n // \"ッ\"으로 바꾸기\n out += \"ッ\";\n i += 1;\n lastMora = null;\n continue;\n }\n\n const next = readMoraAt(i + 1);\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 * おお 장음 규칙\n */\n if (ch === \"お\" && chars[i + 1] === \"お\") {\n let j = i;\n while (chars[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 const originalMora = readOriginalMoraAt(i);\n if (!mora) {\n out += chars[i];\n i += 1;\n lastMora = null;\n continue;\n }\n // 에러\n if (!originalMora) {\n throw Error(\"원본 모라 손실\");\n }\n\n /**\n * ん 규칙\n */\n if (mora.key === \"ん\") {\n const next = readMoraAt(i + 1);\n const nextInfo = next?.info;\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 // 토큰 컨텍스트 기반 \"-san\" → '상'\n if (lastMora?.out === \"사\" && isSanHonorificAt(i)) {\n out = replaceLastHangul(out, JONG.NG);\n i += 1;\n continue;\n }\n\n // --- 기존 ん 동화 규칙 ---\n let jong: number = JONG.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 /**\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 // ✅ 단어(토큰) 시작 유성화: と/こ만 + 예외(이/히/ん/테) + (앞이 っ이면 금지)\n let outSyl = info.out;\n if (atTokenStart && (mora.key === \"と\" || mora.key === \"こ\")) {\n const prevIsSokuon = i > 0 && chars[i - 1] === \"っ\";\n\n const nextKey = peekNextMoraKeySkippingChoonpu(i + mora.len);\n const blockedByNext =\n !!nextKey && INITIAL_VOICING_BLOCK_NEXT.has(nextKey);\n\n const isKou = mora.key === \"こ\" && chars[i + mora.len] === \"う\";\n\n // ✅ 추가: \"진짜 조사 と/こ\"로 쓰인 경우만 유성화 차단\n // - 현재 토큰이 1글자 'と'/'こ'이고,\n // - 이전 토큰이 내용어(명사/동사/형용사 등)면 => 조사로 판단 => 유성화 금지\n let blockedByParticleUsage = false;\n if (tokens && tokForI) {\n const isSingleCharToken = tokForI.surface.length === 1;\n const tokenMatchesMora = tokForI.surface === mora.key;\n\n if (isSingleCharToken && tokenMatchesMora) {\n const p = prevToken(); // curToken(i) 호출로 tokIdx는 sync된 상태\n const prevLooksLikeContent =\n !!p &&\n p.pos !== \"記号\" &&\n p.pos !== \"助詞\" &&\n p.pos !== \"助動詞\" &&\n !HARD_BOUNDARY_SURF.has(p.surface);\n\n if (prevLooksLikeContent) blockedByParticleUsage = true;\n }\n }\n\n const allowVoicing =\n !prevIsSokuon && !blockedByNext && !isKou && !blockedByParticleUsage;\n\n if (allowVoicing) {\n if (mora.key === \"と\") outSyl = \"도\";\n else if (mora.key === \"こ\") outSyl = \"고\";\n }\n }\n\n out += outSyl;\n lastMora = { ...info, out: outSyl };\n\n /**\n * 연음 드랍\n */\n const next1 = chars[i + mora.len]; // 다음 언어\n const afterLen = chars[i + mora.len + 1]; // 다다음언어\n\n // o + う 드랍\n if (next1 === \"う\" && info.vowelMain === \"o\") {\n i += mora.len + 1;\n continue;\n }\n\n // ゅう 드랍 (きゅう) // ゆう는 드랍할까말까? (유우리) 일단 ゆう도 드랍함!\n if (next1 === \"う\" && U_DROP_KEYS.has(mora.key)) {\n i += mora.len + 1;\n continue;\n }\n\n // い 드랍\n if (next1 === \"い\") {\n // せんせい -> 센세\n if (mora.key === \"せ\") {\n i += mora.len + 1;\n continue;\n }\n // 케도 장음인데, 케이사츠라고 검색하는 경우가 더 많으려나? 어떻게 할까...\n else if (mora.key === \"け\") {\n i += mora.len + 1;\n continue;\n }\n // えいご -> 에고\n else if (mora.key === \"え\") {\n // 조사 へ 감지\n if (originalMora.key !== \"へ\") {\n i += mora.len + 1;\n continue;\n }\n }\n // 오이시, 야사시 대응\n else if (mora.key === \"し\") {\n i += mora.len + 1;\n continue;\n }\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","// kanaToHangul.ts\n// 전체 변환 파이프라인을 orchestration만 담당하도록 정리했습니다.\nimport type { Tokenizer } from \"./tokenizer\";\nimport { normalizeInputText, toHiragana } from \"./normalizer\";\nimport { tokenizeAndRewriteParticles } 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\nexport function convertWithTokenizer(\n input: string,\n tokenizer: Tokenizer,\n): string {\n const normalized = normalizeInputText(input);\n\n // ✅ 길이 1:1 보장되는 kana 변환을 먼저 수행(스팬 유지)\n const hiragana = toHiragana(normalized);\n\n // ✅ 핵심: prewrite 이전에 토큰화(=normalized 기준), prewrite는 토큰(pos)을 사용하되 slice는 hiragana 기준\n const { rewritten, spans, rewrittenOriginal } = tokenizeAndRewriteParticles(\n normalized,\n hiragana,\n tokenizer,\n );\n\n // ✅ rewritten + rewritten spans 로 core\n // 한자가 pronunciation으로 변환된 경우 rewrittenOriginal도 같은 길이로 변환됨\n return coreKanaToHangulConvert(rewritten, {\n tokens: spans,\n original: rewrittenOriginal,\n });\n}\n","import kuromoji from \"kuromoji\";\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\n\nconst req = 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(req.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 * Kanabarum - 일본어 가나를 한글로 변환하는 클래스\n *\n * @example\n * const kanabarum = new Kanabarum();\n * await kanabarum.init();\n * kanabarum.kanaToHangul(\"こんにちは\"); // \"곤니치와\"\n */\nexport class Kanabarum {\n private converter: KanaToHangul | null = null;\n\n /**\n * 토크나이저를 초기화합니다. 변환 전에 반드시 호출해야 합니다.\n */\n async init(): Promise<void> {\n const tokenizer = await getTokenizer();\n this.converter = createKanaToHangul(tokenizer);\n }\n\n /**\n * 일본어 가나를 한글로 변환합니다.\n * @param input 변환할 일본어 문자열\n * @returns 한글로 변환된 문자열\n * @throws init()이 호출되지 않은 경우 에러\n */\n kanaToHangul(input: string): string {\n if (!this.converter) {\n throw new Error(\"Kanabarum is not initialized. Call init() first.\");\n }\n return this.converter(input);\n }\n}\n\n// ============================================\n// Async helper (기존 API 호환)\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\n/**\n * 일본어 가나를 한글로 변환합니다. (async helper)\n * init 없이 바로 호출 가능합니다.\n *\n * @example\n * await kanaToHangul(\"こんにちは\"); // \"곤니치와\"\n */\nexport async function kanaToHangul(input: string): Promise<string> {\n const converter = await initKanaToHangul();\n return converter(input);\n}\n\nexport type { KanaToHangul };\n"]}
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import kuromoji from 'kuromoji';
2
+ import path from 'path';
3
+ import { createRequire } from 'module';
2
4
 
3
5
  // src/normalizer.ts
4
6
  function normalizeInputText(input) {
@@ -859,18 +861,10 @@ function convertWithTokenizer(input, tokenizer) {
859
861
  original: rewrittenOriginal
860
862
  });
861
863
  }
864
+ var req = createRequire(import.meta.url);
862
865
  async function buildTokenizer() {
863
- const isNode = typeof process !== "undefined" && typeof process.versions?.node === "string";
864
- let dicPath;
865
- if (isNode) {
866
- const { default: path } = await import('path');
867
- const { createRequire } = await import('module');
868
- const req = createRequire(import.meta.url);
869
- dicPath = path.join(req.resolve("kuromoji"), "..", "..", "dict");
870
- } else {
871
- dicPath = "https://cdn.jsdelivr.net/npm/kuromoji@0.1.2/dict/";
872
- }
873
866
  return new Promise((resolve, reject) => {
867
+ const dicPath = path.join(req.resolve("kuromoji"), "..", "..", "dict");
874
868
  kuromoji.builder({ dicPath }).build((err, tk) => {
875
869
  if (err || !tk)
876
870
  reject(err);