APRENDIZADO & COGNIÇÃO

Rodei o sistema inteiro em ar-EG por uma tarde: o layout quebrado foi o achado mais barato

Um texto sobre renderizar tipografia árabe juntou 287 pontos e 83 comentários no Hacker News. Entrei esperando conversa de fonte…

Um texto sobre renderizar tipografia árabe juntou 287 pontos e 83 comentários no Hacker News. Entrei esperando conversa de fonte e shaping, e boa parte do fio estava em outro lugar: gente contando o que quebrou depois que a interface finalmente ficou certa.

Fiquei com a pulga atrás da orelha e fiz o teste aqui. Uma tarde, um sistema em Node com Postgres que eu conheço linha por linha, locale trocado para ar-EG, e um caderno para anotar tudo que caísse. O CSS foi a primeira coisa a quebrar. Foi também a única que eu consertei no mesmo dia.

O procedimento, se você quiser repetir hoje

Locale não é uma coisa só, são três, e elas quebram em lugares diferentes.

# 1) o processoLANG=ar_EG.UTF-8 LC_ALL=ar_EG.UTF-8 TZ=Africa/Cairo node server.js# 2) o container (imagem slim quase nunca tem locale instalado)docker run --rm -e LANG=ar_EG.UTF-8 -e LC_ALL=ar_EG.UTF-8 app:latest node -e 'console.log(new Intl.NumberFormat().format(1234.5))'# 3) o navegador# chrome://settings/languages e sobe o árabe para o topo da lista# no template, <html lang="ar" dir="rtl">

Depois use o sistema como usuário: cadastra, busca, lista, ordena, exporta CSV, gera PDF, dispara o e-mail transacional. Não leia o código procurando problema. Deixe o sistema te mostrar.

O achado barato: o layout

Menu no lado errado, ícone de voltar apontando para onde ninguém vai voltar, campo de formulário grudado na borda. Tudo isso mora em margin-left, padding-right e text-align: left espalhados pelo CSS. A correção é margin-inline-start, padding-inline e text-align: start.

É chato e é finito. Uma varredura por left e right na folha de estilo resolve a maior parte, e nenhum desses defeitos mexe em dinheiro. Gastei perto de 40 minutos por tela e passei para o resto do caderno, que era o problema de verdade.

Número formatado que volta como NaN

$ LC_ALL=ar_EG.UTF-8 node> const v = new Intl.NumberFormat().format(1234.5)> v'١٬٢٣٤٫٥'> Number.parseFloat(v)NaN> Number(v)NaN

Os dígitos viraram indo-arábicos, o separador decimal virou ٫ e o de milhar virou ٬. Enquanto isso só aparece na tela, está tudo certo, é para isso que o Intl existe.

O problema é onde o seu código formata para o humano e lê de volta como se fosse dado. Relatório que soma uma coluna já renderizada, CSV que o financeiro reimporta na semana seguinte, campo de valor que o próprio sistema preenche e o usuário só confirma. Achei dois desses. Um devolvia NaN, que pelo menos aparece. O outro tinha um || 0 na frente e devolvia zero em silêncio.

A data mudou de calendário sozinha

> new Date('2026-09-04T12:00:00Z').toLocaleDateString('ar-EG')'٤‏/٩‏/٢٠٢٦'> new Date('2026-09-04T12:00:00Z').toLocaleDateString('ar-SA')'٢٣‏/٣‏/١٤٤٨ هـ'

O ar-SA usa o calendário islâmico por padrão (islamic-umalqura). O dia e o mês exatos dependem da versão do ICU que você tem instalada, mas o ano vem 1448 em qualquer uma. Se a data de vencimento sai do backend por toLocaleDateString e volta por um parse do outro lado, o boleto nasce errado e o log não acende nada.

Ordenação e busca: o problema que já é seu

Esse eu já tinha em português e nunca vi, porque o banco de teste só tem João e Maria.

-- collation C compara byte a byteSELECT nome FROM cliente ORDER BY nome COLLATE "C"; nome------ Zuza Ávila

Á em UTF-8 começa em 0xC3, que é maior que o Z. A lista sai de cabeça para baixo e ninguém reclama, porque quem confere olha as três primeiras linhas. A busca tem o mesmo defeito pelo outro lado: ILIKE '%avila%' não acha Ávila sem unaccent, e assim que você embrulha a coluna na função o índice para de ser usado, a menos que você crie um índice sobre a expressão.

Em árabe é a mesma família de problema com outro nome. Os diacríticos (harakat) e as variações do alef mudam os bytes sem mudar a palavra que a pessoa digitou.

A regra que assume que nome tem duas palavras

Essa foi a que mais me irritou, porque o código é meu.

function split(nome) { const partes = nome.trim().split(' '); return { primeiro: partes[0], sobrenome: partes[1] };}split('Ana Beatriz Souza'); // { primeiro: 'Ana', sobrenome: 'Beatriz' }split('Zeca'); // { primeiro: 'Zeca', sobrenome: undefined }split('Muhammad bin Salman') // { primeiro: 'Muhammad', sobrenome: 'bin' }

O estrago não está no cabeçalho da tela. Está na chave de deduplicação do cadastro, na busca que o atendimento usa no telefone e na integração que manda last_name como campo obrigatório e recebe undefined.

O PDF você confere sem abrir

A biblioteca que não faz shaping entrega letra desconectada, e a fonte embarcada sem os glifos entrega quadradinho. Só que ninguém percebe isso passando o olho no arquivo. Gere e leia de volta:

pdftotext fatura.pdf - | head -5

Se o texto que volta não bate com o que você mandou imprimir, o PDF está desenhando forma, não escrevendo texto. Vale para o árabe e vale para o cliente brasileiro que abre o seu boleto e tenta copiar a linha digitável.

Por que isso importa mesmo se você nunca vai vender para o Egito

Trocar o locale por uma tarde é o teste mais barato que eu conheço para achar onde o sistema confundiu apresentação com dado. Nenhum desses cinco achados é sobre árabe. Todos já estavam lá, cobrando pouquinho, no cliente sem sobrenome, na busca com acento e no CSV com vírgula decimal.

Se você rodar hoje à tarde, escreve aqui qual foi o primeiro erro que não era CSS. Quero saber se o parse de número aparece na maioria dos casos ou se é vício do meu stack.

Perguntas frequentes

Respostas institucionais sobre esta cobertura, fontes e limites editoriais.

Sobre o que trata “Rodei o sistema inteiro em ar-EG por uma tarde: o…”?
Um texto sobre renderizar tipografia árabe juntou 287 pontos e 83 comentários no Hacker News. Entrei esperando conversa de fonte e shaping, e boa parte do fio estava em outro lugar: gente contando o que quebrou…
O que a matéria explica sobre “O procedimento, se você quiser repetir hoje”?
Locale não é uma coisa só, são três, e elas quebram em lugares diferentes.# 1) o processoLANG=ar_EG.UTF-8 LC_ALL=ar_EG.UTF-8 TZ=Africa/Cairo node server.js# 2) o container (imagem slim quase nunca tem locale instalado)docker run --rm -e LANG=ar_EG.UTF-8 -e LC_ALL=ar_EG.UTF-8 app:latest node -e 'console.log(new Intl.NumberFormat().format(1234.5))'#…
O que a matéria explica sobre “O achado barato: o layout”?
Menu no lado errado, ícone de voltar apontando para onde ninguém vai voltar, campo de formulário grudado na borda. Tudo isso mora em margin-left, padding-right e text-align: left espalhados pelo CSS. A correção é margin-inline-start, padding-inline e text-align: start.É chato e é…
O que a matéria explica sobre “Número formatado que volta como NaN”?
$ LC_ALL=ar_EG.UTF-8 node> const v = new Intl.NumberFormat().format(1234.5)> v'١٬٢٣٤٫٥'> Number.parseFloat(v)NaN> Number(v)NaNOs dígitos viraram indo-arábicos, o separador decimal virou ٫ e o de milhar virou ٬. Enquanto isso só aparece na tela, está tudo certo, é para isso que o Intl existe.O problema…
O que a matéria explica sobre “A data mudou de calendário sozinha”?
> new Date('2026-09-04T12:00:00Z').toLocaleDateString('ar-EG')'٤‏/٩‏/٢٠٢٦'> new Date('2026-09-04T12:00:00Z').toLocaleDateString('ar-SA')'٢٣‏/٣‏/١٤٤٨ هـ'O ar-SA usa o calendário islâmico por padrão (islamic-umalqura). O dia e o mês exatos dependem da versão do ICU que você tem instalada, mas o ano vem 1448 em qualquer uma. Se a data de vencimento…
Quem edita o conteúdo do Estrato Mente?
A cobertura é produzida e revisada pela equipe editorial do Estrato Mente, com editor-chefe responsável pela linha editorial. Conheça a redação em https://mente.estrato.cc/equipe/.
Quais fontes o Estrato prioriza?
Priorizamos fontes primárias (órgãos oficiais, balanços, estudos revisados, documentos públicos) e cruzamos informações antes da publicação, conforme a política editorial.
Como solicitar correção de uma matéria?
Envie o pedido pela página de Correções (https://mente.estrato.cc/correcoes/) ou Contato (https://mente.estrato.cc/contato/). Erros materiais são corrigidos com registro da atualização.
WhatsApp X LinkedIn
Compartilhar: LinkedIn
Nina Barros

Nina Barros

Nina Barros integra a equipe editorial do Estrato Mente, vertical da rede Estrato, na função de Repórter de Finanças comportamentais. O escopo permanente de cobertura inclui vieses e decisões, com atenção ao leitor brasileiro que busca contexto factual, datas oficiais e implicações práticas. Na produção diária, prioriza fontes primárias (órgãos públicos, balanços, papers e documentos oficiais), cruza pelo menos duas referências independentes quando o tema é contestado e evita manchetes que prometam certeza onde há incerteza. Critérios de qualidade: lead com o fato principal, atribuição clara de autoria da equipe Estrato, atualização com selo quando há mudança material e linguagem acessível sem simplificar demais o risco ou o contexto. Trabalha sob revisão da mesa editorial e do editor-chefe do portal. Matérias YMYL recebem segunda leitura antes da publicação e podem ser atualizadas quando surgem novos dados oficiais. Limites: este perfil descreve o papel editorial na marca Estrato. O conteúdo publicado é informativo e não constitui aconselhamento médico, financeiro, jurídico ou profissional personalizado. Transparência ao leitor: metodologia em /metodologia/, ética em /etica-editorial/, correções em /correcoes/, equipe em /equipe/ e canal em /contato/ ([email protected]). Na página-mãe da rede, a bio ampliada e o arquivo de matérias ficam em estrato.cc/blog/, com link para o arquivo do autor em cada portal.

Deixe um comentário