@fonderie/react-courier-admin-screens 0.7.0 → 0.9.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
@@ -1,11 +1,17 @@
1
1
  // src/screens/TemplateEditorScreen.tsx
2
+ import {
3
+ createAdminT,
4
+ formatAdminDate,
5
+ groupTemplatesByType,
6
+ missingTemplateLocales
7
+ } from "@fonderie/client";
2
8
  import {
3
9
  useTemplate,
4
10
  useTemplatePreview,
5
11
  useTemplateRevisions,
6
12
  useTemplates
7
13
  } from "@fonderie/react-courier-admin";
8
- import { useCallback, useEffect, useRef, useState } from "react";
14
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
9
15
  import { jsx, jsxs } from "react/jsx-runtime";
10
16
  var IMPLICIT = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
11
17
  var PREVIEW_DEBOUNCE_MS = 500;
@@ -16,10 +22,14 @@ function TemplateEditorScreen({
16
22
  onSaved,
17
23
  system = false,
18
24
  onDeleted,
19
- onAddLocale
25
+ onAddLocale,
26
+ onSelectLocale,
27
+ uiLocale
20
28
  }) {
29
+ const t = useMemo(() => createAdminT(uiLocale), [uiLocale]);
21
30
  const { template, isLoading, error, refresh } = useTemplate(client, type, locale);
22
- const { saveTemplate, removeTemplate } = useTemplates(client);
31
+ const { templates, saveTemplate, removeTemplate } = useTemplates(client);
32
+ const siblings = groupTemplatesByType(templates).find((g) => g.type === type)?.entries ?? [];
23
33
  const [isDeleting, setIsDeleting] = useState(false);
24
34
  const { revisions, rollback } = useTemplateRevisions(client, type, locale);
25
35
  const [isSaving, setIsSaving] = useState(false);
@@ -43,13 +53,13 @@ function TemplateEditorScreen({
43
53
  try {
44
54
  const parsed = JSON.parse(sampleJson || "{}");
45
55
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
46
- setSampleError("Sample data must be a JSON object.");
56
+ setSampleError(t("templates.editor.sampleNotObject"));
47
57
  return;
48
58
  }
49
59
  data = parsed;
50
60
  setSampleError(null);
51
61
  } catch {
52
- setSampleError("Sample data is not valid JSON.");
62
+ setSampleError(t("templates.editor.sampleInvalid"));
53
63
  return;
54
64
  }
55
65
  const result = await renderPreview(
@@ -62,7 +72,7 @@ function TemplateEditorScreen({
62
72
  const seeded = { ...data, ...Object.fromEntries(missing.map((v) => [v, v])) };
63
73
  setSampleJson(JSON.stringify(seeded, null, 2));
64
74
  }
65
- }, [renderPreview, type, text, subject, html, locale, sampleJson]);
75
+ }, [renderPreview, type, text, subject, html, locale, sampleJson, t]);
66
76
  const runRef = useRef(run);
67
77
  runRef.current = run;
68
78
  const loadedType = template ? `${type}:${locale ?? ""}` : null;
@@ -90,10 +100,13 @@ function TemplateEditorScreen({
90
100
  setIsSaving(false);
91
101
  }
92
102
  };
103
+ const switchTo = (entry) => {
104
+ if (dirty && !window.confirm(t("templates.editor.discardChanges"))) return;
105
+ onSelectLocale?.(entry);
106
+ };
93
107
  const handleDelete = async () => {
94
- const which = locale ? `the ${locale} version of "${type}"` : `"${type}"`;
95
- const fallback = locale ? " People in that locale will receive the default version." : "";
96
- if (!window.confirm(`Delete ${which}?${fallback}`)) return;
108
+ const message = locale ? t("templates.editor.confirmDeleteLocale", { type, locale }) : t("templates.editor.confirmDelete", { type });
109
+ if (!window.confirm(message)) return;
97
110
  setIsDeleting(true);
98
111
  setSaveError(null);
99
112
  try {
@@ -105,32 +118,54 @@ function TemplateEditorScreen({
105
118
  setIsDeleting(false);
106
119
  }
107
120
  };
108
- if (isLoading) return /* @__PURE__ */ jsx("p", { style: styles.status, children: "Loading template\u2026" });
121
+ if (isLoading) return /* @__PURE__ */ jsx("p", { style: styles.status, children: t("templates.editor.loading") });
109
122
  if (error)
110
123
  return /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: error.explanation });
