Update translations for 40 languages via GPT-5.5 (#6954)

# Description of Changes

- Adds and updates translations across **40 languages** (~1,400–2,200
keys each) using GPT-5.5, filling previously-missing UI strings.
- Switches the translation scripts' default model from the year-old
`gpt-5` (5.0) to `gpt-5.5`, adding a `--model` flag and token/cost
reporting.
- Purely additive and validated: no existing translations changed, all
40 files match the en-US key structure, and no new placeholder issues
introduced.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [x] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [x] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [x] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
This commit is contained in:
Anthony Stirling
2026-07-10 08:53:59 +00:00
committed by GitHub
parent 51d3d27fd3
commit 783a51950f
45 changed files with 90336 additions and 151 deletions
-4
View File
@@ -175,8 +175,6 @@ ignore = [
'certSign.collab.participant.certInvalid',
'certSign.collab.signRequest.steps.invisible',
'certSign.collab.signRequest.steps.visible',
'certSign.collab.signRequest.text.colorLabel',
'certSign.collab.signRequest.text.fontSizePlaceholder',
'common.error',
'compare.pixel.base',
'config.account.profilePicture.cropper.zoom',
@@ -205,7 +203,6 @@ ignore = [
'getPdfInfo.summary.tech.javaScript',
'getPdfInfo.summary.tech.multimedia',
'home.devApi.title',
'landing.heroTitle',
'language.direction',
'mobileScanner.flash',
'no',
@@ -482,7 +479,6 @@ ignore = [
'admin.settings.connections.saml2.label',
'admin.settings.security.xFrameOptions.label',
'certSign.collab.participant.certInvalid',
'certSign.collab.signRequest.text.fontSizePlaceholder',
'convert.cbrToPdf',
'convert.cbzToPdf',
'convert.dpi',
+15 -13
View File
@@ -29,7 +29,7 @@ python3 scripts/translations/auto_translate.py es-ES --no-cleanup
**What it does:**
1. Extracts untranslated entries from the language file
2. Splits into batches (default 500 entries each)
3. Translates each batch using GPT-5 with specialized prompts
3. Translates each batch using GPT-5.6 with specialized prompts
4. Validates placeholders are preserved
5. Merges translated batches
6. Applies translations to language file
@@ -39,7 +39,7 @@ python3 scripts/translations/auto_translate.py es-ES --no-cleanup
**Time:** ~8-10 minutes per language with 1200+ untranslated entries
**Cost:** ~$2-4 per language using GPT-5 (or use `gpt-5-mini` for lower cost)
**Cost:** ~$8-15 per language using GPT-5.5 (or use `gpt-5.6-luna` for ~$2-4 if your org has 5.6 access)
See [`auto_translate.py`](#auto_translatepy-automated-translation-pipeline) for full details.
@@ -248,7 +248,7 @@ python scripts/translations/compact_translator.py it-IT --output to_translate.js
### 5. `auto_translate.py` - Automated Translation Pipeline
**NEW: Fully automated translation workflow using GPT-5.**
**NEW: Fully automated translation workflow using GPT-5.6.**
Combines all translation steps into a single command that handles everything from extraction to verification.
@@ -276,7 +276,7 @@ python3 scripts/translations/auto_translate.py es-ES --skip-verification
**Features:**
- Fully automated end-to-end translation pipeline
- Uses GPT-5 with specialized prompts for Stirling PDF
- Uses GPT-5.6 with specialized prompts for Stirling PDF
- Preserves all placeholders ({n}, {{variable}}, etc.)
- Maintains consistent terminology
- Validates translations automatically
@@ -286,7 +286,7 @@ python3 scripts/translations/auto_translate.py es-ES --skip-verification
**Pipeline Steps:**
1. **Extract**: Finds all untranslated entries
2. **Split**: Divides into manageable batches (default: 500 entries)
3. **Translate**: Uses GPT-5 to translate each batch with specialized prompts
3. **Translate**: Uses GPT-5.6 to translate each batch with specialized prompts
4. **Validate**: Ensures placeholders are preserved
5. **Merge**: Combines all translated batches
6. **Apply**: Updates the language file
@@ -304,7 +304,7 @@ python3 scripts/translations/auto_translate.py es-ES --skip-verification
**Supported Languages:**
All language codes from `frontend/editor/public/locales/` (e.g., es-ES, de-DE, fr-FR, zh-CN, ar-AR, etc.)
### 6. `batch_translator.py` - GPT-5 Translation Engine
### 6. `batch_translator.py` - GPT-5.6 Translation Engine
Low-level translation script used by `auto_translate.py`. Can be used standalone for manual batch translation.
@@ -316,25 +316,27 @@ python3 scripts/translations/batch_translator.py my_batch.json --language es-ES
# Translate multiple batches
python3 scripts/translations/batch_translator.py batch_*.json --language de-DE --api-key YOUR_KEY
# Use different GPT model
python3 scripts/translations/batch_translator.py batch.json --language fr-FR --model gpt-5-mini
# Use a cheaper model
python3 scripts/translations/batch_translator.py batch.json --language fr-FR --model gpt-5.6-luna
# Skip validation
python3 scripts/translations/batch_translator.py batch.json --language it-IT --skip-validation
```
**Features:**
- Translates JSON batch files using OpenAI GPT-5
- Translates JSON batch files using OpenAI GPT-5.6
- Specialized system prompts for Stirling PDF translations
- Automatic placeholder validation
- Supports pattern matching for multiple files
- Configurable model selection (gpt-5, gpt-5-mini, gpt-5-nano)
- Configurable model selection (gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna)
- Per-batch and per-run token + cost reporting
- Rate limiting with configurable delays
**Models:**
- `gpt-5` (default): Best quality, $1.25/1M input, $10/1M output
- `gpt-5-mini`: Balanced quality/cost
- `gpt-5-nano`: Fastest, most economical
- `gpt-5.5` (default): Previous flagship, strong translation quality, $5/1M input, $30/1M output
- `gpt-5.6-sol`: Newest flagship reasoning, $5/1M input, $30/1M output (requires org 5.6 access)
- `gpt-5.6-terra`: Balanced quality/cost, $2.50/1M input, $15/1M output (requires org 5.6 access)
- `gpt-5.6-luna`: Fastest/cheapest, $1/1M input, $6/1M output (requires org 5.6 access)
### 7. `json_beautifier.py`
Restructures and beautifies translation JSON files to match en-US structure exactly.
+13 -5
View File
@@ -140,12 +140,14 @@ def extract_untranslated(language_code, batch_size=500, include_existing=False):
return batch_files
def translate_batches(batch_files, language_code, api_key, timeout=600):
"""Translate all batch files using GPT-5."""
def translate_batches(
batch_files, language_code, api_key, timeout=600, model="gpt-5.5"
):
"""Translate all batch files using the given OpenAI model."""
if not batch_files:
return []
print(f"\n🤖 Translating {len(batch_files)} batches using GPT-5...")
print(f"\n🤖 Translating {len(batch_files)} batches using {model}...")
print(f"Timeout: {timeout}s ({timeout // 60} minutes) per batch")
translated_files = []
@@ -154,7 +156,7 @@ def translate_batches(batch_files, language_code, api_key, timeout=600):
print(f"\n[{i}/{len(batch_files)}] Translating {batch_file}...")
# Always pass API key since it's required
cmd = f'python3 scripts/translations/batch_translator.py "{batch_file}" --language {language_code} --api-key "{api_key}"'
cmd = f'python3 scripts/translations/batch_translator.py "{batch_file}" --language {language_code} --api-key "{api_key}" --model {model}'
# Run with timeout
result = subprocess.run(
@@ -308,6 +310,11 @@ Examples:
action="store_true",
help="Also retranslate existing keys that match English (default: only translate missing keys)",
)
parser.add_argument(
"--model",
default="gpt-5.5",
help="OpenAI model (default: gpt-5.5; gpt-5.6-sol/terra/luna if your org has 5.6 access)",
)
args = parser.parse_args()
@@ -322,6 +329,7 @@ Examples:
print("=" * 60)
print("Automated Translation Pipeline")
print(f"Language: {args.language}")
print(f"Model: {args.model}")
print(f"Batch Size: {args.batch_size} entries")
print("=" * 60)
@@ -341,7 +349,7 @@ Examples:
# Step 2: Translate all batches
translated_files = translate_batches(
batch_files, args.language, api_key, args.timeout
batch_files, args.language, api_key, args.timeout, args.model
)
if translated_files is None:
sys.exit(1)
+53 -4
View File
@@ -22,11 +22,32 @@ except ImportError:
sys.exit(1)
# USD per 1M tokens (input, output)
MODEL_PRICING = {
"gpt-5.5": (5.0, 30.0),
"gpt-5.6-sol": (5.0, 30.0),
"gpt-5.6-terra": (2.5, 15.0),
"gpt-5.6-luna": (1.0, 6.0),
"gpt-5": (1.25, 10.0),
}
def estimate_cost(model: str, prompt_tokens: int, completion_tokens: int) -> float:
"""Estimate USD cost for a call; 0.0 if model pricing is unknown."""
if model not in MODEL_PRICING:
return 0.0
in_price, out_price = MODEL_PRICING[model]
return (prompt_tokens * in_price + completion_tokens * out_price) / 1_000_000
class BatchTranslator:
def __init__(self, api_key: str, model: str = "gpt-5"):
def __init__(self, api_key: str, model: str = "gpt-5.5"):
"""Initialize translator with OpenAI API key."""
self.client = OpenAI(api_key=api_key)
self.model = model
self.total_prompt_tokens = 0
self.total_completion_tokens = 0
self.total_cost = 0.0
def get_translation_prompt(self, language_name: str, language_code: str) -> str:
"""Generate the system prompt for translation."""
@@ -79,6 +100,23 @@ CRITICAL RULES - MUST FOLLOW EXACTLY:
Return ONLY the translated JSON. No markdown, no explanations, just the JSON object."""
def _record_usage(self, response) -> None:
"""Accumulate token usage/cost and print a per-batch line."""
usage = getattr(response, "usage", None)
if usage is None:
return
prompt_tokens = getattr(usage, "prompt_tokens", 0) or 0
completion_tokens = getattr(usage, "completion_tokens", 0) or 0
cost = estimate_cost(self.model, prompt_tokens, completion_tokens)
self.total_prompt_tokens += prompt_tokens
self.total_completion_tokens += completion_tokens
self.total_cost += cost
cost_note = f", ~${cost:.4f}" if cost else ""
print(f" Tokens: {prompt_tokens:,} in / {completion_tokens:,} out{cost_note}")
def translate_batch(
self, batch_data: dict, target_language: str, language_code: str
) -> dict:
@@ -90,7 +128,7 @@ Return ONLY the translated JSON. No markdown, no explanations, just the JSON obj
print(f"Input size: {len(input_json)} characters")
try:
# GPT-5 only supports temperature=1, so we don't include it
# GPT-5.x models only support the default temperature, so we omit it
response = self.client.chat.completions.create(
model=self.model,
messages=[
@@ -107,6 +145,8 @@ Return ONLY the translated JSON. No markdown, no explanations, just the JSON obj
],
)
self._record_usage(response)
translated_text = response.choices[0].message.content.strip()
# Remove markdown code blocks if present
@@ -250,8 +290,8 @@ Examples:
)
parser.add_argument(
"--model",
default="gpt-5",
help="OpenAI model to use (default: gpt-5, options: gpt-5-mini, gpt-5-nano)",
default="gpt-5.5",
help="OpenAI model (default: gpt-5.5; gpt-5.6-sol/terra/luna if your org has 5.6 access)",
)
parser.add_argument(
"--output-suffix",
@@ -354,6 +394,15 @@ Examples:
if failed > 0:
print(f"Failed: {failed}/{len(input_files)}")
# Cost summary
print("-" * 60)
print(
f"Total tokens: {translator.total_prompt_tokens:,} in / "
f"{translator.total_completion_tokens:,} out"
)
if translator.total_cost:
print(f"Estimated cost ({args.model}): ${translator.total_cost:.4f}")
sys.exit(0 if failed == 0 else 1)
+15 -5
View File
@@ -98,6 +98,7 @@ def translate_language(
timeout: int,
skip_verification: bool,
include_existing: bool,
model: str,
) -> Tuple[str, bool, str]:
"""
Translate a single language.
@@ -115,6 +116,8 @@ def translate_language(
str(batch_size),
"--timeout",
str(timeout),
"--model",
model,
]
if skip_verification:
@@ -139,11 +142,11 @@ def translate_language(
safe_print(f"[{language}] ✓ Success")
return (language, True, "Success")
else:
error_msg = (
result.stderr.strip() or result.stdout.strip() or "Unknown error"
)
safe_print(f"[{language}] ✗ Failed: {error_msg[:100]}")
return (language, False, error_msg[:200]) # Truncate long errors
# Show the actual error (last line of output), not the header
output = result.stderr.strip() or result.stdout.strip() or "Unknown error"
error_msg = output.splitlines()[-1][:200]
safe_print(f"[{language}] ✗ Failed: {error_msg}")
return (language, False, error_msg)
except subprocess.TimeoutExpired:
safe_print(f"[{language}] ✗ Timeout exceeded")
@@ -178,6 +181,11 @@ Note: Requires OPENAI_API_KEY environment variable or --api-key argument.
parser.add_argument(
"--api-key", help="OpenAI API key (or set OPENAI_API_KEY env var)"
)
parser.add_argument(
"--model",
default="gpt-5.5",
help="OpenAI model (default: gpt-5.5; gpt-5.6-sol/terra/luna if your org has 5.6 access)",
)
parser.add_argument(
"--parallel",
type=int,
@@ -277,6 +285,7 @@ Note: Requires OPENAI_API_KEY environment variable or --api-key argument.
print("Bulk Translation Configuration")
print(f"{'=' * 60}")
print(f"Languages to translate: {len(languages)}")
print(f"Model: {args.model}")
print(f"Parallel threads: {args.parallel}")
print(f"Batch size: {args.batch_size}")
print(f"Timeout per batch: {args.timeout}s")
@@ -308,6 +317,7 @@ Note: Requires OPENAI_API_KEY environment variable or --api-key argument.
args.timeout,
args.skip_verification,
args.include_existing,
args.model,
): lang
for lang in languages
}