@fonderie/react-courier-admin-screens 0.7.0 → 0.8.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,15 @@
1
1
  // src/screens/TemplateEditorScreen.tsx
2
+ import {
3
+ createAdminT,
4
+ formatAdminDate
5
+ } from "@fonderie/client";
2
6
  import {
3
7
  useTemplate,
4
8
  useTemplatePreview,
5
9
  useTemplateRevisions,
6
10
  useTemplates
7
11
  } from "@fonderie/react-courier-admin";
8
- import { useCallback, useEffect, useRef, useState } from "react";
12
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
9
13
  import { jsx, jsxs } from "react/jsx-runtime";
10
14
  var IMPLICIT = /* @__PURE__ */ new Set(["subject", "preheader", "brandName"]);
11
15
  var PREVIEW_DEBOUNCE_MS = 500;
@@ -16,8 +20,10 @@ function TemplateEditorScreen({
16
20
  onSaved,
17
21
  system = false,
18
22
  onDeleted,
19
- onAddLocale
23
+ onAddLocale,
24
+ uiLocale
20
25
  }) {
26
+ const t = useMemo(() => createAdminT(uiLocale), [uiLocale]);
21
27
  const { template, isLoading, error, refresh } = useTemplate(client, type, locale);
22
28
  const { saveTemplate, removeTemplate } = useTemplates(client);
23
29
  const [isDeleting, setIsDeleting] = useState(false);
@@ -43,13 +49,13 @@ function TemplateEditorScreen({
43
49
  try {
44
50
  const parsed = JSON.parse(sampleJson || "{}");
45
51
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
46
- setSampleError("Sample data must be a JSON object.");
52
+ setSampleError(t("templates.editor.sampleNotObject"));
47
53
  return;
48
54
  }
49
55
  data = parsed;
50
56
  setSampleError(null);
51
57
  } catch {
52
- setSampleError("Sample data is not valid JSON.");
58
+ setSampleError(t("templates.editor.sampleInvalid"));
53
59
  return;
54
60
  }
55
61
  const result = await renderPreview(
@@ -62,7 +68,7 @@ function TemplateEditorScreen({
62
68
  const seeded = { ...data, ...Object.fromEntries(missing.map((v) => [v, v])) };
63
69
  setSampleJson(JSON.stringify(seeded, null, 2));
64
70
  }
65
- }, [renderPreview, type, text, subject, html, locale, sampleJson]);
71
+ }, [renderPreview, type, text, subject, html, locale, sampleJson, t]);
66
72
  const runRef = useRef(run);
67
73
  runRef.current = run;
68
74
  const loadedType = template ? `${type}:${locale ?? ""}` : null;
@@ -91,9 +97,8 @@ function TemplateEditorScreen({
91
97
  }
92
98
  };
93
99
  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;
100
+ const message = locale ? t("templates.editor.confirmDeleteLocale", { type, locale }) : t("templates.editor.confirmDelete", { type });
101
+ if (!window.confirm(message)) return;
97
102
  setIsDeleting(true);
98
103
  setSaveError(null);
99
104
  try {
@@ -105,7 +110,7 @@ function TemplateEditorScreen({
105
110
  setIsDeleting(false);
106
111
  }
107
112
  };
108
- if (isLoading) return /* @__PURE__ */ jsx("p", { style: styles.status, children: "Loading template\u2026" });
113
+ if (isLoading) return /* @__PURE__ */ jsx("p", { style: styles.status, children: t("templates.editor.loading") });
109
114
  if (error)
110
115
  return /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: error.explanation });
111
116
  return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
@@ -117,20 +122,20 @@ function TemplateEditorScreen({
117
122
  type: "button",
118
123
  style: { ...styles.rollbackButton, marginLeft: "auto" },
119
124
  onClick: () => onAddLocale(type),
120
- children: "+ Add locale"
125
+ children: t("templates.editor.addLocale")
121
126
  }
122
127
  ) : null
123
128
  ] }),
