@fonderie/react-admin-screens 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -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,21 +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 }) {
1592
- const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin10.useAdminSubscriber)(client, subscriber, { limit: 20 });
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;
1647
+ const [currency, setCurrency] = (0, import_react5.useState)("");
1648
+ const [currencyDraft, setCurrencyDraft] = (0, import_react5.useState)("");
1649
+ const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin10.useAdminSubscriber)(client, subscriber, { limit: 20, ...currency ? { currency } : {} });
1593
1650
  const [amount, setAmount] = (0, import_react5.useState)("");
1594
1651
  const [note, setNote] = (0, import_react5.useState)("");
1595
1652
  const [granted, setGranted] = (0, import_react5.useState)(null);
1596
- if (isLoading && !wallet && !subscription) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Loading billing\u2026" });
1597
- 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;
1598
1656
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1599
1657
  error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1600
1658
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { ...styles.grid, marginBottom: 16 }, children: [
1601
1659
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.card, children: [
1602
- /* @__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") }),
1603
1661
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1604
1662
  "div",
1605
1663
  {
@@ -1611,14 +1669,14 @@ function SubscriberBilling({ client, subscriber }) {
1611
1669
  flexWrap: "wrap"
1612
1670
  },
1613
1671
  children: [
1614
- /* @__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") }),
1615
1673
  subscription ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1616
1674
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: styles.muted, children: [
1617
1675
  "\xB7 ",
1618
- subscription.interval
1676
+ intervalLabel(t2, subscription.interval)
1619
1677
  ] }),
1620
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: statusTone(subscription.status), children: subscription.status })
1621
- ] }) : /* @__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") })
1622
1680
  ]
1623
1681
  }
1624
1682
  ),
@@ -1630,7 +1688,42 @@ function SubscriberBilling({ client, subscriber }) {
1630
1688
  subscription?.providerSubscriptionId ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { ...styles.statHint, fontFamily: "var(--fonderie-mono,monospace)" }, children: subscription.providerSubscriptionId }) : null
1631
1689
  ] }),
1632
1690
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.card, children: [
1633
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: "Credits" }),
1691
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1692
+ "div",
1693
+ {
1694
+ style: {
1695
+ display: "flex",
1696
+ justifyContent: "space-between",
1697
+ alignItems: "center",
1698
+ gap: 8
1699
+ },
1700
+ children: [
1701
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: t2("billing.credits") }),
1702
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1703
+ "form",
1704
+ {
1705
+ style: { display: "flex", gap: 4 },
1706
+ onSubmit: (e) => {
1707
+ e.preventDefault();
1708
+ setCurrency(currencyDraft.trim().toUpperCase());
1709
+ },
1710
+ children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1711
+ "input",
1712
+ {
1713
+ value: currencyDraft,
1714
+ onChange: (e) => setCurrencyDraft(e.target.value),
1715
+ placeholder: wallet?.currency ?? t2("billing.currencyPlaceholder"),
1716
+ maxLength: 8,
1717
+ style: { ...styles.input, height: 26, width: 84, fontSize: 12 },
1718
+ "aria-label": t2("billing.currencyLabel"),
1719
+ title: t2("billing.currencyTitle")
1720
+ }
1721
+ )
1722
+ }
1723
+ )
1724
+ ]
1725
+ }
1726
+ ),
1634
1727
  wallet ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1635
1728
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statValue, children: [
1636
1729
  wallet.balance,
@@ -1638,13 +1731,15 @@ function SubscriberBilling({ client, subscriber }) {
1638
1731
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontSize: 14, fontWeight: 500 }, children: wallet.currency })
1639
1732
  ] }),
1640
1733
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statHint, children: [
1641
- wallet.purchased ?? "0",
1642
- " permanent \xB7 ",
1643
- wallet.granted ?? "0",
1644
- " plan allowance",
1645
- 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
+ }) : ""
1646
1741
  ] })
1647
- ] }) : /* @__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") })
1648
1743
  ] })
1649
1744
  ] }),
1650
1745
  wallet ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
@@ -1666,23 +1761,25 @@ function SubscriberBilling({ client, subscriber }) {
1666
1761
  ...note ? { description: note } : {},
1667
1762
  idempotencyKey: key
1668
1763
  }).then(() => {
1669
- setGranted(`Granted ${amount.trim()} ${wallet.currency}.`);
1764
+ setGranted(
1765
+ t2("billing.granted", { amount: amount.trim(), currency: wallet.currency })
1766
+ );
1670
1767
  setAmount("");
1671
1768
  setNote("");
1672
1769
  }).catch(() => {
1673
1770
  });
1674
1771
  },
1675
1772
  children: [
1676
- /* @__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") }),
1677
1774
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1678
1775
  "input",
1679
1776
  {
1680
1777
  value: amount,
1681
1778
  onChange: (e) => setAmount(e.target.value),
1682
- placeholder: `amount (${wallet.currency})`,
1779
+ placeholder: t2("billing.amountPlaceholder", { currency: wallet.currency }),
1683
1780
  inputMode: "numeric",
1684
1781
  style: { ...styles.input, width: 150 },
1685
- "aria-label": "Amount"
1782
+ "aria-label": t2("billing.amountLabel")
1686
1783
  }
1687
1784
  ),
1688
1785
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
@@ -1690,9 +1787,9 @@ function SubscriberBilling({ client, subscriber }) {
1690
1787
  {
1691
1788
  value: note,
1692
1789
  onChange: (e) => setNote(e.target.value),
1693
- placeholder: "reason (optional)",
1790
+ placeholder: t2("billing.reasonPlaceholder"),
1694
1791
  style: { ...styles.input, flex: 1, minWidth: 180 },
1695
- "aria-label": "Reason"
1792
+ "aria-label": t2("billing.reasonLabel")
1696
1793
  }
1697
1794
  ),
1698
1795
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
@@ -1703,7 +1800,7 @@ function SubscriberBilling({ client, subscriber }) {
1703
1800
  disabled: !/^\d+$/.test(amount.trim()),
1704
1801
  children: [
1705
1802
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Icon, { name: "plus", size: 14 }),
1706
- "Grant"
1803
+ t2("billing.grant")
1707
1804
  ]
1708
1805
  }
1709
1806
  ),
@@ -1711,20 +1808,20 @@ function SubscriberBilling({ client, subscriber }) {
1711
1808
  ]
1712
1809
  }
1713
1810
  ) : null,
1714
- 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: [
1715
1812
  /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { style: styles.table, children: [
1716
1813
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1717
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "When" }),
1718
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Kind" }),
1719
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Amount" }),
1720
- /* @__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") })
1721
1818
  ] }) }),
1722
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: ledger.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1723
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: new Date(t2.createdAt).toLocaleString() }),
1724
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: t2.type }),
1725
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.amount }),
1726
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.description ?? "" })
1727
- ] }, 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)) })
1728
1825
  ] }),
1729
1826
  hasMoreLedger ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1730
1827
  "button",
@@ -1732,7 +1829,7 @@ function SubscriberBilling({ client, subscriber }) {
1732
1829
  type: "button",
1733
1830
  style: { ...styles.button, marginTop: 8 },
1734
1831
  onClick: () => void loadMoreLedger(),
1735
- children: "Load more"
1832
+ children: t2("common.loadMore")
1736
1833
  }
1737
1834
  ) : null
1738
1835
  ] }) : null
@@ -1745,8 +1842,10 @@ function UsersScreen({
1745
1842
  client,
1746
1843
  pageSize = 50,
1747
1844
  billingClient,
1748
- openUserId
1845
+ openUserId,
1846
+ locale
1749
1847
  }) {
1848
+ const t2 = (0, import_client12.createAdminT)(locale);
1750
1849
  const [input, setInput] = (0, import_react6.useState)("");
1751
1850
  const [selected, setSelected] = (0, import_react6.useState)(
1752
1851
  openUserId ? { id: openUserId } : null
@@ -1764,19 +1863,13 @@ function UsersScreen({
1764
1863
  const userId = user?.id ?? null;
1765
1864
  const sessions = (0, import_react_admin11.useAdminUserSessions)(client, userId);
1766
1865
  const history = (0, import_react_admin11.useAdminLoginHistory)(client, userId, { limit: 20 });
1767
- 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") });
1768
1867
  const clear = () => {
1769
1868
  setSelected(null);
1770
1869
  setInput("");
1771
1870
  };
1772
1871
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.container, children: [
1773
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1774
- PageHeader,
1775
- {
1776
- title: "Users",
1777
- lead: "Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
1778
- }
1779
- ),
1872
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PageHeader, { title: t2("users.title"), lead: t2("users.lead") }),
1780
1873
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1781
1874
  "form",
1782
1875
  {
@@ -1793,18 +1886,18 @@ function UsersScreen({
1793
1886
  type: "email",
1794
1887
  value: input,
1795
1888
  onChange: (e) => setInput(e.target.value),
1796
- placeholder: "email address",
1889
+ placeholder: t2("users.emailPlaceholder"),
1797
1890
  style: { ...styles.input, minWidth: 280 },
1798
- "aria-label": "Email"
1891
+ "aria-label": t2("users.emailLabel")
1799
1892
  }
1800
1893
  ),
1801
1894
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1802
1895
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "search", size: 14 }),
1803
- "Look up"
1896
+ t2("common.lookUp")
1804
1897
  ] }),
1805
1898
  selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: clear, children: [
1806
1899
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "back", size: 14 }),
1807
- "All users"
1900
+ t2("users.allUsers")
1808
1901
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1809
1902
  "button",
1810
1903
  {
@@ -1812,7 +1905,7 @@ function UsersScreen({
1812
1905
  style: styles.button,
1813
1906
  onClick: () => void list.refresh(),
1814
1907
  disabled: list.isLoading,
1815
- children: "Refresh"
1908
+ children: t2("common.refresh")
1816
1909
  }
1817
1910
  ),
1818
1911
  !selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -1826,7 +1919,7 @@ function UsersScreen({
1826
1919
  padding: 0,
1827
1920
  minWidth: 0
1828
1921
  },
1829
- "aria-label": "Which accounts",
1922
+ "aria-label": t2("users.whichAccounts"),
1830
1923
  children: [false, true].map((d) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1831
1924
  "button",
1832
1925
  {
@@ -1837,7 +1930,7 @@ function UsersScreen({
1837
1930
  ...showDeleted === d ? styles.buttonPrimary : styles.button,
1838
1931
  height: 28
1839
1932
  },
1840
- children: d ? "Deleted" : "Active"
1933
+ children: d ? t2("users.deletedAccounts") : t2("users.activeAccounts")
1841
1934
  },
1842
1935
  String(d)
1843
1936
  ))
@@ -1846,34 +1939,39 @@ function UsersScreen({
1846
1939
  ]
1847
1940
  }
1848
1941
  ),
1849
- 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,
1850
1943
  error?.status === 404 && selected?.id ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1851
1944
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.notice, role: "status", children: [
1852
- /* @__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
+ ] }),
1853
1949
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { style: styles.code, children: selected.id }),
1854
1950
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: "." }),
1855
- " It was deleted, or never existed here. Its billing records remain."
1951
+ " ",
1952
+ t2("users.missingIdBody")
1856
1953
  ] }),
1857
1954
  billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1858
- /* @__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") }),
1859
1956
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1860
1957
  SubscriberBilling,
1861
1958
  {
1862
1959
  client: billingClient,
1863
- subscriber: { type: "user", id: selected.id }
1960
+ subscriber: { type: "user", id: selected.id },
1961
+ locale
1864
1962
  }
1865
1963
  )
1866
1964
  ] }) : null
1867
1965
  ] }) : null,
1868
1966
  !selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1869
1967
  list.error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1870
- 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: [
1871
1969
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1872
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Email" }),
1873
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Name" }),
1874
- billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Plan" }) : null,
1875
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Created" }),
1876
- /* @__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") })
1877
1975
  ] }) }),
1878
1976
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: list.users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1879
1977
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -1890,29 +1988,29 @@ function UsersScreen({
1890
1988
  const sub = plans?.get(`user/${u.id}`);
1891
1989
  if (!plans) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "\u2026" });
1892
1990
  if (!sub || sub.status === "canceled")
1893
- 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") });
1894
1992
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1895
1993
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: sub.plan }),
1896
1994
  " ",
1897
- 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
1898
1996
  ] });
1899
1997
  })() }) : null,
1900
- /* @__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") }),
1901
1999
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1902
- u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1903
- u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null,
1904
- !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
1905
2003
  ] })
1906
2004
  ] }, u.id)) })
1907
2005
  ] }),
1908
- 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,
1909
2007
  list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1910
2008
  "button",
1911
2009
  {
1912
2010
  type: "button",
1913
2011
  style: { ...styles.button, marginTop: 8 },
1914
2012
  onClick: () => void list.loadMore(),
1915
- children: "Load more"
2013
+ children: t2("common.loadMore")
1916
2014
  }
1917
2015
  ) : null
1918
2016
  ] }) : null,
@@ -1920,49 +2018,48 @@ function UsersScreen({
1920
2018
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("h2", { style: styles.subtitle, children: [
1921
2019
  user.firstName || user.lastName ? `${user.firstName} ${user.lastName}`.trim() : user.email,
1922
2020
  " ",
1923
- user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1924
- 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
1925
2023
  ] }),
1926
2024
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tbody", { children: [
1927
2025
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1928
- /* @__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") }),
1929
2027
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1930
2028
  ] }),
1931
2029
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1932
- /* @__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") }),
1933
2031
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1934
2032
  user.email,
1935
- " \xB7 verified ",
2033
+ " \xB7 ",
2034
+ t2("users.field.verified"),
2035
+ " ",
1936
2036
  yesNo(user.isEmailVerified)
1937
2037
  ] })
1938
2038
  ] }),
1939
2039
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1940
- /* @__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") }),
1941
2041
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1942
2042
  ] }),
1943
2043
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1944
- /* @__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") }),
1945
2045
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1946
- user.provider || "password",
1947
- user.hasPassword ? "" : " \xB7 no password set"
2046
+ user.provider || t2("users.passwordProvider"),
2047
+ user.hasPassword ? "" : ` \xB7 ${t2("users.noPasswordSet")}`
1948
2048
  ] })
1949
2049
  ] }),
1950
2050
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1951
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "last login" }),
1952
- /* @__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") }) })
1953
2053
  ] }),
1954
2054
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1955
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "created" }),
1956
- /* @__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) })
1957
2057
  ] })
1958
2058
  ] }) }),
1959
2059
  user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...styles.notice, marginTop: 12 }, role: "status", children: [
1960
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("strong", { children: [
1961
- "Deleted on ",
1962
- new Date(user.deletedAt).toLocaleString(),
1963
- "."
1964
- ] }),
1965
- " 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")
1966
2063
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1967
2064
  user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1968
2065
  "button",
@@ -1971,7 +2068,7 @@ function UsersScreen({
1971
2068
  style: styles.button,
1972
2069
  onClick: () => void unsuspend(),
1973
2070
  disabled: isLoading,
1974
- children: "Unsuspend"
2071
+ children: t2("users.unsuspend")
1975
2072
  }
1976
2073
  ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1977
2074
  "button",
@@ -1980,7 +2077,7 @@ function UsersScreen({
1980
2077
  style: styles.buttonDanger,
1981
2078
  onClick: () => void suspend(),
1982
2079
  disabled: isLoading,
1983
- children: "Suspend"
2080
+ children: t2("users.suspend")
1984
2081
  }
1985
2082
  ),
1986
2083
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
@@ -1990,51 +2087,49 @@ function UsersScreen({
1990
2087
  style: styles.button,
1991
2088
  onClick: () => void revokeSessions().then(() => sessions.refresh()),
1992
2089
  disabled: isLoading,
1993
- children: "Sign out everywhere"
2090
+ children: t2("users.signOutEverywhere")
1994
2091
  }
1995
2092
  )
1996
2093
  ] }),
1997
2094
  billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1998
- /* @__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") }),
1999
2096
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2000
2097
  SubscriberBilling,
2001
2098
  {
2002
2099
  client: billingClient,
2003
- subscriber: { type: "user", id: user.id }
2100
+ subscriber: { type: "user", id: user.id },
2101
+ locale
2004
2102
  }
2005
2103
  )
2006
2104
  ] }) : null,
2007
2105
  !user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2008
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Live sessions" }),
2009
- 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: [
2010
2108
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
2011
2109
  " ",
2012
- (0, import_client.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
2013
- (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),
2014
2112
  " "
2015
2113
  ] }) : null,
2016
2114
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
2017
2115
  " ",
2018
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
2019
- "since ",
2020
- new Date(s.createdAt).toLocaleString()
2021
- ] })
2116
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: t2("users.since", { date: (0, import_client12.formatAdminDate)(s.createdAt, locale) }) })
2022
2117
  ] }, s.id)) }),
2023
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
2024
- 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: [
2025
- /* @__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 }),
2026
2121
  " ",
2027
2122
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: e.method }),
2028
2123
  " ",
2029
2124
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
2030
2125
  " ",
2031
- (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,
2032
2127
  e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2033
2128
  " ",
2034
- /* @__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") })
2035
2130
  ] }) : null,
2036
2131
  " ",
2037
- /* @__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) })
2038
2133
  ] }, e.id)) }),
2039
2134
  history.hasMore && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2040
2135
  "button",
@@ -2042,7 +2137,7 @@ function UsersScreen({
2042
2137
  type: "button",
2043
2138
  style: { ...styles.button, marginTop: 8 },
2044
2139
  onClick: () => void history.loadMore(),
2045
- children: "Load more"
2140
+ children: t2("common.loadMore")
2046
2141
  }
2047
2142
  ) : null
2048
2143
  ] }) : null
@@ -2051,24 +2146,33 @@ function UsersScreen({
2051
2146
  }
2052
2147
 
2053
2148
  // src/screens/CatalogScreen.tsx
2149
+ var import_client13 = require("@fonderie/client");
2054
2150
  var import_react_admin12 = require("@fonderie/react-admin");
2055
2151
  var import_jsx_runtime13 = require("react/jsx-runtime");
2056
- function CatalogScreen({ client }) {
2152
+ function CatalogScreen({ client, locale }) {
2153
+ const t2 = (0, import_client13.createAdminT)(locale);
2057
2154
  const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin12.useAdminCatalog)(client);
2058
2155
  const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
2059
2156
  return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: styles.container, children: [
2060
2157
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2061
2158
  PageHeader,
2062
2159
  {
2063
- title: "Catalog",
2064
- lead: "What you sell: plans as configured in code, and as stored in the database, side by side.",
2065
- 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
+ )
2066
2170
  }
2067
2171
  ),
2068
2172
  error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2069
- 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,
2070
2174
  catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2071
- /* @__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") }),
2072
2176
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2073
2177
  "pre",
2074
2178
  {
@@ -2083,15 +2187,15 @@ function CatalogScreen({ client }) {
2083
2187
  children: JSON.stringify(catalog.configured, null, 2)
2084
2188
  }
2085
2189
  ),
2086
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: "Stored (database)" }),
2087
- 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: [
2088
2192
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2089
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Plan" }),
2090
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Tier" }),
2091
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Seats" }),
2092
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Monthly" }),
2093
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Yearly" }),
2094
- /* @__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") }),
2095
2199
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th })
2096
2200
  ] }) }),
2097
2201
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
@@ -2107,18 +2211,18 @@ function CatalogScreen({ client }) {
2107
2211
  p.pricing?.currency ?? ""
2108
2212
  ] }),
2109
2213
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: money(p.pricing?.yearly) }),
2110
- /* @__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" }),
2111
2215
  /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2112
2216
  "button",
2113
2217
  {
2114
2218
  type: "button",
2115
2219
  style: styles.buttonDanger,
2116
2220
  onClick: () => {
2117
- if (window.confirm(`Delete stored plan "${p.name}"?`))
2221
+ if (window.confirm(t2("catalog.deleteConfirm", { name: p.name })))
2118
2222
  void deletePlan(p.id).catch(() => {
2119
2223
  });
2120
2224
  },
2121
- children: "Delete"
2225
+ children: t2("common.delete")
2122
2226
  }
2123
2227
  ) })
2124
2228
  ] }, p.id)) })
@@ -2128,19 +2232,26 @@ function CatalogScreen({ client }) {
2128
2232
  }
2129
2233
 
2130
2234
  // src/screens/SubscriberScreen.tsx
2235
+ var import_client14 = require("@fonderie/client");
2131
2236
  var import_react_admin13 = require("@fonderie/react-admin");
2132
2237
  var import_react7 = require("react");
2133
2238
  var import_jsx_runtime14 = require("react/jsx-runtime");
2134
2239
  var FILTERS = [
2135
- { label: "All", match: () => true },
2136
- { label: "Active", match: (s) => s === "active" },
2137
- { label: "Trialing", match: (s) => s === "trialing" },
2138
- { label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
2139
- { 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" }
2140
2245
  ];
2141
- 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);
2142
2253
  const list = (0, import_react_admin13.useAdminSubscribers)(client, { limit: pageSize });
2143
- const [filter, setFilter] = (0, import_react7.useState)("All");
2254
+ const [filter, setFilter] = (0, import_react7.useState)("all");
2144
2255
  const [open, setOpen] = (0, import_react7.useState)(null);
2145
2256
  const [type, setType] = (0, import_react7.useState)("workspace");
2146
2257
  const [idInput, setIdInput] = (0, import_react7.useState)("");
@@ -2150,26 +2261,33 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2150
2261
  PageHeader,
2151
2262
  {
2152
2263
  title: `${open.type}/${open.id}`,
2153
- lead: "Plan, credits and what moved.",
2264
+ lead: t2("billing.subscriptions.detailLead"),
2154
2265
  actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setOpen(null), children: [
2155
2266
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "back", size: 14 }),
2156
- "All subscriptions"
2267
+ t2("billing.subscriptions.back")
2157
2268
  ] })
2158
2269
  }
2159
2270
  ),
2160
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SubscriberBilling, { client, subscriber: open })
2271
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SubscriberBilling, { client, subscriber: open, locale })
2161
2272
  ] });
2162
2273
  }
2163
- const match = FILTERS.find((f) => f.label === filter)?.match ?? (() => true);
2274
+ const match = FILTERS.find((f) => f.id === filter)?.match ?? (() => true);
2164
2275
  const rows = list.subscriptions.filter((s) => match(s.status));
2165
- 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 });
2166
2277
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2167
2278
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2168
2279
  PageHeader,
2169
2280
  {
2170
- title: "Subscriptions",
2171
- 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.",
2172
- 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
+ )
2173
2291
  }
2174
2292
  ),
2175
2293
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { ...styles.toolbar, justifyContent: "space-between" }, children: [
@@ -2185,23 +2303,23 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2185
2303
  padding: 0,
2186
2304
  minWidth: 0
2187
2305
  },
2188
- "aria-label": "Filter by status",
2306
+ "aria-label": t2("billing.subscriptions.filterLabel"),
2189
2307
  children: FILTERS.map((f) => {
2190
2308
  const n = list.subscriptions.filter((s) => f.match(s.status)).length;
2191
- const on = filter === f.label;
2309
+ const on = filter === f.id;
2192
2310
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2193
2311
  "button",
2194
2312
  {
2195
2313
  type: "button",
2196
2314
  "aria-pressed": on,
2197
- onClick: () => setFilter(f.label),
2315
+ onClick: () => setFilter(f.id),
2198
2316
  style: { ...on ? styles.buttonPrimary : styles.button, height: 28 },
2199
2317
  children: [
2200
- f.label,
2318
+ t2(`billing.subscriptions.filter.${f.id}`),
2201
2319
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { opacity: 0.7 }, children: n })
2202
2320
  ]
2203
2321
  },
2204
- f.label
2322
+ f.id
2205
2323
  );
2206
2324
  })
2207
2325
  }
@@ -2221,10 +2339,10 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2221
2339
  value: type,
2222
2340
  onChange: (e) => setType(e.target.value),
2223
2341
  style: styles.input,
2224
- "aria-label": "Type",
2342
+ "aria-label": t2("billing.subscriptions.typeLabel"),
2225
2343
  children: [
2226
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "workspace", children: "workspace" }),
2227
- /* @__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") })
2228
2346
  ]
2229
2347
  }
2230
2348
  ),
@@ -2233,36 +2351,29 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2233
2351
  {
2234
2352
  value: idInput,
2235
2353
  onChange: (e) => setIdInput(e.target.value),
2236
- placeholder: "subscriber id",
2354
+ placeholder: t2("billing.subscriptions.idPlaceholder"),
2237
2355
  style: { ...styles.input, width: 220 },
2238
- "aria-label": "Subscriber id"
2356
+ "aria-label": t2("billing.subscriptions.idLabel")
2239
2357
  }
2240
2358
  ),
2241
2359
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "submit", style: styles.button, disabled: !idInput.trim(), children: [
2242
2360
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "search", size: 14 }),
2243
- "Open"
2361
+ t2("common.open")
2244
2362
  ] })
2245
2363
  ]
2246
2364
  }
2247
2365
  )
2248
2366
  ] }),
2249
2367
  list.error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
2250
- rows.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2251
- Empty,
2252
- {
2253
- icon: "subscriber",
2254
- title: filter === "All" ? "No subscriptions yet" : `No ${filter.toLowerCase()} subscriptions`,
2255
- children: "Subscriptions appear here once someone checks out."
2256
- }
2257
- ) : /* @__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: [
2258
2369
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2259
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Subscriber" }),
2260
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Plan" }),
2261
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Status" }),
2262
- /* @__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") })
2263
2374
  ] }) }),
2264
2375
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tbody", { children: rows.map((s) => {
2265
- const end = periodEnd(s);
2376
+ const end = periodEnd(s, locale);
2266
2377
  return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2267
2378
  /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2268
2379
  "button",
@@ -2282,14 +2393,14 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2282
2393
  " ",
2283
2394
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { style: styles.muted, children: [
2284
2395
  "\xB7 ",
2285
- s.interval
2396
+ intervalLabel(t2, s.interval)
2286
2397
  ] })
2287
2398
  ] }),
2288
2399
  /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("td", { style: styles.td, children: [
2289
- /* @__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) }),
2290
2401
  s.cancelAtPeriodEnd && s.status !== "canceled" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2291
2402
  " ",
2292
- /* @__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") })
2293
2404
  ] }) : null
2294
2405
  ] }),
2295
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: [
@@ -2300,28 +2411,28 @@ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2300
2411
  ] }, s.id);
2301
2412
  }) })
2302
2413
  ] }),
2303
- 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,
2304
2415
  list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2305
2416
  "button",
2306
2417
  {
2307
2418
  type: "button",
2308
2419
  style: { ...styles.button, marginTop: 8 },
2309
2420
  onClick: () => void list.loadMore(),
2310
- children: "Load more"
2421
+ children: t2("common.loadMore")
2311
2422
  }
2312
2423
  ) : null
2313
2424
  ] });
2314
2425
  }
2315
2426
 
2316
2427
  // src/screens/AuditScreen.tsx
2428
+ var import_client15 = require("@fonderie/client");
2317
2429
  var import_react_admin14 = require("@fonderie/react-admin");
2318
2430
  var import_react8 = require("react");
2319
2431
  var import_jsx_runtime15 = require("react/jsx-runtime");
2320
- function AuditScreen({ client, pageSize = 50 }) {
2321
- const [draft, setDraft] = (0, import_react8.useState)({ workspaceId: "", type: "", actorId: "" });
2322
- const [filter, setFilter] = (0, import_react8.useState)(
2323
- {}
2324
- );
2432
+ function AuditScreen({ client, pageSize = 50, locale }) {
2433
+ const t2 = (0, import_client15.createAdminT)(locale);
2434
+ const [draft, setDraft] = (0, import_react8.useState)({ workspaceId: "", type: "", actorId: "", from: "", to: "" });
2435
+ const [filter, setFilter] = (0, import_react8.useState)({});
2325
2436
  const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin14.useAdminAudit)(client, {
2326
2437
  ...filter,
2327
2438
  limit: pageSize
@@ -2337,13 +2448,7 @@ function AuditScreen({ client, pageSize = 50 }) {
2337
2448
  }
2338
2449
  );
2339
2450
  return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.container, children: [
2340
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2341
- PageHeader,
2342
- {
2343
- title: "Audit",
2344
- lead: "What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
2345
- }
2346
- ),
2451
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PageHeader, { title: t2("audit.title"), lead: t2("audit.lead") }),
2347
2452
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2348
2453
  "form",
2349
2454
  {
@@ -2353,16 +2458,41 @@ function AuditScreen({ client, pageSize = 50 }) {
2353
2458
  setFilter({
2354
2459
  ...draft.workspaceId.trim() ? { workspaceId: draft.workspaceId.trim() } : {},
2355
2460
  ...draft.type.trim() ? { type: draft.type.trim() } : {},
2356
- ...draft.actorId.trim() ? { actorId: draft.actorId.trim() } : {}
2461
+ ...draft.actorId.trim() ? { actorId: draft.actorId.trim() } : {},
2462
+ // Dates are whole days in the operator's time zone: "to" includes that day.
2463
+ ...draft.from ? { from: /* @__PURE__ */ new Date(`${draft.from}T00:00:00`) } : {},
2464
+ ...draft.to ? { to: /* @__PURE__ */ new Date(`${draft.to}T23:59:59.999`) } : {}
2357
2465
  });
