LocaleSync follows a layered architecture with strict separation of concerns. Each layer has a clear responsibility and communicates with adjacent layers through well-defined contracts.
┌──────────────────────────────────────────────┐
│ CLI Layer │
│ (Typer commands, interactive prompts, I/O) │
├──────────────────────────────────────────────┤
│ Application Layer │
│ (Use cases: scan, check, sync orchestration)│
├──────────────────────────────────────────────┤
│ Domain Layer │
│ (Models, contracts, policies, comparator, │
│ placeholder handling, exceptions) │
├──────────────────────────────────────────────┤
│ Infrastructure Layer │
│ (File discovery, JSON parsing/writing, │
│ translators, reporters) │
└──────────────────────────────────────────────┘
The core of the system. Contains:
- Models (
models.py) - Immutable value objects:LocaleKey,LocaleData,LocaleFile,SyncResult,CheckResult, etc. - Contracts (
contracts.py) - PythonProtocolinterfaces forLocaleParser,LocaleWriter,TranslationProvider,FileDiscoverer,Reporter - Comparator (
comparator.py) - Key comparison logic: find missing, extra, and empty keys - Placeholder (
placeholder.py) - Placeholder detection, protection, restoration, and validation - Policies (
policies.py) - Update strategy logic (fill_missing,fill_empty,force_overwrite) - Exceptions (
exceptions.py) - Typed exception hierarchy
Key principle: The domain layer has zero external dependencies. It defines contracts but never imports infrastructure.
Orchestrates domain logic and infrastructure to fulfill use cases:
- ScanUseCase - Discovers files via
FileDiscoverer, returnsScanResult - CheckUseCase - Parses files, compares keys, returns
CheckResult - SyncUseCase - The core sync engine. Handles both
syncandtranslatecommands by accepting an optionalTranslationProvider - DashboardUseCase - Collects all translation data (stats, validation, entries, namespaces) into a
DashboardDatamodel for dashboard rendering
Key principle: Use cases compose dependencies injected at construction time. They don't create infrastructure objects directly.
Concrete implementations of domain contracts:
- Discovery -
JsonLocaleDiscovererfinds.jsonlocale files with locale code validation, auto-discovers locale directories when the given path doesn't match - Parsers -
JsonLocaleParserreads JSON files intoLocaleData - Writers -
JsonLocaleWriterwith atomic writes (temp file + rename), backups, smart key ordering - Translators -
GoogleTranslator(Google Translate via deep-translator),DemoTranslator(offline dictionary),NoopTranslator(baseline),PlaceholderAwareTranslator(decorator) - Reporters -
ConsoleReporter(Rich-formatted),JsonReporter(machine-readable),HtmlReporter(self-contained HTML dashboard) - Importers -
JsonImporter,CsvImporterwith schema-backed validation - Exporters -
JsonExporter,CsvExporterwith configurable formatting
Thin layer that maps CLI arguments to use case invocations:
- Commands -
scan,check,sync,translate,dashboard(each in its own module) - Formatters - Shared terminal output helpers
- Interactive - Isolated interactive prompting (not mixed into domain logic)
We use Python's Protocol (structural subtyping) instead of ABC inheritance. This allows implementations to satisfy contracts without importing or inheriting from them, keeping the dependency graph clean.
Domain models use frozen=True dataclasses where possible. LocaleData uses value-returning mutation methods (with_entry(), with_entries()) instead of in-place mutation.
Update behavior is modeled as an enum-backed UpdatePolicy rather than scattered if/else conditionals. Adding a new strategy is a single enum value + match case.
PlaceholderAwareTranslator wraps any TranslationProvider with automatic placeholder protection. This separates placeholder handling from translation logic.
The JSON writer uses temp-file-then-rename for atomicity. A crash mid-write won't corrupt the original file.
JSON output uses automatic key ordering by default: files with ≤200 keys are sorted for stable diffs, larger files preserve insertion order to avoid noisy re-sorts. Explicit --sort-keys / --no-sort-keys flags override this.
When the given directory doesn't contain locale files, LocaleSync searches subdirectories automatically. Files are recognized as locale files only if their name matches a valid locale code pattern (e.g., en.json, pl.json). Non-locale JSON like package.json or tsconfig.json is ignored.
| Extension | How to Add |
|---|---|
| New file format (YAML, PO) | Implement LocaleParser and LocaleWriter protocols |
| New translation provider | Implement TranslationProvider protocol |
| New report format | Implement Reporter protocol |
| New discovery strategy | Implement FileDiscoverer protocol |
| Config file support | Add a configuration loader that produces SyncConfig |
| Plugin system | Add a registry that discovers and loads protocol implementations |
LocaleSync is a Python tool, but its architecture makes no assumptions about the surrounding project. The core operates on a directory of locale files - it does not know or care whether it is running inside a Python, Angular, Node.js, or Go repository.
This is by design:
- Domain layer operates on
LocaleDataandLocaleKeyabstractions, not file system paths. - Infrastructure layer takes a directory
Pathand discovers locale files generically. - CLI layer accepts paths as arguments - any directory works.
- No project-type detection. The tool does not look for
package.json,angular.json,pyproject.toml, or any project-specific files.
This generic design means LocaleSync can be:
- Installed as a Python package and invoked directly
- Called from npm scripts in Angular/Node projects
- Used in CI pipelines for any project type
- Embedded in Makefiles, shell scripts, or any automation
See docs/integration.md for detailed cross-project integration guidance.
CLI → Application → Domain ← Infrastructure
- CLI depends on Application and Infrastructure (for wiring)
- Application depends on Domain contracts
- Infrastructure implements Domain contracts
- Domain depends on nothing external