124
129
  /* @__PURE__ */ jsxs("p", { style: styles.meta, children: [
125
- /* @__PURE__ */ jsx("strong", { children: locale ?? "default locale" }),
130
+ /* @__PURE__ */ jsx("strong", { children: locale ?? t("templates.defaultLocale") }),
126
131
  " \xB7 v",
127
132
  template?.version ?? 1,
128
- system ? " \xB7 built-in email: edit or roll back, it cannot be deleted" : ""
133
+ system ? ` \xB7 ${t("templates.editor.builtInNote")}` : ""
129
134
  ] }),
130
135
  /* @__PURE__ */ jsxs("div", { style: styles.split, children: [
131
136
  /* @__PURE__ */ jsxs("div", { style: styles.column, children: [
132
137
  /* @__PURE__ */ jsxs("form", { style: styles.form, onSubmit: handleSubmit, children: [
133
- /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-subject", children: "Subject" }),
138
+ /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-subject", children: t("templates.subject") }),
134
139
  /* @__PURE__ */ jsx(
135
140
  "input",
136
141
  {
@@ -140,7 +145,7 @@ function TemplateEditorScreen({
140
145
  onChange: (event) => setSubject(event.target.value)
141
146
  }
142
147
  ),
143
- /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-html", children: "HTML body" }),
148
+ /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-html", children: t("templates.htmlBody") }),
144
149
  /* @__PURE__ */ jsx(
145
150
  "textarea",
146
151
  {
@@ -151,7 +156,7 @@ function TemplateEditorScreen({
151
156
  rows: 8
152
157
  }
153
158
  ),
154
- /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-text", children: "Plain-text body" }),
159
+ /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-text", children: t("templates.textBody") }),
155
160
  /* @__PURE__ */ jsx(
156
161
  "textarea",
157
162
  {
@@ -172,10 +177,10 @@ function TemplateEditorScreen({
172
177
  onChange: (event) => setActive(event.target.checked)
173
178
  }
174
179
  ),
175
- "Active"
180
+ t("templates.editor.active")
176
181
  ] }),
177
182
  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.",
183
+ t("templates.editor.conflict"),
179
184
  " ",
180
185
  /* @__PURE__ */ jsx(
181
186
  "button",
@@ -186,7 +191,7 @@ function TemplateEditorScreen({
186
191
  setSaveError(null);
187
192
  void refresh();
188
193
  },
189
- children: "Reload"
194
+ children: t("common.reload")
190
195
  }
191
196
  )
192
197
  ] }) : saveError ? /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: saveError.explanation }) : null,
@@ -197,10 +202,10 @@ function TemplateEditorScreen({
197
202
  type: "submit",
198
203
  disabled: isSaving || !dirty,
199
204
  style: dirty ? styles.button : styles.buttonDisabled,
200
- children: isSaving ? "Saving\u2026" : "Save"
205
+ children: isSaving ? t("common.saving") : t("common.save")
201
206
  }
202
207
  ),
203
- !dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: "No changes to save" }),
208
+ !dirty && !isSaving && /* @__PURE__ */ jsx("span", { style: styles.meta, children: t("templates.editor.noChanges") }),
204
209
  !system ? /* @__PURE__ */ jsx(
205
210
  "button",
206
211
  {
@@ -214,12 +219,12 @@ function TemplateEditorScreen({
214
219
  color: "var(--fonderie-danger,#e00)",
215
220
  borderColor: "color-mix(in srgb, var(--fonderie-danger,#e00) 40%, transparent)"
216
221
  },
217
- children: isDeleting ? "Deleting\u2026" : "Delete"
222
+ children: isDeleting ? t("common.deleting") : t("common.delete")
218
223
  }
219
224
  ) : null
220
225
  ] })
221
226
  ] }),