111
124
  return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
112
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, flexWrap: "wrap" }, children: [
113
- /* @__PURE__ */ jsx("h1", { style: styles.title, children: type }),
114
- onAddLocale && !locale ? /* @__PURE__ */ jsx(
125
+ /* @__PURE__ */ jsx("h1", { style: styles.title, children: type }),
126
+ onSelectLocale || onAddLocale ? /* @__PURE__ */ jsxs("div", { style: styles.tabBar, children: [
127
+ onSelectLocale ? (
128
+ // Each tab opens another stored row, so this is navigation, not an
129
+ // ARIA tablist: plain buttons, the open one marked aria-current.
130
+ /* @__PURE__ */ jsx("nav", { "aria-label": t("templates.editor.locales"), style: styles.tabs, children: siblings.map((entry) => {
131
+ const selected = entry.locale === (locale ?? null);
132
+ return /* @__PURE__ */ jsx(
133
+ "button",
134
+ {
135
+ type: "button",
136
+ "aria-current": selected ? "true" : void 0,
137
+ onClick: () => selected ? void 0 : switchTo(entry),
138
+ style: selected ? styles.tabSelected : entry.active ? styles.tab : styles.tabInactive,
139
+ title: entry.active ? void 0 : t("common.status.inactive"),
140
+ children: entry.locale ?? t("templates.defaultChip")
141
+ },
142
+ entry.locale ?? ""
143
+ );
144
+ }) })
145
+ ) : null,
146
+ onAddLocale ? /* @__PURE__ */ jsx(
115
147
  "button",
116
148
  {
117
149
  type: "button",
118
150
  style: { ...styles.rollbackButton, marginLeft: "auto" },
119
- onClick: () => onAddLocale(type),
120
- children: "+ Add locale"
151
+ onClick: () => {
152
+ if (dirty && !window.confirm(t("templates.editor.discardChanges"))) return;
153
+ onAddLocale(type, { locales: missingTemplateLocales(templates, type) });
154
+ },
155
+ children: t("templates.editor.addLocale")
121
156
  }
122
157
  ) : null
123
- ] }),
158
+ ] }) : null,
124
159
  /* @__PURE__ */ jsxs("p", { style: styles.meta, children: [
125
- /* @__PURE__ */ jsx("strong", { children: locale ?? "default locale" }),
160
+ /* @__PURE__ */ jsx("strong", { children: locale ?? t("templates.defaultLocale") }),
126
161
  " \xB7 v",
127
162
  template?.version ?? 1,
128
- system ? " \xB7 built-in email: edit or roll back, it cannot be deleted" : ""
163
+ system ? ` \xB7 ${t("templates.editor.builtInNote")}` : ""
129
164
  ] }),
