@fonderie/react-admin-screens 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  module.exports = __toCommonJS(index_exports);
40
40
 
41
41
  // src/screens/AdminShell.tsx
42
+ var import_client16 = require("@fonderie/client");
42
43
  var import_react_config_admin_screens = require("@fonderie/react-config-admin-screens");
43
44
  var import_react_courier_admin_screens = require("@fonderie/react-courier-admin-screens");
44
45
  var import_react9 = require("react");
@@ -592,11 +593,12 @@ function Empty({
592
593
  function RefreshButton({
593
594
  onClick,
594
595
  busy,
595
- label = "Refresh"
596
+ label = "Refresh",
597
+ busyLabel = "Working\u2026"
596
598
  }) {
597
599
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", style: styles.button, onClick, disabled: busy, children: [
598
600
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Icon, { name: "refresh", size: 14 }),
599
- busy ? "Working\u2026" : label
601
+ busy ? busyLabel : label
600
602
  ] });
601
603
  }
602
604
  function MethodChip({ method }) {
@@ -623,9 +625,11 @@ function MethodChip({ method }) {
623
625
  }
624
626
 
625
627
  // src/screens/AdminLogScreen.tsx
628
+ var import_client = require("@fonderie/client");
626
629
  var import_react_admin = require("@fonderie/react-admin");
627
630
  var import_jsx_runtime2 = require("react/jsx-runtime");
628
- function AdminLogScreen({ client, pageSize = 50 }) {
631
+ function AdminLogScreen({ client, pageSize = 50, locale }) {
632
+ const t2 = (0, import_client.createAdminT)(locale);
629
633
  const { entries, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin.useAdminLog)(client, {
630
634
  limit: pageSize
631
635
  });
@@ -633,22 +637,30 @@ function AdminLogScreen({ client, pageSize = 50 }) {
633
637
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
634
638
  PageHeader,
635
639
  {
636
- title: "Admin log",
637
- lead: "Every request made through this surface, newest first \u2014 refused ones included.",
638
- actions: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
640
+ title: t2("log.title"),
641
+ lead: t2("log.lead"),
642
+ actions: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
643
+ RefreshButton,
644
+ {
645
+ onClick: () => void refresh(),
646
+ busy: isLoading,
647
+ label: t2("common.refresh"),
648
+ busyLabel: t2("common.working")
649
+ }
650
+ )
639
651
  }
640
652
  ),
641
- error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? "The admin log is off \u2014 give AdminModule a store." : error.explanation }) : entries.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Empty, { icon: "log", title: "No requests yet" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
653
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? t2("log.off") : error.explanation }) : entries.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Empty, { icon: "log", title: t2("log.empty") }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
642
654
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { style: styles.table, children: [
643
655
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
644
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: "When" }),
645
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: "Actor" }),
646
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: "Request" }),
647
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: "Status" }),
648
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: "Module" })
656
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: t2("log.colWhen") }),
657
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: t2("log.colActor") }),
658
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: t2("log.colRequest") }),
659
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: t2("log.colStatus") }),
660
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { style: styles.th, children: t2("log.colModule") })
649
661
  ] }) }),
650
662
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: entries.map((e) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
651
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: new Date(e.at).toLocaleString() }),
663
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: (0, import_client.formatAdminDate)(e.at, locale, "datetime") }),
652
664
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: styles.td, children: e.actor }),
653
665
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("td", { style: styles.td, children: [
654
666
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MethodChip, { method: e.method }),
@@ -666,14 +678,14 @@ function AdminLogScreen({ client, pageSize = 50 }) {
666
678
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.module })
667
679
  ] }, e.id)) })
668
680
  ] }),
669
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
681
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: styles.status, children: t2("common.loading") }) : null,
670
682
  hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
671
683
  "button",
672
684
  {
673
685
  type: "button",
674
686
  style: { ...styles.button, marginTop: 12 },
675
687
  onClick: () => void loadMore(),
676
- children: "Load more"
688
+ children: t2("common.loadMore")
677
689
  }
678
690
  ) : null
679
691
  ] })
@@ -681,9 +693,11 @@ function AdminLogScreen({ client, pageSize = 50 }) {
681
693
  }
682
694
 
683
695
  // src/screens/AttentionScreen.tsx
696
+ var import_client2 = require("@fonderie/client");
684
697
  var import_react_admin2 = require("@fonderie/react-admin");
685
698
  var import_jsx_runtime3 = require("react/jsx-runtime");
686
- function AttentionScreen({ client }) {
699
+ function AttentionScreen({ client, locale }) {
700
+ const t2 = (0, import_client2.createAdminT)(locale);
687
701
  const { attention, isLoading, error, refresh } = (0, import_react_admin2.useAttention)(client);
688
702
  const { manifest } = (0, import_react_admin2.useManifest)(client);
689
703
  const errors = attention?.items.filter((i) => i.severity === "error") ?? [];
@@ -694,9 +708,19 @@ function AttentionScreen({ client }) {
694
708
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
695
709
  PageHeader,
696
710
  {
697
- title: "Attention",
698
- lead: attention ? `What needs you on this deployment \xB7 checked ${new Date(attention.generatedAt).toLocaleTimeString()}` : "What needs you on this deployment",
699
- actions: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
711
+ title: t2("attention.title"),
712
+ lead: attention ? t2("attention.leadChecked", {
713
+ time: (0, import_client2.formatAdminDate)(attention.generatedAt, locale, "time")
714
+ }) : t2("attention.lead"),
715
+ actions: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
716
+ RefreshButton,
717
+ {
718
+ onClick: () => void refresh(),
719
+ busy: isLoading,
720
+ label: t2("common.refresh"),
721
+ busyLabel: t2("common.working")
722
+ }
723
+ )
700
724
  }
701
725
  ),
702
726
  error ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
@@ -704,44 +728,42 @@ function AttentionScreen({ client }) {
704
728
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
705
729
  Stat,
706
730
  {
707
- label: "Needs action",
731
+ label: t2("attention.needsAction"),
708
732
  value: attention ? errors.length : "\u2014",
709
733
  tone: errors.length ? "bad" : attention ? "ok" : void 0,
710
- hint: "errors to fix"
734
+ hint: t2("attention.needsActionHint")
711
735
  }
712
736
  ),
713
737
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
714
738
  Stat,
715
739
  {
716
- label: "Advice",
740
+ label: t2("attention.advice"),
717
741
  value: attention ? advice.length : "\u2014",
718
742
  tone: advice.length ? "warn" : void 0,
719
- hint: "worth a look"
743
+ hint: t2("attention.adviceHint")
720
744
  }
721
745
  ),
722
746
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
723
747
  Stat,
724
748
  {
725
- label: "Modules ready",
749
+ label: t2("attention.modulesReady"),
726
750
  value: manifest ? `${ready}/${total}` : "\u2014",
727
751
  tone: manifest && ready < total ? "warn" : manifest ? "ok" : void 0,
728
- hint: manifest ? `admin ${manifest.admin.version}` : void 0
752
+ hint: manifest ? t2("attention.modulesReadyHint", { version: manifest.admin.version }) : void 0
729
753
  }
730
754
  ),
731
755
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
732
756
  Stat,
733
757
  {
734
- label: "Routes",
758
+ label: t2("attention.routes"),
735
759
  value: manifest ? manifest.routes.length : "\u2014",
736
760
  hint: manifest ? manifest.env : void 0
737
761
  }
738
762
  )
739
763
  ] }),
740
- isLoading && !attention ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.status, children: "Running the checks\u2026" }) : attention && attention.items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Empty, { title: "Nothing needs you", children: [
741
- "Every check passes. Checked ",
742
- new Date(attention.generatedAt).toLocaleString(),
743
- "."
744
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { style: styles.list, children: [...errors, ...advice].map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
764
+ isLoading && !attention ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { style: styles.status, children: t2("attention.running") }) : attention && attention.items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Empty, { title: t2("attention.emptyTitle"), children: t2("attention.emptyBody", {
765
+ time: (0, import_client2.formatAdminDate)(attention.generatedAt, locale, "datetime")
766
+ }) }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("ul", { style: styles.list, children: [...errors, ...advice].map((item) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
745
767
  "li",
746
768
  {
747
769
  style: { ...styles.row, display: "flex", gap: 12, alignItems: "flex-start" },
@@ -769,7 +791,7 @@ function AttentionScreen({ client }) {
769
791
  marginBottom: 2
770
792
  },
771
793
  children: [
772
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Pill, { tone: item.severity === "error" ? "bad" : "warn", children: item.severity === "error" ? "error" : "advice" }),
794
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Pill, { tone: item.severity === "error" ? "bad" : "warn", children: item.severity === "error" ? t2("common.status.error") : t2("common.status.advice") }),
773
795
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: styles.mono, children: item.source })
774
796
  ]
775
797
  }
@@ -784,23 +806,25 @@ function AttentionScreen({ client }) {
784
806
  }
785
807
 
786
808
  // src/screens/EnvironmentScreen.tsx
809
+ var import_client3 = require("@fonderie/client");
787
810
  var import_react_admin3 = require("@fonderie/react-admin");
788
811
  var import_jsx_runtime4 = require("react/jsx-runtime");
789
- function EnvironmentScreen({ client }) {
812
+ function EnvironmentScreen({ client, locale }) {
813
+ const t2 = (0, import_client3.createAdminT)(locale);
790
814
  const { report, isLoading, error } = (0, import_react_admin3.useAdminEnvironment)(client);
791
815
  const missing = report?.env.filter((e) => !e.set).length ?? 0;
792
816
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: styles.container, children: [
793
817
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
794
818
  PageHeader,
795
819
  {
796
- title: "Environment",
797
- lead: "Whether each module is configured and each variable the app reads is set. Values are never shown.",
798
- actions: report && report.env.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Pill, { tone: missing ? "bad" : "ok", children: missing ? `${missing} missing` : "all set" }) : null
820
+ title: t2("environment.title"),
821
+ lead: t2("environment.lead"),
822
+ actions: report && report.env.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Pill, { tone: missing ? "bad" : "ok", children: missing ? t2("environment.missingCount", { n: missing }) : t2("environment.allSet") }) : null
799
823
  }
800
824
  ),
801
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : report ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
802
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Variables" }),
803
- report.env.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: styles.muted, children: "No variables declared \u2014 pass `env` to AdminModule." }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
825
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: styles.status, children: t2("common.loading") }) : error ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : report ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
826
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: t2("environment.variables") }),
827
+ report.env.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { style: styles.muted, children: t2("environment.noVariables") }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
804
828
  "div",
805
829
  {
806
830
  style: {
@@ -821,14 +845,14 @@ function EnvironmentScreen({ client }) {
821
845
  },
822
846
  children: [
823
847
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { style: { ...styles.mono, wordBreak: "break-all" }, children: e.name }),
824
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Pill, { tone: e.set ? "ok" : "bad", children: e.set ? "set" : "missing" })
848
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Pill, { tone: e.set ? "ok" : "bad", children: e.set ? t2("common.status.set") : t2("common.status.missing") })
825
849
  ]
826
850
  },
827
851
  e.name
828
852
  ))
829
853
  }
830
854
  ),
831
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { style: styles.subtitle, children: "Module readiness" }),
855
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { style: styles.subtitle, children: t2("environment.moduleReadiness") }),
832
856
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { style: styles.list, children: report.modules.map((m) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { style: styles.row, children: [
833
857
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
834
858
  "div",
@@ -845,7 +869,7 @@ function EnvironmentScreen({ client }) {
845
869
  Pill,
846
870
  {
847
871
  tone: m.problems.length === 0 ? "ok" : m.problems.some((p) => p.severity === "error") ? "bad" : "warn",
848
- children: m.problems.length === 0 ? "ready" : `${m.problems.length} problem${m.problems.length === 1 ? "" : "s"}`
872
+ children: m.problems.length === 0 ? t2("common.status.ready") : m.problems.length === 1 ? t2("environment.problemOne") : t2("environment.problemMany", { n: m.problems.length })
849
873
  }
850
874
  )
851
875
  ]
@@ -869,34 +893,44 @@ function EnvironmentScreen({ client }) {
869
893
  }
870
894
 
871
895
  // src/screens/DoctorScreen.tsx
896
+ var import_client4 = require("@fonderie/client");
872
897
  var import_react_admin4 = require("@fonderie/react-admin");
873
898
  var import_jsx_runtime5 = require("react/jsx-runtime");
874
- function DoctorScreen({ client }) {
899
+ function DoctorScreen({ client, locale }) {
900
+ const t2 = (0, import_client4.createAdminT)(locale);
875
901
  const { report, isLoading, error, refresh } = (0, import_react_admin4.useDoctor)(client);
876
902
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: styles.container, children: [
877
903
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
878
904
  PageHeader,
879
905
  {
880
- title: "Doctor",
881
- lead: "Every reconciliation check across the installed modules, run on demand.",
906
+ title: t2("doctor.title"),
907
+ lead: t2("doctor.lead"),
882
908
  actions: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
883
- report && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Pill, { tone: report.ok ? "ok" : "bad", children: report.ok ? "all checks pass" : "a check failed" }) : null,
884
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading, label: "Run again" })
909
+ report && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Pill, { tone: report.ok ? "ok" : "bad", children: report.ok ? t2("doctor.allPass") : t2("doctor.oneFailed") }) : null,
910
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
911
+ RefreshButton,
912
+ {
913
+ onClick: () => void refresh(),
914
+ busy: isLoading,
915
+ label: t2("doctor.runAgain"),
916
+ busyLabel: t2("common.working")
917
+ }
918
+ )
885
919
  ] })