222
- /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-sample", children: "Sample data" }),
227
+ /* @__PURE__ */ jsx("label", { style: styles.label, htmlFor: "template-sample", children: t("templates.editor.sampleData") }),
223
228
  /* @__PURE__ */ jsx(
224
229
  "textarea",
225
230
  {
@@ -235,8 +240,8 @@ function TemplateEditorScreen({
235
240
  ] }),
236
241
  /* @__PURE__ */ jsxs("div", { style: styles.previewColumn, children: [
237
242
  /* @__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" })
243
+ /* @__PURE__ */ jsx("span", { style: styles.label, children: t("templates.editor.preview") }),
244
+ /* @__PURE__ */ jsx("span", { style: styles.meta, children: isPreviewing ? t("templates.editor.rendering") : t("templates.editor.live") })
240
245
  ] }),
241
246
  previewError && /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: previewError.explanation }),
242
247
  preview?.subject && /* @__PURE__ */ jsx("p", { style: styles.previewSubject, children: preview.subject }),
@@ -247,26 +252,26 @@ function TemplateEditorScreen({
247
252
  /* @__PURE__ */ jsx(
248
253
  "iframe",
249
254
  {
250
- title: "Template preview",
255
+ title: t("templates.editor.previewTitle"),
251
256
  style: styles.previewFrame,
252
257
  sandbox: "",
253
258
  srcDoc: preview.html
254
259
  }
255
260
  )
256
- ) : preview ? /* @__PURE__ */ jsx("pre", { style: styles.previewText, children: preview.text }) : /* @__PURE__ */ jsx("p", { style: styles.meta, children: "Nothing rendered yet." })
261
+ ) : preview ? /* @__PURE__ */ jsx("pre", { style: styles.previewText, children: preview.text }) : /* @__PURE__ */ jsx("p", { style: styles.meta, children: t("templates.editor.nothingRendered") })
257
262
  ] })
258
263
  ] }),
