Tekst UI w Figmie to artefakt projektu. To kopia makiety, często pisana przez projektantów, a nie menedżerów produktu, rzadko przeglądana pod kątem i18n. figmascope ekstrahuje ją mimo to — bo nawet zastępczy tekst potrzebuje klucza zasobu, jeśli generujesz kod, który ostatecznie zostanie zlokalizowany, a proces ekstrakcji jest miejscem, gdzie żyje logika.
strings.json to artefakt mapujący ID zasobów w notacji z kropką na dosłowne wartości ciągów. Ten artykuł omawia jak generowane są klucze, co jest filtrowane, co dzieje się przy kolizjach kluczy i jak to mapuje na Android strings.xml i inne formaty i18n.
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"
}
Struktura to płaski obiekt z kluczami będącymi ID zasobów w notacji z kropką. Wartości to dosłowna zawartość ciągu z Figmy. Zagnieżdżone ścieżki z kropką są semantycznie znaczące — results.download i results.upload są w tej samej logicznej grupie — ale plik to płaski JSON, nie zagnieżdżony. Odpowiada to sposobowi działania strings.xml Androida (płaskie nazwy zasobów z konwencją kropkową do grupowania) i obsłudze płaskich przestrzeni kluczy przez większość bibliotek i18n (i18next, Rosetta itp.).
Jak generowane są klucze
Generowanie kluczy zaczyna się od nazwy warstwy Figma węzła, nie od zawartości tekstu. Węzeł tekstowy o nazwie Data Transfer Rate Label z treścią "Data Transfer Rate" generuje klucz z nazwy warstwy, nie z wartości.
Pipeline generowania:
- Pobierz nazwę warstwy
- Zastosuj filtry
sanitizeName: transliteruj znaki cyrylicy, usuń znaki dwukierunkowe/sterujące (zakres Unicode U+202A–U+202E i podobne), usuń znaki niealfanumeryczne i nieniedostępne - Zamień na małe litery i zastąp spacje kropkami
- Przytnij wiodące i końcowe kropki
Więc "Data Transfer Rate Label" → "data.transfer.rate.label". W praktyce projektanci często nazywają warstwy tekstowe bez końcowego rzeczownika, więc "Data Transfer Rate" → "data.transfer.rate".
Krok transliteracji cyrylicy istnieje, bo figmascope jest używane z plikami Figma w różnych językach. Warstwa o nazwie Скорость transliteruje się na Skorost', potem staje się "skorost'" → po usunięciu znaków specjalnych → "skorost". Nie piękne, ale klucz jest stabilny między przebiegami dla tej samej nazwy warstwy.
Generowanie kluczy odbywa się z nazwy warstwy, nie z wartości tekstu. Dwa węzły tekstowe z identyczną zawartością tekstu ale różnymi nazwami warstw dostają różne klucze. Dwa węzły tekstowe z różną zawartością tekstu ale tą samą nazwą warstwy tworzą kolizję — obsługiwaną poniżej.
Filtry kluczy — co jest usuwane
Nie każdy węzeł tekstowy w pliku Figma powinien być lokalizowalnym ciągiem. figmascope filtruje przed generowaniem kluczy:
- Puste ciągi: Węzły tekstowe bez zawartości lub tylko ze spacjami są usuwane.
- Ciągi tylko numeryczne: Wartości takie jak
"42","100%","3.14"są usuwane. To wartości danych, nie copy UI, i powinny pochodzić z warstwy danych. - Krótkie ciągi (≤2 znaki): Znaki pojedyncze i dwuznakowe ciągi są usuwane. Ikony, punktory, indeksy górne — to nie są zasoby ciągów.
- Klucze sanityzowane do pustych: Jeśli nazwa warstwy składa się wyłącznie ze znaków usuniętych przez
sanitizeName, wynikiem jest pusty klucz. Te węzły są cicho usuwane.
Filtr numeryczny zasługuje na więcej wyjaśnienia. Makieta projektu często ma zastępcze liczby: "42 ms" (ping), "150 Mbps" (prędkość pobierania). "42 ms" przechodzi filtr, bo nie jest czysto numeryczny — stałby się kluczem "42.ms". Ale prawdopodobnie chciałbyś wyodrębnić tylko "ms" jako etykietę jednostki i obliczać 42 w czasie działania. figmascope nie robi tej inferencji — bierze pełny tekst tak jak jest. Filtr krótkiego ciągu usuwa "ms" (2 znaki), ale zachowuje "42 ms" jako pełny ciąg.
To znane ograniczenie. Tekst UI mieszający dane szablonowe z dosłownymi etykietami jest trudny do automatycznego rozkładu. Wygenerowany kod powinien traktować wyodrębniony ciąg jako punkt startowy, nie finalną kopię.
Obsługa kolizji
Kolizja występuje, gdy dwa różne węzły tekstowe produkują ten sam klucz ale mają różne wartości. Na przykład: warstwa o nazwie "Label" na ekranie A zawiera "Download" i warstwa o nazwie "Label" na ekranie B zawiera "Upload". Obie generują klucz "label".
Reguła kolizji figmascope: usuń klucz całkowicie z strings.json i emituj ostrzeżenie w _meta.json:
// _meta.json
{
"warnings": [
"strings-collision:label"
]
}
Zachowanie jednego arbitralnie byłoby błędne — cicho przetłumaczyłbyś jeden z dwóch ciągów niepoprawnie. Zachowanie obu z ujednoznacznionymi kluczami (np. "label.1", "label.2") byłoby zgadywaniem nazewnictwa, które nikomu nie służy. Usuwanie jest uczciwe.
IR obsługuje kolizję czysto: gdy klucz jest usuwany z powodu kolizji, figmascope przechodzi IR i usuwa pole stringRef ze wszystkich węzłów liści, które się na niego powoływały. Pole text (wartość dosłowna) pozostaje. Agent widzi liść z text ale bez stringRef i wie, by użyć dosłownej jako fallbacku — co jest bezpieczne, bo reguła CONTEXT.md mówi tylko "użyj stringRef jeśli obecny".
// Przed rozwiązaniem kolizji — dwa liście z tym samym kluczem, różnym tekstem
{ "kind": "leaf", "text": "Download", "stringRef": "label" }
{ "kind": "leaf", "text": "Upload", "stringRef": "label" }
// Po rozwiązaniu kolizji — klucz usunięty, tekst zachowany
{ "kind": "leaf", "text": "Download" }
{ "kind": "leaf", "text": "Upload" }
Ostrzeżenie mówi Ci, by naprawić nazwy warstw w Figmie. Warstwa o nazwie "Download Label" i "Upload Label" produkuje oddzielne klucze "download.label" i "upload.label" bez kolizji.
Mapowanie na Android strings.xml
Android strings.xml używa płaskich ID zasobów ciągów z podkreślnikami. Notacja z kropką figmascope mapuje czysto:
// strings.json
{
"speed.test": "Speed Test",
"data.transfer.rate": "Data Transfer Rate",
"results.download": "Download"
}
// → strings.xml (wygenerowany)
<resources>
<string name="speed_test">Speed Test</string>
<string name="data_transfer_rate">Data Transfer Rate</string>
<string name="results_download">Download</string>
</resources>
Transformacja to: zastąp kropki podkreślnikami. To cała transformacja. ID zasobów Androida nie dopuszczają kropek; podkreślniki to konwencjonalny separator. Klucz speed.test w strings.json staje się R.string.speed_test w Kotlinie, co jest tym, na co ograniczenie CONTEXT.md wskazuje agentowi, gdy widzi stringRef: "speed.test".
// Ograniczenie CONTEXT.md w działaniu
// Liść IR: { "text": "Speed Test", "stringRef": "speed.test" }
// Wyjście agenta:
Text(text = stringResource(R.string.speed_test))
Mapowanie na inne formaty i18n
Notacja z kropką to lingua franca dla i18n. Większość systemów akceptuje ją bezpośrednio lub potrzebuje minimalnej transformacji:
| Format | Format klucza | Transformacja z notacji z kropką |
|---|---|---|
| Android strings.xml | speed_test |
Zastąp . przez _ |
| iOS Localizable.strings | "speed.test" |
Brak (notacja z kropką używana bezpośrednio) |
| i18next (JSON) | Zagnieżdżone: { "speed": { "test": "..." } } |
Rozwiń płaskie klucze w strukturę zagnieżdżoną |
| Flutter ARB | speedTest |
Konwertuj na camelCase |
figmascope wyprowadza format notacji z kropką. Transformacja specyficzna dla celu odbywa się downstream — albo przez agenta (jeśli cel CONTEXT.md jest wystarczająco explicite) albo przez mały skrypt konwersji.
Czego strings.json nie rozwiązuje
Uczciwa uwaga o zakresie: strings.json jest ekstrahowany z tekstu istniejącego w Figmie w czasie eksportu. To nie jest zwalidowany plik zasobów i18n. Trzy problemy, na które napotkasz:
Tekst zastępczy: Makiety Figmy często używają Lorem Ipsum, "[Tytuł tutaj]" lub kopii napisanej przez projektantów, która nie była przeglądana. Wyodrębnione ciągi to to, co jest w pliku, przejrzane lub nie.
Zawartość dynamiczna: Projekty często pokazują "Jan Kowalski" lub "42 Mbps" jako reprezentatywne dane. To nie są lokalizowalne ciągi — to wartości szablonowe. figmascope nie może odróżnić danych czasu projektowania od kopii czasu projektowania. Filtr numeryczny łapie czyste liczby, ale mieszane ciągi jak "42 Mbps" są nadal ekstrahowane.
Brakujące ciągi z filtrowanych węzłów: Jeśli węzeł tekstowy nie przejdzie filtru (za krótki, tylko numeryczny, pusty po sanityzacji), nie pojawi się w strings.json i liść IR nie będzie miał stringRef. To jest zamierzone, ale oznacza, że pokrycie ciągami zależy od jakości nazewnictwa warstw w Figmie.
Wyjście to punkt startowy. Skopiuj je do pliku i18n, przejrzyj go względem faktycznych konwencji nazewnictwa zasobów, usuń zastępcze ciągi i ręcznie dodaj klucze zawartości dynamicznej. To dziesięć minut pracy zamiast ręcznego wyodrębniania każdego węzła tekstowego z Figmy. Spróbuj wyodrębnić ciągi z własnego pliku na figmascope.dev.
Dla pełnego obrazu tego, jak stringRef przepływa przez IR do wygenerowanego kodu, zobacz Per-Screen IR — Stack, Overlay, Absolute, Leaf. Jak zadeklarowane są ograniczenia rządzące użyciem ciągów, zobacz Anatomia CONTEXT.md. Dla workflow generowania Jetpack Compose od końca do końca, zobacz Jetpack Compose z Figmy. Gotowy do wygenerowania własnego strings.json? Użyj głównej aplikacji na figmascope.dev.