Texto de UI no Figma é um artefato de design. É copy de mockup, frequentemente escrita por designers em vez de gerentes de produto, raramente revisada para i18n. O figmascope a extrai mesmo assim — porque mesmo copy de placeholder precisa de uma chave de recurso se você está gerando código que eventualmente será localizado, e o processo de extração é onde a lógica vive.

strings.json é o artefato que mapeia IDs de recurso em notação de ponto para valores de string literais. Este post cobre como as chaves são geradas, o que é filtrado, o que acontece quando as chaves colidem e como isso mapeia para Android strings.xml e outros formatos de i18n.

O formato

{
  "speed.test": "Teste de Velocidade",
  "data.transfer.rate": "Taxa de Transferência de Dados",
  "start": "Iniciar",
  "results.download": "Download",
  "results.upload": "Upload",
  "results.ping": "Ping",
  "unit.mbps": "Mbps",
  "settings.server.auto": "Seleção Automática"
}

A estrutura é um objeto plano com chaves de IDs de recurso em notação de ponto. Os valores são o conteúdo de string literal do Figma. Caminhos de ponto aninhados são semanticamente significativos — results.download e results.upload estão no mesmo grupo lógico — mas o arquivo é JSON plano, não aninhado. Isso corresponde a como o strings.xml do Android funciona (nomes de recurso planos com convenção de ponto para agrupamento) e como a maioria das bibliotecas i18n (i18next, Rosetta etc.) lida com namespacing de chave plana.

Como as chaves são geradas

A geração de chave começa a partir do nome da camada do Figma no nó, não do conteúdo do texto. Um nó de texto chamado Data Transfer Rate Label com conteúdo "Taxa de Transferência de Dados" gera uma chave a partir do nome da camada, não do valor.

O pipeline de geração:

  1. Tome o nome da camada
  2. Aplique os filtros sanitizeName: translitere caracteres cirílicos, remova caracteres bidirecionais/de controle (faixa Unicode U+202A–U+202E e similares), remova caracteres não alfanuméricos não-espaço
  3. Minúsculas e substitua espaços por pontos
  4. Remova pontos no início e no fim

Então "Data Transfer Rate Label""data.transfer.rate.label". Na prática, designers frequentemente nomeiam camadas de texto sem o substantivo final, então "Data Transfer Rate""data.transfer.rate".

O passo de transliteração cirílica existe porque o figmascope é usado com arquivos Figma em vários idiomas. Uma camada chamada Швидкість transliterada fica Shvydkist', depois se torna "shvydkist'" → após remoção de char especial → "shvydkist". Não é bonito, mas a chave é estável entre execuções para o mesmo nome de camada.

A geração de chave é a partir do nome da camada, não do valor do texto. Dois nós de texto com conteúdo de texto idêntico mas nomes de camada diferentes recebem chaves diferentes. Dois nós de texto com conteúdo diferente mas o mesmo nome de camada criam uma colisão — tratada abaixo.

Filtros de chave — o que é descartado

Nem todo nó de texto em um arquivo Figma deve ser uma string localizável. O figmascope filtra antes de gerar chaves:

O filtro numérico merece mais explicação. Um mock de design frequentemente tem números de placeholder: "42 ms" (ping), "150 Mbps" (velocidade de download). "42 ms" passa no filtro porque não é puramente numérico — se tornaria a chave "42.ms". Mas você provavelmente quer extrair apenas "ms" como label de unidade e computar 42 em tempo de execução. O figmascope não faz essa inferência — ele pega o texto completo como está. O filtro de string curta descarta "ms" sozinho (2 caracteres), mas mantém "42 ms" como string completa.

Esta é uma limitação reconhecida. Texto de UI que mistura dados de template com labels literais é difícil de decompor automaticamente. O código gerado deve tratar a string extraída como ponto de partida, não copy final.

Tratamento de colisão

Uma colisão ocorre quando dois nós de texto diferentes produzem a mesma chave mas têm valores diferentes. Por exemplo: uma camada chamada "Label" na tela A contém "Download" e uma camada chamada "Label" na tela B contém "Upload". Ambas geram a chave "label".

A regra de colisão do figmascope: descarte a chave completamente de strings.json e emita um aviso em _meta.json:

// _meta.json
{
  "warnings": [
    "strings-collision:label"
  ]
}

Manter uma arbitrariamente seria errado — você traduziria silenciosamente uma das duas strings incorretamente. Manter ambas com chaves desambiguadas (ex.: "label.1", "label.2") seria uma adivinhação de nomenclatura que não serve a ninguém. Descartar é honesto.

