文字どおりではなく、音のとおりに
日本語を話す学習者に、韓国語の読みをカタカナで見せるとき、文字をひとつずつ置き換えると間違った発音になります。
| 韓国語 | 文字どおり(間違い) | 実際の発音(この変換) |
|---|---|---|
| 감사합니다 | カムサハプニダ | カムサハムニダ(鼻音化) |
| 맛있어요 | マッイッオヨ | マシッソヨ(連音) |
| 같이 | カッイ | カチ(口蓋音化) |
| 좋아요 | チョッアヨ | チョアヨ(ㅎ の脱落) |
| 국물 | クッムル | クンムル(鼻音化) |
| 신라 | シンラ | シルラ(流音化) |
| 밥 먹었어? | パプ モゴッソ? | パム モゴッソ?(分かち書きを越えた鼻音化) |
(右の列は、この記事の変換を実際に動かした結果です。)
ハングルの学習アプリ「推し韓マル」の読みの表記と、Web「iffthen hangul」の読む画面で使っている変換の仕組みです。
構造:規則が先、カナは後
- 音節 → 初声・中声・終声に分ける(分かち書きと句読点は、境目の情報として記録)
- 標準の発音の規則を、決まった順番で当てる(字母の段階)
- 変わった字母 → カナ
規則と表記を混ぜないのが要です。「ㅂ パッチム + ㄴ = ㅁ」は字母の段階の規則で、「ㅁ パッチム = ム」は表記の段階の仕事です。
1)分けることと、境目
function parse(text) {
const out = [];
let boundary = 'phrase'; // 文の頭
for (const ch of text) {
const code = ch.codePointAt(0) - 0xac00;
if (code >= 0 && code < 11172) {
out.push({ ch, cho: CHO[Math.floor(code / 588)], jung: JUNG[Math.floor((code % 588) / 28)],
jong: JONG[code % 28], boundary, after: '', tensedInWord: false });
boundary = ''; // 同じ語の中
} else {
const tail = PUNCT[ch] ?? ch;
if (out.length > 0) out[out.length - 1].after += tail;
if (/\s/.test(ch)) boundary = boundary !== 'phrase' ? 'word' : 'phrase'; // 分かち書き
else if (ch in PUNCT) boundary = 'phrase'; // 句読点
}
}
return out;
}
境目は 3 種類です。''(語の中)・'word'(分かち書きの後)・'phrase'(文の頭・句読点の後)。規則ごとに、越えられる境目が違います。
- 連音・濃音化 = 語の中だけ
- 鼻音化・流音化 = 分かち書きを越えても(続けて話すときの音・밥 먹었어 → パム モゴッソ)
2)規則を当てる順番(順番が結果を変える)
function applyRules(s) {
// 1. ㅎ の規則 (激音化 · ㅎ の脱落 · 共鳴音の後の ㅎ の弱化)
// 2. 連音 · 口蓋音化 (語の中)
// 3. パッチムの代表音 (7 終声)
// 4. 鼻音化 · 流音化 (分かち書きを越えて当てる)
// 5. 濃音化 (語の中)
// 6. 語の中の母音の後の濃音 → ッ で表す
}
たとえば 2 番の連音は、必ず 3 番の代表音より先に来なければなりません。「맛있어요」の ㅅ パッチムが代表音の ㄷ に変わる前に、次の音節へ移らないと、マシッソヨになりません。
連音・口蓋音化の部分です。
for (let i = 0; i < n - 1; i += 1) {
const a = s[i], b = s[i + 1];
if (b.boundary !== '' || b.cho !== 'ㅇ' || a.jong === '' || a.jong === 'ㅇ') continue;
let moved;
if (a.jong in SPLIT) { // 二重パッチム: 片方は残り、片方は移る (닭이 → 달기)
const [remain, m] = SPLIT[a.jong];
a.jong = remain;
moved = m;
} else {
moved = a.jong;
a.jong = '';
}
if ((moved === 'ㄷ' || moved === 'ㅌ') && b.jung === 'ㅣ') moved = moved === 'ㄷ' ? 'ㅈ' : 'ㅊ'; // 같이 → 가치
if (moved === 'ㅎ') moved = 'ㅇ';
b.cho = moved;
}
鼻音化・流音化の部分です。
for (let i = 0; i < n - 1; i += 1) {
const a = s[i], b = s[i + 1];
if (b.boundary === 'phrase') continue; // 文の境目は越えない
if (b.cho === 'ㄹ' && (a.jong === 'ㅁ' || a.jong === 'ㅇ')) b.cho = 'ㄴ';
if (b.cho === 'ㄹ' && (a.jong === 'ㄱ' || a.jong === 'ㅂ')) b.cho = 'ㄴ';
if ((b.cho === 'ㄴ' || b.cho === 'ㅁ') && OBSTRUENT_FINAL.has(a.jong))
a.jong = { ㄱ: 'ㅇ', ㄷ: 'ㄴ', ㅂ: 'ㅁ' }[a.jong]; // 합니다 → 함니다 · 국물 → 궁물
if ((a.jong === 'ㄴ' && b.cho === 'ㄹ') || (a.jong === 'ㄹ' && b.cho === 'ㄴ')) {
a.jong = 'ㄹ'; // 신라 → 실라
b.cho = 'ㄹ';
}
}
スポンサーリンク
スポンサーリンク
3)カナの表記:清音と濁音は「前の音」が決める
韓国語の平音 ㄱ ㄷ ㅂ ㅈ は、文の頭では無声音(カ タ パ チャ)、母音や共鳴音(ㄴ ㄹ ㅁ ㅇ)の後では有声音(ガ ダ バ ジャ)に聞こえます。
const prev = i > 0 ? s[i - 1] : null;
const voicedEnv = x.boundary !== 'phrase' && prev !== null && (prev.jong === '' || SONORANT_FINAL.has(prev.jong));
const [unvoiced, voiced] = ROW[x.cho]; // ㄱ: ['k', 'g'] · ㄷ: ['t', 'd'] ...
const row = LENIS.has(x.cho) && voicedEnv ? voiced : unvoiced;
let kana = KANA[row][V[x.jung]];
if (x.tensedInWord) kana = `ッ${kana}`; // 語の中の母音の後の濃音
パッチムは ㄱ = ク・ㄴ ㅇ = ン・ㄷ = ッ・ㄹ = ル・ㅁ = ム・ㅂ = プ で、ㄱ パッチム+ㄱ の系列・ㅂ パッチム+ㅂ の系列の前では ッ と書きます。
日本で定着している表記は、例外として合わせます。たとえば、ㄱ の系列+ㅙ ㅞ は ケ/ゲ と書きます(괜찮아 → ケンチャナ)。
音節ごとに分けた結果も一緒に返す
読む画面では、文字のマスごとにその音節の読みを付ける必要があります。同じ計算で、音節ごとに分けた結果を返します。前後の音の変化を反映したまま分かれます。
toRubyJaParts('감사합니다');
// [{ko:'감',kana:'カム'},{ko:'사',kana:'サ'},{ko:'합',kana:'ハム'},{ko:'니',kana:'ニ'},{ko:'다',kana:'ダ'}]
「합」は単独ならハプですが、ここでは後ろの「니」のためにハムになります。
Python の元と JS の移植版を、テストでつなぐ
この変換は、もともとコンテンツのビルド用に Python で作り、Web(ブラウザ・静的な生成)のために JS に移しました。2 つがずれると、アプリと Web の読みが違ってしまいます。
そこで、一致のテストを置いています。
- 対象 1:コンテンツの元ファイルにあるすべての韓国語(単語・例文・表現)
- 対象 2:正解の組の表
- 対象 3:決定的な乱数で作った文(分かち書き・句読点を含む)
これらをすべて 2 つの実装に通し、ひとつでも違えば失敗にします。元を直したら移植版も一緒に直す、というのが決まりになります。
また、コンテンツの読みの欄は人が書きません。
--write:変換の結果で埋める--check:元ファイルの読みが変換の結果と同じかを見る(違えば失敗・手で直した例外だけ許す)
限界(正直に)
- サイシオッ、ㄴ の添加(색연필 → 생년필)、「알게 되다」のように ㄹ の後の -게 が濃音にならない場合は扱っていません。こうした語は、手で直した値を例外の一覧に置いています
- 形態素解析をせず、「文字+境目」だけで計算しています。だから軽くて速いのですが、語の境目が分かち書きと違う合成語は間違えることがあります
スポンサーリンク
スポンサーリンク
この記事について
自社のハングル学習アプリ「推し韓マル」と、Web「iffthen hangul」の読みの変換を作ったときの記録です。コードは実際のソースから抜き出し、コメントを読みやすく縮めています。動作を変える修正はしていません。表の右の列は、この変換を実際に動かした結果です。
- 読みの変換を使ってみる(無料):iffthen hangul の読む画面
- アプリ:推し韓マル(App Store・Google Play)


コメント