🤖 format everything with pre-commit by stirlingbot (#5144)

Auto-generated by [create-pull-request][1] with **stirlingbot**

[1]: https://github.com/peter-evans/create-pull-request

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
This commit is contained in:
stirlingbot[bot]
2025-12-22 15:44:38 +00:00
committed by GitHub
co-authored by stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
parent e6d3f20c36
commit c990ab3216
26 changed files with 1239 additions and 822 deletions
+99 -64
View File
@@ -5,16 +5,19 @@ Compares language files against en-GB golden truth file.
"""
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Set, Tuple
from typing import Dict, List, Set
import argparse
import tomllib
class TranslationAnalyzer:
def __init__(self, locales_dir: str = "frontend/public/locales", ignore_file: str = "scripts/ignore_translation.toml"):
def __init__(
self,
locales_dir: str = "frontend/public/locales",
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 = self._load_translation_file(self.golden_truth_file)
@@ -24,7 +27,7 @@ class TranslationAnalyzer:
def _load_translation_file(self, file_path: Path) -> Dict:
"""Load TOML translation file with error handling."""
try:
with open(file_path, 'rb') as f:
with open(file_path, "rb") as f:
return tomllib.load(f)
except FileNotFoundError:
print(f"Error: File not found: {file_path}")
@@ -39,17 +42,23 @@ class TranslationAnalyzer:
return {}
try:
with open(self.ignore_file, 'rb') as f:
with open(self.ignore_file, "rb") as f:
ignore_data = tomllib.load(f)
# Convert lists to sets for faster lookup
return {lang: set(patterns) for lang, data in ignore_data.items()
for patterns in [data.get('ignore', [])] if patterns}
return {
lang: set(patterns)
for lang, data in ignore_data.items()
for patterns in [data.get("ignore", [])]
if patterns
}
except Exception as e:
print(f"Warning: Could not load ignore file {self.ignore_file}: {e}")
return {}
def _flatten_dict(self, d: Dict, parent_key: str = '', separator: str = '.') -> Dict[str, str]:
def _flatten_dict(
self, d: Dict, parent_key: str = "", separator: str = "."
) -> Dict[str, str]:
"""Flatten nested dictionary into dot-notation keys."""
items = []
for k, v in d.items():
@@ -80,7 +89,7 @@ class TranslationAnalyzer:
missing = set(golden_flat.keys()) - set(target_flat.keys())
# Filter out ignored keys
lang_code = target_file.parent.name.replace('-', '_')
lang_code = target_file.parent.name.replace("-", "_")
ignore_set = self.ignore_patterns.get(lang_code, set())
return missing - ignore_set
@@ -91,7 +100,7 @@ class TranslationAnalyzer:
golden_flat = self._flatten_dict(self.golden_truth)
target_flat = self._flatten_dict(target_data)
lang_code = target_file.parent.name.replace('-', '_')
lang_code = target_file.parent.name.replace("-", "_")
ignore_set = self.ignore_patterns.get(lang_code, set())
untranslated = set()
@@ -101,8 +110,14 @@ class TranslationAnalyzer:
golden_value = golden_flat[key]
# Check if marked as [UNTRANSLATED] or identical to en-GB
if (isinstance(target_value, str) and target_value.startswith("[UNTRANSLATED]")) or \
(golden_value == target_value and key not in ignore_set and not self._is_expected_identical(key, golden_value)):
if (
isinstance(target_value, str)
and target_value.startswith("[UNTRANSLATED]")
) or (
golden_value == target_value
and key not in ignore_set
and not self._is_expected_identical(key, golden_value)
):
untranslated.add(key)
return untranslated
@@ -110,14 +125,10 @@ class TranslationAnalyzer:
def _is_expected_identical(self, key: str, value: str) -> bool:
"""Check if a key-value pair is expected to be identical across languages."""
# Keys that should be identical across languages
identical_patterns = [
'language.direction',
'true', 'false',
'unknown'
]
identical_patterns = ["language.direction", "true", "false", "unknown"]
# Values that are often identical (numbers, symbols, etc.)
if value.strip() in ['ltr', 'rtl', 'True', 'False']:
if value.strip() in ["ltr", "rtl", "True", "False"]:
return True
# Check for patterns
@@ -149,7 +160,7 @@ class TranslationAnalyzer:
target_flat = self._flatten_dict(target_data)
# Calculate completion rate excluding ignored keys
lang_code = target_file.parent.name.replace('-', '_')
lang_code = target_file.parent.name.replace("-", "_")
ignore_set = self.ignore_patterns.get(lang_code, set())
relevant_keys = set(golden_flat.keys()) - ignore_set
@@ -161,22 +172,26 @@ class TranslationAnalyzer:
if key in target_flat:
value = target_flat[key]
if not (isinstance(value, str) and value.startswith("[UNTRANSLATED]")):
if key not in untranslated: # Not identical to en-GB (unless expected)
if (
key not in untranslated
): # Not identical to en-GB (unless expected)
properly_translated += 1
completion_rate = (properly_translated / total_keys) * 100 if total_keys > 0 else 0
completion_rate = (
(properly_translated / total_keys) * 100 if total_keys > 0 else 0
)
return {
'language': lang_code,
'file': target_file,
'missing_count': len(missing),
'missing_keys': sorted(missing),
'untranslated_count': len(untranslated),
'untranslated_keys': sorted(untranslated),
'extra_count': len(extra),
'extra_keys': sorted(extra),
'total_keys': total_keys,
'completion_rate': completion_rate
"language": lang_code,
"file": target_file,
"missing_count": len(missing),
"missing_keys": sorted(missing),
"untranslated_count": len(untranslated),
"untranslated_keys": sorted(untranslated),
"extra_count": len(extra),
"extra_keys": sorted(extra),
"total_keys": total_keys,
"completion_rate": completion_rate,
}
def analyze_all_files(self) -> List[Dict]:
@@ -184,24 +199,38 @@ class TranslationAnalyzer:
results = []
for file_path in self.get_all_language_files():
results.append(self.analyze_file(file_path))
return sorted(results, key=lambda x: x['language'])
return sorted(results, key=lambda x: x["language"])
def main():
parser = argparse.ArgumentParser(description='Analyze translation files against en-GB golden truth')
parser.add_argument('--locales-dir', default='frontend/public/locales',
help='Path to locales directory')
parser.add_argument('--ignore-file', default='scripts/ignore_translation.toml',
help='Path to ignore patterns TOML file')
parser.add_argument('--language', help='Analyze specific language only')
parser.add_argument('--missing-only', action='store_true',
help='Show only missing translations')
parser.add_argument('--untranslated-only', action='store_true',
help='Show only untranslated entries')
parser.add_argument('--summary', action='store_true',
help='Show summary statistics only')
parser.add_argument('--format', choices=['text', 'json'], default='text',
help='Output format')
parser = argparse.ArgumentParser(
description="Analyze translation files against en-GB golden truth"
)
parser.add_argument(
"--locales-dir",
default="frontend/public/locales",
help="Path to locales directory",
)
parser.add_argument(
"--ignore-file",
default="scripts/ignore_translation.toml",
help="Path to ignore patterns TOML file",
)
parser.add_argument("--language", help="Analyze specific language only")
parser.add_argument(
"--missing-only", action="store_true", help="Show only missing translations"
)
parser.add_argument(
"--untranslated-only",
action="store_true",
help="Show only untranslated entries",
)
parser.add_argument(
"--summary", action="store_true", help="Show summary statistics only"
)
parser.add_argument(
"--format", choices=["text", "json"], default="text", help="Output format"
)
args = parser.parse_args()
@@ -220,14 +249,14 @@ def main():
else:
results = analyzer.analyze_all_files()
if args.format == 'json':
if args.format == "json":
print(json.dumps(results, indent=2, default=str))
return
# Text format output
for result in results:
lang = result['language']
print(f"\n{'='*60}")
lang = result["language"]
print(f"\n{'=' * 60}")
print(f"Language: {lang}")
print(f"File: {result['file']}")
print(f"Completion Rate: {result['completion_rate']:.1f}%")
@@ -236,42 +265,48 @@ def main():
if not args.summary:
if not args.untranslated_only:
print(f"\nMissing Translations ({result['missing_count']}):")
for key in result['missing_keys'][:10]: # Show first 10
for key in result["missing_keys"][:10]: # Show first 10
print(f" - {key}")
if len(result['missing_keys']) > 10:
if len(result["missing_keys"]) > 10:
print(f" ... and {len(result['missing_keys']) - 10} more")
if not args.missing_only:
print(f"\nUntranslated Entries ({result['untranslated_count']}):")
for key in result['untranslated_keys'][:10]: # Show first 10
for key in result["untranslated_keys"][:10]: # Show first 10
print(f" - {key}")
if len(result['untranslated_keys']) > 10:
if len(result["untranslated_keys"]) > 10:
print(f" ... and {len(result['untranslated_keys']) - 10} more")
if result['extra_count'] > 0:
if result["extra_count"] > 0:
print(f"\nExtra Keys Not in en-GB ({result['extra_count']}):")
for key in result['extra_keys'][:5]:
for key in result["extra_keys"][:5]:
print(f" - {key}")
if len(result['extra_keys']) > 5:
if len(result["extra_keys"]) > 5:
print(f" ... and {len(result['extra_keys']) - 5} more")
print(f"\n{'='*60}")
print(f"\n{'=' * 60}")
print("SUMMARY")
print(f"{'='*60}")
avg_completion = sum(r['completion_rate'] for r in results) / len(results) if results else 0
print(f"{'=' * 60}")
avg_completion = (
sum(r["completion_rate"] for r in results) / len(results) if results else 0
)
print(f"Average Completion Rate: {avg_completion:.1f}%")
print(f"Languages Analyzed: {len(results)}")
# Top languages by completion
sorted_by_completion = sorted(results, key=lambda x: x['completion_rate'], reverse=True)
print(f"\nTop 5 Most Complete Languages:")
sorted_by_completion = sorted(
results, key=lambda x: x["completion_rate"], reverse=True
)
print("\nTop 5 Most Complete Languages:")
for result in sorted_by_completion[:5]:
print(f" {result['language']}: {result['completion_rate']:.1f}%")
print(f"\nBottom 5 Languages Needing Attention:")
print("\nBottom 5 Languages Needing Attention:")
for result in sorted_by_completion[-5:]:
print(f" {result['language']}: {result['completion_rate']:.1f}% ({result['missing_count']} missing, {result['untranslated_count']} untranslated)")
print(
f" {result['language']}: {result['completion_rate']:.1f}% ({result['missing_count']} missing, {result['untranslated_count']} untranslated)"
)
if __name__ == "__main__":
main()
main()