UI-текст в Figma — это дизайн-артефакт. Это макетный копирайт, часто написанный дизайнерами, а не продакт-менеджерами, редко проходящий проверку на i18n. figmascope извлекает его в любом случае — потому что даже плейсхолдерный копирайт нуждается в ключе ресурса, если вы генерируете код, который в итоге будет локализован, а логика живёт в процессе извлечения.

strings.json — это артефакт, сопоставляющий ID ресурсов в точечной нотации со строковыми литеральными значениями. Эта статья охватывает: как генерируются ключи, что фильтруется, что происходит при коллизиях ключей, и как это отображается в Android strings.xml и другие i18n-форматы.

Формат

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

Структура — плоский объект с ключами в виде ID ресурсов в точечной нотации. Значения — строковое содержимое из Figma. Вложенные точечные пути семантически значимы — results.download и results.upload находятся в одной логической группе, — но файл представляет собой плоский JSON, а не вложенный. Это соответствует тому, как работает Android strings.xml (плоские имена ресурсов с точечной конвенцией для группировки), и тому, как большинство i18n-библиотек (i18next, Rosetta и т. д.) обрабатывают плоское пространство имён ключей.

Как генерируются ключи

Генерация ключей начинается с имени слоя Figma, а не с текстового содержимого. Текстовый узел с именем Data Transfer Rate Label и содержимым "Data Transfer Rate" генерирует ключ из имени слоя, а не из значения.

Пайплайн генерации:

  1. Берётся имя слоя
  2. Применяются фильтры sanitizeName: транслитерация кириллических символов, удаление двунаправленных/управляющих символов (диапазон Unicode U+202A–U+202E и аналогичные), удаление не-буквенно-цифровых не-пробельных символов
  3. Перевод в нижний регистр и замена пробелов на точки
  4. Обрезка ведущих и конечных точек

Таким образом, "Data Transfer Rate Label""data.transfer.rate.label". На практике дизайнеры часто называют текстовые слои без конечного существительного, поэтому "Data Transfer Rate""data.transfer.rate".

Шаг транслитерации кириллицы существует потому, что figmascope используется с файлами Figma на разных языках. Слой с именем Скорость транслитерируется в Skorost', затем становится "skorost'" → после удаления спецсимволов → "skorost". Не самый красивый, но ключ стабилен между запусками для одного и того же имени слоя.

Генерация ключей — из имени слоя, не из текстового значения. Два текстовых узла с идентичным текстовым содержимым, но разными именами слоёв получают разные ключи. Два текстовых узла с разным текстовым содержимым, но одинаковым именем слоя создают коллизию — обрабатывается ниже.

Фильтры ключей — что отбрасывается

Не каждый текстовый узел в файле Figma должен быть локализуемой строкой. figmascope фильтрует перед генерацией ключей:

Числовой фильтр требует дополнительного объяснения. Дизайн-макет часто содержит числа-заглушки: "42 ms" (пинг), "150 Mbps" (скорость загрузки). "42 ms" проходит фильтр, потому что это не чисто число — он стал бы ключом "42.ms". Но вероятно, вы хотите извлечь только "ms" как метку единицы и вычислять 42 в runtime. figmascope не делает этот вывод — он берёт полный текст как есть. Фильтр коротких строк отбрасывает "ms" отдельно (2 символа), но сохраняет "42 ms" как полную строку.

Это признанное ограничение. UI-текст, смешивающий данные шаблона с буквальными метками, трудно автоматически разложить. Сгенерированный код должен трактовать извлечённую строку как отправную точку, а не финальный копирайт.

Обработка коллизий

Коллизия происходит, когда два разных текстовых узла производят один и тот же ключ, но имеют разные значения. Например: слой с именем "Label" на экране A содержит "Download", а слой с именем "Label" на экране B содержит "Upload". Оба генерируют ключ "label".

Правило коллизии figmascope: полностью удалить ключ из strings.json и выдать предупреждение в _meta.json:

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

Произвольное сохранение одного из них было бы неправильным — вы бы молча неправильно перевели одну из двух строк. Сохранение обоих с разграничёнными ключами (например, "label.1", "label.2") — это угадывание имён, которое никому не служит. Удаление честно.

