@fonderie/react-admin-screens 1.5.0 → 1.6.1

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