日本語の文字変換ツールを作るとき、比較的実装しやすい処理のひとつが「ひらがな ⇄ カタカナ」の相互変換です。
たとえば、
こんにちは
↓
コンニチハ
あるいは、
カタカナ
↓
かたかな
のような変換です。
一見すると、大量の変換表を用意しなければならないように見えます。
しかし、通常のひらがなとカタカナの多くはUnicode上で一定の規則に沿って配置されているため、コードポイントの差を利用して変換できます。
この記事では、JavaScriptでひらがな・カタカナ変換を実装する考え方と、実装時に注意したい文字について整理します。
ひらがなとカタカナはUnicode上で近い位置にある
Unicodeでは、ひらがなとカタカナは別のブロックに配置されています。
代表的な例を見ると、
| ひらがな | Unicode | カタカナ | Unicode |
|---|---|---|---|
| あ | U+3042 | ア | U+30A2 |
| い | U+3044 | イ | U+30A4 |
| う | U+3046 | ウ | U+30A6 |
| か | U+304B | カ | U+30AB |
| ん | U+3093 | ン | U+30F3 |
通常の文字では、
カタカナのコードポイント
=
ひらがなのコードポイント + 0x60
という関係があります。
たとえば「あ」は U+3042、「ア」は U+30A2 です。
16進数で差を計算すると、
0x30A2 - 0x3042 = 0x60
となります。
この規則を利用すると、文字ごとの巨大な変換表を作らなくても変換できます。
ひらがなからカタカナへ変換する基本コード
JavaScriptでは、文字のコードポイントを取得して 0x60 を加えることで変換できます。
function hiraganaToKatakana(text) {
return text.replace(/[\u3041-\u3096]/g, (char) => {
return String.fromCharCode(char.charCodeAt(0) + 0x60);
});
}
たとえば、
hiraganaToKatakana("こんにちは");
// コンニチハ
となります。
カタカナからひらがなへ戻す
逆変換では 0x60 を引きます。
function katakanaToHiragana(text) {
return text.replace(/[\u30A1-\u30F6]/g, (char) => {
return String.fromCharCode(char.charCodeAt(0) - 0x60);
});
}
例:
katakanaToHiragana("コンニチハ");
// こんにちは
基本的な文字であれば、この方法で双方向変換できます。
濁音・半濁音もそのまま変換できる
このUnicodeの規則は、通常の五十音だけではありません。
濁音や半濁音も同じ範囲に含まれています。
たとえば、
が → ガ
ぎ → ギ
ぐ → グ
げ → ゲ
ご → ゴ
ぱ → パ
ぴ → ピ
ぷ → プ
ぺ → ペ
ぽ → ポ
などです。
そのため、
hiraganaToKatakana("がぎぐげご");
// ガギグゲゴ
hiraganaToKatakana("ぱぴぷぺぽ");
// パピプペポ
のように変換できます。
小さい「ゃ・ゅ・ょ・っ」も変換できる
小書き文字も同じ仕組みで変換できます。
ぁ → ァ
ぃ → ィ
ぅ → ゥ
ぇ → ェ
ぉ → ォ
ゃ → ャ
ゅ → ュ
ょ → ョ
っ → ッ
たとえば、
hiraganaToKatakana("きゃしゅちょ");
// キャシュチョ
となります。
拗音を含む普通の文章でも、文字単位で処理できるため特別な単語辞書は必要ありません。
「ゔ」と「ヴ」も変換できる
「ゔ」と「ヴ」もUnicode上で対応しています。
ゔ ⇄ ヴ
そのため、
hiraganaToKatakana("ゔぁいおりん");
// ヴァイオリン
のような処理も可能です。
すべての文字を0x60だけで処理してはいけない
ここが実装時に重要なポイントです。
「ひらがなとカタカナは全部0x60ずらせばいい」と考えると、一部の文字で問題が起こります。
特に、
ゕ ⇄ ヵ
ゖ ⇄ ヶ
ゝ ⇄ ヽ
ゞ ⇄ ヾ
のような文字は、通常の範囲処理だけに任せず、明示的に扱ったほうが安全です。
例:
const specialHiraToKata = {
"ゕ": "ヵ",
"ゖ": "ヶ",
"ゝ": "ヽ",
"ゞ": "ヾ"
};
const specialKataToHira = {
"ヵ": "ゕ",
"ヶ": "ゖ",
"ヽ": "ゝ",
"ヾ": "ゞ"
};
基本範囲と特殊文字を分けると、実装の意図もわかりやすくなります。
実用的な変換関数
基本範囲と特殊文字を合わせると、次のように実装できます。
const SPECIAL_HIRA_TO_KATA = {
"ゕ": "ヵ",
"ゖ": "ヶ",
"ゝ": "ヽ",
"ゞ": "ヾ"
};
const SPECIAL_KATA_TO_HIRA = {
"ヵ": "ゕ",
"ヶ": "ゖ",
"ヽ": "ゝ",
"ヾ": "ゞ"
};
function hiraganaToKatakana(text) {
return Array.from(text, (char) => {
if (SPECIAL_HIRA_TO_KATA[char]) {
return SPECIAL_HIRA_TO_KATA[char];
}
const code = char.charCodeAt(0);
if (code >= 0x3041 && code <= 0x3096) {
return String.fromCharCode(code + 0x60);
}
return char;
}).join("");
}
function katakanaToHiragana(text) {
return Array.from(text, (char) => {
if (SPECIAL_KATA_TO_HIRA[char]) {
return SPECIAL_KATA_TO_HIRA[char];
}
const code = char.charCodeAt(0);
if (code >= 0x30A1 && code <= 0x30F6) {
return String.fromCharCode(code - 0x60);
}
return char;
}).join("");
}
漢字や英数字は変換しない
実際のツールでは、ひらがな・カタカナ以外の文字をどう扱うかも重要です。
たとえば、
今日はこんにちは
をカタカナ方向へ変換する場合、
今日ハコンニチハ
となります。
「今日」は漢字なのでそのままです。
同様に、
ABC123 ありがとう
なら、
ABC123 アリガトウ
となります。
つまり、対象範囲外の文字はそのまま返す設計にしておけば、文章全体を安全に処理できます。
「東京タワーで遊ぶ」を変換するとどうなる?
文章単位で考えるとわかりやすいです。
入力:
東京タワーで遊ぶ
ひらがな → カタカナ方向へ変換すると、
東京タワーデ遊ブ
となります。
変換されるのは、
で → デ
ぶ → ブ
です。
一方、
東京
タワー
遊
はそのまま残ります。
長音符「ー」は変換しない
カタカナでよく使われる、
ー
は長音符です。
これはひらがな・カタカナの対応文字ではないため、そのまま保持します。
たとえば、
スーパー
をひらがな方向へ処理すると、
すーぱー
のように、カタカナ部分だけがひらがなになります。
長音符自体は変化しません。
中点「・」もそのまま
中点も同じです。
テスト・データ
をひらがなへ変換すると、
てすと・でーた
となります。
「・」は変換対象ではありません。
半角カタカナは別処理にする
次に注意したいのが、
カタカナ
のような半角カタカナです。
これは通常の全角カタカナとはUnicode上の配置が異なります。
そのため、
カタカナ ⇄ かたかな
と、
カタカナ ⇄ カタカナ
は別の変換として考えたほうが実装しやすくなります。
ひらがな・カタカナ変換では、半角カタカナをそのまま保持し、必要なら別の変換処理を用意する設計がわかりやすいです。
「ヷ・ヸ・ヹ・ヺ」はどう扱う?
カタカナには、
ヷ
ヸ
ヹ
ヺ
という文字もあります。
これらは通常のひらがな側に直接対応する一般的な文字がないため、単純な双方向変換の対象から外す設計が安全です。
つまり、
ヷ → ヷ
のように、そのまま保持します。
「変換できない文字を無理に変える」のではなく、「対応関係が明確な文字だけを変換する」という考え方です。
正規表現だけでなく文字単位で処理する理由
単純な用途なら String.replace() だけでも十分です。
ただし、実際のツールでは、
- 特殊文字の個別マッピング
- 変換対象外の保持
- 将来的なルール追加
- テストのしやすさ
を考えると、文字単位で処理する実装も扱いやすくなります。
たとえば、
Array.from(text, (char) => {
// 1文字ずつ判定
}).join("");
の形にしておけば、特殊ケースを追加しやすくなります。
IME入力ではcompositionイベントにも注意する
Web上で日本語入力ツールを作る場合、変換ロジックだけでなくIME入力にも注意が必要です。
日本語IMEでは、入力中の文字が確定するまで、
compositionstart
compositionupdate
compositionend
というイベントが発生します。
IME変換中に毎回入力欄を書き換えてしまうと、
- 変換候補が消える
- カーソル位置がずれる
- 日本語入力が確定できない
といった問題が起きることがあります。
そのため、リアルタイム変換ツールでは、
let isComposing = false;
input.addEventListener("compositionstart", () => {
isComposing = true;
});
input.addEventListener("compositionend", () => {
isComposing = false;
update();
});
input.addEventListener("input", () => {
if (!isComposing) {
update();
}
});
のように、IME変換中は処理を待つ設計にすると安定します。
双方向入力では「入力した側を変更しない」
ひらがなとカタカナを2つの入力欄で相互変換する場合は、
ひらがな欄
↓
カタカナ欄
だけでなく、
カタカナ欄
↓
ひらがな欄
にも対応できます。
このとき重要なのは、ユーザーが現在入力している側をプログラムから書き換えないことです。
入力側を書き換えるとIMEやカーソル操作に影響しやすくなります。
そのため、
hiraInput.addEventListener("input", () => {
kataInput.value = hiraganaToKatakana(hiraInput.value);
});
kataInput.addEventListener("input", () => {
hiraInput.value = katakanaToHiragana(kataInput.value);
});
のように、反対側だけを更新する形がシンプルです。
テストしておきたい文字
実装後は、単純な「あいうえお」だけでなく、境界ケースも確認したほうが安全です。
五十音
あいうえお
↓
アイウエオ
濁音
がぎぐげご
↓
ガギグゲゴ
半濁音
ぱぴぷぺぽ
↓
パピプペポ
拗音
きゃしゅちょ
↓
キャシュチョ
小書き文字
ぁぃぅぇぉ っゃゅょ
↓
ァィゥェォ ッャュョ
特殊な仮名
ゔゕゖ
↓
ヴヵヶ
繰り返し記号
ゝゞ
↓
ヽヾ
こうしたケースをまとめてテストしておくと、単純なUnicode変換だけでは見落としやすい問題を確認できます。
実際にブラウザで変換を確認する
実装を試すだけならJavaScriptの関数でも十分ですが、文章を貼り付けて結果を確認したい場合は、ひらがな・カタカナ変換ツール で双方向に変換できます。
漢字・英数字・記号を残したまま、文章中のひらがなまたはカタカナだけを変換したい場合にも確認できます。
まとめ
ひらがな・カタカナ変換は、多くの文字でUnicodeコードポイントの差 0x60 を利用できます。
基本的な考え方は、
ひらがな → コードポイント + 0x60 → カタカナ
カタカナ → コードポイント - 0x60 → ひらがな
です。
ただし、実際のツールとして使う場合は、
- ゕ ⇄ ヵ
- ゖ ⇄ ヶ
- ゝ ⇄ ヽ
- ゞ ⇄ ヾ
- 漢字や英数字の保持
- 長音符や中点の保持
- 半角カタカナを対象外にする
- 対応しないカタカナを無理に変換しない
- IME入力中に処理しない
といった点も考える必要があります。
単純なUnicode変換だけで終わらせず、入力体験や例外文字まで含めて設計すると、実用的な日本語変換ツールになります。
