BLOGサブスレッドの日常

2020.08.17

Google日本語入力の辞書を、macOS標準の日本語入力プログラムの辞書へ変換する

s.kono

このエントリーを書いた経緯

Twitterで少しGoogle日本語入力が話題に上がっているようです。

大体↑のような流れのようです。

Google日本語入力で使われているmozcの開発が停止していていることは、
今回話題になる前にもツイートしている方がいらっしゃいました。元ツイートは見つけられないのですが、僕はそのツイートでmozc開発停止を知りました。
その頃から僕は、そろそろGoogle日本語入力卒業した方がよいのかな、と考えていました。

ただ、現在Google日本語入力を使っていて困っていないことと、Google日本語入力の変換精度は結構気に入っていたこともあり、別の日本語入力へ変更する理由も機会もなく、今に至ります。

強いて言うならだいたい2018年頃から、文字を入力していると、特殊文字が文中に含まれてしまうという問題があったくらいです。
(しかしこの問題も、はたしてGoogle日本語入力のせいなのかということすら不明)

macOS標準の日本語入力プログラムを試すことにした

じゃあGoogle日本語入力をやめたとして、日本語入力は何を使って行うのか、ということになります。
僕は、日本語入力は変換の精度と安定性を重視しています。
そうなったときに真っ先に候補に上がるのが、macOSに標準で入っている日本語入力です。

ただ、macOS標準の日本語入力、変換精度がすごく悪かったという印象が残ってまして、あまり使うことに気が乗らない気持ちでした…
僕はmacOSを使い始めた頃はmacOS標準の日本語入力を使っていたのですが、どうにも精度が悪くGoogle日本語入力に切り替えた経緯があり、よくない記憶が残っていたのです。

しかし、どうやらmacOSは2014年に旧来の「ことえり」を捨てて、新しい日本語入力エンジンに変更していたようです。(…今更知った…)
もう今はmacOS標準の日本語入力を「ことえり」とは呼ばないみたいですね。
2014年に登場したということは、現在までにもうかなりモノとして安定していると思ってよさそうです。

ということで、しばらくmacOS標準の日本語入力プログラムを使ってみることにしました。

大変社内的に申し上げにくいのですが、T-Codeは選択肢に…ないです…( ˘ω˘))

Google日本語入力の辞書を、macOS標準の日本語入力で使いたい

僕はよく入力する言葉について、入力の手間を省くために、Google日本語入力の辞書に単語登録をしています。
新しくmacOS標準の日本語入力を使用するにあたり、Google日本語入力の辞書をエクスポートして、macOS標準の日本語入力辞書にインポートすることができないことが課題となりました。

Google日本語入力の辞書をmacOS標準の日本語入力辞書へ変換するツールを作っている人がいないか探してみたのですが…なさそうに見えました。
macOS標準の日本語入力辞書→Google日本語入力辞書への変換ツールは見つかるんですが…

Google日本語入力の辞書を、macOS標準の日本語入力プログラム用辞書へ変換するプログラムを書いてみた

なければ作る精神を働かせまして、node.js+TypeScriptでGoogle日本語入力の辞書を、macOS標準の日本語入力プログラム用辞書へ変換するプログラムを書いてみました。

下記の順にコマンドを実行していけば、予めGoogle日本語入力からエクスポートした/path/to/my_exported_google_ime_dictionary.txtが変換され、my_exported_google_ime_dictionary.txt.plistが出来上がります。
予めnode.jsがインストールされていて、node/npm/npxコマンドが使える前提です。
(実装した時に使用したnode.jsのバージョンはv10.16.3です)

## node.jsが入っていることを確認
node -v

## 作業ディレクトリ作成
mkdir imedict
mv imedict

## package.jsonを作成
npm init -y
## 依存関係のインストール
npm install --save-dev @types/node ts-node typescript

## ファイルを作成 (ファイルの内容は後述)
vim index.ts
vim dictionary.ts

## 実行
npx ts-node index.ts /path/to/my_exported_google_ime_dictionary.txt
  • index.ts の内容
import * as fs from "fs"
import * as path from "path"
import * as util from "util"
import { parseGoogleJapaneseImeDictionary, MacosIme, buildMacosImeDictionaryContents } from "./dictionary"

