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"を持つData Transfer Rate Labelという名前のテキストノードは、値ではなくレイヤー名からキーを生成します。

生成パイプライン:

  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"。見栄えが良くないですが、同じレイヤー名に対してキーは実行間で安定しています。

キー生成はテキスト値からではなくレイヤー名から行われます。同一のテキストコンテンツを持つ2つのテキストノードが異なるレイヤー名を持てば異なるキーを得ます。異なるテキストコンテンツを持つ2つのテキストノードが同じレイヤー名を持てば衝突が発生します—以下で処理します。

キーフィルター — 何が除外されるか

Figmaファイルのすべてのテキストノードがローカライズ可能な文字列である必要はありません。figmascopeはキーを生成する前にフィルタリングします:

数値フィルターはより詳しい説明が必要です。デザインモックはしばしばプレースホルダー数値を持ちます:"42 ms"(ping)、"150 Mbps"(ダウンロード速度)。"42 ms"は純粋に数値でないのでフィルターをパスします—キー"42.ms"になります。しかし"ms"だけをユニットラベルとして抽出し42を実行時に計算したい場合があります。figmascopeはその推論を行いません—フルテキストをそのまま取ります。短い文字列フィルターは"ms"単独(2文字)を除外しますが、"42 ms"をフル文字列として保持します。

これは認識されている制限です。テンプレートデータとリテラルラベルを混在させるUIテキストは自動的に分解するのが難しいです。生成されたコードは抽出された文字列を最終的なコピーではなく出発点として扱うべきです。

衝突処理

衝突は2つの異なるテキストノードが同じキーを生成するが異なる値を持つときに発生します。例:画面Aの"Label"という名前のレイヤーには"Download"が含まれ、画面Bの"Label"という名前のレイヤーには"Upload"が含まれます。どちらもキー"label"を生成します。

figmascopeの衝突ルール:strings.jsonからキーを完全に除外し、_meta.jsonに警告を発します:

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

任意に一つを保持することは間違っています—2つの文字列のうち1つを静かに誤って翻訳することになります。明確化されたキーで両方を保持すること(例:"label.1""label.2")は誰にも役立たない推測です。除外することが正直です。

IRは衝突をクリーンに処理します:キーが衝突のために除外されると、figmascopeはIRを走査し、それを参照していたすべてのリーフノードからstringRefフィールドを削除します。textフィールド(リテラル値)は残ります。エージェントはstringRefのないtextを持つリーフを見て、リテラルをフォールバックとして使うことを知ります—CONTEXT.mdのルールは「存在する場合はstringRefを使う」とだけ言っているので。

// 衝突解決前 — 同じキー、異なるテキストを持つ2つのリーフ
{ "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"を見たときにエージェントに使うよう指示するものです。

// 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リソースファイルではありません。遭遇する3つの問題:

プレースホルダーテキスト:Figmaモックはしばしばローレム・イプスム、"[Title Here]"、またはレビューされていないデザイナーが書いたコピーを使います。抽出された文字列はレビュー済みかどうかに関わらずファイルにあるものです。

動的コンテンツ:デザインはしばしば代表的なデータとして"John Doe"または"42 Mbps"を示します。これらはローカライズ可能な文字列ではありません—テンプレート値です。figmascopeはデザイン時のデータとデザイン時のコピーを区別できません。数値フィルターは純粋な数値を捉えますが、"42 Mbps"のような混合文字列はまだ抽出されます。

フィルタリングされたノードからの文字列の欠落:テキストノードがフィルターに失敗した場合(短すぎ、数値のみ、サニタイズ後に空)、strings.jsonに現れず、IRリーフにはstringRefがありません。これは意図的ですが、文字列カバレッジはFigmaのレイヤー命名の品質に依存することを意味します。

出力は出発点です。i18nファイルにコピーして、実際のリソース命名規則と照らしてレビューし、プレースホルダー文字列を削除して、動的コンテンツのキーを手動で追加してください。FigmaのすべてのテキストノードEDEKを手動で抽出する代わりに10分の作業です。figmascope.devで自分のファイルから文字列を抽出してみてください

stringRefがIRを通じて生成されたコードにどのようにフローするかの全体像は画面ごとのIR — スタック・オーバーレイ・アブソリュート・リーフで。文字列使用を管理する制約がどのように宣言されるかはCONTEXT.md の解剖で。エンドツーエンドのJetpack Compose生成ワークフローはFigmaからJetpack Composeで。自分のstrings.jsonを生成する準備ができたらfigmascope.devのメインアプリを使ってください