Figma 中的 UI 文本是设计工件。它是原型文案,通常由设计师而非产品经理撰写,很少经过 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.downloadresults.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"(ping)、"150 Mbps"(下载速度)。"42 ms" 通过过滤,因为它不是纯数字——它会成为键 "42.ms"。但你可能想只提取 "ms" 作为单位标签,并在运行时计算 42。figmascope 不做这种推断——它按原样取完整文本。短字符串过滤丢弃单独的 "ms"(2 个字符),但保留 "42 ms" 作为完整字符串。

这是一个已知的局限。混合模板数据和字面标签的 UI 文本很难自动分解。生成的代码应将提取的字符串视为起点,而非最终文案。

碰撞处理

当两个不同的文本节点产生相同的键但值不同时,就会发生碰撞。例如:屏幕 A 上名为 "Label" 的图层包含 "Download",屏幕 B 上名为 "Label" 的图层包含 "Upload"。两者都生成键 "label"

figmascope 的碰撞规则:完全从 strings.json 中删除键,并在 _meta.json 中发出警告:

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

任意保留一个是错误的——你会默默地错误翻译两个字符串中的一个。用消歧键保留两者(例如 "label.1""label.2")是对命名的猜测,对任何人都没有服务。删除是诚实的。

IR 干净地处理碰撞:当一个键因碰撞被删除时,figmascope 遍历 IR 并从所有引用它的叶节点中移除 stringRef 字段。text 字段(字面值)保留。Agent 看到一个有 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>

转换是:将点替换为下划线。这就是完整的转换。Android 资源 ID 不允许点;下划线是传统分隔符。strings.json 中的键 speed.test 在 Kotlin 中变为 R.string.speed_test,这是 CONTEXT.md 约束在看到 stringRef: "speed.test" 时指向 Agent 使用的内容。

// CONTEXT.md 约束实际应用
// IR 叶节点:{ "text": "Speed Test", "stringRef": "speed.test" }
// Agent 输出:
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 转换为驼峰命名法

figmascope 输出点记法格式。特定目标的转换在下游处理——无论是由 Agent(如果 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 生成工作流,请参阅 从 Figma 生成 Jetpack Compose。准备好生成你自己的 strings.json 了吗?使用 figmascope.dev 主应用