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:
parent
6d89e6b8ce
commit
58c6bb28af
18 changed files with 165 additions and 42 deletions
|
|
@ -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. */
|
||||
|
|
|
|||
|
|
@ -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": "صفحات الخرائط",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": "मानचित्र पृष्ठ",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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ă",
|
||||
|
|
|
|||
|
|
@ -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": "Листы карт",
|
||||
|
|
|
|||
|
|
@ -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": "地图页",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
}
|
||||
}
|
||||
|
|
|
|||
39
plugin-designs/poster-design/src/sizeText.js
Normal file
39
plugin-designs/poster-design/src/sizeText.js
Normal 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 || '')
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in a new issue