LocaleSync is designed for extension without modification. All major components are behind Protocol-based contracts that new implementations can satisfy.
LocaleSync ships with three translation providers:
| Provider | Default? | API Key? | Languages | Use Case |
|---|---|---|---|---|
| GoogleTranslator | ✅ Yes | No | 100+ | Production use |
| DemoTranslator | No | No | 5 (pl, de, fr, es, it) | Offline/testing |
| NoopTranslator | No | No | — | Baseline (returns source text) |
Select a provider with --provider:
locale-sync translate ./locales # Google Translate (default)
locale-sync translate ./locales --provider demo # Offline dictionaryImplement the TranslationProvider protocol:
# my_provider.py
class DeepLTranslator:
"""DeepL translation provider."""
def __init__(self, api_key: str) -> None:
self._api_key = api_key
@property
def name(self) -> str:
return "deepl"
def translate(self, text: str, source_lang: str, target_lang: str) -> str:
# Call DeepL API here
import deepl
translator = deepl.Translator(self._api_key)
result = translator.translate_text(text, source_lang=source_lang, target_lang=target_lang)
return result.textWrap it with PlaceholderAwareTranslator for automatic placeholder safety:
from locale_sync.domain.placeholder import PlaceholderManager
from locale_sync.infrastructure.translators.placeholder_aware import PlaceholderAwareTranslator
base = DeepLTranslator(api_key="...")
safe = PlaceholderAwareTranslator(base, PlaceholderManager())Then inject into the SyncUseCase:
from locale_sync.application.sync_use_case import SyncUseCase
use_case = SyncUseCase(parser=..., writer=..., translator=safe)Implement LocaleParser and LocaleWriter protocols:
class YamlLocaleParser:
def parse(self, path: Path) -> LocaleData:
import yaml
with open(path) as f:
data = yaml.safe_load(f)
return LocaleData.from_nested_dict(data)
def supports(self, path: Path) -> bool:
return path.suffix.lower() in (".yaml", ".yml")
class YamlLocaleWriter:
def write(self, path: Path, data: LocaleData, *, sort_keys=False, indent=2, create_backup=False):
import yaml
nested = data.to_nested_dict(sort_keys=sort_keys)
with open(path, "w") as f:
yaml.dump(nested, f, allow_unicode=True, default_flow_style=False)
return NoneImplement the Reporter protocol:
class CsvReporter:
def report_scan(self, result: ScanResult) -> str:
lines = ["file,locale_code"]
for f in result.locale_files:
lines.append(f"{f.filename},{f.locale_code}")
return "\n".join(lines)
def report_check(self, result: CheckResult) -> str: ...
def report_sync(self, result: SyncResult) -> str: ...class RecursiveMultiFormatDiscoverer:
def discover(self, directory: Path) -> list[LocaleFile]:
# Discover .json, .yaml, .po files recursively
...- Config file (
.localesync.toml) — Load default options - Plugin registry — Auto-discover providers via entry points
- GitHub Actions integration — PR comment generation with check results
- Include/exclude rules — Filter by key patterns or file patterns
- Namespace filtering — Sync only specific key prefixes
- Custom serialization — Control JSON formatting, trailing commas, comments