@fonderie/react-admin-screens 1.4.0 → 1.6.0

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