feat(i18n): varrer portugues sem acento e ensinar o scanner a ve-lo - #467
Conversation
A fase 3 fechou com `i18n_scan.py` reportando TOTAL: 0, mas o criterio era
mais fraco do que parecia: o scanner detectava portugues pelos ACENTOS, entao
`Salvar projeto`, `Gravando` e `Nenhum video` passavam por ele e pelo ratchet
construido em cima dele.
Detector
--------
Segundo passe por lista de palavras (`PORTUGUESE_WORDS`). Achados agora sao
rotulados `[accent]` ou `[word]`, filtraveis com `--kind`. Uma palavra so entra
na lista se for portuguesa E nao for tambem inglesa -- `ate`, `anterior`,
`data`, `taxa` e `cores` ficam de fora de proposito, cada uma delas um falso
positivo real encontrado durante a varredura.
Dois ajustes mantem o passe silencioso o bastante para continuar ligado:
literais em forma de identificador (`distancia_no_{}_cm`, caminhos, eventos do
structlog) nao sao texto de interface; e linhas marcadas `# i18n: not-ui`
cobrem portugues que e COMPARADO, nunca exibido.
A allowlist escondia texto de interface real
--------------------------------------------
Ela casa por substring, e a entrada `grupo` -- necessaria para uma unica chave
de dict -- isentava toda frase que contivesse a palavra. Sete strings
ACENTUADAS passavam despercebidas por isso, duas delas tooltips de varios
paragrafos do wizard, enquanto o scanner anunciava TOTAL: 0. Padroes agora
podem ser escritos `=grupo` para casar apenas o literal inteiro.
Varredura: 123 literais
-----------------------
Quase todos estavam ao lado de uma chamada `_()` ja traduzida, no mesmo arquivo
e muitas vezes na mesma funcao: `arduino_manager` misturava os dois no mesmo
`_notify_log`; `wizard_service.validate_experimental_design` tinha `_()` nos
dois primeiros `return` e portugues cru nos cinco seguintes;
`hardware_benchmark` traduziu os passos 2/3/4/6 e deixou 1 e 5;
`config_validator` traduziu tres das quatro checagens de faixa.
Um achado era inconsistencia de ESQUEMA, nao de idioma: a fase 3 anglicizou os
cabecalhos de geotaxia no ramo dimensionado de `rename_geotaxis_columns` e nao
tocou no fallback, duplicado em `data_transformer` e `word_reporter` -- a mesma
analise gerava `Bottom (0.0-5.0cm) [%]` ou `Geotaxis Zona 1 - Fundo (%)`
conforme as dimensoes do aquario estivessem disponiveis.
Duas colisoes de msgid foram barradas antes de entrar no catalogo, onde
`i18n_pairs.py` teria mantido a primeira e descartado a segunda em silencio:
`File: {name}` (gravado com outro nome de placeholder) e `Pending`, cujo texto
pt_BR e o plural `Pendentes` por rotular uma contagem -- a linha por sujeito do
bloco recebeu msgid proprio, `Not recorded`.
Mensagens de excecao que so chegam a logs seguiram para ingles simples, sem
`_()`, alinhadas as vizinhas que ja estavam assim
(`project_lifecycle_manager`, `schemas`, `wizard/models`).
Rebase sobre os PRs #464 e #465
-------------------------------
Quatro conflitos, dois deles interessantes.
`video_selector_tree_manager`: este commit anotava o literal `Nenhum` com
`# i18n: not-ui`, justificando por que ele e comparado e nunca exibido. O #464
descobriu que o metodo inteiro nao tem chamador nenhum e o removeu. A delecao
vence -- a anotacao defendia uma linha que deixou de existir.
Catalogos: nao foram mesclados como texto. Parti da versao do main, que ja
carrega as cinco strings novas do #465, e reconstrui `.pot`/`.po`/`.mo` com
`scripts/update_translations.py` sobre as fontes ja mescladas. Mesclar `.po`
textualmente e como um conflito acaba silenciosamente descartado; regenerar e a
unica resolucao verificavel. Conferido em runtime via `gettext`: as 5 strings do
#465 mais as 84 deste PR, 89 de 89, todas resolvem.
Verificacao: ruff e ruff format limpos, mypy limpo em 703 arquivos, 4256
rapidos, 1087 gui, `i18n_scan.py` em zero nos dois passes -- agora tambem sobre
o codigo que o #465 acabou de introduzir.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR strengthens the i18n migration gate by updating scripts/i18n_scan.py to detect Portuguese UI strings that contain no accents (via a wordlist-based second pass), tightening allowlist semantics (exact-match patterns like =grupo), and sweeping a large set of remaining Portuguese UI literals into _() with updated catalogs/pairs/tests.
Changes:
- Extend
i18n_scan.pywith a second “word” heuristic, line-level# i18n: not-uiexemptions, and--kindfiltering. - Fix allowlist overreach by adding exact-match patterns (
=...) and documenting the rule. - Migrate remaining Portuguese UI strings to English source +
_(), updating tests and gettext catalogs accordingly.
Reviewed changes
Copilot reviewed 71 out of 73 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/ui/dialogs/test_dialogs_batch3.py | Adjusts dialog-title test to assert localized “No Day” label rather than leaking stored sentinel. |
| tests/ui/dialogs/test_dialogs_batch2.py | Updates expectations for translated progress/cancel status strings. |
| tests/test_wizard_experimental_design.py | Updates assertions to match English validation messages. |
| tests/test_io_arduino_manager.py | Updates assertions for Arduino log messages now emitted via translated msgids. |
| tests/i18n/test_scanner_unaccented_pass.py | Adds unit coverage for the new unaccented (“word”) scanner heuristic and its exemptions. |
| tests/core/test_video_processing_service_tracking.py | Updates cancellation status assertion to English msgid. |
| tests/core/test_project_workflow_service.py | Updates welcome-guide title expectation to translated English msgid. |
| src/zebtrack/utils/hardware_benchmark.py | Wraps previously-untranslated benchmark progress steps with _(). |
| src/zebtrack/ui/wizard/wizard_dialog.py | Translates wizard window title, step title, and cancel confirmation prompts. |
| src/zebtrack/ui/wizard/models.py | Updates several validation messages to English source strings (but some remain unwrapped by _()). |
| src/zebtrack/ui/wizard/experimental_design_step.py | Translates multi-paragraph tooltips for experimental design controls. |
| src/zebtrack/ui/wizard/custom_regex_dialog.py | Translates dialog subtitle, headings, and error marker text. |
| src/zebtrack/ui/project_workflow_adapter.py | Translates default error dialog title/message when detector setup fails. |
| src/zebtrack/ui/gui.py | Translates initial model/OpenVINO/hardware status display strings. |
| src/zebtrack/ui/format_utils.py | Adds # i18n: not-ui marker on stored “sem dia” comparison. |
| src/zebtrack/ui/dialogs/template_dialog.py | Translates template dialog options and labels; adds i18n import. |
| src/zebtrack/ui/dialogs/subject_selection_dialog.py | Localizes day title rendering while keeping comparisons against stored Portuguese tokens. |
| src/zebtrack/ui/dialogs/start_recording_dialog.py | Translates selection labels and cancel button. |
| src/zebtrack/ui/dialogs/project_video_import_dialog.py | Translates dialog title and subject summary strings. |
| src/zebtrack/ui/dialogs/preview_polygon_dialog.py | Translates reject/approve actions, retry label, status, and overlay label. |
| src/zebtrack/ui/dialogs/pending_videos_dialog.py | Translates tree headings and action buttons. |
| src/zebtrack/ui/dialogs/multi_aquarium_live_preview_window.py | Translates timer label text. |
| src/zebtrack/ui/dialogs/model_diagnostics_dialog.py | Translates “Close” button label. |
| src/zebtrack/ui/dialogs/diagnostic_progress_dialog.py | Translates “Processing…”, cancel, and cancellation status text. |
| src/zebtrack/ui/dialogs/create_project_dialog.py | Translates dialog title and validation error UI copy. |
| src/zebtrack/ui/dialogs/calibration_dialog.py | Translates close button and “copy globals”/folder chooser UI strings. |
| src/zebtrack/ui/dialogs/block_detail_dialog.py | Translates recorded/not-recorded labels, “no files”, cancel, and error dialogs; adds # i18n: not-ui where persistence contracts apply. |
| src/zebtrack/ui/dialogs/aquarium_detection_progress_dialog.py | Translates “Waiting for frames…” label. |
| src/zebtrack/ui/dialogs/aquarium_assignment_dialog.py | Translates filename label, metadata labels, and confirm/cancel actions. |
| src/zebtrack/ui/components/validators/config_validator.py | Translates one remaining range-validation message. |
| src/zebtrack/ui/components/validation_manager.py | Adds # i18n: not-ui markers for comparisons against stored day/group spellings. |
| src/zebtrack/ui/components/state_synchronizer.py | Adds # i18n: not-ui marker for legacy prefix stripping logic. |
| src/zebtrack/ui/components/roi_template_manager.py | Translates warning dialogs for missing template/zone data. |
| src/zebtrack/ui/components/project_initializer.py | Translates “Project ready” status message. |
| src/zebtrack/ui/components/event_dispatcher.py | Translates default warning/error titles used by dispatcher. |
| src/zebtrack/ui/components/dialog_manager.py | Translates ROI color dialog title. |
| src/zebtrack/ui/components/canvas/event_handler.py | Translates success/status messages for zone definition and redo. |
| src/zebtrack/ui/components/analysis_display.py | Translates “Active track ID” label. |
| src/zebtrack/ui/components/analysis_controls.py | Translates “Active track ID” label. |
| src/zebtrack/ui/builders/project_widgets.py | Translates progress-grid tab labels and error rendering message. |
| src/zebtrack/ui/builders/common_widgets.py | Adds # i18n: not-ui markers and translates welcome text. |
| src/zebtrack/locales/zebtrack.pot | Regenerates main POT with new msgids and updated references. |
| src/zebtrack/locales/reporter.pot | Regenerates reporter POT with updated line references. |
| src/zebtrack/locales/pt_BR/LC_MESSAGES/reporter.po | Syncs pt_BR reporter translations to regenerated POT metadata/refs. |
| src/zebtrack/locales/_pairs/pr4-unaccented-ui.json | Adds msgid→pt_BR pairs for UI strings found by unaccented sweep (batch 2). |
| src/zebtrack/locales/_pairs/pr4-unaccented-core.json | Adds msgid→pt_BR pairs for core/coordinator/service strings found by unaccented sweep (batch 1). |
| src/zebtrack/io/arduino_manager.py | Translates connect/disconnect/send command logs using _() and format placeholders. |
| src/zebtrack/core/viewmodels/hardware_status_view_model.py | Translates file picker title and filetype label. |
| src/zebtrack/core/video/tracking_session_runner.py | Translates cancellation and “starting process” status messages. |
| src/zebtrack/core/video/processing_worker.py | Translates processing progress status text. |
| src/zebtrack/core/services/wizard_service.py | Translates Arduino port “[no handshake]” label and experimental-design validation returns. |
| src/zebtrack/core/services/weight_manager.py | Translates OpenVINO conversion failure messages (shown verbatim in UI). |
| src/zebtrack/core/services/detector_service.py | Translates detector initialization failure message. |
| src/zebtrack/core/services/arduino_ack_semantics.py | Adds # i18n: not-ui marker for regex literal exemption in scanner. |
| src/zebtrack/core/recording/live_session_manager.py | Translates recording countdown/status messages using formatted placeholders. |
| src/zebtrack/core/recording/frame_processing_pipeline.py | Translates stabilization status and “● Recording” indicator. |
| src/zebtrack/core/project/schemas.py | Anglicizes a schema validation message (non-UI, schema validation). |
| src/zebtrack/core/project/project_workflow_service.py | Translates the welcome guide’s title string. |
| src/zebtrack/core/project/project_lifecycle_manager.py | Anglicizes developer-facing guards for parameter validation. |
| src/zebtrack/core/detection/multi_aquarium_detector.py | Translates aquarium overlay label (“Aquarium {number}”). |
| src/zebtrack/coordinators/ui_state_coordinator.py | Translates cancellation feedback status text. |
| src/zebtrack/coordinators/recording_session_coordinator.py | Translates “Ready.” status on external-trigger clear. |
| src/zebtrack/coordinators/progress_tracking_coordinator.py | Translates processing status/steps (“Processing…”, “Finalizing…”, “Ready.”). |
| src/zebtrack/coordinators/live_camera_session_coordinator.py | Adds # i18n: not-ui marker for stored Portuguese “dia ” prefix stripping. |
| src/zebtrack/analysis/reporters/word_reporter.py | Fixes geotaxis fallback headers to stable English exported schema (non-_()), matching sized branch. |
| src/zebtrack/analysis/data_transformer.py | Fixes geotaxis fallback headers to stable English exported schema (non-_()), matching sized branch. |
| scripts/i18n_scan.py | Implements unaccented Portuguese detection, exact allowlist semantics, per-line exemptions, and --kind filtering. |
| scripts/i18n_allowlist.txt | Documents and applies exact-match allowlist patterns to avoid substring overreach. |
| docs/guides/developer/i18n.md | Documents exact-match allowlist and # i18n: not-ui usage; updates scanner/ratchet guidance. |
| CHANGELOG.md | Updates changelog to describe unaccented sweep and allowlist/scanner improvements. |
Suppressed comments (1)
src/zebtrack/ui/wizard/models.py:204
- The group name validation errors raised here are shown to the user via wizard validation, but the messages are not wrapped with
_(). That prevents localization (pt_BR will show English), despite these msgids already existing in the translation catalogs.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| def validate_external_trigger(cls, v, info): | ||
| """External trigger requires Arduino to be enabled.""" | ||
| if v and not info.data.get("use_arduino"): | ||
| raise ValueError("Modo de trigger externo requer Arduino ativado") | ||
| raise ValueError("External trigger mode requires Arduino to be enabled") | ||
| return v |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Co-authored-by: MarkSant <22659808+MarkSant@users.noreply.github.com>
O critério era mais fraco do que parecia
A fase 3 fechou com
i18n_scan.pyreportandoTOTAL: 0. Mas o scanner detectava português pelos acentos — entãoSalvar projeto,GravandoeNenhum videopassavam por ele, e pelo ratchet construído em cima dele.Detector
Segundo passe por lista de palavras (
PORTUGUESE_WORDS). Achados agora vêm rotulados[accent]ou[word], filtráveis com--kind. Uma palavra só entra na lista se for portuguesa e não for também inglesa —ate,anterior,data,taxaecoresficam de fora de propósito, cada uma um falso positivo real encontrado durante a varredura.Dois ajustes mantêm o passe silencioso o bastante para continuar ligado: literais em forma de identificador (
distancia_no_{}_cm, caminhos, eventos do structlog) não são texto de interface; e linhas marcadas# i18n: not-uicobrem português que é comparado, nunca exibido.A allowlist escondia interface real
Ela casa por substring, e a entrada
grupo— necessária para uma única chave de dict — isentava toda frase que contivesse a palavra. Sete strings acentuadas passavam despercebidas por isso, duas delas tooltips de vários parágrafos do wizard, enquanto o scanner anunciavaTOTAL: 0. Padrões agora podem ser escritos=grupopara casar apenas o literal inteiro.Varredura: 123 literais
Quase todos estavam ao lado de uma chamada
_()já traduzida, no mesmo arquivo e muitas vezes na mesma função:arduino_managermisturava os dois no mesmo_notify_log;wizard_service.validate_experimental_designtinha_()nos dois primeirosreturne português cru nos cinco seguintes;hardware_benchmarktraduziu os passos 2/3/4/6 e deixou 1 e 5;config_validatortraduziu três das quatro checagens de faixa.Um achado era inconsistência de esquema, não de idioma: a fase 3 anglicizou os cabeçalhos de geotaxia no ramo dimensionado de
rename_geotaxis_columnse não tocou no fallback, duplicado emdata_transformereword_reporter. A mesma análise geravaBottom (0.0-5.0cm) [%]ouGeotaxis Zona 1 - Fundo (%)conforme as dimensões do aquário estivessem disponíveis.Duas colisões de msgid foram barradas antes de entrar no catálogo, onde
i18n_pairs.pyteria mantido a primeira e descartado a segunda em silêncio:File: {name}(gravado com outro nome de placeholder) ePending, cujo texto pt_BR é o pluralPendentespor rotular uma contagem — a linha por sujeito do bloco recebeu msgid próprio,Not recorded.Mensagens de exceção que só chegam a logs seguiram para inglês simples, sem
_(), alinhadas às vizinhas que já estavam assim.Rebase sobre #464 e #465
Quatro conflitos; dois merecem nota.
video_selector_tree_manager— este PR anotava o literalNenhumcom# i18n: not-ui, justificando por que é comparado e nunca exibido. O #464 descobriu que o método inteiro não tem chamador e o removeu. A deleção vence: a anotação defendia uma linha que deixou de existir.Catálogos — não foram mesclados como texto. Parti da versão do
main, que já carrega as cinco strings novas do #465, e reconstruí.pot/.po/.mocomscripts/update_translations.pysobre as fontes já mescladas. Mesclar.potextualmente é como um conflito acaba descartado em silêncio; regenerar é a única resolução verificável.Verificação
ruff check+ruff format --check— limpos (704 arquivos)mypy .— limpo (703 arquivos)pytest -q— 4256 passed, 5 skipped (+34)pytest -m gui -n0 -q— 1087 passed, 1 skippedi18n_scan.py --kind=accente--kind=word— zero nos dois, agora também sobre o código que o fix(ui): erros de parametro do detector voltam a chegar ao usuario #465 acabou de introduzirgettext: 89/89 resolvem (5 do fix(ui): erros de parametro do detector voltam a chegar ao usuario #465 + 84 deste PR). Diff de.moé binário; inspecioná-lo não prova nada🤖 Generated with Claude Code