Poster Design names sizes, and plugins' designs, in the reader's language

Styles and Sizes now name every size the one way (src/sizeText.js, shared):
templates.js carries each size as data (inches, side lanes, panels, paper),
and posterDesign.sizes gains sideLanes, packetPage1, panels and anySize in
all 11 locales, so Arabic readers no longer see "5 x 8 ft, side lanes" or
"Letter, portrait". A plugin's declared size (a ladder key, letter, a4) is
named through the shared ladder.

A plugin's design title came from its labelKey, but Poster Design's bundle
ships only the catalogue areas its own code names, so a plugin's own area
(upgMap, ...) read English. internal/profile-meta.mjs now resolves each
block's labelKey in every locale at build time and carries the titles in
manifest.json (posterDesignLabels, only beside a posterDesign block), and
Styles reads the reader's language from there.
This commit is contained in:
Doxa Maps 2026-10-02 13:32:19 +00:00
parent 6d89e6b8ce
commit 58c6bb28af
18 changed files with 165 additions and 42 deletions

View file

@ -12,9 +12,42 @@
* posterDesign { family, name, size, mobileFriendly, labelKey?, instance? } or an array of them: the poster
* design(s) this profile shows, listed by Poster Design's Styles. Carried as written, ONLY when present
* (every other manifest keeps its bytes); check:poster-design-catalogue validates it.
* posterDesignLabels GENERATED beside it: { <design name>: { <locale>: title } } for every block with a labelKey,
* resolved here from library/i18n/locales/<locale>/<area>.json. Poster Design's bundle ships only the
* catalogue areas its own code names, so a plugin's own area (upgMap, …) would be missing there and the card
* would read English; carrying the titles in the manifest works for any area, with no Poster Design rebuild.
*/
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { readFileSync, readdirSync, existsSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const LOCALES = join(dirname(fileURLToPath(import.meta.url)), '..', 'library', 'i18n', 'locales')
// A catalogue key ("area.a.b") in every locale that has it: { en: '…', ar: '…' }; {} when no locale does.
function catalogueTitles(key) {
const [area, ...path] = String(key || '').split('.')
const out = {}
if (!area || !path.length || !existsSync(LOCALES)) return out
for (const loc of readdirSync(LOCALES)) {
const f = join(LOCALES, loc, `${area}.json`)
if (!existsSync(f)) continue
try {
let o = JSON.parse(readFileSync(f, 'utf8'))
for (const p of path) o = o && typeof o === 'object' ? o[p] : undefined
if (typeof o === 'string' && o.trim()) out[loc] = o
} catch { /* a locale file that does not parse names nothing */ }
}
return out
}
function posterDesignLabels(pd) {
const out = {}
for (const b of Array.isArray(pd) ? pd : [pd]) {
if (b && typeof b.name === 'string' && typeof b.labelKey === 'string') {
const titles = catalogueTitles(b.labelKey)
if (Object.keys(titles).length) out[b.name] = titles
}
}
return out
}
const CORNERS = ['bottom-right', 'bottom-left', 'top-right', 'top-left']
@ -82,7 +115,7 @@ export function profileMetaFields(dir, profileName) {
const m = readProfileMeta(dir, profileName)
// visibility is emitted ONLY when it is "public": absent means team, so every manifest in the
// tree today keeps the bytes it has and nothing becomes visible by upgrading the generator.
return { stage: m.stage, ...(m.visibility === 'public' ? { visibility: 'public' } : {}), ...(m.label ? { label: m.label } : {}), ...(m.targets ? { targets: m.targets } : {}), ...(m.heavy ? { heavy: true } : {}), ...(m.widget ? { widget: m.widget } : {}), ...(m.mobile ? { mobile: m.mobile } : {}), ...(m.posterDesign ? { posterDesign: m.posterDesign } : {}) }
return { stage: m.stage, ...(m.visibility === 'public' ? { visibility: 'public' } : {}), ...(m.label ? { label: m.label } : {}), ...(m.targets ? { targets: m.targets } : {}), ...(m.heavy ? { heavy: true } : {}), ...(m.widget ? { widget: m.widget } : {}), ...(m.mobile ? { mobile: m.mobile } : {}), ...(m.posterDesign ? { posterDesign: m.posterDesign } : {}), ...(m.posterDesign && Object.keys(posterDesignLabels(m.posterDesign)).length ? { posterDesignLabels: posterDesignLabels(m.posterDesign) } : {}) }
}
/** A bundle is production if ANY of its profiles says so. */

View file

@ -28,7 +28,11 @@
"no": "لا",
"printHome": "أي طابعة منزلية",
"printShop": "مطبعة (ملف PDF، صفحة لكل ورقة)",
"layoutNote": "تعرض الأوراق تخطيط ملصق الدولة بمقاس 3 × 3 أقدام في كل مقاس: الترويسة والخريطة والأسماء والجدول وتذييل الآية. سيظهر الملصق المرسوم بكل مقاس في الأنماط عند توفره."
"layoutNote": "تعرض الأوراق تخطيط ملصق الدولة بمقاس 3 × 3 أقدام في كل مقاس: الترويسة والخريطة والأسماء والجدول وتذييل الآية. سيظهر الملصق المرسوم بكل مقاس في الأنماط عند توفره.",
"sideLanes": "{dims}، أعمدة جانبية",
"packetPage1": "{paper} (الصفحة 1 من حزمة الصلاة)",
"panels": "{dims}، عدد الألواح: {n}",
"anySize": "أي حجم من جدول الأحجام المشترك"
},
"family": {
"mapSheet": "صفحات الخرائط",

View file

@ -28,7 +28,11 @@
"no": "Nein",
"printHome": "Jeder Drucker zu Hause",
"printShop": "Eine Druckerei (ein PDF, eine Seite pro Bogen)",
"layoutNote": "Die Bögen zeigen das Layout des 3 × 3 ft Länderposters in jeder Größe: Kopfzeile, Karte, Namen, Tabelle und die Fußzeile mit dem Bibelvers. Das in jeder Größe gezeichnete Poster erscheint unter Stile, sobald es verfügbar ist."
"layoutNote": "Die Bögen zeigen das Layout des 3 × 3 ft Länderposters in jeder Größe: Kopfzeile, Karte, Namen, Tabelle und die Fußzeile mit dem Bibelvers. Das in jeder Größe gezeichnete Poster erscheint unter Stile, sobald es verfügbar ist.",
"sideLanes": "{dims}, seitliche Namensspalten",
"packetPage1": "{paper} (Seite 1 der Gebetsmappe)",
"panels": "{dims}, Tafeln: {n}",
"anySize": "Jede Größe aus der gemeinsamen Größentabelle"
},
"family": {
"mapSheet": "Kartenblätter",

View file

@ -28,7 +28,11 @@
"no": "No",
"printHome": "Any home printer",
"printShop": "A print shop (a PDF, one page per sheet)",
"layoutNote": "The sheets show the layout of the 3 × 3 ft country poster at each size: the header, the map, the names, the table and the scripture footer. The poster drawn at each size appears in Styles as it becomes available."
"layoutNote": "The sheets show the layout of the 3 × 3 ft country poster at each size: the header, the map, the names, the table and the scripture footer. The poster drawn at each size appears in Styles as it becomes available.",
"sideLanes": "{dims}, side lanes",
"packetPage1": "{paper} (packet page 1)",
"panels": "{dims}, {n} panels",
"anySize": "Any size from the shared size table"
},
"family": {
"mapSheet": "Map sheets",

View file

@ -28,7 +28,11 @@
"no": "No",
"printHome": "Cualquier impresora doméstica",
"printShop": "Una imprenta (un PDF, una página por hoja)",
"layoutNote": "Las hojas muestran el diseño del póster de país de 3 × 3 ft en cada tamaño: el encabezado, el mapa, los nombres, la tabla y el pie con la Escritura. El póster dibujado en cada tamaño aparecerá en Estilos a medida que esté disponible."
"layoutNote": "Las hojas muestran el diseño del póster de país de 3 × 3 ft en cada tamaño: el encabezado, el mapa, los nombres, la tabla y el pie con la Escritura. El póster dibujado en cada tamaño aparecerá en Estilos a medida que esté disponible.",
"sideLanes": "{dims}, columnas laterales",
"packetPage1": "{paper} (página 1 del paquete de oración)",
"panels": "{dims}, paneles: {n}",
"anySize": "Cualquier tamaño de la tabla de tamaños compartida"
},
"family": {
"mapSheet": "Hojas de mapa",

View file

@ -28,7 +28,11 @@
"no": "Non",
"printHome": "N'importe quelle imprimante",
"printShop": "Un imprimeur (un PDF, une page par feuille)",
"layoutNote": "Les feuilles montrent la mise en page de l'affiche pays de 3 × 3 ft à chaque format : l'en-tête, la carte, les noms, le tableau et le pied avec l'Écriture. L'affiche dessinée à chaque format apparaîtra dans Styles dès qu'elle sera disponible."
"layoutNote": "Les feuilles montrent la mise en page de l'affiche pays de 3 × 3 ft à chaque format : l'en-tête, la carte, les noms, le tableau et le pied avec l'Écriture. L'affiche dessinée à chaque format apparaîtra dans Styles dès qu'elle sera disponible.",
"sideLanes": "{dims}, colonnes latérales",
"packetPage1": "{paper} (page 1 du dossier de prière)",
"panels": "{dims}, panneaux : {n}",
"anySize": "Toute taille du tableau de tailles commun"
},
"family": {
"mapSheet": "Feuilles de carte",

View file

@ -28,7 +28,11 @@
"no": "नहीं",
"printHome": "कोई भी घरेलू प्रिंटर",
"printShop": "प्रिंटिंग प्रेस (PDF, हर शीट के लिए एक पेज)",
"layoutNote": "शीटें हर आकार में 3 × 3 फ़ुट देश पोस्टर का लेआउट दिखाती हैं: शीर्षक, मानचित्र, नाम, तालिका और पवित्रशास्त्र वाला फ़ुटर। हर आकार में बना असली पोस्टर उपलब्ध होते ही शैलियाँ में दिखेगा।"
"layoutNote": "शीटें हर आकार में 3 × 3 फ़ुट देश पोस्टर का लेआउट दिखाती हैं: शीर्षक, मानचित्र, नाम, तालिका और पवित्रशास्त्र वाला फ़ुटर। हर आकार में बना असली पोस्टर उपलब्ध होते ही शैलियाँ में दिखेगा।",
"sideLanes": "{dims}, किनारे के कॉलम",
"packetPage1": "{paper} (प्रार्थना पैकेट का पेज 1)",
"panels": "{dims}, पैनल: {n}",
"anySize": "साझा आकार तालिका का कोई भी आकार"
},
"family": {
"mapSheet": "मानचित्र पृष्ठ",

View file

@ -28,7 +28,11 @@
"no": "No",
"printHome": "Qualsiasi stampante di casa",
"printShop": "Una tipografia (un PDF, una pagina per foglio)",
"layoutNote": "I fogli mostrano l'impaginazione del poster di paese da 3 × 3 ft in ogni formato: intestazione, mappa, nomi, tabella e il piè di pagina con la Scrittura. Il poster disegnato in ogni formato comparirà in Stili appena disponibile."
"layoutNote": "I fogli mostrano l'impaginazione del poster di paese da 3 × 3 ft in ogni formato: intestazione, mappa, nomi, tabella e il piè di pagina con la Scrittura. Il poster disegnato in ogni formato comparirà in Stili appena disponibile.",
"sideLanes": "{dims}, colonne laterali",
"packetPage1": "{paper} (pagina 1 del fascicolo di preghiera)",
"panels": "{dims}, pannelli: {n}",
"anySize": "Qualsiasi formato dalla tabella dei formati comune"
},
"family": {
"mapSheet": "Fogli mappa",

View file

@ -28,7 +28,11 @@
"no": "Não",
"printHome": "Qualquer impressora doméstica",
"printShop": "Uma gráfica (um PDF, uma página por folha)",
"layoutNote": "As folhas mostram o layout do pôster de país de 3 × 3 ft em cada tamanho: o cabeçalho, o mapa, os nomes, a tabela e o rodapé com a Escritura. O pôster desenhado em cada tamanho aparecerá em Estilos quando estiver disponível."
"layoutNote": "As folhas mostram o layout do pôster de país de 3 × 3 ft em cada tamanho: o cabeçalho, o mapa, os nomes, a tabela e o rodapé com a Escritura. O pôster desenhado em cada tamanho aparecerá em Estilos quando estiver disponível.",
"sideLanes": "{dims}, colunas laterais",
"packetPage1": "{paper} (página 1 do pacote de oração)",
"panels": "{dims}, painéis: {n}",
"anySize": "Qualquer tamanho da tabela de tamanhos compartilhada"
},
"family": {
"mapSheet": "Folhas de mapa",

View file

@ -28,7 +28,11 @@
"no": "Nu",
"printHome": "Orice imprimantă de acasă",
"printShop": "O tipografie (un PDF, o pagină pe coală)",
"layoutNote": "Colile arată machetarea afișului de țară de 3 × 3 ft la fiecare dimensiune: antetul, harta, numele, tabelul și subsolul cu versetul din Scriptură. Afișul desenat la fiecare dimensiune va apărea în Stiluri când va fi disponibil."
"layoutNote": "Colile arată machetarea afișului de țară de 3 × 3 ft la fiecare dimensiune: antetul, harta, numele, tabelul și subsolul cu versetul din Scriptură. Afișul desenat la fiecare dimensiune va apărea în Stiluri când va fi disponibil.",
"sideLanes": "{dims}, coloane laterale",
"packetPage1": "{paper} (pagina 1 a pachetului de rugăciune)",
"panels": "{dims}, panouri: {n}",
"anySize": "Orice dimensiune din tabelul comun de dimensiuni"
},
"family": {
"mapSheet": "Foi de hartă",

View file

@ -28,7 +28,11 @@
"no": "Нет",
"printHome": "Любой домашний принтер",
"printShop": "Типография (PDF, одна страница на лист)",
"layoutNote": "На листах показан макет плаката страны 3 × 3 фута в каждом размере: заголовок, карта, названия, таблица и нижняя полоса со стихом из Писания. Плакат, нарисованный в каждом размере, появится в разделе «Стили», когда будет готов."
"layoutNote": "На листах показан макет плаката страны 3 × 3 фута в каждом размере: заголовок, карта, названия, таблица и нижняя полоса со стихом из Писания. Плакат, нарисованный в каждом размере, появится в разделе «Стили», когда будет готов.",
"sideLanes": "{dims}, боковые колонки",
"packetPage1": "{paper} (страница 1 молитвенного пакета)",
"panels": "{dims}, панелей: {n}",
"anySize": "Любой размер из общей таблицы размеров"
},
"family": {
"mapSheet": "Листы карт",

View file

@ -28,7 +28,11 @@
"no": "否",
"printHome": "任何家用打印机",
"printShop": "印刷店(PDF,每张纸一页)",
"layoutNote": "纸张显示的是 3 × 3 英尺国家海报在各尺寸下的版式:标题、地图、名称、表格和经文页脚。各尺寸实际绘制的海报完成后会出现在“样式”中。"
"layoutNote": "纸张显示的是 3 × 3 英尺国家海报在各尺寸下的版式:标题、地图、名称、表格和经文页脚。各尺寸实际绘制的海报完成后会出现在“样式”中。",
"sideLanes": "{dims},两侧名单栏",
"packetPage1": "{paper}(祷告资料包第 1 页)",
"panels": "{dims},展板数:{n}",
"anySize": "共享尺寸表中的任意尺寸"
},
"family": {
"mapSheet": "地图页",

View file

@ -20,19 +20,14 @@ import { POSTER_LADDER, POSTER_LADDER_ORDER, paperFor } from '@map/constants/pos
import { useOptionalT, useOptionalI18n } from '@map/composables/useOptionalT.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { layoutSvgUrl } from './src/layoutSvg.js'
import { num, dimsText } from './src/sizeText.js'
const t = useOptionalT('')
const { locale } = useOptionalI18n('')
// A number as the sizes are spoken: whole, or one decimal place (5.5).
const n = (x) => (Number.isInteger(x) ? String(x) : String(Math.round(x * 10) / 10))
// Feet once a side reaches a yard and both sides are whole or half feet (3 × 5.5 ft); inches below that (18 × 24 in).
function dims(w, h) {
const ft = (x) => x / 12
const halfFeet = (x) => Number.isInteger(ft(x) * 2)
if (Math.min(w, h) >= 36 && halfFeet(w) && halfFeet(h)) return t('posterDesign.sizes.dimsFt', { w: n(ft(w)), h: n(ft(h)) })
return t('posterDesign.sizes.dimsIn', { w: n(w), h: n(h) })
}
// Sizes are named the one way Poster Design names them (src/sizeText.js, shared with Styles).
const n = num
const dims = (w, h) => dimsText(w, h, t)
// Standard printer paper is Letter or A4 by the reader's region, as the print picker decides it.
const paperIsA4 = computed(() => {
const l = locale && (locale.value ?? locale)

View file

@ -32,6 +32,7 @@ import { resolvePosterSize } from '@map/constants/posterSizes.js'
import { listConfigurations, FAMILY_KEYS, SAMPLE_COUNTRY_ID } from './src/templates.js'
import FullDetailsDialog from './src/FullDetailsDialog.vue'
import { loadPluginDesigns } from './src/pluginDesigns.js'
import { sizeText, declaredSizeText } from './src/sizeText.js'
const profileConfig = inject('profileConfig', ref(null))
const instanceId = computed(() => profileConfig.value?.instanceId || 'poster-design-styles')
@ -49,7 +50,7 @@ function stockLabel(s) {
const dims = `${s.w} × ${s.h} in`
return s.side ? `${noun} ${dims} (${t('wall.stockLandscape')})` : `${noun} ${dims}`
}
const labelOf = (c) => (c.stock ? `${t(c.labelKey)}: ${stockLabel(c.stock)}` : `${t(c.labelKey)}: ${c.size}`)
const labelOf = (c) => (c.stock ? `${t(c.labelKey)}: ${stockLabel(c.stock)}` : `${t(c.labelKey)}: ${sizeText(c.size, t)}`)
// Designs other plugins declare (their meta's posterDesign, via manifest.json): the built tree's manifest, or the dev
// server's. A name already in templates.js stays the library's (the check refuses the clash; the page never doubles it).
@ -60,9 +61,17 @@ onMounted(async () => {
const { designs } = await loadPluginDesigns(urls)
pluginDesigns.value = designs.filter((d) => !byName.has(d.name))
})
const pluginLabel = (d) => {
// The design's title in the reader's language: the build's per-locale titles (posterDesignLabels; any catalogue
// area), then this bundle's own catalogue, then English, then the profile's label.
const pluginTitle = (d) => {
const lang = String(getRuntimeLocale() || detectLocale() || 'en').split('-')[0]
if (d.titles && d.titles[lang]) return d.titles[lang]
const v = d.labelKey ? t(d.labelKey) : ''
return `${v && v !== d.labelKey ? v : d.label}: ${d.size}`
if (v && v !== d.labelKey) return v
return (d.titles && d.titles.en) || d.label
}
const pluginLabel = (d) => {
return `${pluginTitle(d)}: ${declaredSizeText(d.size, t)}`
}
// No `count`: these are templates, not lists of people, so the sticker reads "Show full details" alone.
@ -175,15 +184,15 @@ async function sheet(item) {
// ── "Show full details" ───────────────────────────────────────────────────────────────────────
const open = ref(null)
const openLabel = computed(() => (!open.value ? '' : open.value.design ? pluginLabel(open.value.design) : labelOf(open.value)))
const openLabel = computed(() => (!open.value ? '' : open.value.design ? pluginLabel(open.value.design) : labelOf(byName.get(open.value.name))))
// A plugin's design opens in the same dialog, as a configuration: its renderer is the plugin's page, and the dialog
// shows that page live.
function showDetails(item) {
const d = byPluginName.value.get(item.id)
open.value = d
? { name: d.name, size: d.size, settings: { family: d.family, mobileFriendly: d.mobileFriendly, ...(d.instance ? { preset: d.instance } : {}) },
? { name: d.name, size: declaredSizeText(d.size, t), settings: { family: d.family, mobileFriendly: d.mobileFriendly, ...(d.instance ? { preset: d.instance } : {}) },
renderer: `plugin-designs/${d.bundle}/${d.profile}.vue`, pageUrl: d.pageUrl, design: d }
: byName.get(item.id) || null
: byName.has(item.id) ? { ...byName.get(item.id), size: sizeText(byName.get(item.id).size, t) } : null
}
const heading = computed(() => t('posterDesign.styles.heading'))

View file

@ -63,7 +63,7 @@ async function copyName() {
<dd><code dir="ltr" class="pd-dialog__name">{{ config.name }}</code>
<button type="button" class="pd-dialog__copy" @click="copyName">{{ t('posterDesign.details.copyName') }}</button></dd>
<dt>{{ t('posterDesign.details.size') }}</dt>
<dd dir="ltr">{{ config.size }}<span v-if="config.px"> · {{ config.px[0] }} × {{ config.px[1] }} px</span></dd>
<dd>{{ config.size }}<span v-if="config.px"> · {{ ltr(`${config.px[0]} × ${config.px[1]} px`) }}</span></dd>
<template v-if="!config.pageUrl">
<dt>{{ t('posterDesign.details.fileSize') }}</dt>
<dd>{{ fileSize }}</dd>

View file

@ -55,7 +55,9 @@ export function designsFromManifest(manifest, manifestUrl) {
const rel = inst ? inst.iframeUrl : p.iframeUrl
let pageUrl = rel
try { pageUrl = new URL(rel, manifestUrl).href } catch { /* keep it as written */ }
// titles: the labelKey resolved per locale by the build (posterDesignLabels), so any area reads translated
out.push({ name: d.name, family: d.family, size: d.size, mobileFriendly: d.mobileFriendly, labelKey: d.labelKey || '',
titles: (p.posterDesignLabels && p.posterDesignLabels[d.name]) || {},
label: (inst && inst.title) || p.label || p.profile, bundle: b.bundle, profile: p.profile, instance: d.instance || '', pageUrl })
}
}

View file

@ -0,0 +1,39 @@
/**
* sizeText.js — a poster's size in the reader's language, the ONE way Poster Design names sizes (Styles and Sizes).
* Numbers are spoken whole or to one decimal (5.5); a side of a yard or more in whole or half feet reads in feet
* (3 × 5.5 ft), anything smaller in inches (18 × 24 in). Every word comes from the posterDesign.sizes catalogue.
*
* spec = { dims: [w, h] } inches · { dims, lanes: true } · { dims, panels: n } · { paper: 'letter', packetPage? } ·
* { any: true } (a template that prints at whatever size its reader picks)
*/
import { resolvePosterSize } from '@map/constants/posterSizes.js'
export const num = (x) => (Number.isInteger(x) ? String(x) : String(Math.round(x * 10) / 10))
export function dimsText(w, h, t) {
const ft = (x) => x / 12
const halfFeet = (x) => Number.isInteger(ft(x) * 2)
if (Math.min(w, h) >= 36 && halfFeet(w) && halfFeet(h)) return t('posterDesign.sizes.dimsFt', { w: num(ft(w)), h: num(ft(h)) })
return t('posterDesign.sizes.dimsIn', { w: num(w), h: num(h) })
}
export function sizeText(spec, t) {
if (!spec) return ''
if (spec.any) return t('posterDesign.sizes.anySize')
if (spec.paper) {
const paper = t('posterDesign.sizes.paperLetter')
return spec.packetPage ? t('posterDesign.sizes.packetPage1', { paper }) : paper
}
const dims = dimsText(spec.dims[0], spec.dims[1], t)
if (spec.lanes) return t('posterDesign.sizes.sideLanes', { dims })
if (spec.panels) return t('posterDesign.sizes.panels', { dims, n: num(spec.panels) })
return dims
}
/** A plugin's declared size: a ladder key ("66x66") through the shared ladder, letter / a4 by name; anything else as written. */
export function declaredSizeText(size, t) {
if (size === 'letter') return t('posterDesign.sizes.paperLetter')
if (size === 'a4') return t('posterDesign.sizes.paperA4')
const s = resolvePosterSize(size)
return s && s.inches && !s.paper && s.id !== 'paper' ? dimsText(s.inches.w, s.inches.h, t) : String(size || '')
}

View file

@ -32,13 +32,14 @@ export const SAMPLE_COUNTRY_ID = 'country-npl'
// country prints at 66x66 or 36x60 today: those two are drawn with the largest drawable countries (China, Sudan) to
// show the template, and their details say so (missing: noCountryAtSize). Names never change (src/catalogue-names.json).
export const SHEET_FORMATS = {
'3x3': { page: '3 × 3', px: [1500, 1500] },
'60x96': { page: '5 × 8 ft, side lanes', px: [2500, 4000], sample: 'country-ind' },
'66x66': { page: '5.5 × 5.5 ft, side lanes', px: [2750, 2750], sample: 'country-chn', missing: 'posterDesign.missing.noCountryAtSize' },
'36x60': { page: '3 × 5 ft, side lanes', px: [1500, 2500], sample: 'country-sdn', missing: 'posterDesign.missing.noCountryAtSize' },
'3x4': { page: '3 × 4', px: [1500, 2000], legacy: true },
letter: { page: 'Letter, portrait', px: [1500, 1941] },
packet: { page: 'Letter, portrait (packet page 1)', px: [1500, 1941] },
// `size` is the printed size as data (src/sizeText.js names it in the reader's language): inches, side lanes, paper.
'3x3': { size: { dims: [36, 36] }, px: [1500, 1500] },
'60x96': { size: { dims: [60, 96], lanes: true }, px: [2500, 4000], sample: 'country-ind' },
'66x66': { size: { dims: [66, 66], lanes: true }, px: [2750, 2750], sample: 'country-chn', missing: 'posterDesign.missing.noCountryAtSize' },
'36x60': { size: { dims: [36, 60], lanes: true }, px: [1500, 2500], sample: 'country-sdn', missing: 'posterDesign.missing.noCountryAtSize' },
'3x4': { size: { dims: [36, 48] }, px: [1500, 2000], legacy: true },
letter: { size: { paper: 'letter' }, px: [1500, 1941] },
packet: { size: { paper: 'letter', packetPage: true }, px: [1500, 1941] },
}
// Families are the sections of the Styles tab, in reading order. The keys are written out, never
@ -57,7 +58,7 @@ function sheetEntry(format) {
name: format === 'packet' ? 'map-sheet.packet-page' : `map-sheet.country.${format}`,
family: 'mapSheet',
labelKey: format === 'packet' ? 'posterDesign.tpl.mapSheetPacketPage' : 'posterDesign.tpl.mapSheetCountry',
size: f.page,
size: f.size,
px: f.px,
legacy: !!f.legacy,
...(f.missing ? { missing: f.missing } : {}),
@ -76,7 +77,7 @@ export function listConfigurations() {
name: `packet.${variant}`,
family: 'packet',
labelKey: variant === 'full' ? 'posterDesign.tpl.packetFull' : 'posterDesign.tpl.packetLightweight',
size: 'Letter, portrait',
size: { paper: 'letter' },
renderer: 'library/packet/packet-render.js buildPacket (VARIANTS)',
settings: { variant },
sample: SAMPLE_COUNTRY_ID,
@ -88,7 +89,7 @@ export function listConfigurations() {
family: 'cards',
labelKey: 'posterDesign.tpl.cards',
stock: s,
size: `${s.w} × ${s.h} in`,
size: { dims: [s.w, s.h] },
renderer: 'library/packet/card-render.js createCardRenderer (STOCK)',
settings: { stock: id },
sample: SAMPLE_COUNTRY_ID,
@ -99,7 +100,7 @@ export function listConfigurations() {
name: 'wall.bless-panels',
family: 'wall',
labelKey: 'posterDesign.tpl.blessPanels',
size: '5 × 8 ft, 3 panels',
size: { dims: [60, 96], panels: 3 },
renderer: 'library/components/poster/panelSheets.js planPanels (the sheets, joins and content boxes); the cards are drawn by plugin-designs/wall-of-people-groups/src/blessPoster.js',
settings: { size: '60x96', joinFill: 'glue' },
sample: null,
@ -111,7 +112,7 @@ export function listConfigurations() {
name: 'map-print.dialog',
family: 'mapPrint',
labelKey: 'posterDesign.tpl.mapPrint',
size: 'library/constants/posterSizes.js',
size: { any: true }, // the reader picks one from the shared ladder (library/constants/posterSizes.js)
renderer: 'library/components/poster/PosterDialog.vue + library/composables/useMapPoster.js',
settings: {},
sample: null,