@kontakto/email-template-editor 2.4.0 → 2.6.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/dist/index.cjs CHANGED
@@ -7,8 +7,11 @@ var zod = require('zod');
7
7
  var server = require('react-dom/server');
8
8
  var Handlebars = require('handlebars');
9
9
  var styles = require('@mui/material/styles');
10
+ var react = require('@lingui/react');
10
11
  var material = require('@mui/material');
12
+ var core = require('@lingui/core');
11
13
  var zustand = require('zustand');
14
+ var zundo = require('zundo');
12
15
  var iconsMaterial = require('@mui/icons-material');
13
16
  var reactColorful = require('react-colorful');
14
17
  var hljs = require('highlight.js');
@@ -2011,6 +2014,37 @@ var THEME = styles.createTheme(BASE_THEME, {
2011
2014
  ]
2012
2015
  });
2013
2016
  var theme_default = THEME;
2017
+
2018
+ // src/locales/en/messages.ts
2019
+ var messages = JSON.parse('{"drawer.library":["Library"],"drawer.new-sample":["New sample"],"drawer.new-template":["New template"],"drawer.no-samples":["No samples available"],"drawer.no-samples-match":["No samples match your filters"],"drawer.no-templates":["No saved templates yet"],"drawer.no-templates-match":["No templates match your filters"],"drawer.search-samples":["Search samples"],"drawer.search-templates":["Search templates"],"drawer.sort-by":["Sort by"],"drawer.tab.outline":["Outline"],"drawer.tab.samples":["Samples"],"drawer.tab.templates":["Templates"],"drawer.tag.all":["All"],"panel.avatar-block":["Avatar block"],"panel.button-block":["Button block"],"panel.columns-block":["Columns block"],"panel.container-block":["Container block"],"panel.divider-block":["Divider block"],"panel.editor-appearance":["Editor appearance"],"panel.export":["Export"],"panel.global":["Global"],"panel.heading-block":["Heading block"],"panel.html-block":["HTML block"],"panel.image-block":["Image block"],"panel.signature-block":["Signature block"],"panel.spacer-block":["Spacer block"],"panel.template":["Template"],"panel.text-block":["Text block"],"savebar.error-saving":["Error saving"],"savebar.new":["New"],"savebar.new-template-created":["New template created"],"savebar.sample-prefix":["Sample"],"savebar.sample-saved":["Sample saved"],"savebar.save":["Save"],"savebar.save-as":["Save as\u2026"],"savebar.save-as-new":["Save as new\u2026"],"savebar.save-as-new-template":["Save as a new template"],"savebar.save-changes":["Save changes"],"savebar.save-changes-to-sample":["Save changes to this sample"],"savebar.start-fresh":["Start a fresh template"],"savebar.template-saved":["Template saved"],"sort.last-updated":["Last updated"],"sort.name":["Name (A\u2013Z)"],"sort.recently-created":["Recently created"],"style.alignment":["Alignment"],"style.background-color":["Background color"],"style.border-color":["Border color"],"style.border-radius":["Border radius"],"style.font-family":["Font family"],"style.font-size":["Font size"],"style.font-weight":["Font weight"],"style.letter-spacing":["Letter spacing"],"style.line-height":["Line height"],"style.padding":["Padding"],"style.text-color":["Text color"],"toolbar.bold":["Bold"],"toolbar.bold-shortcut":["Bold (Cmd+B)"],"toolbar.italic":["Italic"],"toolbar.italic-shortcut":["Italic (Cmd+I)"],"toolbar.link":["Link"],"toolbar.link-shortcut":["Link (Cmd+K)"],"tune.copy":["Copy block"],"tune.delete":["Delete"],"tune.move-down":["Move down"],"tune.move-up":["Move up"]}');
2020
+
2021
+ // src/locales/fi/messages.ts
2022
+ var messages2 = JSON.parse('{"drawer.library":["Kirjasto"],"drawer.new-sample":["Uusi malli"],"drawer.new-template":["Uusi pohja"],"drawer.no-samples":["Ei malleja saatavilla"],"drawer.no-samples-match":["Mik\xE4\xE4n malli ei vastaa hakua"],"drawer.no-templates":["Ei tallennettuja pohjia"],"drawer.no-templates-match":["Mik\xE4\xE4n pohja ei vastaa hakua"],"drawer.search-samples":["Etsi malleja"],"drawer.search-templates":["Etsi pohjia"],"drawer.sort-by":["J\xE4rjestys"],"drawer.tab.outline":["Rakenne"],"drawer.tab.samples":["Mallit"],"drawer.tab.templates":["Pohjat"],"drawer.tag.all":["Kaikki"],"panel.avatar-block":["Avatarlohko"],"panel.button-block":["Painikelohko"],"panel.columns-block":["Sarakkeet"],"panel.container-block":["S\xE4ili\xF6lohko"],"panel.divider-block":["Erotin"],"panel.editor-appearance":["Editorin ulkoasu"],"panel.export":["Vienti"],"panel.global":["Yleiset"],"panel.heading-block":["Otsikkolohko"],"panel.html-block":["HTML-lohko"],"panel.image-block":["Kuvalohko"],"panel.signature-block":["Allekirjoituslohko"],"panel.spacer-block":["V\xE4lilohko"],"panel.template":["Pohja"],"panel.text-block":["Tekstilohko"],"savebar.error-saving":["Tallennus ep\xE4onnistui"],"savebar.new":["Uusi"],"savebar.new-template-created":["Uusi pohja luotu"],"savebar.sample-prefix":["Malli"],"savebar.sample-saved":["Malli tallennettu"],"savebar.save":["Tallenna"],"savebar.save-as":["Tallenna nimell\xE4\u2026"],"savebar.save-as-new":["Tallenna uutena\u2026"],"savebar.save-as-new-template":["Tallenna uutena pohjana"],"savebar.save-changes":["Tallenna muutokset"],"savebar.save-changes-to-sample":["Tallenna muutokset t\xE4h\xE4n malliin"],"savebar.start-fresh":["Aloita tyhj\xE4st\xE4 pohjasta"],"savebar.template-saved":["Pohja tallennettu"],"sort.last-updated":["Viimeksi p\xE4ivitetty"],"sort.name":["Nimi (A\u2013\xD6)"],"sort.recently-created":["\xC4skett\xE4in luotu"],"style.alignment":["Tasaus"],"style.background-color":["Taustav\xE4ri"],"style.border-color":["Reunan v\xE4ri"],"style.border-radius":["Reunan py\xF6ristys"],"style.font-family":["Fontti"],"style.font-size":["Fonttikoko"],"style.font-weight":["Fontin paksuus"],"style.letter-spacing":["Kirjainv\xE4li"],"style.line-height":["Rivikorkeus"],"style.padding":["T\xE4yte"],"style.text-color":["Tekstin v\xE4ri"],"toolbar.bold":["Lihavointi"],"toolbar.bold-shortcut":["Lihavointi (Cmd+B)"],"toolbar.italic":["Kursivointi"],"toolbar.italic-shortcut":["Kursivointi (Cmd+I)"],"toolbar.link":["Linkki"],"toolbar.link-shortcut":["Linkki (Cmd+K)"],"tune.copy":["Kopioi lohko"],"tune.delete":["Poista"],"tune.move-down":["Siirr\xE4 alas"],"tune.move-up":["Siirr\xE4 yl\xF6s"]}');
2023
+
2024
+ // src/locales/sv/messages.ts
2025
+ var messages3 = JSON.parse('{"drawer.library":["Bibliotek"],"drawer.new-sample":["Ny mall"],"drawer.new-template":["Ny e-postmall"],"drawer.no-samples":["Inga exempel tillg\xE4ngliga"],"drawer.no-samples-match":["Inga exempel matchar dina filter"],"drawer.no-templates":["Inga sparade mallar \xE4nnu"],"drawer.no-templates-match":["Inga mallar matchar dina filter"],"drawer.search-samples":["S\xF6k exempel"],"drawer.search-templates":["S\xF6k mallar"],"drawer.sort-by":["Sortera efter"],"drawer.tab.outline":["Struktur"],"drawer.tab.samples":["Exempel"],"drawer.tab.templates":["Mallar"],"drawer.tag.all":["Alla"],"panel.avatar-block":["Avatarblock"],"panel.button-block":["Knappblock"],"panel.columns-block":["Kolumner"],"panel.container-block":["Beh\xE5llarblock"],"panel.divider-block":["Avgr\xE4nsare"],"panel.editor-appearance":["Editorns utseende"],"panel.export":["Exportera"],"panel.global":["Allm\xE4nt"],"panel.heading-block":["Rubrikblock"],"panel.html-block":["HTML-block"],"panel.image-block":["Bildblock"],"panel.signature-block":["Signaturblock"],"panel.spacer-block":["Mellanrumsblock"],"panel.template":["Mall"],"panel.text-block":["Textblock"],"savebar.error-saving":["Fel vid sparande"],"savebar.new":["Ny"],"savebar.new-template-created":["Ny mall skapad"],"savebar.sample-prefix":["Exempel"],"savebar.sample-saved":["Exempel sparat"],"savebar.save":["Spara"],"savebar.save-as":["Spara som\u2026"],"savebar.save-as-new":["Spara som ny\u2026"],"savebar.save-as-new-template":["Spara som en ny mall"],"savebar.save-changes":["Spara \xE4ndringar"],"savebar.save-changes-to-sample":["Spara \xE4ndringar till detta exempel"],"savebar.start-fresh":["Starta en ny mall"],"savebar.template-saved":["Mall sparad"],"sort.last-updated":["Senast uppdaterad"],"sort.name":["Namn (A\u2013\xD6)"],"sort.recently-created":["Nyligen skapad"],"style.alignment":["Justering"],"style.background-color":["Bakgrundsf\xE4rg"],"style.border-color":["Kantf\xE4rg"],"style.border-radius":["Kantradie"],"style.font-family":["Typsnitt"],"style.font-size":["Typsnittsstorlek"],"style.font-weight":["Typsnittsvikt"],"style.letter-spacing":["Teckenavst\xE5nd"],"style.line-height":["Radh\xF6jd"],"style.padding":["Utrymme"],"style.text-color":["Textf\xE4rg"],"toolbar.bold":["Fet"],"toolbar.bold-shortcut":["Fet (Cmd+B)"],"toolbar.italic":["Kursiv"],"toolbar.italic-shortcut":["Kursiv (Cmd+I)"],"toolbar.link":["L\xE4nk"],"toolbar.link-shortcut":["L\xE4nk (Cmd+K)"],"tune.copy":["Kopiera block"],"tune.delete":["Ta bort"],"tune.move-down":["Flytta ner"],"tune.move-up":["Flytta upp"]}');
2026
+ var SUPPORTED_LOCALES = ["en", "sv", "fi"];
2027
+ var CATALOGS = {
2028
+ en: messages,
2029
+ sv: messages3,
2030
+ fi: messages2
2031
+ };
2032
+ var loaded = false;
2033
+ function activateLocale(locale) {
2034
+ const resolved = SUPPORTED_LOCALES.includes(locale != null ? locale : "") ? locale : "en";
2035
+ if (locale && resolved !== locale) {
2036
+ console.warn(`[email-template-editor] Unsupported locale "${locale}", falling back to "en".`);
2037
+ }
2038
+ if (!loaded) {
2039
+ core.i18n.load({ en: CATALOGS.en, sv: CATALOGS.sv, fi: CATALOGS.fi });
2040
+ loaded = true;
2041
+ }
2042
+ core.i18n.activate(resolved);
2043
+ return resolved;
2044
+ }
2045
+ function t(id, fallback) {
2046
+ return core.i18n._(id, {}, { message: fallback != null ? fallback : id });
2047
+ }
2014
2048
  var EMPTY_DOCUMENT = {
2015
2049
  root: {
2016
2050
  type: "EmailLayout",
@@ -2023,20 +2057,48 @@ var EMPTY_DOCUMENT = {
2023
2057
  }
2024
2058
  }
2025
2059
  };