IR обрабатывает коллизию чисто: когда ключ удаляется из-за коллизии, figmascope обходит IR и удаляет поле stringRef из всех leaf-узлов, которые на него ссылались. Поле text (литеральное значение) остаётся. Агент видит лист с text, но без stringRef, и знает использовать литерал как запасной вариант — что безопасно, потому что правило CONTEXT.md гласит только «использовать stringRef при наличии».

// До разрешения коллизии — два листа с одним ключом, разным текстом
{ "kind": "leaf", "text": "Download", "stringRef": "label" }
{ "kind": "leaf", "text": "Upload",   "stringRef": "label" }

// После разрешения коллизии — ключ удалён, текст сохранён
{ "kind": "leaf", "text": "Download" }
{ "kind": "leaf", "text": "Upload"   }

Предупреждение говорит вам исправить имена слоёв в Figma. Слой с именем "Download Label" и "Upload Label" произвёл бы различные ключи "download.label" и "upload.label" без коллизии.

Маппинг в Android strings.xml

Android strings.xml использует плоские ID строковых ресурсов с подчёркиваниями. Точечная нотация из figmascope отображается чисто:

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

// → strings.xml (сгенерированный)
<resources>
    <string name="speed_test">Speed Test</string>
    <string name="data_transfer_rate">Data Transfer Rate</string>
    <string name="results_download">Download</string>
</resources>

Преобразование: заменить точки подчёркиваниями. Это полное преобразование. ID ресурсов Android не допускают точки; подчёркивания — условный разделитель. Ключ speed.test в strings.json становится R.string.speed_test в Kotlin — это то, на что указывает ограничение CONTEXT.md агенту при виде stringRef: "speed.test".

// Ограничение CONTEXT.md в действии
// IR-лист: { "text": "Speed Test", "stringRef": "speed.test" }
// Вывод агента:
Text(text = stringResource(R.string.speed_test))

Маппинг в другие i18n-форматы

Точечная нотация — это лингва франка для i18n. Большинство систем принимают её напрямую или требуют лишь минимального преобразования:

ФорматФормат ключаПреобразование из точечной нотации
Android strings.xml speed_test Заменить . на _
iOS Localizable.strings "speed.test" Нет (точечная нотация используется напрямую)
i18next (JSON) Вложенный: { "speed": { "test": "..." } } Развернуть плоские ключи во вложенную структуру
Flutter ARB speedTest Конвертировать в camelCase

figmascope выводит формат точечной нотации. Целевое преобразование обрабатывается дальше по пайплайну — либо агентом (если цель CONTEXT.md достаточно явна), либо небольшим скриптом конвертации.

Что strings.json не решает

Честная оговорка об области применения: strings.json извлекается из того текста, который существует в Figma на момент экспорта. Это не проверенный i18n-файл ресурсов. Три проблемы, с которыми вы столкнётесь:

Плейсхолдерный текст: Макеты Figma часто используют Lorem Ipsum, "[Title Here]" или авторский копирайт дизайнера, ещё не прошедший проверку. Извлечённые строки — это то, что есть в файле, проверенное или нет.

Динамический контент: Дизайны часто показывают "John Doe" или "42 Mbps" как репрезентативные данные. Это не локализуемые строки — это значения шаблонов. figmascope не может отличить дизайн-время данные от дизайн-время копирайта. Числовой фильтр улавливает чистые числа, но смешанные строки типа "42 Mbps" всё равно извлекаются.

Отсутствующие строки из отфильтрованных узлов: Если текстовый узел не проходит фильтр (слишком короткий, только цифры, пустой после санации), он не появится в strings.json, и IR-лист не будет иметь stringRef. Это преднамеренно, но означает, что покрытие строками зависит от качества именования слоёв в Figma.

Вывод — это отправная точка. Скопируйте его в ваш i18n-файл, сверьтесь с вашими реальными конвенциями именования ресурсов, удалите плейсхолдерные строки и добавьте ключи динамического контента вручную. Это десять минут работы вместо ручного извлечения каждого текстового узла из Figma. Попробуйте извлечь строки из вашего собственного файла на figmascope.dev.

Для полной картины того, как stringRef проходит через IR в сгенерированный код, см. Попэкранное IR — Stack, Overlay, Absolute, Leaf. Для понимания того, как объявляются ограничения, управляющие использованием строк, см. Анатомия CONTEXT.md. Для полного рабочего процесса генерации Jetpack Compose, см. Jetpack Compose из Figma. Готовы сгенерировать свой strings.json? Используйте основное приложение на figmascope.dev.