Sleep-timer duration strings (durationHoursMinutesSeconds, durationMinutesSeconds, durationMinutesOnly, durationSecondsOnly) contained literal "?" characters instead of the native hour/minute/second abbreviation in 6 locales. Replaced with correct native short-form units per locale, verified byte-exact against a pinned spec table and against the already-correct neighboring hoursLabel/minutesLabel/secondsLabel values in each file.
5.2 KiB
Design: Repair corrupted sleep-timer duration abbreviations in 6 ARB locales
Technical Approach
Content-only data repair. For each of the 6 corrupted ARB files (app_{ar,bn,hi,ja,ru,zh}.arb), replace the value of exactly 4 duration keys (durationHoursMinutesSeconds, durationMinutesSeconds, durationMinutesOnly, durationSecondsOnly, physical lines 19/27/34/40) with the byte-exact pinned strings from the spec table. No Dart, no CI, no generated-file changes. Validation is static-only (tooling hangs here). Maps directly to the proposal's "Approach" and the spec's four requirements.
Architecture Decisions
Decision: Full-value line replacement (not ?-glyph swap)
| Option | Tradeoff | Decision |
|---|---|---|
Swap only ? → abbrev in place |
Fails for ja/zh: pinned strings also collapse inter-unit spaces ({hours}?? {minutes}? {seconds}? → {hours}時間{minutes}分{seconds}秒), so a glyph-only swap yields wrong output |
Rejected |
| Replace the entire quoted value on each target line | Spec pins the exact final string per cell; one deterministic edit per line, no per-locale spacing logic | Chosen |
Rationale: The spec table is the single source of truth. The edit unit is the whole line "<key>": "<value>",; old_string = current corrupted line, new_string = pinned line. Surrounding bytes (and the other ~386 keys) are never touched.
Decision: Surgical single-line edits, no JSON re-serialization
| Option | Tradeoff | Decision |
|---|---|---|
| Parse → mutate → re-dump JSON | Reflows/re-escapes all ~386 keys, reorders/renormalizes unicode, huge noisy diff, risk to untouched cells | Rejected |
| Exact-string edit of only the 4 target lines/file | Diff is exactly 4 lines/file (24 total); all other bytes provably identical | Chosen |
Decision: Static validation instead of flutter/dart tooling
flutter build, gen-l10n, dart analyze, flutter analyze all hang in this environment and MUST NOT run. A short Python one-off (stdlib json, byte reads) gives equivalent safety for a content-only change: JSON well-formedness, UTF-8 integrity, diff scope, residual-?, and placeholder integrity.
Data Flow
spec table (24 pinned cells)
│ authoritative source
▼
Edit tool ──(4 exact-string line edits/file)──▶ app_{locale}.arb ×6
│
▼
static validator (Python + git diff)
│ pass/fail gate
▼
single commit (6 files)
File Changes
| File | Action | Description |
|---|---|---|
lib/l10n/app_ar.arb |
Modify | Lines 19/27/34/40: values → pinned ar cells |
lib/l10n/app_bn.arb |
Modify | Lines 19/27/34/40: values → pinned bn cells |
lib/l10n/app_hi.arb |
Modify | Lines 19/27/34/40: values → pinned hi cells |
lib/l10n/app_ja.arb |
Modify | Lines 19/27/34/40: values → pinned ja cells (spaces collapse) |
lib/l10n/app_ru.arb |
Modify | Lines 19/27/34/40: values → pinned ru cells |
lib/l10n/app_zh.arb |
Modify | Lines 19/27/34/40: values → pinned zh cells (spaces collapse) |
Explicitly out of scope (unchanged): lib/l10n/gen/app_localizations.dart (generated; do NOT hand-edit — ICU placeholder shape {hours}/{minutes}/{seconds} is identical pre/post, so no regeneration is needed for these keys to render correctly), app_localizations_ext.dart, any CI/workflow file, and the @durationXxx.placeholders metadata blocks.
Interfaces / Contracts
No interface change. ICU placeholder names, casing, and count are invariant: {hours}, {minutes}, {seconds}. The generated AppLocalizations method signatures are unaffected.
Testing Strategy
| Layer | What to Test | Approach |
|---|---|---|
| Static | All 6 files parse as JSON | Python json.load each file |
| Static | UTF-8 integrity, no replacement char | Read bytes, decode('utf-8') strict; assert no � |
| Static | Diff scope = only the 4 lines/file | git diff --stat / per-line diff; assert exactly 24 changed lines total, additions==deletions |
| Static | Zero literal ? in 24 target values |
Assert '?' not in value for each target key |
| Static | Placeholder integrity | Regex-extract {...} tokens from each fixed value; assert equals expected set/order per key; @-metadata blocks byte-unchanged |
| Static | Byte-exact match to spec | Assert each fixed value == its pinned spec cell |
git diff --check additionally guards against whitespace/EOL damage. No runtime/widget test (no code path changes; tooling forbidden).
Threat Matrix
N/A — no routing, shell, subprocess, VCS/PR automation, executable-file classification, or process-integration boundary. Pure ARB content edit.
Migration / Rollout
No migration. Single commit touching only 6 ARB files; git revert fully restores prior state with zero cross-dependencies.
Open Questions
- None — the spec pins all 24 target cells byte-exactly; no content or architectural decision remains.