From b76bc32b8bec07b56197efc68c3802cc2548d396 Mon Sep 17 00:00:00 2001 From: arthurbarret0 <54091091+arthurbarret0@users.noreply.github.com> Date: Tue, 2 Jun 2026 14:22:16 -0300 Subject: [PATCH 1/2] Add Portuguese (Brazil) localization file --- apps/web/src/locales/pt-BR.json | 312 ++++++++++++++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 apps/web/src/locales/pt-BR.json diff --git a/apps/web/src/locales/pt-BR.json b/apps/web/src/locales/pt-BR.json new file mode 100644 index 000000000..4f402c9c6 --- /dev/null +++ b/apps/web/src/locales/pt-BR.json @@ -0,0 +1,312 @@ +{ + "widget.shell.heading.feedback": "Compartilhe suas ideias", + "widget.shell.heading.help": "Central de Ajuda", + "widget.shell.heading.changelog": "Novidades", + "widget.shell.tab.feedback": "Feedback", + "widget.shell.tab.changelog": "Changelog", + "widget.shell.tab.help": "Ajuda", + "widget.shell.aria.goBack": "Voltar", + "widget.shell.aria.close": "Fechar widget de feedback", + "widget.shell.aria.userMenu": "Menu do usuário", + "widget.shell.poweredBy": "Desenvolvido por {brand}", + "widget.home.input.placeholder": "Qual é a sua ideia?", + "widget.home.input.details": "Adicione mais detalhes...", + "widget.home.posting.postingTo": "Publicando em", + "widget.home.posting.postingAs": "Publicando como {name}", + "widget.home.posting.postingAnonymously": "Publicando anonimamente", + "widget.home.posting.emailRequired": "Seu email é obrigatório", + "widget.home.form.emailPlaceholder": "Seu email", + "widget.home.form.namePlaceholder": "Nome (opcional)", + "widget.home.form.cancel": "Cancelar", + "widget.home.form.submit": "Enviar", + "widget.home.form.submitting": "Enviando...", + "widget.home.form.errorEmail": "Não foi possível verificar seu email. Tente novamente.", + "widget.home.form.errorSession": "Não foi possível criar a sessão. Tente novamente.", + "widget.home.form.errorNetwork": "Erro de rede. Tente novamente.", + "widget.home.similar.heading": "Ideias semelhantes", + "widget.home.popular.heading": "Ideias populares", + "widget.home.popular.search.placeholder": "Pesquisar ideias...", + "widget.home.popular.search.aria": "Pesquisar ideias", + "widget.home.popular.search.searching": "Pesquisando...", + "widget.home.popular.search.noResults": "Nenhuma ideia encontrada", + "widget.home.popular.search.noResultsHint": "Tente outro termo de busca", + "widget.home.popular.empty": "Ainda não há ideias", + "widget.home.popular.emptyBoard": "Ainda não há ideias neste quadro", + "widget.home.popular.emptyHint": "Seja o primeiro a compartilhar uma!", + "widget.home.popular.loading": "Carregando...", + "widget.home.scroll.ariaLeft": "Rolar para a esquerda", + "widget.home.scroll.ariaRight": "Rolar para a direita", + "widget.postDetail.error.couldNotLoad": "Não foi possível carregar o post", + "widget.postDetail.error.somethingWrong": "Algo deu errado", + "widget.postDetail.authorFallback": "Anônimo", + "widget.postDetail.officialResponse": "Resposta oficial", + "widget.postDetail.comments": "{count, plural, one {# comentário} other {# comentários}}", + "widget.postDetail.commentsLocked": "Os comentários estão bloqueados neste post", + "widget.postDetail.loginToComment": "Entre para participar da conversa", + "widget.postDetail.teamAuthorFallback": "Equipe", + "widget.commentList.empty": "Ainda não há comentários. Seja o primeiro a compartilhar sua opinião!", + "widget.commentList.deleted": "[excluído]", + "widget.commentList.removed": "[removido]", + "widget.commentList.authorFallback": "Anônimo", + "widget.commentList.reply": "Responder", + "widget.commentList.replyPlaceholder": "Responder para {name}...", + "widget.commentList.post": "Publicar", + "widget.commentList.cancel": "Cancelar", + "widget.commentList.teamBadge": "Equipe", + "widget.commentList.pinnedBadge": "Fixado", + "widget.commentForm.placeholder": "Escreva um comentário...", + "widget.commentForm.emailPlaceholder": "Email", + "widget.commentForm.namePlaceholder": "Nome (opcional)", + "widget.commentForm.post": "Publicar", + "widget.commentForm.postingAs": "Publicando como {name}", + "widget.commentForm.errorEmail": "Não foi possível verificar o email. Tente novamente.", + "widget.help.searchPlaceholder": "Pesquisar artigos de ajuda...", + "widget.help.searching": "Pesquisando...", + "widget.help.noResults": "Nenhum resultado encontrado", + "widget.help.noResultsHint": "Tente palavras-chave diferentes ou navegue pelas categorias.", + "widget.help.emptyHeading": "Buscar ajuda", + "widget.help.emptyHint": "Digite uma pergunta ou palavra-chave acima para encontrar artigos de ajuda.", + "widget.helpDetail.loading": "Carregando...", + "widget.helpDetail.notFound": "Artigo não encontrado", + "widget.changelog.loading": "Carregando changelog...", + "widget.changelog.empty": "Ainda não há atualizações", + "widget.changelog.emptyHint": "Volte em breve para conferir as últimas atualizações do produto.", + "widget.changelog.loadingMore": "Carregando...", + "widget.changelogDetail.loading": "Carregando...", + "widget.changelogDetail.notFound": "Registro não encontrado", + "widget.emailCapture.heading": "Informe seu email para continuar", + "widget.emailCapture.placeholder": "voce@exemplo.com", + "widget.emailCapture.submit": "Continuar", + "widget.emailCapture.error": "Algo deu errado. Tente novamente.", + "widget.voteButton.ariaVote": "Votar ({count, plural, one {# voto} other {# votos}})", + "widget.voteButton.ariaRemoveVote": "Remover voto ({count, plural, one {# voto} other {# votos}})", + "portal.header.nav.feedback": "Feedback", + "portal.header.nav.roadmap": "Roadmap", + "portal.header.nav.changelog": "Changelog", + "portal.header.nav.helpCenter": "Central de Ajuda", + "portal.header.auth.logIn": "Entrar", + "portal.header.auth.signUp": "Criar conta", + "portal.header.auth.signOut": "Sair", + "portal.header.auth.settings": "Configurações", + "portal.header.auth.admin": "Admin", + "portal.header.theme.toggleLabel": "Alternar tema", + "portal.header.theme.system": "Sistema", + "portal.header.theme.light": "Claro", + "portal.header.theme.dark": "Escuro", + "portal.feedback.empty.comingSoonTitle": "Em breve", + "portal.feedback.empty.comingSoonDescription": "{orgName} está configurando o portal de feedback. Volte em breve para compartilhar suas ideias e sugestões.", + "portal.feedback.list.noPostsFiltered": "Nenhum post corresponde aos seus filtros.", + "portal.feedback.list.noPostsYet": "Ainda não há posts.", + "portal.feedback.header.postingTo": "Publicando em", + "portal.feedback.header.selectBoard": "Selecione um quadro", + "portal.feedback.header.titlePlaceholder": "Qual é a sua ideia?", + "portal.feedback.header.detailsPlaceholder": "Adicione mais detalhes... Digite / para ver comandos", + "portal.feedback.header.postingAs": "Publicando como", + "portal.feedback.header.signOut": "sair", + "portal.feedback.header.postingAnonymously": "Publicando anonimamente", + "portal.feedback.header.signInToPost": "Entre para postar", + "portal.feedback.header.cancel": "Cancelar", + "portal.feedback.header.submit": "Enviar", + "portal.feedback.header.submitting": "Enviando...", + "portal.feedback.header.errorSelectBoard": "Selecione um quadro", + "portal.feedback.header.errorAddTitle": "Adicione um título", + "portal.feedback.header.errorSignIn": "Entre para enviar feedback", + "portal.feedback.header.errorSession": "Falha ao criar a sessão", + "portal.feedback.header.errorSubmit": "Falha ao enviar feedback", + "portal.feedback.header.submitTooltipSignIn": "Entre para enviar feedback", + "portal.feedback.header.toastSubmitted": "Feedback enviado", + "portal.feedback.header.toastView": "Ver", + "portal.feedback.toolbar.sortTop": "Mais votados", + "portal.feedback.toolbar.sortNew": "Novos", + "portal.feedback.toolbar.sortTrending": "Em alta", + "portal.feedback.toolbar.search": "Pesquisar", + "portal.feedback.toolbar.searchPlaceholder": "Pesquisar posts...", + "portal.feedback.toolbar.searchSubmit": "Pesquisar", + "portal.feedback.toolbar.clearSearch": "Limpar pesquisa", + "portal.feedback.filter.clearAll": "Limpar tudo", + "portal.feedback.sidebar.boards": "Quadros", + "portal.feedback.sidebar.viewAllPosts": "Ver todos os posts", + "portal.feedback.sidebar.poweredBy": "Desenvolvido por {brand}", + "portal.feedback.similarPosts.heading": "Ideias semelhantes", + "portal.postDetail.comments": "{count, plural, one {# comentário} other {# comentários}}", + "portal.postDetail.postActions.ariaLabel": "Ações do post", + "portal.postDetail.postActions.edit": "Editar", + "portal.postDetail.postActions.delete": "Excluir", + "portal.postDetail.edit.titlePlaceholder": "Qual é a sua ideia?", + "portal.postDetail.edit.detailsPlaceholder": "Adicione mais detalhes... Digite / para ver comandos", + "portal.postDetail.edit.cancel": "Cancelar", + "portal.postDetail.edit.save": "Salvar", + "portal.postDetail.edit.saving": "Salvando...", + "portal.postDetail.related": "Relacionados", + "portal.postDetail.mergeBanner.mergedInto": "Este feedback foi mesclado com", + "portal.postDetail.mergeBanner.votesCount": "Os votos e atividades agora contam para o item vinculado.", + "portal.postDetail.metadata.upvotes": "Votos positivos", + "portal.postDetail.metadata.status": "Status", + "portal.postDetail.metadata.board": "Quadro", + "portal.postDetail.metadata.tags": "Tags", + "portal.postDetail.metadata.tagAdd": "Adicionar", + "portal.postDetail.metadata.roadmap": "Roadmap", + "portal.postDetail.metadata.roadmapAdd": "Adicionar", + "portal.postDetail.metadata.date": "Data", + "portal.postDetail.metadata.source": "Origem", + "portal.postDetail.metadata.sourceOriginalFeedback": "Feedback original", + "portal.postDetail.metadata.sourceUnknownAuthor": "Autor desconhecido", + "portal.postDetail.metadata.sourceOpenIn": "Abrir em {name}", + "portal.postDetail.metadata.author": "Autor", + "portal.postDetail.metadata.authorFallback": "Anônimo", + "portal.postDetail.metadata.subscribe": "Assinar", + "portal.postDetail.metadata.subscribeHint": "Receba notificações quando houver atualizações neste post", + "portal.postDetail.metadata.manage": "Gerenciar", + "portal.postDetail.metadata.managePost": "Gerenciar post", + "portal.postDetail.metadata.merge": "Mesclar", + "portal.postDetail.metadata.mergeIntoThis": "Mesclar neste", + "portal.postDetail.metadata.mergeIntoAnother": "Mesclar com outro...", + "portal.postDetail.metadata.lockComments": "Bloquear comentários", + "portal.postDetail.metadata.unlockComments": "Desbloquear comentários", + "portal.postDetail.metadata.restorePost": "Restaurar post", + "portal.postDetail.metadata.deletePost": "Excluir post", + "portal.postDetail.deleteDialog.title": "Excluir post", + "portal.postDetail.deleteDialog.description": "Tem certeza de que deseja excluir \"{title}\"? Esta ação não pode ser desfeita.", + "portal.postDetail.deleteDialog.confirmLabel": "Excluir post", + "portal.postDetail.deleteDialog.deleting": "Excluindo...", + "portal.postDetail.deleteDialog.loading": "Carregando...", + "portal.postDetail.deleteDialog.errorLinks": "Falha ao carregar integrações vinculadas. Feche e tente novamente.", + "portal.postDetail.notFound": "Post não encontrado", + "portal.postDetail.commentsLocked": "Os comentários estão bloqueados neste post", + "portal.changelog.title": "Changelog", + "portal.changelog.description": "Fique por dentro das últimas atualizações do produto e funcionalidades lançadas.", + "portal.changelog.rssFeed": "Feed RSS", + "portal.changelog.entryNotFound.title": "Registro do changelog não encontrado", + "portal.changelog.entryNotFound.description": "Este registro pode ter sido removido ou ainda não foi publicado.", + "portal.changelog.entryNotFound.backLink": "Changelog", + "portal.roadmap.title": "Roadmap", + "portal.roadmap.description": "Veja no que estamos trabalhando e o que vem a seguir.", + "portal.roadmap.empty.title": "Nenhum roadmap disponível", + "portal.roadmap.empty.description": "Volte mais tarde para ver no que estamos trabalhando.", + "portal.roadmap.column.empty": "Ainda não há itens", + "portal.help.title": "Central de Ajuda", + "portal.help.description": "Encontre respostas para suas dúvidas e aprenda a aproveitar melhor nosso produto.", + "portal.help.categoryNotFound.title": "Categoria não encontrada", + "portal.help.categoryNotFound.description": "Esta categoria pode ter sido removida ou não existir.", + "portal.help.categoryNotFound.backLink": "Central de Ajuda", + "portal.help.articleNotFound.title": "Artigo não encontrado", + "portal.help.articleNotFound.description": "Este artigo pode ter sido removido ou ainda não foi publicado.", + "portal.help.articleNotFound.backLink": "Central de Ajuda", + "portal.notifications.title": "Notificações", + "portal.notifications.subtitle": "Atualizações sobre os posts que você assinou", + "portal.notifications.markAllRead": "Marcar tudo como lido", + "portal.notifications.readAll": "Ler tudo", + "portal.notifications.groupToday": "Hoje", + "portal.notifications.groupYesterday": "Ontem", + "portal.notifications.groupEarlier": "Anteriores", + "portal.notifications.empty.title": "Tudo em dia!", + "portal.notifications.empty.description": "Vote ou comente em posts para assiná-los. Você receberá notificações quando houver mudanças de status ou novas atividades.", + "portal.settings.profile.title": "Perfil", + "portal.settings.profile.description": "Gerencie suas informações pessoais", + "portal.settings.preferences.title": "Preferências", + "portal.settings.preferences.description": "Personalize sua experiência", + "portal.settings.preferences.appearance.title": "Aparência", + "portal.settings.preferences.appearance.description": "Personalize a aparência do app", + "portal.settings.preferences.appearance.themeLabel": "Tema", + "portal.settings.preferences.notifications.title": "Notificações por email", + "portal.settings.preferences.notifications.description": "Gerencie notificações por email dos posts que você assinou", + "portal.subscriptionBell.aria.subscribe": "Assinar notificações", + "portal.subscriptionBell.aria.subscribedStatusOnly": "Assinado apenas para mudanças de status", + "portal.subscriptionBell.aria.subscribedAll": "Assinado para todas as atividades", + "portal.subscriptionBell.menu.title": "Notificações", + "portal.subscriptionBell.menu.subtitle": "Escolha o que deseja assinar", + "portal.subscriptionBell.level.all": "Todas as atividades", + "portal.subscriptionBell.level.allHint": "Comentários e mudanças de status", + "portal.subscriptionBell.level.statusOnly": "Mudanças de status", + "portal.subscriptionBell.level.statusOnlyHint": "Quando o status for atualizado", + "portal.subscriptionBell.level.unsubscribe": "Cancelar assinatura", + "portal.unsubscribeBanner.message": "Você cancelou a assinatura deste post. Use o ícone de sino para assinar novamente.", + "portal.unsubscribeBanner.dismiss": "Dispensar", + "portal.pinnedComment.pinnedBadge": "Fixado", + "portal.postCard.authorFallback": "Anônimo", + "portal.postCard.editNotAllowed": "Edição não permitida", + "portal.postCard.deleteNotAllowed": "Exclusão não permitida", + "portal.postCard.quickActions.viewInPortal": "Ver no Portal", + "portal.postCard.quickActions.copyLink": "Copiar link", + "portal.postCard.quickActions.edit": "Editar", + "portal.postCard.quickActions.delete": "Excluir", + "portal.postCard.quickActions.options": "Opções do post", + "portal.postCard.vote.ariaVote": "Votar neste post ({count, plural, one {# voto} other {# votos}})", + "portal.postCard.vote.ariaRemoveVote": "Remover voto ({count, plural, one {# voto} other {# votos}})", + "portal.postCard.toast.linkCopied": "Link copiado para a área de transferência", + "portal.postCard.toast.linkCopyFailed": "Falha ao copiar link", + "portal.commentThread.empty": "Ainda não há comentários. Seja o primeiro a compartilhar sua opinião!", + "portal.commentThread.signInToComment": "Entre para comentar", + "portal.commentThread.signIn": "Entrar", + "portal.commentThread.deleted": "[excluído]", + "portal.commentThread.removed": "[removido]", + "portal.commentThread.authorFallback": "Anônimo", + "portal.commentThread.authorAlt": "Autor do comentário", + "portal.commentThread.teamBadge": "Equipe", + "portal.commentThread.internalNote": "Nota interna", + "portal.commentThread.internalNoteHint": "visível apenas para sua equipe", + "portal.commentThread.pinnedBadge": "Fixado", + "portal.commentThread.changedStatusTo": "alterou o status para", + "portal.commentThread.reply": "Responder", + "portal.commentThread.pin": "Fixar", + "portal.commentThread.unpin": "Desafixar", + "portal.commentThread.restore": "Restaurar", + "portal.commentThread.restoring": "Restaurando...", + "portal.commentThread.delete": "Excluir", + "portal.commentThread.deleting": "Excluindo...", + "portal.commentForm.labelSrOnly": "Seu comentário", + "portal.commentForm.placeholder": "Escreva um comentário...", + "portal.commentForm.authorFallback": "Anônimo", + "portal.commentForm.noStatus": "Sem status", + "portal.commentForm.updateStatus": "Atualizar status", + "portal.commentForm.clearStatusChange": "Limpar mudança de status", + "portal.commentForm.postingAnonymously": "Publicando anonimamente", + "portal.commentForm.postingAs": "Publicando como {name}", + "portal.commentForm.signOut": "sair", + "portal.commentForm.cancel": "Cancelar", + "portal.commentForm.private": "Privado", + "portal.commentForm.privateLockedTooltip": "Respostas a comentários privados são sempre privadas", + "portal.commentForm.privateOnTooltip": "Visível apenas para membros da equipe", + "portal.commentForm.privateOffTooltip": "Tornar este comentário privado (somente equipe)", + "portal.commentForm.submit": "Comentar", + "portal.commentForm.submitWithStatus": "Comentar e marcar como {statusName}", + "portal.commentForm.reply": "Responder", + "portal.commentForm.submitting": "Publicando...", + "portal.commentForm.errorPost": "Falha ao publicar comentário", + "portal.feedback.filter.addFilter": "Adicionar filtro", + "portal.feedback.filter.back": "Voltar", + "portal.feedback.filter.category.board": "Quadro", + "portal.feedback.filter.category.status": "Status", + "portal.feedback.filter.category.tag": "Tag", + "portal.feedback.filter.category.votes": "Quantidade de votos", + "portal.feedback.filter.category.date": "Data de criação", + "portal.feedback.filter.category.response": "Resposta da equipe", + "portal.feedback.filter.statusGroup.active": "Ativo", + "portal.feedback.filter.statusGroup.complete": "Concluído", + "portal.feedback.filter.statusGroup.closed": "Fechado", + "portal.feedback.filter.chip.board": "Quadro:", + "portal.feedback.filter.chip.status": "Status:", + "portal.feedback.filter.chip.tag": "Tag:", + "portal.feedback.filter.chip.tags": "Tags:", + "portal.feedback.filter.chip.votes": "Mín. de votos:", + "portal.feedback.filter.chip.date": "Data:", + "portal.feedback.filter.chip.response": "Resposta da equipe:", + "widget.home.posting.selectBoard": "Selecione um quadro", + "portal.feedback.toolbar.filter": "Filtrar", + "portal.roadmap.toolbar.sortBy": "Ordenar por", + "portal.roadmap.toolbar.sortVotes": "Mais votados", + "portal.roadmap.toolbar.sortNewest": "Mais recentes", + "portal.roadmap.toolbar.sortOldest": "Mais antigos", + "portal.roadmap.filter.category.board": "Quadro", + "portal.roadmap.filter.category.tag": "Tag", + "portal.roadmap.filter.category.segment": "Segmento", + "portal.roadmap.filter.chip.board": "Quadro:", + "portal.roadmap.filter.chip.tag": "Tag:", + "portal.roadmap.filter.chip.segment": "Segmento:", + "portal.roadmap.selector.placeholder": "Selecione um roadmap…", + "portal.roadmap.selector.search": "Pesquisar roadmaps…", + "portal.roadmap.selector.aria": "Escolher roadmap", + "ui.combobox.placeholder": "Selecionar…", + "ui.combobox.search": "Pesquisar…", + "ui.combobox.empty": "Nenhum resultado encontrado." +} From de8d2c560a971c5eaf6172ea567553bad2ada473 Mon Sep 17 00:00:00 2001 From: James Morton Date: Wed, 3 Jun 2026 07:41:16 +0100 Subject: [PATCH 2/2] fix(i18n): register pt-br locale so the catalog loads The pt-BR translation file was added but never wired up. pt-br was missing from SUPPORTED_LOCALES, so normalizeLocale returned null and the catalog was never selected. The file was also named pt-BR.json while loadMessages imports the lowercased locale (pt-br.json), which fails on case-sensitive filesystems. Register pt-br and rename the file to match. --- apps/web/src/lib/shared/__tests__/i18n.test.ts | 2 +- apps/web/src/lib/shared/i18n.ts | 2 +- apps/web/src/locales/{pt-BR.json => pt-br.json} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename apps/web/src/locales/{pt-BR.json => pt-br.json} (100%) diff --git a/apps/web/src/lib/shared/__tests__/i18n.test.ts b/apps/web/src/lib/shared/__tests__/i18n.test.ts index ab33de38d..300b121fc 100644 --- a/apps/web/src/lib/shared/__tests__/i18n.test.ts +++ b/apps/web/src/lib/shared/__tests__/i18n.test.ts @@ -19,7 +19,7 @@ describe('normalizeLocale', () => { expect(normalizeLocale('ru-RU')).toBe('ru') }) it('returns null for locales without message catalogs', () => { - expect(normalizeLocale('pt-BR')).toBeNull() + expect(normalizeLocale('ja-JP')).toBeNull() expect(normalizeLocale('it')).toBeNull() }) it('returns null for unsupported locale', () => { diff --git a/apps/web/src/lib/shared/i18n.ts b/apps/web/src/lib/shared/i18n.ts index d7fc85b25..6ffaca244 100644 --- a/apps/web/src/lib/shared/i18n.ts +++ b/apps/web/src/lib/shared/i18n.ts @@ -1,6 +1,6 @@ export const DEFAULT_LOCALE = 'en' as const -export const SUPPORTED_LOCALES = ['en', 'de', 'fr', 'es', 'ar', 'ru'] as const +export const SUPPORTED_LOCALES = ['en', 'de', 'fr', 'es', 'ar', 'ru', 'pt-br'] as const export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number] diff --git a/apps/web/src/locales/pt-BR.json b/apps/web/src/locales/pt-br.json similarity index 100% rename from apps/web/src/locales/pt-BR.json rename to apps/web/src/locales/pt-br.json