Skip to content

Latest commit

 

History

History
95 lines (81 loc) · 6.19 KB

File metadata and controls

95 lines (81 loc) · 6.19 KB

i18n status: IN-SCOPE (bilingual EN/ES)

habitable is an in-scope internationalization repo under INTERNATIONALIZATION-STANDARD.md. The tool ships a bilingual English + Spanish UI because a habitability-evidence tool a Spanish-speaking tenant cannot operate has failed at its purpose; bilingual reach is a project invariant, not a nice-to-have. This document records which mechanical AUTO-GATES (§4) are live, which are N/A-until-used, and which are deferred to a later phase, so the i18n posture is a declared decision rather than a silent state.

For translator-facing guidance (how to add a language, how to write copy, which strings must never be softened) see localization-guide.md.

Catalog format

  • Format: per-language flat JSON bundles under app/i18n/en.json (the source of truth) and es.json. Every key originates in en.json; each other locale mirrors its key set exactly. The app (app/) is plain HTML/CSS/JS with no build step; app/app.js fetches i18n/<lang>.json and substitutes each element's data-i18n / data-i18n-aria attribute at runtime.
  • Authored language tags: en, es — the bundle filenames, the SUPPORTED array and DEFAULT_LANG in app/app.js, <html lang>, and the data-lang buttons. Both are validated as BCP 47 by G3 (below).
  • Why JSON, not gettext .po: per the standard's §11 roadmap habitable's task is to wire the gates on the existing JSON catalog, not migrate to gettext. The web layer stays JSON; a .po layer would only be added if/when Python-side user-facing strings appear (see [I18N-MIGRATION-PLAN decision 2] upstream).

Mechanical AUTO-GATES (§4) — status

All live gates run in make verify (the i18n target) and in .github/workflows/i18n.yml, byte-for-byte identical so local == CI. The checkers are standard-library only and offline, so CI needs no dependency install.

# Gate Status Where
G1 UTF-8 encoding of all tracked text files LIVE scripts/check_i18n_utf8.py
G2 No hardcoded UI strings (extraction) DEFERRED (later phase)
G3 BCP 47 tag validity (well-formed + registered) LIVE scripts/check_bcp47.py
G4 HTML root lang (WCAG 3.1.1) — html-has-lang + html-lang-valid LIVE (pre-existing) axe-core scan tests/test_app_axe.py; structural tests/test_app_accessibility.py; reinforced offline by G3
G5 Completeness + placeholder + plural parity LIVE scripts/check_i18n_parity.py (extended by FIX-12 to enforce plural + placeholder parity, not just key parity)
G6 EN/ES key-parity LIVE (pre-existing) scripts/check_i18n_parity.py; tests/test_app_i18n.py
G7 PO compilation (msgfmt) N/A — no gettext .po catalogs
G8 XLIFF schema validity N/A — no .xlf files committed
G9 Pseudolocale overflow DEFERRED (frontend-depth phase)
G10 RTL: no physical-direction CSS PARTIAL (RTL-readiness pass, R-48): CSS uses only logical properties (*-inline-*, text-align:start); dir is set on <html> and flipped per active language (RTL_LANGS); dates/numbers already format via Intl keyed to the locale. No RTL bundle ships yet, so a native-speaker visual QA is still required before one does. tests/test_app_i18n.py (physical-direction + dir guards)
G11 Vary: Accept-Language N/A — local-first app; the UI is served from the device, not language-negotiated
G12 CLDR/tzdata freshness N/A by design — see below and ADR 0005

G4 — already enforced (noted, not added)

The HTML root-lang gate is pre-existing and merge-blocking, enforced three ways: the axe-core browser scan (tests/test_app_axe.py) runs the default axe ruleset, which includes html-has-lang and html-lang-valid (WCAG 3.1.1) and blocks on any moderate/serious/critical violation in both the EN and ES renderings; the always-on structural test (tests/test_app_accessibility.py::test_language_title_viewport_manifest) asserts <html> carries a lang; and the new G3 gate additionally validates every committed <html lang> value offline in make verify, independent of the browser. This graduation from advisory is tracked in ACCESSIBILITY-STANDARD §1.

G12 — N/A by design (hand-rolled tables, no CLDR/ICU dependency)

Updated 2026-07-05 — the reason below superseded the original "no locale-aware formatting" rationale, which FIX-12 invalidated (habitable now does format numbers and dates per locale). See docs/adr/0005-i18n-g12-cldr-na-by-design.md for the full decision; summary: i18n.py's format_number/format_date use a small hand-rolled EN/ES separator and month-name table, not babel.numbers / babel.dates / PyICU or any CLDR/ICU data source, so there is no CLDR/ICU dependency for G12 to pin a floor on. G12 pins a CLDR/ICU floor (babel>=2.16, CLDR/ICU ≥ 48.2, tzdata ≥ 2026a) only for repos that render CLDR-formatted numbers/dates; habitable deliberately isn't one, today. If a third locale or richer plural-category needs push the hand-rolled tables past their limit (ADR 0005's revisit trigger), this gate becomes live: add the pinned dependency to pyproject.toml, assert the floor, and adopt the CLDR upgrade cadence below.

CLDR upgrade cadence (when G12 goes live): bump at minimum once per major CLDR release cycle; Renovate opens the bump PR (digest-pinned actions, minimumReleaseAge per SECURITY-AND-SUPPLY-CHAIN-STANDARD) and the i18n gates prove the upgrade is non-breaking.

Not covered here (by design)

  • G2 no-hardcoded-strings extraction is deliberately deferred to a later phase — it is the string-inventory forcing gate and is scoped separately from these mechanical gates.
  • Review-gates R1–R7 (§5: lang-of-parts, RTL QA, translation workflow, locale-acceptance, negotiation correctness, civic-obligations, equitable-quality) are human-judgment artifacts, tracked per release, not part of this mechanical-gate wiring.