Adicionamos japonês e português brasileiro em todo o nosso site por €31

No dia 29 de setembro, lançamos o globalize.now em japonês e português brasileiro. Todas as páginas, todas as tabelas de preços e os 61 posts do blog. A tradução em si levou oito minutos, e o app cobrou cerca de €31. globalize.now é uma infraestrutura de localização com IA, e é assim que foi rodar isso no nosso próprio site, com cada número tirado diretamente da página do job e do histórico do Git, e não de memória.

Nosso post sobre quanto custou o playbook de crescimento por localização em 2017 termina dizendo que ainda não publicamos nossos próprios números. Este é o primeiro. É um número de custo, não de crescimento: ninguém teve tempo de visitar /ja/ ainda.

O que exatamente traduzimos?

O site inteiro, duas vezes. Aqui está o inventário, contado a partir do repositório:

  • Interface: 1.782 strings em inglês distribuídas em cinco catálogos de mensagens (o site principal, preços, páginas de comparação, integrações e exemplos de tradução), cerca de 21.000 palavras.
  • Blog: 61 posts, cerca de 110.000 palavras.
  • Idiomas de destino: japonês (ja) e português brasileiro (pt-BR).

Isso equivale a cerca de 130.000 palavras de origem sendo traduzidas para dois idiomas. O pull request modificou 170 arquivos e adicionou 34.681 linhas. Depois da mesclagem, o sitemap lista 811 URLs em 11 locales, e o site gera 962 páginas estáticas.

Quanto tempo levou?

Oito minutos de tradução, depois uma verificação de qualidade mais longa. O Arturs, nosso CTO, adicionou os dois idiomas ao projeto e o job começou em main às 08:56 UTC. A página do job detalha assim:

EtapaDuração
Tradução (11.264 novas strings, 5.632 por idioma)8 min 1 s
Revisão de QA automatizada27 min 11 s
Entrega em um pull request5 min 29 s

O commit com os dois idiomas foi concluído às 09:29 UTC. As outras 45.106 strings do job vieram da memória de tradução, já que os oito idiomas existentes não tinham sido alterados.

O Arturs chutou dez minutos quando comentou no chat da equipe. Ele acertou na estimativa da tradução, mas foi otimista sobre o job como um todo, e a diferença está na verificação de QA. Preferimos gastar 27 minutos de tempo de máquina verificando do que pular essa etapa.

O que o QA automatizado encontrou?

72 alertas em 11.264 novas strings, todos classificados como menores. 11.192 passaram sem ressalvas, e o QA aplicou 182 correções automáticas ao longo do caminho.

Os alertas que abrimos foram, em sua maioria, a verificação de comprimento sendo cautelosa com o japonês. "Pricing" se torna 料金, dois caracteres, e uma proporção de comprimento de 0,29 fica bem abaixo do limite mínimo de 0,3. Isso é uma tradução correta acionando uma heurística feita para idiomas alfabéticos, não um erro. Estamos deixando os alertas visíveis mesmo assim: uma verificação que nunca dispara não é uma verificação.

Quanto custou?

Cerca de €31: a visualização de custo por idioma do projeto mostra €15,72 para o japonês e €16,15 para o português brasileiro. É isso que o app cobra, o mesmo valor que um cliente traduzindo o mesmo volume veria. Não é nosso custo interno de modelo e não é um desconto.

Em cerca de 260.000 palavras traduzidas, isso dá aproximadamente 12 centavos por 1.000 palavras. Nada mais foi cobrado além disso. Não há cobrança por idioma nem por usuário, então adicionar o décimo segundo idioma custa o mesmo que adicionar o terceiro. Os planos atuais estão na página de preços.

O que a parte de engenharia realmente envolveu?

Um pull request, mesclado na mesma tarde, e nada disso foi tradução. Quando foi aberto, o job de tradução rodou novamente sobre ele e trouxe 56.370 das 56.390 strings direto da memória de tradução, então os catálogos já traduzidos chegaram aos novos arquivos em sete minutos. A tradução é a parte que foi automatizada. Registrar um novo locale em um site Next.js ainda é código seu, e vale a pena saber o que "código seu" significa antes de planejar isso.

Adicionar um locale ao next-intl é uma linha na configuração de roteamento. Tudo que constrói uma URL, lê um locale ou lista seus locales é o restante do diff:

  • Roteamento: a lista de locales, além de uma substituição de prefixo de URL para pt-BR (próxima seção).
  • Metadados de SEO: clusters de hreflang, canônicos, og:locale (ja_JP, pt_BR) e a lista de locales sobre a qual os helpers de metadados iteram.
  • Formatação: tags Intl ja-JP e pt-BR, para que datas e números sejam exibidos corretamente.
  • Seletor de idioma: ele aponta para cada locale com um <a href hreflang> real, para que os rastreadores consigam segui-lo.
  • Sitemap e verificações: o gerador de sitemap, a verificação de lastmod, a verificação de links internos e o verificador de tradução tiveram que aprender os dois novos códigos.
  • Catálogos: arquivos .po vazios, contendo apenas o cabeçalho, para os dois locales, preenchidos no mesmo pull request a partir da memória de tradução.