886
920
  }
887
921
  ),
888
- isLoading && !report ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: styles.status, children: "Running the checks\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { style: styles.table, children: [
922
+ isLoading && !report ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: styles.status, children: t2("doctor.running") }) : error ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("table", { style: styles.table, children: [
889
923
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("tr", { children: [
890
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: { ...styles.th, width: 110 }, children: "Result" }),
891
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: styles.th, children: "Check" }),
892
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: { ...styles.th, textAlign: "right", width: 90 }, children: "Time" })
924
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: { ...styles.th, width: 110 }, children: t2("doctor.colResult") }),
925
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: styles.th, children: t2("doctor.colCheck") }),
926
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("th", { style: { ...styles.th, textAlign: "right", width: 90 }, children: t2("doctor.colTime") })
893
927
  ] }) }),
894
928
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("tbody", { children: report?.checks.map((c) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("tr", { children: [
895
929
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
896
930
  Pill,
897
931
  {
898
932
  tone: c.skipped ? "neutral" : c.ok ? c.findings.length ? "warn" : "ok" : "bad",
899
- children: c.skipped ? "skipped" : c.ok ? c.findings.length ? "advice" : "ok" : "failed"
933
+ children: c.skipped ? t2("common.status.skipped") : c.ok ? c.findings.length ? t2("common.status.advice") : t2("common.status.ok") : t2("common.status.failed")
900
934
  }
901
935
  ) }),
902
936
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("td", { style: styles.td, children: [
@@ -941,30 +975,37 @@ function DoctorScreen({ client }) {
941
975
  }
942
976
 
943
977
  // src/screens/ModulesScreen.tsx
978
+ var import_client5 = require("@fonderie/client");
944
979
  var import_react_admin5 = require("@fonderie/react-admin");
945
980
  var import_jsx_runtime6 = require("react/jsx-runtime");
946
- function ModulesScreen({ client }) {
981
+ function ModulesScreen({ client, locale }) {
982
+ const t2 = (0, import_client5.createAdminT)(locale);
947
983
  const { manifest, isLoading, error } = (0, import_react_admin5.useManifest)(client);
948
984
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: styles.container, children: [
949
985
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
950
986
  PageHeader,
951
987
  {
952
- title: "Modules",
953
- lead: manifest ? `${manifest.env} \xB7 admin ${manifest.admin.version} \xB7 admin log ${manifest.admin.log ? "on" : "off"} \xB7 ${manifest.routes.length} routes` : "Every installed module, its version and whether it is ready."
988
+ title: t2("modules.title"),
989
+ lead: manifest ? t2("modules.leadSummary", {
990
+ env: manifest.env,
991
+ version: manifest.admin.version,
992
+ log: manifest.admin.log ? t2("modules.logOn") : t2("modules.logOff"),
993
+ routes: manifest.routes.length
994
+ }) : t2("modules.lead")
954
995
  }
955
996
  ),
956
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : manifest ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { style: styles.table, children: [
997
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: styles.status, children: t2("common.loading") }) : error ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : manifest ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("table", { style: styles.table, children: [
957
998
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { children: [
958
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: "Module" }),
959
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: "Version" }),
960
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: "Readiness" }),
961
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: "Admin" })
999
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: t2("modules.colModule") }),
1000
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: t2("modules.colVersion") }),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: t2("modules.colReadiness") }),
1002
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("th", { style: styles.th, children: t2("modules.colAdmin") })
962
1003
  ] }) }),
963
1004
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("tbody", { children: manifest.modules.map((m) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("tr", { children: [
964
1005
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: { ...styles.td, ...styles.mono, fontWeight: 600 }, children: m.name }),
965
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: m.version ?? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: styles.muted, children: "not reported" }) }),
1006
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: m.version ?? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: styles.muted, children: t2("modules.notReported") }) }),
966
1007
  /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("td", { style: styles.td, children: [
967
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Pill, { tone: m.readiness.ok ? "ok" : "bad", children: m.readiness.ok ? "ready" : "error" }),
1008
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Pill, { tone: m.readiness.ok ? "ok" : "bad", children: m.readiness.ok ? t2("common.status.ready") : t2("common.status.error") }),
968
1009
  m.readiness.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
969
1010
  "div",
970
1011
  {
@@ -978,17 +1019,19 @@ function ModulesScreen({ client }) {
978
1019
  p.message
979
1020
  ))
980
1021
  ] }),
981
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: styles.td, children: m.describesAdmin ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: styles.badge, children: "describes admin" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: styles.muted, children: "\u2014" }) })
1022
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("td", { style: styles.td, children: m.describesAdmin ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: styles.badge, children: t2("modules.describesAdmin") }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: styles.muted, children: "\u2014" }) })
982
1023
  ] }, m.name)) })
983
1024
  ] }) : null
984
1025
  ] });
985
1026
  }
986
1027
 
987
1028
  // src/screens/RoutesScreen.tsx
1029
+ var import_client6 = require("@fonderie/client");
988
1030
  var import_react_admin6 = require("@fonderie/react-admin");
989
1031
  var import_react = require("react");
990
1032
  var import_jsx_runtime7 = require("react/jsx-runtime");
991
- function RoutesScreen({ client }) {
1033
+ function RoutesScreen({ client, locale }) {
1034
+ const t2 = (0, import_client6.createAdminT)(locale);
992
1035
  const { report, isLoading, error } = (0, import_react_admin6.useAdminRoutes)(client);
993
1036
  const [q, setQ] = (0, import_react.useState)("");
994
1037
  const needle = q.trim().toLowerCase();
@@ -999,27 +1042,27 @@ function RoutesScreen({ client }) {
999
1042
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1000
1043
  PageHeader,
1001
1044
  {
1002
- title: "Routes",
1003
- lead: report ? `${report.routes.length} routes exposed by this deployment, with the guard in front of each.` : "Every exposed route, with its guard.",
1045
+ title: t2("routes.title"),
1046
+ lead: report ? t2("routes.leadCount", { n: report.routes.length }) : t2("routes.lead"),
1004
1047
  actions: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1005
1048
  "input",
1006
1049
  {
1007
1050
  type: "search",
1008
1051
  value: q,
1009
1052
  onChange: (e) => setQ(e.target.value),
1010
- placeholder: "Filter by path, method, module\u2026",
1053
+ placeholder: t2("routes.filterPlaceholder"),
1011
1054
  style: { ...styles.input, width: 280 },
1012
- "aria-label": "Filter routes"
1055
+ "aria-label": t2("routes.filterLabel")
1013
1056
  }
1014
1057
  )
1015
1058
  }
1016
1059
  ),
1017
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { style: styles.table, children: [
1060
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.status, children: t2("common.loading") }) : error ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { style: styles.table, children: [
1018
1061
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
1019
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: { ...styles.th, width: 80 }, children: "Method" }),
1020
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Path" }),
1021
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Guard" }),
1022
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Module" })
1062
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: { ...styles.th, width: 80 }, children: t2("routes.colMethod") }),
1063
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: t2("routes.colPath") }),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: t2("routes.colGuard") }),
1065
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: t2("routes.colModule") })
1023
1066
  ] }) }),
1024
1067
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tbody", { children: [
1025
1068
  routes.map((r) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
@@ -1033,61 +1076,64 @@ function RoutesScreen({ client }) {
1033
1076
  children: r.guard
1034
1077
  }
1035
1078
  ) }),
1036
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: r.module ?? "application" })
1079
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: r.module ?? t2("routes.application") })
1037
1080
  ] }, `${r.method} ${r.path}`)),
1038
- routes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { style: { ...styles.td, ...styles.muted }, colSpan: 4, children: [
1039
- "No route matches \u201C",
1040
- q,
1041
- "\u201D."
1042
- ] }) }) : null
1081
+ routes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.muted }, colSpan: 4, children: t2("routes.noMatch", { q }) }) }) : null
1043
1082
  ] })
1044
1083
  ] })
1045
1084
  ] });
1046
1085
  }
1047
1086
 
1048
1087
  // src/screens/MigrationsScreen.tsx
1088
+ var import_client7 = require("@fonderie/client");
1049
1089
  var import_react_admin7 = require("@fonderie/react-admin");
1050
1090
  var import_jsx_runtime8 = require("react/jsx-runtime");
1051
- function why(m, everApplied) {
1091
+ function why(m, everApplied, t2) {
1052
1092
  if (m.pending.length === 0) return null;
1053
- if (m.blockedBy) return `Apply "${m.blockedBy}" first \u2014 it runs before this one and is behind.`;
1093
+ if (m.blockedBy) return t2("migrations.blockedBy", { module: m.blockedBy });
1054
1094
  if (everApplied && m.pending.some((p) => p.impact === "destructive"))
1055
- return "Contains a migration that deletes data. No down-migration brings it back \u2014 apply this one through CI or `npm run migrate`.";
1095
+ return t2("migrations.destructiveBlocked");
1056
1096
  return null;
1057
1097
  }
1058
- function MigrationsScreen({ client }) {
1098
+ function MigrationsScreen({ client, locale }) {
1099
+ const t2 = (0, import_client7.createAdminT)(locale);
1059
1100
  const { report, isLoading, error, refresh, apply } = (0, import_react_admin7.useAdminMigrations)(client);
1060
1101
  const behind = report?.modules.filter((m) => m.pending.length > 0) ?? [];
1061
1102
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: styles.container, children: [
1062
1103
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1063
1104
  PageHeader,
1064
1105
  {
1065
- title: "Migrations",
1066
- lead: "Schema changes each module ships, and whether this database has them.",
1067
- actions: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
1106
+ title: t2("migrations.title"),
1107
+ lead: t2("migrations.lead"),
1108
+ actions: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1109
+ RefreshButton,
1110
+ {
1111
+ onClick: () => void refresh(),
1112
+ busy: isLoading,
1113
+ label: t2("common.refresh"),
1114
+ busyLabel: t2("common.working")
1115
+ }
1116
+ )
1068
1117
  }
1069
1118
  ),
1070
- error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.error, role: "alert", children: error.status === 403 ? "Applying migrations needs a token with the write scope." : error.explanation }) : null,
1071
- report && !report.everApplied ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.notice, children: "This database has never been migrated \u2014 treating it as a first install, so nothing is held back." }) : null,
1072
- report && behind.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Empty, { icon: "migrations", title: "Every module is up to date", children: "No pending migrations." }) : null,
1119
+ error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.error, role: "alert", children: error.status === 403 ? t2("migrations.needsWrite") : error.explanation }) : null,
1120
+ report && !report.everApplied ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.notice, children: t2("migrations.firstInstall") }) : null,
1121
+ report && behind.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Empty, { icon: "migrations", title: t2("migrations.upToDate"), children: t2("migrations.noPending") }) : null,
1073
1122
  behind.map((m) => {
1074
- const blocked = why(m, report?.everApplied ?? false);
1123
+ const blocked = why(m, report?.everApplied ?? false, t2);
1075
1124
  const files = m.pending.map((p) => p.file);
1076
1125
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("section", { style: { ...styles.card, marginBottom: 16 }, children: [
1077
1126
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: [
1078
1127
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: styles.mono, children: m.name }),
1079
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Pill, { tone: "warn", children: [
1080
- m.pending.length,
1081
- " pending"
1082
- ] })
1128
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Pill, { tone: "warn", children: t2("migrations.pendingCount", { n: m.pending.length }) })
1083
1129
  ] }),
1084
1130
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { style: { margin: "0 0 12px", paddingLeft: 18, lineHeight: 1.9 }, children: m.pending.map((p) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { children: [
1085
1131
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { style: styles.code, children: p.file }),
1086
1132
  " ",
1087
- p.impact === "destructive" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Pill, { tone: "bad", children: "destructive" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Pill, { tone: "neutral", children: "additive" }),
1133
+ p.impact === "destructive" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Pill, { tone: "bad", children: t2("migrations.destructive") }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Pill, { tone: "neutral", children: t2("migrations.additive") }),
1088
1134
  p.destructive.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { children: p.destructive.map((s) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: s.slice(0, 120) }) }, s)) }) : null
1089
1135
  ] }, p.file)) }),
1090
- blocked ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: { ...styles.notice, marginBottom: 0 }, children: blocked }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1136
+ blocked ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: { ...styles.notice, marginBottom: 0 }, children: blocked }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1091
1137
  "button",
1092
1138
  {
1093
1139
  type: "button",
@@ -1095,17 +1141,12 @@ function MigrationsScreen({ client }) {
1095
1141
  disabled: isLoading || !m.appliable,
1096
1142
  onClick: () => {
1097
1143
  if (window.confirm(
1098
- `Apply ${m.pending.length} migration(s) to "${m.name}"? This changes the database schema.`
1144
+ t2("migrations.confirmApply", { n: m.pending.length, module: m.name })
1099
1145
  ))
1100
1146
  void apply(m.name, files).catch(() => {
1101
1147
  });
1102
1148
  },
1103
- children: [
1104
- "Apply ",
1105
- m.pending.length,
1106
- " migration",
1107
- m.pending.length === 1 ? "" : "s"
1108
- ]
1149
+ children: m.pending.length === 1 ? t2("migrations.applyOne") : t2("migrations.applyMany", { n: m.pending.length })
1109
1150
  }
1110
1151
  )
