かんたんな説明
古いアストロラーベ3点を紹介する小冊子を、英語、スペイン語、アラビア語で印刷します。3版は同じデザインで、アラビア語版だけが書体とサイズを変えます。
できあがり
メトロポリタン美術館の3点のアストロラーベ、1291年イエメン製、1654年イラン製、1462年ヨーロッパ製を紹介するA5判4ページの小冊子です。版元はこれを英語、スペイン語、アラビア語で出し、3版はひとつのデザインです。表紙写真、真鍮色の見出し、帯つきの囲み、本文が回り込む図版、暗い表の見出し行は、同じ設定から生まれます。アラビア語版はその文字に属するものだけを変えます。Source SerifとSource Sansの代わりにAmiriとIBM Plex Sans Arabic、大きめのサイズ、大文字も字間もないラベルです。右から読み、右綴じになりますが、独自の座標はひとつもありません。
アラビア語版とスペイン語版はそれぞれの言語のページで見られます。漫画1ページを6言語で描き文字し直すレシピと見比べてください。
このレシピが答える質問
- 多言語の版で1つのデザインを共有し、言語ごとの書体・ハイフネーション・引用符・ラベルだけを変えるには?
- 1つのプロジェクトから、同じ本を2つの言語で出版するには?
手短な答え
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の書き出し
- 種類
- 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)
作り方
#1 · 共通設定と3つの小さな上書き
コードは上の手短な答えにあります。configが共通設定で、mergeConfigLayersがその上に版ごとの上書きをフィールド単位で重ねます。英語とスペイン語には上書きがありません。localeがハイフネーションのパターンと、FigureとTable、FiguraとTablaという語を選びます。アラビア語の上書きは個々のフィールドだけを書きます。bodyText.fontFamilyは書体を変え、色、揃え、ハイフネーションの設定はそのまま。表紙のkicker要素は書体と字間を変え、アンカー、色、大きさはそのままです。configでパレットや余白を変えれば、3版すべてに届きます。
#2 · 左右を文字の方向で呼ぶ
// 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に固定しているので、どれも上書きが要りません。
#3 · 図版を行末に置き、本文を回り込ませる
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'は写真を英語の段では右、アラビア語の段では左に置き、本文がその横を流れます。キャプションと代替テキストは各版の言語で書き、アラビア語のキャプションはヒジュラ暦の年を先に示します。
#4 · 表は各版の言葉と数字で
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 = 'en'; // @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はそのまま動きます。任意のページのモジュールスクリプトに貼り付けるか、レシピをCodePenで開いてください。 GitHub上のレシピのフォルダー ↗ (新しいタブで開きます)
アレンジ
#上書きをバンドルに書き込む
.postextの往復は2言語の本を1つのファイルに書き出します。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をサブセットごとに1ファイルとして配信し、アラビア文字はarabicファイルに入っています。loadFontsが取得するのはlatin(とlatin-ext)だけなので、画面ではアラビア文字がシステムの書体で出て計測が狂い、fontsourceProviderはPDFにそのlatinファイルを渡すため、空の四角が印字されます。キットのarabicブロックを挙げ、loadFontsのあとにloadArabicFonts(FONTS, markdown)を呼び(ページでアラビア文字に使うすべてのウェイトをFONTSに挙げておきます)、renderToPdfにfontProvider: arabicPdfProviderを渡してください。 アラビア語のフォント →
- アラビア文字はつながって書かれるので、共通設定で字間を広げたラベルは、アラビア語の上書きで
letterSpacingを外さなければなりません。外さないとエンジンがjoiningScriptLetterSpacingの警告を出します。あわせてtextTransform: 'none'も指定します。アラビア語に大文字はありません。 - アラビア語版は行が高く、改ページの位置も変わります。どの版も個別に調整しました。アラビア語の囲みはページ下端に送り(
placement="bottom")、締めの文は最後の写真の横に組んでいます。
クレジット
- 本文
- 書き下ろしの文章, 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)


