はじめに
漢字の読みをサッと確認したい、でも変換候補を出すためだけに検索するのも面倒——そんな場面のために、日本語の文章を入力すると単語ごとにふりがなを表示し、さらに音声でも読み上げてくれるデスクトップアプリ「日本語 読み方チェッカー」を作りました。
Pythonの標準GUIライブラリ Tkinter と、形態素解析エンジン SudachiPy、音声合成の gTTS だけで構成したシンプルな構成です。OSSとして公開しているので、この記事では機能と実装のポイントを紹介します。
- リポジトリ: https://github.com/softjapan/yomi-japanese
- ライセンス: MIT
デモ
文章を入力して「読み方を調べる」を押すと、漢字を含む単語にだけふりがなが付き、文章としてそのまま自然に折り返して表示されます。助詞や記号には読みを重ねて表示しない(本物のふりがなと同じ挙動)のがポイントです。
主な機能
- 単語ごとのふりがな表示:SudachiPyで形態素解析し、漢字を含む語だけにひらがなの読みを添えて表示
- 音声読み上げ:gTTSでテキストを音声合成し、その場で再生
- クリップボード貼り付け:ボタン一つで入力欄をクリアしてから貼り付け
- ドラッグ不要の見やすいUI:Canvas描画による角丸ボタンなど、Tkinterでも今どきの見た目に寄せたデザイン
技術的なポイント
1. 読みは「ひらがな」で、しかも本物のふりがなと同じルールで
SudachiPyのreading_form()はカタカナで読みを返しますが、日本語として自然に読めるようひらがなに変換しています。
def katakana_to_hiragana(text: str) -> str:
chars = []
for ch in text:
code = ord(ch)
if 0x30A1 <= code <= 0x30F6:
chars.append(chr(code - 0x60))
else:
chars.append(ch)
return "".join(chars)
さらに、助詞や記号(「は」「です」「、」など)にまで読みを表示すると画面がうるさくなるため、漢字を含む語だけにふりがなを出すようにしています。これは実際の書籍・新聞のふりがなの付け方と同じルールです。
KANJI_RE = re.compile(r"[一-鿿㐀-䶿]")
show_reading = bool(KANJI_RE.search(surface))
2. 「文章として流れる」ふりがな表示
単語ごとの読みは、カード状に区切って並べるのではなく、tk.Textウィジェットにwindow_create()で小さなFrame(読み+表記の2段ラベル)を埋め込み、wrap="char"で折り返す方式にしました。これにより、通常の文章のように自然に折り返しつつ、漢字の上に小さく読みが乗る——という、まさに紙のふりがなに近い見た目を実現しています。
3. 音声読み上げをUIブロックせずに実行
gTTSはネットワーク越しに音声を合成するため、そのままメインスレッドで呼ぶとGUIがフリーズします。バックグラウンドスレッドで処理し、完了時だけroot.after(0, ...)でメインスレッドに結果を戻す、という定石のパターンで実装しました。
def speak_text(self):
...
threading.Thread(target=self._speak_worker, args=(text,), daemon=True).start()
def _speak_worker(self, text):
...
gTTS(text=text, lang="ja").save(tmp_path)
play_audio(tmp_path)
...
self.root.after(0, self._reset_voice_button)
再生中はボタンを無効化して「再生中…」に表示を切り替えることで、多重クリックによる音声の重複再生も防いでいます。
ハマった話:Tk 9.0.3でCJKグリフが潰れて描画されるバグ
開発中、ヘッダーのサブタイトル文字だけが文字化けしたように潰れて表示される不具合に遭遇しました。フォントサイズを大きくしても直らず、原因調査に苦労しました。
切り分けた結果、固定高さ(pack_propagate(False))を設定したFrameの中に、ラベルを2段重ねた入れ子Frameを配置するという構成が引き金になっていることが判明。固定高さのFrame内で子Frameの垂直位置を計算する際に端数ピクセルの配置が発生し、それがTk 9.0.3(macOS)のCore Textによる日本語グリフ描画を壊していたようです。
対策はシンプルで、固定高さの指定をやめてコンテンツに合わせた自然なサイズに任せるだけ。これで文字化けは解消しました。SudachiPyの辞書ロードやフォントサイズなど、一見関係しそうな要素を一つずつ切り分けて検証したのは、地味ながら効果的なデバッグでした。
インストールと実行
パッケージ管理には uv を使っています。
git clone https://github.com/softjapan/yomi-japanese.git
cd yomi-japanese
uv sync
uv run python yomi.py
テスト・CI
Tkinterに依存しない純粋ロジック(形態素解析・かな変換)をyomi_core.pyに切り出し、pytestでユニットテストしています。GitHub Actionsでpushのたびに自動実行されるようにしました。
uv run pytest
今後の展望
現状はPython製のデスクトップアプリですが、形態素解析はkuromoji.js + kuroshiro(またはSudachi.rsのWASM移植)、音声読み上げはブラウザ標準のWeb Speech APIを使えば、サーバーなしの静的Webアプリとしても十分成立しそうです。特にkuroshiroはHTMLの<ruby><rt>タグでふりがなを出力できるため、今回Tkinterで自作した表示ロジックがブラウザではよりシンプルに書けそうだと分かりました。機会があればJS版にも挑戦してみたいです。
おわりに
シンプルな構成でも、実際に手を動かすと「読みをひらがなにする」「助詞には読みを出さない」「日本語グリフが潰れる謎のバグ」など、日本語処理ならではの細かい落とし穴がいろいろありました。コードは全て公開しているので、気になった方はぜひ覗いてみてください。
