mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
Change default language to en-US and add US language (#6621)
This commit is contained in:
@@ -3,7 +3,7 @@ A script to update language progress status in README.md based on
|
||||
frontend locale TOML file comparisons.
|
||||
|
||||
This script compares the default (reference) TOML file,
|
||||
`frontend/editor/public/locales/en-GB/translation.toml`, with other translation
|
||||
`frontend/editor/public/locales/en-US/translation.toml`, with other translation
|
||||
files in `frontend/editor/public/locales/*/translation.toml`.
|
||||
It determines how many keys are fully translated and automatically updates
|
||||
progress badges in the `README.md`.
|
||||
@@ -346,7 +346,7 @@ def main() -> None:
|
||||
# Project layout assumptions
|
||||
cwd = os.getcwd()
|
||||
locales_dir = os.path.join(cwd, "frontend", "editor", "public", "locales")
|
||||
reference_file = os.path.join(locales_dir, "en-GB", "translation.toml")
|
||||
reference_file = os.path.join(locales_dir, "en-US", "translation.toml")
|
||||
scripts_directory = os.path.join(cwd, "scripts")
|
||||
translation_state_file = os.path.join(scripts_directory, "ignore_translation.toml")
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Translation Management Scripts
|
||||
|
||||
This directory contains Python scripts for managing frontend translations in Stirling PDF. These tools help analyze, merge, validate, and manage translations against the en-GB golden truth file.
|
||||
This directory contains Python scripts for managing frontend translations in Stirling PDF. These tools help analyze, merge, validate, and manage translations against the en-US golden truth file.
|
||||
|
||||
## Current Format: TOML
|
||||
|
||||
@@ -33,7 +33,7 @@ python3 scripts/translations/auto_translate.py es-ES --no-cleanup
|
||||
4. Validates placeholders are preserved
|
||||
5. Merges translated batches
|
||||
6. Applies translations to language file
|
||||
7. Beautifies structure to match en-GB
|
||||
7. Beautifies structure to match en-US
|
||||
8. Cleans up temporary files
|
||||
9. Reports final completion percentage
|
||||
|
||||
@@ -86,7 +86,7 @@ python scripts/translations/json_validator.py --all-batches ar_AR --quiet
|
||||
- Trailing commas before closing braces
|
||||
|
||||
#### `validate_placeholders.py`
|
||||
Validates that translation files have correct placeholders matching en-GB (source of truth).
|
||||
Validates that translation files have correct placeholders matching en-US (source of truth).
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
@@ -105,12 +105,12 @@ python scripts/translations/validate_placeholders.py --json
|
||||
|
||||
**Features:**
|
||||
- Detects missing placeholders (e.g., {n}, {total}, {filename})
|
||||
- Detects extra placeholders not in en-GB
|
||||
- Detects extra placeholders not in en-US
|
||||
- Shows exact keys and text where issues occur
|
||||
- Exit code 1 if issues found (good for CI/CD)
|
||||
|
||||
#### `validate_json_structure.py`
|
||||
Validates JSON structure and key consistency with en-GB.
|
||||
Validates JSON structure and key consistency with en-US.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
@@ -130,7 +130,7 @@ python scripts/translations/validate_json_structure.py --json
|
||||
**Features:**
|
||||
- Validates JSON syntax
|
||||
- Detects missing keys (not translated yet)
|
||||
- Detects extra keys (not in en-GB, should be removed)
|
||||
- Detects extra keys (not in en-US, should be removed)
|
||||
- Reports key counts and structure differences
|
||||
- Exit code 1 if issues found (good for CI/CD)
|
||||
|
||||
@@ -160,21 +160,21 @@ python scripts/translations/translation_analyzer.py --format json
|
||||
|
||||
**Features:**
|
||||
- Finds missing translation keys
|
||||
- Identifies untranslated entries (identical to en-GB and [UNTRANSLATED] markers)
|
||||
- Identifies untranslated entries (identical to en-US and [UNTRANSLATED] markers)
|
||||
- Shows accurate completion percentages using ignore patterns
|
||||
- Identifies extra keys not in en-GB
|
||||
- Identifies extra keys not in en-US
|
||||
- Supports JSON and text output formats
|
||||
- Uses `scripts/ignore_translation.toml` for language-specific exclusions
|
||||
|
||||
### 2. `translation_merger.py`
|
||||
Merges missing translations from en-GB into target language files and manages translation workflows.
|
||||
Merges missing translations from en-US into target language files and manages translation workflows.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Operate on all locales (except en-GB) when language is omitted
|
||||
# Operate on all locales (except en-US) when language is omitted
|
||||
python scripts/translations/translation_merger.py add-missing
|
||||
|
||||
# Add missing translations from en-GB to French
|
||||
# Add missing translations from en-US to French
|
||||
python scripts/translations/translation_merger.py fr-FR add-missing
|
||||
|
||||
# Create backups before modifying files
|
||||
@@ -192,19 +192,19 @@ python scripts/translations/translation_merger.py fr-FR apply-translations --tra
|
||||
# Override default paths if needed
|
||||
python scripts/translations/translation_merger.py fr-FR add-missing --locales-dir ./frontend/editor/public/locales --ignore-file ./scripts/ignore_translation.toml
|
||||
|
||||
# Remove unused translations not present in en-GB
|
||||
# Remove unused translations not present in en-US
|
||||
python scripts/translations/translation_merger.py fr-FR remove-unused
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Adds missing keys from en-GB (copies English text directly)
|
||||
- Adds missing keys from en-US (copies English text directly)
|
||||
- Runs across all locales for add-missing/remove-unused when language is omitted
|
||||
- Extracts untranslated entries for external translation
|
||||
- Creates structured templates for AI translation
|
||||
- Applies translated content back to language files (template format or plain JSON)
|
||||
- Supports `--backup` on mutating commands
|
||||
- Automatic backup creation
|
||||
- Removes unused translations not present in en-GB
|
||||
- Removes unused translations not present in en-US
|
||||
|
||||
### 3. `ai_translation_helper.py`
|
||||
Specialized tool for AI-assisted translation workflows with batch processing and validation.
|
||||
@@ -290,7 +290,7 @@ python3 scripts/translations/auto_translate.py es-ES --skip-verification
|
||||
4. **Validate**: Ensures placeholders are preserved
|
||||
5. **Merge**: Combines all translated batches
|
||||
6. **Apply**: Updates the language file
|
||||
7. **Beautify**: Restructures to match en-GB format
|
||||
7. **Beautify**: Restructures to match en-US format
|
||||
8. **Cleanup**: Removes temporary files
|
||||
9. **Verify**: Reports final completion percentage
|
||||
|
||||
@@ -337,11 +337,11 @@ python3 scripts/translations/batch_translator.py batch.json --language it-IT --s
|
||||
- `gpt-5-nano`: Fastest, most economical
|
||||
|
||||
### 7. `json_beautifier.py`
|
||||
Restructures and beautifies translation JSON files to match en-GB structure exactly.
|
||||
Restructures and beautifies translation JSON files to match en-US structure exactly.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Restructure single language to match en-GB structure
|
||||
# Restructure single language to match en-US structure
|
||||
python scripts/translations/json_beautifier.py --language de-DE
|
||||
|
||||
# Restructure all languages
|
||||
@@ -355,7 +355,7 @@ python scripts/translations/json_beautifier.py --language de-DE --no-backup
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Restructures JSON to match en-GB nested structure exactly
|
||||
- Restructures JSON to match en-US nested structure exactly
|
||||
- Preserves key ordering for line-by-line comparison
|
||||
- Creates automatic backups before modification
|
||||
- Validates structure and key ordering
|
||||
@@ -585,7 +585,7 @@ python scripts/translations/json_validator.py --all-batches ar_AR
|
||||
|
||||
#### JSON Structure Mismatches
|
||||
**Problem**: Flattened dot-notation keys instead of proper nested objects
|
||||
**Solution**: Use `json_beautifier.py` to restructure files to match en-GB exactly
|
||||
**Solution**: Use `json_beautifier.py` to restructure files to match en-US exactly
|
||||
|
||||
## Real-World Examples
|
||||
|
||||
@@ -629,7 +629,7 @@ EOF
|
||||
python scripts/translations/translation_merger.py ar-AR apply-translations --translations-file ar_AR_merged.json
|
||||
# Result: Applied 1088 translations
|
||||
|
||||
# Beautify to match en-GB structure
|
||||
# Beautify to match en-US structure
|
||||
python scripts/translations/json_beautifier.py --language ar-AR
|
||||
|
||||
# Check final progress
|
||||
@@ -711,4 +711,4 @@ These scripts integrate with the existing translation system:
|
||||
3. **Updating Existing Language**: Use analyzer to find gaps, then compact or batch method
|
||||
4. **Quality Assurance**: Use analyzer with `--summary` for completion metrics and issue detection
|
||||
5. **External Translation Services**: Use export functionality to generate CSV files for translators
|
||||
6. **Structure Maintenance**: Use json_beautifier to keep files aligned with en-GB structure
|
||||
6. **Structure Maintenance**: Use json_beautifier to keep files aligned with en-US structure
|
||||
|
||||
@@ -20,7 +20,7 @@ import tomli_w
|
||||
class AITranslationHelper:
|
||||
def __init__(self, locales_dir: str = "frontend/editor/public/locales"):
|
||||
self.locales_dir = Path(locales_dir)
|
||||
self.golden_truth_file = self.locales_dir / "en-GB" / "translation.toml"
|
||||
self.golden_truth_file = self.locales_dir / "en-US" / "translation.toml"
|
||||
|
||||
def _load_translation_file(self, file_path: Path) -> Dict:
|
||||
"""Load TOML translation file."""
|
||||
@@ -47,7 +47,7 @@ class AITranslationHelper:
|
||||
batch_data = {
|
||||
"metadata": {
|
||||
"created_at": datetime.now().isoformat(),
|
||||
"source_language": "en-GB",
|
||||
"source_language": "en-US",
|
||||
"target_languages": languages,
|
||||
"max_entries_per_language": max_entries_per_language,
|
||||
"instructions": {
|
||||
@@ -320,7 +320,7 @@ class AITranslationHelper:
|
||||
)
|
||||
|
||||
with open(output_file, "w", newline="", encoding="utf-8") as csvfile:
|
||||
fieldnames = ["key", "context", "en_GB"] + languages
|
||||
fieldnames = ["key", "context", "en_US"] + languages
|
||||
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
|
||||
@@ -331,7 +331,7 @@ class AITranslationHelper:
|
||||
row = {
|
||||
"key": key,
|
||||
"context": self._get_key_context(key),
|
||||
"en_GB": en_value,
|
||||
"en_US": en_value,
|
||||
}
|
||||
|
||||
for lang in languages:
|
||||
@@ -363,7 +363,7 @@ class AITranslationHelper:
|
||||
continue
|
||||
|
||||
export_data["translations"][key] = {
|
||||
"en_GB": en_value,
|
||||
"en_US": en_value,
|
||||
"context": self._get_key_context(key),
|
||||
}
|
||||
|
||||
|
||||
@@ -55,14 +55,14 @@ def extract_untranslated(language_code, batch_size=500, include_existing=False):
|
||||
print(f"\n🔍 Extracting {mode} entries for {language_code}...")
|
||||
|
||||
# Load files
|
||||
golden_path = find_translation_file(Path("frontend/editor/public/locales/en-GB"))
|
||||
golden_path = find_translation_file(Path("frontend/editor/public/locales/en-US"))
|
||||
lang_path = find_translation_file(
|
||||
Path(f"frontend/editor/public/locales/{language_code}")
|
||||
)
|
||||
|
||||
if not golden_path:
|
||||
print(
|
||||
"Error: Golden truth file not found in frontend/editor/public/locales/en-GB"
|
||||
"Error: Golden truth file not found in frontend/editor/public/locales/en-US"
|
||||
)
|
||||
return None
|
||||
|
||||
@@ -222,7 +222,7 @@ def apply_translations(merged_file, language_code):
|
||||
|
||||
|
||||
def beautify_translations(language_code):
|
||||
"""Beautify translation file to match en-GB structure."""
|
||||
"""Beautify translation file to match en-US structure."""
|
||||
print(f"\n✨ Beautifying {language_code} translation file...")
|
||||
|
||||
cmd = f"python3 scripts/translations/toml_beautifier.py --language {language_code}"
|
||||
|
||||
@@ -37,7 +37,7 @@ def get_all_languages(locales_dir: Path) -> List[str]:
|
||||
return []
|
||||
|
||||
for lang_dir in sorted(locales_dir.iterdir()):
|
||||
if lang_dir.is_dir() and lang_dir.name != "en-GB":
|
||||
if lang_dir.is_dir() and lang_dir.name != "en-US":
|
||||
toml_file = lang_dir / "translation.toml"
|
||||
if toml_file.exists():
|
||||
languages.append(lang_dir.name)
|
||||
@@ -57,10 +57,10 @@ def get_language_completion(locales_dir: Path, language: str) -> Optional[float]
|
||||
with open(toml_file, "rb") as f:
|
||||
target_data = tomllib.load(f)
|
||||
|
||||
# Load en-GB reference
|
||||
en_gb_file = locales_dir / "en-GB" / "translation.toml"
|
||||
with open(en_gb_file, "rb") as f:
|
||||
en_gb_data = tomllib.load(f)
|
||||
# Load en-US reference
|
||||
en_us_file = locales_dir / "en-US" / "translation.toml"
|
||||
with open(en_us_file, "rb") as f:
|
||||
en_us_data = tomllib.load(f)
|
||||
|
||||
# Flatten and count
|
||||
def flatten(d, parent=""):
|
||||
@@ -73,16 +73,16 @@ def get_language_completion(locales_dir: Path, language: str) -> Optional[float]
|
||||
items[key] = v
|
||||
return items
|
||||
|
||||
en_gb_flat = flatten(en_gb_data)
|
||||
en_us_flat = flatten(en_us_data)
|
||||
target_flat = flatten(target_data)
|
||||
|
||||
# Count translated (not equal to en-GB)
|
||||
# Count translated (not equal to en-US)
|
||||
translated = sum(
|
||||
1
|
||||
for k in en_gb_flat
|
||||
if k in target_flat and target_flat[k] != en_gb_flat[k]
|
||||
for k in en_us_flat
|
||||
if k in target_flat and target_flat[k] != en_us_flat[k]
|
||||
)
|
||||
total = len(en_gb_flat)
|
||||
total = len(en_us_flat)
|
||||
|
||||
return (translated / total * 100) if total > 0 else 0.0
|
||||
|
||||
@@ -246,7 +246,7 @@ Note: Requires OPENAI_API_KEY environment variable or --api-key argument.
|
||||
print(f"Translating specified languages: {', '.join(languages)}")
|
||||
else:
|
||||
languages = get_all_languages(locales_dir)
|
||||
print(f"Found {len(languages)} languages (excluding en-GB)")
|
||||
print(f"Found {len(languages)} languages (excluding en-US)")
|
||||
|
||||
if not languages:
|
||||
print("No languages to translate!")
|
||||
|
||||
@@ -19,10 +19,10 @@ class CompactTranslationExtractor:
|
||||
ignore_file: str = "scripts/ignore_translation.toml",
|
||||
):
|
||||
self.locales_dir = Path(locales_dir)
|
||||
self.golden_truth_file = self.locales_dir / "en-GB" / "translation.toml"
|
||||
self.golden_truth_file = self.locales_dir / "en-US" / "translation.toml"
|
||||
if not self.golden_truth_file.exists():
|
||||
print(
|
||||
f"Error: en-GB translation file not found at {self.golden_truth_file}",
|
||||
f"Error: en-US translation file not found at {self.golden_truth_file}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
@@ -95,7 +95,7 @@ class CompactTranslationExtractor:
|
||||
# Find missing translations
|
||||
missing_keys = set(golden_flat.keys()) - set(target_flat.keys()) - ignore_set
|
||||
|
||||
# Find untranslated entries (identical to en-GB or marked [UNTRANSLATED])
|
||||
# Find untranslated entries (identical to en-US or marked [UNTRANSLATED])
|
||||
untranslated_keys = set()
|
||||
for key in target_flat:
|
||||
if key in golden_flat and key not in ignore_set:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TOML Beautifier and Structure Fixer for Stirling PDF Frontend
|
||||
Restructures translation TOML files to match en-GB structure and key order exactly.
|
||||
Restructures translation TOML files to match en-US structure and key order exactly.
|
||||
"""
|
||||
|
||||
import sys
|
||||
@@ -17,7 +17,7 @@ import tomli_w
|
||||
class TOMLBeautifier:
|
||||
def __init__(self, locales_dir: str = "frontend/editor/public/locales"):
|
||||
self.locales_dir = Path(locales_dir)
|
||||
self.golden_truth_file = self.locales_dir / "en-GB" / "translation.toml"
|
||||
self.golden_truth_file = self.locales_dir / "en-US" / "translation.toml"
|
||||
self.golden_structure = self._load_toml(self.golden_truth_file)
|
||||
|
||||
def _load_toml(self, file_path: Path) -> Dict:
|
||||
@@ -95,7 +95,7 @@ class TOMLBeautifier:
|
||||
return build_recursive(reference_structure) or OrderedDict()
|
||||
|
||||
def restructure_translation_file(self, target_file: Path) -> Dict[str, Any]:
|
||||
"""Restructure a translation file to match en-GB structure exactly."""
|
||||
"""Restructure a translation file to match en-US structure exactly."""
|
||||
if not target_file.exists():
|
||||
print(f"Error: Target file does not exist: {target_file}")
|
||||
return {}
|
||||
@@ -177,7 +177,7 @@ class TOMLBeautifier:
|
||||
}
|
||||
|
||||
def validate_key_order(self, target_file: Path) -> Dict[str, Any]:
|
||||
"""Validate that keys appear in the same order as en-GB."""
|
||||
"""Validate that keys appear in the same order as en-US."""
|
||||
target_data = self._load_toml(target_file)
|
||||
|
||||
def get_key_order(obj: Dict, path: str = "") -> List[str]:
|
||||
@@ -276,7 +276,7 @@ def main():
|
||||
elif args.all_languages:
|
||||
results = []
|
||||
for lang_dir in Path(args.locales_dir).iterdir():
|
||||
if lang_dir.is_dir() and lang_dir.name != "en-GB":
|
||||
if lang_dir.is_dir() and lang_dir.name != "en-US":
|
||||
translation_file = lang_dir / "translation.toml"
|
||||
if translation_file.exists():
|
||||
if args.validate_only:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Translation Analyzer for Stirling PDF Frontend
|
||||
Compares language files against en-GB golden truth file.
|
||||
Compares language files against en-US golden truth file.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -19,7 +19,7 @@ class TranslationAnalyzer:
|
||||
ignore_file: str = "scripts/ignore_translation.toml",
|
||||
):
|
||||
self.locales_dir = Path(locales_dir)
|
||||
self.golden_truth_file = self.locales_dir / "en-GB" / "translation.toml"
|
||||
self.golden_truth_file = self.locales_dir / "en-US" / "translation.toml"
|
||||
self.golden_truth = self._load_translation_file(self.golden_truth_file)
|
||||
self.ignore_file = Path(ignore_file)
|
||||
self.ignore_patterns = self._load_ignore_patterns()
|
||||
@@ -70,17 +70,17 @@ class TranslationAnalyzer:
|
||||
return dict(items)
|
||||
|
||||
def get_all_language_files(self) -> List[Path]:
|
||||
"""Get all translation files except en-GB."""
|
||||
"""Get all translation files except en-US."""
|
||||
files = []
|
||||
for lang_dir in self.locales_dir.iterdir():
|
||||
if lang_dir.is_dir() and lang_dir.name != "en-GB":
|
||||
if lang_dir.is_dir() and lang_dir.name != "en-US":
|
||||
toml_file = lang_dir / "translation.toml"
|
||||
if toml_file.exists():
|
||||
files.append(toml_file)
|
||||
return sorted(files)
|
||||
|
||||
def find_missing_translations(self, target_file: Path) -> Set[str]:
|
||||
"""Find keys that exist in en-GB but missing in target file."""
|
||||
"""Find keys that exist in en-US but missing in target file."""
|
||||
target_data = self._load_translation_file(target_file)
|
||||
|
||||
golden_flat = self._flatten_dict(self.golden_truth)
|
||||
@@ -94,7 +94,7 @@ class TranslationAnalyzer:
|
||||
return missing - ignore_set
|
||||
|
||||
def find_untranslated_entries(self, target_file: Path) -> Set[str]:
|
||||
"""Find entries that appear to be untranslated (identical to en-GB)."""
|
||||
"""Find entries that appear to be untranslated (identical to en-US)."""
|
||||
target_data = self._load_translation_file(target_file)
|
||||
|
||||
golden_flat = self._flatten_dict(self.golden_truth)
|
||||
@@ -109,7 +109,7 @@ class TranslationAnalyzer:
|
||||
target_value = target_flat[key]
|
||||
golden_value = golden_flat[key]
|
||||
|
||||
# Check if marked as [UNTRANSLATED] or identical to en-GB
|
||||
# Check if marked as [UNTRANSLATED] or identical to en-US
|
||||
if (
|
||||
isinstance(target_value, str)
|
||||
and target_value.startswith("[UNTRANSLATED]")
|
||||
@@ -139,7 +139,7 @@ class TranslationAnalyzer:
|
||||
return False
|
||||
|
||||
def find_extra_translations(self, target_file: Path) -> Set[str]:
|
||||
"""Find keys that exist in target file but not in en-GB."""
|
||||
"""Find keys that exist in target file but not in en-US."""
|
||||
target_data = self._load_translation_file(target_file)
|
||||
|
||||
golden_flat = self._flatten_dict(self.golden_truth)
|
||||
@@ -174,7 +174,7 @@ class TranslationAnalyzer:
|
||||
if not (isinstance(value, str) and value.startswith("[UNTRANSLATED]")):
|
||||
if (
|
||||
key not in untranslated
|
||||
): # Not identical to en-GB (unless expected)
|
||||
): # Not identical to en-US (unless expected)
|
||||
properly_translated += 1
|
||||
|
||||
completion_rate = (
|
||||
@@ -204,7 +204,7 @@ class TranslationAnalyzer:
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Analyze translation files against en-GB golden truth"
|
||||
description="Analyze translation files against en-US golden truth"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--locales-dir",
|
||||
@@ -260,7 +260,7 @@ def main():
|
||||
print(f"Language: {lang}")
|
||||
print(f"File: {result['file']}")
|
||||
print(f"Completion Rate: {result['completion_rate']:.1f}%")
|
||||
print(f"Total Keys in en-GB: {result['total_keys']}")
|
||||
print(f"Total Keys in en-US: {result['total_keys']}")
|
||||
|
||||
if not args.summary:
|
||||
if not args.untranslated_only:
|
||||
@@ -278,7 +278,7 @@ def main():
|
||||
print(f" ... and {len(result['untranslated_keys']) - 10} more")
|
||||
|
||||
if result["extra_count"] > 0:
|
||||
print(f"\nExtra Keys Not in en-GB ({result['extra_count']}):")
|
||||
print(f"\nExtra Keys Not in en-US ({result['extra_count']}):")
|
||||
for key in result["extra_keys"][:5]:
|
||||
print(f" - {key}")
|
||||
if len(result["extra_keys"]) > 5:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Translation Merger for Stirling PDF Frontend
|
||||
Merges missing translations from en-GB into target language files.
|
||||
Merges missing translations from en-US into target language files.
|
||||
Useful for AI-assisted translation workflows.
|
||||
TOML format only.
|
||||
"""
|
||||
@@ -30,7 +30,7 @@ class TranslationMerger:
|
||||
),
|
||||
):
|
||||
self.locales_dir = Path(locales_dir)
|
||||
self.golden_truth_file = self.locales_dir / "en-GB" / "translation.toml"
|
||||
self.golden_truth_file = self.locales_dir / "en-US" / "translation.toml"
|
||||
self.golden_truth = self._load_translation_file(self.golden_truth_file)
|
||||
self.ignore_file = Path(ignore_file)
|
||||
self.ignore_patterns = self._load_ignore_patterns()
|
||||
@@ -178,7 +178,7 @@ class TranslationMerger:
|
||||
save: bool = True,
|
||||
backup: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
"""Add missing translations from en-GB to target file and optionally save."""
|
||||
"""Add missing translations from en-US to target file and optionally save."""
|
||||
if not target_file.parent.exists():
|
||||
target_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
target_data = {}
|
||||
@@ -210,7 +210,7 @@ class TranslationMerger:
|
||||
def extract_untranslated_entries(
|
||||
self, target_file: Path, output_file: Path | None = None
|
||||
) -> dict[str, Any]:
|
||||
"""Extract entries marked as untranslated or identical to en-GB for AI translation."""
|
||||
"""Extract entries marked as untranslated or identical to en-US for AI translation."""
|
||||
if not target_file.exists():
|
||||
print(f"Error: Target file does not exist: {target_file}")
|
||||
return {}
|
||||
@@ -335,7 +335,7 @@ class TranslationMerger:
|
||||
|
||||
template = {
|
||||
"metadata": {
|
||||
"source_language": "en-GB",
|
||||
"source_language": "en-US",
|
||||
"target_language": target_file.parent.name,
|
||||
"total_entries": len(untranslated),
|
||||
"created_at": datetime.now().isoformat(),
|
||||
@@ -384,14 +384,14 @@ def main():
|
||||
parser.add_argument(
|
||||
"language",
|
||||
nargs="?",
|
||||
help="Target language code (e.g., fr-FR). If omitted, add-missing and remove-unused run for all locales except en-GB.",
|
||||
help="Target language code (e.g., fr-FR). If omitted, add-missing and remove-unused run for all locales except en-US.",
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
||||
|
||||
# Add missing command
|
||||
add_parser = subparsers.add_parser(
|
||||
"add-missing", help="Add missing translations from en-GB"
|
||||
"add-missing", help="Add missing translations from en-US"
|
||||
)
|
||||
add_parser.add_argument(
|
||||
"--backup", action="store_true", help="Create backup before modifying files"
|
||||
@@ -424,7 +424,7 @@ def main():
|
||||
|
||||
# Remove unused translations command
|
||||
remove_parser = subparsers.add_parser(
|
||||
"remove-unused", help="Remove unused translations not present in en-GB"
|
||||
"remove-unused", help="Remove unused translations not present in en-US"
|
||||
)
|
||||
remove_parser.add_argument(
|
||||
"--backup", action="store_true", help="Create backup before modifying files"
|
||||
@@ -449,7 +449,7 @@ def main():
|
||||
else:
|
||||
total_added = 0
|
||||
for lang_dir in sorted(Path(args.locales_dir).iterdir()):
|
||||
if not lang_dir.is_dir() or lang_dir.name == "en-GB":
|
||||
if not lang_dir.is_dir() or lang_dir.name == "en-US":
|
||||
continue
|
||||
target_file = lang_dir / "translation.toml"
|
||||
print(f"Processing {lang_dir.name}...")
|
||||
@@ -471,7 +471,7 @@ def main():
|
||||
else:
|
||||
total_removed = 0
|
||||
for lang_dir in sorted(Path(args.locales_dir).iterdir()):
|
||||
if not lang_dir.is_dir() or lang_dir.name == "en-GB":
|
||||
if not lang_dir.is_dir() or lang_dir.name == "en-US":
|
||||
continue
|
||||
target_file = lang_dir / "translation.toml"
|
||||
print(f"Processing {lang_dir.name}...")
|
||||
|
||||
@@ -4,9 +4,9 @@ Validate TOML structure and formatting of translation files.
|
||||
|
||||
Checks for:
|
||||
- Valid TOML syntax
|
||||
- Consistent key structure with en-GB
|
||||
- Consistent key structure with en-US
|
||||
- Missing keys
|
||||
- Extra keys not in en-GB
|
||||
- Extra keys not in en-US
|
||||
- Malformed entries
|
||||
|
||||
Usage:
|
||||
@@ -43,18 +43,18 @@ def validate_translation_file(file_path: Path) -> tuple[bool, str]:
|
||||
|
||||
|
||||
def validate_structure(
|
||||
en_gb_keys: Set[str], lang_keys: Set[str], lang_code: str
|
||||
en_us_keys: Set[str], lang_keys: Set[str], lang_code: str
|
||||
) -> Dict:
|
||||
"""Compare structure between en-GB and target language."""
|
||||
missing_keys = en_gb_keys - lang_keys
|
||||
extra_keys = lang_keys - en_gb_keys
|
||||
"""Compare structure between en-US and target language."""
|
||||
missing_keys = en_us_keys - lang_keys
|
||||
extra_keys = lang_keys - en_us_keys
|
||||
|
||||
return {
|
||||
"language": lang_code,
|
||||
"missing_keys": sorted(missing_keys),
|
||||
"extra_keys": sorted(extra_keys),
|
||||
"total_keys": len(lang_keys),
|
||||
"expected_keys": len(en_gb_keys),
|
||||
"expected_keys": len(en_us_keys),
|
||||
"missing_count": len(missing_keys),
|
||||
"extra_count": len(extra_keys),
|
||||
}
|
||||
@@ -68,12 +68,12 @@ def print_validation_result(result: Dict, verbose: bool = False):
|
||||
print(f"Language: {lang}")
|
||||
print(f"{'=' * 100}")
|
||||
print(f" Total keys: {result['total_keys']}")
|
||||
print(f" Expected keys (en-GB): {result['expected_keys']}")
|
||||
print(f" Expected keys (en-US): {result['expected_keys']}")
|
||||
print(f" Missing keys: {result['missing_count']}")
|
||||
print(f" Extra keys: {result['extra_count']}")
|
||||
|
||||
if result["missing_count"] == 0 and result["extra_count"] == 0:
|
||||
print(" ✅ Structure matches en-GB perfectly!")
|
||||
print(" ✅ Structure matches en-US perfectly!")
|
||||
else:
|
||||
if result["missing_count"] > 0:
|
||||
print(f"\n ⚠️ Missing {result['missing_count']} key(s):")
|
||||
@@ -86,7 +86,7 @@ def print_validation_result(result: Dict, verbose: bool = False):
|
||||
print(" (use --verbose to see all)")
|
||||
|
||||
if result["extra_count"] > 0:
|
||||
print(f"\n ⚠️ Extra {result['extra_count']} key(s) not in en-GB:")
|
||||
print(f"\n ⚠️ Extra {result['extra_count']} key(s) not in en-US:")
|
||||
if verbose or result["extra_count"] <= 20:
|
||||
for key in result["extra_keys"][:50]:
|
||||
print(f" - {key}")
|
||||
@@ -120,31 +120,31 @@ def main():
|
||||
|
||||
# Define paths
|
||||
locales_dir = Path("frontend/editor/public/locales")
|
||||
en_gb_path = locales_dir / "en-GB" / "translation.toml"
|
||||
en_us_path = locales_dir / "en-US" / "translation.toml"
|
||||
|
||||
if not en_gb_path.exists():
|
||||
print(f"❌ Error: en-GB translation file not found at {en_gb_path}")
|
||||
if not en_us_path.exists():
|
||||
print(f"❌ Error: en-US translation file not found at {en_us_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate en-GB itself
|
||||
is_valid, message = validate_translation_file(en_gb_path)
|
||||
# Validate en-US itself
|
||||
is_valid, message = validate_translation_file(en_us_path)
|
||||
if not is_valid:
|
||||
print(f"❌ Error in en-GB file: {message}")
|
||||
print(f"❌ Error in en-US file: {message}")
|
||||
sys.exit(1)
|
||||
|
||||
# Load en-GB structure
|
||||
en_gb = load_translation_file(en_gb_path)
|
||||
# Load en-US structure
|
||||
en_us = load_translation_file(en_us_path)
|
||||
|
||||
en_gb_keys = get_all_keys(en_gb)
|
||||
en_us_keys = get_all_keys(en_us)
|
||||
|
||||
# Get list of languages to validate
|
||||
if args.language:
|
||||
languages = [args.language]
|
||||
else:
|
||||
# Validate all languages except en-GB
|
||||
# Validate all languages except en-US
|
||||
languages = []
|
||||
for d in locales_dir.iterdir():
|
||||
if d.is_dir() and d.name != "en-GB":
|
||||
if d.is_dir() and d.name != "en-US":
|
||||
if (d / "translation.toml").exists():
|
||||
languages.append(d.name)
|
||||
|
||||
@@ -171,7 +171,7 @@ def main():
|
||||
lang_data = load_translation_file(lang_path)
|
||||
|
||||
lang_keys = get_all_keys(lang_data)
|
||||
result = validate_structure(en_gb_keys, lang_keys, lang_code)
|
||||
result = validate_structure(en_us_keys, lang_keys, lang_code)
|
||||
results.append(result)
|
||||
|
||||
# Output results
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate that translation files have the same placeholders as en-GB (source of truth).
|
||||
Validate that translation files have the same placeholders as en-US (source of truth).
|
||||
|
||||
Usage:
|
||||
python scripts/translations/validate_placeholders.py [--language LANG] [--fix]
|
||||
@@ -38,16 +38,16 @@ def flatten_dict(d: dict, parent_key: str = "", sep: str = ".") -> Dict[str, str
|
||||
|
||||
|
||||
def validate_language(
|
||||
en_gb_flat: Dict[str, str], lang_flat: Dict[str, str], lang_code: str
|
||||
en_us_flat: Dict[str, str], lang_flat: Dict[str, str], lang_code: str
|
||||
) -> List[Dict]:
|
||||
"""Validate placeholders for a language against en-GB."""
|
||||
"""Validate placeholders for a language against en-US."""
|
||||
issues = []
|
||||
|
||||
for key in en_gb_flat:
|
||||
for key in en_us_flat:
|
||||
if key not in lang_flat:
|
||||
continue
|
||||
|
||||
en_placeholders = find_placeholders(en_gb_flat[key])
|
||||
en_placeholders = find_placeholders(en_us_flat[key])
|
||||
lang_placeholders = find_placeholders(lang_flat[key])
|
||||
|
||||
if en_placeholders != lang_placeholders:
|
||||
@@ -59,7 +59,7 @@ def validate_language(
|
||||
"key": key,
|
||||
"missing": missing,
|
||||
"extra": extra,
|
||||
"en_text": en_gb_flat[key],
|
||||
"en_text": en_us_flat[key],
|
||||
"lang_text": lang_flat[key],
|
||||
}
|
||||
issues.append(issue)
|
||||
@@ -113,26 +113,26 @@ def main():
|
||||
|
||||
# Define paths
|
||||
locales_dir = Path("frontend/editor/public/locales")
|
||||
en_gb_path = locales_dir / "en-GB" / "translation.toml"
|
||||
en_us_path = locales_dir / "en-US" / "translation.toml"
|
||||
|
||||
if not en_gb_path.exists():
|
||||
print(f"❌ Error: en-GB translation file not found at {en_gb_path}")
|
||||
if not en_us_path.exists():
|
||||
print(f"❌ Error: en-US translation file not found at {en_us_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Load en-GB (source of truth)
|
||||
with open(en_gb_path, "rb") as f:
|
||||
en_gb = tomllib.load(f)
|
||||
# Load en-US (source of truth)
|
||||
with open(en_us_path, "rb") as f:
|
||||
en_us = tomllib.load(f)
|
||||
|
||||
en_gb_flat = flatten_dict(en_gb)
|
||||
en_us_flat = flatten_dict(en_us)
|
||||
|
||||
# Get list of languages to validate
|
||||
if args.language:
|
||||
languages = [args.language]
|
||||
else:
|
||||
# Validate all languages except en-GB
|
||||
# Validate all languages except en-US
|
||||
languages = []
|
||||
for d in locales_dir.iterdir():
|
||||
if d.is_dir() and d.name != "en-GB":
|
||||
if d.is_dir() and d.name != "en-US":
|
||||
if (d / "translation.toml").exists():
|
||||
languages.append(d.name)
|
||||
|
||||
@@ -151,7 +151,7 @@ def main():
|
||||
lang_data = tomllib.load(f)
|
||||
|
||||
lang_flat = flatten_dict(lang_data)
|
||||
issues = validate_language(en_gb_flat, lang_flat, lang_code)
|
||||
issues = validate_language(en_us_flat, lang_flat, lang_code)
|
||||
all_issues.extend(issues)
|
||||
|
||||
# Output results
|
||||
|
||||
Reference in New Issue
Block a user