Le texte UI dans Figma est un artefact de design. C'est du texte de maquette, souvent écrit par des designers plutôt que des chefs de produit, rarement révisé pour l'i18n. figmascope l'extrait quand même — parce que même le texte de placeholder a besoin d'une clé de ressource si vous générez du code qui sera éventuellement localisé, et le processus d'extraction est l'endroit où vit la logique.

strings.json est l'artefact qui mappe les IDs de ressources en notation pointée sur des valeurs de chaînes littérales. Cet article couvre comment les clés sont générées, ce qui est filtré, ce qui se passe quand les clés entrent en collision, et comment cela se mappe sur Android strings.xml et d'autres formats i18n.

Le format

{
  "speed.test": "Speed Test",
  "data.transfer.rate": "Data Transfer Rate",
  "start": "Start",
  "results.download": "Download",
  "results.upload": "Upload",
  "results.ping": "Ping",
  "unit.mbps": "Mbps",
  "settings.server.auto": "Auto Select"
}

La structure est un objet plat clé par IDs de ressources en notation pointée. Les valeurs sont le contenu littéral de la chaîne depuis Figma. Les chemins en notation pointée imbriqués sont sémantiquement significatifs — results.download et results.upload sont dans le même groupe logique — mais le fichier est du JSON plat, pas imbriqué. Cela correspond à la façon dont fonctionne strings.xml d'Android (noms de ressources plats avec convention de points pour le regroupement) et à la façon dont la plupart des bibliothèques i18n (i18next, Rosetta, etc.) gèrent l'espace de noms de clés plats.

Comment les clés sont générées

La génération de clé commence à partir du nom de calque Figma du nœud, pas du contenu textuel. Un nœud texte nommé Data Transfer Rate Label avec le contenu "Data Transfer Rate" génère une clé depuis le nom du calque, pas la valeur.

Le pipeline de génération :

  1. Prendre le nom du calque
  2. Appliquer les filtres sanitizeName : translittérer les caractères cyrilliques, supprimer les caractères bidirectionnels/de contrôle (plage Unicode U+202A–U+202E et similaires), supprimer les caractères non alphanumériques non-espace
  3. Mettre en minuscules et remplacer les espaces par des points
  4. Supprimer les points en début et fin

Donc "Data Transfer Rate Label""data.transfer.rate.label". En pratique, les designers nomment souvent les calques de texte sans le nom final, donc "Data Transfer Rate""data.transfer.rate".

L'étape de translittération cyrillique existe parce que figmascope est utilisé avec des fichiers Figma en plusieurs langues. Un calque nommé Скорость se translittère en Skorost', puis devient "skorost'" → après suppression des caractères spéciaux → "skorost". Pas élégant, mais la clé est stable entre les exécutions pour le même nom de calque.

La génération de clé est à partir du nom de calque, pas de la valeur textuelle. Deux nœuds texte avec un contenu textuel identique mais des noms de calques différents obtiennent des clés différentes. Deux nœuds texte avec un contenu textuel différent mais le même nom de calque créent une collision — gérée ci-dessous.

Filtres de clés — ce qui est supprimé

Tous les nœuds texte dans un fichier Figma ne devraient pas être une chaîne localisable. figmascope filtre avant de générer les clés :

Le filtre numérique mérite plus d'explications. Une maquette de design contient souvent des nombres de placeholder : "42 ms" (ping), "150 Mbps" (vitesse de téléchargement). "42 ms" passe le filtre parce qu'il n'est pas purement numérique — il deviendrait la clé "42.ms". Mais vous voudriez probablement extraire juste "ms" comme étiquette d'unité et calculer 42 au runtime. figmascope ne fait pas cette inférence — il prend le texte complet tel quel. Le filtre de chaîne courte supprime "ms" seul (2 caractères), mais garde "42 ms" comme chaîne complète.

C'est une limitation reconnue. Le texte UI qui mélange des données template avec des étiquettes littérales est difficile à décomposer automatiquement. Le code généré devrait traiter la chaîne extraite comme un point de départ, pas du texte final.

Gestion des collisions

Une collision se produit quand deux nœuds texte différents produisent la même clé mais ont des valeurs différentes. Par exemple : un calque nommé "Label" sur l'écran A contient "Download" et un calque nommé "Label" sur l'écran B contient "Upload". Les deux génèrent la clé "label".

La règle de collision de figmascope : supprimer la clé entièrement de strings.json et émettre un avertissement dans _meta.json :

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

Garder l'une arbitrairement serait incorrect — vous traduiriez silencieusement incorrectement l'une des deux chaînes. Garder les deux avec des clés désambiguïsées (par ex. "label.1", "label.2") serait une supposition de nommage qui ne sert personne. Supprimer est honnête.

