updates
This commit is contained in:
@@ -15,21 +15,8 @@ import argparse
|
||||
import re
|
||||
from datetime import datetime
|
||||
import csv
|
||||
|
||||
try:
|
||||
import tomllib # Python 3.11+
|
||||
except ImportError:
|
||||
try:
|
||||
import toml as tomllib_fallback
|
||||
tomllib = None
|
||||
except ImportError:
|
||||
tomllib = None
|
||||
tomllib_fallback = None
|
||||
|
||||
try:
|
||||
import tomli_w # For writing TOML
|
||||
except ImportError:
|
||||
tomli_w = None
|
||||
import tomllib # Python 3.11+ (stdlib)
|
||||
import tomli_w # For writing TOML
|
||||
|
||||
|
||||
class AITranslationHelper:
|
||||
@@ -55,15 +42,8 @@ class AITranslationHelper:
|
||||
"""Load TOML or JSON translation file based on extension."""
|
||||
try:
|
||||
if file_path.suffix == '.toml':
|
||||
if tomllib:
|
||||
with open(file_path, 'rb') as f:
|
||||
return tomllib.load(f)
|
||||
elif tomllib_fallback:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return tomllib_fallback.load(f)
|
||||
else:
|
||||
print(f"Error: TOML support not available. Install 'toml' or upgrade to Python 3.11+")
|
||||
sys.exit(1)
|
||||
with open(file_path, 'rb') as f:
|
||||
return tomllib.load(f)
|
||||
else: # JSON
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
@@ -74,12 +54,8 @@ class AITranslationHelper:
|
||||
def _save_translation_file(self, data: Dict, file_path: Path) -> None:
|
||||
"""Save translation file (TOML or JSON) based on extension."""
|
||||
if file_path.suffix == '.toml':
|
||||
if tomli_w:
|
||||
with open(file_path, 'wb') as f:
|
||||
tomli_w.dump(data, f)
|
||||
else:
|
||||
print(f"Error: TOML writing not available. Install 'tomli_w'")
|
||||
sys.exit(1)
|
||||
with open(file_path, 'wb') as f:
|
||||
tomli_w.dump(data, f)
|
||||
else: # JSON
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
@@ -106,10 +82,18 @@ class AITranslationHelper:
|
||||
|
||||
for lang in languages:
|
||||
lang_dir = self.locales_dir / lang
|
||||
try:
|
||||
lang_file = self._find_translation_file(lang_dir)
|
||||
|
||||
# Check if translation file exists, if not create empty structure
|
||||
toml_file = lang_dir / "translation.toml"
|
||||
json_file = lang_dir / "translation.json"
|
||||
|
||||
if toml_file.exists():
|
||||
lang_file = toml_file
|
||||
lang_data = self._load_translation_file(lang_file)
|
||||
except SystemExit:
|
||||
elif json_file.exists():
|
||||
lang_file = json_file
|
||||
lang_data = self._load_translation_file(lang_file)
|
||||
else:
|
||||
# No translation file found, create empty structure
|
||||
lang_data = {}
|
||||
|
||||
@@ -285,10 +269,16 @@ class AITranslationHelper:
|
||||
lang_dir = self.locales_dir / lang
|
||||
|
||||
# Load existing data or create new
|
||||
try:
|
||||
lang_file = self._find_translation_file(lang_dir)
|
||||
toml_file = lang_dir / "translation.toml"
|
||||
json_file = lang_dir / "translation.json"
|
||||
|
||||
if toml_file.exists():
|
||||
lang_file = toml_file
|
||||
lang_data = self._load_translation_file(lang_file)
|
||||
except SystemExit:
|
||||
elif json_file.exists():
|
||||
lang_file = json_file
|
||||
lang_data = self._load_translation_file(lang_file)
|
||||
else:
|
||||
# No translation file found, create new JSON file
|
||||
lang_data = {}
|
||||
lang_dir.mkdir(parents=True, exist_ok=True)
|
||||
@@ -347,15 +337,26 @@ class AITranslationHelper:
|
||||
|
||||
for lang in languages:
|
||||
lang_dir = self.locales_dir / lang
|
||||
try:
|
||||
lang_file = self._find_translation_file(lang_dir)
|
||||
toml_file = lang_dir / "translation.toml"
|
||||
json_file = lang_dir / "translation.json"
|
||||
|
||||
if toml_file.exists():
|
||||
lang_file = toml_file
|
||||
lang_data = self._load_translation_file(lang_file)
|
||||
lang_flat = self._flatten_dict(lang_data)
|
||||
value = lang_flat.get(key, '')
|
||||
if value.startswith('[UNTRANSLATED]'):
|
||||
value = ''
|
||||
row[lang] = value
|
||||
except SystemExit:
|
||||
elif json_file.exists():
|
||||
lang_file = json_file
|
||||
lang_data = self._load_translation_file(lang_file)
|
||||
lang_flat = self._flatten_dict(lang_data)
|
||||
value = lang_flat.get(key, '')
|
||||
if value.startswith('[UNTRANSLATED]'):
|
||||
value = ''
|
||||
row[lang] = value
|
||||
else:
|
||||
row[lang] = ''
|
||||
|
||||
writer.writerow(row)
|
||||
@@ -377,16 +378,25 @@ class AITranslationHelper:
|
||||
|
||||
for lang in languages:
|
||||
lang_dir = self.locales_dir / lang
|
||||
try:
|
||||
lang_file = self._find_translation_file(lang_dir)
|
||||
toml_file = lang_dir / "translation.toml"
|
||||
json_file = lang_dir / "translation.json"
|
||||
|
||||
if toml_file.exists():
|
||||
lang_file = toml_file
|
||||
lang_data = self._load_translation_file(lang_file)
|
||||
lang_flat = self._flatten_dict(lang_data)
|
||||
value = lang_flat.get(key, '')
|
||||
if value.startswith('[UNTRANSLATED]'):
|
||||
value = ''
|
||||
export_data['translations'][key][lang] = value
|
||||
elif json_file.exists():
|
||||
lang_file = json_file
|
||||
lang_data = self._load_translation_file(lang_file)
|
||||
lang_flat = self._flatten_dict(lang_data)
|
||||
value = lang_flat.get(key, '')
|
||||
if value.startswith('[UNTRANSLATED]'):
|
||||
value = ''
|
||||
export_data['translations'][key][lang] = value
|
||||
except SystemExit:
|
||||
pass
|
||||
|
||||
# Export files are always JSON
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
|
||||
@@ -2,67 +2,37 @@
|
||||
"""
|
||||
Compact Translation Extractor for Character-Limited AI Translation
|
||||
Outputs untranslated entries in minimal JSON format with whitespace stripped.
|
||||
Supports both TOML and JSON formats.
|
||||
TOML format only.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import argparse
|
||||
try:
|
||||
import tomllib # Python 3.11+
|
||||
except ImportError:
|
||||
try:
|
||||
import toml as tomllib_fallback
|
||||
tomllib = None
|
||||
except ImportError:
|
||||
tomllib = None
|
||||
tomllib_fallback = None
|
||||
import tomllib # Python 3.11+ (stdlib)
|
||||
|
||||
|
||||
class CompactTranslationExtractor:
|
||||
def __init__(self, locales_dir: str = "frontend/public/locales", ignore_file: str = "scripts/ignore_translation.toml"):
|
||||
self.locales_dir = Path(locales_dir)
|
||||
# Try TOML first, then fall back to JSON
|
||||
self.golden_truth_file = self._find_translation_file(self.locales_dir / "en-GB")
|
||||
self.golden_truth_file = self.locales_dir / "en-GB" / "translation.toml"
|
||||
if not self.golden_truth_file.exists():
|
||||
print(f"Error: en-GB translation file not found at {self.golden_truth_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
self.golden_truth = self._load_translation_file(self.golden_truth_file)
|
||||
self.ignore_file = Path(ignore_file)
|
||||
self.ignore_patterns = self._load_ignore_patterns()
|
||||
|
||||
def _find_translation_file(self, lang_dir: Path) -> Path:
|
||||
"""Find translation file (TOML or JSON) in language directory."""
|
||||
toml_file = lang_dir / "translation.toml"
|
||||
json_file = lang_dir / "translation.json"
|
||||
|
||||
if toml_file.exists():
|
||||
return toml_file
|
||||
elif json_file.exists():
|
||||
return json_file
|
||||
else:
|
||||
print(f"Error: No translation file found in {lang_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def _load_translation_file(self, file_path: Path) -> dict:
|
||||
"""Load TOML or JSON translation file based on extension."""
|
||||
"""Load TOML translation file."""
|
||||
try:
|
||||
if file_path.suffix == '.toml':
|
||||
if tomllib:
|
||||
with open(file_path, 'rb') as f:
|
||||
return tomllib.load(f)
|
||||
elif tomllib_fallback:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return tomllib_fallback.load(f)
|
||||
else:
|
||||
print(f"Error: TOML support not available. Install 'toml' or upgrade to Python 3.11+", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else: # JSON
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
with open(file_path, 'rb') as f:
|
||||
return tomllib.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"Error: File not found: {file_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"Error: Invalid file {file_path}: {e}", file=sys.stderr)
|
||||
print(f"Error: Invalid TOML file {file_path}: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def _load_ignore_patterns(self) -> dict:
|
||||
@@ -71,40 +41,13 @@ class CompactTranslationExtractor:
|
||||
return {}
|
||||
|
||||
try:
|
||||
if tomllib:
|
||||
with open(self.ignore_file, 'rb') as f:
|
||||
ignore_data = tomllib.load(f)
|
||||
elif tomllib_fallback:
|
||||
ignore_data = tomllib_fallback.load(self.ignore_file)
|
||||
else:
|
||||
ignore_data = self._parse_simple_toml()
|
||||
|
||||
with open(self.ignore_file, 'rb') as f:
|
||||
ignore_data = tomllib.load(f)
|
||||
return {lang: set(data.get('ignore', [])) for lang, data in ignore_data.items()}
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not load ignore file {self.ignore_file}: {e}", file=sys.stderr)
|
||||
return {}
|
||||
|
||||
def _parse_simple_toml(self) -> dict:
|
||||
"""Simple TOML parser for ignore patterns (fallback)."""
|
||||
ignore_data = {}
|
||||
current_section = None
|
||||
|
||||
with open(self.ignore_file, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
|
||||
if line.startswith('[') and line.endswith(']'):
|
||||
current_section = line[1:-1]
|
||||
ignore_data[current_section] = {'ignore': []}
|
||||
elif line.strip().startswith("'") and current_section:
|
||||
item = line.strip().strip("',")
|
||||
if item:
|
||||
ignore_data[current_section]['ignore'].append(item)
|
||||
|
||||
return ignore_data
|
||||
|
||||
def _flatten_dict(self, d: dict, parent_key: str = '', separator: str = '.') -> dict:
|
||||
"""Flatten nested dictionary into dot-notation keys."""
|
||||
items = []
|
||||
@@ -119,9 +62,9 @@ class CompactTranslationExtractor:
|
||||
def get_untranslated_entries(self, language: str) -> dict:
|
||||
"""Get all untranslated entries for a language in compact format."""
|
||||
lang_dir = self.locales_dir / language
|
||||
try:
|
||||
target_file = self._find_translation_file(lang_dir)
|
||||
except SystemExit:
|
||||
target_file = lang_dir / "translation.toml"
|
||||
|
||||
if not target_file.exists():
|
||||
print(f"Error: Translation file not found for language: {language}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -173,8 +116,7 @@ class CompactTranslationExtractor:
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Extract untranslated entries in compact format for AI translation (supports TOML and JSON)',
|
||||
epilog='Automatically detects and handles both TOML and JSON translation files.'
|
||||
description='Extract untranslated entries in compact format for AI translation (TOML format only)'
|
||||
)
|
||||
parser.add_argument('language', help='Language code (e.g., de-DE, fr-FR)')
|
||||
parser.add_argument('--locales-dir', default='frontend/public/locales', help='Path to locales directory')
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validate JSON/TOML structure and formatting of translation files.
|
||||
Validate TOML structure and formatting of translation files.
|
||||
|
||||
Checks for:
|
||||
- Valid JSON/TOML syntax
|
||||
- Valid TOML syntax
|
||||
- Consistent key structure with en-GB
|
||||
- Missing keys
|
||||
- Extra keys not in en-GB
|
||||
@@ -18,16 +18,7 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Set
|
||||
import argparse
|
||||
|
||||
try:
|
||||
import tomllib # Python 3.11+
|
||||
except ImportError:
|
||||
try:
|
||||
import toml as tomllib_fallback
|
||||
tomllib = None
|
||||
except ImportError:
|
||||
tomllib = None
|
||||
tomllib_fallback = None
|
||||
import tomllib # Python 3.11+ (stdlib)
|
||||
|
||||
|
||||
def get_all_keys(d: dict, parent_key: str = '', sep: str = '.') -> Set[str]:
|
||||
@@ -42,24 +33,11 @@ def get_all_keys(d: dict, parent_key: str = '', sep: str = '.') -> Set[str]:
|
||||
|
||||
|
||||
def validate_translation_file(file_path: Path) -> tuple[bool, str]:
|
||||
"""Validate that a file contains valid JSON or TOML."""
|
||||
"""Validate that a file contains valid TOML."""
|
||||
try:
|
||||
if file_path.suffix == '.toml':
|
||||
if tomllib:
|
||||
with open(file_path, 'rb') as f:
|
||||
tomllib.load(f)
|
||||
elif tomllib_fallback:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
tomllib_fallback.load(f)
|
||||
else:
|
||||
return False, "TOML support not available. Install 'toml' or upgrade to Python 3.11+"
|
||||
return True, "Valid TOML"
|
||||
else: # JSON
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
json.load(f)
|
||||
return True, "Valid JSON"
|
||||
except json.JSONDecodeError as e:
|
||||
return False, f"Invalid JSON at line {e.lineno}, column {e.colno}: {e.msg}"
|
||||
with open(file_path, 'rb') as f:
|
||||
tomllib.load(f)
|
||||
return True, "Valid TOML"
|
||||
except Exception as e:
|
||||
return False, f"Error reading file: {str(e)}"
|
||||
|
||||
@@ -123,24 +101,14 @@ def print_validation_result(result: Dict, verbose: bool = False):
|
||||
|
||||
|
||||
def load_translation_file(file_path: Path) -> dict:
|
||||
"""Load JSON or TOML translation file."""
|
||||
if file_path.suffix == '.toml':
|
||||
if tomllib:
|
||||
with open(file_path, 'rb') as f:
|
||||
return tomllib.load(f)
|
||||
elif tomllib_fallback:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return tomllib_fallback.load(f)
|
||||
else:
|
||||
raise RuntimeError("TOML support not available")
|
||||
else: # JSON
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
"""Load TOML translation file."""
|
||||
with open(file_path, 'rb') as f:
|
||||
return tomllib.load(f)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate translation JSON/TOML structure'
|
||||
description='Validate translation TOML structure'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--language',
|
||||
@@ -162,19 +130,11 @@ def main():
|
||||
|
||||
# Define paths
|
||||
locales_dir = Path('frontend/public/locales')
|
||||
en_gb_path = locales_dir / 'en-GB' / 'translation.toml'
|
||||
file_ext = '.toml'
|
||||
|
||||
# Try TOML first, then JSON
|
||||
en_gb_toml = locales_dir / 'en-GB' / 'translation.toml'
|
||||
en_gb_json = locales_dir / 'en-GB' / 'translation.json'
|
||||
|
||||
if en_gb_toml.exists():
|
||||
en_gb_path = en_gb_toml
|
||||
file_ext = '.toml'
|
||||
elif en_gb_json.exists():
|
||||
en_gb_path = en_gb_json
|
||||
file_ext = '.json'
|
||||
else:
|
||||
print(f"❌ Error: en-GB translation file not found at {en_gb_toml} or {en_gb_json}")
|
||||
if not en_gb_path.exists():
|
||||
print(f"❌ Error: en-GB translation file not found at {en_gb_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Validate en-GB itself
|
||||
@@ -196,7 +156,7 @@ def main():
|
||||
languages = []
|
||||
for d in locales_dir.iterdir():
|
||||
if d.is_dir() and d.name != 'en-GB':
|
||||
if (d / f'translation{file_ext}').exists():
|
||||
if (d / 'translation.toml').exists():
|
||||
languages.append(d.name)
|
||||
|
||||
results = []
|
||||
@@ -204,10 +164,10 @@ def main():
|
||||
|
||||
# Validate each language
|
||||
for lang_code in sorted(languages):
|
||||
lang_path = locales_dir / lang_code / f'translation{file_ext}'
|
||||
lang_path = locales_dir / lang_code / 'translation.toml'
|
||||
|
||||
if not lang_path.exists():
|
||||
print(f"⚠️ Warning: {lang_code}/translation{file_ext} not found, skipping")
|
||||
print(f"⚠️ Warning: {lang_code}/translation.toml not found, skipping")
|
||||
continue
|
||||
|
||||
# First check if file is valid
|
||||
|
||||
@@ -15,16 +15,7 @@ import sys
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Set, Tuple
|
||||
import argparse
|
||||
|
||||
try:
|
||||
import tomllib # Python 3.11+
|
||||
except ImportError:
|
||||
try:
|
||||
import toml as tomllib_fallback
|
||||
tomllib = None
|
||||
except ImportError:
|
||||
tomllib = None
|
||||
tomllib_fallback = None
|
||||
import tomllib # Python 3.11+ (stdlib)
|
||||
|
||||
|
||||
def find_placeholders(text: str) -> Set[str]:
|
||||
@@ -127,35 +118,16 @@ def main():
|
||||
|
||||
# Define paths
|
||||
locales_dir = Path('frontend/public/locales')
|
||||
en_gb_path = locales_dir / 'en-GB' / 'translation.toml'
|
||||
file_ext = '.toml'
|
||||
|
||||
# Try TOML first, then JSON
|
||||
en_gb_toml = locales_dir / 'en-GB' / 'translation.toml'
|
||||
en_gb_json = locales_dir / 'en-GB' / 'translation.json'
|
||||
|
||||
if en_gb_toml.exists():
|
||||
en_gb_path = en_gb_toml
|
||||
file_ext = '.toml'
|
||||
elif en_gb_json.exists():
|
||||
en_gb_path = en_gb_json
|
||||
file_ext = '.json'
|
||||
else:
|
||||
print(f"❌ Error: en-GB translation file not found at {en_gb_toml} or {en_gb_json}")
|
||||
if not en_gb_path.exists():
|
||||
print(f"❌ Error: en-GB translation file not found at {en_gb_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# Load en-GB (source of truth)
|
||||
if file_ext == '.toml':
|
||||
if tomllib:
|
||||
with open(en_gb_path, 'rb') as f:
|
||||
en_gb = tomllib.load(f)
|
||||
elif tomllib_fallback:
|
||||
with open(en_gb_path, 'r', encoding='utf-8') as f:
|
||||
en_gb = tomllib_fallback.load(f)
|
||||
else:
|
||||
print("❌ Error: TOML support not available. Install 'toml' or upgrade to Python 3.11+")
|
||||
sys.exit(1)
|
||||
else:
|
||||
with open(en_gb_path, 'r', encoding='utf-8') as f:
|
||||
en_gb = json.load(f)
|
||||
with open(en_gb_path, 'rb') as f:
|
||||
en_gb = tomllib.load(f)
|
||||
|
||||
en_gb_flat = flatten_dict(en_gb)
|
||||
|
||||
@@ -167,33 +139,22 @@ def main():
|
||||
languages = []
|
||||
for d in locales_dir.iterdir():
|
||||
if d.is_dir() and d.name != 'en-GB':
|
||||
if (d / f'translation{file_ext}').exists():
|
||||
if (d / 'translation.toml').exists():
|
||||
languages.append(d.name)
|
||||
|
||||
all_issues = []
|
||||
|
||||
# Validate each language
|
||||
for lang_code in sorted(languages):
|
||||
lang_path = locales_dir / lang_code / f'translation{file_ext}'
|
||||
lang_path = locales_dir / lang_code / 'translation.toml'
|
||||
|
||||
if not lang_path.exists():
|
||||
print(f"⚠️ Warning: {lang_code}/translation{file_ext} not found, skipping")
|
||||
print(f"⚠️ Warning: {lang_code}/translation.toml not found, skipping")
|
||||
continue
|
||||
|
||||
# Load language file
|
||||
if file_ext == '.toml':
|
||||
if tomllib:
|
||||
with open(lang_path, 'rb') as f:
|
||||
lang_data = tomllib.load(f)
|
||||
elif tomllib_fallback:
|
||||
with open(lang_path, 'r', encoding='utf-8') as f:
|
||||
lang_data = tomllib_fallback.load(f)
|
||||
else:
|
||||
print(f"⚠️ Warning: Cannot read TOML file {lang_path}, skipping")
|
||||
continue
|
||||
else:
|
||||
with open(lang_path, 'r', encoding='utf-8') as f:
|
||||
lang_data = json.load(f)
|
||||
with open(lang_path, 'rb') as f:
|
||||
lang_data = tomllib.load(f)
|
||||
|
||||
lang_flat = flatten_dict(lang_data)
|
||||
issues = validate_language(en_gb_flat, lang_flat, lang_code)
|
||||
|
||||
Reference in New Issue
Block a user