feat(i18n): API responde erros no idioma do usuário - #36
Merged
Conversation
A interface passou a atender pt e en, mas a API respondia sempre no idioma em que a mensagem foi escrita - e estava escrita nos dois, sem criterio: das 255 mensagens, 182 em ingles e 73 em portugues. Quem usava em ingles recebia "Notificação não encontrada"; quem usava em portugues recebia "Record not found". A traducao acontece na borda, num hook preSerialization, e nao em cada throw. Reescrever 255 pontos em 15 modulos seria um diff enorme e arriscado para o mesmo resultado; com o hook, a chave do catalogo e a propria mensagem como ja esta escrita. Texto fora do catalogo passa intacto - pode ser conteudo do proprio usuario. O idioma sai da preferencia salva no Perfil quando ha usuario autenticado e, sem ela, do Accept-Language (respeitando q-values). O frontend passa a mandar o cabecalho a partir do i18n, senao o backend seguiria o idioma do navegador, que nao acompanha a troca feita dentro da plataforma. Verificado num Fastify real: mesma rota devolvendo pt e en, pelo caminho normal e pelo tratador global de excecao, com conteudo do usuario preservado. 38 testes passando (eram 25).
Contributor
There was a problem hiding this comment.
Pull request overview
Este PR padroniza as mensagens de erro da API no idioma do usuário, movendo a tradução para a borda (hook preSerialization) e garantindo consistência via catálogo e testes, além de fazer o frontend enviar o Accept-Language conforme o i18n da plataforma.
Changes:
- Frontend passa a enviar
Accept-Languageem todas as requisições via interceptor do Axios. - Backend adiciona módulo de i18n com resolução de idioma (perfil → header → padrão) e tradução de
error/messagenopreSerialization. - Adiciona catálogo bilíngue de mensagens e teste “guarda” para evitar regressões.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/services/api.js | Inclui Accept-Language baseado no i18n.language em todas as requisições. |
| backend/src/server.js | Adiciona hook preSerialization para traduzir error/message no payload de resposta. |
| backend/src/i18n/index.js | Implementa parsing de Accept-Language, resolução de idioma e tradução de payloads. |
| backend/src/i18n/messages.js | Introduz catálogo bilíngue (pt/en) com chaves baseadas no texto literal existente. |
| backend/tests/i18n-mensagens.test.js | Adiciona testes para cobertura/consistência do catálogo e lógica de idioma/tradução. |
Suppressed comments (1)
backend/src/i18n/messages.js:112
- Para chaves em inglês, o valor em inglês deveria coincidir com a chave para evitar que respostas em
ensejam reescritas/encurtadas pela tradução (aqui some "successfully").
'KB access control updated successfully': ['Controle de acesso do KB atualizado', 'KB access control updated'],
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| 'Invalid group ID': ['ID de grupo inválido', 'Invalid group ID'], | ||
| 'Invalid parent group ID': ['ID de grupo pai inválido', 'Invalid parent group ID'], | ||
| 'Parent group not found': ['Grupo pai não encontrado', 'Parent group not found'], | ||
| 'User added to group successfully': ['Usuário adicionado ao grupo', 'User added to the group'], |
| 'Serviço de email não configurado. Entre em contato com o administrador.': ['Serviço de email não configurado. Entre em contato com o administrador.', 'Email service is not configured. Please contact an administrator.'], | ||
| 'Serviço de e-mail não configurado. Peça ao administrador para redefinir sua senha.': ['Serviço de e-mail não configurado. Peça ao administrador para redefinir sua senha.', 'Email service is not configured. Ask an administrator to reset your password.'], | ||
| 'Falha ao conectar com o servidor de e-mail. Tente novamente mais tarde.': ['Falha ao conectar com o servidor de e-mail. Tente novamente mais tarde.', 'Could not reach the email server. Please try again later.'], | ||
| 'Falha ao conectar com servidor de email. Tente novamente mais tarde.': ['Falha ao conectar com servidor de e-mail. Tente novamente mais tarde.', 'Could not reach the email server. Please try again later.'], |
Comment on lines
+22
to
+24
| // O backend traduz as mensagens de erro pelo idioma do pedido. Sem | ||
| // isto ele cairia no Accept-Language do navegador, que nao acompanha | ||
| // a troca de idioma feita dentro da plataforma. |
Comment on lines
+44
to
+57
| const padrao = /\b(?:error|message)\s*:\s*'((?:[^'\\\n]|\\.)*)'/g; | ||
|
|
||
| for (const file of collectFiles(SRC)) { | ||
| const relativo = path.relative(SRC, file).replace(/\\/g, '/'); | ||
| if (relativo.startsWith('i18n/')) continue; | ||
|
|
||
| const code = fs.readFileSync(file, 'utf8'); | ||
| padrao.lastIndex = 0; | ||
| let match; | ||
| while ((match = padrao.exec(code)) !== null) { | ||
| const texto = match[1].replace(/\\'/g, "'"); | ||
| if (!encontradas.has(texto)) encontradas.set(texto, relativo); | ||
| } | ||
| } |
Merged
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.
O problema
A interface passou a atender pt e en, mas a API continuou respondendo no idioma
em que cada mensagem foi escrita — e estava escrita nos dois, sem critério.
Das 255 mensagens, 182 em inglês e 73 em português. Na prática: quem usa a
plataforma em inglês recebe
Notificação não encontrada, e quem usa emportuguês recebe
Record not found. Nenhum dos dois idiomas era servido deforma consistente.
A abordagem
A tradução acontece na borda, num hook
preSerialization, e não em cadathrow. Reescrever 255 pontos espalhados por 15 módulos seria um diff enorme earriscado para chegar no mesmo resultado — e cada
thrownovo voltaria a seruma chance de esquecer. Com o hook, a chave do catálogo é a própria mensagem
como já está escrita no código, então nenhuma rota muda.
Texto fora do catálogo passa intacto: pode ser conteúdo do próprio usuário
ou mensagem de biblioteca, e reescrever isso seria pior do que deixar como está.
Escolha do idioma, em ordem: a preferência salva no Perfil (usuário
autenticado) →
Accept-Language, respeitando q-values → padrãopt. Ofrontend passa a enviar o cabeçalho a partir do i18n; sem isso o backend
seguiria o idioma do navegador, que não acompanha a troca feita dentro da
plataforma.
A trava
backend/tests/i18n-mensagens.test.js. O modo como isso quebra é silencioso:alguém escreve um
thrownovo, não registra a mensagem, e ela simplesmente sainum idioma só — sem erro, sem aviso. O teste cobra que toda mensagem escrita nas
rotas esteja no catálogo, que nenhuma entrada tenha um lado vazio, e que o
catálogo não acumule órfãs (catálogo com lixo deixa de ser confiável).
Um detalhe do extrator que vale registrar:
log.error('Auth middleware error:', error)tem um
error:dentro da string, e sem restringir a captura a uma únicalinha ela corria até a aspa da linha seguinte, engolindo código como se fosse
mensagem.
Verificação
Subi um Fastify real com o mesmo hook e bati nele:
/kb/1comAccept-Language: pt-BRRegistro não encontrado/kb/1comAccept-Language: en-US,en;q=0.9Record not foundNotificação não encontradaNotification not foundfr-FR)messagede conteúdo do usuárioCobre os dois caminhos: resposta montada na rota e exceção passando pelo
tratador global.
npm test→ 38 passam (eram 25)npm test→ 7 passam ·vite buildok