O IR lida com a colisão de forma limpa: quando uma chave é descartada por colisão, o figmascope percorre o IR e remove o campo stringRef de todos os nós leaf que a referenciavam. O campo text (valor literal) permanece. O agente vê um leaf com text mas sem stringRef e sabe usar o literal como fallback — o que é seguro porque a regra do CONTEXT.md diz apenas "use stringRef se presente."

// Antes da resolução de colisão — dois leaves com mesma chave, texto diferente
{ "kind": "leaf", "text": "Download", "stringRef": "label" }
{ "kind": "leaf", "text": "Upload",   "stringRef": "label" }

// Após a resolução de colisão — chave descartada, texto preservado
{ "kind": "leaf", "text": "Download" }
{ "kind": "leaf", "text": "Upload"   }

O aviso informa para corrigir os nomes de camada no Figma. Uma camada chamada "Download Label" e "Upload Label" produziria chaves distintas "download.label" e "upload.label" sem colisão.

Mapeamento para Android strings.xml

O strings.xml do Android usa IDs de recurso de string planos com underscores. A notação de ponto do figmascope mapeia de forma limpa:

// strings.json
{
  "speed.test": "Teste de Velocidade",
  "data.transfer.rate": "Taxa de Transferência de Dados",
  "results.download": "Download"
}

// → strings.xml (gerado)
<resources>
    <string name="speed_test">Teste de Velocidade</string>
    <string name="data_transfer_rate">Taxa de Transferência de Dados</string>
    <string name="results_download">Download</string>
</resources>

A transformação é: substitua pontos por underscores. Essa é a transformação completa. IDs de recurso Android não permitem pontos; underscores são o separador convencional. A chave speed.test em strings.json se torna R.string.speed_test em Kotlin, que é o que a restrição do CONTEXT.md aponta ao agente para usar quando ele vê stringRef: "speed.test".

// Restrição do CONTEXT.md em ação
// IR leaf: { "text": "Teste de Velocidade", "stringRef": "speed.test" }
// Saída do agente:
Text(text = stringResource(R.string.speed_test))

Mapeamento para outros formatos i18n

A notação de ponto é uma língua franca para i18n. A maioria dos sistemas a aceita diretamente ou precisa apenas de transformação mínima:

FormatoFormato da chaveTransformação da notação de ponto
Android strings.xml speed_test Substitua . por _
iOS Localizable.strings "speed.test" Nenhuma (notação de ponto usada diretamente)
i18next (JSON) Aninhado: { "speed": { "test": "..." } } Expanda chaves planas em estrutura aninhada
Flutter ARB speedTest Converta para camelCase

O figmascope produz o formato de notação de ponto. A transformação específica para o alvo é tratada downstream — pelo agente (se o alvo do CONTEXT.md for explícito o suficiente) ou por um pequeno script de conversão.

O que strings.json não resolve

Nota honesta de escopo: strings.json é extraído do que quer que exista no Figma no momento do export. Não é um arquivo de recurso i18n validado. Três problemas que você vai encontrar:

Texto de placeholder: Mocks do Figma frequentemente usam Lorem Ipsum, "[Título Aqui]" ou copy criada pelo designer que ainda não foi revisada. As strings extraídas são o que está no arquivo, revisadas ou não.

Conteúdo dinâmico: Designs frequentemente mostram "João Silva" ou "42 Mbps" como dados representativos. Não são strings localizáveis — são valores de template. O figmascope não consegue distinguir dados de design-time de copy de design-time. O filtro numérico captura números puros, mas strings mistas como "42 Mbps" ainda são extraídas.

Strings ausentes de nós filtrados: Se um nó de texto falha em um filtro (muito curto, apenas numérico, vazio após sanitização), ele não aparecerá em strings.json e o leaf do IR não terá um stringRef. Isso é intencional, mas significa que sua cobertura de strings depende da qualidade da nomenclatura de camadas no Figma.

A saída é um ponto de partida. Copie-a para seu arquivo i18n, revise-a de acordo com suas convenções reais de nomenclatura de recursos, delete as strings de placeholder e adicione as chaves de conteúdo dinâmico manualmente. São dez minutos de trabalho em vez de extrair manualmente cada nó de texto do Figma. Experimente extrair strings do seu próprio arquivo no figmascope.dev.

Para o quadro completo de como stringRef flui pelo IR para o código gerado, veja IR por Tela — Stack, Overlay, Absolute, Leaf. Para como as restrições que governam o uso de strings são declaradas, veja Anatomia do CONTEXT.md. Para o fluxo completo de geração com Jetpack Compose, veja Jetpack Compose a partir do Figma. Pronto para gerar seu próprio strings.json? Use o app principal no figmascope.dev.