1111
1152
  ] }, m.name);
@@ -1114,20 +1155,22 @@ function MigrationsScreen({ client }) {
1114
1155
  }
1115
1156
 
1116
1157
  // src/screens/OperatorsScreen.tsx
1158
+ var import_client8 = require("@fonderie/client");
1117
1159
  var import_react_admin8 = require("@fonderie/react-admin");
1118
1160
  var import_react2 = require("react");
1119
1161
  var import_jsx_runtime9 = require("react/jsx-runtime");
1120
1162
  var LEVELS = [
1121
- { label: "Read only", scopes: ["read"], hint: "look around, change nothing" },
1122
- { label: "Editor", scopes: ["read", "write"], hint: "change config, templates, users" },
1123
- {
1124
- label: "Owner",
1125
- scopes: ["read", "write", "secrets"],
1126
- hint: "secrets, tokens and operators too"
1127
- }
1163
+ { id: "read", scopes: ["read"] },
1164
+ { id: "editor", scopes: ["read", "write"] },
1165
+ { id: "owner", scopes: ["read", "write", "secrets"] }
1128
1166
  ];
1129
- var levelOf = (scopes) => scopes.includes("secrets") ? "Owner" : scopes.includes("write") ? "Editor" : "Read only";
1130
- function Minted({ link, onClose }) {
1167
+ var levelOf = (scopes) => scopes.includes("secrets") ? "owner" : scopes.includes("write") ? "editor" : "read";
1168
+ function Minted({
1169
+ link,
1170
+ onClose,
1171
+ t: t2,
1172
+ locale
1173
+ }) {
1131
1174
  const [copied, setCopied] = (0, import_react2.useState)(false);
1132
1175
  const url = `${window.location.origin}${link.url}`;
1133
1176
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
@@ -1139,15 +1182,9 @@ function Minted({ link, onClose }) {
1139
1182
  borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
1140
1183
  },
1141
1184
  children: [
1142
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("strong", { children: [
1143
- "Send this link to ",
1144
- link.email,
1145
- ". It is shown once."
1146
- ] }),
1147
- " It works once and expires",
1185
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: t2("operators.mintedTitle", { email: link.email }) }),
1148
1186
  " ",
1149
- new Date(link.expiresAt).toLocaleString(),
1150
- ".",
1187
+ t2("operators.mintedBody", { date: (0, import_client8.formatAdminDate)(link.expiresAt, locale) }),
1151
1188
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", gap: 8, marginTop: 8, alignItems: "center" }, children: [
1152
1189
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { style: { ...styles.code, flex: 1, padding: "7px 10px", wordBreak: "break-all" }, children: url }),
1153
1190
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -1159,32 +1196,36 @@ function Minted({ link, onClose }) {
1159
1196
  void navigator.clipboard?.writeText(url);
1160
1197
  setCopied(true);
1161
1198
  },
1162
- children: copied ? "Copied" : "Copy"
1199
+ children: copied ? t2("common.copied") : t2("common.copy")
1163
1200
  }
1164
1201
  ),
1165
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", style: styles.buttonGhost, onClick: onClose, "aria-label": "Dismiss", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "close", size: 14 }) })
1202
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1203
+ "button",
1204
+ {
1205
+ type: "button",
1206
+ style: styles.buttonGhost,
1207
+ onClick: onClose,
1208
+ "aria-label": t2("common.dismiss"),
1209
+ children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "close", size: 14 })
1210
+ }
1211
+ )
1166
1212
  ] })
1167
1213
  ]
1168
1214
  }
1169
1215
  );
1170
1216
  }
1171
- function OperatorsScreen({ client, me }) {
1217
+ function OperatorsScreen({ client, me, locale }) {
1218
+ const t2 = (0, import_client8.createAdminT)(locale);
1172
1219
  const { report, isLoading, error, invite, recover, update, revokeLink } = (0, import_react_admin8.useAdminOperators)(client);
1173
1220
  const [email, setEmail] = (0, import_react2.useState)("");
1174
- const [level, setLevel] = (0, import_react2.useState)("Editor");
1221
+ const [level, setLevel] = (0, import_react2.useState)("editor");
1175
1222
  const [minted, setMinted] = (0, import_react2.useState)(null);
1176
- const scopesFor = (label) => LEVELS.find((l) => l.label === label)?.scopes ?? ["read"];
1177
- const status = (o) => o.disabledAt ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "neutral", children: "disabled" }) : o.locked ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "warn", children: "locked" }) : !o.enrolled ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "warn", children: "setting up" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "ok", children: "active" });
1223
+ const scopesFor = (id) => LEVELS.find((l) => l.id === id)?.scopes ?? ["read"];
1224
+ const status = (o) => o.disabledAt ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "neutral", children: t2("common.status.disabled") }) : o.locked ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "warn", children: t2("common.status.locked") }) : !o.enrolled ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "warn", children: t2("operators.settingUp") }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: "ok", children: t2("common.status.active") });
1178
1225
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: styles.container, children: [
1179
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1180
- PageHeader,
1181
- {
1182
- title: "Operators",
1183
- lead: "The people who can sign in to this console. Each one uses a password and an authenticator app; there is no sign-up."
1184
- }
1185
- ),
1186
- error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.error, role: "alert", children: error.status === 403 && error.reason === "FORBIDDEN" ? "Managing operators needs the Owner level." : error.explanation }) : null,
1187
- minted ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Minted, { link: minted, onClose: () => setMinted(null) }) : null,
1226
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PageHeader, { title: t2("operators.title"), lead: t2("operators.lead") }),
1227
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.error, role: "alert", children: error.status === 403 && error.reason === "FORBIDDEN" ? t2("operators.needsOwner") : error.explanation }) : null,
1228
+ minted ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Minted, { link: minted, onClose: () => setMinted(null), t: t2, locale }) : null,
1188
1229
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1189
1230
  "form",
1190
1231
  {
@@ -1206,7 +1247,7 @@ function OperatorsScreen({ client, me }) {
1206
1247
  },
1207
1248
  children: [
1208
1249
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "users", size: 16 }),
1209
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { style: { fontSize: 13.5, marginRight: 4 }, children: "Invite" }),
1250
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { style: { fontSize: 13.5, marginRight: 4 }, children: t2("operators.invite") }),
1210
1251
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1211
1252
  "input",
1212
1253
  {
@@ -1214,9 +1255,9 @@ function OperatorsScreen({ client, me }) {
1214
1255
  required: true,
1215
1256
  value: email,
1216
1257
  onChange: (e) => setEmail(e.target.value),
1217
- placeholder: "teammate@company.com",
1258
+ placeholder: t2("operators.invitePlaceholder"),
1218
1259
  style: { ...styles.input, flex: 1, minWidth: 220 },
1219
- "aria-label": "Email to invite"
1260
+ "aria-label": t2("operators.inviteLabel")
1220
1261
  }
1221
1262
  ),
1222
1263
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -1225,27 +1266,27 @@ function OperatorsScreen({ client, me }) {
1225
1266
  value: level,
1226
1267
  onChange: (e) => setLevel(e.target.value),
1227
1268
  style: styles.input,
1228
- "aria-label": "Access level",
1229
- children: LEVELS.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("option", { value: l.label, children: [
1230
- l.label,
1269
+ "aria-label": t2("operators.accessLevelLabel"),
1270
+ children: LEVELS.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("option", { value: l.id, children: [
1271
+ t2(`operators.level.${l.id}`),
1231
1272
  " \u2014 ",
1232
- l.hint
1233
- ] }, l.label))
1273
+ t2(`operators.levelHint.${l.id}`)
1274
+ ] }, l.id))
1234
1275
  }
1235
1276
  ),
1236
1277
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: !email.trim(), children: [
1237
1278
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Icon, { name: "plus", size: 14 }),
1238
- "Create invite link"
1279
+ t2("operators.createInvite")
1239
1280
  ] })
1240
1281
  ]
1241
1282
  }
1242
1283
  ),
1243
- isLoading && !report ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : report && report.operators.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Empty, { icon: "users", title: "No operators yet" }) : report ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { style: styles.table, children: [
1284
+ isLoading && !report ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.status, children: t2("common.loading") }) : report && report.operators.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Empty, { icon: "users", title: t2("operators.emptyTitle") }) : report ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { style: styles.table, children: [
1244
1285
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
1245
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Operator" }),
1246
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Access" }),
1247
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Status" }),
1248
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Last sign-in" }),
1286
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: t2("operators.col.operator") }),
1287
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: t2("operators.col.access") }),
1288
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: t2("operators.col.status") }),
1289
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: t2("operators.col.lastSignIn") }),
1249
1290
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th })
1250
1291
  ] }) }),
1251
1292
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: report.operators.map((o) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
@@ -1261,32 +1302,30 @@ function OperatorsScreen({ client, me }) {
1261
1302
  }),
1262
1303
  style: { ...styles.input, height: 28, fontSize: 12.5 },
1263
1304
  disabled: o.email === me,
1264
- "aria-label": `Access level for ${o.email}`,
1265
- children: LEVELS.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: l.label, children: l.label }, l.label))
1305
+ "aria-label": t2("operators.accessLevelFor", { email: o.email }),
1306
+ children: LEVELS.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: l.id, children: t2(`operators.level.${l.id}`) }, l.id))
1266
1307
  }
1267
1308
  ) }),
1268
1309
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { style: styles.td, children: [
1269
1310
  status(o),
1270
- o.enrolled && o.backupCodesLeft <= 2 && !o.disabledAt ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { ...styles.muted, marginTop: 4 }, children: [
1271
- o.backupCodesLeft,
1272
- " backup code(s) left"
1273
- ] }) : null
1311
+ o.enrolled && o.backupCodesLeft <= 2 && !o.disabledAt ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: { ...styles.muted, marginTop: 4 }, children: t2(
1312
+ o.backupCodesLeft === 1 ? "operators.backupCodesLeftOne" : "operators.backupCodesLeftMany",
1313
+ { n: o.backupCodesLeft }
1314
+ ) }) : null
1274
1315
  ] }),
1275
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: o.lastLoginAt ? new Date(o.lastLoginAt).toLocaleString() : "never" }),
1276
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, textAlign: "right", whiteSpace: "nowrap" }, children: o.email === me ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: styles.muted, children: "you" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1316
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: o.lastLoginAt ? (0, import_client8.formatAdminDate)(o.lastLoginAt, locale) : t2("common.never") }),
1317
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: { ...styles.td, textAlign: "right", whiteSpace: "nowrap" }, children: o.email === me ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: styles.muted, children: t2("common.you") }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1277
1318
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1278
1319
  "button",
1279
1320
  {
1280
1321
  type: "button",
1281
1322
  style: { ...styles.button, height: 28, marginRight: 6 },
1282
1323
  onClick: () => {
1283
- if (window.confirm(
1284
- `Create a recovery link for ${o.email}? It signs them out everywhere; the link sets a new password and a new authenticator.`
1285
- ))
1324
+ if (window.confirm(t2("operators.recoveryConfirm", { email: o.email })))
1286
1325
  void recover(o.id).then(setMinted).catch(() => {
1287
1326
  });
1288
1327
  },
1289
- children: "Recovery link"
1328
+ children: t2("operators.recoveryLink")
1290
1329
  }
1291
1330
  ),
1292
1331
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
@@ -1298,35 +1337,32 @@ function OperatorsScreen({ client, me }) {
1298
1337
  height: 28
1299
1338
  },
1300
1339
  onClick: () => {
1301
- if (o.disabledAt || window.confirm(`Disable ${o.email}? They are signed out immediately.`))
1340
+ if (o.disabledAt || window.confirm(t2("operators.disableConfirm", { email: o.email })))
1302
1341
  void update(o.id, { disabled: !o.disabledAt }).catch(() => {
1303
1342
  });
1304
1343
  },
1305
- children: o.disabledAt ? "Enable" : "Disable"
1344
+ children: o.disabledAt ? t2("operators.enable") : t2("operators.disable")
1306
1345
  }
1307
1346
  )
1308
1347
  ] }) })
1309
1348
  ] }, o.id)) })
1310
1349
  ] }) : null,
1311
1350
  report && report.links.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
1312
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: styles.subtitle, children: "Pending links" }),
1351
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: styles.subtitle, children: t2("operators.pendingLinks") }),
1313
1352
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { style: styles.list, children: report.links.map((l) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1314
1353
  "li",
1315
1354
  {
1316
1355
  style: { ...styles.row, display: "flex", gap: 12, alignItems: "center" },
1317
1356
  children: [
1318
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: l.kind === "invite" ? "info" : "warn", dot: false, children: l.kind }),
1357
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Pill, { tone: l.kind === "invite" ? "info" : "warn", dot: false, children: l.kind === "invite" ? t2("operators.linkKind.invite") : t2("operators.linkKind.recovery") }),
1319
1358
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: { flex: 1 }, children: [
1320
1359
  l.email,
1321
1360
  l.kind === "invite" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: styles.muted, children: [
1322
1361
  " \xB7 ",
1323
- levelOf(l.scopes)
1362
+ t2(`operators.level.${levelOf(l.scopes)}`)
1324
1363
  ] }) : null
1325
1364
  ] }),
1326
- /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { style: styles.muted, children: [
1327
- "expires ",
1328
- new Date(l.expiresAt).toLocaleString()
1329
- ] }),
1365
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { style: styles.muted, children: t2("operators.expiresOn", { date: (0, import_client8.formatAdminDate)(l.expiresAt, locale) }) }),
1330
1366
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1331
1367
  "button",
