Skip to content

Latest commit

 

History

History
139 lines (96 loc) · 7.48 KB

File metadata and controls

139 lines (96 loc) · 7.48 KB

Architecture

Overview

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)                    │
└──────────────────────────────────────────────┘

Layers

Domain Layer (domain/)

The core of the system. Contains:

  • Models (models.py) - Immutable value objects: LocaleKey, LocaleData, LocaleFile, SyncResult, CheckResult, etc.
  • Contracts (contracts.py) - Python Protocol interfaces for LocaleParser, 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.

Application Layer (application/)

Orchestrates domain logic and infrastructure to fulfill use cases:

  • ScanUseCase - Discovers files via FileDiscoverer, returns ScanResult
  • CheckUseCase - Parses files, compares keys, returns CheckResult
  • SyncUseCase - The core sync engine. Handles both sync and translate commands by accepting an optional TranslationProvider
  • DashboardUseCase - Collects all translation data (stats, validation, entries, namespaces) into a DashboardData model for dashboard rendering

Key principle: Use cases compose dependencies injected at construction time. They don't create infrastructure objects directly.

Infrastructure Layer (infrastructure/)

Concrete implementations of domain contracts:

  • Discovery - JsonLocaleDiscoverer finds .json locale files with locale code validation, auto-discovers locale directories when the given path doesn't match
  • Parsers - JsonLocaleParser reads JSON files into LocaleData
  • Writers - JsonLocaleWriter with 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, CsvImporter with schema-backed validation
  • Exporters - JsonExporter, CsvExporter with configurable formatting

CLI Layer (cli/)

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)

Key Design Decisions

1. Protocol-based Contracts

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.

2. Immutable Domain Models

Domain models use frozen=True dataclasses where possible. LocaleData uses value-returning mutation methods (with_entry(), with_entries()) instead of in-place mutation.

3. Strategy Pattern for Update Policies

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.

4. Decorator Pattern for Placeholder Safety

PlaceholderAwareTranslator wraps any TranslationProvider with automatic placeholder protection. This separates placeholder handling from translation logic.

5. Atomic File Writes

The JSON writer uses temp-file-then-rename for atomicity. A crash mid-write won't corrupt the original file.

6. Smart Key Ordering

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.

7. Auto-Discovery

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 Points

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

Cross-Project Design

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 LocaleData and LocaleKey abstractions, not file system paths.
  • Infrastructure layer takes a directory Path and 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.

Dependency Flow

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