@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.js CHANGED
@@ -5,9 +5,12 @@ import { z } from 'zod';
5
5
  import { renderToStaticMarkup as renderToStaticMarkup$1 } from 'react-dom/server';
6
6
  import Handlebars from 'handlebars';
7
7
  import { createTheme, alpha, lighten, darken } from '@mui/material/styles';
8
+ import { I18nProvider, Trans } from '@lingui/react';
8
9
  import { MenuItem, Stack, ThemeProvider, CssBaseline, useTheme, Drawer, Box, Tabs, Tab, Typography, Tooltip, IconButton, TextField, InputAdornment, Chip, CircularProgress, Alert, ToggleButtonGroup, ToggleButton, Snackbar, Menu, ListItemIcon, ListItemText, Divider as Divider$1, Dialog, DialogTitle, DialogContent, Button as Button$1, DialogActions, List, ListItemButton, InputBase, AlertTitle, FormControlLabel, Switch, InputLabel, Slider, ButtonBase, Popper, Paper, Fade } from '@mui/material';
9
- import { create } from 'zustand';
10
- import { AddOutlined, SearchOutlined, MonitorOutlined, PhoneIphoneOutlined, MoreVertOutlined, DriveFileRenameOutlineOutlined, ContentCopyOutlined, LibraryAddOutlined, FileUploadOutlined, FileDownloadOutlined, DeleteOutlined, InsertDriveFileOutlined, DescriptionOutlined, EditOutlined, PreviewOutlined, CodeOutlined, SubjectOutlined, DataObjectOutlined, LastPageOutlined, AppRegistrationOutlined, CloudUploadOutlined, SaveOutlined, SaveAsOutlined, ViewColumnSharp, ExpandMore, ChevronRight, KeyboardArrowUp, KeyboardArrowDown, FirstPageOutlined, MenuOutlined, GridOnOutlined, SquareOutlined, CheckOutlined, InputOutlined, DeleteOutline, RoundedCornerOutlined, AspectRatioOutlined, HeightOutlined, CollectionsOutlined, ErrorOutlineOutlined, VerticalAlignTopOutlined, VerticalAlignCenterOutlined, VerticalAlignBottomOutlined, SpaceBarOutlined, BusinessOutlined, ViewColumnOutlined, HtmlOutlined, Crop32Outlined, HorizontalRuleOutlined, ContactMailOutlined, AccountCircleOutlined, ImageOutlined, SmartButtonOutlined, NotesOutlined, HMobiledataOutlined, DashboardOutlined, CloseOutlined, AlignVerticalTopOutlined, AlignVerticalBottomOutlined, AlignHorizontalLeftOutlined, AlignHorizontalRightOutlined, FormatAlignLeftOutlined, FormatAlignCenterOutlined, FormatAlignRightOutlined, FormatLineSpacingOutlined, TextFieldsOutlined, FormatBoldOutlined, FormatItalicOutlined, LinkOutlined, ArrowUpwardOutlined, ArrowDownwardOutlined } from '@mui/icons-material';
10
+ import { i18n } from '@lingui/core';
11
+ import { create, useStore } from 'zustand';
12
+ import { temporal } from 'zundo';
13
+ import { AddOutlined, SearchOutlined, MonitorOutlined, PhoneIphoneOutlined, MoreVertOutlined, DriveFileRenameOutlineOutlined, ContentCopyOutlined, LibraryAddOutlined, FileUploadOutlined, FileDownloadOutlined, DeleteOutlined, InsertDriveFileOutlined, DescriptionOutlined, EditOutlined, PreviewOutlined, CodeOutlined, SubjectOutlined, DataObjectOutlined, UndoOutlined, RedoOutlined, LastPageOutlined, AppRegistrationOutlined, CloudUploadOutlined, SaveOutlined, SaveAsOutlined, ViewColumnSharp, ExpandMore, ChevronRight, KeyboardArrowUp, KeyboardArrowDown, FirstPageOutlined, MenuOutlined, GridOnOutlined, SquareOutlined, CheckOutlined, InputOutlined, DeleteOutline, RoundedCornerOutlined, AspectRatioOutlined, HeightOutlined, CollectionsOutlined, ErrorOutlineOutlined, VerticalAlignTopOutlined, VerticalAlignCenterOutlined, VerticalAlignBottomOutlined, SpaceBarOutlined, BusinessOutlined, ViewColumnOutlined, HtmlOutlined, Crop32Outlined, HorizontalRuleOutlined, ContactMailOutlined, AccountCircleOutlined, ImageOutlined, SmartButtonOutlined, NotesOutlined, HMobiledataOutlined, DashboardOutlined, CloseOutlined, AlignVerticalTopOutlined, AlignVerticalBottomOutlined, AlignHorizontalLeftOutlined, AlignHorizontalRightOutlined, FormatAlignLeftOutlined, FormatAlignCenterOutlined, FormatAlignRightOutlined, FormatLineSpacingOutlined, TextFieldsOutlined, FormatBoldOutlined, FormatItalicOutlined, LinkOutlined, ArrowUpwardOutlined, ArrowDownwardOutlined } from '@mui/icons-material';
11
14
  import { HexColorPicker, HexColorInput } from 'react-colorful';
12
15
  import hljs from 'highlight.js';
13
16
  import jsonHighlighter from 'highlight.js/lib/languages/json';
@@ -2000,6 +2003,37 @@ var THEME = createTheme(BASE_THEME, {
2000
2003
  ]
2001
2004
  });
2002
2005
  var theme_default = THEME;
2006
+
2007
+ // src/locales/en/messages.ts
2008
+ 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"]}');
2009
+
2010
+ // src/locales/fi/messages.ts
2011
+ 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"]}');
2012
+
2013
+ // src/locales/sv/messages.ts
2014
+ 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"]}');
2015
+ var SUPPORTED_LOCALES = ["en", "sv", "fi"];
2016
+ var CATALOGS = {
2017
+ en: messages,
2018
+ sv: messages3,
2019
+ fi: messages2
2020
+ };
2021
+ var loaded = false;
2022
+ function activateLocale(locale) {
2023
+ const resolved = SUPPORTED_LOCALES.includes(locale != null ? locale : "") ? locale : "en";
2024
+ if (locale && resolved !== locale) {
2025
+ console.warn(`[email-template-editor] Unsupported locale "${locale}", falling back to "en".`);
2026
+ }
2027
+ if (!loaded) {
2028
+ i18n.load({ en: CATALOGS.en, sv: CATALOGS.sv, fi: CATALOGS.fi });
2029
+ loaded = true;
2030
+ }
2031
+ i18n.activate(resolved);
2032
+ return resolved;
2033
+ }
2034
+ function t(id, fallback) {
2035
+ return i18n._(id, {}, { message: fallback != null ? fallback : id });
2036
+ }
2003
2037
  var EMPTY_DOCUMENT = {
2004
2038
  root: {
2005
2039
  type: "EmailLayout",
@@ -2012,20 +2046,48 @@ var EMPTY_DOCUMENT = {
2012
2046
  }
2013
2047
  }
2014
2048
  };
