diff --git a/config/changelog.yaml b/config/changelog.yaml index c5de1986208..f2366ced630 100644 --- a/config/changelog.yaml +++ b/config/changelog.yaml @@ -1,6 +1,18 @@ Unreleased: Added: + Added: + plugin-annotations: + - The `title` macro now takes a `notes` and `classes.notes` as its config, allowing you to add notes + - The `classes.cutlist` config is removed from the title plugin, cutlist info is now included as notes + plugin-i18n: + - This plugin now supports translation of nested arrays of strings, giving you more flexibility to concatenate translated parts of strings + react-components: + - This Pattern component now supports translation of nested arrays of strings, giving you more flexibility to concatenate translated parts of strings + + Removed: + - The `classes.cutlist` config is removed from the title plugin, cutlist info is now included as notes + 3.1.0: date: 2023-12-26 Added: diff --git a/packages/react-components/src/pattern/utils.mjs b/packages/react-components/src/pattern/utils.mjs index 2445c91ccc9..fde6a025584 100644 --- a/packages/react-components/src/pattern/utils.mjs +++ b/packages/react-components/src/pattern/utils.mjs @@ -71,7 +71,8 @@ export const getId = ({ export const translateStrings = (t, list) => { let translated = '' for (const string of list) { - if (string) translated += t(string.toString()).replace(/"/g, '"') + ' ' + if (Array.isArray(string)) translated += translateStrings(t, string) + else if (string) translated += t(string.toString()).replace(/"/g, '"') + ' ' } return translated diff --git a/plugins/plugin-annotations/src/title.mjs b/plugins/plugin-annotations/src/title.mjs index 79af07f8031..d8ae7083809 100644 --- a/plugins/plugin-annotations/src/title.mjs +++ b/plugins/plugin-annotations/src/title.mjs @@ -1,3 +1,6 @@ +const capitalize = (string) => + typeof string === 'string' ? string.charAt(0).toUpperCase() + string.slice(1) : '' + /* * Defaults for the title macro */ @@ -12,10 +15,10 @@ const macroDefaults = { rotation: 0, scale: 1, title: 'plugin-annotations:noName', + notes: false, classes: { - cutlist: 'text-md fill-current', + notes: 'text-md fill-current', date: 'text-sm fill-current', - for: 'fill-current font-bold', name: 'fill-note', nr: 'text-4xl fill-note font-bold', title: 'text-lg fill-current font-bold', @@ -74,9 +77,8 @@ const title = function (config, { Point, points, scale, locale, store, part, log /* * Get the list of IDs - * Initialize the verticle cadence */ - const ids = store.generateMacroIds(['cutlist', 'date', 'for', 'name', 'nr', 'title'], mc.id) + const ids = store.generateMacroIds(['nr', 'date', 'title', 'name', 'notes'], mc.id) let shift = mc.dy @@ -93,6 +95,24 @@ const title = function (config, { Point, points, scale, locale, store, part, log store.set(['partNumbers', part.name], mc.nr) } else delete ids.nr + /* + * Title: date + */ + points[ids.date] = mc.at + .shift(-90, shift / 2) + .addText( + new Date().toLocaleString(locale || 'en', { + weekday: 'long', + year: 'numeric', + month: 'short', + day: 'numeric', + }), + `${mc.classes.date} ${mc.align}` + ) + .attr('data-text-transform', transform) + .attr('data-render-always', 1) // Render even when outside the part bounding box + shift += mc.dy + /* * Title: title */ @@ -100,18 +120,39 @@ const title = function (config, { Point, points, scale, locale, store, part, log points[ids.title] = mc.at .clone() .shift(-90, shift) - .attr('data-text', mc.title, mc.append ? false : true) - .attr('data-text-class', `${mc.classes.title} ${mc.align}`) .attr('data-text-transform', transform) .attr('data-render-always', 1) // Render even when outside the part bounding box + if (mc.append) points[ids.title].addText(mc.title, `${mc.classes.title} ${mc.align}`) + else points[ids.title].setText(mc.title, `${mc.classes.title} ${mc.align}`) shift += mc.dy store.set(['partTitles', part.name], mc.title) } else delete ids.title /* - * Title: cutlist + * Title: name */ + points[ids.name] = mc.at + .clone() + .shift(-90, shift) + .addText( + `FreeSewing ${capitalize( + (store.data?.name || 'plugin-annotations:noName').replace('@freesewing/', '') + )} v${store.data?.version || 'plugin-annotations:noVersion'} ( `, + + `${mc.classes.name} ${mc.align}` + ) + .addText(store.data?.for ? store.data.for : 'ephemeral') + .addText(' )') + .attr('data-text-transform', transform) + .attr('data-render-always', 1) // Render even when outside the part bounding box + shift += mc.dy + + /* + * Title: notes + */ + const notes = [] if (mc.cutlist) { + points[ids.notes] = mc.at.clone().shift(-90, shift) /* * Get cutlist instructions from the store, only proceed if the list is available */ @@ -123,92 +164,37 @@ const title = function (config, { Point, points, scale, locale, store, part, log for (const [material, instructions] of Object.entries(partCutlist.materials)) { instructions.forEach(({ cut, identical, onBias, onFold }, c) => { /* - * Create point - */ - const id = `${ids.cutlist}_${material}_${c}` - ids[`cutlist_${material}_${c}`] = id - points[id] = mc.at - .clone() - .shift(-90, shift) - .attr('data-text', 'plugin-annotations:cut') - .attr('data-text-class', `${mc.classes.cutlist} ${mc.align}`) - .attr('data-text-transform', transform) - .attr('data-render-always', 1) // Render even when outside the part bounding box - .addText(cut) - shift += mc.dy - - /* - * Add instructions if parts are mirrored - */ - if (!identical && cut > 1) points[id].addText('plugin-annotations:mirrored') - - /* - * Add instructions if parts are cut on fold + * Concat line */ + notes.push('plugin-annotations:cut') + notes.push(cut) + if (!identical && cut > 1) notes.push('plugin-annotations:mirrored') if (onFold) - points[id].addText( - onBias ? 'plugin-annotations:onFoldAndBias' : 'plugin-annotations:onFold' - ) + notes.push(onBias ? 'plugin-annotations:onFoldAndBias' : 'plugin-annotations:onFold') + else if (onBias) notes.push('plugin-annotations:onBias') + notes.push('plugin-annotations:from', 'plugin-annotations:' + material) /* - * Add instructions if parts on on bias - */ else if (onBias) points[id].addText('plugin-annotations:onBias') - - /* - * Add 'from' (material) text + * Force a line break between materials */ - points[id].addText('plugin-annotations:from').addText('plugin-annotations:' + material) + notes.push('\n') }) } } - } else delete ids.cutlist - - /* - * Title: Design name - */ - points[ids.name] = mc.at - .clone() - .shift(-90, shift) - .attr( - 'data-text', - `${(store.data?.name || 'plugin-annotations:noName').replace('@freesewing/', '')} v${ - store.data?.version || 'plugin-annotations:noVersion' - }` - ) - .attr('data-text-class', `${mc.classes.name} ${mc.align}`) - .attr('data-text-transform', transform) - .attr('data-render-always', 1) // Render even when outside the part bounding box - shift += mc.dy - - /* - * Title: For (measurements set) - */ - if (store.data.for) { - points[ids.for] = mc.at - .shift(-90, shift) - .attr('data-text', `(${store.data.for})`) - .attr('data-text-class', `${mc.classes.for} ${mc.align}`) + } + if (mc.notes) { + if (Array.isArray(notes)) notes.push(...mc.notes) + else notes.push(mc.notes) + } + if (notes.length > 0) { + /* + * Add all text on a single point + */ + points[ids.notes] + .addText(notes, `${mc.classes.notes} ${mc.align}`) .attr('data-text-transform', transform) .attr('data-render-always', 1) // Render even when outside the part bounding box - shift += mc.dy - } else delete ids.for - - /* - * Title: Date - */ - points[ids.date] = mc.at - .shift(-90, shift) - .attr( - 'data-text', - new Date().toLocaleString(locale || 'en', { - weekday: 'long', - year: 'numeric', - month: 'short', - day: 'numeric', - }) - ) - .attr('data-text-class', `${mc.classes.date} ${mc.align}`) - .attr('data-text-transform', transform) - .attr('data-render-always', 1) // Render even when outside the part bounding box + .attr('data-text-lineheight', mc.dy) + } else delete ids.cutlist /* * Store all IDs in the store so we can remove this macro with rmtitle diff --git a/plugins/plugin-i18n/src/index.mjs b/plugins/plugin-i18n/src/index.mjs index 6bee18b6ebc..2943dc54019 100644 --- a/plugins/plugin-i18n/src/index.mjs +++ b/plugins/plugin-i18n/src/index.mjs @@ -1,25 +1,34 @@ import { name, version } from '../data.mjs' +const translate = (locale, text, t, pattern) => { + let translated = '' + /* + * Call oneself recursively if text is an array + */ + if (Array.isArray(text)) + return text.map((string) => translate(locale, string, t, pattern)).join(' ') + + if (t instanceof Function) return t(text, locale) + else if (typeof t[locale] === 'object') return t[locale][text] || text + else { + const msg = + "No translation method or object was passed to the i18n plugin. This plugin won't do anything without that" + if (pattern?.store?.log?.warn) { + if (!pattern.store.get(['plugins', 'plugin-i18n', 'missingMethodWarning'])) { + pattern.store.set(['plugins', 'plugin-i18n', 'missingMethodWarning'], true) + pattern.store.log.warn(msg) + } + } else console.log(msg) + + return text + } +} + export const plugin = { name, version, hooks: { - insertText: (locale, text, t, pattern) => { - if (t instanceof Function) return t(text, locale) - else if (typeof t[locale] === 'object') return t[locale][text] || text - else { - const msg = - "No translation method or object was passed to the i18n plugin. This plugin won't do anything without that" - if (pattern?.store?.log?.warn) { - if (!pattern.store.get(['plugins', 'plugin-i18n', 'missingMethodWarning'])) { - pattern.store.set(['plugins', 'plugin-i18n', 'missingMethodWarning'], true) - pattern.store.log.warn(msg) - } - } else console.log(msg) - - return text - } - }, + insertText: (locale, text, t, pattern) => translate(locale, text, t, pattern), }, }