2358
2466
  },
2359
2467
  children: [
2360
- field("workspaceId", "workspace id (all if empty)"),
2361
- field("type", "event type"),
2362
- field("actorId", "actor id"),
2468
+ field("workspaceId", t2("audit.workspacePlaceholder")),
2469
+ field("type", t2("audit.typePlaceholder")),
2470
+ field("actorId", t2("audit.actorPlaceholder")),
2471
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2472
+ "input",
2473
+ {
2474
+ type: "date",
2475
+ value: draft.from,
2476
+ onChange: (e) => setDraft({ ...draft, from: e.target.value }),
2477
+ style: styles.input,
2478
+ "aria-label": t2("audit.fromLabel"),
2479
+ title: t2("audit.fromTitle")
2480
+ }
2481
+ ),
2482
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2483
+ "input",
2484
+ {
2485
+ type: "date",
2486
+ value: draft.to,
2487
+ onChange: (e) => setDraft({ ...draft, to: e.target.value }),
2488
+ style: styles.input,
2489
+ "aria-label": t2("audit.toLabel"),
2490
+ title: t2("audit.toTitle")
2491
+ }
2492
+ ),
2363
2493
  /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
2364
2494
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "search", size: 14 }),
2365
- "Filter"
2495
+ t2("common.filter")
2366
2496
  ] }),
