refactor(i18n): language-agnostic identifiers and payloads — i18n with English fallback, not a Spanish default #73
Labels
No labels
blocked
bug
enhancement
high-priority
low-priority
needs-info
needs-triage
ready-for-agent
ready-for-human
referenced
research
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
gabogg/hikcentral#73
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Blocked by: #49, #178, #186
Principle
The program should be language-agnostic. Identifiers, API payloads and stored data must not encode a language (
SPANISH_DAY_NAMES,reason_es/reason_en). Human-readable text comes from i18n, with English as a fallback for a missing key, never Spanish or English as the default. The UI language is a user preference that i18n resolves, not something code assumes.Evidence from a first scan (
masterat42891fa)Language in identifiers and payloads
app/services/analytics_service.py:14:SPANISH_DAY_NAMES = ["Lunes", …, "Domingo"]. It should be day keys resolved by i18n.app/schemas/models.py:409-411andapp/services/door_classifier.py:137-164: per-language fieldsbadge_text(Spanish) /badge_text_en,reason_es/reason_en. The API ships both languages instead of a key.app/static/js/app.js:892-893: the frontend picksbadge_textvsbadge_text_enandreason_esvsreason_enbycurrentLang.Spanish as the default, not a fallback
app/static/js/i18n.js:1768:currentLang = localStorage.getItem('hc_lang') || 'es'.app/static/index.html:2,app/static/docs.html:2:<html lang="es">hardcoded.app/static/js/app.js:264,:371,:382: inlinecurrentLang === 'es' ? '…' : '…'strings that bypass i18n.User-facing Spanish text built in the backend (counts of Spanish string literals per file):
occupancy_service.py13,door_classifier.py11,door_service.py6,analytics_service.py3,schemas/models.py3,access_cycle_aggregator.py2,database.py2. Examples:"Salida: Botón de Apertura / Sensor"(schemas/models.py:135,access_cycle_aggregator.py:40)"Cierre no justificado por HikCentral (Sincronización)"(access_cycle_aggregator.py:168)"Ajuste manual de calibración por administrador."(analytics_service.py:408, 421)"Cámara {code}","Últimas 24 Horas"(occupancy_service.py)"Miércoles","Sábado"(database.py:229, 232)Some of these are persisted (calibration log
reason, door classifier reasons), so the stored data is itself in Spanish.Scope (to be settled in triage)
*_es,*_en,SPANISH_*,ENGLISH_*, …) and non-ASCII string literals in backend code outside i18n, so regressions are caught in CI.'es'default.Open questions for triage
Accept-Language, a per-deployment setting, or English?jornada→ business cycle (#49) fold into this sweep?Related: #49.
Triage decisions (2026-09-24)
jornada.navigator.languagewhen a translation exists, otherwise English. No per-deployment setting; no hardcoded'es'. English is the fallback for missing keys.Additions from the round-3 reviews of the
[data-veracity]PRs (2026-09-24):"No passenger flow readings returned"in the passenger flow sync.'SIN CALIBRAR'inapp.js(maturity badge);dwell_kpi_labelbuilt on the server asf"{dwell_mins:g} min · …", display text and unit that i18n can't reach.DATA TRUST/CYCLE COMPLETENESStiles and theCALIBRATION AND TRUST THRESHOLDSpanel (with its 12 field labels) inindex.htmlare hardcoded English with nodata-i18n, unlike the surrounding analytics text.Follow-up from PR #93 review: a card-only cycle can retain the Spanish button/sensor
summaryLabelafter async person-name resolution. The cycle has the resolvedpersonNameand CREDENTIAL trigger, but the command-deck activity row renderssummaryLabel. When replacing localized payload text with language-agnostic identifiers in #73, derive that label from the final trigger/name in the active UI language. This also covers English and Spanish verification for the cardholder-name flow.gabogg referenced this issue2026-09-27 23:39:54 +00:00
Scope added from #184 (maintainer triage, 2026-10-02). These two items from #175's second review now belong here:
"Excepción de horario", andschedule_labelappends"(Cerrado)"(occupancy_service.py).app.jsshows that label verbatim, so the English card reads "Schedule Exception: Excepción de horario (Cerrado)".occupancy_repository.py,occupancy_service.py). It goes away with item 6.Related decision on #184 item 7: an exception name is
nullor non-empty, never"". #178 also replaces its new"Cerrado"inholiday_hourswith aclosedflag, so it doesn't add to this.🤖 Generated with Claude Code
Sequencing update (2026-10-03)
Marked
blocked. Its blockers are now #49, #178 and #186:jornadamoving into the es i18n layer) is still open. This sweep must not re-flagjornada; that was the original blocker.schedule_label, the"Excepción de horario"fallback,"(Cerrado)"), and the holiday payloads. Sweeping those files in parallel would cause conflicts and churn.When unblocked: the scan evidence in the body is from 2026-09-24 (
42891fa). Re-run the scan on current master before starting, because many line numbers and some items have changed. The triage decisions in the body still hold:navigator.language, with an English fallback;Scope note: this issue now spans doors, occupancy, statistics, schedule exceptions and the frontend default language. The agent should open a milestone ("Language-agnostic payloads"), with one PR per area, rather than one giant PR.
Scope note from PR #178 review pass 4: the default holiday name is a hardcoded literal.
"Excepción de horario"appears twice inadd_holiday_async(the insert andnew_dict), and the holiday backfill migration uses a different one,"Feriado". Fold this into area (c), occupancy and schedule payloads: one stable code, translated in the browser. #244 removes the migration copy.Scope notes from PR #228 review pass 2 (deferred to #73 sweep):
Nameless holidays export an empty name in CSV (Spec P3-3)
analytics_service.py(d.holiday_name or "").""). Because CSV exports are not localized by the browser, these cells are now blank. Fold into area (c) / language-neutral export formatting.Backend-only Spanish suffix
(Cerrado)in live-card fallback (Spec P3-4)occupancy_service.py(schedule_label).(Cerrado)is pre-existing backend text concatenated inschedule_label. Fold into area (c) when replacing localized backend payload strings with language-neutral identifiers/flags.Note from the PR #222 pass-3 review (spec P3-5), for the frontend display-name work here:
Since #222, an unnamed schedule exception has
exception_name/holiday_name= null everywhere (#184 item 7). The liveschedule_labelfalls back to the weekday name (e.g. "Domingo (Cerrado)"), the live card atapp/static/js/app.js:1889renders "Schedule Exception: " with an empty name, and the holiday list atapp.js:2482shows an empty name. This PR should supply the localized EN/ES fallback ("Unnamed exception" / "Excepción sin nombre") on those paths.