Files
ccn/Packages/Localization
javier ea00b94841 Tweaks and fixes throughout the project (#27)
This PR contains the work done to do a little bit of housekeeping pass across all packages and the Website service.

To provide further details about the work:
* Refreshed the READMEs and source documentation to match the current code;
* Tagged every test case consistently across the Infrastructure, Localization, Persistence, and Website test targets;
* Removed Website middleware tests now covered by Infrastructure's own suite;
* Conformed the `PrepareDB` method to Sendable;
* Relaxes the production Compose DATABASE_TLS default from require to prefer;
* Added Persistence test verifying the prefer posture falls back to plaintext connections.

Reviewed-on: rock-n-code/loud-amsterdam#27
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
2026-07-30 06:33:57 +00:00
..

Localization

The server-side localization toolkit the Loud services build on: locale-explicit String Catalog lookups, Accept-Language negotiation, and the catalog-derived language list — with no dependencies beyond Foundation.

Overview

The package provides, grouped by role:

Role Types
Lookup Localize, a bundle-bound localizer that resolves a catalog key for an explicit locale
Negotiation Negotiate, which picks the best supported language from an Accept-Language header per RFC 9110
Languages LanguageList, the supported and default languages a bundle's String Catalog defines
Diagnostics CatalogState, the outcome of reading the catalog (loaded, missing, or undecodable)

Design rules

  • The String Catalog is the single source of truth. Supported languages, the default language, and every string come from the bundle's Localizable.xcstrings. Adding a language is a translation-only change — once a locale exists in the catalog, LanguageList and Negotiate pick it up with no code change.
  • The locale is always explicit. A server has no single "current" locale, so every lookup names the locale to resolve in; nothing reads process-wide locale state.
  • Raw .xcstrings parsing, for Linux parity. The catalog is decoded from its JSON rather than through Foundation's compiled-catalog APIs, which are unavailable or non-functional on Linux. Consumers must .copy the catalog resource verbatim (not .process it) so it ships as raw JSON on every platform. Only simple stringUnit values are decoded; plural and device variations are not represented.
  • Resolution never fails. A missing entry falls back to the source-language string, then to the key itself; a missing or undecodable catalog degrades the language list to the default. Check catalogState once at startup and warn when it is not .loaded, before visitors ever see raw keys.
  • Method structs. Localize and Negotiate hold their lifetime-fixed configuration (the bundle) in init and take only per-call inputs in callAsFunction.
  • One decoded catalog per bundle. Catalogs are immutable at runtime, so every Localize, Negotiate, and LanguageList bound to the same bundle and table shares one cached StringCatalog.

Layout

Sources are split by visibility, then by kind, one type per file:

Sources/
├── Public/
│   ├── Enumerations/      CatalogState
│   ├── Methods/           Localize, Negotiate
│   └── Types/             LanguageList
└── Internal/
    ├── Protocols/         CatalogResolving, the seam between the public API and the catalog backend
    └── Types/             StringCatalog (the cached .xcstrings decoder), LanguageRange
Tests/
├── Cases/                 the test suites, mirroring the Sources/ layout
├── Catalogs/              the String Catalog fixture, copied verbatim so it loads on Linux
└── Utils/                 the StubCatalog resolver and the suite Tag constants

Testing

Every suite carries a tag naming the kind of API it exercises — .method or .type, declared in Tests/Utils/Extensions/Tag+Constants.swift — so test plans and result summaries can slice the run by kind. A new suite must adopt the tag matching its subject (or add a tag there if none fits).

Requirements

  • Swift 6.3 toolchain (swift-tools-version:6.3).
  • macOS 15, matching the sibling Infrastructure and Persistence packages (the services deploy to Linux containers; the packages carry no UI platforms).
  • No package dependencies — Foundation and Synchronization only.