L'IR gère la collision proprement : quand une clé est supprimée en raison d'une collision, figmascope parcourt l'IR et supprime le champ stringRef de tous les nœuds leaf qui le référençaient. Le champ text (valeur littérale) reste. L'agent voit un leaf avec text mais sans stringRef et sait utiliser le littéral comme fallback — ce qui est sûr parce que la règle CONTEXT.md dit seulement « utiliser stringRef si présent ».

// Avant la résolution de collision — deux leaves avec la même clé, texte différent
{ "kind": "leaf", "text": "Download", "stringRef": "label" }
{ "kind": "leaf", "text": "Upload",   "stringRef": "label" }

// Après la résolution de collision — clé supprimée, texte préservé
{ "kind": "leaf", "text": "Download" }
{ "kind": "leaf", "text": "Upload"   }

L'avertissement vous dit de corriger les noms de calques dans Figma. Un calque nommé "Download Label" et "Upload Label" produirait des clés distinctes "download.label" et "upload.label" sans collision.

Mapping vers Android strings.xml

Android strings.xml utilise des IDs de ressources de chaînes plats avec des tirets bas. La notation pointée de figmascope se mappe proprement :

// strings.json
{
  "speed.test": "Speed Test",
  "data.transfer.rate": "Data Transfer Rate",
  "results.download": "Download"
}

// → strings.xml (généré)
<resources>
    <string name="speed_test">Speed Test</string>
    <string name="data_transfer_rate">Data Transfer Rate</string>
    <string name="results_download">Download</string>
</resources>

La transformation est : remplacer les points par des tirets bas. C'est la transformation complète. Les IDs de ressources Android n'autorisent pas les points ; les tirets bas sont le séparateur conventionnel. La clé speed.test dans strings.json devient R.string.speed_test en Kotlin, ce qui est ce que la contrainte CONTEXT.md indique à l'agent d'utiliser quand il voit stringRef: "speed.test".

// Contrainte CONTEXT.md en action
// Leaf IR : { "text": "Speed Test", "stringRef": "speed.test" }
// Sortie agent :
Text(text = stringResource(R.string.speed_test))

Mapping vers d'autres formats i18n

La notation pointée est une lingua franca pour l'i18n. La plupart des systèmes l'acceptent directement ou n'ont besoin que d'une transformation minimale :

FormatFormat de cléTransformation depuis la notation pointée
Android strings.xml speed_test Remplacer . par _
iOS Localizable.strings "speed.test" Aucune (notation pointée utilisée directement)
i18next (JSON) Imbriqué : { "speed": { "test": "..." } } Développer les clés plates en structure imbriquée
Flutter ARB speedTest Convertir en camelCase

figmascope produit le format à notation pointée. La transformation spécifique à la cible est gérée en aval — soit par l'agent (si la cible CONTEXT.md est suffisamment explicite) soit par un petit script de conversion.

Ce que strings.json ne résout pas

Note de portée honnête : strings.json est extrait depuis le texte qui existe dans Figma au moment de l'export. Ce n'est pas un fichier de ressources i18n validé. Trois problèmes que vous rencontrerez :

Texte de placeholder : Les maquettes Figma utilisent fréquemment du Lorem Ipsum, "[Titre ici]", ou du texte écrit par le designer qui n'a pas été révisé. Les chaînes extraites sont ce qui est dans le fichier, révisé ou non.

Contenu dynamique : Les designs montrent souvent "John Dupont" ou "42 Mbps" comme données représentatives. Ce ne sont pas des chaînes localisables — ce sont des valeurs template. figmascope ne peut pas distinguer les données du design des textes du design. Le filtre numérique capture les nombres purs, mais les chaînes mixtes comme "42 Mbps" sont quand même extraites.

Chaînes manquantes depuis les nœuds filtrés : Si un nœud texte échoue un filtre (trop court, uniquement numérique, vide après sanitisation), il n'apparaîtra pas dans strings.json et le leaf IR n'aura pas de stringRef. C'est intentionnel mais signifie que votre couverture de chaînes dépend de la qualité du nommage des calques dans Figma.

La sortie est un point de départ. Copiez-la dans votre fichier i18n, révisez-la selon vos conventions réelles de nommage des ressources, supprimez les chaînes de placeholder, et ajoutez les clés de contenu dynamique manuellement. C'est dix minutes de travail au lieu d'extraire manuellement chaque nœud texte depuis Figma. Essayez d'extraire les chaînes de votre propre fichier sur figmascope.dev.

Pour l'image complète de comment stringRef circule à travers l'IR dans le code généré, voir IR par écran — Stack, Overlay, Absolute, Leaf. Pour comment les contraintes qui gouvernent l'utilisation des chaînes sont déclarées, voir Anatomie de CONTEXT.md. Pour le workflow de génération Jetpack Compose de bout en bout, voir Jetpack Compose depuis Figma. Prêt à générer votre propre strings.json ? Utilisez l'application principale sur figmascope.dev.