feat(i18n): traduzir ui/dialogs e ui/wizard (fase 3 de 3) - #463
Merged
Conversation
…, lote 1)
Primeiro lote da fase 3. `src/zebtrack/analysis` vai a zero literais e entra em
MIGRATED_PATHS -- `analysis/reporters/**` ja estava limpo desde a fase 1, entao
o pacote inteiro fecha de uma vez.
Dois defeitos achados de quebra, nenhum deles "so traduzir":
1. `data_transformer.rename_geotaxis_columns` gravava "Fundo"/"Meio"/"Superficie"
como CABECALHO DE COLUNA do `_summary.xlsx`, no meio de um arquivo em que
todo o resto (COLUMN_MAPPING, DISPLAY_COLUMN_MAPPING) ja e ingles. Eram as
unicas colunas do sumario cujo nome dependia do idioma: a mesma analise saia
com esquema diferente conforme a maquina, quebrando script de terceiro. Viram
"Bottom"/"Middle"/"Surface" em ingles FIXO, sem `_()` -- rotulo de coluna de
arquivo de dados nao pode variar com `ui.language`. Comentario no local
registra o porque.
2. `visualization_generator` tinha uma unica legenda em portugues ("Area de
processamento...") entre ~20 textos de grafico em ingles fixo (set_title,
set_xlabel, ax.text). Mesmo tratamento: ingles fixo, sem `_()`.
O que e prosa de interface foi para o catalogo `zebtrack` com `_()`:
`roi.py` (12 avisos de degradacao do `seg_overlap` + o ValueError de
`bbox_intersects`) e `analysis_service.py` (aviso multi-animal, barra de status,
dialogos de erro/cancelamento/sucesso). Os avisos de `roi.py` sao conteudo de
relatorio: chegam ao leitor por degradation_warnings -> validation_warnings ->
apendice de validacao do `.docx`. Dominio `zebtrack`, nao `reporter`: so
`analysis/reporters/**` importa o `_` do reporter_context.
Cinco msgids que este lote precisava JA EXISTIAM com o portugues IDENTICO
("Starting processing for {count} videos...", "Analysis Error", "Cancelled",
"Success", "Ready.") -- reusados, omitidos de pr3-analysis.json. Escrever de
novo faria `update_translations.py` avisar "conflicting translations" e ignorar
o par em silencio.
Tres strings sem acento que o scanner NAO ve foram junto ("Cancelado",
"Sucesso", "Pronto." em analysis_service): confirmacao de que a contagem de 942
literais e piso, nao teto.
Asserts migrados no mesmo commit: test_seg_overlap_fallback (5),
test_roi_analyzer, test_visualization_generator, test_data_transformer_roi_metrics
e test_geotaxis_features (4 casos de nome de coluna).
Verde: ruff limpo, mypy limpo em 698 arquivos, 4209 fast (+1 = o novo caso
parametrizado do ratchet), 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e 3, lote 2)
Os dois arquivos soltos em `src/zebtrack/ui/` vao a zero e entram no ratchet.
O achado do lote: DEZ dos dezesseis msgids necessarios JA EXISTIAM com o
portugues identico. `gui.py` mantem copias proprias de dialogos e de defaults de
estado que os componentes migrados na fase 2 ja possuem -- "No Project",
"Open a project before adjusting the specific calibration.", "Failed to save
camera", "Could not save the camera as the project default:", "Form reloaded",
"Values restored to reflect the current settings." (dialog_manager),
"No analysis in progress.", "No task in progress.", "Analysis configuration:
{value}" (analysis_controls) e "Error" (pr1). Todos reusados; `pr3-ui-shell.json`
so carrega os 6 genuinamente novos. A duplicacao em si nao foi mexida -- e
refactor, nao traducao -- mas fica registrada aqui.
Outros pontos:
- `gui.py:805` ja estava em INGLES e sem `_()` ("This project is not configured
for live experimental tracking."): aparecia em ingles para o usuario pt_BR sem
ninguem notar, porque o scanner so procura acento. Agora traduzido de fato.
- `gui.py:865` passava `reason="Atualizacao manual"` ao debouncer do overview.
`reason=` e tag de proveniencia, nunca renderizada -- virou "Manual refresh"
em ingles FIXO, sem `_()`, como todo outro `reason=` da camada de UI
("zones_concluded", "Template Applied", "TabBuilder initialization").
- `ui_coordinator.py:873` montava "sessao(oes)" para fugir do plural. Virou dois
msgids simples escolhidos por `session_count == 1`, o padrao do projeto
(format_status_label): os arquivos de pares nao carregam formas plurais e
ngettext nao e usado em lugar nenhum.
- Tres literais sem acento que o scanner nao ve entraram junto ("Ocorreu um erro
desconhecido.", "Nenhuma tarefa em andamento.", "Nenhum Projeto"), achados por
varredura lexica dos dois arquivos.
Verde: ruff limpo, mypy limpo em 698 arquivos, 4211 fast (+2 = os dois novos
casos parametrizados do ratchet), 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`core/recording` a zero e no ratchet: 53 literais em 5 arquivos, o fluxo ao vivo
inteiro (selecao de modo por hardware, abertura de camera, status de atraso da
analise, mensagens de conclusao).
DOIS `_` COMO DESCARTE sombreavam o gettext -- o gotcha nº 1 do guia, de novo:
live_session_manager.py:459 success, _ = initialize_detector(...)
frame_processing_pipeline.py:693 detections, _ = detector.detect(...)
Ambos ligam `_` como local, o que sombreia o alias do gettext na FUNCAO INTEIRA,
inclusive nas chamadas ACIMA do ponto de atribuicao. Pegos por ruff F823 e por
mypy (`Name "_" is used before definition`), NENHUM por leitura do diff.
Renomeados para `_detector` / `_annotated` com comentario no local.
Defeitos de texto corrigidos junto:
- `live_camera_mode.recommend_mode` montava a recomendacao com `reason +=` sobre
meia oracao ("...mas N foram solicitados. " + "Recomendado: ..."). Um tradutor
nao consegue reordenar meia frase. Virou DUAS oracoes completas, cada uma seu
msgid.
- "aquario(s)" e "frame(s) descartado(s)" eram fuga do plural. Viraram pares
singular/plural escolhidos por comparacao (`max_supported == 1`,
`_dropped_frames_video == 1`), o padrao do projeto -- sem ngettext, que os
arquivos de pares nao suportam.
- `get_mode_description` monta o dicionario DENTRO da funcao, entao pode levar
`_()` direto; comentario registra por que nao pode subir para o corpo do
modulo. O docstring dizia "Portuguese description" -- agora "Description in
the active interface language".
- `LiveCameraModeRecommendation.__str__` NAO foi traduzido: nenhum call site o
renderiza, e a primeira linha imprime `recommended_mode.value`, valor de enum
persistido. Passou a ingles fixo, com o motivo no docstring.
`reason == "no_detections"` (live_analysis_post_processor) e sentinela comparada,
nao texto exibido -- intocada.
Asserts migrados: test_live_camera_phase5_hygiene ("verifique" -> "check the
disk"), test_live_camera_workflow_e2e ("HARDWARE INSUFICIENTE" ->
"INSUFFICIENT HARDWARE").
Verde: ruff limpo, mypy limpo em 698 arquivos, 4212 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`core/project` a zero e no ratchet: 72 literais acentuados em 7 arquivos, mais
uma duzia sem acento que o scanner nao ve.
ACHADO PRINCIPAL -- o guia pos-criacao de projeto apontava para botoes que nao
existem. `project_workflow_service._generate_post_creation_guide` instrui o
pesquisador citando nomes de aba e de botao ENTRE ASPAS, retipados a mao. As
fases 1 e 2 traduziram esses widgets; o guia nao acompanhou. Dois dos seis ja
estavam errados ANTES desta migracao:
- "aba 'Relatorios'" -> a aba se chama "Processamento e Relatorios"
- "Clique em 'Adicionar e Processar Novos Videos'" -> esse rotulo nao existe
em lugar nenhum do codigo; o botao e "Processar Videos Pendentes..."
Agora cada rotulo citado e interpolado a partir do msgid DO PROPRIO WIDGET
(`_(" - Open the '{tab}' tab").format(tab=_("Main Control"))`), entao o guia
nao pode mais divergir em silencio. Os seis msgids ja existiam das fases 1-2 e
foram reusados.
UM `_` COMO DESCARTE de novo, o terceiro da fase: `asset_manager.py:434`
(`_, entry = self._resolve_roi_template_entry(...)`). Desta vez o ruff NAO
pegou -- so o mypy, com `"int" not callable` / `"None" not callable`. Aproveitei
para eliminar os outros seis descartes latentes de `core/project`
(zone_manager, project_manager): hoje estao em funcoes sem `_()`, mas sao
armadilhas armadas para quem adicionar a primeira string traduzida.
Outros defeitos:
- `can_remove_asset` montava "Nao ha {label} registrada para este video." com o
substantivo interpolado. O portugues concorda em genero com a palavra
encaixada ("registrada"), coisa que nenhuma substituicao garante em traducao.
Virou uma frase COMPLETA por asset.
- `project_lifecycle_manager` gravava `analysis_profiles[0].name = "Padrao"` no
project.json, enquanto o factory canonico
`AssetManager._default_analysis_profile()` ja gravava `"default"`. Dois nomes
para o mesmo perfil embutido. Alinhado ao canonico e mantido em INGLES: e
valor PERSISTIDO, e um nome que mudasse com `ui.language` daria projetos
diferentes por maquina. `"active_weight": _("Default")` (project_workflow_
service) e o oposto -- valor de EXIBICAO empurrado ao StateManager -- e leva
`_()`.
- Guardas que nomeiam parametros de codigo (`num_aquariums deve ser <= 100`,
`save_location invalido`, `Asset type '{asset}' desconhecido`) e as
`Field(description=...)` do Pydantic em `schemas.py` (avaliadas no corpo da
classe, onde `_()` e proibido) passaram a INGLES FIXO, sem `_()`: sao
documentacao de esquema e guardas de desenvolvedor, nao copy de interface.
Asserts migrados em 7 arquivos de teste. Um foi DELIBERADAMENTE mantido em
portugues: `test_detection_enforcement.py:140` casa contra
`core/viewmodels/analysis_control_view_model.py`, que e do lote 3d. Esse arquivo
tem uma SEGUNDA redacao da mesma validacao det/seg -- quando 3d migra-lo, deve
reusar o msgid deste lote em vez de criar outro.
Verde: ruff limpo, mypy limpo em 698 arquivos, 4213 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…face `_exempt_docstring` isentava apenas `body[0]` -- a docstring de modulo, classe ou funcao. Uma docstring de ATRIBUTO (PEP 258: o paragrafo logo abaixo de um membro de enum ou de um atributo de classe) e um `ast.Expr` de string como qualquer outro, so que nao e o primeiro do corpo, entao passava direto e era reportada como literal por traduzir. Custava tres falsos positivos em `core/services/external_trigger_gate.py`, onde cada membro do `ExternalTriggerDecision` documenta em portugues por que aquela decisao existe -- prosa que o projeto decidiu MANTER (regra 10 do guia). Sem esse conserto, fechar `core/services` exigiria ou traduzir documentacao ou inflar o allowlist com texto que nao e contrato nenhum. A regra nova e mais geral e mais barata de justificar: uma string que e um EXPRESSION-STATEMENT e avaliada e descartada -- ela nao tem como chegar a um widget. A unica razao para escrever uma e documentar. Total do pacote cai de 786 para 783 literais sem tocar em uma linha de `src/zebtrack`. Os 60 testes de `tests/i18n` seguem verdes, ou seja, a isencao nao escondeu nenhuma string real nos pacotes ja migrados. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`core/services` a zero e no ratchet: 63 literais em 7 arquivos.
ACHADO PRINCIPAL -- ramificacao pelo TEXTO da excecao, o antipadrao que o guia
proibe, a um passo de ser detonado por esta propria migracao.
`detector_service._validate_range` fazia:
try:
val = float(value)
if val < min_val or val > max_val:
raise ValueError(f"{param} deve estar entre {min} e {max}, ...")
except (TypeError, ValueError) as e:
if isinstance(e, ValueError) and "deve estar entre" in str(e):
raise
raise ValueError(f"{param} deve ser um numero valido") from e
O `if` existia so para distinguir "fora da faixa" de "nao e numero", porque as
duas coisas eram levantadas de dentro do MESMO `try`. Traduzir a mensagem da
faixa faria o `in str(e)` falhar e o erro seria reetiquetado como "nao e um
numero" -- ramo ERRADO, sem excecao nenhuma para denunciar. A correcao nao e
traduzir com cuidado: e tirar o range check de dentro do `try`, o que elimina a
necessidade de distinguir. Comentario no local registra o porque.
Outros pontos:
- `arduino_ack_semantics.describe_inversion` encaixava "entrada"/"saida" DUAS
vezes na mesma frase, com um "uma {edge_pt} deveria {expected}" que so
funciona porque os dois substantivos sao femininos em portugues. Virou duas
frases completas escolhidas pelo `edge` -- que e sentinela de codigo, nao
texto exibido, entao a ramificacao continua legitima.
- `ArduinoBinding.describe` idem, em versao mais simples.
- Mais dois `_` como descarte em `weight_manager` (`_, current_default =` e um
`for _, filename, *_` numa generator expression). O quarto e o quinto da fase.
- Os `metadata["description"]` gravados no `metadata.json` do modelo convertido
passaram a INGLES FIXO, sem `_()`: sao valores escritos em disco, e uma
descricao que mudasse com `ui.language` daria arquivos diferentes por maquina
para o mesmo modelo. Mesma logica separa `details["last_conversion_error"]`
(GRAVADO, ingles) do `OpenVINOExportError` (LEVANTADO ao operador, traduzido)
-- a mesma frase, dois papeis distintos.
- As guardas de parametro do detector (`conf_threshold`, `track_buffer`) ficaram
em ingles sem `_()`: nomeiam parametros de codigo e seus erros sobem pelo
EventBus, que os engole antes de qualquer dialogo.
Asserts migrados em 8 arquivos. QUATRO deles so quebram sob `-m gui`
(test_calibration_step) -- a suite rapida passou verde com o bug presente, que e
exatamente por que o `-m gui -n0` e obrigatorio antes do push.
Verde: ruff limpo, mypy limpo em 698 arquivos, 4214 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`core/detection`, `core/video` e `core/viewmodels` a zero. Com isso o pacote
`core` inteiro esta migrado e as CINCO entradas que o ratchet acumulava
(app_runner.py, application_bootstrapper.py, project, recording, services)
colapsam em `src/zebtrack/core` -- qualquer arquivo novo sob `core/` passa a
nascer coberto.
DUAS REDACOES DA MESMA VALIDACAO viraram uma. `analysis_control_view_model`
tinha a sua propria versao da checagem det/seg que `project_workflow_service`
ja fazia, com texto diferente:
pws: "O modo de deteccao (det) para animais so e compativel com 1 animal
por aquario. Configuracao atual: N animais... altere o metodo ...
para 'seg' (segmentacao) nas configuracoes."
acvm: "O modo de deteccao (det) suporta apenas 1 animal por aquario.
Voce configurou N animais... use o modo de segmentacao (seg)."
Mesma regra, mesma correcao a sugerir, conselho redigido de dois jeitos
conforme o caminho que disparasse. Agora as duas compartilham UM msgid (o do
lote 3b) -- e o assert que eu tinha deixado propositalmente em portugues no
`test_detection_enforcement` pode finalmente casar com o ingles.
`_SLOT_LABELS` deixou de ser `ClassVar` e virou `_slot_labels()`. Era um dos
sitios de traducao em tempo de import listados no handoff: um dict de chamadas
a `_()` no corpo da classe e avaliado no import, ANTES de `i18n.install()`, e
congelaria os rotulos no idioma-fonte independentemente de `ui.language`.
`progress_notifier`: o plano era migrar o produtor e depois simplificar o
consumidor (`state_synchronizer`) para so `"step:"`. A apuracao mostrou que o
plano estava errado. NENHUM produtor poe prefixo no campo `step` -- os valores
vem do `live_camera_session_coordinator` ja traduzidos e nus, e o `"Etapa:"` do
`progress_notifier` vai para a MENSAGEM do SET_STATUS, outro campo. A tira-
prefixo e defensiva contra um produtor que nao existe, e o unico teste que a
cobre (`test_analysis_metadata_display`) REIMPLEMENTA a funcao em vez de
chama-la, entao passaria igual se o codigo real sumisse. Simplificar para so
`"step:"` seria pior ainda: em pt_BR nada tem prefixo nenhum. Deixei o
comportamento tolerante e corrigi o comentario, que afirmava o contrario.
`"Erro de Processamento"` (4 sitios) foi unificado com o `"Erro no
Processamento"` ja existente: um titulo de dialogo, um msgid. A variacao
de/no era ruido do fonte.
Guardas de `aquarium_detector` e `multi_aquarium_detector` ("Only 2 aquariums
are supported", "ByteTracker not initialised for aquarium N") ficaram em ingles
sem `_()`: nomeiam estado interno e contrato de API. O limite de 2 aquarios que
o OPERADOR ve continua vindo do wizard, esse sim traduzido.
Verde: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lote 4a)
Primeiro lote do `ui/wizard`. Tres arquivos a zero; os outros dez seguem.
`models.py` NAO foi traduzido: passou a INGLES FIXO, sem `_()`. E a camada de
esquema Pydantic, e a decisao segue a mesma de `core/project/schemas.py`:
- `Field(description=...)` e avaliado no CORPO DA CLASSE, onde `_()` e
proibido (congelaria no idioma-fonte no import);
- o arquivo JA era misto -- os campos mais novos (`roi_inclusion_rule`,
`behavioral_analysis`) ja tinham descricao em ingles. Traduzir metade e
deixar metade era o estado anterior, nao um objetivo;
- as mensagens dos validators nomeiam campos e so aparecem como
`ValidationError` cru. O texto que o operador realmente le vem do
`validate()` de cada step -- esse sim traduzido -- e da
`wizard_service.validate_*`, migrada no lote 3c.
Cuidado deliberado com os valores de enum (`experimental`, `live`, `skip`,
`import_zones`, `partial`, `full`, `replace`, `merge`, `manual`): sao
persistidos no projeto e ficaram intocados. So rotulo exibido foi tocado.
`wizard_dialog.py`: os quatro botoes de navegacao e o titulo do messagebox de
validacao. `"Cancelar"` reusou o msgid `"Cancel"` da fase 1.
`experimental_design_step.py`: rotulos, os dois textos longos (tooltip de
duracao e caixa "Como isso sera usado?") e o resumo com contagem. Sete literais
acentuados mais oito sem acento que o scanner nao ve ("dias", "grupos",
"animais/grupo", "Animais por Grupo:", "Nomes dos Grupos", ...).
Verde: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
O guia descrevia o mecanismo (`_()`, dominios, `.format()`) mas nenhuma das regras que a fase 3 descobriu na pratica. Elas existiam so em mensagens de commit, o que nao ajuda quem abre o arquivo. Acrescentadas: - **`_` e o alias do gettext, nunca descarte.** Ligar `_` em qualquer ponto sombreia o alias na FUNCAO INTEIRA, inclusive acima da atribuicao. Sete ocorrencias na migracao, todas pegas por ruff/mypy e nenhuma por leitura. - **Nao ramifique por texto exibido nem por texto de excecao** -- agora com o caso real do `detector_service`, onde a distincao "fora da faixa" vs "nao e numero" saia de um `in str(e)`, e com o conserto certo (tirar o range check de dentro do `try` em vez de traduzir com cuidado). - **Rotulo citado em texto de ajuda apodrece**: interpole o msgid do proprio widget. O guia pos-criacao tinha seis rotulos retipados, dois ja errados. - **Reuso de msgid**: par conflitante e DESCARTADO com um aviso facil de perder; e reuso alto e sinal de duplicacao (10 de 16 msgids de `gui.py` ja existiam). - **Regra geral do que nao traduzir**: gravado ou comparado => ingles fixo; renderizado => `_()`. Com as categorias novas (cabecalho de coluna de xlsx, metadata.json, `Field(description=...)` do Pydantic, tags de proveniencia) e o caso em que a MESMA frase tem os dois papeis. - **Plural sem ngettext** e **um msgid por frase completa**, com o motivo da concordancia de genero que substituicao nenhuma garante. A secao "The migration" deixou de descrever o plano e passou a descrever o processo: o checklist por lote, o colapso das entradas do ratchet quando um pacote fecha, e a ressalva de que a contagem do scanner e PISO -- ele so acha acento, e cada lote rendeu de um terco a metade a mais de portugues sem acento. Nenhuma mudanca de codigo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
104 literais acentuados saem de ui/wizard (665 -> 561). Quatro defeitos
apareceram no caminho, nenhum deles visivel no scan.
1. ConfirmationStep obtinha a identidade do template chamando
.replace('Template carregado: ', '') sobre o texto JA RENDERIZADO do
banner. Traduzir o prefixo faria o replace parar de casar em silencio e
o resumo passaria a exibir "Template carregado: X" inteiro dentro de uma
linha que ja diz o que e. templates.py ganhou
format_template_banner_details(), que devolve so a identidade;
format_template_banner() passa a ser o prefixo traduzido em volta dela.
2. coordinators/ ja esta em MIGRATED_PATHS e mesmo assim publicava
"Aguardando sinal externo... (porta N)" como UI_SET_STATUS em dois
arquivos. A frase nao tem acento, e o scanner so detecta acento -- logo
o ratchet construido sobre ele nunca a viu. E a confirmacao pratica de
que a contagem do scanner e piso, nao teto. O tooltip do gatilho externo
retipava essa mesma frase como prosa; agora interpola o msgid, entao as
duas nao podem mais divergir.
3. O `--previous` do Babel enrolava o msgid anterior no meio de uma string
ao escrever o comentario `#|`, gerando um .po que o polib recusa
("unknown keyword Recommended:") e travando todo o pipeline. Corrigido
com --no-fuzzy-matching: o i18n_pairs.py ja descartava explicitamente
todo palpite fuzzy do Babel, entao a adivinhacao so produzia msgstr
errado, flag fuzzy (que o CI reprova) e, aqui, corrupcao.
4. Quatro assercoes de teste eram vacuas e passariam com o codigo de
producao quebrado: `or "Template" in banner_text`, `or "vazio" in ...`,
e dois `"indispon" not in ...` que, uma vez traduzido o aviso, passariam
ate se o aviso fosse exibido. Todas reancoradas no texto real.
Tambem unificados dois pares de grafias divergentes da mesma mensagem:
"pyserial nao instalado"/"nao disponivel" e "Total de Videos"/"Total de
videos" (este ultimo reusando o msgid ja existente em core/project).
Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
69 literais acentuados saem de ui/wizard (561 -> 97 na pasta; wizard fica so
com os seis arquivos do lote 4d). Os dois arquivos tinham um dict de rotulos
em CORPO DE MODULO (_METHOD_LABELS, _METHOD_OPTIONS): traduzir no lugar
resolveria o catalogo no import e congelaria o idioma, entao viraram funcao.
Tres defeitos estruturais em model_selection_step, nenhum visivel no scan:
1. " ⭐ Recomendado" era anexado ao peso recomendado PARA EXIBIR e retirado
de volta por _strip_annotation() e pelo startswith de revalidacao. Traduzir
so o lado da exibicao deixaria o marcador colado no nome do peso, e entao
validate() recusaria um peso que o usuario acabou de escolher na propria
lista -- com a mensagem "selecione um peso valido". Agora existe uma
definicao unica, _recommended_suffix(), usada nos tres pontos.
2. _refresh_weight_dropdowns fazia `rec, _ = ...` duas linhas acima de onde o
marcador precisa chamar _(). O gettext viraria o segundo elemento da tupla
e seria chamado como funcao (TypeError), ou ficaria nao-atribuido quando
nao ha perspectiva (UnboundLocalError). Mais tres `name, _ =` em
_default_weight_for_method, renomeados pelo mesmo motivo.
3. O erro de faixa montava f"❌ {label.capitalize()} deve estar entre 0 e 1",
capitalizando um substantivo traduzido. Virou uma frase pronta por campo,
que e o que o guia pede e tambem resolve a concordancia.
Nos testes, quatro sitios DIRIGIAM o widget com o rotulo em portugues
(set("Detecção (det)")). Com o rotulo traduzido eles deixam de resolver,
_method_key_from_label devolve a string crua e o hint fica vazio -- o que
fazia test_animal_method_hint_cleared_for_seg, que exige justamente hint
vazio, passar pelo motivo errado. Passam a usar _method_display().
Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Os seis arquivos restantes de ui/wizard: discovery_step, custom_regex_dialog,
file_selection_step, import_config_step, calibration_step e
design_editor_dialog. O pacote ui/wizard vai a ZERO literais acentuados
(561 -> 395 no pacote todo; o que sobra e ui/dialogs).
Tres achados estruturais:
1. custom_regex_dialog guardava CINCO copias do mesmo
{"group": "Grupo", "day": "Dia", "subject": "Sujeito"}: o cabecalho da
arvore, a linha de resultado por campo, o placeholder "aguardando", o erro
de validacao e a pre-visualizacao. Cada copia precisaria do seu proprio
_(), e elas TEM de concordar entre si na tela (o mesmo rotulo aparece nos
cinco lugares). Colapsaram em _field_labels().
2. file_selection_step montava o resumo colando "N arquivo(s)" + " + " +
" selecionado(s)". O participio final tem de concordar com um sujeito que
pode ser masculino (arquivo), feminino (pasta) ou os dois ao mesmo tempo --
e era por isso que carregava o "(s)". O portugues agora comeca por rotulo
("Seleção: 1 arquivo + 2 pastas"), entao nada precisa concordar, e as
contagens usam os pares singular/plural que ja existiam.
3. import_config_step e confirmation_step descrevem as mesmas estrategias de
ROI com portugues diferente ("Merge (manter ambos)" no radio,
"Mesclar (manter ambos)" no resumo; "Full (do zero)" vs
"Full (processar do zero)"). Mantidos msgids distintos em vez de colidir --
colidir faria update_translations descartar um dos pares em silencio.
Nos testes, "1 arquivo(s)" virou "1 file": a assercao agora confere a forma
singular de verdade, que era justamente o que o "(s)" escondia.
Junto vao cinco anotacoes `str` -> `Path | str` em custom_regex_dialog e
file_selection_step. Sao violacoes PRE-EXISTENTES da regra do proprio repo: o
hook check-path-consistency so inspeciona arquivos tocados pelo commit, e este
lote e a primeira mudanca a alcancar esses arquivos. Os corpos sao orientados a
string (re.search, fatiamento, os.path.basename), entao cada um normaliza com
str(path) no topo -- a anotacao alarga, o comportamento nao muda.
Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
color_selection_dialog, center_periphery_dialog, diagnostic_progress_dialog,
model_diagnostics_dialog, subject_selection_dialog, pending_videos_dialog e
preview_polygon_dialog. 395 -> 356 literais acentuados.
Dois defeitos estruturais:
1. Em color_selection_dialog o NOME da cor fazia quatro trabalhos ao mesmo
tempo: rotulo do radio, VALOR do radio (via name.lower()), chave comparada
no apply() e result["name"]. Traduzir o nome deixaria
StringVar(value="verde") sem casar com nada -- nenhuma cor viria
pre-selecionada, e apply() so acharia a cor depois de o usuario clicar.
A tabela agora carrega uma chave estavel ao lado do rotulo traduzido.
E seguro traduzir o rotulo porque o que se persiste e result["rgb"];
result["name"] so e interpolado numa mensagem de status. Os seis nomes de
cor reusam os msgids ja criados para ui/components/canvas/zone_editor.py,
que mapeia exatamente as mesmas seis tuplas BGR.
2. preview_polygon_dialog tinha _BADGE_AUTO_TEXT/_BADGE_MANUAL_TEXT em corpo
de MODULO: traduzir no lugar congelaria o cracha no idioma vigente no
import. Viraram funcoes. As duas constantes _BADGE_*_BG continuam
constantes -- cor nao e idioma.
subject_selection_dialog montava f"Cobaia {n}: {'Concluído' if ... }": os dois
particípios concordam com o substantivo, entao viraram uma frase pronta cada.
O TAG_STYLES (ClassVar) de pending_videos_dialog estava anotado no handoff
como sitio de traducao em tempo de import, mas contem so cores hexadecimais --
nao ha nada a traduzir ali.
Nos testes, os casos de cor fixavam justamente o acoplamento que este lote
desfaz: procuravam a cor pelo nome em portugues e dirigiam o widget com essa
mesma string como valor do radio. Passam a usar a chave estavel para busca e
selecao, e a conferir o rotulo em ingles -- deixam de depender do idioma.
Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
O texto do commit do 5a diz "395 -> 356"; o numero correto e 375 (o lote removeu 20 literais, nao 39). O codigo esta certo, so a contagem no texto estava errada -- registrada aqui em vez de reescrever um commit ja empurrado. Tambem quebra o restante do lote 5 em sub-lotes com as contagens atuais, e carimba o unico sitio de traducao em tempo de import que resta em ui/dialogs: MODE_DESCRIPTIONS, um typing.ClassVar em corpo de classe de live_camera_mode_selection_dialog. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… 5b)
Os dois maiores arquivos de ui/dialogs. 375 -> 237 literais acentuados.
Tres achados:
1. "Dia {n} - {grupo}" estava retipado dentro de DEZESSETE mensagens
diferentes deste dialogo (titulo, progresso, avisos, cabecalho do .docx,
confirmacao do lote...). Virou um unico _block_label(). E texto de
EXIBICAO: a chave persistida do mesmo bloco e montada em separado como
"Dia_{n}_{grupo}" em add_note e continua em portugues, porque e chave de
dicionario dentro de project_data, nao copy.
2. Dois `_` usados como descarte dentro de _write_partial_report_word ficavam
ABAIXO da chamada _() que monta o cabecalho do relatorio. O gettext virava
variavel local nao atribuida naquele ponto; ruff F823 pegou. Renomeados.
3. Conflito de msgid detectado antes de gravar: "Detection Error" e
"Detection failed" ja existiam em pr2-coordinators-tail.json com portugues
um pouco diferente ("Erro na Detecção", "Detecção falhou"). Gravar um
segundo par faria o update_translations.py descartar um dos dois em
silencio -- os dois call sites reusam o msgid existente.
O trio "Sem Sessões"/"Sem Relatórios"/aviso de duracoes aparece identico em
generate_partial_report e mark_batch_complete (o proprio comentario do codigo
diz "mesmos textos"), entao os dois call sites continuam compartilhando um
msgid cada.
block_detail_dialog tambem escreve um .docx parcial. Esses cabecalhos ficam no
dominio zebtrack, e nao no reporter, porque o babel extrai por DIRETORIO
(--ignore-dirs reporters) e este arquivo nao esta sob analysis/reporters:
importar o _() do reporter aqui extrairia para zebtrack.pot mas procuraria no
catalogo reporter, ou seja, cairia sempre no ingles.
Os ValueError levantados em live_analysis_dialog.validate() foram embrulhados
porque sao interpolados direto numa messagebox, nao apenas registrados no log.
Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
create_project_dialog, single_video_config_dialog e
project_video_import_dialog. 237 -> 132 literais acentuados.
Quatro msgids foram DESCARTADOS deste lote depois que a checagem de corpus
apontou colisao -- gravar um par conflitante faz o update_translations.py
derrubar um dos dois em silencio:
'Number of Groups:', 'Group Names' e 'Validation' ja existiam identicos em
pr3-wizard-shell.json;
'Metadata' ja existia em pr2-components-tail.json como "Metadados". O
cabecalho da arvore reusa o msgid existente e agora concorda com o resto
do app, em vez de mostrar "Metadata" so nesta tela.
Os rotulos de coluna "Grupo"/"Dia"/"Sujeito" do project_video_import_dialog
reusam os msgids no singular criados no 4d (custom_regex_dialog), e o titulo
"Validação" -- que aparece em QUATORZE call sites -- ficou com um msgid so.
O resumo de selecao do create_project_dialog usava a mesma construcao
"N arquivo(s) + N pasta(s) selecionado(s)" do file_selection_step, entao
reusa os pares singular/plural e o involucro "Selected: {parts}" do 4d --
o participio final nao precisa mais concordar com nada.
"Grupo {n}:" no create_project_dialog e rotulo puro: group_name_vars nascem
vazias e o operador digita o nome real, entao nada persistido depende dele.
Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
live_camera_mode_selection_dialog, aquarium_detection_progress_dialog, calibration_dialog, start_recording_dialog, aquarium_assignment_dialog e multi_aquarium_live_preview_window. 132 -> 53 literais acentuados. MODE_DESCRIPTIONS era um typing.ClassVar em CORPO DE CLASSE -- o ultimo sitio de traducao em tempo de import que restava em ui/dialogs. Um dict literal em corpo de classe e avaliado no import, entao envolver aquelas quatro descricoes em _() ali dentro congelaria o idioma no momento em que o modulo fosse importado, antes de i18n.install() rodar. Virou o staticmethod _mode_descriptions(). O nome curto do RECORD_ONLY reusa o msgid "Recording Only" criado no 4b, onde o live_config_step cita exatamente esse modo pelo nome no aviso de hardware insuficiente -- os dois nao podem mais divergir. start_recording_dialog duplica o seletor de camera inteiro do block_detail_dialog, palavra por palavra. Todas essas strings reusam os msgids do 5b; so tres entradas deste arquivo sao novas. Os ValueError de aquarium_assignment_dialog._collect_configs foram embrulhados porque o validate() os renderiza com messagebox.showerror(..., str(e)) -- nao sao mensagens so de log. Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4210 fast, 1079 gui. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fecha ui/dialogs. O scanner sai de 53 literais acentuados em 8 arquivos para
zero; o total real migrado foi de 86 msgids novos, porque metade do portugues
desses arquivos nao tem acento e o scanner e cego a isso.
Defeitos encontrados:
1. core/recording/live_session_manager.py ja estava DENTRO do ratchet e mesmo
assim empurrava quatro status em portugues para o rotulo do LivePreviewWindow
-- "Carregando detector...", "Iniciando em Ns...", "Iniciando captura..." e
"Gravando". Nenhuma tem acento, entao nem o scanner nem o ratchet construido
sobre ele jamais as viram. Ficavam literalmente ao lado de chamadas _() ja
traduzidas no MESMO rotulo, de forma que a linha de status trocava de idioma
no meio da sessao.
2. LivePreviewWindow.start_timer escrevia "Inicio: HH:MM:SS" no rotulo de VALOR,
que ja tem "Inicio:" como rotulo da coluna a esquerda -- a linha renderizava
"Inicio: Inicio: 12:34:56". stop_time_label, ao lado, nunca fez isso. O
valor agora traz so o relogio, igual ao par dele.
3. ui/wizard chegou a zero no lote 4d mas nunca entrou em MIGRATED_PATHS:
passou quatro lotes sem guarda. Entrou agora, junto com ui/dialogs.
4. "Nenhuma camera encontrada" em live_config_dialog era EXIBIDA e tambem
gravada em camera_var, que apply() usa como chave de busca em
available_cameras. Traduzir um sitio e nao o outro quebraria a busca em
silencio. Virou _no_camera_label(), definicao unica para os tres sitios.
"Camera {index}" (descricao padrao) estava duplicada em _detect_devices e
_refresh_cameras; ambas passam a reusar o msgid criado no lote 4b.
5. "Porta Arduino:" era uma segunda grafia do "Porta do Arduino:" que
live_config_step ja possuia -- mesmo campo, dois rotulos. Colapsou no msgid
"Arduino Port:". Ja "Configuracao de Aquarios" NAO foi colapsado no
"Aquarium Configuration" de aquarium_assignment_dialog: sao dialogos
distintos, entao virou "Aquarium Setup".
6. A mensagem de erro do Arduino citava a opcao 'Usar Arduino' retipada como
prosa; agora interpola o msgid do proprio checkbox.
7. save_roi_template_dialog usava `lambda *_:` num trace_add -- o padrao de `_`
como descarte que ja quebrou o gettext tres vezes nesta migracao. Renomeado
por precaucao, mesmo sem _() dentro do lambda.
8. camera_disconnect_recovery_dialog repetia a legenda da contagem regressiva
em dois sitios com a mesma interpolacao; virou _countdown_text().
Em zone_reuse_dialog, "• Metodo: {auto-detectado|desenhado manualmente}" virou
uma frase completa por caso, em vez de um fragmento interpolado que o portugues
nao consegue concordar.
O hook Check Path | str acusou uma violacao PRE-EXISTENTE em
multi_aquarium_confirm_dialog.__init__ (video_path: str | None); a anotacao foi
alargada para Path | str | None e normalizada no topo da funcao.
Testes: seis asseracoes ancoradas no texto em portugues foram migradas (a suite
roda com ZEBTRACK_LANGUAGE=en). Duas delas -- "Tempo:" e "nenhuma porta serial"
-- so apareceram no `pytest -m gui -n0`, que nao roda na suite rapida.
Verificacao: ruff check limpo, mypy limpo em 698 arquivos, 4212 passed na suite
rapida, 1079 passed na de GUI, i18n_scan em TOTAL: 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MIGRATED_PATHS colapsa nas 17 entradas anteriores para ("src/zebtrack",), de
forma que qualquer arquivo novo do pacote ja nasce coberto. O comentario
"Migrado na PR1" saiu do topo do modulo.
No lugar dele entrou a ressalva que importa: verde nesse teste significa "sem
portugues ACENTUADO", nao "sem portugues". O i18n_scan detecta portugues pelo
acento, entao "Salvar", "Nenhum video", "Remover" e "dias" atravessam o scanner
e o ratchet sem serem vistos. Nao e teorico -- o lote 5e achou quatro strings
desse tipo dentro de core/recording/, pacote listado no ratchet desde o lote 3a,
e o lote 4b achou outras duas em coordinators/. A mesma ressalva foi registrada
no guia, no CHANGELOG e no ROLLING_TASK_LOG; a varredura dedicada a portugues
sem acento continua PENDENTE e nao foi feita aqui.
O CHANGELOG ganhou a secao das fases 2 e 3, que ate agora so tinha a fase 1.
Ela lista os defeitos que a traducao tornou visiveis em vez de so dizer o que
foi traduzido: rotulos citados de memoria em texto de ajuda (dois ja errados
ANTES da migracao, um deles nomeando um botao inexistente), ramificacao pelo
texto de uma excecao, o marcador "⭐ Recomendado" que era anexado para exibir e
retirado por _strip_annotation(), a identidade de template obtida por .replace()
sobre texto renderizado, cabecalhos de .xlsx em portugues no meio de arquivos
ingleses, e as grafias duplicadas da mesma mensagem que foram unificadas.
TASK-068 marcada como Completed, com 5b/5c/5d/5e preenchidos -- os tres
primeiros tinham ficado com o checkbox desmarcado embora os commits ja
tivessem entrado.
Verificacao: 44 passed em tests/i18n, i18n_scan em TOTAL: 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
As quatro asseracoes deste arquivo nao testavam o codigo de producao. O `state_synchronizer` era um `Mock()` cujos `side_effect` REIMPLEMENTAVAM os dois formatadores -- `_apply_analysis_metadata_strings` e `update_analysis_task_status` -- copiando as mesmas f-strings que as asseracoes depois conferiam. O teste comparava o stub com a expectativa escrita a partir do proprio stub: passaria com os metodos de producao deletados, renomeados ou com qualquer formato diferente. Agora o fixture injeta um `StateSynchronizer(instance)` de verdade. O `validation_manager` continua stub de proposito -- transformar metadados crus em strings de exibicao e unidade dele, coberta em test_validation_manager.py -- mas seus fallbacks passam a vir de `no_group_label()` / `no_day_label()` / `not_reported_label()`, em vez de repetir "No Group"/"No Day"/"Not reported" a mao, que era mais uma copia capaz de divergir em silencio. Verificado por mutacao: trocando o texto dos dois formatadores em state_synchronizer.py, os 4 testes FALHAM (antes desta mudanca, passavam). Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4197 passed na suite rapida (4212 - 16 entradas de MIGRATED_PATHS + 1, apos o colapso do ratchet). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Finalizes the phase 3/3 i18n migration by moving remaining UI (wizard + dialogs) and core/analysis-facing strings to English msgids (with pt_BR in _pairs / catalog), plus tightening scanner/ratchet coverage and updating tests to assert the new msgids.
Changes:
- Migrates
ui/wizard/andui/dialogs/UI copy to gettext msgids and refactors a few string-dependent flows to avoid translation breakage. - Propagates i18n updates across
analysis/,core/, and runtime/coordinator status strings, aligning user-visible messaging. - Updates i18n tooling (
i18n_scan.py,update_translations.py), ratchet scope, and tests to match the new i18n baseline.
Reviewed changes
Copilot reviewed 140 out of 143 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| CHANGELOG.md | Documents the completed i18n migration and known scanner limitation (unaccented PT). |
| scripts/i18n_scan.py | Exempts bare string expression-statements (attribute docstrings) from untranslated-literal scanning. |
| scripts/update_translations.py | Adds --no-fuzzy-matching to avoid Babel fuzzy/previous-msgid parse hazards. |
| src/zebtrack/analysis/analysis_service.py | Localizes analysis warnings/status strings and normalizes UI-facing messages. |
| src/zebtrack/analysis/data_transformer.py | Fixes geotaxis column headers to stable English schema (not language-dependent). |
| src/zebtrack/analysis/roi.py | Moves ROI analyzer errors/degradation warnings to msgids. |
| src/zebtrack/analysis/visualization_generator.py | Replaces a remaining Portuguese plot label with English. |
| src/zebtrack/coordinators/live_camera_session_coordinator.py | Localizes external-trigger wait status message. |
| src/zebtrack/coordinators/recording_session_coordinator.py | Localizes external-trigger wait status message. |
| src/zebtrack/core/detection/aquarium_detector.py | Standardizes internal contract error message to English. |
| src/zebtrack/core/detection/multi_aquarium_detector.py | Standardizes runtime errors to English. |
| src/zebtrack/core/project/project_manager.py | Localizes project-manager user-facing errors and avoids _ shadowing. |
| src/zebtrack/core/project/project_workflow_service.py | Localizes validation and guide text; interpolates widget msgids instead of retyping labels. |
| src/zebtrack/core/project/roi_template_manager.py | Localizes template validation/load errors; clarifies developer-facing guards. |
| src/zebtrack/core/project/schemas.py | Converts schema descriptions/messages to English (avoids import-time _() in class metadata). |
| src/zebtrack/core/project/zone_manager.py | Localizes user-facing ValueError and avoids _ shadowing. |
| src/zebtrack/core/recording/frame_processing_pipeline.py | Localizes aquarium-detection status and avoids _ shadowing in tuple unpacking. |
| src/zebtrack/core/recording/live_analysis_post_processor.py | Localizes completion dialogs and file lists. |
| src/zebtrack/core/recording/live_session_manager.py | Localizes live-session status updates; avoids _ shadowing; adds plural-safe strings. |
| src/zebtrack/core/recording/recording_service.py | Localizes recording start failure messages. |
| src/zebtrack/core/services/arduino_ack_semantics.py | Localizes inversion descriptions without fragile string splicing. |
| src/zebtrack/core/services/arduino_bindings.py | Localizes token-conflict description string. |
| src/zebtrack/core/services/detector_service.py | Localizes several detector init errors and refactors range validation; updates track_buffer validation. |
| src/zebtrack/core/services/model_override_service.py | Localizes warnings/status for applying global settings and project overrides. |
| src/zebtrack/core/services/model_service.py | Localizes OpenVINO status text and model-not-found errors. |
| src/zebtrack/core/video/analysis_pipeline_runner.py | Localizes processing error titles/messages. |
| src/zebtrack/core/video/processing_mode.py | Localizes display names for processing modes. |
| src/zebtrack/core/video/progress_notifier.py | Localizes progress/status messages. |
| src/zebtrack/core/video/tracking_session_runner.py | Localizes “trajectory generated” status update. |
| src/zebtrack/core/video/video_context_factory.py | Localizes processing error titles/messages and details. |
| src/zebtrack/core/viewmodels/analysis_control_view_model.py | Localizes error/status strings and deduplicates det/seg validation msgid. |
| src/zebtrack/core/viewmodels/hardware_status_view_model.py | Avoids import-time translation by generating slot-label dict at call time. |
| src/zebtrack/core/viewmodels/main_view_model_runtime.py | Localizes critical camera-thread shutdown error payload. |
| src/zebtrack/locales/_pairs/pr3-analysis.json | Adds Portuguese pairs for analysis-domain msgids added in this phase. |
| src/zebtrack/locales/_pairs/pr3-core-recording.json | Adds Portuguese pairs for core/recording msgids added in this phase. |
| src/zebtrack/locales/_pairs/pr3-core-tail.json | Adds Portuguese pairs for remaining core/viewmodel msgids. |
| src/zebtrack/locales/_pairs/pr3-dialogs-5a.json | Adds Portuguese pairs for dialog msgids added in this phase. |
| src/zebtrack/locales/_pairs/pr3-ui-shell.json | Adds Portuguese pairs for ui shell msgids added in this phase. |
| src/zebtrack/locales/_pairs/pr3-wizard-shell.json | Adds Portuguese pairs for wizard shell msgids added in this phase. |
| src/zebtrack/ui/components/state_synchronizer.py | Keeps step-prefix tolerance but avoids relying on displayed text for branching. |
| src/zebtrack/ui/dialogs/aquarium_assignment_dialog.py | Localizes dialog title/frames and some warnings; adds validation errors. |
| src/zebtrack/ui/dialogs/aquarium_detection_progress_dialog.py | Localizes detection progress dialog strings and thumbnail labels. |
| src/zebtrack/ui/dialogs/calibration_dialog.py | Localizes calibration dialog titles/tabs and copy-to-project flows. |
| src/zebtrack/ui/dialogs/camera_disconnect_recovery_dialog.py | Localizes disconnect copy; centralizes countdown text to avoid drift. |
| src/zebtrack/ui/dialogs/center_periphery_dialog.py | Localizes method/value labels. |
| src/zebtrack/ui/dialogs/color_selection_dialog.py | Separates stable color keys from translated labels; updates apply() result schema. |
| src/zebtrack/ui/dialogs/diagnostic_progress_dialog.py | Localizes default title/status strings while avoiding import-time translation. |
| src/zebtrack/ui/dialogs/live_config_dialog.py | Localizes live config dialog strings; centralizes the “no camera” placeholder to avoid lookup drift. |
| src/zebtrack/ui/dialogs/missing_metadata_dialog.py | Localizes dialog copy and validation errors. |
| src/zebtrack/ui/dialogs/model_diagnostics_dialog.py | Localizes dialog title. |
| src/zebtrack/ui/dialogs/multi_aquarium_confirm_dialog.py | Localizes copy and normalizes video_path to Path for safer handling/logging. |
| src/zebtrack/ui/dialogs/multi_aquarium_live_preview_window.py | Localizes window title and several captions; updates timer label behavior. |
| src/zebtrack/ui/dialogs/pending_videos_dialog.py | Localizes dialog title/copy; adds singular/plural variant strings. |
| src/zebtrack/ui/dialogs/preview_polygon_dialog.py | Avoids import-time translations by replacing badge constants with helper functions; localizes UI copy. |
| src/zebtrack/ui/dialogs/save_roi_template_dialog.py | Localizes dialog copy; avoids _ shadowing in trace callback; localizes file dialog labels. |
| src/zebtrack/ui/dialogs/start_recording_dialog.py | Localizes dialog titles/labels and camera chooser UI. |
| src/zebtrack/ui/dialogs/subject_selection_dialog.py | Localizes per-subject completion labels without fragile string splicing. |
| src/zebtrack/ui/dialogs/zone_calibration_dialog.py | Localizes zone calibration selection dialog copy/buttons. |
| src/zebtrack/ui/dialogs/zone_reuse_dialog.py | Localizes zone-reuse confirmation copy and status details. |
| src/zebtrack/ui/gui.py | Localizes fallback error strings, analysis-status defaults, and several dialog titles/messages. |
| src/zebtrack/ui/ui_coordinator.py | Localizes runtime status updates; adds plural-safe batch report status message. |
| src/zebtrack/ui/wizard/experimental_design_step.py | Migrates wizard step UI copy to gettext with formatted msgids. |
| src/zebtrack/ui/wizard/templates.py | Introduces format_template_banner_details() to avoid parsing translated banner text. |
| src/zebtrack/ui/wizard/wizard_dialog.py | Localizes navigation buttons and validation dialog title. |
| tests/analysis/test_data_transformer_roi_metrics.py | Updates assertions for localized warning text. |
| tests/analysis/test_roi_analyzer.py | Updates assertions for localized error message text. |
| tests/analysis/test_seg_overlap_fallback.py | Updates assertions for localized degradation warnings. |
| tests/analysis/test_visualization_generator.py | Updates assertions for updated plot label text. |
| tests/core/services/test_wizard_service_camera_friendly_names.py | Updates camera description assertions to English msgids. |
| tests/core/test_analysis_control_view_model.py | Updates assertions for localized titles/status messages. |
| tests/core/test_arduino_ack_semantics.py | Updates inversion description assertions to new English wording. |
| tests/core/test_asset_manager_basic.py | Updates assertions for localized error messages. |
| tests/core/test_detector_service.py | Updates assertions for localized detector init error strings. |
| tests/core/test_live_camera_phase5_hygiene.py | Updates assertions for localized disk-check hint text. |
| tests/core/test_processing_mode.py | Updates assertions for ProcessingMode display names. |
| tests/core/test_project_workflow_service.py | Updates assertions for localized validation and guide messages. |
| tests/core/test_roi_template_manager_validation.py | Updates docstrings/regex matches for localized template errors. |
| tests/core/test_schemas.py | Updates assertions for localized schema validation errors. |
| tests/core/test_weight_manager.py | Updates assertion regex for localized “cannot delete last weight” error. |
| tests/core/test_wizard_service_multi_aquarium.py | Updates assertions for localized multi-aquarium validation errors. |
| tests/i18n/test_no_untranslated_literals.py | Expands ratchet scope to all of src/zebtrack. |
| tests/test_analysis_metadata_display.py | Stops using a mock reimplementation; asserts real StateSynchronizer formatting. |
| tests/test_class_names_validation.py | Updates assertion regex for localized model-not-found error. |
| tests/test_detection_enforcement.py | Updates assertions for localized enforcement errors/titles. |
| tests/test_geotaxis_features.py | Updates expected geotaxis zone display names to stable English. |
| tests/test_live_analysis_ui.py | Updates assertions for localized live-analysis UI labels and fixes duplicated start-time caption expectation. |
| tests/test_live_camera_workflow_e2e.py | Updates assertions for localized insufficient-hardware warning. |
| tests/test_project_manager.py | Updates assertions for localized “reports” reason and “not found” project load error. |
| tests/ui/dialogs/test_block_detail_dialog.py | Updates assertion for localized day label. |
| tests/ui/dialogs/test_calibration_dialog.py | Updates assertions for localized titles/tab names. |
| tests/ui/dialogs/test_color_selection_dialog.py | Updates tests for new stable color key + translated label result. |
| tests/ui/dialogs/test_dialogs_batch2.py | Updates assertions for localized diagnostics progress dialog strings. |
| tests/ui/dialogs/test_live_camera_mode_selection_dialog.py | Updates assertions for localized mode display names. |
| tests/ui/dialogs/test_live_config_dialog.py | Updates assertions for localized live config validation errors. |
| tests/ui/dialogs/test_preview_polygon_dialog.py | Updates tests to use badge helper functions rather than import-time constants. |
| tests/ui/dialogs/test_subject_selection_dialog.py | Updates assertions for localized subject completion labels. |
| tests/ui/test_aquarium_dialogs.py | Updates assertion for localized multi-aquarium confirm dialog title. |
| tests/ui/wizard/test_calibration_step.py | Updates assertions for localized validation errors and template banner text. |
| tests/ui/wizard/test_live_config_step.py | Updates assertions for localized “unavailable” warnings. |
| tests/ui/wizard/test_model_selection_step.py | Stops driving widgets by Portuguese labels; uses production display helpers. |
| tests/ui/wizard/test_wizard_confirmation.py | Updates assertions for localized summary/validation strings. |
| tests/ui/wizard/test_wizard_detection.py | Updates assertions for localized status text. |
| tests/ui/wizard/test_wizard_file_selection.py | Updates assertions for localized validation and summary strings. |
| tests/ui/wizard/test_wizard_import_config.py | Updates assertions for localized “no video to configure” message. |
| tests/ui/wizard/test_wizard_integration.py | Updates default project-name assertions to English msgids. |
Suppressed comments (2)
src/zebtrack/ui/dialogs/multi_aquarium_live_preview_window.py:233
- Several UI strings in the session footer remain hardcoded in Portuguese (e.g., "Fim:", "Frames gravados") and are not wrapped with _(), so they won't follow the selected UI language.
src/zebtrack/ui/dialogs/aquarium_assignment_dialog.py:485 - Labels for group/subject are still hardcoded in Portuguese ("Grupo:", "Sujeito:") and bypass gettext, so this dialog will not fully localize with the selected UI language.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
508
to
+512
| try: | ||
| val = float(value) | ||
| if val < min_val or val > max_val: | ||
| raise ValueError( | ||
| f"{param_name} deve estar entre {min_val} e {max_val}, recebido {val}" | ||
| ) | ||
| except (TypeError, ValueError) as e: | ||
| if isinstance(e, ValueError) and "deve estar entre" in str(e): | ||
| raise | ||
| raise ValueError(f"{param_name} deve ser um número válido") from e | ||
| raise ValueError(f"{param_name} must be a valid number") from e | ||
| # The range check lives OUTSIDE the try on purpose. It used to sit |
Comment on lines
530
to
+535
| if "track_buffer" in params_dict: | ||
| try: | ||
| if int(params_dict["track_buffer"]) < 1: | ||
| raise ValueError("track_buffer deve ser pelo menos 1") | ||
| raise ValueError("track_buffer must be at least 1") | ||
| except (TypeError, ValueError) as e: | ||
| raise ValueError("track_buffer deve ser um número inteiro") from e | ||
| raise ValueError("track_buffer must be an integer") from e |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…ro de tipo Apontado pelo revisor na PR #463. E o mesmo defeito que o lote 3c removeu de _validate_range, deixado intacto no bloco vizinho, dez linhas abaixo: try: if int(params_dict["track_buffer"]) < 1: raise ValueError("track_buffer must be at least 1") except (TypeError, ValueError) as e: raise ValueError("track_buffer must be an integer") from e O `raise` da faixa esta DENTRO do try, entao o proprio `except ValueError` abaixo o captura e o re-levanta como erro de tipo. Passar `track_buffer=0` -- um inteiro -- era respondido com "track_buffer must be an integer". A conversao agora fica sozinha no try e a checagem de faixa vem depois, igual ao _validate_range; a mensagem passa a citar o valor recebido. O que NAO foi mudado: o limite. O revisor sugeriu alinhar em `>= 0` com o DetectorSetupCoordinator, mas ha TRES limites divergentes para o mesmo parametro e o coordinator nao e a referencia canonica: settings.py Field(300, ge=10, le=1000) <- contrato de esquema DetectorSetupCoordinator track_buffer < 0 <- aceita 0 DetectorService track_buffer < 1 <- rejeita 0 Escolher entre eles muda o que o app aceita e e decisao de dominio, nao de mensagem de erro; fica registrado para ser resolvido em separado. Esta mudanca preserva exatamente o conjunto de valores aceitos e corrige so a mensagem. O outro comentario do revisor -- embrulhar estes ValueError em _() -- foi verificado e NAO se aplica. As mensagens sao embrulhadas em DetectorSetupCoordinatorError, que nao e capturado em NENHUM ponto de src/: model_diagnostics_panel captura ValidationError do pydantic e ui_state_coordinator.apply_detector_thresholds (que captura ValueError) nao e chamado por ninguem. Elas nunca chegam a um showerror; terminam em log. Pela regra do proprio guia, texto de excecao que so alcanca log fica em ingles puro. Teste de regressao distingue os dois erros e foi verificado por mutacao: restaurando a forma antiga, os dois casos de faixa falham. Verificacao: ruff limpo, mypy limpo em 698 arquivos, 4202 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fecha a migração de i18n. Com esta PR,
i18n_scan.py src/zebtrackvai aTOTAL: 0eMIGRATED_PATHSpassa a ser("src/zebtrack",).Cobre
analysis/,core/,ui/gui.py,ui/ui_coordinator.py,ui/wizard/eui/dialogs/— 20 commits, ~1900 msgids. Fases 1 (#461) e 2 (#462) já no main.A migração é um movimento, não um trabalho de tradução: o literal em
português vira o valor no
_pairs/, então o diff do.poé o inverso exato dodiff do fonte.
Defeitos encontrados
Nenhum destes era o objetivo da tarefa; todos apareceram porque traduzir uma
string obriga a responder "quem mais depende deste texto?".
Código que ramificava por texto exibido ou por texto de exceção
detector_service._validate_rangedecidia entre "fora da faixa" e "não éum número" com
if "deve estar entre" in str(e). Traduzir a mensagem teriareclassificado toda violação de faixa como erro de parsing, em silêncio. A
checagem de faixa saiu do
try, então oexceptsó vê falha de conversão.confirmation_stepobtinha a identidade do template com.replace("Template carregado: ", "")sobre o texto renderizado; traduziro prefixo o deixaria dentro do resumo. Nasceu
format_template_banner_details().model_selection_stepanexava" ⭐ Recomendado"para exibir e retiravacom
_strip_annotation()/startswith. Traduzir só a exibição colaria omarcador no nome do peso e
validate()recusaria um peso escolhido na próprialista. Passou a existir uma definição só,
_recommended_suffix(), usada nostrês pontos.
live_config_dialogexibia"Nenhuma câmera encontrada"e gravava amesma string em
camera_var, queapply()usa como chave emavailable_cameras. Virou_no_camera_label().Texto de interface que o scanner nunca viu
O
i18n_scan.pydetecta português pelo acento. Isso deixou passar frasesinteiras dentro de pacotes que o ratchet já dava por prontos:
coordinators/publicava"Aguardando sinal externo... (porta N)"comostatus de UI em dois arquivos, e o tooltip do gatilho externo retipava a mesma
frase como prosa.
core/recording/live_session_managerempurrava quatro status(
"Carregando detector...","Iniciando em Ns...","Iniciando captura...","Gravando") para o mesmo rótulo onde já havia chamadas_()traduzidas.Na prática a linha de status trocava de idioma no meio da sessão.
Rótulos citados de memória
O guia pós-criação de projeto em
project_workflow_serviceretipava seisnomes de aba/botão como prosa e dois já estavam errados antes da migração —
um deles nomeava um botão que não existe no código. Agora interpolam o msgid do
próprio widget.
Duplicação que a tradução transformaria em divergência
"Dia {n} - {grupo}"estava retipado em dezessete mensagens deblock_detail_dialog; virou_block_label(). A chave persistida do mesmobloco (
"Dia_{n}_{grupo}") continua em português, de propósito.custom_regex_dialogguardava cinco cópias de{"group": "Grupo", "day": "Dia", "subject": "Sujeito"}, que precisamconcordar entre si na tela; viraram
_field_labels().da validação det/seg,
pyserial não instalado/não disponível,Total de Vídeos/Total de vídeos,Porta Arduino:/Porta do Arduino:.start_time_labelrenderizava"Início: Início: 12:34:56", repetindo novalor o rótulo da coluna vizinha.
stop_time_label, ao lado, nunca fez isso._usado como descarteSete ocorrências (
name, _ = ...,rec, _ = ...,h, w, _ = frame.shape)sombreavam o alias do gettext na função inteira — inclusive em chamadas
acima da atribuição. Todas foram pegas por
ruffF823 ou pormypy;nenhuma pela leitura do diff.
Tradução em tempo de import
Dicionários de rótulos em corpo de módulo/classe congelam o idioma no import:
_METHOD_LABELS,_METHOD_OPTIONS,_BADGE_*_TEXTe oMODE_DESCRIPTIONS(
typing.ClassVar). Todos viraram função.tests/i18n/test_no_import_time_translation.pyguarda isso.Testes
or "Template" in ...,or "vazio" in ..., dois"indispon" not in ...) passariam mesmo se o avisotestado fosse exibido.
(
set("Detecção (det)")); com o rótulo traduzido paravam de resolver e ohintficava vazio — o que faziatest_animal_method_hint_cleared_for_seg(que exige hint vazio) passar pelo motivo errado.
test_analysis_metadata_displaynão testava o código de produção: ostate_synchronizerera umMock()cujosside_effectreimplementavam osdois formatadores, copiando as f-strings que as asserções depois conferiam.
Agora injeta um
StateSynchronizerreal; verificado por mutação (alterando oformatador de produção, os 4 testes falham — antes, passavam).
update_translations.py:--previousdoBabel enrolava o msgid anterior no meio da string ao gerar o comentário
#|,produzindo um
.poque opolibrecusa ler.--no-fuzzy-matchingresolve.O que NÃO foi traduzido, de propósito
Grupo_*/Dia_*/Sujeito_*, as chaves desession_duration_overrides, a abapor_animal, as chaves do dictreporte os cabeçalhos de coluna do.xlsxsão contratos de persistência. Traduzi-los não produz um app em inglês, produz
um app que não lê os projetos que ele mesmo gravou. Lista em
scripts/i18n_allowlist.txt.TOTAL: 0prova apenas que não sobrou português ACENTUADO. O scanner — eportanto o ratchet construído sobre ele — é cego a
Salvar,Nenhum video,Remover,dias,grupos. Foi exatamente assim quecoordinators/ecore/recording/seguiram publicando texto em português depois de travados, efoi só por leitura manual que apareceram.
Uma varredura dedicada a português sem acento continua pendente; não foi
feita nesta PR e não deve ser considerada coberta por ela.
Verificação
ruff check .limpomypy .limpo em 698 arquivospytest -q: 4197 passedpytest -m gui -n0: 1079 passedi18n_scan.py src/zebtrack --format=count:TOTAL: 0🤖 Generated with Claude Code