2015
- var editorStateStore = create(() => ({
2016
- document: EMPTY_DOCUMENT,
2017
- selectedBlockId: null,
2018
- selectedSidebarTab: "styles",
2019
- selectedMainTab: "editor",
2020
- selectedScreenSize: "desktop",
2021
- inspectorDrawerOpen: true,
2022
- samplesDrawerOpen: true,
2023
- persistenceEnabled: false,
2024
- lastFocusedEditable: null,
2025
- hoveredBlockId: null,
2026
- draggingBlock: null,
2027
- workspaceBackground: "checkerboard"
2028
- }));
2049
+ var COALESCE_MS = 300;
2050
+ function leadingThrottle(fn, wait) {
2051
+ let last = Number.NEGATIVE_INFINITY;
2052
+ return (...args) => {
2053
+ const now = Date.now();
2054
+ if (now - last >= wait) {
2055
+ last = now;
2056
+ fn(...args);
2057
+ }
2058
+ };
2059
+ }
2060
+ var editorStateStore = create()(
2061
+ temporal(
2062
+ () => ({
2063
+ document: EMPTY_DOCUMENT,
2064
+ selectedBlockId: null,
2065
+ selectedSidebarTab: "styles",
2066
+ selectedMainTab: "editor",
2067
+ selectedScreenSize: "desktop",
2068
+ inspectorDrawerOpen: true,
2069
+ samplesDrawerOpen: true,
2070
+ persistenceEnabled: false,
2071
+ lastFocusedEditable: null,
2072
+ hoveredBlockId: null,
2073
+ draggingBlock: null,
2074
+ workspaceBackground: "checkerboard"
2075
+ }),
2076
+ {
2077
+ limit: 100,
2078
+ // Only the document participates in history — selection, drawers, tabs
2079
+ // and other UI state are intentionally excluded.
2080
+ partialize: (state) => ({ document: state.document }),
2081
+ // Skip UI-only state changes: if the document reference is unchanged,
2082
+ // no history entry is recorded.
2083
+ equality: (a, b) => a.document === b.document,
2084
+ handleSet: (handleSet) => leadingThrottle(
2085
+ (pastState, replace, currentState) => handleSet(pastState, replace, currentState),
2086
+ COALESCE_MS
2087
+ )
2088
+ }
2089
+ )
2090
+ );
2029
2091
  function useDocument() {
2030
2092
  return editorStateStore((s) => s.document);
2031
2093
  }
@@ -2068,11 +2130,15 @@ function setSidebarTab(selectedSidebarTab) {
2068
2130
  return editorStateStore.setState({ selectedSidebarTab });
2069
2131
  }
