UI-Text in Figma ist ein Design-Artefakt. Es ist Mockup-Kopie, oft von Designern statt Product Managern geschrieben, selten auf i18n geprüft. figmascope extrahiert ihn trotzdem — denn selbst Platzhalter-Kopie braucht einen Ressourcen-Key, wenn man Code generiert, der irgendwann lokalisiert wird, und der Extraktionsprozess ist da, wo die Logik lebt.
strings.json ist das Artefakt, das Punkt-Notation-Ressourcen-IDs auf Literal-String-Werte mappt. Dieser Beitrag deckt ab, wie Keys generiert werden, was herausgefiltert wird, was passiert, wenn Keys kollidieren, und wie das auf Android strings.xml und andere i18n-Formate mappt.
Das Format
{
"speed.test": "Geschwindigkeitstest",
"data.transfer.rate": "Datenübertragungsrate",
"start": "Start",
"results.download": "Download",
"results.upload": "Upload",
"results.ping": "Ping",
"unit.mbps": "Mbps",
"settings.server.auto": "Automatisch auswählen"
}
Die Struktur ist ein flaches Objekt, gekey-t nach Punkt-Notation-Ressourcen-IDs. Werte sind der Literal-String-Inhalt aus Figma. Verschachtelte Punkt-Pfade sind semantisch bedeutsam — results.download und results.upload sind in derselben logischen Gruppe — aber die Datei ist flaches JSON, nicht verschachtelt. Das entspricht wie Androids strings.xml funktioniert (flache Ressourcennamen mit Punkt-Konvention für Gruppierung) und wie die meisten i18n-Bibliotheken (i18next, Rosetta usw.) flaches Key-Namespacing handhaben.
Wie Keys generiert werden
Die Key-Generierung beginnt beim Figma-Layer-Namen des Nodes, nicht beim Text-Inhalt. Ein Text-Node namens Data Transfer Rate Label mit Inhalt "Datenübertragungsrate" generiert einen Key aus dem Layer-Namen, nicht dem Wert.
Die Generierungs-Pipeline:
- Den Layer-Namen nehmen
- Die
sanitizeName-Filter anwenden: kyrillische Zeichen transliterieren, bidirektionale/Steuerzeichen entfernen (Unicode-Bereich U+202A–U+202E und ähnliche), nicht-alphanumerische Nicht-Leerzeichen-Zeichen entfernen - Kleinschreiben und Leerzeichen durch Punkte ersetzen
- Führende und abschließende Punkte trimmen
Also "Data Transfer Rate Label" → "data.transfer.rate.label". In der Praxis benennen Designer Text-Layer oft ohne das abschließende Substantiv, also "Data Transfer Rate" → "data.transfer.rate".
Der kyrillische Transliterationsschritt existiert, weil figmascope mit Figma-Dateien über Sprachen hinweg verwendet wird. Ein Layer namens Скорость transliteriert zu Skorost', wird dann zu "skorost'" → nach Sonderzeichen-Strip → "skorost". Nicht schön, aber der Key ist über Läufe hinweg stabil für denselben Layer-Namen.
Key-Generierung ist aus dem Layer-Namen, nicht dem Text-Wert. Zwei Text-Nodes mit identischem Text-Inhalt aber verschiedenen Layer-Namen erhalten verschiedene Keys. Zwei Text-Nodes mit verschiedenem Text-Inhalt aber demselben Layer-Namen erzeugen eine Kollision — unten behandelt.
Key-Filter — was herausfällt
Nicht jeder Text-Node in einer Figma-Datei sollte ein lokalisierbarer String sein. figmascope filtert vor der Key-Generierung:
- Leere Strings: Text-Nodes ohne Inhalt oder mit nur-Leerzeichen-Inhalt werden verworfen.
- Nur-Zahlen-Strings: Werte wie
"42","100%","3,14"werden verworfen. Das sind Datenwerte, keine UI-Kopie, und sollten aus der Datenschicht kommen. - Kurze Strings (≤2 Zeichen): Einzelzeichen und Zwei-Zeichen-Strings werden verworfen. Icons, Aufzählungszeichen, Hochstellungen — das sind keine String-Ressourcen.
- Keys, die zu leer sanitisieren: Wenn ein Layer-Name vollständig aus Zeichen besteht, die von
sanitizeNameherausgefiltert werden, ist das Ergebnis ein leerer Key. Diese Nodes werden still verworfen.
Der Zahlen-Filter verdient mehr Erklärung. Ein Design-Mock hat oft Platzhalter-Zahlen: "42 ms" (Ping), "150 Mbps" (Download-Geschwindigkeit). "42 ms" besteht den Filter, weil es nicht rein numerisch ist — es würde zu Key "42.ms". Aber man würde wahrscheinlich nur "ms" als Einheitenlabel extrahieren und 42 zur Laufzeit berechnen wollen. figmascope macht diese Ableitung nicht — es nimmt den vollständigen Text wie er ist. Der Kurzstring-Filter verwirft "ms" allein (2 Zeichen), aber behält "42 ms" als vollständigen String.
Das ist eine anerkannte Einschränkung. UI-Text, der Template-Daten mit Literal-Labels mischt, ist schwer automatisch zu zerlegen. Der generierte Code sollte den extrahierten String als Ausgangspunkt behandeln, nicht als finale Kopie.
Kollisionsbehandlung
Eine Kollision tritt auf, wenn zwei verschiedene Text-Nodes denselben Key produzieren, aber verschiedene Werte haben. Zum Beispiel: ein Layer namens "Label" auf Screen A enthält "Download" und ein Layer namens "Label" auf Screen B enthält "Upload". Beide generieren Key "label".
figmascopes Kollisionsregel: den Key vollständig aus strings.json fallen lassen und eine Warnung in _meta.json emittieren:
// _meta.json
{
"warnings": [
"strings-collision:label"
]
}
Einen willkürlich zu behalten wäre falsch — man würde einen der beiden Strings still falsch übersetzen. Beide mit disambiguierten Keys zu behalten (z.B. "label.1", "label.2") wäre eine Raterei bei der Benennung, die niemandem dient. Fallen lassen ist ehrlich.
Das IR behandelt Kollisionen sauber: wenn ein Key wegen Kollision fallen gelassen wird, geht figmascope durch das IR und entfernt das stringRef-Feld von allen Leaf-Nodes, die ihn referenzierten. Das text-Feld (Literal-Wert) bleibt. Der Agent sieht einen Leaf mit text aber ohne stringRef und weiß, den Literal als Fallback zu verwenden — was sicher ist, weil die CONTEXT.md-Regel nur sagt "stringRef verwenden wenn vorhanden".
// Vor Kollisionsauflösung — zwei Leaves mit gleichem Key, verschiedenem Text
{ "kind": "leaf", "text": "Download", "stringRef": "label" }
{ "kind": "leaf", "text": "Upload", "stringRef": "label" }
// Nach Kollisionsauflösung — Key fallen gelassen, Text erhalten
{ "kind": "leaf", "text": "Download" }
{ "kind": "leaf", "text": "Upload" }
Die Warnung sagt, die Layer-Namen in Figma zu reparieren. Ein Layer namens "Download Label" und "Upload Label" würden distinkte Keys "download.label" und "upload.label" produzieren, ohne Kollision.
Mapping auf Android strings.xml
Android strings.xml verwendet flache String-Ressourcen-IDs mit Unterstrichen. Die Punkt-Notation aus figmascope mappt sauber:
// strings.json
{
"speed.test": "Geschwindigkeitstest",
"data.transfer.rate": "Datenübertragungsrate",
"results.download": "Download"
}
// → strings.xml (generiert)
<resources>
<string name="speed_test">Geschwindigkeitstest</string>
<string name="data_transfer_rate">Datenübertragungsrate</string>
<string name="results_download">Download</string>
</resources>
Die Transformation ist: Punkte durch Unterstriche ersetzen. Das ist die vollständige Transformation. Android-Ressourcen-IDs erlauben keine Punkte; Unterstriche sind der konventionelle Separator. Der Key speed.test in strings.json wird zu R.string.speed_test in Kotlin, was die CONTEXT.md-Einschränkung den Agenten anweist zu verwenden, wenn er stringRef: "speed.test" sieht.
// CONTEXT.md-Einschränkung in Aktion
// IR-Leaf: { "text": "Geschwindigkeitstest", "stringRef": "speed.test" }
// Agenten-Output:
Text(text = stringResource(R.string.speed_test))
Mapping auf andere i18n-Formate
Punkt-Notation ist eine Lingua Franca für i18n. Die meisten Systeme akzeptieren sie direkt oder brauchen nur minimale Transformation:
| Format | Key-Format | Transformation aus Punkt-Notation |
|---|---|---|
| Android strings.xml | speed_test |
. durch _ ersetzen |
| iOS Localizable.strings | "speed.test" |
Keine (Punkt-Notation direkt verwendet) |
| i18next (JSON) | Verschachtelt: { "speed": { "test": "..." } } |
Flache Keys in verschachtelte Struktur expandieren |
| Flutter ARB | speedTest |
In camelCase konvertieren |
figmascope gibt das Punkt-Notation-Format aus. Zielspezifische Transformation wird downstream gehandhabt — entweder durch den Agenten (wenn das CONTEXT.md-Ziel explizit genug ist) oder durch ein kleines Konvertierungsskript.
Was strings.json nicht löst
Ehrlicher Geltungsbereichs-Hinweis: strings.json wird aus dem Text extrahiert, der zum Export-Zeitpunkt in Figma vorhanden ist. Es ist keine validierte i18n-Ressourcendatei. Drei Probleme, auf die man stoßen wird:
Platzhalter-Text: Figma-Mocks verwenden häufig Lorem Ipsum, "[Titel hier]", oder designergeschriebene Kopie, die nicht überprüft wurde. Die extrahierten Strings sind das, was in der Datei ist, überprüft oder nicht.
Dynamischer Inhalt: Designs zeigen oft "Max Mustermann" oder "42 Mbps" als repräsentative Daten. Das sind keine lokalisierbaren Strings — es sind Template-Werte. figmascope kann Design-Zeit-Daten nicht von Design-Zeit-Kopie unterscheiden. Der Zahlen-Filter fängt reine Zahlen, aber gemischte Strings wie "42 Mbps" werden trotzdem extrahiert.
Fehlende Strings aus gefilterten Nodes: Wenn ein Text-Node einen Filter nicht besteht (zu kurz, nur-numerisch, nach Sanitisierung leer), erscheint er nicht in strings.json und der IR-Leaf hat keinen stringRef. Das ist beabsichtigt, bedeutet aber, dass die String-Abdeckung von der Qualität der Layer-Benennung in Figma abhängt.
Der Output ist ein Ausgangspunkt. In die i18n-Datei kopieren, gegen die eigenen Ressourcen-Benennungskonventionen überprüfen, Platzhalter-Strings löschen und die dynamischen Content-Keys manuell hinzufügen. Das ist zehn Minuten Arbeit statt jeden Text-Node manuell aus Figma zu extrahieren. Eigene Strings aus einer Datei auf figmascope.dev extrahieren.
Für das vollständige Bild, wie stringRef durch das IR in generierten Code fließt, siehe Per-Screen IR — Stack, Overlay, Absolute, Leaf. Für wie die Einschränkungen, die die String-Verwendung regeln, deklariert sind, siehe Aufbau von CONTEXT.md. Für den Jetpack-Compose-Generierungs-Workflow von Anfang bis Ende, siehe Jetpack Compose aus Figma. Bereit, eigene strings.json zu generieren? Die Haupt-App auf figmascope.dev verwenden.