1332
1368
  {
@@ -1334,7 +1370,7 @@ function OperatorsScreen({ client, me }) {
1334
1370
  style: { ...styles.buttonDanger, height: 28 },
1335
1371
  onClick: () => void revokeLink(l.id).catch(() => {
1336
1372
  }),
1337
- children: "Revoke"
1373
+ children: t2("operators.revoke")
1338
1374
  }
1339
1375
  )
1340
1376
  ]
@@ -1346,11 +1382,13 @@ function OperatorsScreen({ client, me }) {
1346
1382
  }
1347
1383
 
1348
1384
  // src/screens/TokensScreen.tsx
1385
+ var import_client9 = require("@fonderie/client");
1349
1386
  var import_react_admin9 = require("@fonderie/react-admin");
1350
1387
  var import_react3 = require("react");
1351
1388
  var import_jsx_runtime10 = require("react/jsx-runtime");
1352
1389
  var ALL = ["read", "write", "secrets"];
1353
- function TokensScreen({ client }) {
1390
+ function TokensScreen({ client, locale }) {
1391
+ const t2 = (0, import_client9.createAdminT)(locale);
1354
1392
  const { report, isLoading, error, issue, revoke } = (0, import_react_admin9.useAdminTokens)(client);
1355
1393
  const [name, setName] = (0, import_react3.useState)("");
1356
1394
  const [scopes, setScopes] = (0, import_react3.useState)(["read"]);
@@ -1358,19 +1396,13 @@ function TokensScreen({ client }) {
1358
1396
  const [minted, setMinted] = (0, import_react3.useState)(null);
1359
1397
  const toggle = (s) => setScopes((prev) => prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]);
1360
1398
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.container, children: [
1361
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1362
- PageHeader,
1363
- {
1364
- title: "Tokens",
1365
- lead: "Credentials for machines: the root token's strength, scoped tokens for the CLI and CI, and any legacy per-module tokens. People sign in as operators."
1366
- }
1367
- ),
1368
- error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.error, role: "alert", children: error.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.explanation }) : null,
1369
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Root token" }),
1399
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PageHeader, { title: t2("tokens.title"), lead: t2("tokens.lead") }),
1400
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.error, role: "alert", children: error.status === 401 ? t2("tokens.needsRoot") : error.explanation }) : null,
1401
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: t2("tokens.rootToken") }),
1370
1402
  report ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { ...styles.card, display: "flex", gap: 12, alignItems: "flex-start" }, children: [
1371
1403
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { name: "lock", size: 18, style: { marginTop: 2 } }),
1372
1404
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { children: [
1373
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: report.admin.ok ? "ok" : "bad", children: report.admin.ok ? "strong" : "weak" }),
1405
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: report.admin.ok ? "ok" : "bad", children: report.admin.ok ? t2("tokens.strong") : t2("tokens.weak") }),
1374
1406
  report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1375
1407
  "div",
1376
1408
  {
@@ -1381,8 +1413,8 @@ function TokensScreen({ client }) {
1381
1413
  ))
1382
1414
  ] })
1383
1415
  ] }) : null,
1384
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Issued tokens" }),
1385
- report?.issued === null ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "Issuing is off \u2014 give AdminModule a store and run its migrations." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1416
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: t2("tokens.issuedTokens") }),
1417
+ report?.issued === null ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: t2("tokens.issuingOff") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1386
1418
  minted ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1387
1419
  "div",
1388
1420
  {
@@ -1392,7 +1424,7 @@ function TokensScreen({ client }) {
1392
1424
  borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
1393
1425
  },
1394
1426
  children: [
1395
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: "Copy this now \u2014 it is never shown again." }),
1427
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: t2("tokens.copyNow") }),
1396
1428
  " (",
1397
1429
  minted.name,
1398
1430
  ")",
@@ -1423,8 +1455,8 @@ function TokensScreen({ client }) {
1423
1455
  name: name.trim(),
1424
1456
  scopes,
1425
1457
  ...days && Number.isInteger(d) && d > 0 ? { expiresInDays: d } : {}
1426
- }).then((t2) => {
1427
- setMinted({ name: t2.name, token: t2.token });
1458
+ }).then((t3) => {
1459
+ setMinted({ name: t3.name, token: t3.token });
1428
1460
  setName("");
1429
1461
  setDays("");
1430
1462
  }).catch(() => {
@@ -1436,9 +1468,9 @@ function TokensScreen({ client }) {
1436
1468
  {
1437
1469
  value: name,
1438
1470
  onChange: (e) => setName(e.target.value),
1439
- placeholder: "name",
1471
+ placeholder: t2("tokens.namePlaceholder"),
1440
1472
  style: styles.input,
1441
- "aria-label": "Name"
1473
+ "aria-label": t2("tokens.nameLabel")
1442
1474
  }
1443
1475
  ),
1444
1476
  ALL.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -1455,7 +1487,7 @@ function TokensScreen({ client }) {
1455
1487
  children: [
1456
1488
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "checkbox", checked: scopes.includes(s), onChange: () => toggle(s) }),
1457
1489
  " ",
1458
- s
1490
+ t2(`tokens.scope.${s}`)
1459
1491
  ]
1460
1492
  },
1461
1493
  s
@@ -1465,9 +1497,9 @@ function TokensScreen({ client }) {
1465
1497
  {
1466
1498
  value: days,
1467
1499
  onChange: (e) => setDays(e.target.value),
1468
- placeholder: "days (optional)",
1500
+ placeholder: t2("tokens.daysPlaceholder"),
1469
1501
  style: { ...styles.input, width: 140 },
1470
- "aria-label": "Days"
1502
+ "aria-label": t2("tokens.daysLabel")
1471
1503
  }
1472
1504
  ),
1473
1505
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -1478,7 +1510,7 @@ function TokensScreen({ client }) {
1478
1510
  disabled: !name.trim() || scopes.length === 0 || isLoading,
1479
1511
  children: [
1480
1512
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon, { name: "plus", size: 14 }),
1481
- "Issue token"
1513
+ t2("tokens.issue")
1482
1514
  ]
1483
1515
  }
1484
1516
  )
@@ -1487,70 +1519,89 @@ function TokensScreen({ client }) {
1487
1519
  ),
1488
1520
  report?.issued && report.issued.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("table", { style: styles.table, children: [
1489
1521
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1490
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Name" }),
1491
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Scopes" }),
1492
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Created" }),
1493
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Expires" }),
1494
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Last used" }),
1522
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: t2("tokens.col.name") }),
1523
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: t2("tokens.col.scopes") }),
1524
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: t2("tokens.col.created") }),
1525
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: t2("tokens.col.expires") }),
1526
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: t2("tokens.col.lastUsed") }),
1495
1527
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th })
1496
1528
  ] }) }),
1497
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("tbody", { children: report.issued.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1529
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("tbody", { children: report.issued.map((tk) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
1498
1530
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
1499
- t2.name,
1500
- t2.revokedAt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1531
+ tk.name,
1532
+ tk.revokedAt ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1501
1533
  " ",
1502
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "neutral", children: "revoked" })
1534
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Pill, { tone: "neutral", children: t2("tokens.revoked") })
1503
1535
  ] }) : null
1504
1536
  ] }),
1505
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.scopes.join(", ") }),
1537
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: tk.scopes.map((s) => t2(`tokens.scope.${s}`)).join(", ") }),
1506
1538
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: { ...styles.td, ...styles.muted }, children: [
1507
- new Date(t2.createdAt).toLocaleDateString(),
1539
+ (0, import_client9.formatAdminDate)(tk.createdAt, locale, "date"),
1508
1540
  " \xB7 ",
1509
- t2.createdBy
1541
+ tk.createdBy
1510
1542
  ] }),
1511
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.expiresAt ? new Date(t2.expiresAt).toLocaleDateString() : "never" }),
1512
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.lastUsedAt ? new Date(t2.lastUsedAt).toLocaleString() : "never" }),
1513
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: t2.revokedAt ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1543
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: tk.expiresAt ? (0, import_client9.formatAdminDate)(tk.expiresAt, locale, "date") : t2("common.never") }),
1544
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: tk.lastUsedAt ? (0, import_client9.formatAdminDate)(tk.lastUsedAt, locale) : t2("common.never") }),
1545
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: tk.revokedAt ? null : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1514
1546
  "button",
1515
1547
  {
1516
1548
  type: "button",
1517
1549
  style: styles.buttonDanger,
1518
1550
  onClick: () => {
1519
- if (window.confirm(
1520
- `Revoke "${t2.name}"? Anything using it stops working immediately.`
1521
- ))
1522
- void revoke(t2.id).catch(() => {
1551
+ if (window.confirm(t2("tokens.revokeConfirm", { name: tk.name })))
1552
+ void revoke(tk.id).catch(() => {
1523
1553
  });
1524
1554
  },
1525
- children: "Revoke"
1555
+ children: t2("tokens.revoke")
1526
1556
  }
1527
1557
  ) })
1528
- ] }, t2.id)) })
1529
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Empty, { icon: "tokens", title: "No scoped tokens yet", children: "Issue a read-only token for a dashboard or a teammate instead of sharing the root." })
1558
+ ] }, tk.id)) })
1559
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Empty, { icon: "tokens", title: t2("tokens.emptyTitle"), children: t2("tokens.emptyBody") })
1530
1560
  ] }),
1531
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
1532
- report && report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { style: styles.list, children: report?.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { style: styles.row, children: [
1561
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: t2("tokens.legacyTitle") }),
1562
+ report && report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: t2("tokens.legacyNone") }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("ul", { style: styles.list, children: report?.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("li", { style: styles.row, children: [
1533
1563
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.mono, children: l.module }),
1534
1564
  " ",
1535
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
1565
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: t2("tokens.legacyRow") })
1536
1566
  ] }, l.module)) })
1537
1567
  ] });
1538
1568
  }
1539
1569
 
1540
1570
  // src/screens/UsersScreen.tsx
1541
- var import_client = require("@fonderie/client");
1571
+ var import_client12 = require("@fonderie/client");
1542
1572
  var import_react_admin11 = require("@fonderie/react-admin");
1543
1573
  var import_react6 = require("react");
1544
1574
 
1545
1575
  // src/billing.ts
1576
+ var import_client10 = require("@fonderie/client");
1546
1577
  var import_react4 = require("react");
1547
- function periodEnd(s) {
1578
+ function periodEnd(s, locale) {
1548
1579
  if (!s.currentPeriodEnd) return null;
1549
- const date = new Date(s.currentPeriodEnd).toLocaleDateString();
1580
+ const t2 = (0, import_client10.createAdminT)(locale);
1581
+ const date = (0, import_client10.formatAdminDate)(s.currentPeriodEnd, locale, "date");
1550
1582
  if (s.status === "canceled")
1551
- return { label: new Date(s.currentPeriodEnd) < /* @__PURE__ */ new Date() ? "Ended" : "Ends", date };
1552
- if (s.cancelAtPeriodEnd) return { label: "Ends", date };
1553
- return { label: "Renews", date };
1583
+ return {
1584
+ label: new Date(s.currentPeriodEnd) < /* @__PURE__ */ new Date() ? t2("billing.ended") : t2("billing.ends"),
1585
+ date
1586
+ };
1587
+ if (s.cancelAtPeriodEnd) return { label: t2("billing.ends"), date };
1588
+ return { label: t2("billing.renews"), date };
1589
+ }
1590
+ var STATUS_KEYS = [
1591
+ "active",
1592
+ "trialing",
1593
+ "past_due",
1594
+ "unpaid",
1595
+ "canceled",
1596
+ "incomplete",
1597
+ "incomplete_expired",
1598
+ "paused"
1599
+ ];
1600
+ function statusLabel(t2, status) {
1601
+ return STATUS_KEYS.includes(status) ? t2(`billing.statusLabel.${status}`) : status;
1602
+ }
1603
+ function intervalLabel(t2, interval) {
1604
+ return interval === "month" || interval === "year" ? t2(`billing.intervalLabel.${interval}`) : interval;
1554
1605
  }
1555
1606
  var statusTone = (status) => status === "active" ? "ok" : status === "trialing" ? "info" : status === "past_due" || status === "unpaid" ? "warn" : "neutral";
1556
1607
  function useSubscriptionIndex(client) {
@@ -1585,23 +1636,28 @@ function useSubscriptionIndex(client) {
1585
1636
  }
1586
1637
 
1587
1638
  // src/screens/SubscriberBilling.tsx
1639
+ var import_client11 = require("@fonderie/client");
1588
1640
  var import_react_admin10 = require("@fonderie/react-admin");
1589
1641
  var import_react5 = require("react");
1590
1642
  var import_jsx_runtime11 = require("react/jsx-runtime");
1591
- function SubscriberBilling({ client, subscriber }) {
1643
+ function SubscriberBilling({ client, subscriber, locale }) {
1644
+ const t2 = (0, import_client11.createAdminT)(locale);
1645
+ const LEDGER_KINDS = ["purchase", "grant", "usage", "refund", "adjustment", "expiry"];
1646
+ const ledgerKind = (kind) => LEDGER_KINDS.includes(kind) ? t2(`billing.ledgerKind.${kind}`) : kind;
1592
1647
  const [currency, setCurrency] = (0, import_react5.useState)("");
1593
1648
  const [currencyDraft, setCurrencyDraft] = (0, import_react5.useState)("");
1594
1649
  const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin10.useAdminSubscriber)(client, subscriber, { limit: 20, ...currency ? { currency } : {} });
1595
1650
  const [amount, setAmount] = (0, import_react5.useState)("");
1596
1651
  const [note, setNote] = (0, import_react5.useState)("");
1597
1652
  const [granted, setGranted] = (0, import_react5.useState)(null);
1598
- if (isLoading && !wallet && !subscription) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Loading billing\u2026" });
1599
- const end = subscription ? periodEnd(subscription) : null;
1653
+ if (isLoading && !wallet && !subscription)
1654
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: t2("billing.loading") });
1655
+ const end = subscription ? periodEnd(subscription, locale) : null;
1600
1656
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1601
1657
  error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1602
1658
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { ...styles.grid, marginBottom: 16 }, children: [
1603
1659
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.card, children: [
1604
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: "Plan" }),
1660
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: t2("billing.plan") }),
1605
1661
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1606
1662
  "div",