130
165
  /* @__PURE__ */ jsxs("div", { style: styles.split, children: [
131
166
  /* @__PURE__ */ jsxs("div", { style: styles.column, children: [
132
167
  /* @__PURE__ */ jsxs("form", { style: styles.form, onSubmit: handleSubmit, children: [
133
- /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-subject", children: "Subject" }),
168
+ /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-subject", children: t("templates.subject") }),
134
169
  /* @__PURE__ */ jsx(
135
170
  "input",
136
171
  {
@@ -140,7 +175,7 @@ function TemplateEditorScreen({
140
175
  onChange: (event) => setSubject(event.target.value)
141
176
  }
142
177
  ),
143
- /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-html", children: "HTML body" }),
178
+ /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-html", children: t("templates.htmlBody") }),
144
179
  /* @__PURE__ */ jsx(
145
180
  "textarea",
146
181
  {
@@ -151,7 +186,7 @@ function TemplateEditorScreen({
151
186
  rows: 8
152
187
  }
153
188
  ),
154
- /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-text", children: "Plain-text body" }),
189
+ /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-text", children: t("templates.textBody") }),
155
190
  /* @__PURE__ */ jsx(
156
191
  "textarea",
157
192
  {
@@ -172,10 +207,10 @@ function TemplateEditorScreen({
172
207
  onChange: (event) => setActive(event.target.checked)
173
208
  }
174
209
  ),
175
- "Active"
210
+ t("templates.editor.active")
176
211
  ] }),
177
212
  saveError?.reason === "VERSION_CONFLICT" ? /* @__PURE__ */ jsxs("p", { style: styles.error, role: "alert", children: [
178
- "Someone changed this template since you opened it. Reload to see their change, then edit again.",
213
+ t("templates.editor.conflict"),
179
214
  " ",
180
215
  /* @__PURE__ */ jsx(
181
216
  "button",
@@ -186,7 +221,7 @@ function TemplateEditorScreen({
186
221
  setSaveError(null);
187
222
  void refresh();
188
223
  },
189
- children: "Reload"
224
+ children: t("common.reload")
190
225
  }
191
226
  )
192
227
  ] }) : saveError ? /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: saveError.explanation }) : null,
@@ -197,10 +232,10 @@ function TemplateEditorScreen({
197
232
  type: "submit",
198
233
  disabled: isSaving || !dirty,
199
234
  style: dirty ? styles.button : styles.buttonDisabled,
200
- children: isSaving ? "Saving\u2026" : "Save"
235
+ children: isSaving ? t("common.saving") : t("common.save")
201
236
  }
202
237
  ),
203
- !dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: "No changes to save" }),
238
+ !dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: t("templates.editor.noChanges") }),
204
239
  !system ? /* @__PURE__ */ jsx(
205
240
  "button",
206
241
  {
@@ -214,12 +249,12 @@ function TemplateEditorScreen({
214
249
  color: "var(--fonderie-danger,#e00)",
215
250
  borderColor: "color-mix(in srgb, var(--fonderie-danger,#e00) 40%, transparent)"
216
251
  },
217
- children: isDeleting ? "Deleting\u2026" : "Delete"
252
+ children: isDeleting ? t("common.deleting") : t("common.delete")
218
253
  }
219
254
  ) : null
220
255
  ] })
221
256
  ] }),
222
- /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-sample", children: "Sample data" }),
257
+ /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-sample", children: t("templates.editor.sampleData") }),
223
258
  /* @__PURE__ */ jsx(
224
259
  "textarea",
225
260
  {
@@ -235,8 +270,8 @@ function TemplateEditorScreen({
235
270
  ] }),
236
271
  /* @__PURE__ */ jsxs("div", { style: styles.previewColumn, children: [
237
272
  /* @__PURE__ */ jsxs("div", { style: styles.previewHeader, children: [
238
- /* @__PURE__ */ jsx("span", { style: styles.label, children: "Preview" }),
239
- /* @__PURE__ */ jsx("span", { style: styles.meta, children: isPreviewing ? "Rendering\u2026" : "Live" })
273
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: t("templates.editor.preview") }),
274
+ /* @__PURE__ */ jsx("span", { style: styles.meta, children: isPreviewing ? t("templates.editor.rendering") : t("templates.editor.live") })
240
275
  ] }),
241
276
  previewError && /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: previewError.explanation }),
242
277
  preview?.subject && /* @__PURE__ */ jsx("p", { style: styles.previewSubject, children: preview.subject }),
@@ -247,26 +282,26 @@ function TemplateEditorScreen({
247
282
  /* @__PURE__ */ jsx(
248
283
  "iframe",
249
284
  {
250
- title: "Template preview",
285
+ title: t("templates.editor.previewTitle"),
251
286
  style: styles.previewFrame,
252
287
  sandbox: "",
253
288
  srcDoc: preview.html
254
289
  }
255
290
  )
256
- ) : preview ? /* @__PURE__ */ jsx("pre", { style: styles.previewText, children: preview.text }) : /* @__PURE__ */ jsx("p", { style: styles.meta, children: "Nothing rendered yet." })
291
+ ) : preview ? /* @__PURE__ */ jsx("pre", { style: styles.previewText, children: preview.text }) : /* @__PURE__ */ jsx("p", { style: styles.meta, children: t("templates.editor.nothingRendered") })
257
292
  ] })
258
293
  ] }),
