UI-tekst in Figma is een ontwerpmateriaal. Het is mockup-tekst, vaak geschreven door ontwerpers in plaats van productmanagers, zelden beoordeeld voor i18n. figmascope extraheert het toch — want zelfs tijdelijke tekst heeft een bronsleutel nodig als je code genereert die uiteindelijk gelokaliseerd zal worden, en het extractieproces is waar de logica leeft.

strings.json is het artefact dat puntnotatie-resource-ID's koppelt aan letterlijke tekenreekswaarden. Dit bericht behandelt hoe sleutels worden gegenereerd, wat wordt gefilterd, wat er gebeurt bij botsingen, en hoe dit wordt gekoppeld aan Android strings.xml en andere i18n-formaten.

Het formaat

{
  "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"
}

De structuur is een plat object met puntnotatie-resource-ID's als sleutels. Waarden zijn de letterlijke tekenreeksinhoud uit Figma. Geneste puntpaden zijn semantisch betekenisvol — results.download en results.upload bevinden zich in dezelfde logische groep — maar het bestand is platte JSON, niet genest. Dit komt overeen met hoe Android's strings.xml werkt (platte resourcenamen met puntconventie voor groepering) en hoe de meeste i18n-bibliotheken (i18next, Rosetta, enz.) platte sleutel-namespaces verwerken.

Hoe sleutels worden gegenereerd

Sleutelgeneratie begint vanuit de laagnaam van het knooppunt in Figma, niet de tekstinhoud. Een tekstknooppunt genaamd Data Transfer Rate Label met inhoud "Data Transfer Rate" genereert een sleutel op basis van de laagnaam, niet de waarde.

De generatiepijplijn:

  1. Neem de laagnaam
  2. Pas de sanitizeName-filters toe: translitereer Cyrillische tekens, verwijder bidirectionele/controletekens (Unicode-bereik U+202A–U+202E en soortgelijk), verwijder niet-alfanumerieke niet-spatietekens
  3. Maak kleine letters en vervang spaties door punten
  4. Verwijder voor- en achterliggende punten

Dus "Data Transfer Rate Label""data.transfer.rate.label". In de praktijk noemen ontwerpers tekstlagen vaak zonder het achterliggende zelfstandig naamwoord, zodat "Data Transfer Rate""data.transfer.rate".

De Cyrillische transliteratiestap bestaat omdat figmascope wordt gebruikt met Figma-bestanden in meerdere talen. Een laag genaamd Скорость translitereert naar Skorost', dan naar "skorost'" → na verwijdering van speciale tekens → "skorost". Niet mooi, maar de sleutel is stabiel over meerdere runs voor dezelfde laagnaam.

Sleutelgeneratie is op basis van laagnaam, niet tekstwaarde. Twee tekstknooppunten met identieke tekstinhoud maar verschillende laagnamen krijgen verschillende sleutels. Twee tekstknooppunten met verschillende tekstinhoud maar dezelfde laagnaam creëren een botsing — hieronder behandeld.

Sleutelfilters — wat wordt weggelaten

Niet elk tekstknooppunt in een Figma-bestand moet een lokaliseerbare string zijn. figmascope filtert vóór het genereren van sleutels:

Het numerieke filter verdient meer uitleg. Een ontwerpmock heeft vaak tijdelijke getallen: "42 ms" (ping), "150 Mbps" (downloadsnelheid). "42 ms" passeert het filter omdat het niet puur numeriek is — het zou sleutel "42.ms" worden. Maar je wilt waarschijnlijk alleen "ms" als eenheidslabel extraheren en 42 tijdens runtime berekenen. figmascope maakt die gevolgtrekking niet — het neemt de volledige tekst zoals die is. Het korte-stringfilter laat "ms" alleen vallen (2 tekens), maar behoudt "42 ms" als volledige string.

Dit is een erkende beperking. UI-tekst die sjabloongegevens combineert met letterlijke labels is moeilijk automatisch te ontleden. De gegenereerde code zou de geëxtraheerde string als startpunt moeten behandelen, niet als definitieve tekst.

Botsingsafhandeling

Een botsing treedt op wanneer twee verschillende tekstknooppunten dezelfde sleutel produceren maar verschillende waarden hebben. Bijvoorbeeld: een laag genaamd "Label" op scherm A bevat "Download" en een laag genaamd "Label" op scherm B bevat "Upload". Beide genereren sleutel "label".

De botsingsregel van figmascope: verwijder de sleutel volledig uit strings.json en geef een waarschuwing in _meta.json:

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

Eén willekeurig bewaren zou fout zijn — je zou stilzwijgend één van de twee strings onjuist vertalen. Beide bewaren met ondubbelzinnige sleutels (bijv. "label.1", "label.2") zou een gok zijn bij het benoemen die niemand dient. Verwijderen is eerlijk.