2070
2132
  function resetDocument(document2) {
2071
- return editorStateStore.setState({
2133
+ const temporalApi = editorStateStore.temporal.getState();
2134
+ temporalApi.pause();
2135
+ editorStateStore.setState({
2072
2136
  document: document2,
2073
2137
  selectedSidebarTab: "styles",
2074
2138
  selectedBlockId: null
2075
2139
  });
2140
+ temporalApi.clear();
2141
+ temporalApi.resume();
2076
2142
  }
2077
2143
  function getDocument() {
2078
2144
  return editorStateStore.getState().document;
@@ -2083,6 +2149,9 @@ function setDocument(document2) {
2083
2149
  document: __spreadValues(__spreadValues({}, originalDocument), document2)
2084
2150
  });
2085
2151
  }
2152
+ function replaceDocument(document2) {
2153
+ editorStateStore.setState({ document: document2 });
2154
+ }
2086
2155
  function toggleInspectorDrawerOpen() {
2087
2156
  const inspectorDrawerOpen = !editorStateStore.getState().inspectorDrawerOpen;
2088
2157
  return editorStateStore.setState({ inspectorDrawerOpen });
@@ -2127,6 +2196,18 @@ function setWorkspaceBackground(workspaceBackground) {
2127
2196
  function setLastFocusedEditable(lastFocusedEditable) {
2128
2197
  return editorStateStore.setState({ lastFocusedEditable });
2129
2198
  }
2199
+ function undo() {
2200
+ editorStateStore.temporal.getState().undo();
2201
+ }
2202
+ function redo() {
2203
+ editorStateStore.temporal.getState().redo();
2204
+ }
2205
+ function useCanUndo() {
2206
+ return useStore(editorStateStore.temporal, (s) => s.pastStates.length > 0);
2207
+ }
2208
+ function useCanRedo() {
2209
+ return useStore(editorStateStore.temporal, (s) => s.futureStates.length > 0);
2210
+ }
2130
2211
 
2131
2212
  // src/app/save-payload.ts
2132
2213
  var ROOT_BLOCK_ID = "root";
@@ -2236,8 +2317,27 @@ function ImageCallbacksProvider({
2236
2317
  function useImageCallbacks() {
2237
2318
  return useContext(ImageCallbacksContext);
2238
2319
  }
2320
+ var TITLE_KEYS = {
2321
+ "Editor appearance": "panel.editor-appearance",
2322
+ Template: "panel.template",
2323
+ Export: "panel.export",
2324
+ Global: "panel.global",
2325
+ "Text block": "panel.text-block",
2326
+ "Heading block": "panel.heading-block",
2327
+ "Button block": "panel.button-block",
2328
+ "Image block": "panel.image-block",
2329
+ "Avatar block": "panel.avatar-block",
2330
+ "Signature block": "panel.signature-block",
2331
+ "Spacer block": "panel.spacer-block",
2332
+ "Divider block": "panel.divider-block",
2333
+ "Columns block": "panel.columns-block",
2334
+ "Container block": "panel.container-block",
2335
+ "Html block": "panel.html-block"
2336
+ };
2239
2337
  function BaseSidebarPanel({ title, children }) {
2240
- return /* @__PURE__ */ React57.createElement(Box, { p: 2 }, /* @__PURE__ */ React57.createElement(Typography, { variant: "overline", color: "text.secondary", sx: { display: "block", mb: 2 } }, title), /* @__PURE__ */ React57.createElement(Stack, { spacing: 5, mb: 3 }, children));
2338
+ const key = TITLE_KEYS[title];
2339
+ const displayTitle = key ? t(key, title) : title;
2340
+ return /* @__PURE__ */ React57.createElement(Box, { p: 2 }, /* @__PURE__ */ React57.createElement(Typography, { variant: "overline", color: "text.secondary", sx: { display: "block", mb: 2 } }, displayTitle), /* @__PURE__ */ React57.createElement(Stack, { spacing: 5, mb: 3 }, children));
2241
2341
  }
2242
2342
  function RadioGroupInput({ label, children, defaultValue, onChange }) {
2243
2343
  const [value, setValue] = useState(defaultValue);
@@ -2741,9 +2841,9 @@ function SingleStylePropertyPanel({ name, value, onChange }) {
2741
2841
  };
2742
2842
  switch (name) {
2743
2843
  case "backgroundColor":
2744
- return /* @__PURE__ */ React57.createElement(NullableColorInput, { label: "Background color", defaultValue, onChange: handleChange });
2844
+ return /* @__PURE__ */ React57.createElement(NullableColorInput, { label: t("style.background-color", "Background color"), defaultValue, onChange: handleChange });
2745
2845
  case "borderColor":
2746
- return /* @__PURE__ */ React57.createElement(NullableColorInput, { label: "Border color", defaultValue, onChange: handleChange });
2846
+ return /* @__PURE__ */ React57.createElement(NullableColorInput, { label: t("style.border-color", "Border color"), defaultValue, onChange: handleChange });
2747
2847
  case "borderRadius":
2748
2848
  return /* @__PURE__ */ React57.createElement(
2749
2849
  SliderInput,
@@ -2754,27 +2854,27 @@ function SingleStylePropertyPanel({ name, value, onChange }) {
2754
2854
  marks: true,
2755
2855
  min: 0,
2756
2856
  max: 48,
2757
- label: "Border radius",
2857
+ label: t("style.border-radius", "Border radius"),
2758
2858
  defaultValue,
2759
2859
  onChange: handleChange
2760
2860
  }
2761
2861
  );
2762
2862
  case "color":
2763
- return /* @__PURE__ */ React57.createElement(NullableColorInput, { label: "Text color", defaultValue, onChange: handleChange });
2863
+ return /* @__PURE__ */ React57.createElement(NullableColorInput, { label: t("style.text-color", "Text color"), defaultValue, onChange: handleChange });
2764
2864
  case "fontFamily":
2765
- return /* @__PURE__ */ React57.createElement(NullableFontFamily, { label: "Font family", defaultValue, onChange: handleChange });
2865
+ return /* @__PURE__ */ React57.createElement(NullableFontFamily, { label: t("style.font-family", "Font family"), defaultValue, onChange: handleChange });
2766
2866
  case "fontSize":
2767
- return /* @__PURE__ */ React57.createElement(FontSizeInput, { label: "Font size", defaultValue, onChange: handleChange });
2867
+ return /* @__PURE__ */ React57.createElement(FontSizeInput, { label: t("style.font-size", "Font size"), defaultValue, onChange: handleChange });
2768
2868
  case "fontWeight":
2769
- return /* @__PURE__ */ React57.createElement(FontWeightInput, { label: "Font weight", defaultValue, onChange: handleChange });
2869
+ return /* @__PURE__ */ React57.createElement(FontWeightInput, { label: t("style.font-weight", "Font weight"), defaultValue, onChange: handleChange });
2770
2870
  case "lineHeight":
2771
- return /* @__PURE__ */ React57.createElement(LineHeightInput, { label: "Line height", defaultValue, onChange: handleChange });
2871
+ return /* @__PURE__ */ React57.createElement(LineHeightInput, { label: t("style.line-height", "Line height"), defaultValue, onChange: handleChange });
2772
2872
  case "letterSpacing":
2773
- return /* @__PURE__ */ React57.createElement(LetterSpacingInput, { label: "Letter spacing", defaultValue, onChange: handleChange });
2873
+ return /* @__PURE__ */ React57.createElement(LetterSpacingInput, { label: t("style.letter-spacing", "Letter spacing"), defaultValue, onChange: handleChange });
2774
2874
  case "textAlign":
2775
- return /* @__PURE__ */ React57.createElement(TextAlignInput, { label: "Alignment", defaultValue, onChange: handleChange });
2875
+ return /* @__PURE__ */ React57.createElement(TextAlignInput, { label: t("style.alignment", "Alignment"), defaultValue, onChange: handleChange });
2776
2876
  case "padding":
2777
- return /* @__PURE__ */ React57.createElement(PaddingInput, { label: "Padding", defaultValue, onChange: handleChange });
2877
+ return /* @__PURE__ */ React57.createElement(PaddingInput, { label: t("style.padding", "Padding"), defaultValue, onChange: handleChange });
2778
2878
  }
2779
2879
  }
2780
2880
 
@@ -4081,15 +4181,15 @@ function DetailsEditor({ templateId, currentName, currentTags, canEdit, onSave }
4081
4181
  const addTag = () => {
4082
4182
  const trimmed = tagInput.trim();
4083
4183
  if (!trimmed) return;
4084
- if (tags.some((t) => t.toLowerCase() === trimmed.toLowerCase())) {
4184
+ if (tags.some((t2) => t2.toLowerCase() === trimmed.toLowerCase())) {
4085
4185
  setTagInput("");
4086
4186
  return;
4087
4187
  }
4088
4188
  setTags([...tags, trimmed]);
4089
4189
  setTagInput("");
4090
4190
  };
4091
- const removeTag = (tag) => setTags(tags.filter((t) => t !== tag));
4092
- const dirty = name !== currentName || tags.length !== currentTags.length || tags.some((t, i) => t !== currentTags[i]);
4191
+ const removeTag = (tag) => setTags(tags.filter((t2) => t2 !== tag));
4192
+ const dirty = name !== currentName || tags.length !== currentTags.length || tags.some((t2, i) => t2 !== currentTags[i]);
4093
4193
  const handleSave = () => __async(null, null, function* () {
4094
4194
  if (!canEdit || !dirty || !name.trim()) return;
4095
4195
  setSaving(true);
@@ -5517,15 +5617,15 @@ function RenameDialog({
5517
5617
  const addTag = () => {
5518
5618
  const trimmed = tagInput.trim();
5519
5619
  if (!trimmed) return;
5520
- if (tags.some((t) => t.toLowerCase() === trimmed.toLowerCase())) {
5620
+ if (tags.some((t2) => t2.toLowerCase() === trimmed.toLowerCase())) {
5521
5621
  setTagInput("");
5522
5622
  return;
5523
5623
  }
5524
5624
  setTags([...tags, trimmed]);
5525
5625
  setTagInput("");
5526
5626
  };
5527
- const removeTag = (tag) => setTags(tags.filter((t) => t !== tag));
5528
- const tagsUnchanged = tags.length === currentTags.length && tags.every((t, i) => t === currentTags[i]);
5627
+ const removeTag = (tag) => setTags(tags.filter((t2) => t2 !== tag));
5628
+ const tagsUnchanged = tags.length === currentTags.length && tags.every((t2, i) => t2 === currentTags[i]);
5529
5629
  const handleSubmit = () => __async(null, null, function* () {
5530
5630
  const trimmedSlug = slug.trim();
5531
5631
  if (!trimmedSlug) {
@@ -5749,9 +5849,9 @@ var EMPTY_TEMPLATE = {
5749
5849
  description: "A blank email template to start from scratch"
5750
5850
  };
5751
5851
  var SORT_OPTIONS = [
5752
- { value: "updatedAt", label: "Last updated" },
5753
- { value: "createdAt", label: "Recently created" },
5754
- { value: "slug", label: "Name (A\u2013Z)" }
5852
+ { value: "updatedAt", labelKey: "sort.last-updated", fallback: "Last updated" },
5853
+ { value: "createdAt", labelKey: "sort.recently-created", fallback: "Recently created" },
5854
+ { value: "slug", labelKey: "sort.name", fallback: "Name (A\u2013Z)" }
5755
5855
  ];
5756
5856
  function compareTemplates(a, b, key) {
5757
5857
  if (key === "slug") {
@@ -5835,9 +5935,9 @@ function SamplesDrawer({
5835
5935
  const now = (/* @__PURE__ */ new Date()).toISOString();
5836
5936
  const row = { id, slug, kind, tags, createdAt: now, updatedAt: now };
5837
5937
  if (kind === "sample") {
5838
- setSamples((prev) => prev.some((t) => t.id === id) ? prev : [...prev, row]);
5938
+ setSamples((prev) => prev.some((t2) => t2.id === id) ? prev : [...prev, row]);
5839
5939
  } else {
5840
- setTemplates((prev) => prev.some((t) => t.id === id) ? prev : [...prev, row]);
5940
+ setTemplates((prev) => prev.some((t2) => t2.id === id) ? prev : [...prev, row]);
5841
5941
  }
5842
5942
  };
5843
5943
  useEffect(() => {
@@ -5855,30 +5955,30 @@ function SamplesDrawer({
5855
5955
  const { templateRows, sampleRows } = useMemo(() => {
5856
5956
  const byId = /* @__PURE__ */ new Map();
5857
5957
  for (const s of samples) byId.set(s.id, s);
5858
- for (const t of templates) byId.set(t.id, t);
5958
+ for (const t2 of templates) byId.set(t2.id, t2);
5859
5959
  const all = Array.from(byId.values());
5860
5960
  return {
5861
- templateRows: all.filter((t) => t.kind === "template"),
5862
- sampleRows: all.filter((t) => t.kind === "sample")
5961
+ templateRows: all.filter((t2) => t2.kind === "template"),
5962
+ sampleRows: all.filter((t2) => t2.kind === "sample")
5863
5963
  };
5864
5964
  }, [samples, templates]);
5865
5965
  const allTags = useMemo(() => {
5866
5966
  var _a2, _b2;
5867
5967
  const set = /* @__PURE__ */ new Set();
5868
- for (const t of templateRows) (_a2 = t.tags) == null ? void 0 : _a2.forEach((tag) => set.add(tag));
5968
+ for (const t2 of templateRows) (_a2 = t2.tags) == null ? void 0 : _a2.forEach((tag) => set.add(tag));
5869
5969
  for (const s of sampleRows) (_b2 = s.tags) == null ? void 0 : _b2.forEach((tag) => set.add(tag));
5870
5970
  return Array.from(set).sort();
5871
5971
  }, [templateRows, sampleRows]);
5872
5972
  const matchesSearchAndTags = useMemo(() => {
5873
5973
  const term = search.trim().toLowerCase();
5874
- return (t) => {
5974
+ return (t2) => {
5875
5975
  if (term) {
5876
- const haystack = [t.slug, t.description, t.subject].filter(Boolean).join(" ").toLowerCase();
5976
+ const haystack = [t2.slug, t2.description, t2.subject].filter(Boolean).join(" ").toLowerCase();
5877
5977
  if (!haystack.includes(term)) return false;
5878
5978
  }
5879
5979
  if (activeTags.length > 0) {
5880
- if (!t.tags || t.tags.length === 0) return false;
5881
- if (!activeTags.every((tag) => t.tags.includes(tag))) return false;
5980
+ if (!t2.tags || t2.tags.length === 0) return false;
5981
+ if (!activeTags.every((tag) => t2.tags.includes(tag))) return false;
5882
5982
  }
5883
5983
  return true;
5884
5984
  };
@@ -5892,7 +5992,7 @@ function SamplesDrawer({
5892
5992
  [sampleRows, matchesSearchAndTags, sortKey]
5893
5993
  );
5894
5994
  const toggleTag = (tag) => {
5895
- setActiveTags((prev) => prev.includes(tag) ? prev.filter((t) => t !== tag) : [...prev, tag]);
5995
+ setActiveTags((prev) => prev.includes(tag) ? prev.filter((t2) => t2 !== tag) : [...prev, tag]);
5896
5996
  };
5897
5997
  const handleDuplicate = (template) => {
5898
5998
  if (!copyTemplate) return;
@@ -5922,7 +6022,7 @@ function SamplesDrawer({
5922
6022
  if (!renameTarget || !renameTemplate) return;
5923
6023
  yield renameTemplate(renameTarget.id, newSlug, opts);
5924
6024
  const newTags = (_b2 = (_a2 = opts == null ? void 0 : opts.tags) != null ? _a2 : renameTarget.tags) != null ? _b2 : [];
5925
- const patch = (t) => t.id === renameTarget.id ? __spreadProps(__spreadValues({}, t), { slug: newSlug, tags: newTags }) : t;
6025
+ const patch = (t2) => t2.id === renameTarget.id ? __spreadProps(__spreadValues({}, t2), { slug: newSlug, tags: newTags }) : t2;
5926
6026
  setTemplates((prev) => prev.map(patch));
5927
6027
  setSamples((prev) => prev.map(patch));
5928
6028
  if (currentTemplateId === renameTarget.id) {
@@ -5931,8 +6031,8 @@ function SamplesDrawer({
5931
6031
  showMessage("Details saved");
5932
6032
  });
5933
6033
  const flipKindLocally = (id, kind) => {
5934
- setTemplates((prev) => prev.map((t) => t.id === id ? __spreadProps(__spreadValues({}, t), { kind }) : t));
5935
- setSamples((prev) => prev.map((t) => t.id === id ? __spreadProps(__spreadValues({}, t), { kind }) : t));
6034
+ setTemplates((prev) => prev.map((t2) => t2.id === id ? __spreadProps(__spreadValues({}, t2), { kind }) : t2));
6035
+ setSamples((prev) => prev.map((t2) => t2.id === id ? __spreadProps(__spreadValues({}, t2), { kind }) : t2));
5936
6036
  };
5937
6037
  const handleSetKind = (template, kind) => {
5938
6038
  if (!setTemplateKind) return;
@@ -5969,7 +6069,7 @@ function SamplesDrawer({
5969
6069
  };
5970
6070
  const handleSaveAsSubmit = (templateName) => __async(null, null, function* () {
5971
6071
  if (!saveAs || !pendingSaveAs) return false;
5972
- const taken = templateRows.some((t) => t.slug.toLowerCase() === templateName.toLowerCase());
6072
+ const taken = templateRows.some((t2) => t2.slug.toLowerCase() === templateName.toLowerCase());
5973
6073
  if (taken) {
5974
6074
  setNewError("A template with this name already exists");
5975
6075
  return false;
@@ -6032,7 +6132,7 @@ function SamplesDrawer({
6032
6132
  if (!enabled) {
6033
6133
  return null;
6034
6134
  }
6035
- const existingSlugs = templates.map((t) => t.slug);
6135
+ const existingSlugs = templates.map((t2) => t2.slug);
6036
6136
  return /* @__PURE__ */ React57.createElement(React57.Fragment, null, /* @__PURE__ */ React57.createElement(
6037
6137
  Drawer,
6038
6138
  {
@@ -6058,12 +6158,12 @@ function SamplesDrawer({
6058
6158
  spacing: 1.5,
6059
6159
  sx: { overflowY: "auto" }
6060
6160
  },
6061
- /* @__PURE__ */ React57.createElement(Stack, { direction: "row", alignItems: "center", justifyContent: "space-between", sx: { pt: 1 } }, /* @__PURE__ */ React57.createElement(Typography, { variant: "h6", component: "h1" }, "Library"), saveAs && (activeLeftTab === "templates" || activeLeftTab === "samples") && /* @__PURE__ */ React57.createElement(Tooltip, { title: activeLeftTab === "samples" ? "New sample" : "New template" }, /* @__PURE__ */ React57.createElement(
6161
+ /* @__PURE__ */ React57.createElement(Stack, { direction: "row", alignItems: "center", justifyContent: "space-between", sx: { pt: 1 } }, /* @__PURE__ */ React57.createElement(Typography, { variant: "h6", component: "h1" }, /* @__PURE__ */ React57.createElement(Trans, { id: "drawer.library" }, "Library")), saveAs && (activeLeftTab === "templates" || activeLeftTab === "samples") && /* @__PURE__ */ React57.createElement(Tooltip, { title: activeLeftTab === "samples" ? t("drawer.new-sample", "New sample") : t("drawer.new-template", "New template") }, /* @__PURE__ */ React57.createElement(
6062
6162
  IconButton,
6063
6163
  {
6064
6164
  size: "small",
6065
6165
  onClick: () => openNewPicker(activeLeftTab === "samples" ? "sample" : "template"),
6066
- "aria-label": activeLeftTab === "samples" ? "New sample" : "New template"
6166
+ "aria-label": activeLeftTab === "samples" ? t("drawer.new-sample", "New sample") : t("drawer.new-template", "New template")
6067
6167
  },
6068
6168
  /* @__PURE__ */ React57.createElement(AddOutlined, { fontSize: "small" })
6069
6169
  ))),
@@ -6075,15 +6175,15 @@ function SamplesDrawer({
6075
6175
  variant: "fullWidth",
6076
6176
  sx: { minHeight: 36, "& .MuiTab-root": { minHeight: 36, minWidth: 0, px: 1, fontSize: 13 } }
6077
6177
  },
6078
- /* @__PURE__ */ React57.createElement(Tab, { value: "templates", label: "Templates", disabled: !loadTemplates }),
6079
- /* @__PURE__ */ React57.createElement(Tab, { value: "samples", label: "Samples" }),
6080
- /* @__PURE__ */ React57.createElement(Tab, { value: "outline", label: "Outline" })
6178
+ /* @__PURE__ */ React57.createElement(Tab, { value: "templates", label: t("drawer.tab.templates", "Templates"), disabled: !loadTemplates }),
6179
+ /* @__PURE__ */ React57.createElement(Tab, { value: "samples", label: t("drawer.tab.samples", "Samples") }),
6180
+ /* @__PURE__ */ React57.createElement(Tab, { value: "outline", label: t("drawer.tab.outline", "Outline") })
6081
6181
  ),
6082
6182
  activeLeftTab === "outline" ? /* @__PURE__ */ React57.createElement(OutlinePanel, null) : /* @__PURE__ */ React57.createElement(React57.Fragment, null, /* @__PURE__ */ React57.createElement(
6083
6183
  TextField,
6084
6184
  {
6085
6185
  size: "small",
6086
- placeholder: activeLeftTab === "templates" ? "Search templates" : "Search samples",
6186
+ placeholder: activeLeftTab === "templates" ? t("drawer.search-templates", "Search templates") : t("drawer.search-samples", "Search samples"),
6087
6187
  value: search,
6088
6188
  onChange: (e) => setSearch(e.target.value),
6089
6189
  InputProps: {
@@ -6095,15 +6195,15 @@ function SamplesDrawer({
6095
6195
  {
6096
6196
  select: true,
6097
6197
  size: "small",
6098
- label: "Sort by",
6198
+ label: t("drawer.sort-by", "Sort by"),
6099
6199
  value: sortKey,
6100
6200
  onChange: (e) => setSortKey(e.target.value)
6101
6201
  },
6102
- SORT_OPTIONS.map((opt) => /* @__PURE__ */ React57.createElement(MenuItem, { key: opt.value, value: opt.value }, opt.label))
6202
+ SORT_OPTIONS.map((opt) => /* @__PURE__ */ React57.createElement(MenuItem, { key: opt.value, value: opt.value }, t(opt.labelKey, opt.fallback)))
6103
6203
  ), allTags.length > 0 && /* @__PURE__ */ React57.createElement(Stack, { direction: "row", sx: { flexWrap: "wrap", gap: 0.5 } }, /* @__PURE__ */ React57.createElement(
6104
6204
  Chip,
6105
6205
  {
6106
- label: "All",
6206
+ label: t("drawer.tag.all", "All"),
6107
6207
  size: "small",
6108
6208
  clickable: true,
6109
6209
  color: activeTags.length === 0 ? "primary" : "default",
@@ -6133,7 +6233,7 @@ function SamplesDrawer({
6133
6233
  onDelete: deleteTemplate ? () => handleDelete(template) : void 0,
6134
6234
  onPromote: setTemplateKind ? () => handleSetKind(template, "sample") : void 0
6135
6235
  }
6136
- ))) : /* @__PURE__ */ React57.createElement(Typography, { variant: "body2", sx: { color: "text.secondary", py: 1 } }, templateRows.length === 0 ? "No saved templates yet" : "No templates match your filters")) : /* @__PURE__ */ React57.createElement(Box, null, loadingSamples ? /* @__PURE__ */ React57.createElement(Stack, { alignItems: "center", width: "100%", py: 2 }, /* @__PURE__ */ React57.createElement(CircularProgress, { size: 24 })) : filteredSamples.length > 0 ? /* @__PURE__ */ React57.createElement(Stack, { spacing: 0.25, sx: { width: "100%" } }, filteredSamples.map((sample) => /* @__PURE__ */ React57.createElement(
6236
+ ))) : /* @__PURE__ */ React57.createElement(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.createElement(Box, null, loadingSamples ? /* @__PURE__ */ React57.createElement(Stack, { alignItems: "center", width: "100%", py: 2 }, /* @__PURE__ */ React57.createElement(CircularProgress, { size: 24 })) : filteredSamples.length > 0 ? /* @__PURE__ */ React57.createElement(Stack, { spacing: 0.25, sx: { width: "100%" } }, filteredSamples.map((sample) => /* @__PURE__ */ React57.createElement(
6137
6237
  TemplateRow,
6138
6238
  {
6139
6239
  key: sample.id,
@@ -6145,7 +6245,7 @@ function SamplesDrawer({
6145
6245
  onDelete: deleteTemplate ? () => handleDelete(sample) : void 0,
6146
6246
  onDemote: setTemplateKind ? () => handleSetKind(sample, "template") : void 0
6147
6247
  }
6148
- ))) : /* @__PURE__ */ React57.createElement(Typography, { variant: "body2", sx: { color: "text.secondary", py: 1 } }, sampleRows.length === 0 ? "No samples available" : "No samples match your filters")))
6248
+ ))) : /* @__PURE__ */ React57.createElement(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"))))
6149
6249
  )
6150
6250
  ), renameTarget && /* @__PURE__ */ React57.createElement(
6151
6251
  RenameDialog,
@@ -6176,7 +6276,7 @@ function SamplesDrawer({
6176
6276
  open: pickerKind !== null,
6177
6277
  kind: pickerKind != null ? pickerKind : "template",
6178
6278
  samples: sampleRows,
6179
- existingSlugs: pickerKind === "sample" ? sampleRows.map((s) => s.slug) : templateRows.map((t) => t.slug),
6279
+ existingSlugs: pickerKind === "sample" ? sampleRows.map((s) => s.slug) : templateRows.map((t2) => t2.slug),
6180
6280
  onClose: () => setPickerKind(null),
6181
6281
  onCreate: handleCreateFromPicker
6182
6282
  }
@@ -6800,7 +6900,7 @@ function EmailLayoutEditor(props) {
6800
6900
  }
6801
6901
  }
6802
6902
  delete nDocument[selectedBlockId];
6803
- resetDocument(nDocument);
6903
+ replaceDocument(nDocument);
6804
6904
  }, [selectedBlockId, document2]);
6805
6905
  const handleCopy = useCallback((e) => {
6806
6906
  if (!(e.metaKey || e.ctrlKey) || e.key !== "c") return;
@@ -6843,7 +6943,7 @@ function EmailLayoutEditor(props) {
6843
6943
  childrenIds: currentChildrenIds
6844
6944
  })
6845
6945
  };
6846
- resetDocument(doc);
6946
+ replaceDocument(doc);
6847
6947
  setSelectedBlockId(newRootId);
6848
6948
  }), [document2, childrenIds, selectedBlockId, currentBlockId]);
6849
6949
  useEffect(() => {
@@ -6884,7 +6984,6 @@ function EmailLayoutEditor(props) {
6884
6984
  }
6885
6985
  }
6886
6986
  );
6887
- const WORKSPACE_BG = "#e7e8ec";
6888
6987
  const CARD_MAX_WIDTH = 664;
6889
6988
  const cardStyle = {
6890
6989
  maxWidth: CARD_MAX_WIDTH,
@@ -6900,7 +6999,6 @@ function EmailLayoutEditor(props) {
6900
6999
  setSelectedBlockId(null);
6901
7000
  },
6902
7001
  style: __spreadProps(__spreadValues({}, baseStyle), {
6903
- backgroundColor: WORKSPACE_BG,
6904
7002
  padding: "32px",
6905
7003
  width: "100%",
6906
7004
  minHeight: "100%"
@@ -6925,7 +7023,6 @@ function EmailLayoutEditor(props) {
6925
7023
  setSelectedBlockId(null);
6926
7024
  },
6927
7025
  style: __spreadProps(__spreadValues({}, baseStyle), {
6928
- backgroundColor: WORKSPACE_BG,
6929
7026
  padding: "32px 16px",
6930
7027
  width: "100%",
6931
7028
  minHeight: "100%"
@@ -7123,7 +7220,7 @@ function TuneMenu({ blockId }) {
7123
7220
  resetDocument(nDocument);
7124
7221
  setSelectedBlockId(blockId);
7125
7222
  };
7126
- return /* @__PURE__ */ React57.createElement(Paper, { sx, onClick: (ev) => ev.stopPropagation() }, /* @__PURE__ */ React57.createElement(Stack, null, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Move up", placement: "left-start" }, /* @__PURE__ */ React57.createElement(IconButton, { onClick: () => handleMoveClick("up"), sx: { color: "text.primary" } }, /* @__PURE__ */ React57.createElement(ArrowUpwardOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(Tooltip, { title: "Move down", placement: "left-start" }, /* @__PURE__ */ React57.createElement(IconButton, { onClick: () => handleMoveClick("down"), sx: { color: "text.primary" } }, /* @__PURE__ */ React57.createElement(ArrowDownwardOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(Tooltip, { title: "Copy block", placement: "left-start" }, /* @__PURE__ */ React57.createElement(IconButton, { onClick: handleCopyClick, sx: { color: "text.primary" } }, /* @__PURE__ */ React57.createElement(ContentCopyOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(Tooltip, { title: "Delete", placement: "left-start" }, /* @__PURE__ */ React57.createElement(IconButton, { onClick: handleDeleteClick, sx: { color: "text.primary" } }, /* @__PURE__ */ React57.createElement(DeleteOutlined, { fontSize: "small" })))));
7223
+ return /* @__PURE__ */ React57.createElement(Paper, { sx, onClick: (ev) => ev.stopPropagation() }, /* @__PURE__ */ React57.createElement(Stack, null, /* @__PURE__ */ React57.createElement(Tooltip, { title: t("tune.move-up", "Move up"), placement: "left-start" }, /* @__PURE__ */ React57.createElement(IconButton, { "aria-label": t("tune.move-up", "Move up"), onClick: () => handleMoveClick("up"), sx: { color: "text.primary" } }, /* @__PURE__ */ React57.createElement(ArrowUpwardOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(Tooltip, { title: t("tune.move-down", "Move down"), placement: "left-start" }, /* @__PURE__ */ React57.createElement(IconButton, { "aria-label": t("tune.move-down", "Move down"), onClick: () => handleMoveClick("down"), sx: { color: "text.primary" } }, /* @__PURE__ */ React57.createElement(ArrowDownwardOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(Tooltip, { title: t("tune.copy", "Copy block"), placement: "left-start" }, /* @__PURE__ */ React57.createElement(IconButton, { "aria-label": t("tune.copy", "Copy block"), onClick: handleCopyClick, sx: { color: "text.primary" } }, /* @__PURE__ */ React57.createElement(ContentCopyOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(Tooltip, { title: t("tune.delete", "Delete"), placement: "left-start" }, /* @__PURE__ */ React57.createElement(IconButton, { "aria-label": t("tune.delete", "Delete"), onClick: handleDeleteClick, sx: { color: "text.primary" } }, /* @__PURE__ */ React57.createElement(DeleteOutlined, { fontSize: "small" })))));
7127
7224
  }
7128
7225
 
7129
7226
  // src/editor/blocks/helpers/block-wrappers/editor-block-wrapper.tsx
@@ -7486,7 +7583,7 @@ function InlineFormattingToolbar({
7486
7583
  },
7487
7584
  sx: { width: 220 }
7488
7585
  }
7489
- )) : /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 0.25 }, /* @__PURE__ */ React57.createElement(IconButton, { size: "small", onClick: onBold, title: "Bold (Cmd+B)", "aria-label": "Bold" }, /* @__PURE__ */ React57.createElement(FormatBoldOutlined, { fontSize: "small" })), /* @__PURE__ */ React57.createElement(IconButton, { size: "small", onClick: onItalic, title: "Italic (Cmd+I)", "aria-label": "Italic" }, /* @__PURE__ */ React57.createElement(FormatItalicOutlined, { fontSize: "small" })), /* @__PURE__ */ React57.createElement(IconButton, { size: "small", onClick: onLinkRequest, title: "Link (Cmd+K)", "aria-label": "Link" }, /* @__PURE__ */ React57.createElement(LinkOutlined, { fontSize: "small" })))));
7586
+ )) : /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 0.25 }, /* @__PURE__ */ React57.createElement(IconButton, { size: "small", onClick: onBold, title: t("toolbar.bold-shortcut", "Bold (Cmd+B)"), "aria-label": t("toolbar.bold", "Bold") }, /* @__PURE__ */ React57.createElement(FormatBoldOutlined, { fontSize: "small" })), /* @__PURE__ */ React57.createElement(IconButton, { size: "small", onClick: onItalic, title: t("toolbar.italic-shortcut", "Italic (Cmd+I)"), "aria-label": t("toolbar.italic", "Italic") }, /* @__PURE__ */ React57.createElement(FormatItalicOutlined, { fontSize: "small" })), /* @__PURE__ */ React57.createElement(IconButton, { size: "small", onClick: onLinkRequest, title: t("toolbar.link-shortcut", "Link (Cmd+K)"), "aria-label": t("toolbar.link", "Link") }, /* @__PURE__ */ React57.createElement(LinkOutlined, { fontSize: "small" })))));
7490
7587
  }
7491
7588
 
7492
7589
  // src/editor/blocks/heading/heading-editor.tsx
@@ -8437,7 +8534,7 @@ function SaveBar({ loadTemplates, saveAs }) {
8437
8534
  const [nameError, setNameError] = useState(null);
8438
8535
  const hasOpenRow = Boolean(currentTemplateId);
8439
8536
  const isSample = currentTemplateKind === "sample";
8440
- const primaryLabel = hasOpenRow ? "Save" : "Save as new\u2026";
8537
+ const primaryLabel = hasOpenRow ? t("savebar.save", "Save") : t("savebar.save-as-new", "Save as new\u2026");
8441
8538
  const handlePrimary = () => __async(null, null, function* () {
8442
8539
  try {
8443
8540
  if (!hasOpenRow) {
@@ -8446,11 +8543,11 @@ function SaveBar({ loadTemplates, saveAs }) {
8446
8543
  return;
8447
8544
  }
8448
8545
  saveTemplate();
8449
- showMessage(isSample ? "Sample saved" : "Template saved");
8546
+ showMessage(isSample ? t("savebar.sample-saved", "Sample saved") : t("savebar.template-saved", "Template saved"));
8450
8547
  if (loadTemplates) yield loadTemplates();
8451
8548
  } catch (e) {
8452
8549
  console.error("Error saving:", e);
8453
- showMessage("Error saving");
8550
+ showMessage(t("savebar.error-saving", "Error saving"));
8454
8551
  }
8455
8552
  });
8456
8553
  const handleSaveAs = (name) => __async(null, null, function* () {
@@ -8462,12 +8559,14 @@ function SaveBar({ loadTemplates, saveAs }) {
8462
8559
  setCurrentTemplate(id, slug, "template");
8463
8560
  ctxLoadTemplate(starterContent, id, slug, "template");
8464
8561
  if (loadTemplates) yield loadTemplates();
8465
- showMessage(dialogMode === "new-blank" ? "New template created" : "Template saved");
8562
+ showMessage(
8563
+ dialogMode === "new-blank" ? t("savebar.new-template-created", "New template created") : t("savebar.template-saved", "Template saved")
8564
+ );
8466
8565
  window.location.hash = `#template/${id}`;
8467
8566
  return true;
8468
8567
  } catch (e) {
8469
8568
  console.error("Error in saveAs:", e);
8470
- showMessage("Error saving");
8569
+ showMessage(t("savebar.error-saving", "Error saving"));
8471
8570
  return false;
8472
8571
  }
8473
8572
  });
@@ -8514,10 +8613,10 @@ function SaveBar({ loadTemplates, saveAs }) {
8514
8613
  },
8515
8614
  title: currentTemplateName
8516
8615
  },
8517
- isSample ? "Sample \xB7 " : "",
8616
+ isSample ? `${t("savebar.sample-prefix", "Sample")} \xB7 ` : "",
8518
8617
  /* @__PURE__ */ React57.createElement(Box, { component: "span", sx: { color: "text.primary", fontWeight: 600 } }, currentTemplateName)
8519
8618
  ),
8520
- /* @__PURE__ */ React57.createElement(Tooltip, { title: hasOpenRow ? `Save changes${isSample ? " to this sample" : ""}` : "Save as a new template" }, /* @__PURE__ */ React57.createElement(
8619
+ /* @__PURE__ */ React57.createElement(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.createElement(
8521
8620
  Button$1,
8522
8621
  {
8523
8622
  variant: "contained",
@@ -8528,7 +8627,7 @@ function SaveBar({ loadTemplates, saveAs }) {
8528
8627
  },
8529
8628
  primaryLabel
8530
8629
  )),
8531
- hasOpenRow && saveAs && /* @__PURE__ */ React57.createElement(Tooltip, { title: "Save as a new template" }, /* @__PURE__ */ React57.createElement(
8630
+ hasOpenRow && saveAs && /* @__PURE__ */ React57.createElement(Tooltip, { title: t("savebar.save-as-new-template", "Save as a new template") }, /* @__PURE__ */ React57.createElement(
8532
8631
  Button$1,
8533
8632
  {
8534
8633
  variant: "outlined",
@@ -8540,9 +8639,9 @@ function SaveBar({ loadTemplates, saveAs }) {
8540
8639
  },
8541
8640
  sx: { borderRadius: 999, textTransform: "none", px: 2, fontSize: 14 }
8542
8641
  },
8543
- "Save as\u2026"
8642
+ t("savebar.save-as", "Save as\u2026")
8544
8643
  )),
8545
- saveAs && /* @__PURE__ */ React57.createElement(Tooltip, { title: "Start a fresh template" }, /* @__PURE__ */ React57.createElement(
8644
+ saveAs && /* @__PURE__ */ React57.createElement(Tooltip, { title: t("savebar.start-fresh", "Start a fresh template") }, /* @__PURE__ */ React57.createElement(
8546
8645
  Button$1,
8547
8646
  {
8548
8647
  variant: "text",
@@ -8554,7 +8653,7 @@ function SaveBar({ loadTemplates, saveAs }) {
8554
8653
  },
8555
8654
  sx: { borderRadius: 999, textTransform: "none", px: 2, fontSize: 14, color: "text.secondary" }
8556
8655
  },
8557
- "New"
8656
+ t("savebar.new", "New")
8558
8657
  ))
8559
8658
  )
8560
8659
  ), /* @__PURE__ */ React57.createElement(
@@ -8572,6 +8671,64 @@ function SaveBar({ loadTemplates, saveAs }) {
8572
8671
  }
8573
8672
  ));
8574
8673
  }
8674
+ function isMac() {
8675
+ if (typeof navigator === "undefined") return false;
8676
+ const platform = (navigator.platform || "").toLowerCase();
8677
+ if (platform.includes("mac")) return true;
8678
+ const ua = (navigator.userAgent || "").toLowerCase();
8679
+ return ua.includes("mac") && !ua.includes("windows");
8680
+ }
8681
+ function isEditableTarget(target) {
8682
+ if (!(target instanceof HTMLElement)) return false;
8683
+ const tag = target.tagName;
8684
+ if (tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT") return true;
8685
+ if (target.isContentEditable) return true;
8686
+ return false;
8687
+ }
8688
+ function UndoRedoButtons() {
8689
+ const canUndo = useCanUndo();
8690
+ const canRedo = useCanRedo();
8691
+ const mac = isMac();
8692
+ const modKey = mac ? "\u2318" : "Ctrl";
8693
+ const undoHint = `${modKey}+Z`;
8694
+ const redoHint = mac ? `${modKey}+\u21E7+Z` : `${modKey}+Shift+Z / ${modKey}+Y`;
8695
+ useEffect(() => {
8696
+ const onKeyDown = (e) => {
8697
+ const mod = mac ? e.metaKey : e.ctrlKey;
8698
+ if (!mod) return;
8699
+ if (isEditableTarget(e.target)) return;
8700
+ const key = e.key.toLowerCase();
8701
+ if (key === "z" && !e.shiftKey) {
8702
+ e.preventDefault();
8703
+ undo();
8704
+ } else if (key === "z" && e.shiftKey || key === "y" && !mac) {
8705
+ e.preventDefault();
8706
+ redo();
8707
+ }
8708
+ };
8709
+ window.addEventListener("keydown", onKeyDown);
8710
+ return () => window.removeEventListener("keydown", onKeyDown);
8711
+ }, [mac]);
8712
+ return /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 0.5, alignItems: "center" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: `${t("undo.tooltip", "Undo")} (${undoHint})` }, /* @__PURE__ */ React57.createElement("span", null, /* @__PURE__ */ React57.createElement(
8713
+ IconButton,
8714
+ {
8715
+ size: "small",
8716
+ onClick: undo,
8717
+ disabled: !canUndo,
8718
+ "aria-label": t("undo.label", "Undo")
8719
+ },
8720
+ /* @__PURE__ */ React57.createElement(UndoOutlined, { fontSize: "small" })
8721
+ ))), /* @__PURE__ */ React57.createElement(Tooltip, { title: `${t("redo.tooltip", "Redo")} (${redoHint})` }, /* @__PURE__ */ React57.createElement("span", null, /* @__PURE__ */ React57.createElement(
8722
+ IconButton,
8723
+ {
8724
+ size: "small",
8725
+ onClick: redo,
8726
+ disabled: !canRedo,
8727
+ "aria-label": t("redo.label", "Redo")
8728
+ },
8729
+ /* @__PURE__ */ React57.createElement(RedoOutlined, { fontSize: "small" })
8730
+ ))));
8731
+ }
8575
8732
  function SubjectInput() {
8576
8733
  var _a;
8577
8734
  const document2 = useDocument();
@@ -8800,7 +8957,7 @@ function ImageDropPasteHandler({ enabled, children }) {
8800
8957
 
8801
8958
  // src/app/email-canvas/index.tsx
8802
8959
  var WORKSPACE_SOLID = "#e7e8ec";
8803
- var WORKSPACE_CHECKERBOARD = "repeating-conic-gradient(#eceef2 0% 25%, #dfe1e6 0% 50%) 50% / 24px 24px";
8960
+ var WORKSPACE_CHECKERBOARD = "repeating-conic-gradient(#eceef2 0% 25%, #dfe1e6 0% 50%) 50% / 12px 12px";
8804
8961
  function TemplatePanel2({ loadTemplates, saveAs, samplesDrawerEnabled = true }) {
8805
8962
  const document2 = useDocument();
8806
8963
  const selectedMainTab = useSelectedMainTab();
@@ -8870,7 +9027,7 @@ function TemplatePanel2({ loadTemplates, saveAs, samplesDrawerEnabled = true })
8870
9027
  alignItems: "center"
8871
9028
  },
8872
9029
  samplesDrawerEnabled && /* @__PURE__ */ React57.createElement(ToggleSamplesPanelButton, null),
8873
- /* @__PURE__ */ React57.createElement(Stack, { px: 2, direction: "row", gap: 2, width: "100%", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57.createElement(MainTabsGroup, null)), /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57.createElement(ToggleButtonGroup, { value: selectedScreenSize, exclusive: true, size: "small", onChange: handleScreenSizeChange }, /* @__PURE__ */ React57.createElement(ToggleButton, { value: "desktop" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Desktop view" }, /* @__PURE__ */ React57.createElement(MonitorOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(ToggleButton, { value: "mobile" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Mobile view" }, /* @__PURE__ */ React57.createElement(PhoneIphoneOutlined, { fontSize: "small" })))))),
9030
+ /* @__PURE__ */ React57.createElement(Stack, { px: 2, direction: "row", gap: 2, width: "100%", justifyContent: "space-between", alignItems: "center" }, /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 2 }, /* @__PURE__ */ React57.createElement(MainTabsGroup, null)), /* @__PURE__ */ React57.createElement(Stack, { direction: "row", spacing: 2, alignItems: "center" }, selectedMainTab === "editor" && /* @__PURE__ */ React57.createElement(UndoRedoButtons, null), /* @__PURE__ */ React57.createElement(ToggleButtonGroup, { value: selectedScreenSize, exclusive: true, size: "small", onChange: handleScreenSizeChange }, /* @__PURE__ */ React57.createElement(ToggleButton, { value: "desktop" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Desktop view" }, /* @__PURE__ */ React57.createElement(MonitorOutlined, { fontSize: "small" }))), /* @__PURE__ */ React57.createElement(ToggleButton, { value: "mobile" }, /* @__PURE__ */ React57.createElement(Tooltip, { title: "Mobile view" }, /* @__PURE__ */ React57.createElement(PhoneIphoneOutlined, { fontSize: "small" })))))),
8874
9031
  /* @__PURE__ */ React57.createElement(ToggleInspectorPanelButton, null)
8875
9032
  ), selectedMainTab === "editor" && /* @__PURE__ */ React57.createElement(SubjectInput, null), selectedMainTab === "preview" && /* @__PURE__ */ React57.createElement(SubjectPreview, null), /* @__PURE__ */ React57.createElement(ImageDropPasteHandler, { enabled: selectedMainTab === "editor" }, /* @__PURE__ */ React57.createElement(
8876
9033
  Box,
@@ -9031,8 +9188,10 @@ var EmailEditor = forwardRef((props, ref) => {
9031
9188
  uploadImage,
9032
9189
  loadImages,
9033
9190
  deleteImage,
9034
- theme
9191
+ theme,
9192
+ locale
9035
9193
  } = props;
9194
+ activateLocale(locale);
9036
9195
  const resolvedTemplate = useMemo(
9037
9196
  () => typeof initialTemplateProp === "string" ? htmlToEditorConfig(initialTemplateProp) : initialTemplateProp,
9038
9197
  [initialTemplateProp]
@@ -9049,7 +9208,7 @@ var EmailEditor = forwardRef((props, ref) => {
9049
9208
  () => ({ uploadImage, loadImages, deleteImage }),
9050
9209
  [uploadImage, loadImages, deleteImage]
9051
9210
  );
9052
- return /* @__PURE__ */ React57.createElement(ThemeProvider, { theme: theme || theme_default }, /* @__PURE__ */ React57.createElement(CssBaseline, null), /* @__PURE__ */ React57.createElement("div", { style: { height: "100%", overflow: "auto" } }, /* @__PURE__ */ React57.createElement(SnackbarProvider, null, /* @__PURE__ */ React57.createElement(ImageCallbacksProvider, { callbacks: imageCallbacks }, /* @__PURE__ */ React57.createElement(
9211
+ return /* @__PURE__ */ React57.createElement(I18nProvider, { i18n }, /* @__PURE__ */ React57.createElement(ThemeProvider, { theme: theme || theme_default }, /* @__PURE__ */ React57.createElement(CssBaseline, null), /* @__PURE__ */ React57.createElement("div", { style: { height: "100%", overflow: "auto" } }, /* @__PURE__ */ React57.createElement(SnackbarProvider, null, /* @__PURE__ */ React57.createElement(ImageCallbacksProvider, { callbacks: imageCallbacks }, /* @__PURE__ */ React57.createElement(
9053
9212
  EmailEditorProvider,
9054
9213
  {
9055
9214
  initialTemplate: resolvedTemplate,
@@ -9077,11 +9236,11 @@ var EmailEditor = forwardRef((props, ref) => {
9077
9236
  onChange
9078
9237
  }
9079
9238
  )
9080
- )))));
9239
+ ))))));
9081
9240
  });
9082
9241
  EmailEditor.displayName = "EmailEditor";
9083
9242
  EmailEditorInternal.displayName = "EmailEditorInternal";
9084
9243
 
9085
- export { avatar_default as Avatar, AvatarProps, AvatarPropsDefaults, AvatarPropsSchema, BlockConfiguration, button_default as Button, ButtonProps, ButtonPropsDefaults, ButtonPropsSchema, columns_container_default as ColumnsContainer, ColumnsContainerProps, ColumnsContainerPropsSchema, ColumnsContainerReader, container_default as Container, ContainerProps, ContainerPropsSchema, ContainerReader, divider_default as Divider, DividerProps, DividerPropsDefaults, DividerPropsSchema, DocumentBlocksDictionary, EmailEditor, EmailEditorProvider, EmailLayoutPropsSchema, EmailLayoutReader, EmailMarkdown, heading_default as Heading, HeadingProps, HeadingPropsDefaults, HeadingPropsSchema, html_default as Html, HtmlProps, HtmlPropsSchema, image_default as Image, ImageProps, ImagePropsSchema, Reader, ReaderBlock, ReaderBlockSchema, ReaderDocumentSchema, signature_default as Signature, SignatureProps, SignaturePropsDefaults, SignaturePropsSchema, spacer_default as Spacer, SpacerProps, SpacerPropsDefaults, SpacerPropsSchema, TemplateVariableSchema, Text, TextProps, TextPropsDefaults, TextPropsSchema, buildBlockComponent, buildBlockConfigurationDictionary, buildBlockConfigurationSchema, editorHandlebars, evaluateHandlebars, htmlToEditorConfig, renderToStaticMarkup, renderToText, theme_default as theme, useEmailEditor };
9244
+ export { avatar_default as Avatar, AvatarProps, AvatarPropsDefaults, AvatarPropsSchema, BlockConfiguration, button_default as Button, ButtonProps, ButtonPropsDefaults, ButtonPropsSchema, columns_container_default as ColumnsContainer, ColumnsContainerProps, ColumnsContainerPropsSchema, ColumnsContainerReader, container_default as Container, ContainerProps, ContainerPropsSchema, ContainerReader, divider_default as Divider, DividerProps, DividerPropsDefaults, DividerPropsSchema, DocumentBlocksDictionary, EmailEditor, EmailEditorProvider, EmailLayoutPropsSchema, EmailLayoutReader, EmailMarkdown, heading_default as Heading, HeadingProps, HeadingPropsDefaults, HeadingPropsSchema, html_default as Html, HtmlProps, HtmlPropsSchema, image_default as Image, ImageProps, ImagePropsSchema, Reader, ReaderBlock, ReaderBlockSchema, ReaderDocumentSchema, SUPPORTED_LOCALES, signature_default as Signature, SignatureProps, SignaturePropsDefaults, SignaturePropsSchema, spacer_default as Spacer, SpacerProps, SpacerPropsDefaults, SpacerPropsSchema, TemplateVariableSchema, Text, TextProps, TextPropsDefaults, TextPropsSchema, buildBlockComponent, buildBlockConfigurationDictionary, buildBlockConfigurationSchema, editorHandlebars, evaluateHandlebars, htmlToEditorConfig, renderToStaticMarkup, renderToText, theme_default as theme, useEmailEditor };
9086
9245
  //# sourceMappingURL=index.js.map
9087
9246
  //# sourceMappingURL=index.js.map