Skip to content
Copied!
published on 2026-04-11

2. Building the Translation Automation

Choosing a Model

The TranslateGemma model page on Ollama lists three parameter sizes: 4b, 12b, and 27b. All have a 128K context window and accept both text and image input. Three quantization variants are available for each size: q4_K_M, q8_0, and bf16.

Modelq4_K_Mq8_0bf16
4b3.3 GB4.9 GB8.6 GB
12b8.1 GB13 GB24 GB
27b17 GB30 GB55 GB

Quantization is a technique that approximates model weights with low-bit integers to reduce size and improve inference speed. q4_K_M denotes 4-bit quantization; "K" refers to the K-quants method, which preserves some weights at higher precision based on the importance of each block, and "M" denotes the Medium setting, which balances size and accuracy. Specifying translategemma:12b in Ollama uses the q4_K_M variant.

In testing, the 4b model showed noticeable hallucinations and sometimes failed to translate words correctly. The 27b model showed no clear quality advantage over 12b. Based on these findings, the script uses the 12b model.

Installation

TranslateGemma is a translation model installable via Ollama. To set it up, run ollama pull translategemma:12b, then confirm the model is registered with ollama list. If translategemma:12b appears in the list, the model is ready to use.

$ ollama pull translategemma:12b
$ ollama list
NAME                  ID              SIZE      MODIFIED
translategemma:12b    c2f9a9ca1ec7    8.1 GB    10 days ago

According to the TranslateGemma documentation, the translation prompt must strictly follow a prescribed format:

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}

Specifically, the source language name and code are provided as {SOURCE_LANG} and {SOURCE_CODE}, the target language name and code as {TARGET_LANG} and {TARGET_CODE}, and the text to translate as {TEXT} at the end. Two blank lines must be inserted between the instruction and {TEXT}. For example, when translating from English to Japanese, the fixed template is filled with English, Japanese, and their respective language codes en and ja, with the source text such as hello, world placed at the end.

This prompt must be written exactly as specified, and manually entering it each time is tedious. Opening Open Web UI for every translation adds further friction. The solution is a shell script that calls the Ollama API directly. Alfred can invoke shell scripts via shortcuts and pass the currently selected text as an argument, allowing selected text to be translated instantly while maintaining the required prompt format.

The following sections explain each part of the script.

translate
bash
#!/bin/bash

# ========== Configuration ==========
KEEP_ALIVE="5m"  # How long to keep model loaded (e.g. "1m", "5m", "0")
MODEL="translategemma:12b"
# ====================================

# Argument parsing
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

# Fall back to clipboard if no text argument
if [ -z "$TEXT" ]; then
	TEXT=$(pbpaste)
fi

# Exit if text is still empty
if [ -z "$TEXT" ]; then
	osascript -e 'display notification "No text to translate" with title "Translation Error"'
	exit 1
fi

# Determine translation direction
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
	# Auto-detect
	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

# Notify translation start
osascript -e "display notification \"Translating...\" with title \"${SOURCE_LANG} → ${TARGET_LANG}\""

# Build prompt
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}"

# Write JSON to a temp file
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"

# Run inference via Ollama API
RESULT=$(curl -s http://localhost:11434/api/generate -d @"$TMPFILE" | jq -r '.response')

# Remove temp file
rm "$TMPFILE"

# Replace "、" with ","
RESULT=${RESULT///,}

# Notify and exit on empty result
if [ -z "$RESULT" ] || [ "$RESULT" = "null" ]; then
	osascript -e 'display notification "Translation failed" with title "Translation Error"'
	exit 1
fi

# Copy to clipboard
echo -n "$RESULT" | pbcopy

# Notify completion
osascript -e "display notification \"Translation copied to clipboard\" with title \"${SOURCE_LANG} → ${TARGET_LANG}\""

# Output result (for Alfred)
echo "$RESULT"

Configuration

The configuration block at the top defines the model name and the keep_alive parameter as variables.

bash
KEEP_ALIVE="5m"  # How long to keep model loaded (e.g. "1m", "5m", "0")
MODEL="translategemma:12b"

keep_alive is an Ollama API parameter that specifies how long to keep the model loaded in memory after inference completes. Setting it to "0" unloads the model immediately after the response; "5m" keeps it loaded for five minutes. Since loading a model takes several seconds, keeping it in memory reduces wait time when performing multiple translations in succession.

Argument Parsing and Text Retrieval

bash
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

The script accepts three kinds of arguments: --en-ja, --ja-en, and the text to translate. The while [[ $# -gt 0 ]]; do ... done loop continues as long as arguments remain, consuming them one by one with shift. --en-ja and --ja-en are stored in LANG_OPTION; any other argument is treated as the translation target TEXT.

If no text argument is provided, the clipboard contents are retrieved via pbpaste, the standard macOS command that writes clipboard contents to standard output. If the clipboard is also empty, osascript displays a notification and the script exits.

Determining Translation Direction

bash
if [ "$LANG_OPTION" = "en-ja" ]; then
	...
else
	# Auto-detect
	if echo "$TEXT" | grep -qE '[ぁ-んァ-ヶー一-龯]'; then
		SOURCE_LANG="Japanese"
		...
	else
		SOURCE_LANG="English"
		...
	fi
fi

If --en-ja or --ja-en is specified, that direction is used. Otherwise, the text is tested against the regular expression [ぁ-んァ-ヶー一-龯]; if hiragana, katakana, or CJK characters are found, the direction is Japanese → English; otherwise, English → Japanese.

Building the Prompt

bash
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}"

The TranslateGemma fixed prompt described in the previous section is filled with the determined language direction variables. Bash expands variables in the form ${VAR} within double-quoted strings. The prompt spans multiple lines; two blank lines are inserted immediately before ${TEXT} as required by the TranslateGemma specification.

Calling the Ollama API

bash
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 exposes an HTTP API; inference is triggered by sending a POST request in JSON format to http://localhost:11434/api/generate. Passing the prompt directly to curl is cumbersome when the text contains Japanese characters, newlines, or quotation marks that require JSON escaping. jq is used instead. jq -n --arg name value properly JSON-escapes the provided strings, so text with special characters is handled safely. The JSON is written to a temporary file created by mktemp and sent as the request body via curl's -d @filename. Specifying stream: false causes the full response to be returned at once. The translation result is stored in the .response field of the response JSON, and jq -r extracts it as a plain string. The temporary file is removed with rm after use.

Post-processing

bash
RESULT=${RESULT///,}

echo -n "$RESULT" | pbcopy

osascript -e "display notification \"Translation copied to clipboard\" with title \"${SOURCE_LANG} → ${TARGET_LANG}\""

echo "$RESULT"

${RESULT//、/,} is a Bash parameter expansion that replaces every (Japanese comma) with in RESULT. Since TranslateGemma does not allow fine-grained control over output style, this kind of adjustment is applied as a post-processing step. The translation result is copied to the clipboard with pbcopy, and osascript displays a completion notification. The final echo "$RESULT" outputs the result so Alfred can display it in Large Type or similar; it also causes the result to appear on standard output when the script is run directly from the command line.