باختصار
كتيّب عن ثلاثة أسطرلابات قديمة، طُبع بالإنجليزية والإسبانية والعربية. تشترك الطبعات الثلاث في تصميم واحد، ولا تغيّر إلا الطبعة العربية خطوطها وأحجامها.
ما الذي ستنضده
كتيّب من أربع صفحات بقطع A5 عن ثلاثة أسطرلابات في متحف المتروبوليتان للفنون: أحدها صُنع في اليمن سنة ١٢٩١، والثاني في إيران سنة ١٦٥٤، والثالث في أوروبا سنة ١٤٦٢. يصدره الناشر بالإنجليزية والإسبانية والعربية، والطبعات الثلاث تصميم واحد. فصورة الغلاف والعناوين بلون النحاس والصندوق بخطه الجانبي والصور التي يلتفّ النص حولها ورأس الجدول الداكن كلها من إعدادات واحدة. أما الطبعة العربية فتغيّر ما يخصّ خطّها: أميري وIBM Plex Sans Arabic مكان Source Serif وSource Sans، وحجمًا أكبر، وتسميات بلا حروف كبيرة ولا تباعد. وهي تُقرأ من اليمين وتُجلَّد من اليمين دون أي إحداثي خاص بها.
تجيب هذه الوصفة عن
- كيف أجعل طبعات كتاب بعدّة لغات تتشارك تصميمًا واحدًا وأغيّر فقط خطوط كل لغة وتقطيع كلماتها وعلامات اقتباسها وتسمياتها؟
- كيف أنشر الكتاب نفسه بلغتين من مشروع واحد؟
الجواب المختصر
const ARABIC_LABEL = { fontFamily: 'IBM Plex Sans Arabic', letterSpacing: pt(0),
textTransform: 'none' }; // Arabic letters join: never tracked, and they have no capitals
// English and Spanish need nothing more than their locale: it picks the hyphenation patterns
// and the words of the figure and table labels. Arabic sets the faces of its script.
const overrides = {
ar: {
bodyText: { fontFamily: 'Amiri', fontSize: pt(12), lineHeight: pt(19),
firstLineIndent: em(1.5) },
headings: { fontFamily: 'Amiri', fontWeight: 700,
levels: [{ level: 2, fontSize: pt(15), lineHeight: pt(24) }] },
// Lists keyed by id merge entry by entry: only these fields of these elements change.
headingStyles: [{ id: 'cover', advancedDesign: { slot: { elements: [
{ id: 'kicker', ...ARABIC_LABEL, fontSize: pt(10) },
{ id: 'title', fontFamily: 'Amiri', fontWeight: 700, fontSize: pt(50), lineHeight: 1.25 },
{ id: 'facts', ...ARABIC_LABEL, fontSize: pt(9.5) },
] } } }],
header: { elements: ['verso-folio', 'verso-title', 'recto-title', 'recto-folio']
.map((id) => ({ id, ...ARABIC_LABEL, fontSize: pt(9) })) },
calloutStyles: [{ id: 'note', titleStyle: { ...ARABIC_LABEL, fontSize: pt(10) },
body: { fontSize: pt(11.5), lineHeight: pt(19) } }],
tableStyle: { headerFontFamily: 'IBM Plex Sans Arabic', headerFontSize: pt(9),
bodyFontFamily: 'IBM Plex Sans Arabic', bodyFontSize: pt(9.4) },
captionStyle: { fontFamily: 'IBM Plex Sans Arabic', fontSize: pt(9) },
paragraphStyles: [{ id: 'colophon', fontFamily: 'IBM Plex Sans Arabic', fontSize: pt(8.4),
lineHeight: pt(13) }],
},
};
const edition = mergeConfigLayers(config, overrides[LANG]); // config itself for en and es
المكونات
- الميزات
- إعدادات خاصة بكل لغةالنص من اليمين إلى اليسارالخطوط العربيةكتب مجلّدة من اليمينأشكال الأرقام في الأعداد المولَّدةتقسيم الكلمات ولغة المستند«شكل» و«جدول» بلغتكالأغلفة وصفحات العنوان وبيانات النشرأنماط العناوينسمات العنوانالصور في تصاميم الصفحةالترويسات وأرقام الصفحاتالتفاف النص حول الصور والصناديقإطارات التنبيهنمط الجدولنمط التعليقلوحة ألوان دلاليةالتصدير إلى PDF
- تستخدم أيضًا
- إطارات عائمةأشكال في هذا الموضع بالضبطشريط فصل بعرض الصفحةصفحات افتتاح مصمَّمةالترويسات بحسب دور الصفحةأنماط الفقراتالخطوط المضمَّنة في PDFالأشكال والجداول بوصفها مواردفواصل الأسطر في العناوين
- الخطوط
- Source Serif 4, Source Sans 3, Amiri, IBM Plex Sans Arabic (SIL OFL 1.1)
- الأصول
mashhad-800.jpgrasulid-cover-1400.jpgregiomontanus-800.jpg- Astrolabe of ʿUmar ibn Yusuf, Yemen, 1291 (the cover; background extended at the top and darkened) (The Metropolitan Museum of Art, CC0 1.0)
- Muhammad Zaman, planispheric astrolabe, Iran, 1654–55 (cropped) (The Metropolitan Museum of Art, CC0 1.0)
- Astrolabe attributed to Regiomontanus, 1462 (cropped) (The Metropolitan Museum of Art, CC0 1.0)
طريقة التحضير
#١ · إعدادات عامة وثلاثة إعدادات صغيرة للغات
الشيفرة في الجواب المختصر أعلاه. يحمل config الإعدادات العامة، ويضع mergeConfigLayers فوقها إعدادات الطبعة حقلًا حقلًا. ليس للإنجليزية والإسبانية إعدادات خاصة: فالـlocale يختار أنماط تقطيع الكلمات وكلمات مثل Figure وTable أو Figura وTabla. أما الإعدادات العربية فلا تذكر إلا حقولًا مفردة: bodyText.fontFamily يغيّر الخط ويُبقي اللون والمحاذاة وإعداد التقطيع، والعنصر kicker في الغلاف يغيّر خطه وتباعده ويُبقي مرساته ولونه وحجمه. وأي تغيير في لوحة الألوان أو الهوامش داخل config يصل إلى الطبعات الثلاث.
#٢ · جهات تُسمّى باتجاه النص
// An even page's outer edge is its start side in either direction (the left of an English
// verso, the right of an Arabic one), an odd page's its end side.
const head = (id, content, parity, edge, x, extra = {}) => ({ kind: 'text', id, content,
parity, pages: 'body', fontFamily: LABEL, fontSize: pt(7.5), fontWeight: 600,
letterSpacing: pt(1.1), textTransform: 'uppercase', color: col('muted'),
align: edge.endsWith('start') ? 'start' : 'end',
placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(12) } }, ...extra });
const folio = { fontWeight: 700, color: col('brass') };
const header = { elements: [
head('verso-folio', '{pageNumber}', 'even', 'top-start', 16, folio),
head('verso-title', '{title}', 'even', 'top-start', 24),
head('recto-title', '{subtitle}', 'odd', 'top-end', -24),
head('recto-folio', '{pageNumber}', 'odd', 'top-end', -16, folio),
] };
الحافة الخارجية للصفحة الزوجية هي بداية أسطرها في الاتجاهين، وحافة الفردية هي نهايتها؛ ولذلك يضع top-start وtop-end أرقام الصفحات في الخارج في الطبعة الإنجليزية والعربية معًا. والخط الجانبي للصندوق side: 'start'، ونصوص الغلاف مثبتة إلى top-start: لا يحتاج شيء منها إلى إعداد خاص.
#٣ · الصور في نهاية السطر والنص بجانبها
const photo = (id, file, w, h, extra = {}) => ({ id, typeId: 'figure', kind: 'bitmap',
createdAt: 0, updatedAt: 0, ...extra,
bitmap: { fileId: file, format: 'jpeg', width: w, height: h } });
// 'end' is the right of an English column and the left of an Arabic one.
const wrapped = { position: 'here', wrap: 'end', width: 0.38 };
const note = (n) => `The Met, ${n}, CC0`;
const resources = [
photo('rasulid', 'rasulid-cover-1400.jpg', 1400, 1985),
photo('mashhad', 'mashhad-800.jpg', 800, 1187, { placement: wrapped, note: note('63.166a–j'),
caption: t({ en: 'Planispheric astrolabe, Iran, 1654–55. Brass and steel.',
es: 'Astrolabio planisférico, Irán, 1654-1655. Latón y acero.',
ar: 'أسطرلاب مسطّح، إيران، ١٠٦٥\u00a0هـ / ١٦٥٤–١٦٥٥\u00a0م. نحاس أصفر وفولاذ.' }),
altText: t({ en: 'A brass astrolabe whose rete is a lattice of leaves and scrolls',
es: 'Un astrolabio de latón cuya red es un enrejado de hojas y volutas',
ar: 'أسطرلاب من النحاس الأصفر عنكبوته شبكة من الأوراق والأغصان الملتفّة' }) }),
photo('regiomontanus', 'regiomontanus-800.jpg', 800, 1083,
{ placement: wrapped,
note: note('2025.347a–j'),
caption: t({ en: 'Astrolabe attributed to Regiomontanus, 1462. Copper alloy.',
es: 'Astrolabio atribuido a Regiomontano, 1462. Aleación de cobre.',
ar: 'أسطرلاب يُنسب إلى ريجيومونتانوس، ١٤٦٢\u00a0م. سبيكة نحاسية.' }),
altText: t({ en: 'A round brass astrolabe with Latin star names on its rete',
es: 'Un astrolabio redondo de latón con nombres latinos de estrellas en la red',
ar: 'أسطرلاب مستدير من النحاس عليه أسماء نجوم لاتينية' }) }),
];
يضع wrap: 'end' كل صورة على يمين العمود الإنجليزي وعلى يسار العمود العربي، ويجري النص بجانبها. والتعليقات والنصوص البديلة بلغة كل طبعة، والتعليقات العربية تبدأ بالتاريخ الهجري.
#٤ · الجدول بكلمات كل طبعة وأرقامها
const row = (...cells) => cells.map((content) => ({ content }));
const head2 = (...cells) => cells.map((content) => ({ content, isHeader: true }));
resources.push({ id: 'three', typeId: 'table', kind: 'table', createdAt: 0, updatedAt: 0,
placement: { position: 'here' },
caption: t({ en: 'The three instruments.', es: 'Los tres instrumentos.',
ar: 'الأدوات الثلاث.' }),
table: { model: { headerRowCount: 1, columnWidths: [1.15, 1.1, 1.2, 1.1], rows: t({
en: [head2('Made', 'Where', 'Maker', 'Size'),
row('1291', 'Yemen', 'ʿUmar ibn Yūsuf', '15.6 cm wide'),
row('1654–55', 'Iran, Mashhad?', 'Muhammad Zaman', '21.6 cm high'),
row('1462', 'Germany or Italy', 'Regiomontanus', '13.5 cm high')],
es: [head2('Fecha', 'Lugar', 'Autor', 'Tamaño'),
row('1291', 'Yemen', 'ʿUmar ibn Yūsuf', '15,6 cm de ancho'),
row('1654-1655', 'Irán, ¿Mashhad?', 'Muhammad Zaman', '21,6 cm de alto'),
row('1462', 'Alemania o Italia', 'Regiomontano', '13,5 cm de alto')],
ar: [head2('التاريخ', 'المكان', 'الصانع', 'المقاس'),
row('٦٩٠\u00a0هـ / ١٢٩١\u00a0م', 'اليمن', 'عمر بن يوسف', 'عرضه ١٥٫٦\u00a0سم'),
row('١٠٦٥\u00a0هـ / ١٦٥٤\u00a0م', 'إيران، مشهد؟', 'محمد زمان', 'ارتفاعه ٢١٫٦\u00a0سم'),
row('١٤٦٢\u00a0م', 'ألمانيا أو إيطاليا', 'ريجيومونتانوس', 'ارتفاعه ١٣٫٥\u00a0سم')] }) } } });
بيانات الجدول محتوى، فتتغيّر مع الطبعة: أرقام عربية مشرقية وفاصلة عشرية عربية في الطبعة العربية، وفاصلة عشرية في الإسبانية. أما نمطه فعام، والإعدادات العربية لا تحدد إلا الخط العربي وحجمه.
الوصفة كاملة
// ═══ Postext Cookbook · Nº 178 · One design for three language editions ═══════════
// https://postext.dev/en/cookbook/shared-design-per-language
// Code: MIT · Text: original (CC BY 4.0) · Photographs: The Met, Open Access (CC0)
// Fonts: Source Serif 4, Source Sans 3, Amiri, IBM Plex Sans Arabic (OFL) · Needs postext ≥ 1.65.0
import {
buildDocument, withLoadedFonts, renderPageToCanvas, registerResourceImage, mergeConfigLayers,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
const LANG = 'ar'; // @lang: the edition: 'en' | 'es' | 'ar'
const RECIPE = 'shared-design-per-language';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: night ink, the brass of the instruments, a parchment tint
const palette = {
ink: '#1f2230', // text: a blue near-black
brass: '#80561c', // the one accent: kickers, rules, the box stripe (6.2:1 on paper)
tint: '#f4eee2', // the box ground
rule: '#cfc5b2', // hairlines
muted: '#6a655c', // running heads, credits
paper: '#ffffff',
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
{ id: 'main-color', name: 'brass (defaults)', value: { hex: palette.brass, model: 'hex' } },
];
// #endregion
const [TEXT, LABEL] = ['Source Serif 4', 'Source Sans 3'];
const PAGE = { w: 148, h: 210 }; // mm: an A5 booklet
const LEAD = 14; // pt
// #region cover: the photograph to the trim, the title on its dark head
const at = (edge, x, y, width) => ({ anchor: { to: 'page', edge },
offset: { x: mm(x), y: mm(y) }, ...(width ? { size: { width: mm(width) } } : {}) });
const caps = { fontFamily: LABEL, fontWeight: 700, textTransform: 'uppercase',
letterSpacing: pt(1.6) };
// Every side is named by the direction of the text: 'top-start' is the left of an English
// page and the right of an Arabic one, so the cover needs no override to turn round.
const cover = { id: 'cover',
advancedDesign: { enabled: true, slot: { elements: [
{ kind: 'image', id: 'photo', resourceId: 'rasulid', decorative: true, fit: 'cover',
placement: { anchor: { to: 'page', edge: 'top-left' },
size: { width: mm(PAGE.w), height: mm(PAGE.h) } } },
{ kind: 'text', id: 'kicker', content: '{attr.kicker}', fontSize: pt(8.5), align: 'start',
color: col('paper'), ...caps, placement: at('top-start', 17, 18) },
{ kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1.5), color: col('tint'),
placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { x: mm(0), y: mm(3) },
يعمل ملف script.js المجمّع كما هو: الصقه في سكربت الوحدة (module) لأي صفحة، أو افتح الوصفة على CodePen. مجلد الوصفة على GitHub ↗ (يفتح في تبويب جديد)
تنويعات
#احفظ إعدادات اللغات في حزمة
وصفة حفظ ملف .postext وفتحه تكتب كتابًا بلغتين في ملف واحد. ومنذ postext 1.58 يحفظ createBundle الإعدادات العامة مرة واحدة، ويحفظ بجانب فصول كل لغة إعداداتها الخاصة، كما يقرؤها mergeConfigLayers هنا. وكتاب هذه الوصفة في Sandbox يُفتح على هذا النحو: بدّل لوحة التصميم بين «كل اللغات» والطبعة العربية لترى الحقول التي تحددها الطبعة العربية.
#اطبع الطبعة العربية للمغرب العربي
يُبقي ar-MA الاتجاه والتجليد، ويكتب الأرقام المولَّدة، أي أرقام الصفحات والأشكال، بالأرقام الأوروبية. وأعد كتابة أرقام الجدول أيضًا.
- locale: t({ en: 'en-gb', es: 'es', ar: 'ar' }), // ar: right to left, bound on the right
+ locale: t({ en: 'en-gb', es: 'es', ar: 'ar-MA' }), // ar-MA: European digitsأخطاء شائعة
خطأ شائع
الخطوط العربية تحتاج إلى ملفها arabic، عبر الكتلة arabic
يقدّم Fontsource الخطوط Amiri وNoto Naskh Arabic وScheherazade New في ملف لكل مجموعة فرعية، والحروف العربية في ملف arabic. لا يجلب loadFonts إلا latin (وlatin-ext)، فيأتي النص العربي على الشاشة من خط النظام ويُقاس خطأً، ويسلّم fontsourceProvider ملف latin ذاك إلى PDF، فيطبع مربعات فارغة. اذكر كتلة الأدوات arabic، واستدعِ loadArabicFonts(FONTS, markdown) بعد loadFonts، مع ذكر كل وزن تضع به الصفحاتُ نصًا عربيًا في FONTS، وأعطِ renderToPdf القيمة fontProvider: arabicPdfProvider. الخطوط العربية →
- الحروف العربية متصلة، فالتسمية التي أُعطيت تباعدًا في الإعدادات العامة يجب أن تفقد
letterSpacingفي الإعدادات العربية، وإلا أصدر المحرك التحذيرjoiningScriptLetterSpacing. ومعهtextTransform: 'none': ليس في العربية حروف كبيرة. - أسطر الطبعة العربية أعلى، فتنكسر صفحاتها في مواضع أخرى. وقد ضُبطت كل طبعة على حدة: يطفو الصندوق العربي إلى أسفل صفحته (
placement="bottom")، وتجري جمل الخاتمة بجانب الصورة الأخيرة.
الحقوق
- الوصفة
- Ignacio Ferro
- النص
- نص أصلي, CC BY 4.0
- الصور
- Astrolabe of ʿUmar ibn Yusuf, Yemen, 1291 (the cover; background extended at the top and darkened) · The Metropolitan Museum of Art · CC0 1.0
- Muhammad Zaman, planispheric astrolabe, Iran, 1654–55 (cropped) · The Metropolitan Museum of Art · CC0 1.0
- Astrolabe attributed to Regiomontanus, 1462 (cropped) · The Metropolitan Museum of Art · CC0 1.0
- الخطوط
- Source Serif 4 (SIL OFL 1.1) · Source Sans 3 (SIL OFL 1.1) · Amiri (SIL OFL 1.1) · IBM Plex Sans Arabic (SIL OFL 1.1)
- الشيفرة
- MIT، مثل Postext
حرّر هذا الشرح ↗ (يفتح في تبويب جديد)مجلد الوصفة على GitHub ↗ (يفتح في تبويب جديد)