1607
1663
  {
@@ -1613,14 +1669,14 @@ function SubscriberBilling({ client, subscriber }) {
1613
1669
  flexWrap: "wrap"
1614
1670
  },
1615
1671
  children: [
1616
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { style: { fontSize: 18 }, children: subscription ? subscription.plan : "Free" }),
1672
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { style: { fontSize: 18 }, children: subscription ? subscription.plan : t2("billing.free") }),
1617
1673
  subscription ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1618
1674
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: styles.muted, children: [
1619
1675
  "\xB7 ",
1620
- subscription.interval
1676
+ intervalLabel(t2, subscription.interval)
1621
1677
  ] }),
1622
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: statusTone(subscription.status), children: subscription.status })
1623
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: "no subscription" })
1678
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: statusTone(subscription.status), children: statusLabel(t2, subscription.status) })
1679
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: t2("billing.noSubscription") })
1624
1680
  ]
1625
1681
  }
1626
1682
  ),
@@ -1642,7 +1698,7 @@ function SubscriberBilling({ client, subscriber }) {
1642
1698
  gap: 8
1643
1699
  },
1644
1700
  children: [
1645
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: "Credits" }),
1701
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: t2("billing.credits") }),
1646
1702
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1647
1703
  "form",
1648
1704
  {
@@ -1656,11 +1712,11 @@ function SubscriberBilling({ client, subscriber }) {
1656
1712
  {
1657
1713
  value: currencyDraft,
1658
1714
  onChange: (e) => setCurrencyDraft(e.target.value),
1659
- placeholder: wallet?.currency ?? "currency",
1715
+ placeholder: wallet?.currency ?? t2("billing.currencyPlaceholder"),
1660
1716
  maxLength: 8,
1661
1717
  style: { ...styles.input, height: 26, width: 84, fontSize: 12 },
1662
- "aria-label": "Wallet currency",
1663
- title: "Show the wallet in another currency"
1718
+ "aria-label": t2("billing.currencyLabel"),
1719
+ title: t2("billing.currencyTitle")
1664
1720
  }
1665
1721
  )
1666
1722
  }
@@ -1675,13 +1731,15 @@ function SubscriberBilling({ client, subscriber }) {
1675
1731
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontSize: 14, fontWeight: 500 }, children: wallet.currency })
1676
1732
  ] }),
1677
1733
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statHint, children: [
1678
- wallet.purchased ?? "0",
1679
- " permanent \xB7 ",
1680
- wallet.granted ?? "0",
1681
- " plan allowance",
1682
- wallet.grantedExpiresAt ? ` (expires ${new Date(wallet.grantedExpiresAt).toLocaleDateString()})` : ""
1734
+ t2("billing.breakdown", {
1735
+ permanent: wallet.purchased ?? "0",
1736
+ allowance: wallet.granted ?? "0"
1737
+ }),
1738
+ wallet.grantedExpiresAt ? t2("billing.expires", {
1739
+ date: (0, import_client11.formatAdminDate)(wallet.grantedExpiresAt, locale, "date")
1740
+ }) : ""
1683
1741
  ] })
1684
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statHint, children: "No wallet \u2014 billing has no wallet configuration." })
1742
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statHint, children: t2("billing.noWallet") })
1685
1743
  ] })
1686
1744
  ] }),
1687
1745
  wallet ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
@@ -1703,23 +1761,25 @@ function SubscriberBilling({ client, subscriber }) {
1703
1761
  ...note ? { description: note } : {},
1704
1762
  idempotencyKey: key
1705
1763
  }).then(() => {
1706
- setGranted(`Granted ${amount.trim()} ${wallet.currency}.`);
1764
+ setGranted(
1765
+ t2("billing.granted", { amount: amount.trim(), currency: wallet.currency })
1766
+ );
1707
1767
  setAmount("");
1708
1768
  setNote("");
1709
1769
  }).catch(() => {
1710
1770
  });
1711
1771
  },
1712
1772
  children: [
1713
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { style: { fontSize: 13.5, marginRight: 4 }, children: "Grant credits" }),
1773
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { style: { fontSize: 13.5, marginRight: 4 }, children: t2("billing.grantCredits") }),
1714
1774
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1715
1775
  "input",
1716
1776
  {
1717
1777
  value: amount,
1718
1778
  onChange: (e) => setAmount(e.target.value),
1719
- placeholder: `amount (${wallet.currency})`,
1779
+ placeholder: t2("billing.amountPlaceholder", { currency: wallet.currency }),
1720
1780
  inputMode: "numeric",
1721
1781
  style: { ...styles.input, width: 150 },
1722
- "aria-label": "Amount"
1782
+ "aria-label": t2("billing.amountLabel")
1723
1783
  }
1724
1784
  ),
1725
1785
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
@@ -1727,9 +1787,9 @@ function SubscriberBilling({ client, subscriber }) {
1727
1787
  {
1728
1788
  value: note,
1729
1789
  onChange: (e) => setNote(e.target.value),
1730
- placeholder: "reason (optional)",
1790
+ placeholder: t2("billing.reasonPlaceholder"),
1731
1791
  style: { ...styles.input, flex: 1, minWidth: 180 },
1732
- "aria-label": "Reason"
1792
+ "aria-label": t2("billing.reasonLabel")
1733
1793
  }
1734
1794
  ),
1735
1795
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
@@ -1740,7 +1800,7 @@ function SubscriberBilling({ client, subscriber }) {
1740
1800
  disabled: !/^\d+$/.test(amount.trim()),
1741
1801
  children: [
1742
1802
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Icon, { name: "plus", size: 14 }),
1743
- "Grant"
1803
+ t2("billing.grant")
1744
1804
  ]
1745
1805
  }
1746
1806
  ),
@@ -1748,20 +1808,20 @@ function SubscriberBilling({ client, subscriber }) {
1748
1808
  ]
1749
1809
  }
1750
1810
  ) : null,
1751
- wallet ? ledger.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Empty, { icon: "subscriber", title: "No credit movements yet" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1811
+ wallet ? ledger.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Empty, { icon: "subscriber", title: t2("billing.noMovements") }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1752
1812
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { style: styles.table, children: [
1753
1813
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1754
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "When" }),
1755
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Kind" }),
1756
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Amount" }),
1757
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Note" })
1814
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: t2("billing.col.when") }),
1815
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: t2("billing.col.kind") }),
1816
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: t2("billing.col.amount") }),
1817
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: t2("billing.col.note") })
1758
1818
  ] }) }),
1759
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: ledger.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1760
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: new Date(t2.createdAt).toLocaleString() }),
1761
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: t2.type }),
1762
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.amount }),
1763
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.description ?? "" })
1764
- ] }, t2.id)) })
1819
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: ledger.map((tx) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1820
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: (0, import_client11.formatAdminDate)(tx.createdAt, locale) }),
1821
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: ledgerKind(tx.type) }),
1822
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: tx.amount }),
1823
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: tx.description ?? "" })
1824
+ ] }, tx.id)) })
1765
1825
  ] }),
1766
1826
  hasMoreLedger ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1767
1827
  "button",
@@ -1769,7 +1829,7 @@ function SubscriberBilling({ client, subscriber }) {
1769
1829
  type: "button",
1770
1830
  style: { ...styles.button, marginTop: 8 },
1771
1831
  onClick: () => void loadMoreLedger(),
1772
- children: "Load more"
1832
+ children: t2("common.loadMore")
1773
1833
  }
1774
1834
  ) : null
1775
1835
  ] }) : null
@@ -1782,8 +1842,10 @@ function UsersScreen({
1782
1842
  client,
1783
1843
  pageSize = 50,
1784
1844
  billingClient,
1785
- openUserId
1845
+ openUserId,
1846
+ locale
1786
1847
  }) {
1848
+ const t2 = (0, import_client12.createAdminT)(locale);
1787
1849
  const [input, setInput] = (0, import_react6.useState)("");
1788
1850
  const [selected, setSelected] = (0, import_react6.useState)(
1789
1851
  openUserId ? { id: openUserId } : null
@@ -1801,19 +1863,13 @@ function UsersScreen({
1801
1863
  const userId = user?.id ?? null;
1802
1864
  const sessions = (0, import_react_admin11.useAdminUserSessions)(client, userId);
1803
1865
  const history = (0, import_react_admin11.useAdminLoginHistory)(client, userId, { limit: 20 });
1804
- const yesNo = (v) => v ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: "yes" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "no" });
1866
+ const yesNo = (v) => v ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: t2("common.yes") }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: t2("common.no") });
1805
1867
  const clear = () => {
1806
1868
  setSelected(null);
1807
1869
  setInput("");
1808
1870
  };
1809
1871
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.container, children: [
1810
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1811
- PageHeader,
1812
- {
1813
- title: "Users",
1814
- lead: "Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
1815
- }
1816
- ),
1872
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PageHeader, { title: t2("users.title"), lead: t2("users.lead") }),
1817
1873
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1818
1874
  "form",
1819
1875
  {
@@ -1830,18 +1886,18 @@ function UsersScreen({
1830
1886
  type: "email",
1831
1887
  value: input,
1832
1888
  onChange: (e) => setInput(e.target.value),
1833
- placeholder: "email address",
1889
+ placeholder: t2("users.emailPlaceholder"),
1834
1890
  style: { ...styles.input, minWidth: 280 },
1835
- "aria-label": "Email"
1891
+ "aria-label": t2("users.emailLabel")
1836
1892
  }
1837
1893
  ),
1838
1894
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1839
1895
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "search", size: 14 }),
1840
- "Look up"
1896
+ t2("common.lookUp")
1841
1897
  ] }),
1842
1898
  selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: clear, children: [
1843
1899
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "back", size: 14 }),
1844
- "All users"
1900
+ t2("users.allUsers")
1845
1901
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1846
1902
  "button",
1847
1903
  {
@@ -1849,7 +1905,7 @@ function UsersScreen({
1849
1905
  style: styles.button,
1850
1906
  onClick: () => void list.refresh(),
1851
1907
  disabled: list.isLoading,
1852
- children: "Refresh"
1908
+ children: t2("common.refresh")
1853
1909
  }
1854
1910
  ),
1855
1911
  !selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -1863,7 +1919,7 @@ function UsersScreen({
1863
1919
  padding: 0,
1864
1920
  minWidth: 0
1865
1921
  },
1866
- "aria-label": "Which accounts",
1922
+ "aria-label": t2("users.whichAccounts"),
1867
1923
  children: [false, true].map((d) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1868
1924
  "button",
1869
1925
  {
@@ -1874,7 +1930,7 @@ function UsersScreen({
1874
1930
  ...showDeleted === d ? styles.buttonPrimary : styles.button,
1875
1931
  height: 28
1876
1932
  },
1877
- children: d ? "Deleted" : "Active"
1933
+ children: d ? t2("users.deletedAccounts") : t2("users.activeAccounts")
1878
1934
  },
1879
1935
  String(d)
1880
1936
  ))
@@ -1883,34 +1939,39 @@ function UsersScreen({
1883
1939
  ]
1884
1940
  }
1885
1941
  ),
1886
- error && !(error.status === 404 && selected?.id) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
1942
+ error && !(error.status === 404 && selected?.id) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? t2("users.noUserWithEmail") : error.explanation }) : null,
1887
1943
  error?.status === 404 && selected?.id ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1888
1944
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.notice, role: "status", children: [
1889
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: "No account with id " }),
1945
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("strong", { children: [
1946
+ t2("users.missingIdPrefix"),
1947
+ " "
1948
+ ] }),
1890
1949
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { style: styles.code, children: selected.id }),
1891
1950
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: "." }),
1892
- " It was deleted, or never existed here. Its billing records remain."
1951
+ " ",
1952
+ t2("users.missingIdBody")
1893
1953
  ] }),
1894
1954
  billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1895
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Plan & credits" }),
1955
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: t2("users.planCredits") }),
1896
1956
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1897
1957
  SubscriberBilling,
1898
1958
  {
1899
1959
  client: billingClient,
1900
- subscriber: { type: "user", id: selected.id }
1960
+ subscriber: { type: "user", id: selected.id },
1961
+ locale
1901
1962
  }
1902
1963
  )
1903
1964
  ] }) : null
1904
1965
  ] }) : null,
1905
1966
  !selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1906
1967
  list.error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1907
