UI-text i Figma är en designartefakt. Det är mockup-copy, ofta skrivet av designers snarare än produktchefer, sällan granskat för i18n. figmascope extraherar det ändå — eftersom även platshållartext behöver en resursnyckel om du genererar kod som så småningom ska lokaliseras, och extraheringsprocessen är där logiken lever.
strings.json är artefakten som mappar punktnoterade resurs-ID:n till literala strängvärden. Det här inlägget täcker hur nycklar genereras, vad som filtreras bort, vad som händer när nycklar kolliderar, och hur detta mappas till Android strings.xml och andra i18n-format.
Formatet
{
"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"
}
Strukturen är ett platt objekt med punktnoterade resurs-ID:n som nycklar. Värdena är det literala stränginnehållet från Figma. Nästlade punktsökvägar är semantiskt meningsfulla — results.download och results.upload är i samma logiska grupp — men filen är platt JSON, inte nästlad. Detta matchar hur Androids strings.xml fungerar (platta resursnamn med punktkonvention för gruppering) och hur de flesta i18n-bibliotek (i18next, Rosetta, m.fl.) hanterar platt nyckelnamnsgivning.
Hur nycklar genereras
Nyckelgenerering börjar från nodens Figma-lagernamn, inte textinnehållet. En textnod med namnet Data Transfer Rate Label och innehållet "Data Transfer Rate" genererar en nyckel från lagernamnet, inte värdet.
Genereringspipeline:
- Ta lagernamnet
- Använd
sanitizeName-filtren: translitterera kyrilliska tecken, ta bort bidirektonella/kontrolltecken (Unicode-intervall U+202A–U+202E och liknande), ta bort icke-alfanumeriska icke-blankstegstecken - Gemener och ersätt blanksteg med punkter
- Ta bort inledande och avslutande punkter
Så "Data Transfer Rate Label" → "data.transfer.rate.label". I praktiken namnger designers ofta textlager utan det avslutande substantivet, så "Data Transfer Rate" → "data.transfer.rate".
Det kyrilliska translittereringssteget finns eftersom figmascope används med Figma-filer på flera språk. Ett lager med namnet Скорость translittereras till Skorost', blir sedan "skorost'" → efter specialteckenborttagning → "skorost". Inte vackert, men nyckeln är stabil mellan körningar för samma lagernamn.
Nyckelgenerering utgår från lagernamn, inte textvärde. Två textnoder med identiskt textinnehåll men olika lagernamn får olika nycklar. Två textnoder med olika textinnehåll men samma lagernamn skapar en kollision — hanteras nedan.
Nyckelfilter — vad som faller bort
Inte varje textnod i en Figma-fil bör vara en lokaliserbar sträng. figmascope filtrerar innan nycklar genereras:
- Tomma strängar: Textnoder utan innehåll eller med enbart blanksteg tas bort.
- Enbart numeriska strängar: Värden som
"42","100%","3.14"tas bort. Det här är datavärden, inte UI-copy, och bör komma från ditt datalager. - Korta strängar (≤2 tecken): Enskilda tecken och tvåteckenssträngar tas bort. Ikoner, punktlistetecken, upphöjda tecken — dessa är inte strängresurser.
- Nycklar som saneras till tomma strängar: Om ett lagernamn enbart består av tecken som stripps av
sanitizeNameär resultatet en tom nyckel. Dessa noder tas bort tyst.
Det numeriska filtret förtjänar mer förklaring. En designmock har ofta platshållarsiffror: "42 ms" (ping), "150 Mbps" (nedladdningshastighet). "42 ms" passerar filtret eftersom det inte är rent numeriskt — det skulle bli nyckeln "42.ms". Men du vill förmodligen bara extrahera "ms" som en enhetsetikett och beräkna 42 i körtid. figmascope gör inte den slutsatsen — det tar hela texten som den är. Det korta-strängar-filtret tar bort "ms" ensamt (2 tecken), men behåller "42 ms" som en fullständig sträng.
Det här är en erkänd begränsning. UI-text som blandar malldata med literala etiketter är svår att automatiskt dekomponera. Den genererade koden bör behandla den extraherade strängen som en startpunkt, inte slutgiltig copy.
Kollisionshantering
En kollision uppstår när två olika textnoder producerar samma nyckel men har olika värden. Till exempel: ett lager med namnet "Label" på skärm A innehåller "Download" och ett lager med namnet "Label" på skärm B innehåller "Upload". Båda genererar nyckeln "label".
figmascopes kollisionsregel: ta bort nyckeln helt från strings.json och ge en varning i _meta.json:
// _meta.json
{
"warnings": [
"strings-collision:label"
]
}
Att godtyckligt behålla en vore fel — du skulle tyst översätta en av de två strängarna felaktigt. Att behålla båda med disambiguerade nycklar (t.ex. "label.1", "label.2") vore ett gissningsbaserat namngivningsval som inte tjänar någon. Att ta bort är ärligt.
IR hanterar kollisioner rent: när en nyckel tas bort på grund av kollision går figmascope igenom IR och tar bort stringRef-fältet från alla bladnoder som refererade till den. text-fältet (literalt värde) kvarstår. Agenten ser ett blad med text men utan stringRef och vet att använda literalen som reserv — vilket är säkert eftersom CONTEXT.md-regeln bara säger "använd stringRef om det finns."
// Före kollisionslösning — två blad med samma nyckel, olika text
{ "kind": "leaf", "text": "Download", "stringRef": "label" }
{ "kind": "leaf", "text": "Upload", "stringRef": "label" }
// Efter kollisionslösning — nyckel borttagen, text bevarad
{ "kind": "leaf", "text": "Download" }
{ "kind": "leaf", "text": "Upload" }
Varningen säger åt dig att fixa lagernamnen i Figma. Ett lager med namnet "Download Label" och ett med "Upload Label" skulle ge distinkta nycklar "download.label" och "upload.label" utan kollision.
Mappning till Android strings.xml
Android strings.xml använder platta strängresurs-ID:n med understreck. Punktnotationen från figmascope mappas rent:
// strings.json
{
"speed.test": "Speed Test",
"data.transfer.rate": "Data Transfer Rate",
"results.download": "Download"
}
// → strings.xml (genererad)
<resources>
<string name="speed_test">Speed Test</string>
<string name="data_transfer_rate">Data Transfer Rate</string>
<string name="results_download">Download</string>
</resources>
Transformationen är: ersätt punkter med understreck. Det är den fullständiga transformationen. Android-resurs-ID:n tillåter inte punkter; understreck är den konventionella separatorn. Nyckeln speed.test i strings.json blir R.string.speed_test i Kotlin, vilket är vad CONTEXT.md-begränsningen pekar agenten mot när den ser stringRef: "speed.test".
// CONTEXT.md-begränsning i praktiken
// IR-blad: { "text": "Speed Test", "stringRef": "speed.test" }
// Agentutdata:
Text(text = stringResource(R.string.speed_test))
Mappning till andra i18n-format
Punktnotation är ett lingua franca för i18n. De flesta system accepterar det direkt eller behöver bara minimal transformation:
| Format | Nyckelformat | Transform från punktnotation |
|---|---|---|
| Android strings.xml | speed_test |
Ersätt . med _ |
| iOS Localizable.strings | "speed.test" |
Ingen (punktnotation används direkt) |
| i18next (JSON) | Nästlad: { "speed": { "test": "..." } } |
Expandera platta nycklar till nästlad struktur |
| Flutter ARB | speedTest |
Konvertera till camelCase |
figmascope levererar punktnotationsformatet. Målspecifik transformation hanteras nedströms — antingen av agenten (om CONTEXT.md-målet är tillräckligt explicit) eller av ett litet konverteringsskript.
Vad strings.json inte löser
Ärlig scopenotering: strings.json extraheras från vilken text som helst som finns i Figma vid exporttillfället. Det är inte en validerad i18n-resursfil. Tre problem du stöter på:
Platshållartext: Figma-mocks använder ofta Lorem Ipsum, "[Titel här]", eller designer-skriven copy som inte har granskats. De extraherade strängarna är vad som finns i filen, granskad eller inte.
Dynamiskt innehåll: Designs visar ofta "John Doe" eller "42 Mbps" som representativa data. Dessa är inte lokaliseringsbara strängar — de är mallvärden. figmascope kan inte skilja designtidsdata från designtids-copy. Det numeriska filtret fångar rena siffror, men blandade strängar som "42 Mbps" extraheras fortfarande.
Saknade strängar från filtrerade noder: Om en textnod misslyckas med ett filter (för kort, enbart numerisk, tom efter sanering) visas den inte i strings.json och IR-bladet har inte stringRef. Det är avsiktligt men innebär att din strängtäckning beror på kvaliteten på lagernamnen i Figma.
Utdata är en startpunkt. Kopiera det till din i18n-fil, granska det mot dina faktiska resursnamnskonventioner, ta bort platshållarsträngarna och lägg till nycklarna för dynamiskt innehåll manuellt. Det är tio minuters arbete istället för att manuellt extrahera varje textnod från Figma. Prova att extrahera strängar från din egen fil på figmascope.dev.
För en fullständig bild av hur stringRef flödar genom IR till genererad kod, se Per-skärm IR — Stack, Overlay, Absolute, Leaf. För hur begränsningarna som styr stränganvändning deklareras, se CONTEXT.md-anatomin. För Jetpack Compose-genereringsarbetsflödet från start till slut, se Jetpack Compose från Figma. Redo att generera din egen strings.json? Använd huvudappen på figmascope.dev.