2026
- var editorStateStore = zustand.create(() => ({
2027
- document: EMPTY_DOCUMENT,
2028
- selectedBlockId: null,
2029
- selectedSidebarTab: "styles",
2030
- selectedMainTab: "editor",
2031
- selectedScreenSize: "desktop",
2032
- inspectorDrawerOpen: true,
2033
- samplesDrawerOpen: true,
2034
- persistenceEnabled: false,
2035
- lastFocusedEditable: null,
2036
- hoveredBlockId: null,
2037
- draggingBlock: null,
2038
- workspaceBackground: "checkerboard"
2039
- }));
2060
+ var COALESCE_MS = 300;
2061
+ function leadingThrottle(fn, wait) {
2062
+ let last = Number.NEGATIVE_INFINITY;
2063
+ return (...args) => {
2064
+ const now = Date.now();
2065
+ if (now - last >= wait) {
2066
+ last = now;
2067
+ fn(...args);
2068
+ }
2069
+ };
2070
+ }
2071
+ var editorStateStore = zustand.create()(
2072
+ zundo.temporal(
2073
+ () => ({
2074
+ document: EMPTY_DOCUMENT,
2075
+ selectedBlockId: null,
2076
+ selectedSidebarTab: "styles",
2077
+ selectedMainTab: "editor",
2078
+ selectedScreenSize: "desktop",
2079
+ inspectorDrawerOpen: true,
2080
+ samplesDrawerOpen: true,
2081
+ persistenceEnabled: false,
2082
+ lastFocusedEditable: null,
2083
+ hoveredBlockId: null,
2084
+ draggingBlock: null,
2085
+ workspaceBackground: "checkerboard"
2086
+ }),
2087
+ {
2088
+ limit: 100,
2089
+ // Only the document participates in history — selection, drawers, tabs
2090
+ // and other UI state are intentionally excluded.
2091
+ partialize: (state) => ({ document: state.document }),
2092
+ // Skip UI-only state changes: if the document reference is unchanged,
2093
+ // no history entry is recorded.
2094
+ equality: (a, b) => a.document === b.document,
2095
+ handleSet: (handleSet) => leadingThrottle(
2096
+ (pastState, replace, currentState) => handleSet(pastState, replace, currentState),
2097
+ COALESCE_MS
2098
+ )
2099
+ }
2100
+ )
2101
+ );
2040
2102
  function useDocument() {
2041
2103
  return editorStateStore((s) => s.document);
2042
2104
  }
@@ -2079,11 +2141,15 @@ function setSidebarTab(selectedSidebarTab) {
2079
2141
  return editorStateStore.setState({ selectedSidebarTab });
2080
2142
  }