De IR verwerkt botsingen netjes: wanneer een sleutel vanwege een botsing wordt verwijderd, doorloopt figmascope de IR en verwijdert het stringRef-veld van alle bladknooppunten die ernaar verwezen. Het text-veld (letterlijke waarde) blijft bestaan. De agent ziet een blad met text maar zonder stringRef en weet dat hij de letterlijke tekst als terugval moet gebruiken — wat veilig is omdat de CONTEXT.md-regel alleen zegt "gebruik stringRef indien aanwezig."

// Vóór botsingsoplossing — twee bladeren met dezelfde sleutel, andere tekst
{ "kind": "leaf", "text": "Download", "stringRef": "label" }
{ "kind": "leaf", "text": "Upload",   "stringRef": "label" }

// Na botsingsoplossing — sleutel verwijderd, tekst bewaard
{ "kind": "leaf", "text": "Download" }
{ "kind": "leaf", "text": "Upload"   }

De waarschuwing vertelt je om de laagnamen in Figma te corrigeren. Een laag genaamd "Download Label" en "Upload Label" zou aparte sleutels "download.label" en "upload.label" produceren zonder botsing.

Koppeling aan Android strings.xml

Android strings.xml gebruikt platte string-resource-ID's met underscores. De puntnotatie van figmascope mapt schoon:

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

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

De transformatie is: vervang punten door underscores. Dat is de volledige transformatie. Android-resource-ID's staan geen punten toe; underscores zijn de conventionele scheidingsteken. De sleutel speed.test in strings.json wordt R.string.speed_test in Kotlin, wat de CONTEXT.md-beperking de agent aanwijst om te gebruiken wanneer het stringRef: "speed.test" ziet.

// CONTEXT.md-beperking in actie
// IR-blad: { "text": "Speed Test", "stringRef": "speed.test" }
// Agentuitvoer:
Text(text = stringResource(R.string.speed_test))

Koppeling aan andere i18n-formaten

Puntnotatie is een gemeenschappelijke taal voor i18n. De meeste systemen accepteren het direct of hebben slechts minimale transformatie nodig:

FormaatSleutelformaatTransformatie vanuit puntnotatie
Android strings.xml speed_test Vervang . door _
iOS Localizable.strings "speed.test" Geen (puntnotatie direct gebruikt)
i18next (JSON) Genest: { "speed": { "test": "..." } } Platte sleutels uitbreiden naar geneste structuur
Flutter ARB speedTest Omzetten naar camelCase

figmascope levert het puntnotatieformaat. Doelspecifieke transformatie wordt stroomafwaarts afgehandeld — door de agent (als het CONTEXT.md-doel expliciet genoeg is) of door een klein conversiescript.

Wat strings.json niet oplost

Eerlijke scoperopmerking: strings.json wordt geëxtraheerd uit welke tekst er ook in Figma staat op het moment van exporteren. Het is geen gevalideerd i18n-bronbestand. Drie problemen die je zult tegenkomen:

Tijdelijke tekst: Figma-mocks gebruiken vaak Lorem Ipsum, "[Titel hier]" of door ontwerpers geschreven tekst die niet is beoordeeld. De geëxtraheerde strings zijn wat er in het bestand staat, al dan niet beoordeeld.

Dynamische inhoud: Ontwerpen tonen vaak "Jan de Vries" of "42 Mbps" als representatieve gegevens. Dit zijn geen lokaliseerbare strings — het zijn sjabloonwaarden. figmascope kan geen onderscheid maken tussen ontwerpgegevens en ontwerptekst. Het numerieke filter vangt pure getallen, maar gemengde strings zoals "42 Mbps" worden nog steeds geëxtraheerd.

Ontbrekende strings van gefilterde knooppunten: Als een tekstknooppunt een filter niet passeert (te kort, alleen numeriek, leeg na opschoning), verschijnt het niet in strings.json en heeft het IR-blad geen stringRef. Dit is opzettelijk maar betekent dat je stringdekking afhankelijk is van de kwaliteit van laagnaamgeving in Figma.

De uitvoer is een startpunt. Kopieer het naar je i18n-bestand, bekijk het op basis van je werkelijke resourcenaamconventies, verwijder de tijdelijke strings en voeg handmatig de dynamische inhoudssleutels toe. Dat is tien minuten werk in plaats van elk tekstknooppunt handmatig uit Figma te extraheren. Probeer strings te extraheren uit je eigen bestand op figmascope.dev.

Voor het volledige beeld van hoe stringRef door de IR naar gegenereerde code stroomt, zie Per-scherm IR — Stack, Overlay, Absolute, Leaf. Voor hoe de beperkingen die stringgebruik beheersen worden gedeclareerd, zie Anatomie van CONTEXT.md. Voor de end-to-end Jetpack Compose-generatieworkflow, zie Jetpack Compose uit Figma. Klaar om je eigen strings.json te genereren? Gebruik de hoofdapp op figmascope.dev.