النصوص في Figma هي نتاج تصميمي. إنها نصوص نموذجية يكتبها المصممون في الغالب وليس مديري المنتج، ونادرًا ما تُراجع للتعريب. تستخرجها figmascope على أي حال — لأن حتى النصوص النموذجية تحتاج إلى مفتاح مورد إن كنت تولّد كودًا سيُعرَّب لاحقًا، وعملية الاستخراج هي المكان الذي تعيش فيه المنطق.
strings.json هو الناتج الذي يُعيّن معرّفات موارد بتدوين النقطة إلى قيم النصوص الحرفية. يتناول هذا المقال كيفية توليد المفاتيح، وما يُستبعد منها، وما يحدث عند تصادم المفاتيح، وكيف يتعيّن ذلك على 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"
}
البنية عبارة عن كائن مسطّح مفهرَس بمعرّفات موارد بتدوين النقطة. القيم هي محتوى النص الحرفي من Figma. مسارات النقطة المتداخلة ذات معنى دلالي — results.download وresults.upload ينتميان إلى المجموعة المنطقية ذاتها — لكن الملف JSON مسطّح وليس متداخلاً. هذا يتوافق مع طريقة عمل strings.xml في Android (أسماء موارد مسطّحة مع اتفاقية النقطة للتجميع) وكيفية تعامل معظم مكتبات i18n (i18next وRosetta وغيرها) مع فضاء الأسماء المسطّح.
كيف تُولَّد المفاتيح
يبدأ توليد المفتاح من اسم طبقة Figma للعقدة، لا من محتوى النص. عقدة نصية اسمها Data Transfer Rate Label بمحتوى "Data Transfer Rate" تُولّد مفتاحًا من اسم الطبقة لا من القيمة.
خط أنابيب التوليد:
- أخذ اسم الطبقة
- تطبيق فلاتر
sanitizeName: ترجمة حروف السيريلية، إزالة محارف ثنائية الاتجاه/التحكم (نطاق Unicode من U+202A إلى U+202E وما يشابهها)، إزالة المحارف غير الأبجدية الرقمية وغير المسافة - التحويل إلى أحرف صغيرة واستبدال المسافات بنقاط
- حذف النقاط البادئة والنهائية
إذن "Data Transfer Rate Label" ← "data.transfer.rate.label". في الواقع العملي، كثيرًا ما يُسمّي المصممون طبقات النص دون الاسم النهائي، فيصبح "Data Transfer Rate" ← "data.transfer.rate".
خطوة ترجمة السيريلية موجودة لأن figmascope يُستخدم مع ملفات Figma بلغات متعددة. طبقة اسمها Скорость تُترجم إلى Skorost'، ثم تصبح "skorost'" وبعد حذف المحارف الخاصة تصبح "skorost". ليست جميلة، لكن المفتاح مستقر عبر التشغيلات لاسم الطبقة ذاته.
توليد المفتاح مبني على اسم الطبقة لا على قيمة النص. عقدتان نصيتان بمحتوى نصي متطابق لكن بأسماء طبقات مختلفة يحصلان على مفتاحين مختلفين. عقدتان نصيتان بمحتوى نصي مختلف لكن باسم طبقة متطابق يُنشئان تصادمًا — معالج أدناه.
فلاتر المفاتيح — ما يُستبعد
ليس كل عقدة نصية في ملف Figma ينبغي أن تكون نصًا قابلاً للتعريب. تُصفّي figmascope قبل توليد المفاتيح:
- النصوص الفارغة: العقد النصية بدون محتوى أو محتوى فراغات فقط تُستبعد.
- النصوص الرقمية فقط: القيم كـ
"42"و"100%"و"3.14"تُستبعد. هذه قيم بيانات لا نصوص واجهة، ويجب أن تأتي من طبقة البيانات. - النصوص القصيرة (≤2 حرف): المحارف الفردية والنصوص ذات حرفين تُستبعد. أيقونات، نقاط قائمة، مؤشرات علوية — هذه ليست موارد نصوص.
- المفاتيح التي تُصبح فارغة بعد التطهير: إذا كان اسم طبقة يتكون بأكمله من محارف تُحذف بـ
sanitizeName، فالنتيجة مفتاح فارغ. تُستبعد هذه العقد بصمت.
فلتر الأرقام يستحق مزيدًا من التوضيح. كثيرًا ما تحتوي نماذج التصميم على أرقام نموذجية: "42 ms" (البينغ)، "150 Mbps" (سرعة التنزيل). "42 ms" يجتاز الفلتر لأنه ليس رقميًا خالصًا — سيصبح مفتاح "42.ms". لكنك ربما تريد استخراج "ms" فقط كتسمية وحدة وحساب 42 في وقت التشغيل. لا يُجري figmascope هذا الاستنتاج — يأخذ النص الكامل كما هو. فلتر النصوص القصيرة يستبعد "ms" وحده (حرفان)، لكن يحتفظ بـ "42 ms" كنص كامل.
هذا قيد معترَف به. نص الواجهة الذي يمزج بيانات النماذج مع التسميات الحرفية يصعب تفكيكه آليًا. ينبغي أن تعامل الكود المولَّد النص المستخرج كنقطة بداية لا نسخة نهائية.
معالجة التصادمات
يحدث التصادم عندما تُنتج عقدتان نصيتان مختلفتان المفتاح ذاته لكن بقيم مختلفة. مثلاً: طبقة اسمها "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 من جميع عقد الأوراق التي أشارت إليه. يظل حقل 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 معرّفات موارد نصية مسطّحة مع شرطات سفلية. يُعيَّن تدوين النقطة من 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 لا تسمح بالنقاط؛ الشرطات السفلية هي الفاصل المتعارف عليه. المفتاح 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 نص لوريم إيبسوم، "[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.