async function main() {
    // 引数からファイル名を取得
    const filepathArgument = process.argv[2]
    if (!filepathArgument) {
        throw Error('You must specify filepath to command line arguments.')
    }

    // ファイルを全て読み込む
    const filepath = path.resolve(filepathArgument)
    console.log(`Read a file:`)
    console.log(`  input from: ${filepath}`)
    const contentsBuffer = await util.promisify(fs.readFile)(filepath)
    const inputFileContents = contentsBuffer.toString('utf-8', 0, contentsBuffer.length)

    console.log(`Converting...`)
    // Google日本語入力の値をパース
    const googleJapaneseImeEntries = parseGoogleJapaneseImeDictionary(inputFileContents)
    // macOS標準辞書としてのアイテムへ変換
    const macosImeEntries = googleJapaneseImeEntries.map(o => {
        return {
            reading: o.reading,
            word: o.word,
        } as MacosIme.DictionaryItem
    })
    // ファイルに書き込む内容を作成
    const outputFileContents = buildMacosImeDictionaryContents(macosImeEntries)

    // ファイルへ書き込む
    console.log(`Write a file:`)
    const writeToFilepath = path.resolve(`${filepath}.plist`)
    console.log(`  output to: ${writeToFilepath}`)
    util.promisify(fs.writeFile)(writeToFilepath, outputFileContents)

    console.log(`Finished!`)
}

main().catch(e => {
    console.error(e)
})
  • dictionary.ts の内容
export namespace GoogleJapaneseIme {
    export interface DictionaryItem {
        reading: string
        word: string
        category: string
        comment: string
    }
}

export namespace MacosIme {
    export interface DictionaryItem {
        reading: string
        word: string
    }
}

export function parseGoogleJapaneseImeDictionary(contents: string): GoogleJapaneseIme.DictionaryItem[] {
    const entries = contents.split('\n')
    return entries.filter(line => {
        // 空行はスキップする
        return line.length !== 0
    }).map(line => {
        const cells = line.split('\t')
        // Assertion
        if (cells.length !== 4) {
            throw Error(`Invalid syntax of Google Japanese InputMethod Dictionary. Please check your dictionary syntax is correct. line=${line}`)
        }
        return {
            reading: cells[0],
            word: cells[1],
            category: cells[2],
            comment: cells[3],
        } as GoogleJapaneseIme.DictionaryItem
    })
}


function escapeForXml(text: string) {
    return text.replace('<', '&lt;').replace('>', '&gt;')
}

/**
 * 数値を半角から全角にするメソッド。macOS日本語入力では、数字は全角でなければ辞書変換できない。
 */
function replaceNumbersHankakuToZenkaku(text: string) {
    return text.replace(/[0-9]/g, o => String.fromCharCode(o.charCodeAt(0) + 0xFEE0))
}

const templates = {
    main: (content: string) => {
        return `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
${content}
</array>
</plist>
`
    },
    item: (item: MacosIme.DictionaryItem) => {
        return `\t<dict>
\t\t<key>phrase</key>
\t\t<string>${escapeForXml(item.word)}</string>
\t\t<key>shortcut</key>
\t\t<string>${escapeForXml(replaceNumbersHankakuToZenkaku(item.reading))}</string>
\t</dict>`
    }
}

export function buildMacosImeDictionaryContents(entries: MacosIme.DictionaryItem[]) {
    const xmlItems = entries.map(o => templates.item(o))
    const fileContents = templates.main(xmlItems.join('\n'))
    return fileContents
}

実行結果

$ npx ts-node index.ts ./userdic.txt
Read a file:
  input from: /path/to/userdic.txt
Converting...
Write a file:
  output to: /path/to/userdic.txt.plist
Finished!
  • userdic.txt.plistの内容
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <dict>
        <key>phrase</key>
        <string>✧\\ ٩( 'ω' )و //✧</string>
        <key>shortcut</key>
        <string>やった</string>
    </dict>
    <dict>
        <key>phrase</key>
        <string>m(_ _)m</string>
        <key>shortcut</key>
        <string>ありがとう</string>
    </dict>
    (...省略)
</array>
</plist>

本当にショットでしか使わないプログラムなので、あまりかっこいいコードではいのですが、
Google日本語入力→macOS標準入力へ戻したい、という方の参考になれば。

本当はドラッグアンドドロップで変換するmacOSアプリとか作りたいなぁ。

この記事を書いた人

s.kono