259
294
  revisions.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles.revisions, children: [
260
- /* @__PURE__ */ jsx("h2", { style: styles.subtitle, children: "History" }),
295
+ /* @__PURE__ */ jsx("h2", { style: styles.subtitle, children: t("templates.editor.history") }),
261
296
  /* @__PURE__ */ jsx("ul", { style: styles.list, children: revisions.map((rev) => /* @__PURE__ */ jsxs("li", { style: styles.row, children: [
262
297
  /* @__PURE__ */ jsxs("span", { children: [
263
298
  "v",
264
299
  rev.version,
265
300
  " \u2014 ",
266
- rev.actor ?? "unknown",
301
+ rev.actor ?? t("templates.editor.unknownActor"),
267
302
  " \u2014",
268
303
  " ",
269
- new Date(rev.createdAt).toLocaleString()
304
+ formatAdminDate(rev.createdAt, uiLocale)
270
305
  ] }),
271
306
  /* @__PURE__ */ jsx(
272
307
  "button",
@@ -274,15 +309,45 @@ function TemplateEditorScreen({
274
309
  type: "button",
275
310
  onClick: () => rollback(rev.version),
276
311
  style: styles.rollbackButton,
277
- children: "Roll back"
312
+ children: t("templates.editor.rollBack")
278
313
  }
279
314
  )
280
315
  ] }, rev.version)) })
281
316
  ] })
282
317
  ] });
283
318
  }
319
+ var tab = {
320
+ height: 32,
321
+ padding: "0 10px",
322
+ background: "none",
323
+ border: "none",
324
+ borderBottom: "2px solid transparent",
325
+ color: "var(--fonderie-text-muted,#5c5c5c)",
326
+ fontSize: 13,
327
+ fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)',
328
+ cursor: "pointer"
329
+ };
284
330
  var styles = {
285
331
  container: { padding: "8px 40px 64px", maxWidth: 1400, boxSizing: "border-box" },
332
+ // One tab per locale of this email, underlined like a document's tabs.
333
+ tabBar: {
334
+ display: "flex",
335
+ alignItems: "flex-end",
336
+ gap: 12,
337
+ borderBottom: "1px solid var(--fonderie-border,#e0e0e0)",
338
+ margin: "4px 0 12px",
339
+ paddingBottom: 6
340
+ },
341
+ tabs: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: -7 },
342
+ tab,
343
+ tabSelected: {
344
+ ...tab,
345
+ color: "var(--fonderie-text,#171717)",
346
+ fontWeight: 600,
347
+ borderBottomColor: "var(--fonderie-text,#171717)",
348
+ cursor: "default"
349
+ },
350
+ tabInactive: { ...tab, opacity: 0.55, textDecoration: "line-through" },
286
351
  // Two columns, always: the editor on the left, the rendered result on the
287
352
  // right. Stacking the preview under a long form hid it below the fold.