- list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Empty, { icon: "users", title: "No users yet", children: "Sign-ups appear here as they happen." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { style: styles.table, children: [
1968
+ list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Empty, { icon: "users", title: t2("users.emptyTitle"), children: t2("users.emptyBody") }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { style: styles.table, children: [
1908
1969
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1909
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Email" }),
1910
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Name" }),
1911
- billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Plan" }) : null,
1912
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Created" }),
1913
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Status" })
1970
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: t2("users.col.email") }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: t2("users.col.name") }),
1972
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: t2("users.col.plan") }) : null,
1973
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: t2("users.col.created") }),
1974
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: t2("users.col.status") })
1914
1975
  ] }) }),
1915
1976
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: list.users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1916
1977
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -1927,29 +1988,29 @@ function UsersScreen({
1927
1988
  const sub = plans?.get(`user/${u.id}`);
1928
1989
  if (!plans) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "\u2026" });
1929
1990
  if (!sub || sub.status === "canceled")
1930
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "free" });
1991
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: t2("common.status.free") });
1931
1992
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1932
1993
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: sub.plan }),
1933
1994
  " ",
1934
- sub.status !== "active" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: statusTone(sub.status), children: sub.status }) : null
1995
+ sub.status !== "active" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: statusTone(sub.status), children: statusLabel(t2, sub.status) }) : null
1935
1996
  ] });
1936
1997
  })() }) : null,
1937
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
1998
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: (0, import_client12.formatAdminDate)(u.createdAt, locale, "date") }),
1938
1999
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1939
- u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1940
- u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null,
1941
- !u.suspended && !u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: "active" }) : null
2000
+ u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: t2("common.status.suspended") }) : null,
2001
+ u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: t2("common.status.deleted") }) : null,
2002
+ !u.suspended && !u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: t2("common.status.active") }) : null
1942
2003
  ] })
1943
2004
  ] }, u.id)) })
1944
2005
  ] }),
1945
- list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2006
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: t2("common.loading") }) : null,
1946
2007
  list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1947
2008
  "button",
1948
2009
  {
1949
2010
  type: "button",
1950
2011
  style: { ...styles.button, marginTop: 8 },
1951
2012
  onClick: () => void list.loadMore(),
1952
- children: "Load more"
2013
+ children: t2("common.loadMore")
1953
2014
  }
1954
2015
  ) : null
1955
2016
  ] }) : null,
@@ -1957,49 +2018,48 @@ function UsersScreen({
1957
2018
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("h2", { style: styles.subtitle, children: [
1958
2019
  user.firstName || user.lastName ? `${user.firstName} ${user.lastName}`.trim() : user.email,
1959
2020
  " ",
1960
- user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1961
- user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null
2021
+ user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: t2("common.status.suspended") }) : null,
2022
+ user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: t2("common.status.deleted") }) : null
1962
2023
  ] }),
1963
2024
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tbody", { children: [
1964
2025
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1965
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "id" }),
2026
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: t2("users.field.id") }),
1966
2027
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1967
2028
  ] }),
1968
2029
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1969
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "email" }),
2030
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: t2("users.field.email") }),
1970
2031
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1971
2032
  user.email,
1972
- " \xB7 verified ",
2033
+ " \xB7 ",
2034
+ t2("users.field.verified"),
2035
+ " ",
1973
2036
  yesNo(user.isEmailVerified)
1974
2037
  ] })
1975
2038
  ] }),
1976
2039
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1977
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "MFA" }),
2040
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: t2("users.field.mfa") }),
1978
2041
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1979
2042
  ] }),
1980
2043
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1981
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "provider" }),
2044
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: t2("users.field.provider") }),
1982
2045
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1983
- user.provider || "password",
1984
- user.hasPassword ? "" : " \xB7 no password set"
2046
+ user.provider || t2("users.passwordProvider"),
2047
+ user.hasPassword ? "" : ` \xB7 ${t2("users.noPasswordSet")}`
1985
2048
  ] })
1986
2049
  ] }),
1987
2050
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1988
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "last login" }),
1989
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: user.lastLogin ? new Date(user.lastLogin).toLocaleString() : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "never" }) })
2051
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: t2("users.field.lastLogin") }),
2052
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: user.lastLogin ? (0, import_client12.formatAdminDate)(user.lastLogin, locale) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: t2("common.never") }) })
1990
2053
  ] }),
1991
2054
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1992
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "created" }),
1993
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
2055
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: t2("users.field.created") }),
2056
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: (0, import_client12.formatAdminDate)(user.createdAt, locale) })
1994
2057
  ] })
1995
2058
  ] }) }),
1996
2059
  user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...styles.notice, marginTop: 12 }, role: "status", children: [
1997
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("strong", { children: [
1998
- "Deleted on ",
1999
- new Date(user.deletedAt).toLocaleString(),
2000
- "."
2001
- ] }),
2002
- " The account cannot sign in, and it is erased when the retention purge runs. Its billing records stay."
2060
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: t2("users.deletedOn", { date: (0, import_client12.formatAdminDate)(user.deletedAt, locale) }) }),
2061
+ " ",
2062
+ t2("users.deletedBody")
2003
2063
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
2004
2064
  user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2005
2065
  "button",
@@ -2008,7 +2068,7 @@ function UsersScreen({
2008
2068
  style: styles.button,
2009
2069
  onClick: () => void unsuspend(),
2010
2070
  disabled: isLoading,
2011
- children: "Unsuspend"
2071
+ children: t2("users.unsuspend")
2012
2072
  }
2013
2073
  ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2014
2074
  "button",
@@ -2017,7 +2077,7 @@ function UsersScreen({
2017
2077
  style: styles.buttonDanger,
2018
2078
  onClick: () => void suspend(),
2019
2079
  disabled: isLoading,
2020
- children: "Suspend"
2080
+ children: t2("users.suspend")
2021
2081
  }
2022
2082
  ),
2023
2083
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -2027,51 +2087,49 @@ function UsersScreen({
2027
2087
  style: styles.button,
2028
2088
  onClick: () => void revokeSessions().then(() => sessions.refresh()),
2029
2089
  disabled: isLoading,
2030
- children: "Sign out everywhere"
2090
+ children: t2("users.signOutEverywhere")
2031
2091
  }
2032
2092
  )
2033
2093
  ] }),
2034
2094
  billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2035
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Plan & credits" }),
2095
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: t2("users.planCredits") }),
2036
2096
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2037
2097
  SubscriberBilling,
2038
2098
  {
2039
2099
  client: billingClient,
2040
- subscriber: { type: "user", id: user.id }
2100
+ subscriber: { type: "user", id: user.id },
2101
+ locale
2041
2102
  }
2042
2103
  )
2043
2104
  ] }) : null,
2044
2105
  !user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2045
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Live sessions" }),
2046
- sessions.sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: "None." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { style: styles.list, children: sessions.sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { style: styles.row, children: [
2106
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: t2("users.liveSessions") }),
2107
+ sessions.sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: t2("common.none") }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { style: styles.list, children: sessions.sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { style: styles.row, children: [
2047
2108
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
2048
2109
  " ",
2049
- (0, import_client.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
2050
- (0, import_client.describeLocation)(s.location),
2110
+ (0, import_client12.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
2111
+ (0, import_client12.describeLocation)(s.location),
2051
2112
  " "
2052
2113
  ] }) : null,
2053
2114
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
2054
2115
  " ",
2055
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
2056
- "since ",
2057
- new Date(s.createdAt).toLocaleString()
2058
- ] })
2116
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: t2("users.since", { date: (0, import_client12.formatAdminDate)(s.createdAt, locale) }) })
2059
2117
  ] }, s.id)) }),
2060
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
2061
- history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: "None recorded." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { style: styles.list, children: history.events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { style: styles.row, children: [
2062
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome }),
2118
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: t2("users.recentSignIns") }),
2119
+ history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: t2("users.noneRecorded") }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { style: styles.list, children: history.events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { style: styles.row, children: [
2120
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome === "success" ? t2("users.outcome.success") : e.outcome === "failure" ? t2("users.outcome.failure") : e.outcome }),
2063
2121
  " ",
2064
2122
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: e.method }),
2065
2123
  " ",
2066
2124
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
2067
2125
  " ",
2068
- (0, import_client.describeLocation)(e.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: (0, import_client.describeLocation)(e.location) }) : null,
2126
+ (0, import_client12.describeLocation)(e.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: (0, import_client12.describeLocation)(e.location) }) : null,
2069
2127
  e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2070
2128
  " ",
2071
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
2129
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: e.location.proxy ? t2("users.proxyVpn") : t2("users.hosting") })
2072
2130
  ] }) : null,
2073
2131
  " ",
2074
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
2132
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: (0, import_client12.formatAdminDate)(e.createdAt, locale) })
2075
2133
  ] }, e.id)) }),
2076
2134
  history.hasMore && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2077
2135
  "button",
@@ -2079,7 +2137,7 @@ function UsersScreen({
2079
2137
  type: "button",
2080
2138
  style: { ...styles.button, marginTop: 8 },
2081
2139
  onClick: () => void history.loadMore(),
2082
- children: "Load more"
2140
+ children: t2("common.loadMore")
2083
2141
  }
2084
2142
  ) : null
2085
2143
  ] }) : null
@@ -2088,24 +2146,33 @@ function UsersScreen({
2088
2146
  }
2089
2147
 
2090
2148
  // src/screens/CatalogScreen.tsx
2149
+ var import_client13 = require("@fonderie/client");
2091
2150
  var import_react_admin12 = require("@fonderie/react-admin");
2092
2151
  var import_jsx_runtime13 = require("react/jsx-runtime");
2093
- function CatalogScreen({ client }) {
2152
+ function CatalogScreen({ client, locale }) {
2153
+ const t2 = (0, import_client13.createAdminT)(locale);
2094
2154
  const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin12.useAdminCatalog)(client);
2095
2155
  const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
2096
2156
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: styles.container, children: [
2097
2157
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2098
2158
  PageHeader,
2099
2159
  {
2100
- title: "Catalog",
2101
- lead: "What you sell: plans as configured in code, and as stored in the database, side by side.",
2102
- actions: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
2160
+ title: t2("catalog.title"),
2161
+ lead: t2("catalog.lead"),
2162
+ actions: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2163
+ RefreshButton,
2164
+ {
2165
+ onClick: () => void refresh(),
2166
+ busy: isLoading,
2167
+ label: t2("common.refresh")
2168
+ }
2169
+ )
2103
2170
  }
2104
2171
  ),
2105
2172
  error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2106
- isLoading && !catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2173
+ isLoading && !catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.status, children: t2("common.loading") }) : null,
2107
2174
  catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2108
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Configured (code)" }),
2175
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: t2("catalog.configured") }),
2109
2176
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2110
2177
  "pre",
2111
2178
  {
@@ -2120,15 +2187,15 @@ function CatalogScreen({ client }) {
2120
2187
  children: JSON.stringify(catalog.configured, null, 2)
2121
2188
  }
2122
2189
  ),
2123
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: "Stored (database)" }),
2124
- catalog.stored.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Empty, { icon: "catalog", title: "No stored plans", children: "Plans are configured in code; nothing has been written to the database." }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { style: styles.table, children: [
2190
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: t2("catalog.stored") }),
2191
+ catalog.stored.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Empty, { icon: "catalog", title: t2("catalog.emptyTitle"), children: t2("catalog.emptyBody") }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { style: styles.table, children: [
2125
2192
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2126
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Plan" }),
2127
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Tier" }),
2128
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Seats" }),
2129
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Monthly" }),
2130
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Yearly" }),
2131
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Trial" }),
2193
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: t2("catalog.col.plan") }),
2194
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: t2("catalog.col.tier") }),
2195
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: t2("catalog.col.seats") }),
2196
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: t2("catalog.col.monthly") }),
2197
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: t2("catalog.col.yearly") }),
2198
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: t2("catalog.col.trial") }),
2132
2199
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th })
2133
2200
  ] }) }),
2134
2201
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
@@ -2144,18 +2211,18 @@ function CatalogScreen({ client }) {
2144
2211
  p.pricing?.currency ?? ""
2145
2212
  ] }),
2146
2213
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: money(p.pricing?.yearly) }),
2147
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
2214
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.trialDays ? t2("catalog.trialDays", { n: p.trialDays }) : "\u2014" }),
2148
2215
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2149
2216
  "button",
2150
2217
  {
2151
2218
  type: "button",
2152
2219
  style: styles.buttonDanger,
2153
2220
  onClick: () => {
2154
- if (window.confirm(`Delete stored plan "${p.name}"?`))
2221
+ if (window.confirm(t2("catalog.deleteConfirm", { name: p.name })))
2155
2222
  void deletePlan(p.id).catch(() => {
2156
2223
  });
2157
2224
  },
2158
- children: "Delete"
2225
+ children: t2("common.delete")
2159
2226
  }
2160
2227
  ) })
2161
2228
  ] }, p.id)) })
@@ -2165,19 +2232,26 @@ function CatalogScreen({ client }) {
2165
2232
  }
2166
2233
 
2167
2234
  // src/screens/SubscriberScreen.tsx
2235
+ var import_client14 = require("@fonderie/client");
2168
2236
  var import_react_admin13 = require("@fonderie/react-admin");
2169
2237
  var import_react7 = require("react");
2170
2238
  var import_jsx_runtime14 = require("react/jsx-runtime");
