Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 13 additions & 85 deletions scripts/update-translations
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@ cd "$SCRIPTS_DIR/.."

# Directory containing .po files
LOCALES_DIR="$PWD/src/frontend/src/lib/locales"
MODEL="openai/gpt-5"
MAX_PARALLEL="${MAX_PARALLEL:-2}"
MAX_RETRIES="${MAX_RETRIES:-5}"
INITIAL_BACKOFF_SECONDS="${INITIAL_BACKOFF_SECONDS:-3}"
GITHUB_TOKEN="$(gh auth token)"

INFERENCE_URL="https://models.github.ai/inference/chat/completions"
echo "Using model: $MODEL"
echo "Using Gemini CLI"

INSTRUCTIONS="SYSTEM: You are a silent \`.po\` file translation bot.
1. You will receive a \`.po\` file with missing translations.
Expand All @@ -22,8 +15,10 @@ INSTRUCTIONS="SYSTEM: You are a silent \`.po\` file translation bot.
4. Ensure all variables (like {variable}) and tags (like <0>...</0>) are preserved in the translation.
5. Even if a language is RTL, you should still keep tags like <0>...</0> in the same order as in the source text, and not reverse them.
6. Apply the correct ICU plural categories required by the target language (e.g., one/other, one/few/many/other, or just other).
7. Your output must be *only* the plain text \`.po\` entries (from msgid ... to msgstr \"...\") that you have just translated.
8. DO NOT output any other text, greetings, explanations, or markdown. Your response must be *only* the plain text code snippet."
7. Lingui/ICU compliance: \`other\` is mandatory; additional categories are optional. If a target-language category is not explicitly present, ICU falls back to \`other\`.
8. If the source plural has fewer categories than the target language, add target-language categories when needed for natural grammar. It is acceptable to keep fewer categories (such as only \`one\` + \`other\`) when that remains grammatically natural.
9. Your output must be *only* the plain text \`.po\` entries (from msgid ... to msgstr \"...\") that you have just translated.
10. DO NOT output any other text, greetings, explanations, or markdown. Your response must be *only* the plain text code snippet."

# Make sure .po files are up to date
echo "Updating .po files with latest changes"
Expand Down Expand Up @@ -62,12 +57,13 @@ translate_po_file() {
# Extract untranslated entries (msgstr is empty)
local untranslated_entries
untranslated_entries=$(echo "$without_header" | awk '
BEGIN {entry=""}
BEGIN {entry=""; count=0}
{
entry = entry $0 "\n"
if ($0 ~ /^msgstr /) {
if ($0 == "msgstr \"\"") {
if ($0 == "msgstr \"\"" && count < 100) {
printf "%s", entry
count++
}
entry = ""
}
Expand All @@ -90,7 +86,7 @@ translate_po_file() {
}
')

# Use GitHub Models API (via gh CLI token) to translate missing translations
# Use Gemini CLI to translate missing translations
echo -e "\nTranslating file: $po_filename"
local prompt
prompt="$INSTRUCTIONS
Expand All @@ -101,73 +97,11 @@ $translated_snippet
Entries to translate:
$untranslated_entries"

local request_payload
request_payload=$(jq -n --arg sys "$INSTRUCTIONS" --arg user "$prompt" --arg model "$MODEL" \
'{model:$model,messages:[{role:"system",content:$sys},{role:"user",content:$user}]}')

local response
local api_error
local attempt=1
local backoff_seconds="$INITIAL_BACKOFF_SECONDS"

while true; do
local curl_output
local http_code
curl_output=$(curl -sS -w $'\n%{http_code}' "$INFERENCE_URL" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-H "Content-Type: application/json" \
-d "$request_payload")

http_code="${curl_output##*$'\n'}"
response="${curl_output%$'\n'*}"

if ! echo "$response" | jq -e . >/dev/null 2>&1; then
local response_snippet
response_snippet=$(echo "$response" | head -c 300 | tr '\n' ' ')
if [[ "$response_snippet" == *"Too many requests"* ]] && (( attempt < MAX_RETRIES )); then
echo "Rate limited translating $po_filename (attempt $attempt/$MAX_RETRIES). Retrying in ${backoff_seconds}s..." >&2
sleep "$backoff_seconds"
backoff_seconds=$((backoff_seconds * 2))
attempt=$((attempt + 1))
continue
fi
echo "Failed translating $po_filename with model '$MODEL': non-JSON response from API (HTTP $http_code): $response_snippet" >&2
return 1
fi

api_error=$(echo "$response" | jq -r '.error.message // empty')
if [[ "$http_code" == "429" || "$api_error" == *"Too many requests"* || "$api_error" == *"rate limit"* ]]; then
if (( attempt < MAX_RETRIES )); then
echo "Rate limited translating $po_filename (attempt $attempt/$MAX_RETRIES). Retrying in ${backoff_seconds}s..." >&2
sleep "$backoff_seconds"
backoff_seconds=$((backoff_seconds * 2))
attempt=$((attempt + 1))
continue
fi
fi

if [[ -n "$api_error" ]]; then
echo "Failed translating $po_filename with model '$MODEL': $api_error" >&2
return 1
fi

break
done

local translated_entries
translated_entries=$(echo "$response" | jq -r '
(.choices[0].message.content // empty) as $content
| if ($content | type) == "string" then $content
elif ($content | type) == "array" then
[ $content[]? | select(.type == "text") | .text ] | join("")
else ""
end
')
translated_entries=$(npx -y @google/gemini-cli -m gemini-2.5-flash-lite -p "$prompt")

if [[ -z "${translated_entries//[[:space:]]/}" ]]; then
echo "Failed translating $po_filename with model '$MODEL': empty response content" >&2
echo "Failed translating $po_filename with Gemini CLI: empty response content" >&2
return 1
fi

Expand Down Expand Up @@ -197,19 +131,13 @@ $untranslated_entries"
echo "$merged_content" > "$po_file"
}

# Loop through all .po files and translate in parallel
# Loop through all .po files and translate sequentially
for po_file in "$LOCALES_DIR"/*.po; do
if [[ -f "$po_file" ]]; then
while (( $(jobs -rp | wc -l | tr -d ' ') >= MAX_PARALLEL )); do
wait -n
done
translate_po_file "$po_file" &
translate_po_file "$po_file"
fi
done

# Wait for all background translation jobs to finish
wait

# Make sure .po files are cleaned up after adding the translations
echo -e "\nCleaning up .po files with latest translations"
npm run extract # We do not silence the output here so we can check if there are still missing translations
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,11 @@
? error.description
: error instanceof Error
? error.message
: $t`Something went wrong`}
: $t({
message: "Something went wrong",
context:
"Fallback error message when an unexpected error is caught",
})}
<Dialog>
<FeaturedIcon size="lg" variant="error" class="mb-4 self-start">
<CircleAlertIcon class="size-6" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
<MessageSquareXIcon class="size-6" />
</FeaturedIcon>
<h2 class="text-text-primary mb-3 text-2xl font-medium">
{$t`Something is wrong!`}
{$t({
message: "Something is wrong!",
context: "The user entered an incorrect value during a verification step",
})}
</h2>
<p class="text-text-tertiary mb-8 text-base font-medium">
{#if inputMethod === "selecting"}
Expand Down
13 changes: 11 additions & 2 deletions src/frontend/src/lib/constants/locale.constants.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
// First locale is default directly defined in svelte components
export const availableLocales = ["en", "de", "es", "id", "ur"];
export const availableLocales = [
"en",
"de",
"es",
"fr",
"id",
"it",
"pl",
"ur",
];

// List of languages that are actually enabled
export const enabledLocales = ["en", "es"];
export const enabledLocales = ["en", "de", "es", "fr", "id", "it", "pl", "ur"];

const rtlLocales = ["ur"];

Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/lib/locales/de.po
Original file line number Diff line number Diff line change
Expand Up @@ -809,9 +809,11 @@ msgstr "Sichere Anmeldung"
msgid "Simplify your sign-in"
msgstr "Vereinfachen Sie Ihre Anmeldung"

msgctxt "The user entered an incorrect value during a verification step"
msgid "Something is wrong!"
msgstr "Etwas stimmt nicht!"

msgctxt "Fallback error message when an unexpected error is caught"
msgid "Something went wrong"
msgstr "Etwas ist schiefgelaufen"

Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/lib/locales/en.po
Original file line number Diff line number Diff line change
Expand Up @@ -809,9 +809,11 @@ msgstr "Signing in securely"
msgid "Simplify your sign-in"
msgstr "Simplify your sign-in"

msgctxt "The user entered an incorrect value during a verification step"
msgid "Something is wrong!"
msgstr "Something is wrong!"

msgctxt "Fallback error message when an unexpected error is caught"
msgid "Something went wrong"
msgstr "Something went wrong"

Expand Down
4 changes: 3 additions & 1 deletion src/frontend/src/lib/locales/es.po
Original file line number Diff line number Diff line change
Expand Up @@ -809,9 +809,11 @@ msgstr "Iniciando sesión de forma segura"
msgid "Simplify your sign-in"
msgstr "Simplifica tu inicio de sesión"

msgctxt "The user entered an incorrect value during a verification step"
msgid "Something is wrong!"
msgstr "¡Algo salió mal!"
msgstr "¡Algo va mal!"

msgctxt "Fallback error message when an unexpected error is caught"
msgid "Something went wrong"
msgstr "Algo salió mal"

Expand Down
Loading
Loading