Se o seu site for menos personalizado que o nosso, a maior parte dessa lista é configuração. Se você monta URLs manualmente em algum lugar, e a maioria dos sites faz isso em algum ponto, você vai descobrir cada um desses lugares na primeira vez que um link sair errado. O guia para desenvolvedores cobre a configuração, e o que um agente de localização com IA realmente faz cobre a divisão entre o que é automatizado e o que não é.

Por que o português brasileiro está em /pt-br/ e não em /pt-BR/?

Porque uma tag de idioma e um segmento de URL são coisas diferentes, com funções diferentes.

A tag correta é pt-BR, de acordo com a BCP 47. Nós a usamos em todo lugar em que uma máquina lê o idioma: nomes de arquivos, <html lang>, hreflang, og:locale e Intl. Apenas a URL fica em minúsculas:

// i18n/routing.ts (trimmed)
const prefixes = {
  "pt-BR": "/pt-br",
} as const;

export const routing = defineRouting({
  locales: ["en", "it", "de", "fr", "es", "lv", "hi", "lt", "et", "ja", "pt-BR"],
  defaultLocale: "en",
  localePrefix: { mode: "always", prefixes },
});

/** The URL path segment for a locale: `pt-BR` → `pt-br`, `de` → `de`. */
export function localeUrlSegment(locale: string): string {
  const prefix = (prefixes as Record<string, string | undefined>)[locale];
  return prefix ? prefix.slice(1) : locale;
}

Caminhos com maiúsculas e minúsculas misturadas são fáceis de digitar errado, e um site que responde tanto em /pt-BR/ quanto em /pt-br/ acaba com duas cópias de cada página competindo entre si. Por isso /pt-BR/ redireciona para /pt-br/, e localeUrlSegment() constrói cada URL feita manualmente a partir do mapa de prefixos. Uma única fonte de verdade significa que o seletor de idioma, os canônicos e o sitemap não podem se desalinhar.

Por que japonês e português brasileiro?

São grandes idiomas da web que ainda não cobríamos. Pela contagem do W3Techs no dia em que fizemos o lançamento, o japonês é o idioma de conteúdo de 4,9% dos sites, e o português, de 4,1%.

O argumento a favor disso é mais antigo do que nós. A CSA Research entrevistou 8.709 consumidores em 29 países em 2020, e 76% disseram preferir comprar produtos com informações em seu próprio idioma. Esse é o lado da demanda. O que mudou foi o lado da oferta: dois idiomas costumavam significar uma relação com fornecedor, e agora custam €31.

Nossa própria regra para escolher idiomas é a do post do playbook de crescimento. Observe de onde já vem seu tráfego, adicione dois idiomas, não mude mais nada e meça por um trimestre. Vamos fazer exatamente isso com esses dois.

O que ainda não verificamos?

A revisão nativa. Ninguém que leia japonês ou português brasileiro nativamente passou pelo resultado ainda.

O que já verificamos: QA automatizado em todas as strings, o build passa em todas as verificações de pré-build, e as páginas que checamos manualmente em staging retornam 200 com o lang, canonical e hreflang corretos, e renderizam por completo. O que um build não consegue verificar é se uma frase parece ter sido escrita por uma pessoa. Aprendemos isso da forma mais difícil quando auditamos nosso próprio alemão e francês e encontramos o registro errado em páginas inteiras.

Então a revisão vem a seguir, e vamos atualizar este post com o que ela encontrar, incluindo qualquer coisa que estivesse errada.

Você poderia fazer o mesmo no seu site?

Se suas strings já vivem em catálogos, sim, e a parte de tradução levará minutos. Se ainda estiverem fixas nos componentes, a conversão vem primeiro. Isso roda uma única vez, no fluxo de conexão dentro do app, e os trabalhos de push traduzem novas strings a partir daí. O guia para vibe coders cobre apps feitos com Lovable, Bolt ou Cursor, onde strings fixas no código são a norma.

O custo escala com as palavras que você traduz, não com os idiomas ou com as pessoas da equipe. É por isso que a resposta para “vale a pena adicionar um idioma?” mudou. Medir agora é mais barato do que a reunião para discutir isso.

Experimente no seu próprio repositório

Dois idiomas nos custaram oito minutos de tradução e cerca de €31. Conecte seu repositório, escolha dois idiomas que seus dados de analytics já indicam, e veja como fica o seu próprio comprovante.

O globalize.now transforma textos fixos do app em arquivos de locale prontos para tradução e os mantém atualizados a cada novo lançamento.

Experimente o globalize.now grátis