288
353
  split: {
@@ -450,55 +515,78 @@ var styles = {
450
515
  };
451
516
 
452
517
  // src/screens/TemplateListScreen.tsx
518
+ import {
519
+ createAdminT as createAdminT2,
520
+ groupTemplatesByType as groupTemplatesByType2
521
+ } from "@fonderie/client";
453
522
  import { useTemplates as useTemplates2 } from "@fonderie/react-courier-admin";
454
523
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
455
524
  function TemplateListScreen({
456
525
  client,
457
526
  onSelectTemplate,
458
- onCreateTemplate
527
+ onCreateTemplate,
528
+ locale
459
529
  }) {
530
+ const t = createAdminT2(locale);
460
531
  const { templates, isLoading, error } = useTemplates2(client);
532
+ const groups = groupTemplatesByType2(templates);
461
533
  return /* @__PURE__ */ jsxs2("div", { style: styles2.container, children: [
462
534
  /* @__PURE__ */ jsxs2(
463
535
  "div",
464
536
  {
465
537
  style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 },
466
538
  children: [
467
- /* @__PURE__ */ jsx2("h1", { style: styles2.title, children: "Templates" }),
539
+ /* @__PURE__ */ jsx2("h1", { style: styles2.title, children: t("templates.list.title") }),
468
540
  onCreateTemplate ? /* @__PURE__ */ jsx2(
469
541
  "button",
470
542
  {
471
543
  type: "button",
472
544
  onClick: () => onCreateTemplate({
473
545
  locales: [
474
- ...new Set(templates.map((t) => t.locale).filter((l) => !!l))
546
+ ...new Set(templates.map((row) => row.locale).filter((l) => !!l))
475
547
  ]
476
548
  }),
477
549
  style: styles2.newButton,
478
- children: "New template"
550
+ children: t("templates.list.newTemplate")
479
551
  }
480
552
  ) : null
481
553
  ]
482
554
  }
483
555
  ),
484
- /* @__PURE__ */ jsx2("p", { style: styles2.hint, children: "Every email the app sends. Open one to edit its copy and preview it live." }),
485
- isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: "Loading templates\u2026" }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsx2("ul", { style: styles2.list, children: templates.map((template) => /* @__PURE__ */ jsx2("li", { style: styles2.row, children: /* @__PURE__ */ jsxs2(
486
- "button",
487
- {
488
- type: "button",
489
- onClick: () => onSelectTemplate?.(template),
490
- style: styles2.rowButton,
491
- children: [
492
- /* @__PURE__ */ jsx2("span", { style: styles2.type, children: template.type }),
493
- /* @__PURE__ */ jsx2("span", { style: styles2.locale, children: template.locale ?? "default locale" }),
494
- template.system ? /* @__PURE__ */ jsx2("span", { style: styles2.builtIn, children: "built-in" }) : null,
495
- /* @__PURE__ */ jsxs2("span", { style: template.active ? styles2.active : styles2.inactive, children: [
496
- /* @__PURE__ */ jsx2("span", { style: styles2.dot }),
497
- template.active ? "active" : "inactive"
498
- ] })
499
- ]
500
- }
501
- ) }, `${template.type}:${template.locale ?? "base"}`)) })
556
+ /* @__PURE__ */ jsx2("p", { style: styles2.hint, children: t("templates.list.hint") }),
557
+ isLoading ? /* @__PURE__ */ jsx2("p", { style: styles2.status, children: t("templates.list.loading") }) : error ? /* @__PURE__ */ jsx2("p", { style: styles2.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsx2("ul", { style: styles2.list, children: groups.map((group) => /* @__PURE__ */ jsxs2("li", { style: styles2.row, children: [
558
+ /* @__PURE__ */ jsxs2(
559
+ "button",
560
+ {
561
+ type: "button",
562
+ onClick: () => onSelectTemplate?.(group.primary),
563
+ style: styles2.rowButton,
564
+ children: [
565
+ /* @__PURE__ */ jsx2("span", { style: styles2.type, children: group.type }),
566
+ group.system ? /* @__PURE__ */ jsx2("span", { style: styles2.builtIn, children: t("templates.builtInBadge") }) : null,
567
+ /* @__PURE__ */ jsxs2("span", { style: group.primary.active ? styles2.active : styles2.inactive, children: [
568
+ /* @__PURE__ */ jsx2("span", { style: styles2.dot }),
569
+ group.primary.active ? t("common.status.active") : t("common.status.inactive")
570
+ ] })
571
+ ]
572
+ }
573
+ ),
574
+ /* @__PURE__ */ jsx2("span", { style: styles2.chips, children: group.entries.map((entry) => {
575
+ const name = entry.locale ?? t("templates.defaultChip");
576
+ return /* @__PURE__ */ jsx2(
577
+ "button",
578
+ {
579
+ type: "button",
580
+ onClick: () => onSelectTemplate?.(entry),
581
+ style: entry.active ? styles2.chip : styles2.chipInactive,
582
+ "aria-label": t("templates.list.openLocale", { type: group.type, locale: name }),
583
+ title: entry.active ? void 0 : t("common.status.inactive"),
584
+ children: name
585
+ },
586
+ entry.locale ?? ""
587
+ );
588
+ }) })
589
+ ] }, group.type)) })
502
590
  ] });
503
591
  }
504
592
  var pill = {
@@ -512,6 +600,18 @@ var pill = {
512
600
  lineHeight: 1.5,
513
601
  minWidth: 64
514
602
  };
603
+ var chip = {
604
+ height: 24,
605
+ boxSizing: "border-box",
606
+ padding: "0 8px",
607
+ borderRadius: 6,
608
+ border: "1px solid var(--fonderie-border,#e0e0e0)",
609
+ background: "var(--fonderie-surface,#fff)",
610
+ color: "var(--fonderie-text,#171717)",
611
+ fontSize: 12,
612
+ fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)',
613
+ cursor: "pointer"
614
+ };
515
615
  var styles2 = {
516
616
  container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
517
617
  title: {
@@ -534,9 +634,16 @@ var styles2 = {
534
634
  overflow: "hidden",
535
635
  boxShadow: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
536
636
  },
537
- row: { borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)" },
637
+ row: {
638
+ display: "flex",
639
+ alignItems: "center",
640
+ gap: 8,
641
+ paddingRight: 16,
642
+ borderBottom: "1px solid var(--fonderie-border-light,#f5f5f5)"
643
+ },
538
644
  rowButton: {
539
- width: "100%",
645
+ flex: 1,
646
+ minWidth: 0,
540
647
  display: "flex",
541
648
  justifyContent: "space-between",
542
649
  alignItems: "center",
@@ -555,7 +662,9 @@ var styles2 = {
555
662
  flex: 1,
556
663
  fontFamily: 'var(--fonderie-mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace)'
557
664
  },
558
- locale: { fontSize: 12.5, color: "var(--fonderie-text-muted,#5c5c5c)" },
665
+ chips: { display: "flex", flexWrap: "wrap", justifyContent: "flex-end", gap: 6 },
666
+ chip,
667
+ chipInactive: { ...chip, opacity: 0.5, textDecoration: "line-through" },
559
668
  dot: { width: 6, height: 6, borderRadius: 999, background: "currentColor" },
560
669
  builtIn: {
561
670
  fontSize: 11,
@@ -593,7 +702,10 @@ var styles2 = {
593
702
  };
594
703
 
595
704
  // src/screens/TemplateCreateScreen.tsx
596
- import { FonderieApiError } from "@fonderie/client";
705
+ import {
706
+ FonderieApiError,
707
+ createAdminT as createAdminT3
708
+ } from "@fonderie/client";
597
709
  import { useEffect as useEffect2, useState as useState2 } from "react";
598
710
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
599
711
  var TYPE_RE = /^[a-z0-9][a-z0-9_-]{0,80}$/;
@@ -602,11 +714,13 @@ function TemplateCreateScreen({
602
714
  client,
603
715
  type,
604
716
  locales = [],
605
- onCreated
717
+ onCreated,
718
+ locale
606
719
  }) {
720
+ const tr = createAdminT3(locale);
607
721
  const addingLocale = type !== void 0;
608
722
  const [newType, setNewType] = useState2("");
609
- const [locale, setLocale] = useState2("");
723
+ const [targetLocale, setTargetLocale] = useState2("");
610
724
  const [subject, setSubject] = useState2("");
611
725
  const [html, setHtml] = useState2("");
612
726
  const [text, setText] = useState2("");
@@ -624,13 +738,11 @@ function TemplateCreateScreen({
624
738
  e.preventDefault();
625
739
  setError(null);
626
740
  const t = addingLocale ? type : newType.trim();
627
- const l = locale.trim() || null;
628
- if (!TYPE_RE.test(t))
629
- return setError("Type: lowercase letters, digits, - and _ (e.g. weekly-digest).");
630
- if (addingLocale && !l) return setError("Choose the locale to add, e.g. fr or fr-CA.");
631
- if (l && !LOCALE_RE.test(l)) return setError("Locale: a language tag such as fr, es or fr-CA.");
632
- if (!text.trim())
633
- return setError("The plain-text body is required \u2014 it is what every email client can show.");
741
+ const l = targetLocale.trim() || null;
742
+ if (!TYPE_RE.test(t)) return setError(tr("templates.create.errorType"));
743
+ if (addingLocale && !l) return setError(tr("templates.create.errorLocaleRequired"));
744
+ if (l && !LOCALE_RE.test(l)) return setError(tr("templates.create.errorLocale"));
745
+ if (!text.trim()) return setError(tr("templates.create.errorText"));
634
746
  setBusy(true);
635
747
  try {
636
748
  const exists = await client.getTemplate(t, l).then(
@@ -638,7 +750,9 @@ function TemplateCreateScreen({
638
750
  (err) => !(err instanceof FonderieApiError && err.status === 404)
639
751
  );
640
752
  if (exists) {
641
- setError(`"${t}"${l ? ` (${l})` : ""} already exists \u2014 open it from the list to edit.`);
753
+ setError(
754
+ l ? tr("templates.create.existsLocale", { type: t, locale: l }) : tr("templates.create.exists", { type: t })
755
+ );
642
756
  return;
643
757
  }
644
758
  const input = { text, active: true };
@@ -653,11 +767,11 @@ function TemplateCreateScreen({
653
767
  }
654
768
  };
655
769
  return /* @__PURE__ */ jsxs3("div", { style: styles3.container, children: [
656
- /* @__PURE__ */ jsx3("h1", { style: styles3.title, children: addingLocale ? `Add a locale to ${type}` : "New template" }),
657
- /* @__PURE__ */ jsx3("p", { style: styles3.meta, children: addingLocale ? "Starts from the default copy. People whose locale matches receive this version; everyone else keeps the default." : "For an email your app sends that has no stored copy yet. The type must match what the app sends." }),
770
+ /* @__PURE__ */ jsx3("h1", { style: styles3.title, children: addingLocale ? tr("templates.create.titleLocale", { type }) : tr("templates.create.titleNew") }),
771
+ /* @__PURE__ */ jsx3("p", { style: styles3.meta, children: addingLocale ? tr("templates.create.hintLocale") : tr("templates.create.hintNew") }),
658
772
  /* @__PURE__ */ jsxs3("form", { style: styles3.form, onSubmit: (e) => void submit(e), children: [
659
773
  !addingLocale ? /* @__PURE__ */ jsxs3(Fragment, { children: [
660
- /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-type", children: "Type" }),
774
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-type", children: tr("templates.create.type") }),
661
775
  /* @__PURE__ */ jsx3(
662
776
  "input",
663
777
  {
@@ -671,25 +785,22 @@ function TemplateCreateScreen({
671
785
  }
672
786
  )
673
787
  ] }) : null,
674
- /* @__PURE__ */ jsxs3("label", { style: styles3.label, htmlFor: "new-template-locale", children: [
675
- "Locale ",
676
- addingLocale ? "" : "(optional \u2014 empty is the default)"
677
- ] }),
788
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-locale", children: addingLocale ? tr("templates.create.locale") : tr("templates.create.localeOptional") }),
678
789
  /* @__PURE__ */ jsx3(
679
790
  "input",
680
791
  {
681
792
  id: "new-template-locale",
682
793
  style: styles3.input,
683
- value: locale,
794
+ value: targetLocale,
684
795
  placeholder: "fr",
685
796
  list: "new-template-locales",
686
797
  autoComplete: "off",
687
798
  spellCheck: false,
688
- onChange: (e) => setLocale(e.target.value)
799
+ onChange: (e) => setTargetLocale(e.target.value)
689
800
  }
690
801
  ),
691
802
  /* @__PURE__ */ jsx3("datalist", { id: "new-template-locales", children: [...new Set(locales)].map((l) => /* @__PURE__ */ jsx3("option", { value: l }, l)) }),
692
- /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-subject", children: "Subject" }),
803
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-subject", children: tr("templates.subject") }),
693
804
  /* @__PURE__ */ jsx3(
694
805
  "input",
695
806
  {
@@ -699,7 +810,7 @@ function TemplateCreateScreen({
699
810
  onChange: (e) => setSubject(e.target.value)
700
811
  }
701
812
  ),
702
- /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-html", children: "HTML body" }),
813
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-html", children: tr("templates.htmlBody") }),
703
814
  /* @__PURE__ */ jsx3(
704
815
  "textarea",
705
816
  {
@@ -709,7 +820,7 @@ function TemplateCreateScreen({
709
820
  onChange: (e) => setHtml(e.target.value)
710
821
  }
711
822
  ),
712
- /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-text", children: "Plain-text body" }),
823
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-text", children: tr("templates.textBody") }),
713
824
  /* @__PURE__ */ jsx3(
714
825
  "textarea",
715
826
  {
@@ -720,7 +831,7 @@ function TemplateCreateScreen({
720
831
  }
721
832
  ),
722
833
  error ? /* @__PURE__ */ jsx3("p", { style: styles3.error, role: "alert", children: error }) : null,
723
- /* @__PURE__ */ jsx3("button", { type: "submit", disabled: busy, style: styles3.button, children: busy ? "Creating\u2026" : addingLocale ? "Add locale" : "Create template" })
834
+ /* @__PURE__ */ jsx3("button", { type: "submit", disabled: busy, style: styles3.button, children: busy ? tr("templates.create.creating") : addingLocale ? tr("templates.create.submitLocale") : tr("templates.create.submitNew") })
724
835
  ] })
725
836
  ] });
726
837
  }