2171
2239
  var FILTERS = [
2172
- { label: "All", match: () => true },
2173
- { label: "Active", match: (s) => s === "active" },
2174
- { label: "Trialing", match: (s) => s === "trialing" },
2175
- { label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
2176
- { label: "Canceled", match: (s) => s === "canceled" }
2240
+ { id: "all", match: () => true },
2241
+ { id: "active", match: (s) => s === "active" },
2242
+ { id: "trialing", match: (s) => s === "trialing" },
2243
+ { id: "pastDue", match: (s) => s === "past_due" || s === "unpaid" },
2244
+ { id: "canceled", match: (s) => s === "canceled" }
2177
2245
  ];
2178
- function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2246
+ function SubscriberScreen({
2247
+ client,
2248
+ pageSize = 50,
2249
+ onOpenUser,
2250
+ locale
2251
+ }) {
2252
+ const t2 = (0, import_client14.createAdminT)(locale);
2179
2253
  const list = (0, import_react_admin13.useAdminSubscribers)(client, { limit: pageSize });
2180
- const [filter, setFilter] = (0, import_react7.useState)("All");
2254
+ const [filter, setFilter] = (0, import_react7.useState)("all");
2181
2255
  const [open, setOpen] = (0, import_react7.useState)(null);
2182
2256
  const [type, setType] = (0, import_react7.useState)("workspace");
2183
2257
  const [idInput, setIdInput] = (0, import_react7.useState)("");
@@ -2187,26 +2261,33 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2187
2261
  PageHeader,
2188
2262
  {
2189
2263
  title: `${open.type}/${open.id}`,
2190
- lead: "Plan, credits and what moved.",
2264
+ lead: t2("billing.subscriptions.detailLead"),
2191
2265
  actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setOpen(null), children: [
2192
2266
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "back", size: 14 }),
2193
- "All subscriptions"
2267
+ t2("billing.subscriptions.back")
2194
2268
  ] })
2195
2269
  }
2196
2270
  ),
2197
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SubscriberBilling, { client, subscriber: open })
2271
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SubscriberBilling, { client, subscriber: open, locale })
2198
2272
  ] });
2199
2273
  }
2200
- const match = FILTERS.find((f) => f.label === filter)?.match ?? (() => true);
2274
+ const match = FILTERS.find((f) => f.id === filter)?.match ?? (() => true);
2201
2275
  const rows = list.subscriptions.filter((s) => match(s.status));
2202
- const openRow = (t2, id) => t2 === "user" && onOpenUser ? onOpenUser(id) : setOpen({ type: t2, id });
2276
+ const openRow = (t3, id) => t3 === "user" && onOpenUser ? onOpenUser(id) : setOpen({ type: t3, id });
2203
2277
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2204
2278
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2205
2279
  PageHeader,
2206
2280
  {
2207
- title: "Subscriptions",
2208
- lead: "Who pays, and who is behind. A user's plan and credits are also on their page under Users \u2014 free users appear only there.",
2209
- actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RefreshButton, { onClick: () => void list.refresh(), busy: list.isLoading })
2281
+ title: t2("billing.subscriptions.title"),
2282
+ lead: t2("billing.subscriptions.lead"),
2283
+ actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2284
+ RefreshButton,
2285
+ {
2286
+ onClick: () => void list.refresh(),
2287
+ busy: list.isLoading,
2288
+ label: t2("common.refresh")
2289
+ }
2290
+ )
2210
2291
  }
2211
2292
  ),
2212
2293
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { ...styles.toolbar, justifyContent: "space-between" }, children: [
@@ -2222,23 +2303,23 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2222
2303
  padding: 0,
2223
2304
  minWidth: 0
2224
2305
  },
2225
- "aria-label": "Filter by status",
2306
+ "aria-label": t2("billing.subscriptions.filterLabel"),
2226
2307
  children: FILTERS.map((f) => {
2227
2308
  const n = list.subscriptions.filter((s) => f.match(s.status)).length;
2228
- const on = filter === f.label;
2309
+ const on = filter === f.id;
2229
2310
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2230
2311
  "button",
2231
2312
  {
2232
2313
  type: "button",
2233
2314
  "aria-pressed": on,
2234
- onClick: () => setFilter(f.label),
2315
+ onClick: () => setFilter(f.id),
2235
2316
  style: { ...on ? styles.buttonPrimary : styles.button, height: 28 },
2236
2317
  children: [
2237
- f.label,
2318
+ t2(`billing.subscriptions.filter.${f.id}`),
2238
2319
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { opacity: 0.7 }, children: n })
2239
2320
  ]
2240
2321
  },
2241
- f.label
2322
+ f.id
2242
2323
  );
2243
2324
  })
2244
2325
  }
@@ -2258,10 +2339,10 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2258
2339
  value: type,
2259
2340
  onChange: (e) => setType(e.target.value),
2260
2341
  style: styles.input,
2261
- "aria-label": "Type",
2342
+ "aria-label": t2("billing.subscriptions.typeLabel"),
2262
2343
  children: [
2263
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "workspace", children: "workspace" }),
2264
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "user", children: "user" })
2344
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "workspace", children: t2("billing.subscriptions.typeWorkspace") }),
2345
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "user", children: t2("billing.subscriptions.typeUser") })
2265
2346
  ]
2266
2347
  }
2267
2348
  ),
@@ -2270,36 +2351,29 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2270
2351
  {
2271
2352
  value: idInput,
2272
2353
  onChange: (e) => setIdInput(e.target.value),
2273
- placeholder: "subscriber id",
2354
+ placeholder: t2("billing.subscriptions.idPlaceholder"),
2274
2355
  style: { ...styles.input, width: 220 },
2275
- "aria-label": "Subscriber id"
2356
+ "aria-label": t2("billing.subscriptions.idLabel")
2276
2357
  }
2277
2358
  ),
2278
2359
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "submit", style: styles.button, disabled: !idInput.trim(), children: [
2279
2360
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "search", size: 14 }),
2280
- "Open"
2361
+ t2("common.open")
2281
2362
  ] })
2282
2363
  ]
2283
2364
  }
2284
2365
  )
2285
2366
  ] }),
2286
2367
  list.error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
2287
- rows.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2288
- Empty,
2289
- {
2290
- icon: "subscriber",
2291
- title: filter === "All" ? "No subscriptions yet" : `No ${filter.toLowerCase()} subscriptions`,
2292
- children: "Subscriptions appear here once someone checks out."
2293
- }
2294
- ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("table", { style: styles.table, children: [
2368
+ rows.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Empty, { icon: "subscriber", title: t2(`billing.subscriptions.empty.${filter}`), children: t2("billing.subscriptions.emptyHint") }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("table", { style: styles.table, children: [
2295
2369
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2296
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Subscriber" }),
2297
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Plan" }),
2298
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Status" }),
2299
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Renews / ends" })
2370
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: t2("billing.subscriptions.col.subscriber") }),
2371
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: t2("billing.subscriptions.col.plan") }),
2372
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: t2("billing.subscriptions.col.status") }),
2373
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: t2("billing.subscriptions.col.renewsEnds") })
2300
2374
  ] }) }),
2301
2375
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tbody", { children: rows.map((s) => {
2302
- const end = periodEnd(s);
2376
+ const end = periodEnd(s, locale);
2303
2377
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2304
2378
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2305
2379
  "button",
@@ -2319,14 +2393,14 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2319
2393
  " ",
2320
2394
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { style: styles.muted, children: [
2321
2395
  "\xB7 ",
2322
- s.interval
2396
+ intervalLabel(t2, s.interval)
2323
2397
  ] })
2324
2398
  ] }),
2325
2399
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("td", { style: styles.td, children: [
2326
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: statusTone(s.status), children: s.status }),
2400
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: statusTone(s.status), children: statusLabel(t2, s.status) }),
2327
2401
  s.cancelAtPeriodEnd && s.status !== "canceled" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2328
2402
  " ",
2329
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: "warn", dot: false, children: "cancels" })
2403
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: "warn", dot: false, children: t2("billing.subscriptions.cancels") })
2330
2404
  ] }) : null
2331
2405
  ] }),
2332
2406
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: { ...styles.td, whiteSpace: "nowrap" }, children: end ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
@@ -2337,24 +2411,26 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2337
2411
  ] }, s.id);
2338
2412
  }) })
2339
2413
  ] }),
2340
- list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2414
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.status, children: t2("common.loading") }) : null,
2341
2415
  list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2342
2416
  "button",
2343
2417
  {
2344
2418
  type: "button",
2345
2419
  style: { ...styles.button, marginTop: 8 },
2346
2420
  onClick: () => void list.loadMore(),
2347
- children: "Load more"
2421
+ children: t2("common.loadMore")
2348
2422
  }
2349
2423
  ) : null
2350
2424
  ] });
2351
2425
  }
2352
2426
 
2353
2427
  // src/screens/AuditScreen.tsx
2428
+ var import_client15 = require("@fonderie/client");
2354
2429
  var import_react_admin14 = require("@fonderie/react-admin");
2355
2430
  var import_react8 = require("react");
2356
2431
  var import_jsx_runtime15 = require("react/jsx-runtime");
2357
- function AuditScreen({ client, pageSize = 50 }) {
2432
+ function AuditScreen({ client, pageSize = 50, locale }) {
2433
+ const t2 = (0, import_client15.createAdminT)(locale);
2358
2434
  const [draft, setDraft] = (0, import_react8.useState)({ workspaceId: "", type: "", actorId: "", from: "", to: "" });
2359
2435
  const [filter, setFilter] = (0, import_react8.useState)({});
2360
2436
  const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin14.useAdminAudit)(client, {
@@ -2372,13 +2448,7 @@ function AuditScreen({ client, pageSize = 50 }) {
2372
2448
  }
2373
2449
  );
2374
2450
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.container, children: [
2375
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2376
- PageHeader,
2377
- {
2378
- title: "Audit",
2379
- lead: "What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
2380
- }
2381
- ),
2451
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PageHeader, { title: t2("audit.title"), lead: t2("audit.lead") }),
2382
2452
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2383
2453
  "form",
2384
2454
  {
@@ -2395,9 +2465,9 @@ function AuditScreen({ client, pageSize = 50 }) {
2395
2465
  });
2396
2466
  },
2397
2467
  children: [
2398
- field("workspaceId", "workspace id (all if empty)"),
2399
- field("type", "event type"),
2400
- field("actorId", "actor id"),
2468
+ field("workspaceId", t2("audit.workspacePlaceholder")),
2469
+ field("type", t2("audit.typePlaceholder")),
2470
+ field("actorId", t2("audit.actorPlaceholder")),
2401
2471
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2402
2472
  "input",
2403
2473
  {
@@ -2405,8 +2475,8 @@ function AuditScreen({ client, pageSize = 50 }) {
2405
2475
  value: draft.from,
2406
2476
  onChange: (e) => setDraft({ ...draft, from: e.target.value }),
2407
2477
  style: styles.input,
2408
- "aria-label": "From date",
2409
- title: "From (inclusive)"
2478
+ "aria-label": t2("audit.fromLabel"),
2479
+ title: t2("audit.fromTitle")
2410
2480
  }
2411
2481
  ),
2412
2482
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
@@ -2416,13 +2486,13 @@ function AuditScreen({ client, pageSize = 50 }) {
2416
2486
  value: draft.to,
2417
2487
  onChange: (e) => setDraft({ ...draft, to: e.target.value }),
2418
2488
  style: styles.input,
2419
- "aria-label": "To date",
2420
- title: "To (inclusive)"
2489
+ "aria-label": t2("audit.toLabel"),
2490
+ title: t2("audit.toTitle")
2421
2491
  }
2422
2492
  ),
2423
2493
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
2424
2494
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "search", size: 14 }),
2425
- "Filter"
2495
+ t2("common.filter")
2426
2496
  ] }),
2427
2497
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2428
2498
  "button",
@@ -2431,37 +2501,37 @@ function AuditScreen({ client, pageSize = 50 }) {
2431
2501
  style: styles.button,
2432
2502
  onClick: () => void refresh(),
2433
2503
  disabled: isLoading,
2434
- children: "Refresh"
2504
+ children: t2("common.refresh")
2435
2505
  }
2436
2506
  )
2437
2507
  ]
2438
2508
  }
2439
2509
  ),
2440
2510
  error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2441
- events.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Empty, { icon: "audit", title: "Nothing recorded for this filter" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("table", { style: styles.table, children: [
2511
+ events.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Empty, { icon: "audit", title: t2("audit.empty") }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("table", { style: styles.table, children: [
2442
2512
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2443
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "When" }),
2444
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Type" }),
2445
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Workspace" }),
2446
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Actor" }),
2447
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Request" })
2513
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: t2("audit.col.when") }),
2514
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: t2("audit.col.type") }),
2515
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: t2("audit.col.workspace") }),
2516
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: t2("audit.col.actor") }),
2517
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: t2("audit.col.request") })
2448
2518
  ] }) }),
2449
2519
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2450
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
2520
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: (0, import_client15.formatAdminDate)(e.createdAt, locale) }),
2451
2521
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
2452
2522
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
2453
2523
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
2454
2524
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
2455
2525
  ] }, e.id)) })
2456
2526
  ] }),
2457
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2527
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.status, children: t2("common.loading") }) : null,
2458
2528
  hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2459
2529
  "button",
2460
2530
  {
2461
2531
  type: "button",
2462
2532
  style: { ...styles.button, marginTop: 8 },
2463
2533
  onClick: () => void loadMore(),
2464
- children: "Load more"
2534
+ children: t2("common.loadMore")
2465
2535
  }
