Skip to content

Commit 8f7f779

Browse files
committed
Add French, Italian and Polish translations
1 parent 4758ae3 commit 8f7f779

File tree

5 files changed

+3488
-86
lines changed

5 files changed

+3488
-86
lines changed

scripts/update-translations

Lines changed: 13 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,7 @@ cd "$SCRIPTS_DIR/.."
66

77
# Directory containing .po files
88
LOCALES_DIR="$PWD/src/frontend/src/lib/locales"
9-
MODEL="openai/gpt-5"
10-
MAX_PARALLEL="${MAX_PARALLEL:-2}"
11-
MAX_RETRIES="${MAX_RETRIES:-5}"
12-
INITIAL_BACKOFF_SECONDS="${INITIAL_BACKOFF_SECONDS:-3}"
13-
GITHUB_TOKEN="$(gh auth token)"
14-
15-
INFERENCE_URL="https://models.github.ai/inference/chat/completions"
16-
echo "Using model: $MODEL"
9+
echo "Using Gemini CLI"
1710

1811
INSTRUCTIONS="SYSTEM: You are a silent \`.po\` file translation bot.
1912
1. You will receive a \`.po\` file with missing translations.
@@ -22,8 +15,10 @@ INSTRUCTIONS="SYSTEM: You are a silent \`.po\` file translation bot.
2215
4. Ensure all variables (like {variable}) and tags (like <0>...</0>) are preserved in the translation.
2316
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.
2417
6. Apply the correct ICU plural categories required by the target language (e.g., one/other, one/few/many/other, or just other).
25-
7. Your output must be *only* the plain text \`.po\` entries (from msgid ... to msgstr \"...\") that you have just translated.
26-
8. DO NOT output any other text, greetings, explanations, or markdown. Your response must be *only* the plain text code snippet."
18+
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\`.
19+
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.
20+
9. Your output must be *only* the plain text \`.po\` entries (from msgid ... to msgstr \"...\") that you have just translated.
21+
10. DO NOT output any other text, greetings, explanations, or markdown. Your response must be *only* the plain text code snippet."
2722

2823
# Make sure .po files are up to date
2924
echo "Updating .po files with latest changes"
@@ -62,12 +57,13 @@ translate_po_file() {
6257
# Extract untranslated entries (msgstr is empty)
6358
local untranslated_entries
6459
untranslated_entries=$(echo "$without_header" | awk '
65-
BEGIN {entry=""}
60+
BEGIN {entry=""; count=0}
6661
{
6762
entry = entry $0 "\n"
6863
if ($0 ~ /^msgstr /) {
69-
if ($0 == "msgstr \"\"") {
64+
if ($0 == "msgstr \"\"" && count < 100) {
7065
printf "%s", entry
66+
count++
7167
}
7268
entry = ""
7369
}
@@ -90,7 +86,7 @@ translate_po_file() {
9086
}
9187
')
9288

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

104-
local request_payload
105-
request_payload=$(jq -n --arg sys "$INSTRUCTIONS" --arg user "$prompt" --arg model "$MODEL" \
106-
'{model:$model,messages:[{role:"system",content:$sys},{role:"user",content:$user}]}')
107-
108-
local response
109-
local api_error
110-
local attempt=1
111-
local backoff_seconds="$INITIAL_BACKOFF_SECONDS"
112-
113-
while true; do
114-
local curl_output
115-
local http_code
116-
curl_output=$(curl -sS -w $'\n%{http_code}' "$INFERENCE_URL" \
117-
-H "Accept: application/vnd.github+json" \
118-
-H "Authorization: Bearer $GITHUB_TOKEN" \
119-
-H "X-GitHub-Api-Version: 2022-11-28" \
120-
-H "Content-Type: application/json" \
121-
-d "$request_payload")
122-
123-
http_code="${curl_output##*$'\n'}"
124-
response="${curl_output%$'\n'*}"
125-
126-
if ! echo "$response" | jq -e . >/dev/null 2>&1; then
127-
local response_snippet
128-
response_snippet=$(echo "$response" | head -c 300 | tr '\n' ' ')
129-
if [[ "$response_snippet" == *"Too many requests"* ]] && (( attempt < MAX_RETRIES )); then
130-
echo "Rate limited translating $po_filename (attempt $attempt/$MAX_RETRIES). Retrying in ${backoff_seconds}s..." >&2
131-
sleep "$backoff_seconds"
132-
backoff_seconds=$((backoff_seconds * 2))
133-
attempt=$((attempt + 1))
134-
continue
135-
fi
136-
echo "Failed translating $po_filename with model '$MODEL': non-JSON response from API (HTTP $http_code): $response_snippet" >&2
137-
return 1
138-
fi
139-
140-
api_error=$(echo "$response" | jq -r '.error.message // empty')
141-
if [[ "$http_code" == "429" || "$api_error" == *"Too many requests"* || "$api_error" == *"rate limit"* ]]; then
142-
if (( attempt < MAX_RETRIES )); then
143-
echo "Rate limited translating $po_filename (attempt $attempt/$MAX_RETRIES). Retrying in ${backoff_seconds}s..." >&2
144-
sleep "$backoff_seconds"
145-
backoff_seconds=$((backoff_seconds * 2))
146-
attempt=$((attempt + 1))
147-
continue
148-
fi
149-
fi
150-
151-
if [[ -n "$api_error" ]]; then
152-
echo "Failed translating $po_filename with model '$MODEL': $api_error" >&2
153-
return 1
154-
fi
155-
156-
break
157-
done
158-
159100
local translated_entries
160-
translated_entries=$(echo "$response" | jq -r '
161-
(.choices[0].message.content // empty) as $content
162-
| if ($content | type) == "string" then $content
163-
elif ($content | type) == "array" then
164-
[ $content[]? | select(.type == "text") | .text ] | join("")
165-
else ""
166-
end
167-
')
101+
translated_entries=$(npx @google/gemini-cli -m gemini-2.5-flash-lite -p "$prompt")
168102

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

@@ -197,19 +131,13 @@ $untranslated_entries"
197131
echo "$merged_content" > "$po_file"
198132
}
199133

200-
# Loop through all .po files and translate in parallel
134+
# Loop through all .po files and translate sequentially
201135
for po_file in "$LOCALES_DIR"/*.po; do
202136
if [[ -f "$po_file" ]]; then
203-
while (( $(jobs -rp | wc -l | tr -d ' ') >= MAX_PARALLEL )); do
204-
wait -n
205-
done
206-
translate_po_file "$po_file" &
137+
translate_po_file "$po_file"
207138
fi
208139
done
209140

210-
# Wait for all background translation jobs to finish
211-
wait
212-
213141
# Make sure .po files are cleaned up after adding the translations
214142
echo -e "\nCleaning up .po files with latest translations"
215143
npm run extract # We do not silence the output here so we can check if there are still missing translations

src/frontend/src/lib/constants/locale.constants.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
// First locale is default directly defined in svelte components
2-
export const availableLocales = ["en", "de", "es", "id", "ur"];
2+
export const availableLocales = [
3+
"en",
4+
"de",
5+
"es",
6+
"fr",
7+
"id",
8+
"it",
9+
"pl",
10+
"ur",
11+
];
312

413
// List of languages that are actually enabled
514
export const enabledLocales = ["en", "es"];

0 commit comments

Comments
 (0)