259
264
  revisions.length > 0 && /* @__PURE__ */ jsxs("div", { style: styles.revisions, children: [
260
- /* @__PURE__ */ jsx("h2", { style: styles.subtitle, children: "History" }),
265
+ /* @__PURE__ */ jsx("h2", { style: styles.subtitle, children: t("templates.editor.history") }),
261
266
  /* @__PURE__ */ jsx("ul", { style: styles.list, children: revisions.map((rev) => /* @__PURE__ */ jsxs("li", { style: styles.row, children: [
262
267
  /* @__PURE__ */ jsxs("span", { children: [
263
268
  "v",
264
269
  rev.version,
265
270
  " \u2014 ",
266
- rev.actor ?? "unknown",
271
+ rev.actor ?? t("templates.editor.unknownActor"),
267
272
  " \u2014",
268
273
  " ",
269
- new Date(rev.createdAt).toLocaleString()
274
+ formatAdminDate(rev.createdAt, uiLocale)
270
275
  ] }),
271
276
  /* @__PURE__ */ jsx(
272
277
  "button",
@@ -274,7 +279,7 @@ function TemplateEditorScreen({
274
279
  type: "button",
275
280
  onClick: () => rollback(rev.version),
276
281
  style: styles.rollbackButton,
277
- children: "Roll back"
282
+ children: t("templates.editor.rollBack")
278
283
  }
279
284
  )
280
285
  ] }, rev.version)) })
@@ -450,13 +455,18 @@ var styles = {
450
455
  };
451
456
 
452
457
  // src/screens/TemplateListScreen.tsx
458
+ import {
459
+ createAdminT as createAdminT2
460
+ } from "@fonderie/client";
453
461
  import { useTemplates as useTemplates2 } from "@fonderie/react-courier-admin";
454
462
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
455
463
  function TemplateListScreen({
456
464
  client,
457
465
  onSelectTemplate,
458
- onCreateTemplate
466
+ onCreateTemplate,
467
+ locale
459
468
  }) {
469
+ const t = createAdminT2(locale);
460
470
  const { templates, isLoading, error } = useTemplates2(client);
461
471
  return /* @__PURE__ */ jsxs2("div", { style: styles2.container, children: [
462
472
  /* @__PURE__ */ jsxs2(
@@ -464,25 +474,25 @@ function TemplateListScreen({
464
474
  {
465
475
  style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 },
466
476
  children: [
467
- /* @__PURE__ */ jsx2("h1", { style: styles2.title, children: "Templates" }),
477
+ /* @__PURE__ */ jsx2("h1", { style: styles2.title, children: t("templates.list.title") }),
468
478
  onCreateTemplate ? /* @__PURE__ */ jsx2(
469
479
  "button",
470
480
  {
471
481
  type: "button",
472
482
  onClick: () => onCreateTemplate({
473
483
  locales: [
474
- ...new Set(templates.map((t) => t.locale).filter((l) => !!l))
484
+ ...new Set(templates.map((row) => row.locale).filter((l) => !!l))
475
485
  ]
476
486
  }),
477
487
  style: styles2.newButton,
478
- children: "New template"
488
+ children: t("templates.list.newTemplate")
479
489
  }
480
490
  ) : null
481
491
  ]
482
492
  }
483
493
  ),
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(
494
+ /* @__PURE__ */ jsx2("p", { style: styles2.hint, children: t("templates.list.hint") }),
495
+ 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: templates.map((template) => /* @__PURE__ */ jsx2("li", { style: styles2.row, children: /* @__PURE__ */ jsxs2(
486
496
  "button",
487
497
  {
488
498
  type: "button",
@@ -490,11 +500,11 @@ function TemplateListScreen({
490
500
  style: styles2.rowButton,
491
501
  children: [
492
502
  /* @__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,
503
+ /* @__PURE__ */ jsx2("span", { style: styles2.locale, children: template.locale ?? t("templates.defaultLocale") }),
504
+ template.system ? /* @__PURE__ */ jsx2("span", { style: styles2.builtIn, children: t("templates.builtInBadge") }) : null,
495
505
  /* @__PURE__ */ jsxs2("span", { style: template.active ? styles2.active : styles2.inactive, children: [
496
506
  /* @__PURE__ */ jsx2("span", { style: styles2.dot }),
497
- template.active ? "active" : "inactive"
507
+ template.active ? t("common.status.active") : t("common.status.inactive")
498
508
  ] })
499
509
  ]
500
510
  }
@@ -593,7 +603,10 @@ var styles2 = {
593
603
  };
594
604
 
595
605
  // src/screens/TemplateCreateScreen.tsx
596
- import { FonderieApiError } from "@fonderie/client";
606
+ import {
607
+ FonderieApiError,
608
+ createAdminT as createAdminT3
609
+ } from "@fonderie/client";
597
610
  import { useEffect as useEffect2, useState as useState2 } from "react";
598
611
  import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
599
612
  var TYPE_RE = /^[a-z0-9][a-z0-9_-]{0,80}$/;
@@ -602,11 +615,13 @@ function TemplateCreateScreen({
602
615
  client,
603
616
  type,
604
617
  locales = [],
605
- onCreated
618
+ onCreated,
619
+ locale
606
620
  }) {
621
+ const tr = createAdminT3(locale);
607
622
  const addingLocale = type !== void 0;
608
623
  const [newType, setNewType] = useState2("");
609
- const [locale, setLocale] = useState2("");
624
+ const [targetLocale, setTargetLocale] = useState2("");
610
625
  const [subject, setSubject] = useState2("");
611
626
  const [html, setHtml] = useState2("");
612
627
  const [text, setText] = useState2("");
@@ -624,13 +639,11 @@ function TemplateCreateScreen({
624
639
  e.preventDefault();
625
640
  setError(null);
626
641
  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.");
642
+ const l = targetLocale.trim() || null;
643
+ if (!TYPE_RE.test(t)) return setError(tr("templates.create.errorType"));
644
+ if (addingLocale && !l) return setError(tr("templates.create.errorLocaleRequired"));
645
+ if (l && !LOCALE_RE.test(l)) return setError(tr("templates.create.errorLocale"));
646
+ if (!text.trim()) return setError(tr("templates.create.errorText"));
634
647
  setBusy(true);
635
648
  try {
636
649
  const exists = await client.getTemplate(t, l).then(
@@ -638,7 +651,9 @@ function TemplateCreateScreen({
638
651
  (err) => !(err instanceof FonderieApiError && err.status === 404)
639
652
  );
640
653
  if (exists) {
641
- setError(`"${t}"${l ? ` (${l})` : ""} already exists \u2014 open it from the list to edit.`);
654
+ setError(
655
+ l ? tr("templates.create.existsLocale", { type: t, locale: l }) : tr("templates.create.exists", { type: t })
656
+ );
642
657
  return;
643
658
  }
644
659
  const input = { text, active: true };
@@ -653,11 +668,11 @@ function TemplateCreateScreen({
653
668
  }
654
669
  };
655
670
  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." }),
671
+ /* @__PURE__ */ jsx3("h1", { style: styles3.title, children: addingLocale ? tr("templates.create.titleLocale", { type }) : tr("templates.create.titleNew") }),
672
+ /* @__PURE__ */ jsx3("p", { style: styles3.meta, children: addingLocale ? tr("templates.create.hintLocale") : tr("templates.create.hintNew") }),
658
673
  /* @__PURE__ */ jsxs3("form", { style: styles3.form, onSubmit: (e) => void submit(e), children: [
659
674
  !addingLocale ? /* @__PURE__ */ jsxs3(Fragment, { children: [
660
- /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-type", children: "Type" }),
675
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-type", children: tr("templates.create.type") }),
661
676
  /* @__PURE__ */ jsx3(
662
677
  "input",
663
678
  {
@@ -671,25 +686,22 @@ function TemplateCreateScreen({
671
686
  }
672
687
  )
673
688
  ] }) : null,
674
- /* @__PURE__ */ jsxs3("label", { style: styles3.label, htmlFor: "new-template-locale", children: [
675
- "Locale ",
676
- addingLocale ? "" : "(optional \u2014 empty is the default)"
677
- ] }),
689
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-locale", children: addingLocale ? tr("templates.create.locale") : tr("templates.create.localeOptional") }),
678
690
  /* @__PURE__ */ jsx3(
679
691
  "input",
680
692
  {
681
693
  id: "new-template-locale",
682
694
  style: styles3.input,
683
- value: locale,
695
+ value: targetLocale,
684
696
  placeholder: "fr",
685
697
  list: "new-template-locales",
686
698
  autoComplete: "off",
687
699
  spellCheck: false,
688
- onChange: (e) => setLocale(e.target.value)
700
+ onChange: (e) => setTargetLocale(e.target.value)
689
701
  }
690
702
  ),
691
703
  /* @__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" }),
704
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-subject", children: tr("templates.subject") }),
693
705
  /* @__PURE__ */ jsx3(
694
706
  "input",
695
707
  {
@@ -699,7 +711,7 @@ function TemplateCreateScreen({
699
711
  onChange: (e) => setSubject(e.target.value)
700
712
  }
701
713
  ),
702
- /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-html", children: "HTML body" }),
714
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-html", children: tr("templates.htmlBody") }),
703
715
  /* @__PURE__ */ jsx3(
704
716
  "textarea",
705
717
  {
@@ -709,7 +721,7 @@ function TemplateCreateScreen({
709
721
  onChange: (e) => setHtml(e.target.value)
710
722
  }
711
723
  ),
712
- /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-text", children: "Plain-text body" }),
724
+ /* @__PURE__ */ jsx3("label", { style: styles3.label, htmlFor: "new-template-text", children: tr("templates.textBody") }),
713
725
  /* @__PURE__ */ jsx3(
714
726
  "textarea",
715
727
  {
@@ -720,7 +732,7 @@ function TemplateCreateScreen({
720
732
  }
721
733
  ),
722
734
  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" })
735
+ /* @__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
736
  ] })
725
737
  ] });
726
738
  }