2466
2536
  ) : null
2467
2537
  ] });
@@ -2499,34 +2569,43 @@ function useNarrow() {
2499
2569
  return narrow;
2500
2570
  }
2501
2571
  var NAV = [
2502
- { group: "Today", items: [{ page: "attention", label: "Attention" }] },
2572
+ { group: "nav.groups.today", items: [{ page: "attention", label: "nav.items.attention" }] },
2503
2573
  {
2504
- group: "System",
2574
+ group: "nav.groups.system",
2505
2575
  items: [
2506
- { page: "modules", label: "Modules" },
2507
- { page: "environment", label: "Environment" },
2508
- { page: "doctor", label: "Doctor" },
2509
- { page: "routes", label: "Routes" }
2576
+ { page: "modules", label: "nav.items.modules" },
2577
+ { page: "environment", label: "nav.items.environment" },
2578
+ { page: "doctor", label: "nav.items.doctor" },
2579
+ { page: "routes", label: "nav.items.routes" }
2510
2580
  ]
2511
2581
  },
2512
- { group: "People", items: [{ page: "users", label: "Users", needs: "auth" }] },
2513
2582
  {
2514
- group: "Money",
2583
+ group: "nav.groups.people",
2584
+ items: [{ page: "users", label: "nav.items.users", needs: "auth" }]
2585
+ },
2586
+ {
2587
+ group: "nav.groups.money",
2515
2588
  items: [
2516
- { page: "catalog", label: "Catalog", needs: "billing" },
2517
- { page: "subscriber", label: "Subscriptions", needs: "billing" }
2589
+ { page: "catalog", label: "nav.items.catalog", needs: "billing" },
2590
+ { page: "subscriber", label: "nav.items.subscriptions", needs: "billing" }
2518
2591
  ]
2519
2592
  },
2520
- { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
2521
- { group: "Messaging", items: [{ page: "templates", label: "Templates", needs: "courier" }] },
2522
2593
  {
2523
- group: "Activity",
2594
+ group: "nav.groups.settings",
2595
+ items: [{ page: "settings", label: "nav.items.settings", needs: "config" }]
2596
+ },
2597
+ {
2598
+ group: "nav.groups.messaging",
2599
+ items: [{ page: "templates", label: "nav.items.templates", needs: "courier" }]
2600
+ },
2601
+ {
2602
+ group: "nav.groups.activity",
2524
2603
  items: [
2525
- { page: "audit", label: "Audit", needs: "audit" },
2526
- { page: "log", label: "Admin log" },
2527
- { page: "operators", label: "Operators", needs: "operators" },
2528
- { page: "tokens", label: "Tokens" },
2529
- { page: "migrations", label: "Migrations" }
2604
+ { page: "audit", label: "nav.items.audit", needs: "audit" },
2605
+ { page: "log", label: "nav.items.log" },
2606
+ { page: "operators", label: "nav.items.operators", needs: "operators" },
2607
+ { page: "tokens", label: "nav.items.tokens" },
2608
+ { page: "migrations", label: "nav.items.migrations" }
2530
2609
  ]
2531
2610
  }
2532
2611
  ];
@@ -2545,8 +2624,10 @@ function AdminShell({
2545
2624
  footer,
2546
2625
  operators = false,
2547
2626
  currentOperator,
2548
- publicConfigUrl
2627
+ publicConfigUrl,
2628
+ locale
2549
2629
  }) {
2630
+ const t2 = (0, import_client16.createAdminT)(locale);
2550
2631
  const narrow = useNarrow();
2551
2632
  const [drawer, setDrawer] = (0, import_react9.useState)(false);
2552
2633
  const [own, setOwn] = (0, import_react9.useState)("attention");
@@ -2569,40 +2650,41 @@ function AdminShell({
2569
2650
  let body;
2570
2651
  switch (current) {
2571
2652
  case "attention":
2572
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AttentionScreen, { client });
2653
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AttentionScreen, { client, locale });
2573
2654
  break;
2574
2655
  case "modules":
2575
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModulesScreen, { client });
2656
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModulesScreen, { client, locale });
2576
2657
  break;
2577
2658
  case "doctor":
2578
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DoctorScreen, { client });
2659
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DoctorScreen, { client, locale });
2579
2660
  break;
2580
2661
  case "environment":
2581
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EnvironmentScreen, { client });
2662
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EnvironmentScreen, { client, locale });
2582
2663
  break;
2583
2664
  case "routes":
2584
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RoutesScreen, { client });
2665
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RoutesScreen, { client, locale });
2585
2666
  break;
2586
2667
  case "tokens":
2587
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TokensScreen, { client });
2668
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TokensScreen, { client, locale });
2588
2669
  break;
2589
2670
  case "operators":
2590
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OperatorsScreen, { client, me: currentOperator });
2671
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OperatorsScreen, { client, me: currentOperator, locale });
2591
2672
  break;
2592
2673
  case "migrations":
2593
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MigrationsScreen, { client });
2674
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MigrationsScreen, { client, locale });
2594
2675
  break;
2595
2676
  case "log":
2596
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AdminLogScreen, { client });
2677
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AdminLogScreen, { client, locale });
2597
2678
  break;
2598
2679
  case "catalog":
2599
- body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to see the catalog here." });
2680
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CatalogScreen, { client: billingClient, locale }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: t2("shell.missingClient", { client: "BillingAdminClient" }) });
2600
2681
  break;
2601
2682
  case "subscriber":
2602
2683
  body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2603
2684
  SubscriberScreen,
2604
2685
  {
2605
2686
  client: billingClient,
2687
+ locale,
2606
2688
  ...authClient ? {
2607
2689
  onOpenUser: (id) => {
2608
2690
  setOpenUser(id);
@@ -2610,10 +2692,10 @@ function AdminShell({
2610
2692
  }
2611
2693
  } : {}
2612
2694
  }
2613
- ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to look up subscribers here." });
2695
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: t2("shell.missingClient", { client: "BillingAdminClient" }) });
2614
2696
  break;
2615
2697
  case "audit":
2616
- body = auditClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AuditScreen, { client: auditClient }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuditAdminClient to see the audit trail here." });
2698
+ body = auditClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AuditScreen, { client: auditClient, locale }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: t2("shell.missingClient", { client: "AuditAdminClient" }) });
2617
2699
  break;
2618
2700
  case "users":
2619
2701
  body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
@@ -2621,16 +2703,17 @@ function AdminShell({
2621
2703
  {
2622
2704
  client: authClient,
2623
2705
  billingClient,
2624
- openUserId: openUser
2706
+ openUserId: openUser,
2707
+ locale
2625
2708
  },
2626
2709
  openUser ?? "list"
2627
- ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuthAdminClient to look up users here." });
2710
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: t2("shell.missingClient", { client: "AuthAdminClient" }) });
2628
2711
  break;
2629
2712
  case "settings":
2630
- body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && (editing.kind === "config" || editing.kind === "secret") ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2713
+ body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: t2("shell.missingClient", { client: "ConfigAdminClient" }) }) : editing && (editing.kind === "config" || editing.kind === "secret") ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2631
2714
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2632
2715
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2633
- "Config & secrets"
2716
+ t2("nav.items.settings")
2634
2717
  ] }) }),
2635
2718
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2636
2719
  import_react_config_admin_screens.ConfigEditorScreen,
@@ -2641,7 +2724,8 @@ function AdminShell({
2641
2724
  ...editing.environment !== void 0 ? { environment: editing.environment } : environment !== void 0 ? { environment } : {},
2642
2725
  ...editing.environments ? { environments: editing.environments } : {},
2643
2726
  onSaved: () => setEditing(null),
2644
- onDeleted: () => setEditing(null)
2727
+ onDeleted: () => setEditing(null),
2728
+ locale
2645
2729
  },
2646
2730
  `${editing.kind}:${editing.key}:${editing.environment ?? ""}`
2647
2731
  )
@@ -2649,6 +2733,7 @@ function AdminShell({
2649
2733
  import_react_config_admin_screens.ConfigListScreen,
2650
2734
  {
2651
2735
  client: configClient,
2736
+ locale,
2652
2737
  ...environment !== void 0 ? { environment } : {},
2653
2738
  ...publicConfigUrl ? { publicConfigUrl } : {},
2654
2739
  onSelectConfig: (key, env) => setEditing({ kind: "config", key, environment: env }),
@@ -2669,15 +2754,16 @@ function AdminShell({
2669
2754
  );
2670
2755
  break;
2671
2756
  case "templates":
2672
- body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template-new" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2757
+ body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: t2("shell.missingClient", { client: "CourierAdminClient" }) }) : editing?.kind === "template-new" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2673
2758
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2674
2759
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2675
- "Templates"
2760
+ t2("nav.items.templates")
2676
2761
  ] }) }),
2677
2762
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2678
2763
  import_react_courier_admin_screens.TemplateCreateScreen,
2679
2764
  {
2680
2765
  client: courierClient,
2766
+ locale,
2681
2767
  ...editing.type ? { type: editing.type } : {},
2682
2768
  ...editing.locales ? { locales: editing.locales } : {},
2683
2769
  onCreated: (c) => setEditing({ kind: "template", type: c.type, locale: c.locale, system: false })
@@ -2686,7 +2772,7 @@ function AdminShell({
2686
2772
  ] }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2687
2773
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2688
2774
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2689
- "Templates"
2775
+ t2("nav.items.templates")
2690
2776
  ] }) }),
2691
2777
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2692
2778
  import_react_courier_admin_screens.TemplateEditorScreen,
@@ -2697,7 +2783,14 @@ function AdminShell({
2697
2783
  system: editing.system,
2698
2784
  onSaved: () => setEditing(null),
2699
2785
  onDeleted: () => setEditing(null),
2700
- onAddLocale: (type) => setEditing({ kind: "template-new", type })
2786
+ onAddLocale: (type, c) => setEditing({ kind: "template-new", type, locales: c.locales }),
2787
+ onSelectLocale: (tpl) => setEditing({
2788
+ kind: "template",
2789
+ type: tpl.type,
2790
+ locale: tpl.locale,
2791
+ system: tpl.system === true
2792
+ }),
2793
+ uiLocale: locale
2701
2794
  },
2702
2795
  `${editing.type}:${editing.locale ?? ""}`
2703
2796
  )
@@ -2705,11 +2798,12 @@ function AdminShell({
2705
2798
  import_react_courier_admin_screens.TemplateListScreen,
2706
2799
  {
2707
2800
  client: courierClient,
2708
- onSelectTemplate: (t2) => setEditing({
2801
+ locale,
2802
+ onSelectTemplate: (tpl) => setEditing({
2709
2803
  kind: "template",
2710
- type: t2.type,
2711
- locale: t2.locale ?? null,
2712
- system: t2.system === true
2804
+ type: tpl.type,
2805
+ locale: tpl.locale ?? null,
2806
+ system: tpl.system === true
2713
2807
  }),
2714
2808
  onCreateTemplate: (c) => setEditing({ kind: "template-new", locales: c.locales })
2715
2809
  }
@@ -2743,7 +2837,7 @@ function AdminShell({
2743
2837
  {
2744
2838
  type: "button",
2745
2839
  style: styles.buttonGhost,
2746
- "aria-label": "Close menu",
2840
+ "aria-label": t2("shell.closeMenu"),
2747
2841
  onClick: () => setDrawer(false),
2748
2842
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "close" })
2749
2843
  }
@@ -2756,14 +2850,14 @@ function AdminShell({
2756
2850
  ...styles.nav,
2757
2851
  ...narrow ? { width: 280, maxWidth: "85vw", boxShadow: "0 10px 40px rgba(0,0,0,.25)" } : {}
2758
2852
  },
2759
- "aria-label": "Admin",
2853
+ "aria-label": t2("shell.navLabel"),
2760
2854
  children: [
2761
2855
  brand,
2762
2856
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navScroll, children: NAV.map((g) => {
2763
2857
  const items = g.items.filter((i) => !i.needs || has[i.needs]);
2764
2858
  if (items.length === 0) return null;
2765
2859
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
2766
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navGroup, children: g.group }),
2860
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navGroup, children: t2(g.group) }),
2767
2861
  items.map((i) => {
2768
2862
  const active = current === i.page;
2769
2863
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -2780,7 +2874,7 @@ function AdminShell({
2780
2874
  },
2781
2875
  children: [
2782
2876
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2783
- i.label
2877
+ t2(i.label)
2784
2878
  ]
2785
2879
  },
2786
2880
  i.page
@@ -2792,7 +2886,8 @@ function AdminShell({
2792
2886
  ]
2793
2887
  }
2794
2888
  );
2795
- const title = NAV.flatMap((g) => g.items).find((i) => i.page === current)?.label ?? "";
2889
+ const titleKey = NAV.flatMap((g) => g.items).find((i) => i.page === current)?.label;
2890
+ const title = titleKey ? t2(titleKey) : "";
2796
2891
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2797
2892
  narrow ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2798
2893
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.topbar, children: [
@@ -2801,7 +2896,7 @@ function AdminShell({
2801
2896
  {
2802
2897
  type: "button",
2803
2898
  style: styles.buttonGhost,
2804
- "aria-label": "Open menu",
2899
+ "aria-label": t2("shell.openMenu"),
2805
2900
  "aria-expanded": drawer,
2806
2901
  onClick: () => setDrawer(true),
2807
2902
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "menu" })