2. 翻訳自動化の構築
モデルの選択
OllamaのTranslateGemma モデル情報には,4b,12b,27bの3つのパラメータサイズが公開されています。いずれもコンテキストウィンドウは128K,対応入力はテキストと画像です。各サイズにはq4_K_M,q8_0,bf16の3種の量子化バリアントが用意されています。
| モデル | q4_K_M | q8_0 | bf16 |
|---|---|---|---|
| 4b | 3.3 GB | 4.9 GB | 8.6 GB |
| 12b | 8.1 GB | 13 GB | 24 GB |
| 27b | 17 GB | 30 GB | 55 GB |
量子化とは,モデルの重みを低ビット整数で近似することでサイズと推論速度を改善する技術です。q4_K_M は4ビット量子化を意味し,「K」はブロックの重要度に応じて一部をより高精度に保つK-quants方式,「M」はサイズと精度のバランスを取ったMedium設定を表します。Ollamaで translategemma:12b を指定すると,このq4_K_Mバリアントが使用されます。
4bは試した範囲でハルシネーションが目立ち,単語を正しく訳出できない場合が見られました。27bは12bに対する翻訳品質の優位性が確認できませんでした。以上から,本スクリプトでは12bを採用しています。
インストール
TranslateGemmaは,Ollamaを用いてインストールできる翻訳モデルです。導入には,たとえば ollama pull translategemma:12b を実行し,その後 ollama list によってモデルが登録されていることを確認します。この場合は,translategemma:12b が一覧に表示されれば,利用可能な状態であると判断できます。
$ ollama pull translategemma:12b
$ ollama list
NAME ID SIZE MODIFIED
translategemma:12b c2f9a9ca1ec7 8.1 GB 10 days agoTranslateGemmaの説明によれば,翻訳時のプロンプトは所定の形式に厳密に従う必要があります。
You are a professional {SOURCE_LANG} ({SOURCE_CODE}) to {TARGET_LANG} ({TARGET_CODE}) translator. Your goal is to accurately convey the meaning and nuances of the original {SOURCE_LANG} text while adhering to {TARGET_LANG} grammar, vocabulary, and cultural sensitivities.
Produce only the {TARGET_LANG} translation, without any additional explanations or commentary. Please translate the following {SOURCE_LANG} text into {TARGET_LANG}:
{TEXT}具体的には,翻訳元言語名および言語コードを {SOURCE_LANG},{SOURCE_CODE} とし,翻訳先言語名および言語コードを {TARGET_LANG},{TARGET_CODE} として指定したうえで,翻訳対象テキスト {TEXT} を末尾に配置します。この際,指示文と {TEXT} の間には2行の空行を挿入する必要があります。たとえば,英語から日本語へ翻訳する場合には,英語,日本語,およびそれぞれの言語コード en,ja を埋め込んだ定型文を用い,最後に翻訳対象文として hello, world を与える構成となります。
しかし,このプロンプトは厳密に記述しなければならず,毎回手作業で入力することは煩雑です。また,翻訳のたびにOpen Web UIを開く運用も手間が大きいという課題があります。そこで,Ollama APIを直接呼び出すシェルスクリプトを作成しました。さらに,Alfredではショートカット等をトリガーとしてシェルスクリプトを呼び出すことができ,その際に選択中のテキストを引数として渡すことも可能です。これにより,所定のプロンプト形式を維持したまま,選択テキストを即座に翻訳できる運用が実現されます。
以下では,スクリプトの各部分を説明します。
translate
#!/bin/bash
# ========== 設定 ==========
KEEP_ALIVE="5m" # モデル保持時間(例: "1m", "5m", "0")
MODEL="translategemma:12b"
# ==========================
# 引数処理
LANG_OPTION=""
TEXT=""
while [[ $# -gt 0 ]]; do
case $1 in
--en-ja)
LANG_OPTION="en-ja"
shift
;;
--ja-en)
LANG_OPTION="ja-en"
shift
;;
*)
TEXT="$1"
shift
;;
esac
done
# テキストが空ならクリップボードから取得
if [ -z "$TEXT" ]; then
TEXT=$(pbpaste)
fi
# テキストが空なら終了
if [ -z "$TEXT" ]; then
osascript -e 'display notification "No text to translate" with title "Translation Error"'
exit 1
fi
# 言語方向の決定
if [ "$LANG_OPTION" = "en-ja" ]; then
SOURCE_LANG="English"
SOURCE_CODE="en"
TARGET_LANG="Japanese"
TARGET_CODE="ja"
elif [ "$LANG_OPTION" = "ja-en" ]; then
SOURCE_LANG="Japanese"
SOURCE_CODE="ja"
TARGET_LANG="English"
TARGET_CODE="en"
else
# 自動判定
if echo "$TEXT" | grep -qE '[ぁ-んァ-ヶー一-龯]'; then
SOURCE_LANG="Japanese"
SOURCE_CODE="ja"
TARGET_LANG="English"
TARGET_CODE="en"
else
SOURCE_LANG="English"
SOURCE_CODE="en"
TARGET_LANG="Japanese"
TARGET_CODE="ja"
fi
fi
# 翻訳開始通知
osascript -e "display notification \"Translating...\" with title \"${SOURCE_LANG} → ${TARGET_LANG}\""
# プロンプト構築
PROMPT="You are a professional ${SOURCE_LANG} (${SOURCE_CODE}) to ${TARGET_LANG} (${TARGET_CODE}) translator. Your goal is to accurately convey the meaning and nuances of the original ${SOURCE_LANG} text while adhering to ${TARGET_LANG} grammar, vocabulary, and cultural sensitivities.
Produce only the ${TARGET_LANG} translation, without any additional explanations or commentary. Please translate the following ${SOURCE_LANG} text into ${TARGET_LANG}:
${TEXT}"
# JSON形式で一時ファイルに保存
TMPFILE=$(mktemp)
jq -n --arg prompt "$PROMPT" --arg model "$MODEL" --arg keep_alive "$KEEP_ALIVE" '{
model: $model,
prompt: $prompt,
stream: false,
keep_alive: $keep_alive
}' > "$TMPFILE"
# Ollama APIで翻訳実行
RESULT=$(curl -s http://localhost:11434/api/generate -d @"$TMPFILE" | jq -r '.response')
# 一時ファイル削除
rm "$TMPFILE"
# 「、」を「,」に置換
RESULT=${RESULT//、/,}
# 結果が空なら通知して終了
if [ -z "$RESULT" ] || [ "$RESULT" = "null" ]; then
osascript -e 'display notification "Translation failed" with title "Translation Error"'
exit 1
fi
# クリップボードにコピー
echo -n "$RESULT" | pbcopy
# 翻訳完了通知
osascript -e "display notification \"Translation copied to clipboard\" with title \"${SOURCE_LANG} → ${TARGET_LANG}\""
# 結果出力(Alfred用)
echo "$RESULT"設定
冒頭の設定ブロックでは,使用するモデル名と keep_alive パラメータを変数として定義しています。
KEEP_ALIVE="5m" # モデル保持時間(例: "1m", "5m", "0")
MODEL="translategemma:12b"keep_alive はOllama APIのパラメータで,推論完了後にモデルをメモリ上に保持し続ける時間を指定します。"0" を指定するとレスポンス後に即座にアンロードされ,"5m" を指定すると5分間保持されます。モデルのロードには数秒の時間がかかるため,続けて翻訳を行う場面ではメモリ保持によって待ち時間を短縮できます。
引数処理とテキスト取得
LANG_OPTION=""
TEXT=""
while [[ $# -gt 0 ]]; do
case $1 in
--en-ja)
LANG_OPTION="en-ja"
shift
;;
--ja-en)
LANG_OPTION="ja-en"
shift
;;
*)
TEXT="$1"
shift
;;
esac
done
if [ -z "$TEXT" ]; then
TEXT=$(pbpaste)
fi受け付ける引数は --en-ja,--ja-en,および翻訳対象テキストの3種類です。while [[ $# -gt 0 ]]; do ... done は引数が残っている間ループし,shift で先頭の引数を消費します。--en-ja と --ja-en は LANG_OPTION に格納され,それ以外の引数は翻訳対象テキスト TEXT として扱います。
テキスト引数が渡されなかった場合は,pbpaste でクリップボードの内容を取得します。pbpaste はmacOS標準のコマンドで,クリップボードの内容を標準出力に書き出します。クリップボードにも内容がなければ,osascript で通知を表示してスクリプトを終了します。
言語方向の決定
if [ "$LANG_OPTION" = "en-ja" ]; then
...
else
# 自動判定
if echo "$TEXT" | grep -qE '[ぁ-んァ-ヶー一-龯]'; then
SOURCE_LANG="Japanese"
...
else
SOURCE_LANG="English"
...
fi
fi--en-ja または --ja-en が指定されていれば,その方向に従います。指定がない場合は,テキストを正規表現 [ぁ-んァ-ヶー一-龯] で検査し,ひらがな・カタカナ・漢字のいずれかが含まれていれば日本語から英語へ,そうでなければ英語から日本語へ翻訳します。
プロンプト構築
PROMPT="You are a professional ${SOURCE_LANG} (${SOURCE_CODE}) to ${TARGET_LANG} (${TARGET_CODE}) translator. Your goal is to accurately convey the meaning and nuances of the original ${SOURCE_LANG} text while adhering to ${TARGET_LANG} grammar, vocabulary, and cultural sensitivities.
Produce only the ${TARGET_LANG} translation, without any additional explanations or commentary. Please translate the following ${SOURCE_LANG} text into ${TARGET_LANG}:
${TEXT}"前節で述べたTranslateGemmaの定型プロンプトに,決定した言語方向の変数を埋め込みます。Bashはダブルクォートで囲んだ文字列内で ${VAR} の形に変数を展開します。プロンプト文字列は複数行にわたっており,TranslateGemmaの仕様に従って ${TEXT} の直前に2行の空行を挿入しています。
Ollama API 呼び出し
TMPFILE=$(mktemp)
jq -n --arg prompt "$PROMPT" --arg model "$MODEL" --arg keep_alive "$KEEP_ALIVE" '{
model: $model,
prompt: $prompt,
stream: false,
keep_alive: $keep_alive
}' > "$TMPFILE"
RESULT=$(curl -s http://localhost:11434/api/generate -d @"$TMPFILE" | jq -r '.response')
rm "$TMPFILE"OllamaはHTTP APIを提供しており,http://localhost:11434/api/generate にJSON形式でPOSTリクエストを送ることで推論を実行できます。curl に直接プロンプトを渡す場合,日本語や改行・引用符を含む文字列のJSONエスケープが煩雑になります。そこで jq を使用します。jq -n --arg name value は渡した文字列を適切にJSONエスケープするため,特殊文字を含むテキストも安全に扱えます。JSONは mktemp で作成した一時ファイルに書き出し,curl の -d @ファイル名 でリクエストボディとして送信します。stream: false を指定することで,レスポンス全体をまとめて受け取ります。レスポンスのJSONには .response フィールドに翻訳結果が格納されており,jq -r で文字列として取り出します。使用後の一時ファイルは rm で削除します。
後処理
RESULT=${RESULT//、/,}
echo -n "$RESULT" | pbcopy
osascript -e "display notification \"Translation copied to clipboard\" with title \"${SOURCE_LANG} → ${TARGET_LANG}\""
echo "$RESULT"${RESULT//、/,} はBashのパラメータ展開で,RESULT 内のすべての「、」を「,」に置換します。TranslateGemmaは出力スタイルの細かい指定ができないため,後変換でこのような調整を行います。翻訳結果は pbcopy でクリップボードにコピーし,osascript で完了通知を表示します。最後の echo "$RESULT" は,Alfred経由で呼び出した際に翻訳結果をLarge Type等で表示するためのものです。コマンドラインから直接実行した場合も,標準出力に翻訳結果が出力されます。