# Exploration: i18n locale audit (13 ARB locales) ## Current State l10n.yaml: arb-dir=lib/l10n, template-arb-file=app_es.arb, output-dir=lib/l10n/gen, output-class=AppLocalizations. 13 ARB files present: app_{ar,bn,de,en,es,fr,hi,id,it,ja,pt,ru,zh}.arb. lib/l10n/gen/app_localizations.dart (generated, checked into git, not gitignored) declares supportedLocales with the same 13 Locale() entries in the same order. lib/app.dart:83 wires `supportedLocales: AppLocalizations.supportedLocales` into MaterialApp — inventory matches exactly, no drift. A second, hand-maintained translation source exists outside the ARB pipeline: lib/l10n/app_localizations_ext.dart (PluriAppLocalizationsExt) — per-locale Map for weekdayLong/monthName/dateTimeSentence, covering all 13 locale codes with `values['en']` fallback if a code is missing. ## Key Parity (template = app_es.arb) Verified via `grep -c '^ "[a-zA-Z][a-zA-Z0-9]*":'` (excludes "@key" ICU metadata and "@@locale") on all 13 files: every single file — es included — has exactly 387 top-level translation keys. No missing keys, no extra/orphaned keys, no empty values found in sampled content. Previously-flagged line-count mismatch (es=637 lines vs all others=674 lines, per prior audit note) — RE-VERIFIED, confirmed as a FALSE ALARM / pure JSON-formatting difference, NOT a key/content defect. Root cause: app_es.arb compacts several `"@key": {"placeholders": {...}}` ICU metadata blocks onto a single line (e.g. es:471 `"@miniPlayerOpenLabel": {"placeholders": {"stationName": {}}},`), while all 12 other locale files expand the identical metadata object across 4-5 lines. This inflates their line count by ~37 with zero actual key/content difference (confirmed by the matching 387-key count above). ## Untranslated Content Project-memory-flagged "SEARCH pilot block" issue (app_ja.arb/app_zh.arb/app_ru.arb allegedly still using English fallback for search* keys) — RE-VERIFIED AND RESOLVED. All three files now have genuine native translations: ja "電波を探す"/"ラジオ・オリゾンテ、ジャズ、ニュース..."; zh "搜索信号"/"地平线电台、爵士、新闻..."; ru "Поиск сигнала"/"Радио Горизонт, джаз, новости...". Broad regex scan for 3+ consecutive Latin words inside string values, across app_{ar,bn,hi,ja,ru,zh}.arb: ZERO matches — no leftover English/copy-paste blocks found in any non-Latin-script locale. ## Encoding Integrity — CRITICAL FINDING - No U+FFFD replacement characters in any of the 13 ARB files. - No double-encoded UTF-8 mojibake (é-style) in any of the 13 ARB files. - Accented Latin (es/fr/de/pt/it) and non-Latin scripts (ja/zh/ar/hi/bn/ru) render correctly and intact across hundreds of sampled lines per file. - HOWEVER: literal ASCII "?" (U+003F) characters — genuine data-loss corruption, not mojibake — replace native duration-unit abbreviations in exactly 4 keys × 6 non-Latin-script locale files (24 corrupted lines total, always at file lines 19/27/34/40): - Keys: durationHoursMinutesSeconds, durationMinutesSeconds, durationMinutesOnly, durationSecondsOnly. - lib/l10n/app_ar.arb:19,27,34,40 e.g. `"{hours} ? {minutes} ? {seconds} ?"` - lib/l10n/app_bn.arb:19,27,34,40 e.g. `"{hours} ? {minutes} ?? {seconds} ??"` - lib/l10n/app_hi.arb:19,27,34,40 e.g. `"{hours} ?? {minutes} ?? {seconds} ??"` - lib/l10n/app_ja.arb:19,27,34,40 e.g. `"{hours}?? {minutes}? {seconds}?"` - lib/l10n/app_ru.arb:19,27,34,40 e.g. `"{hours} ? {minutes} ??? {seconds} ?"` - lib/l10n/app_zh.arb:19,27,34,40 e.g. `"{hours} ?? {minutes} ? {seconds} ?"` - Every other string in these 6 files (383 other keys each) is correctly translated — corruption is isolated to exactly these 4 keys, consistent with one past edit/tool that wrote these 4 lines through a non-UTF-8-safe path, not a whole-file encoding problem. - Latin-script locales unaffected (their duration abbreviations are plain ASCII: en h/min/s, de Std/Min/Sek, fr/it/pt h/min/s, id jam/mnt/dtk — nothing to corrupt). - ICU placeholder tokens {hours}/{minutes}/{seconds} are still present and correctly named in the corrupted lines — no placeholder/crash risk, purely visual content corruption. - User impact: these 4 strings feed `_formatearDuracionTimer()` in lib/app.dart:492-512, rendering the sleep-timer countdown/duration in the "Timer de sueño" bottom sheet (`_mostrarTimerDialog`, lib/app.dart). Users on ar/bn/hi/ja/ru/zh locales see literal "?" instead of their language's h/min/s abbreviation. - Not caught by CI: JSON stays valid, ICU placeholders intact, so `flutter analyze`/gen-l10n do not fail. ## ICU/Placeholder Consistency Sampled ~35 placeholder/plural-bearing keys across all 13 locales (duration family, skipCurrentAlarmExecution, languageUpdated, equalizer*, preferredStation*, backup*, recordingsMaxSize*, favoriteGroups*, favorites*, stationsCount, nearYouInCountry, alarmsCount, activeAlarmsWithoutNextSubtitle, alarmFadeIn*, alarmNextExecution, alarmSkipped*, alarmVacation*, alarmSnoozeOptionLabel, androidReliabilityStatus, miniPlayerOpenLabel, stationSemanticLabel, equalizerBand*, radio*Error, alarmSchedule*, localMusicFolderSaveError, advancedEqDevicePresetLabel, *Countdown, stationCount plural). All placeholder tokens present/correctly named everywhere sampled. stationCount plural forms correctly vary per CLDR: es/en use =1/other; ja/zh use only "other" (correct, no plural distinction in these languages); ru correctly uses one/few/other. No mismatches found. ## Remaining Hardcoded Literals Broad regex scan of lib/pantallas/**/*.dart and lib/widgets/**/*.dart for Text('...')/Text("...") literals and label:/title:/tooltip:/hintText:/content: literal assignments found only ONE match: lib/widgets/pluri_onboarding_dialog.dart:115 `title: Text('v${nota.version}')` — a version-number prefix, technical format, not translatable (consistent with documented "technical formats" exclusion). No other hardcoded user-facing literal found — the June 2026 i18n migration appears complete across sampled screens/widgets. lib/pantallas/pantalla_ajustes.dart:497-509 hardcodes 13 language-picker autonyms (English/Español/中文/हिन्दी/العربية/Português/Français/Русский/Deutsch/日本語/Bahasa Indonesia/বাংলা/Italiano) — documented intentional autonym exception, not a gap. Structural risk (not currently broken): lib/l10n/app_localizations_ext.dart is a hand-maintained Map per locale (not validated by gen-l10n/CI) — a future locale added to supportedLocales without updating this file would silently fall back to English for weekday/month/date-sentence text. ## CI Relevance .gitea/workflows/build.yml job `analizar` runs `flutter analyze --no-fatal-infos --no-fatal-warnings` (line 31) — warnings do not fail the build. `flutter build apk`/`appbundle` (lines 110/113) implicitly regenerate AppLocalizations from ARB but only fail on invalid JSON/ICU syntax, not on wrong/corrupted string content. The "?" duration-key corruption would ship silently past this CI today. ## Severity-Ranked Findings 1. [HIGH] Broken encoding/content — literal "?" replacing native duration-unit abbreviations in durationHoursMinutesSeconds/durationMinutesSeconds/durationMinutesOnly/durationSecondsOnly, in app_ar.arb, app_bn.arb, app_hi.arb, app_ja.arb, app_ru.arb, app_zh.arb (lines 19/27/34/40 each). User-visible in sleep-timer feature; not caught by CI. 2. [MEDIUM] Structural risk in lib/l10n/app_localizations_ext.dart — hand-maintained, unvalidated by tooling; latent risk for future locale additions (not currently broken, all 13 current locales present). 3. [LOW/INFO — confirmed non-issue] es=637 vs others=674 line-count disparity is pure JSON-formatting difference (compacted vs expanded @metadata), zero key/content impact. 4. [LOW/INFO — confirmed RESOLVED] Prior "SEARCH pilot English fallback" in ja/zh/ru is fixed; genuinely translated now. 5. [NONE FOUND] No missing/extra/empty keys anywhere; no placeholder/ICU mismatches; no U+FFFD or mojibake; no remaining hardcoded literals beyond one benign technical-format false positive. ## Tooling/Environment Limitation (risk for orchestrator) This explore-phase execution had access only to Read/Grep/Glob/WebFetch/WebSearch/mem_save/codegraph_explore — no Bash/git tool and no file-write (Write/Edit) tool were available. - Could NOT run `git fetch`/`git pull` to check for latest remote changes (part of the user's original request) — needs to happen in a phase/agent with git access before any push. - Could NOT write `openspec/changes/i18n-locale-audit/explore.md` to disk (hybrid artifact-store mode requires both Engram + filesystem; only the Engram half could be completed here). The orchestrator or a phase with file-write access should persist this content to that path. - Did not run `flutter build`/`flutter gen-l10n`/`dart analyze`/`flutter analyze` per hard environment constraint (these hang in this sandbox) — all findings are from static reads/grep only; full JSON schema/ICU validation via the real Dart intl tooling was not performed (structural sampling strongly suggests valid JSON/ICU throughout, but this is not a substitute for the real generator). ## Recommendation Proceed to sdd-propose for a fix scoped narrowly to: (a) replace the 24 corrupted "?" duration-format strings in the 6 affected ARB files with correct native translations, sourced from a native speaker or a verified translation reference (do not machine-translate blindly given the project's "real native translations" quality bar); (b) optionally add a lightweight CI/pre-commit check that greps ARB files for stray literal "?" adjacent to placeholders, to prevent silent recurrence; (c) note the app_localizations_ext.dart structural risk for a future hardening task (not urgent, no current locale is missing). ## Ready for Proposal Yes — findings are concrete, file/line-cited, and scoped small enough (4 keys × 6 files) for a low-risk, low-line-count fix PR.