All notable changes to erli18n will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning 2.0.0.
Versioning policy
Per SemVer 2.0.0 §4, this project is in the 0.x.y initial-development phase:
0.x.y→0.x.y+1(patch): backward-compatible bug fixes only.0.x.y→0.x+1.0(minor): may introduce backward-incompatible changes, announced in advance via CHANGELOG. Additive changes (new functions, new arities, new opt-in flags, new telemetry events) are the norm.- Telemetry events are versioned per the schema policy documented in the
erli18n_telemetrymodule-moduledoc; events marked@stablecannot change schema within0.xseries, events marked@unstablemay.
Criteria for 1.0.0
The 1.0.0 release commits to API stability. Tag bumps to 1.0.0 only when all of the following hold:
- At least one external project uses
erli18nin production for ≥ 6 months without reporting breaking issues. - The Post-0.1.0 Roadmap items that affect public API surface (charset support, hot upgrade behavior, async load) are either implemented or formally rejected with rationale.
- Parity SUITE (
erli18n_parity_SUITE) passes end-to-end against the real GNUgettext/ngettextCLI (gettext-tools≥ 0.21) as oracle (currently 6 scenarios; target ≥ 20 covering the full PSD-001…009 semantics matrix). - No unfixed
@unstabletelemetry events remain — all events either promoted to@stableor removed. - CHANGELOG documents zero behavioral changes for at least 2 consecutive minor releases.
[Unreleased]
No unreleased changes.
[0.1.0] — 2026-06-14
Initial development release. The public API is functional but subject to backward-incompatible
changes on minor bumps per the 0.x SemVer policy.
Requires OTP 27 or newer. The public modules carry native -doc / -moduledoc
documentation attributes (EEP-59), which only compile on OTP 27+; OTP 25.3 and 26 reject
them at compile time with attribute doc after function definitions.
Added
- Core OTP application:
erli18n_app,erli18n_sup(intensity{5, 10}hardcoded per AMB-002). erli18n_server— genserver + ETS catalog store with anti-bottleneck pattern (hot path `lookup*is lock-free direct ETS from caller process; writes serialized throughprotected` table owner).erli18n_po— hand-written recursive-descent parser for GNU gettext.poformat. Honors PSDs 001-009:- PSD-001: fuzzy entries dropped by default; opt-in via
#{include_fuzzy => true}. - PSD-002: charset support restricted to UTF-8, Latin-1, US-ASCII (native to
unicode:characters_to_binary/3). - PSD-003: empty
msgstrpreserved; fallback-to-msgid handled at lookup. - PSD-004: header
Plural-Formsis runtime source of truth; CLDR consulted at load only for divergence warning. - PSD-005: BOM UTF-8 stripped silently.
- PSD-006: msgctxt stored as a separate ETS key field, matching how GNU gettext keys contextual entries (
msgctxt+EOT+msgid). - PSD-007: obsolete
#~entries skipped. - PSD-008: degenerate plural (
nplurals=1) accepted. - PSD-009:
npluralsmismatch rejected with structured error.
- PSD-001: fuzzy entries dropped by default; opt-in via
erli18n_plural— recursive-descent C-expression evaluator forPlural-Formsheader. CLDR data inlined for 49 locales. Bignum-clean.erli18n_server:ensure_loaded/3,4andreload/3,4— atomic catalog load (parse → compile plural → validate vs CLDR → insert), with idempotency fast-path (RISK-012 mitigation).erli18n(façade) — full GNU gettext C-macro API surface:gettextfamily (singular),ngettextfamily (plural),pgettextfamily (contextual),npgettextfamily (contextual + plural), withd/dcaliases. Per-process locale via process dictionary; application-wide defaults viaapplication:get_env/2.erli18n_telemetry— 7:telemetryevents as first-class observability concern (catalog load/reload/unload spans; lookup miss/fuzzy_skip opt-in; plural divergence warning; memory warning rate-limited).telemetrydeclared as optional dep viaoptional_applications(OTP 24+).- Test suite: 289 Common Test cases, green on OTP 27 and 28 — façade API, gen_server / catalog,
.poparser, plural evaluator, loader, and telemetry suites, plus PropEr properties (200 runs each) and fuzz scenarios (100–500 runs each). 6 of these are parity scenarios run against the real GNUgettext/ngettextCLI oracle; that suite skips cleanly whengettext-toolsor thept_BR.UTF-8/ru_RU.UTF-8locales are absent. - Coverage: 100% of behaviorally reachable lines. Dead defensive code removed (no silent fallbacks for invariant violations — crashes are explicit via
function_clause/case_clause/badmatch). - Apache 2.0 license.
- GitHub Actions CI (
.github/workflows/ci.yml) — three jobs on pinnedubuntu-24.04runners:lint(fast quality gate on OTP 28),test(Common Test + coverage across OTP 27 and 28, withgettextinstalled and thept_BR.UTF-8/ru_RU.UTF-8locales generated soerli18n_parity_SUITEexercises the oracle path),dialyzer(isolated job with PLT cache). CI runs automatically only onmain; every other branch runs on demand viaworkflow_dispatch. Concurrency cancellation per ref, least-privilegecontents: readtoken, rebar3 build cache keyed per OTP. - Local CI emulation via
actand a custom runner image (Dockerfile.act-runner): extendsghcr.io/catthehacker/ubuntu:full-24.04with ELP2026-02-27(SHA256-verified per SLSA v0.2). Reuses the workflow YAML unchanged — GitHub-hosted runners gracefully[SKIP]the ELP steps in real CI. Bootstrap is declarative incompose.yml(act-toolcachevolume init + image build).actionlint 1.7.12pinned viamise.tomlfor static workflow analysis. - Repo hygiene:
README.md(with usage / install / compatibility / dev sections),CONTRIBUTING.md,SECURITY.md,CODE_OF_CONDUCT.md(Contributor Covenant 3.0),.editorconfig.
Architecture decisions
The design rationale is captured inline in the source: PO-semantics decisions
(PSD-001…PSD-009), risk mitigations (RISK-*), and ambiguity resolutions
(AMB-*) are referenced from the relevant module -moduledoc / -doc attributes
and code comments. The internal planning corpus that originally tracked them is not
part of the published package.