2367
2497
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2368
2498
  "button",
@@ -2371,37 +2501,37 @@ function AuditScreen({ client, pageSize = 50 }) {
2371
2501
  style: styles.button,
2372
2502
  onClick: () => void refresh(),
2373
2503
  disabled: isLoading,
2374
- children: "Refresh"
2504
+ children: t2("common.refresh")
2375
2505
  }
2376
2506
  )
2377
2507
  ]
2378
2508
  }
2379
2509
  ),
2380
2510
  error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2381
- 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: [
2382
2512
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2383
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "When" }),
2384
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Type" }),
2385
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Workspace" }),
2386
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Actor" }),
2387
- /* @__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") })
2388
2518
  ] }) }),
2389
2519
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2390
- /* @__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) }),
2391
2521
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
2392
2522
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
2393
2523
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
2394
2524
  /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
2395
2525
  ] }, e.id)) })
2396
2526
  ] }),
2397
- 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,
2398
2528
  hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2399
2529
  "button",
2400
2530
  {
2401
2531
  type: "button",
2402
2532
  style: { ...styles.button, marginTop: 8 },
2403
2533
  onClick: () => void loadMore(),
2404
- children: "Load more"
2534
+ children: t2("common.loadMore")
2405
2535
  }
2406
2536
  ) : null
2407
2537
  ] });
@@ -2439,34 +2569,43 @@ function useNarrow() {
2439
2569
  return narrow;
2440
2570
  }
2441
2571
  var NAV = [
2442
- { group: "Today", items: [{ page: "attention", label: "Attention" }] },
2572
+ { group: "nav.groups.today", items: [{ page: "attention", label: "nav.items.attention" }] },
2443
2573
  {
2444
- group: "System",
2574
+ group: "nav.groups.system",
2445
2575
  items: [
2446
- { page: "modules", label: "Modules" },
2447
- { page: "environment", label: "Environment" },
2448
- { page: "doctor", label: "Doctor" },
2449
- { 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" }
2450
2580
  ]
2451
2581
  },
2452
- { group: "People", items: [{ page: "users", label: "Users", needs: "auth" }] },
2453
2582
  {
2454
- group: "Money",
2583
+ group: "nav.groups.people",
2584
+ items: [{ page: "users", label: "nav.items.users", needs: "auth" }]
2585
+ },
2586
+ {
2587
+ group: "nav.groups.money",
2455
2588
  items: [
2456
- { page: "catalog", label: "Catalog", needs: "billing" },
2457
- { page: "subscriber", label: "Subscriptions", needs: "billing" }
2589
+ { page: "catalog", label: "nav.items.catalog", needs: "billing" },
2590
+ { page: "subscriber", label: "nav.items.subscriptions", needs: "billing" }
2458
2591
  ]
2459
2592
  },
2460
- { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
2461
- { group: "Messaging", items: [{ page: "templates", label: "Templates", needs: "courier" }] },
2462
2593
  {
2463
- 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",
2464
2603
  items: [
2465
- { page: "audit", label: "Audit", needs: "audit" },
2466
- { page: "log", label: "Admin log" },
2467
- { page: "operators", label: "Operators", needs: "operators" },
2468
- { page: "tokens", label: "Tokens" },
2469
- { 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" }
2470
2609
  ]
2471
2610
  }
2472
2611
  ];
@@ -2484,8 +2623,11 @@ function AdminShell({
2484
2623
  envLabel,
2485
2624
  footer,
2486
2625
  operators = false,
2487
- currentOperator
2626
+ currentOperator,
2627
+ publicConfigUrl,
2628
+ locale
2488
2629
  }) {
2630
+ const t2 = (0, import_client16.createAdminT)(locale);
2489
2631
  const narrow = useNarrow();
2490
2632
  const [drawer, setDrawer] = (0, import_react9.useState)(false);
2491
2633
  const [own, setOwn] = (0, import_react9.useState)("attention");
@@ -2508,40 +2650,41 @@ function AdminShell({
2508
2650
  let body;
2509
2651
  switch (current) {
2510
2652
  case "attention":
2511
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AttentionScreen, { client });
2653
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AttentionScreen, { client, locale });
2512
2654
  break;
2513
2655
  case "modules":
2514
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModulesScreen, { client });
2656
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModulesScreen, { client, locale });
2515
2657
  break;
2516
2658
  case "doctor":
2517
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DoctorScreen, { client });
2659
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DoctorScreen, { client, locale });
2518
2660
  break;
2519
2661
  case "environment":
2520
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EnvironmentScreen, { client });
2662
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EnvironmentScreen, { client, locale });
2521
2663
  break;
2522
2664
  case "routes":
2523
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RoutesScreen, { client });
2665
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RoutesScreen, { client, locale });
2524
2666
  break;
2525
2667
  case "tokens":
2526
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TokensScreen, { client });
2668
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TokensScreen, { client, locale });
2527
2669
  break;
2528
2670
  case "operators":
2529
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OperatorsScreen, { client, me: currentOperator });
2671
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OperatorsScreen, { client, me: currentOperator, locale });
2530
2672
  break;
2531
2673
  case "migrations":
2532
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MigrationsScreen, { client });
2674
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MigrationsScreen, { client, locale });
2533
2675
  break;
2534
2676
  case "log":
2535
- body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AdminLogScreen, { client });
2677
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AdminLogScreen, { client, locale });
2536
2678
  break;
2537
2679
  case "catalog":
2538
- 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" }) });
2539
2681
  break;
2540
2682
  case "subscriber":
2541
2683
  body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2542
2684
  SubscriberScreen,
2543
2685
  {
2544
2686
  client: billingClient,
2687
+ locale,
2545
2688
  ...authClient ? {
2546
2689
  onOpenUser: (id) => {
2547
2690
  setOpenUser(id);
@@ -2549,10 +2692,10 @@ function AdminShell({
2549
2692
  }
2550
2693
  } : {}
2551
2694
  }
2552
- ) : /* @__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" }) });
2553
2696
  break;
2554
2697
  case "audit":
2555
- 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" }) });
2556
2699
  break;
2557
2700
  case "users":
2558
2701
  body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
@@ -2560,16 +2703,17 @@ function AdminShell({
2560
2703
  {
2561
2704
  client: authClient,
2562
2705
  billingClient,
2563
- openUserId: openUser
2706
+ openUserId: openUser,
2707
+ locale
2564
2708
  },
2565
2709
  openUser ?? "list"
2566
- ) : /* @__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" }) });
2567
2711
  break;
2568
2712
  case "settings":
2569
- 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 !== "template" ? /* @__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: [
2570
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: [
2571
2715
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2572
- "Config & secrets"
2716
+ t2("nav.items.settings")
2573
2717
  ] }) }),
2574
2718
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2575
2719
  import_react_config_admin_screens.ConfigEditorScreen,
@@ -2577,41 +2721,85 @@ function AdminShell({
2577
2721
  client: configClient,
2578
2722
  kind: editing.kind,
2579
2723
  configKey: editing.key,
2580
- ...environment !== void 0 ? { environment } : {},
2581
- onSaved: () => setEditing(null)
2582
- }
2724
+ ...editing.environment !== void 0 ? { environment: editing.environment } : environment !== void 0 ? { environment } : {},
2725
+ ...editing.environments ? { environments: editing.environments } : {},
2726
+ onSaved: () => setEditing(null),
2727
+ onDeleted: () => setEditing(null),
2728
+ locale
2729
+ },
2730
+ `${editing.kind}:${editing.key}:${editing.environment ?? ""}`
2583
2731
  )
2584
2732
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2585
2733
  import_react_config_admin_screens.ConfigListScreen,
2586
2734
  {
2587
2735
  client: configClient,
2736
+ locale,
2588
2737
  ...environment !== void 0 ? { environment } : {},
2589
- onSelectConfig: (key) => setEditing({ kind: "config", key }),
2590
- onSelectSecret: (key) => setEditing({ kind: "secret", key }),
2591
- onCreateConfig: () => setEditing({ kind: "config", key: "" }),
2592
- onCreateSecret: () => setEditing({ kind: "secret", key: "" })
2738
+ ...publicConfigUrl ? { publicConfigUrl } : {},
2739
+ onSelectConfig: (key, env) => setEditing({ kind: "config", key, environment: env }),
2740
+ onSelectSecret: (key, env) => setEditing({ kind: "secret", key, environment: env }),
2741
+ onCreateConfig: (c) => setEditing({
2742
+ kind: "config",
2743
+ key: "",
2744
+ environments: c.environments,
2745
+ ...c.environment ? { environment: c.environment } : {}
2746
+ }),
2747
+ onCreateSecret: (c) => setEditing({
2748
+ kind: "secret",
2749
+ key: "",
2750
+ environments: c.environments,
2751
+ ...c.environment ? { environment: c.environment } : {}
2752
+ })
2593
2753
  }
2594
2754
  );
2595
2755
  break;
2596
2756
  case "templates":
2597
- 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" ? /* @__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: [
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: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2760
+ t2("nav.items.templates")
2761
+ ] }) }),
2762
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2763
+ import_react_courier_admin_screens.TemplateCreateScreen,
2764
+ {
2765
+ client: courierClient,
2766
+ locale,
2767
+ ...editing.type ? { type: editing.type } : {},
2768
+ ...editing.locales ? { locales: editing.locales } : {},
2769
+ onCreated: (c) => setEditing({ kind: "template", type: c.type, locale: c.locale, system: false })
2770
+ }
2771
+ )
2772
+ ] }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2598
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: [
2599
2774
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2600
- "Templates"
2775
+ t2("nav.items.templates")
2601
2776
  ] }) }),
2602
2777
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2603
2778
  import_react_courier_admin_screens.TemplateEditorScreen,
2604
2779
  {
2605
2780
  client: courierClient,
2606
2781
  type: editing.type,
2607
- onSaved: () => setEditing(null)
2608
- }
2782
+ locale: editing.locale,
2783
+ system: editing.system,
2784
+ onSaved: () => setEditing(null),
2785
+ onDeleted: () => setEditing(null),
2786
+ onAddLocale: (type) => setEditing({ kind: "template-new", type }),
2787
+ uiLocale: locale
2788
+ },
2789
+ `${editing.type}:${editing.locale ?? ""}`
2609
2790
  )
2610
2791
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2611
2792
  import_react_courier_admin_screens.TemplateListScreen,
2612
2793
  {
2613
2794
  client: courierClient,
2614
- onSelectTemplate: (t2) => setEditing({ kind: "template", type: t2.type })
2795
+ locale,
2796
+ onSelectTemplate: (tpl) => setEditing({
2797
+ kind: "template",
2798
+ type: tpl.type,
2799
+ locale: tpl.locale ?? null,
2800
+ system: tpl.system === true
2801
+ }),
2802
+ onCreateTemplate: (c) => setEditing({ kind: "template-new", locales: c.locales })
2615
2803
  }
2616
2804
  );
2617
2805
  break;
@@ -2643,7 +2831,7 @@ function AdminShell({
2643
2831
  {
2644
2832
  type: "button",
2645
2833
  style: styles.buttonGhost,
2646
- "aria-label": "Close menu",
2834
+ "aria-label": t2("shell.closeMenu"),
2647
2835
  onClick: () => setDrawer(false),
2648
2836
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "close" })
2649
2837
  }
@@ -2656,14 +2844,14 @@ function AdminShell({
2656
2844
  ...styles.nav,
2657
2845
  ...narrow ? { width: 280, maxWidth: "85vw", boxShadow: "0 10px 40px rgba(0,0,0,.25)" } : {}
2658
2846
  },
2659
- "aria-label": "Admin",
2847
+ "aria-label": t2("shell.navLabel"),
2660
2848
  children: [
2661
2849
  brand,
2662
2850
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navScroll, children: NAV.map((g) => {
2663
2851
  const items = g.items.filter((i) => !i.needs || has[i.needs]);
2664
2852
  if (items.length === 0) return null;
2665
2853
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
2666
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navGroup, children: g.group }),
2854
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navGroup, children: t2(g.group) }),
2667
2855
  items.map((i) => {
2668
2856
  const active = current === i.page;
2669
2857
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
@@ -2680,7 +2868,7 @@ function AdminShell({
2680
2868
  },
2681
2869
  children: [
2682
2870
  /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2683
- i.label
2871
+ t2(i.label)
2684
2872
  ]
2685
2873
  },
2686
2874
  i.page
@@ -2692,7 +2880,8 @@ function AdminShell({
2692
2880
  ]
2693
2881
  }
2694
2882
  );
2695
- const title = NAV.flatMap((g) => g.items).find((i) => i.page === current)?.label ?? "";
2883
+ const titleKey = NAV.flatMap((g) => g.items).find((i) => i.page === current)?.label;
2884
+ const title = titleKey ? t2(titleKey) : "";
2696
2885
  return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2697
2886
  narrow ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2698
2887
  /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.topbar, children: [
@@ -2701,7 +2890,7 @@ function AdminShell({
2701
2890
  {
2702
2891
  type: "button",
2703
2892
  style: styles.buttonGhost,
2704
- "aria-label": "Open menu",
2893
+ "aria-label": t2("shell.openMenu"),
2705
2894
  "aria-expanded": drawer,
2706
2895
  onClick: () => setDrawer(true),
2707
2896
  children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "menu" })