2081
2143
  function resetDocument(document2) {
2082
- return editorStateStore.setState({
2144
+ const temporalApi = editorStateStore.temporal.getState();
2145
+ temporalApi.pause();
2146
+ editorStateStore.setState({
2083
2147
  document: document2,
2084
2148
  selectedSidebarTab: "styles",
2085
2149
  selectedBlockId: null
2086
2150
  });
2151
+ temporalApi.clear();
2152
+ temporalApi.resume();
2087
2153
  }
2088
2154
  function getDocument() {
2089
2155
  return editorStateStore.getState().document;
@@ -2094,6 +2160,9 @@ function setDocument(document2) {
2094
2160
  document: __spreadValues(__spreadValues({}, originalDocument), document2)
2095
2161
  });
2096
2162
  }
2163
+ function replaceDocument(document2) {
2164
+ editorStateStore.setState({ document: document2 });
2165
+ }
2097
2166
  function toggleInspectorDrawerOpen() {
2098
2167
  const inspectorDrawerOpen = !editorStateStore.getState().inspectorDrawerOpen;
2099
2168
  return editorStateStore.setState({ inspectorDrawerOpen });
@@ -2138,6 +2207,18 @@ function setWorkspaceBackground(workspaceBackground) {
2138
2207
  function setLastFocusedEditable(lastFocusedEditable) {
2139
2208
  return editorStateStore.setState({ lastFocusedEditable });
2140
2209
  }
2210
+ function undo() {
2211
+ editorStateStore.temporal.getState().undo();
2212
+ }
2213
+ function redo() {
2214
+ editorStateStore.temporal.getState().redo();
2215
+ }
2216
+ function useCanUndo() {
2217
+ return zustand.useStore(editorStateStore.temporal, (s) => s.pastStates.length > 0);
2218
+ }
2219
+ function useCanRedo() {
2220
+ return zustand.useStore(editorStateStore.temporal, (s) => s.futureStates.length > 0);
2221
+ }
2141
2222
 
2142
2223
  // src/app/save-payload.ts
2143
2224
  var ROOT_BLOCK_ID = "root";
@@ -2247,8 +2328,27 @@ function ImageCallbacksProvider({
2247
2328
  function useImageCallbacks() {
2248
2329
  return React57.useContext(ImageCallbacksContext);
2249
2330
  }
2331
+ var TITLE_KEYS = {
2332
+ "Editor appearance": "panel.editor-appearance",
2333
+ Template: "panel.template",
2334
+ Export: "panel.export",
2335
+ Global: "panel.global",
2336
+ "Text block": "panel.text-block",
2337
+ "Heading block": "panel.heading-block",
2338
+ "Button block": "panel.button-block",
2339
+ "Image block": "panel.image-block",
2340
+ "Avatar block": "panel.avatar-block",
2341
+ "Signature block": "panel.signature-block",
2342
+ "Spacer block": "panel.spacer-block",
2343
+ "Divider block": "panel.divider-block",
2344
+ "Columns block": "panel.columns-block",
2345
+ "Container block": "panel.container-block",
2346
+ "Html block": "panel.html-block"
2347
+ };
2250
2348
  function BaseSidebarPanel({ title, children }) {
2251
- return /* @__PURE__ */ React57__default.default.createElement(material.Box, { p: 2 }, /* @__PURE__ */ React57__default.default.createElement(material.Typography, { variant: "overline", color: "text.secondary", sx: { display: "block", mb: 2 } }, title), /* @__PURE__ */ React57__default.default.createElement(material.Stack, { spacing: 5, mb: 3 }, children));
2349
+ const key = TITLE_KEYS[title];
2350
+ const displayTitle = key ? t(key, title) : title;
2351
+ return /* @__PURE__ */ React57__default.default.createElement(material.Box, { p: 2 }, /* @__PURE__ */ React57__default.default.createElement(material.Typography, { variant: "overline", color: "text.secondary", sx: { display: "block", mb: 2 } }, displayTitle), /* @__PURE__ */ React57__default.default.createElement(material.Stack, { spacing: 5, mb: 3 }, children));
2252
2352
  }
2253
2353
  function RadioGroupInput({ label, children, defaultValue, onChange }) {
2254
2354
  const [value, setValue] = React57.useState(defaultValue);
@@ -2752,9 +2852,9 @@ function SingleStylePropertyPanel({ name, value, onChange }) {
2752
2852
  };
2753
2853
  switch (name) {
2754
2854
  case "backgroundColor":
2755
- return /* @__PURE__ */ React57__default.default.createElement(NullableColorInput, { label: "Background color", defaultValue, onChange: handleChange });
2855
+ return /* @__PURE__ */ React57__default.default.createElement(NullableColorInput, { label: t("style.background-color", "Background color"), defaultValue, onChange: handleChange });
2756
2856
  case "borderColor":
2757
- return /* @__PURE__ */ React57__default.default.createElement(NullableColorInput, { label: "Border color", defaultValue, onChange: handleChange });
2857
+ return /* @__PURE__ */ React57__default.default.createElement(NullableColorInput, { label: t("style.border-color", "Border color"), defaultValue, onChange: handleChange });
2758
2858
  case "borderRadius":
2759
2859
  return /* @__PURE__ */ React57__default.default.createElement(
2760
2860
  SliderInput,
@@ -2765,27 +2865,27 @@ function SingleStylePropertyPanel({ name, value, onChange }) {
2765
2865
  marks: true,
2766
2866
  min: 0,
2767
2867
  max: 48,
2768
- label: "Border radius",
2868
+ label: t("style.border-radius", "Border radius"),
2769
2869
  defaultValue,
2770
2870
  onChange: handleChange
2771
2871
  }
2772
2872
  );
2773
2873
  case "color":
2774
- return /* @__PURE__ */ React57__default.default.createElement(NullableColorInput, { label: "Text color", defaultValue, onChange: handleChange });
2874
+ return /* @__PURE__ */ React57__default.default.createElement(NullableColorInput, { label: t("style.text-color", "Text color"), defaultValue, onChange: handleChange });
2775
2875
  case "fontFamily":
2776
- return /* @__PURE__ */ React57__default.default.createElement(NullableFontFamily, { label: "Font family", defaultValue, onChange: handleChange });
2876
+ return /* @__PURE__ */ React57__default.default.createElement(NullableFontFamily, { label: t("style.font-family", "Font family"), defaultValue, onChange: handleChange });
2777
2877
  case "fontSize":
2778
- return /* @__PURE__ */ React57__default.default.createElement(FontSizeInput, { label: "Font size", defaultValue, onChange: handleChange });
2878
+ return /* @__PURE__ */ React57__default.default.createElement(FontSizeInput, { label: t("style.font-size", "Font size"), defaultValue, onChange: handleChange });
2779
2879
  case "fontWeight":
2780
- return /* @__PURE__ */ React57__default.default.createElement(FontWeightInput, { label: "Font weight", defaultValue, onChange: handleChange });
2880
+ return /* @__PURE__ */ React57__default.default.createElement(FontWeightInput, { label: t("style.font-weight", "Font weight"), defaultValue, onChange: handleChange });
2781
2881
  case "lineHeight":
2782
- return /* @__PURE__ */ React57__default.default.createElement(LineHeightInput, { label: "Line height", defaultValue, onChange: handleChange });
2882
+ return /* @__PURE__ */ React57__default.default.createElement(LineHeightInput, { label: t("style.line-height", "Line height"), defaultValue, onChange: handleChange });
2783
2883
  case "letterSpacing":
2784
- return /* @__PURE__ */ React57__default.default.createElement(LetterSpacingInput, { label: "Letter spacing", defaultValue, onChange: handleChange });
2884
+ return /* @__PURE__ */ React57__default.default.createElement(LetterSpacingInput, { label: t("style.letter-spacing", "Letter spacing"), defaultValue, onChange: handleChange });
2785
2885
  case "textAlign":
2786
- return /* @__PURE__ */ React57__default.default.createElement(TextAlignInput, { label: "Alignment", defaultValue, onChange: handleChange });
2886
+ return /* @__PURE__ */ React57__default.default.createElement(TextAlignInput, { label: t("style.alignment", "Alignment"), defaultValue, onChange: handleChange });
2787
2887
  case "padding":
2788
- return /* @__PURE__ */ React57__default.default.createElement(PaddingInput, { label: "Padding", defaultValue, onChange: handleChange });
2888
+ return /* @__PURE__ */ React57__default.default.createElement(PaddingInput, { label: t("style.padding", "Padding"), defaultValue, onChange: handleChange });
2789
2889
  }
2790
2890
  }
2791
2891
 
@@ -4092,15 +4192,15 @@ function DetailsEditor({ templateId, currentName, currentTags, canEdit, onSave }
4092
4192
  const addTag = () => {
4093
4193
  const trimmed = tagInput.trim();
4094
4194
  if (!trimmed) return;
4095
- if (tags.some((t) => t.toLowerCase() === trimmed.toLowerCase())) {
4195
+ if (tags.some((t2) => t2.toLowerCase() === trimmed.toLowerCase())) {
4096
4196
  setTagInput("");
4097
4197
  return;
4098
4198
  }
4099
4199
  setTags([...tags, trimmed]);
4100
4200
  setTagInput("");
4101
4201
  };
4102
- const removeTag = (tag) => setTags(tags.filter((t) => t !== tag));
4103
- const dirty = name !== currentName || tags.length !== currentTags.length || tags.some((t, i) => t !== currentTags[i]);
4202
+ const removeTag = (tag) => setTags(tags.filter((t2) => t2 !== tag));
4203
+ const dirty = name !== currentName || tags.length !== currentTags.length || tags.some((t2, i) => t2 !== currentTags[i]);
4104
4204
  const handleSave = () => __async(null, null, function* () {
4105
4205
  if (!canEdit || !dirty || !name.trim()) return;
4106
4206
  setSaving(true);
@@ -5528,15 +5628,15 @@ function RenameDialog({
5528
5628
  const addTag = () => {
5529
5629
  const trimmed = tagInput.trim();
5530
5630
  if (!trimmed) return;
5531
- if (tags.some((t) => t.toLowerCase() === trimmed.toLowerCase())) {
5631
+ if (tags.some((t2) => t2.toLowerCase() === trimmed.toLowerCase())) {
5532
5632
  setTagInput("");
5533
5633
  return;
5534
5634
  }
5535
5635
  setTags([...tags, trimmed]);
5536
5636
  setTagInput("");
5537
5637
  };
5538
- const removeTag = (tag) => setTags(tags.filter((t) => t !== tag));
5539
- const tagsUnchanged = tags.length === currentTags.length && tags.every((t, i) => t === currentTags[i]);
5638
+ const removeTag = (tag) => setTags(tags.filter((t2) => t2 !== tag));
5639
+ const tagsUnchanged = tags.length === currentTags.length && tags.every((t2, i) => t2 === currentTags[i]);
5540
5640
  const handleSubmit = () => __async(null, null, function* () {
5541
5641
  const trimmedSlug = slug.trim();
5542
5642
  if (!trimmedSlug) {
@@ -5760,9 +5860,9 @@ var EMPTY_TEMPLATE = {
5760
5860
  description: "A blank email template to start from scratch"
5761
5861
  };
5762
5862
  var SORT_OPTIONS = [
5763
- { value: "updatedAt", label: "Last updated" },
5764
- { value: "createdAt", label: "Recently created" },
5765
- { value: "slug", label: "Name (A\u2013Z)" }
5863
+ { value: "updatedAt", labelKey: "sort.last-updated", fallback: "Last updated" },
5864
+ { value: "createdAt", labelKey: "sort.recently-created", fallback: "Recently created" },
5865
+ { value: "slug", labelKey: "sort.name", fallback: "Name (A\u2013Z)" }
5766
5866
  ];
5767
5867
  function compareTemplates(a, b, key) {
5768
5868
  if (key === "slug") {
@@ -5846,9 +5946,9 @@ function SamplesDrawer({
5846
5946
  const now = (/* @__PURE__ */ new Date()).toISOString();
5847
5947
  const row = { id, slug, kind, tags, createdAt: now, updatedAt: now };
5848
5948
  if (kind === "sample") {
5849
- setSamples((prev) => prev.some((t) => t.id === id) ? prev : [...prev, row]);
5949
+ setSamples((prev) => prev.some((t2) => t2.id === id) ? prev : [...prev, row]);
5850
5950
  } else {
5851
- setTemplates((prev) => prev.some((t) => t.id === id) ? prev : [...prev, row]);
5951
+ setTemplates((prev) => prev.some((t2) => t2.id === id) ? prev : [...prev, row]);
5852
5952
  }
5853
5953
  };
5854
5954
  React57.useEffect(() => {
@@ -5866,30 +5966,30 @@ function SamplesDrawer({
5866
5966
  const { templateRows, sampleRows } = React57.useMemo(() => {
5867
5967
  const byId = /* @__PURE__ */ new Map();
5868
5968
  for (const s of samples) byId.set(s.id, s);
5869
- for (const t of templates) byId.set(t.id, t);
5969
+ for (const t2 of templates) byId.set(t2.id, t2);
5870
5970
  const all = Array.from(byId.values());
5871
5971
  return {
5872
- templateRows: all.filter((t) => t.kind === "template"),
5873
- sampleRows: all.filter((t) => t.kind === "sample")
5972
+ templateRows: all.filter((t2) => t2.kind === "template"),
5973
+ sampleRows: all.filter((t2) => t2.kind === "sample")
5874
5974
  };
5875
5975
  }, [samples, templates]);
5876
5976
  const allTags = React57.useMemo(() => {
5877
5977
  var _a2, _b2;
5878
5978
  const set = /* @__PURE__ */ new Set();
5879
- for (const t of templateRows) (_a2 = t.tags) == null ? void 0 : _a2.forEach((tag) => set.add(tag));
5979
+ for (const t2 of templateRows) (_a2 = t2.tags) == null ? void 0 : _a2.forEach((tag) => set.add(tag));
5880
5980
  for (const s of sampleRows) (_b2 = s.tags) == null ? void 0 : _b2.forEach((tag) => set.add(tag));
5881
5981
  return Array.from(set).sort();
5882
5982
  }, [templateRows, sampleRows]);
5883
5983
  const matchesSearchAndTags = React57.useMemo(() => {
5884
5984
  const term = search.trim().toLowerCase();
5885
- return (t) => {
5985
+ return (t2) => {
5886
5986
  if (term) {
5887
- const haystack = [t.slug, t.description, t.subject].filter(Boolean).join(" ").toLowerCase();
5987
+ const haystack = [t2.slug, t2.description, t2.subject].filter(Boolean).join(" ").toLowerCase();
5888
5988
  if (!haystack.includes(term)) return false;
5889
5989
  }
5890
5990
  if (activeTags.length > 0) {
5891
- if (!t.tags || t.tags.length === 0) return false;
5892
- if (!activeTags.every((tag) => t.tags.includes(tag))) return false;
5991
+ if (!t2.tags || t2.tags.length === 0) return false;
5992
+ if (!activeTags.every((tag) => t2.tags.includes(tag))) return false;
5893
5993
  }
5894
5994
  return true;
5895
5995
  };
@@ -5903,7 +6003,7 @@ function SamplesDrawer({
5903
6003
  [sampleRows, matchesSearchAndTags, sortKey]
5904
6004
  );
5905
6005
  const toggleTag = (tag) => {
5906
- setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]);
6006
+ setActiveTags((prev) => prev.includes(tag) ? prev.filter((t2) => t2 !== tag) : [...prev, tag]);
5907
6007
  };
5908
6008
  const handleDuplicate = (template) => {
5909
6009
  if (!copyTemplate) return;
@@ -5933,7 +6033,7 @@ function SamplesDrawer({
5933
6033
  if (!renameTarget || !renameTemplate) return;
5934
6034
  yield renameTemplate(renameTarget.id, newSlug, opts);
5935
6035
  const newTags = (_b2 = (_a2 = opts == null ? void 0 : opts.tags) != null ? _a2 : renameTarget.tags) != null ? _b2 : [];
5936
- const patch = (t) => t.id === renameTarget.id ? __spreadProps(__spreadValues({}, t), { slug: newSlug, tags: newTags }) : t;
6036
+ const patch = (t2) => t2.id === renameTarget.id ? __spreadProps(__spreadValues({}, t2), { slug: newSlug, tags: newTags }) : t2;
5937
6037
  setTemplates((prev) => prev.map(patch));
5938
6038
  setSamples((prev) => prev.map(patch));
5939
6039
  if (currentTemplateId === renameTarget.id) {
@@ -5942,8 +6042,8 @@ function SamplesDrawer({
5942
6042
  showMessage("Details saved");
5943
6043
  });
5944
6044
  const flipKindLocally = (id, kind) => {
5945
- setTemplates((prev) => prev.map((t) => t.id === id ? __spreadProps(__spreadValues({}, t), { kind }) : t));
5946
- setSamples((prev) => prev.map((t) => t.id === id ? __spreadProps(__spreadValues({}, t), { kind }) : t));
6045
+ setTemplates((prev) => prev.map((t2) => t2.id === id ? __spreadProps(__spreadValues({}, t2), { kind }) : t2));
6046
+ setSamples((prev) => prev.map((t2) => t2.id === id ? __spreadProps(__spreadValues({}, t2), { kind }) : t2));
5947
6047
  };
5948
6048
  const handleSetKind = (template, kind) => {
5949
6049
  if (!setTemplateKind) return;
@@ -5980,7 +6080,7 @@ function SamplesDrawer({
5980
6080
  };
5981
6081
  const handleSaveAsSubmit = (templateName) => __async(null, null, function* () {
5982
6082
  if (!saveAs || !pendingSaveAs) return false;
5983
- const taken = templateRows.some((t) => t.slug.toLowerCase() === templateName.toLowerCase());
6083
+ const taken = templateRows.some((t2) => t2.slug.toLowerCase() === templateName.toLowerCase());
5984
6084
  if (taken) {
5985
6085
  setNewError("A template with this name already exists");
5986
6086
  return false;
@@ -6043,7 +6143,7 @@ function SamplesDrawer({
6043
6143
  if (!enabled) {
6044
6144
  return null;
6045
6145
  }
6046
- const existingSlugs = templates.map((t) => t.slug);
6146
+ const existingSlugs = templates.map((t2) => t2.slug);
6047
6147
  return /* @__PURE__ */ React57__default.default.createElement(React57__default.default.Fragment, null, /* @__PURE__ */ React57__default.default.createElement(
6048
6148
  material.Drawer,
6049
6149
  {
@@ -6069,12 +6169,12 @@ function SamplesDrawer({
6069
6169
  spacing: 1.5,
6070
6170
  sx: { overflowY: "auto" }
6071
6171
  },
6072
- /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", alignItems: "center", justifyContent: "space-between", sx: { pt: 1 } }, /* @__PURE__ */ React57__default.default.createElement(material.Typography, { variant: "h6", component: "h1" }, "Library"), saveAs && (activeLeftTab === "templates" || activeLeftTab === "samples") && /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: activeLeftTab === "samples" ? "New sample" : "New template" }, /* @__PURE__ */ React57__default.default.createElement(
6172
+ /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", alignItems: "center", justifyContent: "space-between", sx: { pt: 1 } }, /* @__PURE__ */ React57__default.default.createElement(material.Typography, { variant: "h6", component: "h1" }, /* @__PURE__ */ React57__default.default.createElement(react.Trans, { id: "drawer.library" }, "Library")), saveAs && (activeLeftTab === "templates" || activeLeftTab === "samples") && /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: activeLeftTab === "samples" ? t("drawer.new-sample", "New sample") : t("drawer.new-template", "New template") }, /* @__PURE__ */ React57__default.default.createElement(
6073
6173
  material.IconButton,
6074
6174
  {
6075
6175
  size: "small",
6076
6176
  onClick: () => openNewPicker(activeLeftTab === "samples" ? "sample" : "template"),
6077
- "aria-label": activeLeftTab === "samples" ? "New sample" : "New template"
6177
+ "aria-label": activeLeftTab === "samples" ? t("drawer.new-sample", "New sample") : t("drawer.new-template", "New template")
6078
6178
  },
6079
6179
  /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.AddOutlined, { fontSize: "small" })
6080
6180
  ))),
@@ -6086,15 +6186,15 @@ function SamplesDrawer({
6086
6186
  variant: "fullWidth",
6087
6187
  sx: { minHeight: 36, "& .MuiTab-root": { minHeight: 36, minWidth: 0, px: 1, fontSize: 13 } }
6088
6188
  },
6089
- /* @__PURE__ */ React57__default.default.createElement(material.Tab, { value: "templates", label: "Templates", disabled: !loadTemplates }),
6090
- /* @__PURE__ */ React57__default.default.createElement(material.Tab, { value: "samples", label: "Samples" }),
6091
- /* @__PURE__ */ React57__default.default.createElement(material.Tab, { value: "outline", label: "Outline" })
6189
+ /* @__PURE__ */ React57__default.default.createElement(material.Tab, { value: "templates", label: t("drawer.tab.templates", "Templates"), disabled: !loadTemplates }),
6190
+ /* @__PURE__ */ React57__default.default.createElement(material.Tab, { value: "samples", label: t("drawer.tab.samples", "Samples") }),
6191
+ /* @__PURE__ */ React57__default.default.createElement(material.Tab, { value: "outline", label: t("drawer.tab.outline", "Outline") })
6092
6192
  ),
6093
6193
  activeLeftTab === "outline" ? /* @__PURE__ */ React57__default.default.createElement(OutlinePanel, null) : /* @__PURE__ */ React57__default.default.createElement(React57__default.default.Fragment, null, /* @__PURE__ */ React57__default.default.createElement(
6094
6194
  material.TextField,
6095
6195
  {
6096
6196
  size: "small",
6097
- placeholder: activeLeftTab === "templates" ? "Search templates" : "Search samples",
6197
+ placeholder: activeLeftTab === "templates" ? t("drawer.search-templates", "Search templates") : t("drawer.search-samples", "Search samples"),
6098
6198
  value: search,
6099
6199
  onChange: (e) => setSearch(e.target.value),
6100
6200
  InputProps: {
@@ -6106,15 +6206,15 @@ function SamplesDrawer({
6106
6206
  {
6107
6207
  select: true,
6108
6208
  size: "small",
6109
- label: "Sort by",
6209
+ label: t("drawer.sort-by", "Sort by"),
6110
6210
  value: sortKey,
6111
6211
  onChange: (e) => setSortKey(e.target.value)
6112
6212
  },
6113
- SORT_OPTIONS.map((opt) => /* @__PURE__ */ React57__default.default.createElement(material.MenuItem, { key: opt.value, value: opt.value }, opt.label))
6213
+ SORT_OPTIONS.map((opt) => /* @__PURE__ */ React57__default.default.createElement(material.MenuItem, { key: opt.value, value: opt.value }, t(opt.labelKey, opt.fallback)))
6114
6214
  ), allTags.length > 0 && /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", sx: { flexWrap: "wrap", gap: 0.5 } }, /* @__PURE__ */ React57__default.default.createElement(
6115
6215
  material.Chip,
6116
6216
  {
6117
- label: "All",
6217
+ label: t("drawer.tag.all", "All"),
6118
6218
  size: "small",
6119
6219
  clickable: true,
6120
6220
  color: activeTags.length === 0 ? "primary" : "default",
@@ -6144,7 +6244,7 @@ function SamplesDrawer({
6144
6244
  onDelete: deleteTemplate ? () => handleDelete(template) : void 0,
6145
6245
  onPromote: setTemplateKind ? () => handleSetKind(template, "sample") : void 0
6146
6246
  }
6147
- ))) : /* @__PURE__ */ React57__default.default.createElement(material.Typography, { variant: "body2", sx: { color: "text.secondary", py: 1 } }, templateRows.length === 0 ? "No saved templates yet" : "No templates match your filters")) : /* @__PURE__ */ React57__default.default.createElement(material.Box, null, loadingSamples ? /* @__PURE__ */ React57__default.default.createElement(material.Stack, { alignItems: "center", width: "100%", py: 2 }, /* @__PURE__ */ React57__default.default.createElement(material.CircularProgress, { size: 24 })) : filteredSamples.length > 0 ? /* @__PURE__ */ React57__default.default.createElement(material.Stack, { spacing: 0.25, sx: { width: "100%" } }, filteredSamples.map((sample) => /* @__PURE__ */ React57__default.default.createElement(
6247
+ ))) : /* @__PURE__ */ React57__default.default.createElement(material.Typography, { variant: "body2", sx: { color: "text.secondary", py: 1 } }, templateRows.length === 0 ? t("drawer.no-templates", "No saved templates yet") : t("drawer.no-templates-match", "No templates match your filters"))) : /* @__PURE__ */ React57__default.default.createElement(material.Box, null, loadingSamples ? /* @__PURE__ */ React57__default.default.createElement(material.Stack, { alignItems: "center", width: "100%", py: 2 }, /* @__PURE__ */ React57__default.default.createElement(material.CircularProgress, { size: 24 })) : filteredSamples.length > 0 ? /* @__PURE__ */ React57__default.default.createElement(material.Stack, { spacing: 0.25, sx: { width: "100%" } }, filteredSamples.map((sample) => /* @__PURE__ */ React57__default.default.createElement(
6148
6248
  TemplateRow,
6149
6249
  {
6150
6250
  key: sample.id,
@@ -6156,7 +6256,7 @@ function SamplesDrawer({
6156
6256
  onDelete: deleteTemplate ? () => handleDelete(sample) : void 0,
6157
6257
  onDemote: setTemplateKind ? () => handleSetKind(sample, "template") : void 0
6158
6258
  }
6159
- ))) : /* @__PURE__ */ React57__default.default.createElement(material.Typography, { variant: "body2", sx: { color: "text.secondary", py: 1 } }, sampleRows.length === 0 ? "No samples available" : "No samples match your filters")))
6259
+ ))) : /* @__PURE__ */ React57__default.default.createElement(material.Typography, { variant: "body2", sx: { color: "text.secondary", py: 1 } }, sampleRows.length === 0 ? t("drawer.no-samples", "No samples available") : t("drawer.no-samples-match", "No samples match your filters"))))
6160
6260
  )
6161
6261
  ), renameTarget && /* @__PURE__ */ React57__default.default.createElement(
6162
6262
  RenameDialog,
@@ -6187,7 +6287,7 @@ function SamplesDrawer({
6187
6287
  open: pickerKind !== null,
6188
6288
  kind: pickerKind != null ? pickerKind : "template",
6189
6289
  samples: sampleRows,
6190
- existingSlugs: pickerKind === "sample" ? sampleRows.map((s) => s.slug) : templateRows.map((t) => t.slug),
6290
+ existingSlugs: pickerKind === "sample" ? sampleRows.map((s) => s.slug) : templateRows.map((t2) => t2.slug),
6191
6291
  onClose: () => setPickerKind(null),
6192
6292
  onCreate: handleCreateFromPicker
6193
6293
  }
@@ -6811,7 +6911,7 @@ function EmailLayoutEditor(props) {
6811
6911
  }
6812
6912
  }
6813
6913
  delete nDocument[selectedBlockId];
6814
- resetDocument(nDocument);
6914
+ replaceDocument(nDocument);
6815
6915
  }, [selectedBlockId, document2]);
6816
6916
  const handleCopy = React57.useCallback((e) => {
6817
6917
  if (!(e.metaKey || e.ctrlKey) || e.key !== "c") return;
@@ -6854,7 +6954,7 @@ function EmailLayoutEditor(props) {
6854
6954
  childrenIds: currentChildrenIds
6855
6955
  })
6856
6956
  };
6857
- resetDocument(doc);
6957
+ replaceDocument(doc);
6858
6958
  setSelectedBlockId(newRootId);
6859
6959
  }), [document2, childrenIds, selectedBlockId, currentBlockId]);
6860
6960
  React57.useEffect(() => {
@@ -6895,7 +6995,6 @@ function EmailLayoutEditor(props) {
6895
6995
  }
6896
6996
  }
6897
6997
  );
6898
- const WORKSPACE_BG = "#e7e8ec";
6899
6998
  const CARD_MAX_WIDTH = 664;
6900
6999
  const cardStyle = {
6901
7000
  maxWidth: CARD_MAX_WIDTH,
@@ -6911,7 +7010,6 @@ function EmailLayoutEditor(props) {
6911
7010
  setSelectedBlockId(null);
6912
7011
  },
6913
7012
  style: __spreadProps(__spreadValues({}, baseStyle), {
6914
- backgroundColor: WORKSPACE_BG,
6915
7013
  padding: "32px",
6916
7014
  width: "100%",
6917
7015
  minHeight: "100%"
@@ -6936,7 +7034,6 @@ function EmailLayoutEditor(props) {
6936
7034
  setSelectedBlockId(null);
6937
7035
  },
6938
7036
  style: __spreadProps(__spreadValues({}, baseStyle), {
6939
- backgroundColor: WORKSPACE_BG,
6940
7037
  padding: "32px 16px",
6941
7038
  width: "100%",
6942
7039
  minHeight: "100%"
@@ -7134,7 +7231,7 @@ function TuneMenu({ blockId }) {
7134
7231
  resetDocument(nDocument);
7135
7232
  setSelectedBlockId(blockId);
7136
7233
  };
7137
- return /* @__PURE__ */ React57__default.default.createElement(material.Paper, { sx, onClick: (ev) => ev.stopPropagation() }, /* @__PURE__ */ React57__default.default.createElement(material.Stack, null, /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Move up", placement: "left-start" }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { onClick: () => handleMoveClick("up"), sx: { color: "text.primary" } }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.ArrowUpwardOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Move down", placement: "left-start" }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { onClick: () => handleMoveClick("down"), sx: { color: "text.primary" } }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.ArrowDownwardOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Copy block", placement: "left-start" }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { onClick: handleCopyClick, sx: { color: "text.primary" } }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.ContentCopyOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Delete", placement: "left-start" }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { onClick: handleDeleteClick, sx: { color: "text.primary" } }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.DeleteOutlined, { fontSize: "small" })))));
7234
+ return /* @__PURE__ */ React57__default.default.createElement(material.Paper, { sx, onClick: (ev) => ev.stopPropagation() }, /* @__PURE__ */ React57__default.default.createElement(material.Stack, null, /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: t("tune.move-up", "Move up"), placement: "left-start" }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { "aria-label": t("tune.move-up", "Move up"), onClick: () => handleMoveClick("up"), sx: { color: "text.primary" } }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.ArrowUpwardOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: t("tune.move-down", "Move down"), placement: "left-start" }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { "aria-label": t("tune.move-down", "Move down"), onClick: () => handleMoveClick("down"), sx: { color: "text.primary" } }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.ArrowDownwardOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: t("tune.copy", "Copy block"), placement: "left-start" }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { "aria-label": t("tune.copy", "Copy block"), onClick: handleCopyClick, sx: { color: "text.primary" } }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.ContentCopyOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: t("tune.delete", "Delete"), placement: "left-start" }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { "aria-label": t("tune.delete", "Delete"), onClick: handleDeleteClick, sx: { color: "text.primary" } }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.DeleteOutlined, { fontSize: "small" })))));
7138
7235
  }
7139
7236
 
7140
7237
  // src/editor/blocks/helpers/block-wrappers/editor-block-wrapper.tsx
@@ -7497,7 +7594,7 @@ function InlineFormattingToolbar({
7497
7594
  },
7498
7595
  sx: { width: 220 }
7499
7596
  }
7500
- )) : /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", spacing: 0.25 }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { size: "small", onClick: onBold, title: "Bold (Cmd+B)", "aria-label": "Bold" }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.FormatBoldOutlined, { fontSize: "small" })), /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { size: "small", onClick: onItalic, title: "Italic (Cmd+I)", "aria-label": "Italic" }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.FormatItalicOutlined, { fontSize: "small" })), /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { size: "small", onClick: onLinkRequest, title: "Link (Cmd+K)", "aria-label": "Link" }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.LinkOutlined, { fontSize: "small" })))));
7597
+ )) : /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", spacing: 0.25 }, /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { size: "small", onClick: onBold, title: t("toolbar.bold-shortcut", "Bold (Cmd+B)"), "aria-label": t("toolbar.bold", "Bold") }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.FormatBoldOutlined, { fontSize: "small" })), /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { size: "small", onClick: onItalic, title: t("toolbar.italic-shortcut", "Italic (Cmd+I)"), "aria-label": t("toolbar.italic", "Italic") }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.FormatItalicOutlined, { fontSize: "small" })), /* @__PURE__ */ React57__default.default.createElement(material.IconButton, { size: "small", onClick: onLinkRequest, title: t("toolbar.link-shortcut", "Link (Cmd+K)"), "aria-label": t("toolbar.link", "Link") }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.LinkOutlined, { fontSize: "small" })))));
7501
7598
  }
7502
7599
 
7503
7600
  // src/editor/blocks/heading/heading-editor.tsx
@@ -8448,7 +8545,7 @@ function SaveBar({ loadTemplates, saveAs }) {
8448
8545
  const [nameError, setNameError] = React57.useState(null);
8449
8546
  const hasOpenRow = Boolean(currentTemplateId);
8450
8547
  const isSample = currentTemplateKind === "sample";
8451
- const primaryLabel = hasOpenRow ? "Save" : "Save as new\u2026";
8548
+ const primaryLabel = hasOpenRow ? t("savebar.save", "Save") : t("savebar.save-as-new", "Save as new\u2026");
8452
8549
  const handlePrimary = () => __async(null, null, function* () {
8453
8550
  try {
8454
8551
  if (!hasOpenRow) {
@@ -8457,11 +8554,11 @@ function SaveBar({ loadTemplates, saveAs }) {
8457
8554
  return;
8458
8555
  }
8459
8556
  saveTemplate();
8460
- showMessage(isSample ? "Sample saved" : "Template saved");
8557
+ showMessage(isSample ? t("savebar.sample-saved", "Sample saved") : t("savebar.template-saved", "Template saved"));
8461
8558
  if (loadTemplates) yield loadTemplates();
8462
8559
  } catch (e) {
8463
8560
  console.error("Error saving:", e);
8464
- showMessage("Error saving");
8561
+ showMessage(t("savebar.error-saving", "Error saving"));
8465
8562
  }
8466
8563
  });
8467
8564
  const handleSaveAs = (name) => __async(null, null, function* () {
@@ -8473,12 +8570,14 @@ function SaveBar({ loadTemplates, saveAs }) {
8473
8570
  setCurrentTemplate(id, slug, "template");
8474
8571
  ctxLoadTemplate(starterContent, id, slug, "template");
8475
8572
  if (loadTemplates) yield loadTemplates();
8476
- showMessage(dialogMode === "new-blank" ? "New template created" : "Template saved");
8573
+ showMessage(
8574
+ dialogMode === "new-blank" ? t("savebar.new-template-created", "New template created") : t("savebar.template-saved", "Template saved")
8575
+ );
8477
8576
  window.location.hash = `#template/${id}`;
8478
8577
  return true;
8479
8578
  } catch (e) {
8480
8579
  console.error("Error in saveAs:", e);
8481
- showMessage("Error saving");
8580
+ showMessage(t("savebar.error-saving", "Error saving"));
8482
8581
  return false;
8483
8582
  }
8484
8583
  });
@@ -8525,10 +8624,10 @@ function SaveBar({ loadTemplates, saveAs }) {
8525
8624
  },
8526
8625
  title: currentTemplateName
8527
8626
  },
8528
- isSample ? "Sample \xB7 " : "",
8627
+ isSample ? `${t("savebar.sample-prefix", "Sample")} \xB7 ` : "",
8529
8628
  /* @__PURE__ */ React57__default.default.createElement(material.Box, { component: "span", sx: { color: "text.primary", fontWeight: 600 } }, currentTemplateName)
8530
8629
  ),
8531
- /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: hasOpenRow ? `Save changes${isSample ? " to this sample" : ""}` : "Save as a new template" }, /* @__PURE__ */ React57__default.default.createElement(
8630
+ /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: hasOpenRow ? isSample ? t("savebar.save-changes-to-sample", "Save changes to this sample") : t("savebar.save-changes", "Save changes") : t("savebar.save-as-new-template", "Save as a new template") }, /* @__PURE__ */ React57__default.default.createElement(
8532
8631
  material.Button,
8533
8632
  {
8534
8633
  variant: "contained",
@@ -8539,7 +8638,7 @@ function SaveBar({ loadTemplates, saveAs }) {
8539
8638
  },
8540
8639
  primaryLabel
8541
8640
  )),
8542
- hasOpenRow && saveAs && /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Save as a new template" }, /* @__PURE__ */ React57__default.default.createElement(
8641
+ hasOpenRow && saveAs && /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: t("savebar.save-as-new-template", "Save as a new template") }, /* @__PURE__ */ React57__default.default.createElement(
8543
8642
  material.Button,
8544
8643
  {
8545
8644
  variant: "outlined",
@@ -8551,9 +8650,9 @@ function SaveBar({ loadTemplates, saveAs }) {
8551
8650
  },
8552
8651
  sx: { borderRadius: 999, textTransform: "none", px: 2, fontSize: 14 }
8553
8652
  },
8554
- "Save as\u2026"
8653
+ t("savebar.save-as", "Save as\u2026")
8555
8654
  )),
8556
- saveAs && /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Start a fresh template" }, /* @__PURE__ */ React57__default.default.createElement(
8655
+ saveAs && /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: t("savebar.start-fresh", "Start a fresh template") }, /* @__PURE__ */ React57__default.default.createElement(
8557
8656
  material.Button,
8558
8657
  {
8559
8658
  variant: "text",
@@ -8565,7 +8664,7 @@ function SaveBar({ loadTemplates, saveAs }) {
8565
8664
  },
8566
8665
  sx: { borderRadius: 999, textTransform: "none", px: 2, fontSize: 14, color: "text.secondary" }
8567
8666
  },
8568
- "New"
8667
+ t("savebar.new", "New")
8569
8668
  ))
8570
8669
  )
8571
8670
  ), /* @__PURE__ */ React57__default.default.createElement(
@@ -8583,6 +8682,64 @@ function SaveBar({ loadTemplates, saveAs }) {
8583
8682
  }
8584
8683
  ));
8585
8684
  }
8685
+ function isMac() {
8686
+ if (typeof navigator === "undefined") return false;
8687
+ const platform = (navigator.platform || "").toLowerCase();
8688
+ if (platform.includes("mac")) return true;
8689
+ const ua = (navigator.userAgent || "").toLowerCase();
8690
+ return ua.includes("mac") && !ua.includes("windows");
8691
+ }
8692
+ function isEditableTarget(target) {
8693
+ if (!(target instanceof HTMLElement)) return false;
8694
+ const tag = target.tagName;
8695
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
8696
+ if (target.isContentEditable) return true;
8697
+ return false;
8698
+ }
8699
+ function UndoRedoButtons() {
8700
+ const canUndo = useCanUndo();
8701
+ const canRedo = useCanRedo();
8702
+ const mac = isMac();
8703
+ const modKey = mac ? "\u2318" : "Ctrl";
8704
+ const undoHint = `${modKey}+Z`;
8705
+ const redoHint = mac ? `${modKey}+\u21E7+Z` : `${modKey}+Shift+Z / ${modKey}+Y`;
8706
+ React57.useEffect(() => {
8707
+ const onKeyDown = (e) => {
8708
+ const mod = mac ? e.metaKey : e.ctrlKey;
8709
+ if (!mod) return;
8710
+ if (isEditableTarget(e.target)) return;
8711
+ const key = e.key.toLowerCase();
8712
+ if (key === "z" && !e.shiftKey) {
8713
+ e.preventDefault();
8714
+ undo();
8715
+ } else if (key === "z" && e.shiftKey || key === "y" && !mac) {
8716
+ e.preventDefault();
8717
+ redo();
8718
+ }
8719
+ };
8720
+ window.addEventListener("keydown", onKeyDown);
8721
+ return () => window.removeEventListener("keydown", onKeyDown);
8722
+ }, [mac]);
8723
+ return /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", spacing: 0.5, alignItems: "center" }, /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: `${t("undo.tooltip", "Undo")} (${undoHint})` }, /* @__PURE__ */ React57__default.default.createElement("span", null, /* @__PURE__ */ React57__default.default.createElement(
8724
+ material.IconButton,
8725
+ {
8726
+ size: "small",
8727
+ onClick: undo,
8728
+ disabled: !canUndo,
8729
+ "aria-label": t("undo.label", "Undo")
8730
+ },
8731
+ /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.UndoOutlined, { fontSize: "small" })
8732
+ ))), /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: `${t("redo.tooltip", "Redo")} (${redoHint})` }, /* @__PURE__ */ React57__default.default.createElement("span", null, /* @__PURE__ */ React57__default.default.createElement(
8733
+ material.IconButton,
8734
+ {
8735
+ size: "small",
8736
+ onClick: redo,
8737
+ disabled: !canRedo,
8738
+ "aria-label": t("redo.label", "Redo")
8739
+ },
8740
+ /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.RedoOutlined, { fontSize: "small" })
8741
+ ))));
8742
+ }
8586
8743
  function SubjectInput() {
8587
8744
  var _a;
8588
8745
  const document2 = useDocument();
@@ -8811,7 +8968,7 @@ function ImageDropPasteHandler({ enabled, children }) {
8811
8968
 
8812
8969
  // src/app/email-canvas/index.tsx
8813
8970
  var WORKSPACE_SOLID = "#e7e8ec";
8814
- var WORKSPACE_CHECKERBOARD = "repeating-conic-gradient(#eceef2 0% 25%, #dfe1e6 0% 50%) 50% / 24px 24px";
8971
+ var WORKSPACE_CHECKERBOARD = "repeating-conic-gradient(#eceef2 0% 25%, #dfe1e6 0% 50%) 50% / 12px 12px";
8815
8972
  function TemplatePanel2({ loadTemplates, saveAs, samplesDrawerEnabled = true }) {
8816
8973
  const document2 = useDocument();
8817
8974
  const selectedMainTab = useSelectedMainTab();
@@ -8881,7 +9038,7 @@ function TemplatePanel2({ loadTemplates, saveAs, samplesDrawerEnabled = true })
8881
9038
  alignItems: "center"
8882
9039
  },
8883
9040
  samplesDrawerEnabled && /* @__PURE__ */ React57__default.default.createElement(ToggleSamplesPanelButton, null),
8884
- /* @__PURE__ */ React57__default.default.createElement(material.Stack, { px: 2, direction: "row", gap: 2, width: "100%", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57__default.default.createElement(MainTabsGroup, null)), /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57__default.default.createElement(material.ToggleButtonGroup, { value: selectedScreenSize, exclusive: true, size: "small", onChange: handleScreenSizeChange }, /* @__PURE__ */ React57__default.default.createElement(material.ToggleButton, { value: "desktop" }, /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Desktop view" }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.MonitorOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57__default.default.createElement(material.ToggleButton, { value: "mobile" }, /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Mobile view" }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.PhoneIphoneOutlined, { fontSize: "small" })))))),
9041
+ /* @__PURE__ */ React57__default.default.createElement(material.Stack, { px: 2, direction: "row", gap: 2, width: "100%", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57__default.default.createElement(MainTabsGroup, null)), /* @__PURE__ */ React57__default.default.createElement(material.Stack, { direction: "row", spacing: 2, alignItems: "center" }, selectedMainTab === "editor" && /* @__PURE__ */ React57__default.default.createElement(UndoRedoButtons, null), /* @__PURE__ */ React57__default.default.createElement(material.ToggleButtonGroup, { value: selectedScreenSize, exclusive: true, size: "small", onChange: handleScreenSizeChange }, /* @__PURE__ */ React57__default.default.createElement(material.ToggleButton, { value: "desktop" }, /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Desktop view" }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.MonitorOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57__default.default.createElement(material.ToggleButton, { value: "mobile" }, /* @__PURE__ */ React57__default.default.createElement(material.Tooltip, { title: "Mobile view" }, /* @__PURE__ */ React57__default.default.createElement(iconsMaterial.PhoneIphoneOutlined, { fontSize: "small" })))))),
8885
9042
  /* @__PURE__ */ React57__default.default.createElement(ToggleInspectorPanelButton, null)
8886
9043
  ), selectedMainTab === "editor" && /* @__PURE__ */ React57__default.default.createElement(SubjectInput, null), selectedMainTab === "preview" && /* @__PURE__ */ React57__default.default.createElement(SubjectPreview, null), /* @__PURE__ */ React57__default.default.createElement(ImageDropPasteHandler, { enabled: selectedMainTab === "editor" }, /* @__PURE__ */ React57__default.default.createElement(
8887
9044
  material.Box,
@@ -9042,8 +9199,10 @@ var EmailEditor = React57.forwardRef((props, ref) => {
9042
9199
  uploadImage,
9043
9200
  loadImages,
9044
9201
  deleteImage,
9045
- theme
9202
+ theme,
9203
+ locale
9046
9204
  } = props;
9205
+ activateLocale(locale);
9047
9206
  const resolvedTemplate = React57.useMemo(
9048
9207
  () => typeof initialTemplateProp === "string" ? htmlToEditorConfig(initialTemplateProp) : initialTemplateProp,
9049
9208
  [initialTemplateProp]
@@ -9060,7 +9219,7 @@ var EmailEditor = React57.forwardRef((props, ref) => {
9060
9219
  () => ({ uploadImage, loadImages, deleteImage }),
9061
9220
  [uploadImage, loadImages, deleteImage]
9062
9221
  );
9063
- return /* @__PURE__ */ React57__default.default.createElement(material.ThemeProvider, { theme: theme || theme_default }, /* @__PURE__ */ React57__default.default.createElement(material.CssBaseline, null), /* @__PURE__ */ React57__default.default.createElement("div", { style: { height: "100%", overflow: "auto" } }, /* @__PURE__ */ React57__default.default.createElement(SnackbarProvider, null, /* @__PURE__ */ React57__default.default.createElement(ImageCallbacksProvider, { callbacks: imageCallbacks }, /* @__PURE__ */ React57__default.default.createElement(
9222
+ return /* @__PURE__ */ React57__default.default.createElement(react.I18nProvider, { i18n: core.i18n }, /* @__PURE__ */ React57__default.default.createElement(material.ThemeProvider, { theme: theme || theme_default }, /* @__PURE__ */ React57__default.default.createElement(material.CssBaseline, null), /* @__PURE__ */ React57__default.default.createElement("div", { style: { height: "100%", overflow: "auto" } }, /* @__PURE__ */ React57__default.default.createElement(SnackbarProvider, null, /* @__PURE__ */ React57__default.default.createElement(ImageCallbacksProvider, { callbacks: imageCallbacks }, /* @__PURE__ */ React57__default.default.createElement(
9064
9223
  EmailEditorProvider,
9065
9224
  {
9066
9225
  initialTemplate: resolvedTemplate,
@@ -9088,7 +9247,7 @@ var EmailEditor = React57.forwardRef((props, ref) => {
9088
9247
  onChange
9089
9248
  }
9090
9249
  )
9091
- )))));
9250
+ ))))));
9092
9251
  });
9093
9252
  EmailEditor.displayName = "EmailEditor";
9094
9253
  EmailEditorInternal.displayName = "EmailEditorInternal";
@@ -9134,6 +9293,7 @@ exports.Reader = Reader;
9134
9293
  exports.ReaderBlock = ReaderBlock;
9135
9294
  exports.ReaderBlockSchema = ReaderBlockSchema;
9136
9295
  exports.ReaderDocumentSchema = ReaderDocumentSchema;
9296
+ exports.SUPPORTED_LOCALES = SUPPORTED_LOCALES;
9137
9297
  exports.Signature = signature_default;
9138
9298
  exports.SignatureProps = SignatureProps;
9139
9299
  exports.SignaturePropsDefaults = SignaturePropsDefaults;