Handy/src/i18n/locales/pt/translation.json
Viren Mohindra 58cda3f3b3
fix: sha256 verification to prevent corrupt partial download loop (#1095)
* fix: add SHA256 verification to prevent corrupt partial download loop

Fixes issue #858 — corrupt .partial files caused an infinite loop where
a failed/cancelled download would resume from EOF, pass extraction, and
loop forever with no way to recover without manual file deletion.

Three-part fix:
- Add sha2 crate and compute_sha256() helper; verify hash post-download
  before extraction/rename for all 15 built-in models. Hash mismatch
  deletes the .partial so the next attempt starts fresh.
- Delete .partial on extraction failure (independent of checksums) so
  corrupt archives can't re-enter the loop via the same path.
- Emit model-download-failed event from the command handler so all
  callers (ModelsSettings, Onboarding) get consistent error feedback
  via a central store listener rather than per-caller return-value checks.

SHA256 hashes hardcoded alongside each model's URL in ModelInfo.
Custom models (sha256: None) skip verification silently.

* chore: remove doc comment from sha256 field

Doc comments on exported specta types propagate into bindings.ts as
inline JSDoc — not useful in generated TS. Removing at the source.

* test: add SHA256 verification unit tests

Extracts inline verify logic into ModelManager::verify_sha256() and
covers all four branches:
- None expected hash → skipped (custom models)
- Matching hash → ok, file kept
- Mismatched hash → error returned, partial file deleted
- Unreadable file → error returned

Ensures corrupt partial downloads are always cleaned up so the next
attempt starts fresh.

* chore: cargo fmt

* fix: clear cancel flag on sha256 failure, spawn_blocking for hash, fallback cleanup

- remove cancel_flags entry on sha256 failure to prevent a stale flag
  from immediately cancelling the next retry attempt (HIGH)
- add fallback state cleanup in modelStore when result.status != ok,
  so spinner clears even if model-download-failed event is missed (HIGH)
- run compute_sha256 in spawn_blocking to avoid stalling the async
  executor while hashing large model files (up to 1.6 GB) (MEDIUM)

* emit some more events for the ui to pick up

* add translations + format

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-21 14:19:26 +08:00

592 lines
24 KiB
JSON

{
"tray": {
"settings": "Configurações...",
"checkUpdates": "Verificar Atualizações...",
"copyLastTranscript": "Copiar última transcrição",
"unloadModel": "Descarregar modelo",
"model": "Modelo",
"quit": "Sair",
"cancel": "Cancelar"
},
"sidebar": {
"general": "Geral",
"models": "Modelos",
"advanced": "Avançado",
"postProcessing": "Pós-Processamento",
"history": "Histórico",
"debug": "Depuração",
"about": "Sobre"
},
"onboarding": {
"subtitle": "Para começar, escolha um modelo de transcrição",
"recommended": "Recomendado",
"download": "Baixar",
"downloading": "Baixando...",
"customModelDescription": "Não suportado oficialmente",
"downloadFailed": "Falha no download. Por favor, tente novamente.",
"modelCard": {
"accuracy": "precisão",
"speed": "velocidade"
},
"models": {
"small": {
"name": "Whisper Small",
"description": "Rápido e razoavelmente preciso."
},
"medium": {
"name": "Whisper Medium",
"description": "Boa precisão, velocidade média"
},
"turbo": {
"name": "Whisper Turbo",
"description": "Precisão e velocidade balanceadas."
},
"large": {
"name": "Whisper Large",
"description": "Boa precisão, mas lento."
},
"parakeet-tdt-0.6b-v2": {
"name": "Parakeet V2",
"description": "Apenas inglês. O melhor modelo para falantes de inglês."
},
"parakeet-tdt-0.6b-v3": {
"name": "Parakeet V3",
"description": "Rápido e preciso"
},
"moonshine-base": {
"name": "Moonshine Base",
"description": "Muito rápido, apenas inglês. Lida bem com sotaques."
},
"moonshine-tiny-streaming-en": {
"name": "Moonshine V2 Tiny",
"description": "Ultrarrápido, apenas inglês"
},
"moonshine-small-streaming-en": {
"name": "Moonshine V2 Small",
"description": "Rápido, apenas inglês. Bom equilíbrio entre velocidade e precisão."
},
"moonshine-medium-streaming-en": {
"name": "Moonshine V2 Medium",
"description": "Apenas inglês. Alta qualidade."
},
"breeze-asr": {
"name": "Breeze ASR",
"description": "Otimizado para mandarim taiwanês. Suporte para alternância de código."
},
"sense-voice-int8": {
"name": "SenseVoice",
"description": "Muito rápido. Chinês, inglês, japonês, coreano, cantonês."
},
"gigaam-v3-e2e-ctc": {
"name": "GigaAM v3",
"description": "Reconhecimento de voz em russo. Rápido e preciso."
},
"canary-180m-flash": {
"name": "Canary 180M Flash",
"description": "Muito rápido. Inglês, alemão, espanhol, francês. Suporta tradução."
},
"canary-1b-v2": {
"name": "Canary 1B v2",
"description": "Multilíngue preciso. 25 idiomas europeus. Suporta tradução."
}
},
"errors": {
"loadModels": "Falha ao carregar modelos disponíveis",
"downloadModel": "Falha ao baixar modelo: {{error}}"
},
"permissions": {
"title": "Permissões Necessárias",
"description": "O Handy precisa de algumas permissões para funcionar corretamente.",
"microphone": {
"title": "Acesso ao Microfone",
"description": "Necessário para ouvir sua voz para transcrição."
},
"accessibility": {
"title": "Acesso à Acessibilidade",
"description": "Necessário para digitar o texto transcrito em seus aplicativos."
},
"grant": "Conceder Permissão",
"granted": "Concedido",
"waiting": "Aguardando...",
"allGranted": "Tudo pronto!",
"errors": {
"checkFailed": "Erro ao verificar permissões. Por favor, tente novamente.",
"requestFailed": "Erro ao solicitar permissão. Por favor, tente novamente."
}
}
},
"modelSelector": {
"custom": "Personalizado",
"active": "Ativo",
"noModelsAvailable": "Nenhum modelo disponível",
"verifying": "Verificando {{modelName}}...",
"verifyingGeneric": "Verificando...",
"extracting": "Extraindo {{modelName}}...",
"extractingMultiple": "Extraindo {{count}} modelos...",
"extractingGeneric": "Extraindo...",
"downloading": "Baixando {{percentage}}%",
"downloadingMultiple": "Baixando {{count}} modelos...",
"modelReady": "Modelo Pronto",
"loading": "Carregando {{modelName}}...",
"loadingGeneric": "Carregando...",
"modelError": "Erro no Modelo",
"modelUnloaded": "Modelo Descarregado",
"noModelDownloadRequired": "Sem Modelo - Download Necessário",
"deleteModel": "Excluir {{modelName}}",
"switching": "Alternando...",
"downloadSpeed": "{{speed}} MB/s",
"capabilities": {
"languageSelection": "Suporta vários idiomas de entrada",
"multiLanguage": "Multi-idioma",
"translation": "Pode traduzir para inglês",
"translate": "Traduzir para inglês",
"singleLanguage": "Suporta apenas este idioma",
"languageOnly": "Apenas {{language}}"
},
"cancel": "Cancelar",
"cancelDownload": "Cancelar download"
},
"settings": {
"modelSettings": {
"title": "Configurações de {{model}}",
"noSettingsNeeded": "Este modelo funciona automaticamente sem necessidade de configuração."
},
"general": {
"title": "Geral",
"shortcut": {
"title": "Atalhos do Handy",
"description": "Configure atalhos de teclado para iniciar a gravação de voz para texto",
"loading": "Carregando atalhos...",
"none": "Nenhum atalho configurado",
"notFound": "Atalho não encontrado",
"pressKeys": "Pressione as teclas...",
"bindings": {
"transcribe": {
"name": "Atalho de Transcrição",
"description": "O atalho de teclado para gravar e transcrever sua voz."
},
"cancel": {
"name": "Atalho de Cancelar",
"description": "O atalho de teclado para cancelar a gravação atual."
},
"transcribe_with_post_process": {
"name": "Tecla de Pós-Processamento",
"description": "Opcional: Uma tecla de atalho dedicada que sempre aplica pós-processamento com IA à sua transcrição."
}
},
"errors": {
"restore": "Falha ao restaurar atalho original",
"set": "Falha ao definir atalho: {{error}}",
"reset": "Falha ao redefinir atalho para o valor original"
}
},
"language": {
"title": "Idioma",
"description": "Selecione o idioma para reconhecimento de fala. Auto detectará automaticamente o idioma, enquanto selecionar um idioma específico pode melhorar a precisão para esse idioma.",
"descriptionUnsupported": "O modelo Parakeet detecta automaticamente o idioma. Não é necessária seleção manual.",
"searchPlaceholder": "Buscar idiomas...",
"noResults": "Nenhum idioma encontrado",
"auto": "Auto"
},
"pushToTalk": {
"label": "Pressionar para Falar",
"description": "Segure para gravar, solte para parar"
}
},
"models": {
"title": "Modelos de Transcrição",
"description": "Selecione um modelo de transcrição ou baixe modelos adicionais. Diferentes modelos oferecem diferentes níveis de precisão e velocidade.",
"downloaded": "Baixados",
"available": "Disponíveis para Download",
"deleteConfirm": "Tem certeza de que deseja excluir {{modelName}}? Você precisará baixá-lo novamente para usá-lo.",
"deleteActiveConfirm": "{{modelName}} é o seu modelo ativo. Excluí-lo interromperá as transcrições até que você selecione um novo modelo. Tem certeza?",
"deleteTitle": "Excluir Modelo",
"filters": {
"all": "Todos",
"multiLanguage": "Multi-idioma",
"translation": "Tradução",
"allLanguages": "Todos os Idiomas"
},
"noModelsMatch": "Nenhum modelo corresponde a este filtro.",
"yourModels": "Modelos baixados",
"availableModels": "Disponíveis para download"
},
"sound": {
"title": "Som",
"microphone": {
"title": "Microfone",
"description": "Selecione seu dispositivo de microfone preferido",
"placeholder": "Selecionar microfone...",
"loading": "Carregando..."
},
"audioFeedback": {
"label": "Feedback de Áudio",
"description": "Reproduzir som quando a gravação iniciar e parar"
},
"outputDevice": {
"title": "Dispositivo de Saída",
"description": "Selecione seu dispositivo de saída de áudio preferido para sons de feedback",
"placeholder": "Selecionar dispositivo de saída...",
"loading": "Carregando..."
},
"volume": {
"title": "Volume",
"description": "Ajustar o volume dos sons de feedback de áudio"
}
},
"advanced": {
"title": "Avançado",
"groups": {
"app": "Aplicativo",
"output": "Saída",
"transcription": "Transcrição",
"history": "Histórico",
"experimental": "Experimental"
},
"experimentalToggle": {
"label": "Recursos Experimentais",
"description": "Ativar recursos experimentais que ainda estão em desenvolvimento."
},
"lazyStreamClose": {
"label": "Manter microfone aberto entre transcrições",
"description": "Mantém o fluxo do microfone aberto por 30 segundos após parar a gravação, reduzindo a latência em transcrições consecutivas. Pode degradar a qualidade do áudio Bluetooth enquanto ativo."
},
"acceleration": {
"whisper": {
"title": "Aceleração Whisper",
"description": "Aceleração por hardware para modelos Whisper. Auto usa GPU se disponível (Metal no macOS, Vulkan no Windows/Linux)."
},
"ort": {
"title": "Aceleração ONNX",
"description": "Aceleração por hardware para modelos ONNX (Parakeet, Canary, Moonshine, etc.). DirectML no Windows é experimental. Os modelos podem falhar na transcrição."
}
},
"startHidden": {
"label": "Iniciar Oculto",
"description": "Iniciar na bandeja do sistema sem abrir a janela."
},
"autostart": {
"label": "Iniciar na Inicialização",
"description": "Iniciar automaticamente o Handy quando você fizer login no seu computador."
},
"showTrayIcon": {
"label": "Mostrar ícone na bandeja",
"description": "Exibir o ícone do Handy na bandeja do sistema."
},
"overlay": {
"title": "Posição da Sobreposição",
"description": "Exibir sobreposição de feedback visual durante gravação e transcrição. No Linux, 'Nenhum' é recomendado.",
"options": {
"none": "Nenhum",
"bottom": "Inferior",
"top": "Superior"
}
},
"pasteMethod": {
"title": "Método de Colar",
"description": "Escolha como o texto é inserido. Direto: simula digitação via entrada do sistema. Nenhum: ignora colar, apenas atualiza histórico/área de transferência.",
"options": {
"clipboard": "Área de Transferência ({{modifier}}+V)",
"clipboardCtrlShiftV": "Área de Transferência (Ctrl+Shift+V)",
"clipboardShiftInsert": "Área de Transferência (Shift+Insert)",
"direct": "Direto",
"none": "Nenhum",
"externalScript": "Script externo"
},
"externalScriptPlaceholder": "/caminho/para/seu/script.sh"
},
"typingTool": {
"title": "Ferramenta de digitação",
"description": "Escolha qual ferramenta de digitação do Linux usar para o método de colagem direta. Auto detectará e usará automaticamente a melhor ferramenta disponível para o seu sistema.",
"options": {
"auto": "Auto (Recomendado)"
}
},
"clipboardHandling": {
"title": "Manipulação da Área de Transferência",
"description": "Não Modificar Área de Transferência preserva o conteúdo atual da área de transferência após a transcrição. Copiar para Área de Transferência deixa o resultado da transcrição na área de transferência após colar.",
"options": {
"dontModify": "Não Modificar Área de Transferência",
"copyToClipboard": "Copiar para Área de Transferência"
}
},
"autoSubmit": {
"title": "Envio automático",
"description": "Envia automaticamente a combinação de teclas selecionada após a inserção do texto. Cmd+Enter aplica-se no macOS, enquanto Windows/Linux usam Super+Enter.",
"options": {
"off": "Desativado",
"enter": "Enter",
"cmdEnter": "Cmd+Enter",
"superEnter": "Super+Enter",
"ctrlEnter": "Ctrl+Enter"
}
},
"translateToEnglish": {
"label": "Traduzir para Inglês",
"description": "Traduzir automaticamente fala de outros idiomas para inglês durante a transcrição.",
"descriptionUnsupported": "Tradução não é suportada pelo modelo {{model}}."
},
"modelUnload": {
"title": "Descarregar Modelo",
"description": "Liberar automaticamente memória GPU/CPU quando o modelo não for usado pelo tempo especificado",
"options": {
"never": "Nunca",
"immediately": "Imediatamente",
"min2": "Após 2 minutos",
"min5": "Após 5 minutos",
"min10": "Após 10 minutos",
"min15": "Após 15 minutos",
"hour1": "Após 1 hora",
"sec15": "Após 15 segundos (Depuração)"
}
},
"customWords": {
"title": "Palavras Personalizadas",
"description": "Adicione palavras que são frequentemente mal ouvidas ou mal escritas durante a transcrição. O sistema irá automaticamente corrigir palavras semelhantes para corresponder à sua lista.",
"placeholder": "Adicionar uma palavra",
"add": "Adicionar",
"remove": "Remover {{word}}",
"duplicate": "\"{{word}}\" já existe"
}
},
"postProcessing": {
"title": "Pós-Processamento",
"hotkey": {
"title": "Tecla de atalho"
},
"api": {
"title": "API (Compatível com OpenAI)",
"provider": {
"title": "Provedor",
"description": "Selecione um provedor compatível com OpenAI."
},
"appleIntelligence": {
"title": "Apple Intelligence",
"description": "Executa totalmente no dispositivo. Nenhuma chave de API ou acesso à rede é necessário.",
"requirements": "Requer um Mac Apple Silicon executando macOS Tahoe (26.0) ou posterior. Apple Intelligence deve estar habilitado nas Configurações do Sistema.",
"unavailable": "Apple Intelligence não está disponível neste dispositivo. Requer um Mac Apple Silicon executando macOS Tahoe (26.0) ou posterior com Apple Intelligence habilitado nas Configurações do Sistema."
},
"baseUrl": {
"title": "URL Base",
"description": "URL base da API para o provedor selecionado. Apenas o provedor personalizado pode ser editado.",
"placeholder": "https://api.openai.com/v1"
},
"apiKey": {
"title": "Chave da API",
"description": "Chave da API para o provedor selecionado.",
"placeholder": "sk-..."
},
"model": {
"title": "Modelo",
"descriptionApple": "Forneça um limite numérico opcional de tokens ou mantenha a predefinição padrão no dispositivo.",
"descriptionCustom": "Forneça o identificador do modelo esperado pelo seu endpoint personalizado.",
"descriptionDefault": "Escolha um modelo exposto pelo provedor selecionado.",
"placeholderApple": "Apple Intelligence",
"placeholderWithOptions": "Buscar ou selecionar um modelo",
"placeholderNoOptions": "Digite o nome de um modelo",
"refreshModels": "Atualizar modelos"
}
},
"prompts": {
"title": "Prompt",
"selectedPrompt": {
"title": "Prompt Selecionado",
"description": "Selecione um modelo para refinar transcrições ou crie um novo. Use ${output} dentro do texto do prompt para referenciar a transcrição capturada."
},
"noPrompts": "Nenhum prompt disponível",
"selectPrompt": "Selecionar um prompt",
"createNew": "Criar Novo Prompt",
"promptLabel": "Rótulo do Prompt",
"promptLabelPlaceholder": "Digite o nome do prompt",
"promptInstructions": "Instruções do Prompt",
"promptInstructionsPlaceholder": "Escreva as instruções para executar após a transcrição. Exemplo: Melhore a gramática e clareza do seguinte texto: ${output}",
"promptTip": "Dica: Use <code>${output}</code> para inserir o texto transcrito no seu prompt.",
"updatePrompt": "Atualizar Prompt",
"deletePrompt": "Excluir Prompt",
"createPrompt": "Criar Prompt",
"cancel": "Cancelar",
"selectToEdit": "Selecione um prompt acima para visualizar e editar seus detalhes.",
"createFirst": "Clique em 'Criar Novo Prompt' acima para criar seu primeiro prompt de pós-processamento."
}
},
"history": {
"title": "Histórico",
"openFolder": "Abrir Pasta de Gravações",
"loading": "Carregando histórico...",
"empty": "Nenhuma transcrição ainda. Comece a gravar para construir seu histórico!",
"copyToClipboard": "Copiar transcrição para área de transferência",
"save": "Salvar transcrição",
"unsave": "Remover dos salvos",
"delete": "Excluir entrada",
"deleteError": "Falha ao excluir entrada. Por favor, tente novamente."
},
"debug": {
"title": "Depuração",
"logDirectory": {
"title": "Diretório de Logs",
"description": "Local onde os arquivos de log são armazenados"
},
"logLevel": {
"title": "Nível de Log",
"description": "Definir a verbosidade do registro"
},
"updateChecks": {
"label": "Verificar Atualizações",
"description": "Verificar automaticamente novas versões do Handy"
},
"soundTheme": {
"label": "Tema de Som",
"description": "Escolha um tema de som para feedback de início e parada de gravação"
},
"wordCorrectionThreshold": {
"title": "Limite de Correção de Palavras",
"description": "Sensibilidade para correções de palavras personalizadas"
},
"historyLimit": {
"title": "Limite de Histórico",
"description": "Número máximo de entradas de histórico a manter",
"entries": "entradas"
},
"recordingRetention": {
"title": "Excluir Gravações Automaticamente",
"description": "Excluir automaticamente gravações antigas para economizar espaço",
"never": "Nunca",
"preserveLimit": "Manter as últimas {{count}}",
"days3": "Após 3 dias",
"weeks2": "Após 2 semanas",
"months3": "Após 3 meses",
"placeholder": "Selecionar período de retenção..."
},
"alwaysOnMicrophone": {
"label": "Microfone Sempre Ativo",
"description": "Manter microfone ativo para resposta mais rápida"
},
"clamshellMicrophone": {
"title": "Microfone em Modo Fechado",
"description": "Microfone a usar quando a tampa do laptop está fechada"
},
"postProcessingToggle": {
"label": "Pós-Processamento",
"description": "Habilitar refinamento de texto com IA após a transcrição"
},
"muteWhileRecording": {
"label": "Silenciar Durante Gravação",
"description": "Silenciar áudio do sistema durante a gravação"
},
"appendTrailingSpace": {
"label": "Adicionar Espaço Final",
"description": "Adicionar um espaço após a transcrição colada"
},
"keyboardImplementation": {
"title": "Implementação do Teclado",
"description": "Escolha o sistema responsável pelos atalhos de teclado.",
"bindingsReset": "Os atalhos de teclado eram incompatíveis e foram redefinidos para os padrões"
},
"paths": {
"appData": "Dados do App:",
"models": "Modelos:",
"settings": "Configurações:"
},
"pasteDelay": {
"title": "Atraso de colagem",
"description": "Atraso antes de enviar a tecla de colar (em milissegundos). Aumente se o texto errado estiver sendo colado."
},
"recordingBuffer": {
"title": "Buffer de gravação extra",
"description": "Tempo extra (em milissegundos) para continuar gravando após soltar a tecla, para capturar áudio restante. 0 = sem buffer extra."
}
},
"about": {
"title": "Sobre",
"version": {
"title": "Versão",
"description": "Versão atual do Handy"
},
"appDataDirectory": {
"title": "Diretório de Dados do App",
"description": "Local onde o Handy armazena seus dados"
},
"sourceCode": {
"title": "Código Fonte",
"description": "Visualizar código fonte e contribuir",
"button": "Ver no GitHub"
},
"supportDevelopment": {
"title": "Apoiar o Desenvolvimento",
"description": "Ajude-nos a continuar construindo o Handy",
"button": "Doar"
},
"acknowledgments": {
"title": "Agradecimentos",
"whisper": {
"title": "Whisper.cpp",
"description": "Inferência de alto desempenho do modelo de reconhecimento automático de fala Whisper da OpenAI",
"details": "O Handy usa Whisper.cpp para processamento rápido e local de fala para texto. Agradecemos ao incrível trabalho de Georgi Gerganov e colaboradores."
}
}
}
},
"footer": {
"downloadingModel": "Baixando {{model}}...",
"checkingUpdates": "Verificando atualizações...",
"updateAvailable": "Atualização disponível: {{version}}",
"updateAvailableShort": "Atualização disponível",
"upToDate": "Atualizado",
"downloadUpdate": "Baixar Atualização",
"restart": "Reiniciar",
"updateCheckingDisabled": "Verificação de Atualização Desabilitada",
"downloading": "Baixando... {{progress}}%",
"installing": "Instalando...",
"preparing": "Preparando...",
"checkForUpdates": "Verificar atualizações"
},
"common": {
"loading": "Carregando...",
"save": "Salvar",
"cancel": "Cancelar",
"reset": "Redefinir",
"add": "Adicionar",
"remove": "Remover",
"delete": "Excluir",
"edit": "Editar",
"create": "Criar",
"update": "Atualizar",
"close": "Fechar",
"open": "Abrir",
"default": "Padrão",
"enabled": "Habilitado",
"disabled": "Desabilitado",
"on": "Ligado",
"off": "Desligado",
"yes": "Sim",
"no": "Não",
"noOptionsFound": "Nenhuma opção encontrada"
},
"accessibility": {
"permissionsRequired": "Permissões de Acessibilidade Necessárias",
"permissionsDescription": "O Handy precisa de permissões de acessibilidade para digitar o texto transcrito.",
"openSettings": "Abrir Configurações do Sistema",
"dismiss": "Dispensar"
},
"errors": {
"loadDirectory": "Erro ao carregar diretório: {{error}}",
"micPermissionDeniedTitle": "Acesso ao microfone negado",
"micPermissionDenied": {
"generic": "O acesso ao microfone foi negado pelo sistema operacional. Conceda a permissão do microfone nas configurações do sistema.",
"windows": "Ative o acesso ao microfone em Configurações → Privacidade e segurança → Microfone (incluindo acesso de aplicativos da área de trabalho).",
"macos": "Conceda o acesso ao microfone em Ajustes do Sistema → Privacidade e Segurança → Microfone.",
"linux": "Conceda o acesso ao microfone nas configurações de som ou privacidade do seu sistema."
},
"recordingFailed": "Falha ao iniciar a gravação: {{error}}",
"modelLoadFailed": "Falha ao carregar o modelo: {{model}}",
"modelLoadFailedUnknown": "modelo desconhecido"
},
"appLanguage": {
"title": "Idioma da Aplicação",
"description": "Alterar o idioma da interface do Handy"
},
"overlay": {
"transcribing": "Transcrevendo...",
"processing": "Processando..."
}
}