@seliseblocks/mailcraft 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DOCS.md +557 -0
- package/LICENSE +21 -0
- package/README.md +126 -0
- package/dist/mailcraft-editor.bundle.js +519 -0
- package/dist/mailcraft-editor.bundle.js.map +7 -0
- package/examples/templates/activate-your-account.html +54 -0
- package/examples/templates/back-in-stock.html +63 -0
- package/examples/templates/cart-left-behind.html +74 -0
- package/examples/templates/community-giveaway.html +81 -0
- package/examples/templates/frontend-futures-invite.html +69 -0
- package/examples/templates/give-25-get-25.html +69 -0
- package/examples/templates/invoice-paid.html +77 -0
- package/examples/templates/meet-nova-launch.html +69 -0
- package/examples/templates/mega-weekend-sale.html +57 -0
- package/examples/templates/order-confirmed.html +80 -0
- package/examples/templates/rate-your-headphones.html +71 -0
- package/examples/templates/reset-your-password.html +54 -0
- package/examples/templates/thankyou-promo-code.html +68 -0
- package/examples/templates/the-sunday-brief.html +47 -0
- package/examples/templates/welcome-to-your-workspace.html +62 -0
- package/examples/templates/your-order-shipped.html +74 -0
- package/examples/templates/your-password-was-changed.html +58 -0
- package/examples/templates/your-signin-code.html +58 -0
- package/examples/vanilla.html +1574 -0
- package/package.json +66 -0
- package/src/core/accent.js +194 -0
- package/src/core/assets.js +15 -0
- package/src/core/binder.js +119 -0
- package/src/core/blocks.js +256 -0
- package/src/core/css-cascade.js +117 -0
- package/src/core/editor-core.js +1492 -0
- package/src/core/export.js +58 -0
- package/src/core/footer.js +73 -0
- package/src/core/i18n/ar.js +177 -0
- package/src/core/i18n/bg.js +152 -0
- package/src/core/i18n/bn.js +176 -0
- package/src/core/i18n/ca.js +152 -0
- package/src/core/i18n/cs.js +152 -0
- package/src/core/i18n/da.js +152 -0
- package/src/core/i18n/de-CH.js +152 -0
- package/src/core/i18n/de.js +152 -0
- package/src/core/i18n/dz.js +179 -0
- package/src/core/i18n/el.js +152 -0
- package/src/core/i18n/en.js +269 -0
- package/src/core/i18n/es.js +152 -0
- package/src/core/i18n/et.js +152 -0
- package/src/core/i18n/fi.js +152 -0
- package/src/core/i18n/fr.js +152 -0
- package/src/core/i18n/hr.js +152 -0
- package/src/core/i18n/hu.js +152 -0
- package/src/core/i18n/index.js +83 -0
- package/src/core/i18n/it.js +152 -0
- package/src/core/i18n/lt.js +152 -0
- package/src/core/i18n/lv.js +152 -0
- package/src/core/i18n/nb.js +152 -0
- package/src/core/i18n/nl.js +152 -0
- package/src/core/i18n/pl.js +152 -0
- package/src/core/i18n/pt.js +152 -0
- package/src/core/i18n/ro.js +152 -0
- package/src/core/i18n/ru.js +152 -0
- package/src/core/i18n/sk.js +152 -0
- package/src/core/i18n/sl.js +152 -0
- package/src/core/i18n/sv.js +152 -0
- package/src/core/i18n/tables.js +50 -0
- package/src/core/i18n/tr.js +152 -0
- package/src/core/i18n/uk.js +152 -0
- package/src/core/icons.js +235 -0
- package/src/core/ids.js +1 -0
- package/src/core/import-html.js +959 -0
- package/src/core/layout-style.js +115 -0
- package/src/core/parse.js +10 -0
- package/src/core/placeholder.js +15 -0
- package/src/core/sanitize.js +141 -0
- package/src/core/storage-limits.js +184 -0
- package/src/core/storage.js +85 -0
- package/src/core/theme.js +1 -0
- package/src/core/toolbar.js +67 -0
- package/src/core/variables.js +11 -0
- package/src/create-editor.js +109 -0
- package/src/index.js +9 -0
- package/src/mailcraft-editor.js +1862 -0
- package/src/render/block-body.js +295 -0
- package/src/render/canvas.js +284 -0
- package/src/render/fields.js +609 -0
- package/src/render/focus-preserve.js +158 -0
- package/src/render/rte.js +212 -0
- package/src/render/screenshot.js +132 -0
- package/src/render/story.js +415 -0
- package/src/render/style.js +452 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ported verbatim from the original `buildHtml()`, including its defining
|
|
3
|
+
* trait: it does not re-template each block from `props` a second time, it
|
|
4
|
+
* reads back the already-rendered live DOM node for each block (`grab`) and
|
|
5
|
+
* strips editor-only attributes. The one adaptation: the original queried
|
|
6
|
+
* plain `document` (the whole prototype ran outside any shadow tree); this
|
|
7
|
+
* package renders inside a Shadow DOM, so `grab` is parameterized on the root
|
|
8
|
+
* to search (the element's `shadowRoot`) instead of hardcoding `document`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { cssUrl } from './sanitize.js';
|
|
12
|
+
import { rowBorderCss, rowMargin, rowPad } from './layout-style.js';
|
|
13
|
+
|
|
14
|
+
export function buildHtml(state, root, boxCss) {
|
|
15
|
+
const d = state.doc; const t = d.theme;
|
|
16
|
+
const grab = (id) => {
|
|
17
|
+
const el = root.querySelector('[data-mc-content="' + id + '"]');
|
|
18
|
+
if (!el) return '';
|
|
19
|
+
return el.outerHTML
|
|
20
|
+
.replace(/\scontenteditable="[^"]*"/g, '')
|
|
21
|
+
.replace(/\sdata-mc-[a-z-]+="[^"]*"/g, '')
|
|
22
|
+
.replace(/\sspellcheck="[^"]*"/g, '')
|
|
23
|
+
.replace(/\sdraggable="[^"]*"/g, '');
|
|
24
|
+
};
|
|
25
|
+
const rows = d.rows.map((r) => {
|
|
26
|
+
const rp = r.props;
|
|
27
|
+
const colInner = (c) => c.blocks.map((b) => {
|
|
28
|
+
if (b.type === 'css') return '<style>' + (b.props.code || '') + '</style>';
|
|
29
|
+
if (b.type === 'html') return b.props.code || '';
|
|
30
|
+
if (b.type === 'svg') return '<div style="text-align:' + b.props.align + ';padding:' + b.props.py + 'px 0">' + (b.props.code || '') + '</div>';
|
|
31
|
+
return '<div style="' + boxCss(b.props) + '">' + grab(b.id) + '</div>';
|
|
32
|
+
}).join('\n ') || ' ';
|
|
33
|
+
const cells = r.cols.map((c) => {
|
|
34
|
+
// Column-level styling (bg/radius/inner padding) renders as a wrapper
|
|
35
|
+
// <div> inside the cell so the gutter padding stays unpainted --
|
|
36
|
+
// mirrors render/canvas.js's `host` wrapper.
|
|
37
|
+
const inner = (c.bg || c.border || c.radius || c.padY || c.padX)
|
|
38
|
+
? '<div style="background:' + (c.bg || 'transparent') + ';' + (c.border ? 'border:' + c.border + 'px ' + (c.borderStyle || 'solid') + ' ' + (c.lineColor || '#e2e2e5') + ';' : '') + 'border-radius:' + (c.radius || 0) + 'px;padding:' + (c.padY || 0) + 'px ' + (c.padX || 0) + 'px">\n ' + colInner(c) + '\n </div>'
|
|
39
|
+
: colInner(c);
|
|
40
|
+
return '<td width="' + c.span + '%" valign="' + rp.valign + '" style="padding:0 ' + Math.round(rp.gap / 2) + 'px;">\n ' + inner + '\n </td>';
|
|
41
|
+
}).join('\n ');
|
|
42
|
+
const cssBody = rp.layout === 'grid'
|
|
43
|
+
? '<div style="display:grid;grid-template-columns:repeat(' + (rp.gridCols || 2) + ',minmax(0,1fr));gap:' + rp.gap + 'px">\n ' + r.cols.map((c) => '<div>' + colInner(c) + '</div>').join('\n ') + '\n </div>'
|
|
44
|
+
: '<div style="display:flex;flex-direction:' + (rp.flexDir || 'row') + ';justify-content:' + (rp.justify || 'flex-start') + ';align-items:' + (rp.alignItems || 'stretch') + ';flex-wrap:' + (rp.wrap ? 'wrap' : 'nowrap') + ';gap:' + rp.gap + 'px">\n ' + r.cols.map((c) => '<div style="flex:' + c.span + ' 1 auto;min-width:0">' + colInner(c) + '</div>').join('\n ') + '\n </div>';
|
|
45
|
+
const body = rp.layout && rp.layout !== 'columns'
|
|
46
|
+
? cssBody
|
|
47
|
+
: '<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr>\n ' + cells + '\n </tr></table>';
|
|
48
|
+
const tdBg = rp.bgImage
|
|
49
|
+
? 'background-color:' + (rp.bg || t.contentBg) + ';background-image:' + (rp.overlay ? 'linear-gradient(rgba(20,22,24,' + (rp.overlay / 100) + '),rgba(20,22,24,' + (rp.overlay / 100) + ')),' : '') + 'url("' + cssUrl(rp.bgImage) + '");background-size:' + (rp.bgSize || 'cover') + ';background-position:' + (rp.bgPos || 'center') + ';background-repeat:' + (rp.bgRepeat || 'no-repeat') + ';'
|
|
50
|
+
: 'background:' + (rp.bg || t.contentBg) + ';';
|
|
51
|
+
const tdBorder = rowBorderCss(rp)
|
|
52
|
+
+ (rp.radius ? 'border-radius:' + rp.radius + 'px;' : '')
|
|
53
|
+
+ (rp.shadow ? 'box-shadow:' + rp.shadow + ';' : '')
|
|
54
|
+
+ ((rp.mt || rp.mr || rp.mb || rp.ml || rp.my) ? 'margin:' + rowMargin(rp) + ';' : '');
|
|
55
|
+
return ' <tr>\n <td style="padding:' + rowPad(rp) + ';' + tdBg + tdBorder + '">\n ' + body + '\n </td>\n </tr>';
|
|
56
|
+
}).join('\n');
|
|
57
|
+
return '<!doctype html>\n<html lang="en">\n<head>\n<meta charset="utf-8">\n<meta name="viewport" content="width=device-width,initial-scale=1">\n<meta name="color-scheme" content="light dark">\n<title>' + 'Email' + '</title>\n</head>\n<body style="margin:0;padding:0;background:' + t.bg + ';font-family:' + t.font.replace(/"/g, "'") + ';color:' + t.text + ';-webkit-font-smoothing:antialiased;">\n<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:' + t.bg + ';">\n <tr><td align="center" style="padding:24px 12px;">\n <table role="presentation" width="' + t.width + '" cellpadding="0" cellspacing="0" border="0" style="width:' + t.width + 'px;max-width:100%;background:' + t.contentBg + ';">\n' + rows + '\n </table>\n </td></tr>\n</table>\n</body>\n</html>';
|
|
58
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The attribution strip along the bottom of the shell -- by default
|
|
3
|
+
* "Powered by SELISE Blocks (c) 2026".
|
|
4
|
+
*
|
|
5
|
+
* It is configurable for the same reason the top bar is (core/toolbar.js): the
|
|
6
|
+
* editor is embedded inside chrome the host already owns, and a host with its
|
|
7
|
+
* own footer, its own attribution, or a white-label agreement should not have
|
|
8
|
+
* to fight a bar it cannot address. So the strip can carry the host's own
|
|
9
|
+
* line, point at the host's own link, or be removed outright.
|
|
10
|
+
*
|
|
11
|
+
* The default text lives in the message table (`footer.poweredBy`), not here,
|
|
12
|
+
* so it translates with `locale` and can be overridden per-string through
|
|
13
|
+
* `.messages` like every other label.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The same "off" spellings the toolbar accepts -- markup only has strings, and one convention beats two. */
|
|
17
|
+
const HIDDEN = ['none', 'hidden', 'off', 'false'];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A footer link is host-authored, but it is rendered inside the editor's own
|
|
21
|
+
* DOM, so the scheme is allowlisted the way `cssUrl` allowlists image sources:
|
|
22
|
+
* a relative path has no scheme and is fine, `javascript:` resolves to nothing
|
|
23
|
+
* rather than becoming a click target in someone else's UI.
|
|
24
|
+
*/
|
|
25
|
+
function safeHref(value) {
|
|
26
|
+
const raw = String(value == null ? '' : value).trim();
|
|
27
|
+
if (!raw) return '';
|
|
28
|
+
const scheme = raw.match(/^([a-z][a-z0-9+.-]*):/i);
|
|
29
|
+
if (scheme && !/^(https?|mailto)$/i.test(scheme[1])) return '';
|
|
30
|
+
return raw;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Normalizes whatever the host set into `{ text, href, target }`, or `null`
|
|
35
|
+
* for "no footer at all".
|
|
36
|
+
*
|
|
37
|
+
* Accepts, in the shapes a Web Component gets configured through:
|
|
38
|
+
*
|
|
39
|
+
* - unset (`null`/`undefined`) the built-in attribution
|
|
40
|
+
* - `false` or `"none"` no strip at all
|
|
41
|
+
* - a string replaces the text (`footer="© 2026 Acme"`)
|
|
42
|
+
* - an object `{ text, href, target, show }` -- text plus a
|
|
43
|
+
* link, for a host that wants its own mark to
|
|
44
|
+
* lead somewhere
|
|
45
|
+
*
|
|
46
|
+
* `text: null` in the result means "use the translated default": the string is
|
|
47
|
+
* resolved through the translator at render time, so it follows `locale` and
|
|
48
|
+
* `.messages` instead of being frozen here. An explicit empty string is
|
|
49
|
+
* honored as empty, for a host that wants a link and nothing else.
|
|
50
|
+
*/
|
|
51
|
+
export function resolveFooter(value) {
|
|
52
|
+
if (value === false) return null;
|
|
53
|
+
if (value == null) return { text: null, href: '', target: '' };
|
|
54
|
+
|
|
55
|
+
if (typeof value === 'string') {
|
|
56
|
+
const raw = value.trim();
|
|
57
|
+
if (HIDDEN.indexOf(raw.toLowerCase()) >= 0) return null;
|
|
58
|
+
return { text: raw || null, href: '', target: '' };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (typeof value === 'object') {
|
|
62
|
+
if (value.show === false) return null;
|
|
63
|
+
return {
|
|
64
|
+
text: value.text == null ? null : String(value.text),
|
|
65
|
+
href: safeHref(value.href),
|
|
66
|
+
// Only meaningful alongside an href; the default is applied at render,
|
|
67
|
+
// so this stays a plain description of what the host asked for.
|
|
68
|
+
target: value.target ? String(value.target) : '',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { text: null, href: '', target: '' };
|
|
73
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arabic (العربية) — `ar`. Right-to-left -- pair with `dir="rtl"` (the
|
|
3
|
+
* editor defaults `dir` from `isRtl(locale)` automatically unless the host
|
|
4
|
+
* sets `dir` explicitly).
|
|
5
|
+
*
|
|
6
|
+
* AI-drafted, not reviewed by a native speaker. Correctable one key at a
|
|
7
|
+
* time via `.messages` overrides.
|
|
8
|
+
*/
|
|
9
|
+
export const AR = {
|
|
10
|
+
'action.undoHint': 'تراجع — ⌘Z',
|
|
11
|
+
'action.redoHint': 'إعادة — ⌘⇧Z',
|
|
12
|
+
'action.chromeToDark': 'داكن',
|
|
13
|
+
'action.chromeToLight': 'فاتح',
|
|
14
|
+
'action.chromeHint': 'وضع المحرر فاتح / داكن',
|
|
15
|
+
'action.aiCopy': 'نص',
|
|
16
|
+
'action.aiCopyHint': 'صياغة نص بالذكاء الاصطناعي',
|
|
17
|
+
'action.code': 'الكود',
|
|
18
|
+
'action.codeHint': 'تحرير HTML الخام مع معاينة مباشرة',
|
|
19
|
+
'action.preview': 'معاينة',
|
|
20
|
+
'action.export': 'تصدير',
|
|
21
|
+
|
|
22
|
+
'device.desktop': 'سطح المكتب',
|
|
23
|
+
'device.desktopHint': 'عرض سطح المكتب',
|
|
24
|
+
'device.mobile': 'الجوال',
|
|
25
|
+
'device.mobileHint': 'عرض الجوال',
|
|
26
|
+
'device.dark': 'داكن',
|
|
27
|
+
'device.darkHint': 'وضع الوارد الداكن',
|
|
28
|
+
|
|
29
|
+
'status.autosaveOn': 'الحفظ التلقائي مفعّل',
|
|
30
|
+
'status.saved': 'تم الحفظ {time}',
|
|
31
|
+
'status.saveFailed': 'لم يتم الحفظ — التخزين ممتلئ',
|
|
32
|
+
|
|
33
|
+
'canvas.meta': '{rows} صفوف · {blocks} كتل · {width}px',
|
|
34
|
+
|
|
35
|
+
'tab.design': 'النمط',
|
|
36
|
+
'tab.designHint': 'تنسيق العنصر المحدد',
|
|
37
|
+
'tab.blocks': 'الكتل',
|
|
38
|
+
'tab.blocksHint': 'كتل المحتوى والمتغيرات',
|
|
39
|
+
'tab.rows': 'الأقسام',
|
|
40
|
+
'tab.rowsHint': 'تخطيطات الأقسام ونموذج اللوحة',
|
|
41
|
+
'tab.files': 'الملفات',
|
|
42
|
+
'tab.filesHint': 'مكتبة الأصول',
|
|
43
|
+
'tab.layers': 'البنية',
|
|
44
|
+
'tab.layersHint': 'بنية المستند',
|
|
45
|
+
'tab.theme': 'السمة',
|
|
46
|
+
'tab.themeHint': 'الأنماط العامة',
|
|
47
|
+
'tab.data': 'المتغيرات',
|
|
48
|
+
'tab.dataHint': 'المتغيرات المقدَّمة من برنامجك',
|
|
49
|
+
|
|
50
|
+
'inspector.blockProperties': 'خصائص الكتلة',
|
|
51
|
+
'inspector.sectionProperties': 'خصائص القسم',
|
|
52
|
+
'inspector.title': 'المفتش',
|
|
53
|
+
'inspector.sectionLabel': 'قسم',
|
|
54
|
+
'inspector.nothingSelected': 'لا يوجد تحديد',
|
|
55
|
+
'action.duplicateHint': 'مضاعفة — ⌘D',
|
|
56
|
+
'action.deleteHint': 'حذف — ⌫',
|
|
57
|
+
'inspector.emptyBody':
|
|
58
|
+
'اختر صفًا أو كتلة على اللوحة لتنسيقه هنا. اسحب الكتل من تبويب <strong style="color: var(--ed-text); font-weight: 600;">الكتل</strong>؛ انقر نقرًا مزدوجًا على أي نص لتحريره في مكانه.',
|
|
59
|
+
'inspector.shortcuts': 'الاختصارات',
|
|
60
|
+
'shortcut.undoRedo': '⌘Z / ⌘⇧Z — تراجع، إعادة',
|
|
61
|
+
'shortcut.duplicate': '⌘D — مضاعفة التحديد',
|
|
62
|
+
'shortcut.delete': '⌫ — حذف التحديد',
|
|
63
|
+
'shortcut.export': '⌘E — تصدير HTML',
|
|
64
|
+
'shortcut.escape': 'ESC — إلغاء التحديد، إغلاق',
|
|
65
|
+
|
|
66
|
+
'blocks.dragHint': 'اسحب إلى اللوحة',
|
|
67
|
+
'blocks.count': '{count} كتلة',
|
|
68
|
+
|
|
69
|
+
'rows.sectionLayouts': 'تخطيطات الأقسام',
|
|
70
|
+
'rows.customMarkup': 'ترميز مخصص',
|
|
71
|
+
'rows.htmlRowHint': 'اسحب لإدراجه، أو انقر للإضافة — صف بعرض كامل تملأه بـ HTML خام',
|
|
72
|
+
'rows.rawHtmlSection': 'قسم HTML خام',
|
|
73
|
+
'rows.rawHtmlDesc': 'قسم بعرض كامل، ترميزك الخاص، وقابل للسحب أيضًا',
|
|
74
|
+
'rows.canvasModel': 'نموذج اللوحة',
|
|
75
|
+
'rows.modeRowBased': 'قائم على الصفوف',
|
|
76
|
+
'rows.modeFreeStack': 'تكديس حر',
|
|
77
|
+
'rows.hintRows':
|
|
78
|
+
'قائم على الأقسام: أسقط تخطيطًا أولًا، ثم أسقط الكتل داخل أعمدته — كل قسم يحمل خلفيته وحدوده وإعدادات flex أو grid الخاصة به.',
|
|
79
|
+
'rows.hintStack': 'تكديس حر: بدون تخطيطات — كل كتلة تسحبها تصبح قسمًا مستقلًا بعرض كامل، مرتبة بالتسلسل.',
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
'files.libraryCount': 'المكتبة — {count} ملفات',
|
|
83
|
+
'files.openManager': 'فتح مدير الملفات',
|
|
84
|
+
'files.dragToCanvas': 'اسحب أي ملف مباشرة إلى اللوحة لوضعه كصورة.',
|
|
85
|
+
|
|
86
|
+
'layers.structure': 'البنية',
|
|
87
|
+
'layers.sectionSingle': 'قسم',
|
|
88
|
+
'layers.sectionMulti': 'قسم · {count} أعمدة',
|
|
89
|
+
'layers.blockCount': '{count} كتلة',
|
|
90
|
+
|
|
91
|
+
'theme.globalStyles': 'الأنماط العامة',
|
|
92
|
+
|
|
93
|
+
'vars.title': 'المتغيرات',
|
|
94
|
+
'vars.fromCode': '{count} من برنامجك',
|
|
95
|
+
'vars.declaredNote': 'مُعرَّفة من قِبل تطبيقك — تعرض اللوحة دائمًا الرمز، وليس القيمة أبدًا.',
|
|
96
|
+
'vars.filterPlaceholder': 'تصفية المتغيرات…',
|
|
97
|
+
'vars.insertHint': 'انقر للإدراج في النص أو العنوان أو الزر المحدد',
|
|
98
|
+
'vars.insert': 'إدراج',
|
|
99
|
+
'vars.noMatch': 'لا يوجد متغير يطابق هذا الفلتر.',
|
|
100
|
+
'vars.noneDeclared': 'لم يُعرِّف تطبيقك أي متغيرات بعد.',
|
|
101
|
+
'vars.rteHint': 'أثناء تحرير النص يمكنك أيضًا اختيار متغير من القائمة المنسدلة في شريط الأدوات المضمّن.',
|
|
102
|
+
|
|
103
|
+
'modal.fileManager': 'مدير الملفات',
|
|
104
|
+
'modal.assetsTitle': 'الأصول',
|
|
105
|
+
'modal.replaceImage': 'استبدال الصورة',
|
|
106
|
+
'library.searchPlaceholder': 'بحث في الملفات…',
|
|
107
|
+
'action.upload': 'رفع',
|
|
108
|
+
'library.clickToPlace': 'انقر على ملف لوضعه · أو اسحب ملفًا إلى اللوحة',
|
|
109
|
+
'library.clickToReplace': 'انقر على ملف لاستبدال الصورة المحددة',
|
|
110
|
+
'library.storageLabel': '{count} ملفات · {size}',
|
|
111
|
+
'library.folders': 'المجلدات',
|
|
112
|
+
'library.dropHint': 'أسقط ملفات الصور هنا للرفع — أو استخدم زر الرفع.',
|
|
113
|
+
'library.deleteFileHint': 'حذف الملف',
|
|
114
|
+
'library.del': 'حذف',
|
|
115
|
+
'toast.assetDeleted': 'تم حذف {name}',
|
|
116
|
+
|
|
117
|
+
'modal.export': 'تصدير',
|
|
118
|
+
'modal.exportTitle': 'HTML بريد إلكتروني قائم على الجداول',
|
|
119
|
+
'export.copy': 'نسخ',
|
|
120
|
+
'export.copied': 'تم النسخ',
|
|
121
|
+
'action.download': 'تنزيل',
|
|
122
|
+
'export.meta': '{kb} كيلوبايت · جداول + أنماط مضمّنة · عروض آمنة لـ Outlook',
|
|
123
|
+
|
|
124
|
+
'modal.rawHtmlTitle': 'HTML خام، معاينة مباشرة',
|
|
125
|
+
'code.statusEdited': 'عرض الكود — مُعدَّل — المعاينة مباشرة، لم يُطبَّق بعد',
|
|
126
|
+
'code.statusSynced': 'عرض الكود — متزامن مع اللوحة',
|
|
127
|
+
'action.reload': 'إعادة تحميل',
|
|
128
|
+
'action.reloadHint': 'إعادة تحميل المصدر من اللوحة',
|
|
129
|
+
'action.applyToCanvas': 'تطبيق على اللوحة',
|
|
130
|
+
'action.applyToCanvasHint': 'تحليل المصدر مرة أخرى إلى صفوف قابلة للتحرير',
|
|
131
|
+
'action.closeWithoutApplyingHint': 'إغلاق دون تطبيق',
|
|
132
|
+
'code.source': 'المصدر',
|
|
133
|
+
'code.livePreview': 'معاينة مباشرة',
|
|
134
|
+
'code.liveHtmlPreviewTitle': 'معاينة HTML مباشرة',
|
|
135
|
+
'code.applyNote':
|
|
136
|
+
'يحلّل زر التطبيق كل صف جدول من المستوى الأعلى إلى صف لوحة يمكنك تحديده وإعادة ترتيبه وحذفه — الترميز المكتوب يدويًا يبقى سليمًا.',
|
|
137
|
+
'code.meta': '{kb} كيلوبايت · {lines} أسطر',
|
|
138
|
+
'toast.sourceReloaded': 'تمت إعادة تحميل المصدر من اللوحة',
|
|
139
|
+
'toast.sourceAppliedOne': 'تم تطبيق المصدر — صف واحد على اللوحة',
|
|
140
|
+
'toast.sourceAppliedMany': 'تم تطبيق المصدر — {rows} صفوف على اللوحة',
|
|
141
|
+
'toast.parseError': 'تعذّر تحليل ذلك الـ HTML',
|
|
142
|
+
|
|
143
|
+
'modal.copyAssist': 'مساعد الصياغة',
|
|
144
|
+
'modal.draftCopyTitle': 'صياغة نص لهذه الحملة',
|
|
145
|
+
'ai.goal': 'الهدف',
|
|
146
|
+
'ai.tone': 'الأسلوب',
|
|
147
|
+
'ai.briefLabel': 'ما موضوع هذا البريد الإلكتروني؟',
|
|
148
|
+
'ai.briefPlaceholder': 'مثال: إطلاق ملابس الخريف، خصم 15% للمشتركين، شحن مجاني فوق 150$',
|
|
149
|
+
'ai.generate': 'إنشاء النص',
|
|
150
|
+
'ai.writing': 'جارٍ الكتابة…',
|
|
151
|
+
'ai.kindHeadline': 'عنوان رئيسي',
|
|
152
|
+
'ai.kindBody': 'نص أساسي',
|
|
153
|
+
'ai.kindButton': 'نص الزر',
|
|
154
|
+
'ai.actionInsertHeading': 'إدراج كعنوان',
|
|
155
|
+
'ai.actionInsertText': 'إدراج ككتلة نصية',
|
|
156
|
+
'ai.actionInsertButton': 'إدراج كزر',
|
|
157
|
+
'toast.headingAdded': 'تمت إضافة العنوان',
|
|
158
|
+
'toast.textBlockAdded': 'تمت إضافة الكتلة النصية',
|
|
159
|
+
'toast.buttonAdded': 'تمت إضافة الزر',
|
|
160
|
+
|
|
161
|
+
'preview.kickerMobile': 'معاينة — جوال 375px',
|
|
162
|
+
'preview.kickerDark': 'معاينة — وارد داكن',
|
|
163
|
+
'preview.kickerDesktop': 'معاينة — سطح مكتب {width}px',
|
|
164
|
+
'action.close': 'إغلاق',
|
|
165
|
+
|
|
166
|
+
'toast.duplicated': 'تمت المضاعفة',
|
|
167
|
+
'toast.fileUploadedOne': 'تم رفع ملف واحد',
|
|
168
|
+
'toast.fileUploadedMany': 'تم رفع {count} ملفات',
|
|
169
|
+
'toast.imageReplaced': 'تم استبدال الصورة',
|
|
170
|
+
'toast.imageAdded': 'تمت إضافة الصورة إلى اللوحة',
|
|
171
|
+
'toast.htmlCopied': 'تم نسخ HTML إلى الحافظة',
|
|
172
|
+
'toast.templateLoaded': 'تم تحميل {name}',
|
|
173
|
+
'toast.snippetDefaultLabel': 'مقتطف',
|
|
174
|
+
'toast.snippetInserted': 'تم إدراج {name}',
|
|
175
|
+
'toast.snippetCopied': 'تم نسخ {name} — حدد كتلة نصية لإدراجه',
|
|
176
|
+
};
|
|
177
|
+
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/** Bulgarian (Български) — `bg`. AI-drafted, not reviewed by a native speaker. Correctable one key at a time via `.messages` overrides. */
|
|
2
|
+
export const BG = {
|
|
3
|
+
'action.undoHint': 'Отмяна — ⌘Z',
|
|
4
|
+
'action.redoHint': 'Повторение — ⌘⇧Z',
|
|
5
|
+
'action.chromeToDark': 'Тъмна',
|
|
6
|
+
'action.chromeToLight': 'Светла',
|
|
7
|
+
'action.chromeHint': 'Тема на редактора светла / тъмна',
|
|
8
|
+
'action.aiCopy': 'Текст',
|
|
9
|
+
'action.aiCopyHint': 'Написване на текст с изкуствен интелект',
|
|
10
|
+
'action.code': 'Код',
|
|
11
|
+
'action.codeHint': 'Редактиране на суров HTML с преглед на живо',
|
|
12
|
+
'action.preview': 'Преглед',
|
|
13
|
+
'action.export': 'Изнасяне',
|
|
14
|
+
'device.desktop': 'Компютър',
|
|
15
|
+
'device.desktopHint': 'Ширина за компютър',
|
|
16
|
+
'device.mobile': 'Телефон',
|
|
17
|
+
'device.mobileHint': 'Ширина за телефон',
|
|
18
|
+
'device.dark': 'Тъмна',
|
|
19
|
+
'device.darkHint': 'Тъмен режим на пощата',
|
|
20
|
+
'status.autosaveOn': 'автоматичното запазване е включено',
|
|
21
|
+
'status.saved': 'запазено {time}',
|
|
22
|
+
'status.saveFailed': 'не е запазено — хранилището е пълно',
|
|
23
|
+
'canvas.meta': '{rows} реда · {blocks} блока · {width}px',
|
|
24
|
+
'tab.design': 'Стил',
|
|
25
|
+
'tab.designHint': 'Стилизиране на избраното',
|
|
26
|
+
'tab.blocks': 'Блокове',
|
|
27
|
+
'tab.blocksHint': 'Блокове съдържание и променливи',
|
|
28
|
+
'tab.rows': 'Секции',
|
|
29
|
+
'tab.rowsHint': 'Оформления на секции и модел на платното',
|
|
30
|
+
'tab.files': 'Файлове',
|
|
31
|
+
'tab.filesHint': 'Библиотека с ресурси',
|
|
32
|
+
'tab.layers': 'Структура',
|
|
33
|
+
'tab.layersHint': 'Структура на документа',
|
|
34
|
+
'tab.theme': 'Тема',
|
|
35
|
+
'tab.themeHint': 'Глобални стилове',
|
|
36
|
+
'tab.data': 'Промен.',
|
|
37
|
+
'tab.dataHint': 'Променливи, предоставени от вашия код',
|
|
38
|
+
'inspector.blockProperties': 'Свойства на блока',
|
|
39
|
+
'inspector.sectionProperties': 'Свойства на секцията',
|
|
40
|
+
'inspector.title': 'Инспектор',
|
|
41
|
+
'inspector.sectionLabel': 'Секция',
|
|
42
|
+
'inspector.nothingSelected': 'Нищо не е избрано',
|
|
43
|
+
'action.duplicateHint': 'Дублиране — ⌘D',
|
|
44
|
+
'action.deleteHint': 'Изтриване — ⌫',
|
|
45
|
+
'inspector.emptyBody':
|
|
46
|
+
'Изберете ред или блок на платното, за да го стилизирате тук. Плъзнете блокове от раздела <strong style="color: var(--ed-text); font-weight: 600;">Блокове</strong>; кликнете два пъти върху текст, за да го редактирате на място.',
|
|
47
|
+
'inspector.shortcuts': 'Клавишни комбинации',
|
|
48
|
+
'shortcut.undoRedo': '⌘Z / ⌘⇧Z — отмяна, повторение',
|
|
49
|
+
'shortcut.duplicate': '⌘D — дублиране на избора',
|
|
50
|
+
'shortcut.delete': '⌫ — изтриване на избора',
|
|
51
|
+
'shortcut.export': '⌘E — изнасяне на HTML',
|
|
52
|
+
'shortcut.escape': 'ESC — премахване на избора, затваряне',
|
|
53
|
+
'blocks.dragHint': 'Плъзнете върху платното',
|
|
54
|
+
'blocks.count': '{count} блока',
|
|
55
|
+
'rows.sectionLayouts': 'Оформления на секции',
|
|
56
|
+
'rows.customMarkup': 'Персонализирана маркировка',
|
|
57
|
+
'rows.htmlRowHint': 'Плъзнете, за да вмъкнете, или кликнете, за да добавите — ред на цялата ширина, който запълвате със суров HTML',
|
|
58
|
+
'rows.rawHtmlSection': 'Секция със суров HTML',
|
|
59
|
+
'rows.rawHtmlDesc': 'Секция на цялата ширина, ваша собствена маркировка, все още може да се плъзга',
|
|
60
|
+
'rows.canvasModel': 'Модел на платното',
|
|
61
|
+
'rows.modeRowBased': 'Базирано на редове',
|
|
62
|
+
'rows.modeFreeStack': 'Свободно подреждане',
|
|
63
|
+
'rows.hintRows':
|
|
64
|
+
'Базирано на секции: първо пуснете оформление, после блокове в неговите колони — всяка секция има собствен фон, рамка, flex или grid настройки.',
|
|
65
|
+
'rows.hintStack': 'Свободно подреждане: без оформления — всеки пуснат блок става собствена секция на цялата ширина, подредена по ред.',
|
|
66
|
+
'files.libraryCount': 'Библиотека — {count} файла',
|
|
67
|
+
'files.openManager': 'Отваряне на файловия мениджър',
|
|
68
|
+
'files.dragToCanvas': 'Плъзнете произволен файл директно върху платното, за да го поставите като блок с изображение.',
|
|
69
|
+
'layers.structure': 'Структура',
|
|
70
|
+
'layers.sectionSingle': 'Секция',
|
|
71
|
+
'layers.sectionMulti': 'Секция · {count} колони',
|
|
72
|
+
'layers.blockCount': '{count} бл.',
|
|
73
|
+
'theme.globalStyles': 'Глобални стилове',
|
|
74
|
+
'vars.title': 'Променливи',
|
|
75
|
+
'vars.fromCode': '{count} от вашия код',
|
|
76
|
+
'vars.declaredNote': 'Декларирани от вашето приложение — платното винаги показва токена, никога стойност.',
|
|
77
|
+
'vars.filterPlaceholder': 'Филтриране на променливи…',
|
|
78
|
+
'vars.insertHint': 'Кликнете, за да вмъкнете в избрания текст, заглавие или бутон',
|
|
79
|
+
'vars.insert': 'вмъкване',
|
|
80
|
+
'vars.noMatch': 'Няма променлива, отговаряща на този филтър.',
|
|
81
|
+
'vars.noneDeclared': 'Вашето приложение все още не е декларирало променливи.',
|
|
82
|
+
'vars.rteHint': 'При редактиране на текст можете да изберете променлива и директно от падащото меню на вградената лента с инструменти.',
|
|
83
|
+
'modal.fileManager': 'Файлов мениджър',
|
|
84
|
+
'modal.assetsTitle': 'Ресурси',
|
|
85
|
+
'modal.replaceImage': 'Замяна на изображение',
|
|
86
|
+
'library.searchPlaceholder': 'Търсене на файлове…',
|
|
87
|
+
'action.upload': 'Качване',
|
|
88
|
+
'library.clickToPlace': 'Кликнете върху файл, за да го поставите · или плъзнете файл върху платното',
|
|
89
|
+
'library.clickToReplace': 'Кликнете върху файл, за да замените избраното изображение',
|
|
90
|
+
'library.storageLabel': '{count} файла · {size}',
|
|
91
|
+
'library.folders': 'Папки',
|
|
92
|
+
'library.dropHint': 'Пуснете файлове с изображения тук за качване — или използвайте бутона Качване.',
|
|
93
|
+
'library.deleteFileHint': 'Изтриване на файл',
|
|
94
|
+
'library.del': 'ИЗТР',
|
|
95
|
+
'toast.assetDeleted': '{name} изтрит',
|
|
96
|
+
'modal.export': 'Изнасяне',
|
|
97
|
+
'modal.exportTitle': 'HTML на имейл, базиран на таблици',
|
|
98
|
+
'export.copy': 'Копиране',
|
|
99
|
+
'export.copied': 'Копирано',
|
|
100
|
+
'action.download': 'Изтегляне',
|
|
101
|
+
'export.meta': '{kb} KB · таблици + вградени стилове · ширини, безопасни за Outlook',
|
|
102
|
+
'modal.rawHtmlTitle': 'Суров HTML, преглед на живо',
|
|
103
|
+
'code.statusEdited': 'Изглед на кода — редактиран — преглед на живо, все още не е приложен',
|
|
104
|
+
'code.statusSynced': 'Изглед на кода — синхронизиран с платното',
|
|
105
|
+
'action.reload': 'Презареждане',
|
|
106
|
+
'action.reloadHint': 'Презареждане на изходния код от платното',
|
|
107
|
+
'action.applyToCanvas': 'Приложи върху платното',
|
|
108
|
+
'action.applyToCanvasHint': 'Анализиране на изходния код обратно в редактируеми редове',
|
|
109
|
+
'action.closeWithoutApplyingHint': 'Затваряне без прилагане',
|
|
110
|
+
'code.source': 'Изходен код',
|
|
111
|
+
'code.livePreview': 'Преглед на живо',
|
|
112
|
+
'code.liveHtmlPreviewTitle': 'Преглед на HTML на живо',
|
|
113
|
+
'code.applyNote':
|
|
114
|
+
'Прилагането анализира всеки ред на таблица от най-високо ниво в ред на платното, който все още можете да избирате, преподреждате и изтривате — ръчно написаната маркировка преминава непроменена.',
|
|
115
|
+
'code.meta': '{kb} KB · {lines} реда',
|
|
116
|
+
'toast.sourceReloaded': 'Изходният код е презареден от платното',
|
|
117
|
+
'toast.sourceAppliedOne': 'Изходният код е приложен — 1 ред на платното',
|
|
118
|
+
'toast.sourceAppliedMany': 'Изходният код е приложен — {rows} реда на платното',
|
|
119
|
+
'toast.parseError': 'Този HTML не можа да бъде анализиран',
|
|
120
|
+
'modal.copyAssist': 'Помощник за текст',
|
|
121
|
+
'modal.draftCopyTitle': 'Напишете текст за тази кампания',
|
|
122
|
+
'ai.goal': 'Цел',
|
|
123
|
+
'ai.tone': 'Тон',
|
|
124
|
+
'ai.briefLabel': 'За какво е този имейл?',
|
|
125
|
+
'ai.briefPlaceholder': 'напр. Есенна колекция, 15% за абонати, безплатна доставка над 150 лв',
|
|
126
|
+
'ai.generate': 'Генериране на текст',
|
|
127
|
+
'ai.writing': 'Пишем…',
|
|
128
|
+
'ai.kindHeadline': 'Заглавие',
|
|
129
|
+
'ai.kindBody': 'Основен текст',
|
|
130
|
+
'ai.kindButton': 'Текст на бутона',
|
|
131
|
+
'ai.actionInsertHeading': 'Вмъкни като заглавие',
|
|
132
|
+
'ai.actionInsertText': 'Вмъкни като текстов блок',
|
|
133
|
+
'ai.actionInsertButton': 'Вмъкни като бутон',
|
|
134
|
+
'toast.headingAdded': 'Заглавието е добавено',
|
|
135
|
+
'toast.textBlockAdded': 'Текстовият блок е добавен',
|
|
136
|
+
'toast.buttonAdded': 'Бутонът е добавен',
|
|
137
|
+
'preview.kickerMobile': 'Преглед — телефон 375px',
|
|
138
|
+
'preview.kickerDark': 'Преглед — тъмна пощенска кутия',
|
|
139
|
+
'preview.kickerDesktop': 'Преглед — компютър {width}px',
|
|
140
|
+
'action.close': 'Затваряне',
|
|
141
|
+
'toast.duplicated': 'Дублирано',
|
|
142
|
+
'toast.fileUploadedOne': 'Качен е 1 файл',
|
|
143
|
+
'toast.fileUploadedMany': 'Качени са {count} файла',
|
|
144
|
+
'toast.imageReplaced': 'Изображението е заменено',
|
|
145
|
+
'toast.imageAdded': 'Изображението е добавено към платното',
|
|
146
|
+
'toast.htmlCopied': 'HTML е копиран в клипборда',
|
|
147
|
+
'toast.templateLoaded': '{name} е зареден',
|
|
148
|
+
'toast.snippetDefaultLabel': 'Откъс',
|
|
149
|
+
'toast.snippetInserted': '{name} е вмъкнат',
|
|
150
|
+
'toast.snippetCopied': '{name} е копиран — изберете текстов блок, за да го вмъкнете',
|
|
151
|
+
};
|
|
152
|
+
|