En poques paraules
Un quadernet sobre tres astrolabis antics, imprès en anglès, castellà i àrab. Les tres edicions comparteixen el disseny; només l'àrab canvia les fonts i els cossos.
Què compondràs
Un quadernet de quatre pàgines A5 sobre tres astrolabis del Metropolitan Museum of Art: un fet al Iemen el 1291, un altre a l'Iran el 1654 i un altre a Europa el 1462. L'editorial el publica en anglès, castellà i àrab, i les tres edicions són un sol disseny. La foto de coberta, els títols en llautó, el requadre amb el filet, les figures amb el text al costat i la capçalera fosca de la taula surten d'uns mateixos ajustos. L'edició àrab canvia el que és de la seva escriptura: Amiri i IBM Plex Sans Arabic en lloc de Source Serif i Source Sans, un cos més gran i etiquetes sense majúscules ni espaiat. Es llegeix des de la dreta i s'enquaderna per la dreta sense cap coordenada pròpia.
Aquesta recepta respon a
- Com comparteixo un disseny entre les edicions d'un llibre en diverses llengües i canvio només les fonts, la partició de mots, les cometes i les etiquetes de cada llengua?
- Com publico el mateix llibre en dues llengües des d'un sol projecte?
La resposta curta
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
Ingredients
- Funcions
- Ajustos propis de cada llenguaText de dreta a esquerraFonts àrabsLlibres enquadernats per la dretaXifres dels números generatsPartició de mots i llengua del documentFigura i Taula en la teva llenguaCobertes, portades i colofonsEstils de títolAtributs de títolImatges als dissenys de pàginaCapçaleres i folisText al voltant d'imatges i caixesRequadresEstil de taulesEstil dels peusPaleta de color semànticaExportació a PDF
- També fa servir
- Figures just aquíBanda de capítol d'amplada completaObertures dissenyadesCapçaleres segons el tipus de pàginaEstils de paràgrafFonts incrustades al PDFFigures i taules com a recursosSalts de línia als títols
- Tipografia
- Source Serif 4, Source Sans 3, Amiri, IBM Plex Sans Arabic (SIL OFL 1.1)
- Recursos
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)
Elaboració
#1 · Uns ajustos generals i tres ajustos de llengua petits
El codi és a la resposta curta. config desa els ajustos generals; mergeConfigLayers hi posa a sobre els d'una edició camp per camp. L'anglès i el castellà no tenen ajustos propis: el seu locale tria els patrons de partició de mots i les paraules Figure i Table, Figura i Tabla. Els ajustos àrabs només anomenen camps solts: bodyText.fontFamily canvia la font i conserva el color, l'alineació i la partició, i l'element kicker de la coberta canvia la font i l'espaiat i conserva l'àncora, el color i la mida. Un canvi a la paleta o als marges de config arriba a les tres edicions.
#2 · Costats anomenats segons la direcció del text
// 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),
] };
La vora exterior d'una pàgina parella és el costat on comencen les línies en totes dues direccions, i la d'una senar, el costat on acaben; per això top-start i top-end porten els folis a fora tant en una pàgina parella anglesa com en una d'àrab. El filet del requadre és side: 'start' i el text de la coberta s'ancora a top-start: cap no necessita un ajust propi.
#3 · Figures al final de la línia, amb el text al costat
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' posa cada fotografia a la dreta d'una columna anglesa i a l'esquerra d'una d'àrab, i el text corre al costat. Els peus i els textos alternatius van en la llengua de cada edició; els peus àrabs donen primer la data de l'hègira.
#4 · La taula amb les paraules i les xifres de cada edició
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سم')] }) } } });
Les dades de la taula són contingut i canvien amb l'edició: xifres aràbigues orientals i el separador decimal àrab a l'edició àrab, coma decimal a la castellana. L'estil és general: l'ajust àrab només posa la font i el cos.
La recepta completa
// ═══ 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 = 'es'; // @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) },
L'script.js compost funciona tal com és: enganxa'l com a script de mòdul en qualsevol pàgina o obre la recepta a CodePen. Carpeta de la recepta a GitHub ↗ (s'obre en una pestanya nova)
Variants
#Desa els ajustos de cada llengua en un paquet
L'anada i tornada del .postext desa un llibre en dues llengües en un fitxer. Des de postext 1.58, createBundle escriu una vegada els ajustos generals i, al costat dels capítols de cada llengua, els seus ajustos propis, tal com els llegeix aquí mergeConfigLayers. El llibre d'aquesta recepta al Sandbox s'obre així: canvia el tauler de disseny entre Totes les llengües i l'edició àrab per veure quins camps fixa l'edició àrab.
#Imprimeix l'edició àrab per al Magrib
ar-MA conserva la direcció i l'enquadernació i escriu en xifres europees els nombres generats: els folis i els números de les figures. Torna a escriure també les xifres de la taula.
- 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 digitsErrors freqüents
Error freqüent
Les fonts àrabs necessiten el seu fitxer arabic, amb el bloc arabic
Fontsource serveix Amiri, Noto Naskh Arabic o Scheherazade New en un fitxer per subconjunt, i les lletres àrabs són al fitxer arabic. loadFonts només baixa latin (i latin-ext), de manera que a la pantalla l'àrab surt d'una font del sistema i es mesura malament, i fontsourceProvider lliura al PDF aquell fitxer latin, que imprimeix caixes buides. Afegeix el bloc arabic del kit, crida loadArabicFonts(FONTS, markdown) després de loadFonts, amb tots els pesos que les pàgines fan servir en àrab llistats a FONTS, i passa a renderToPdf fontProvider: arabicPdfProvider. Fonts àrabs →
- Les lletres àrabs van enllaçades, de manera que una etiqueta espaiada als ajustos generals ha de perdre el
letterSpacingals ajustos àrabs; si no, el motor dona un avísjoiningScriptLetterSpacing. Amb ell vatextTransform: 'none': l'àrab no té majúscules. - Les línies d'una edició àrab són més altes i les seves pàgines tallen en altres llocs. Cada edició s'ha ajustat pel seu compte: el requadre àrab flota al peu de la pàgina (
placement="bottom") i les frases de tancament corren al costat de l'última fotografia.
Crèdits
- Recepta
- Ignacio Ferro
- Text
- Text original, CC BY 4.0
- Imatges
- 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
- Fonts
- 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)
- Codi
- MIT, com Postext
Editar aquest text ↗ (s'obre en una pestanya nova)Carpeta de la recepta a GitHub ↗ (s'obre en una pestanya nova)


