efficient-shell 4.127.0 → 4.132.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.
Files changed (128) hide show
  1. package/README.md +938 -5
  2. package/dist/{Browser-F5TKMP4K.js → Browser-XPVJ2K7B.js} +13 -10
  3. package/dist/Browser-XPVJ2K7B.js.map +1 -0
  4. package/dist/{Calculator-T3XG7S73.js → Calculator-ZXJPXG7F.js} +12 -9
  5. package/dist/Calculator-ZXJPXG7F.js.map +1 -0
  6. package/dist/ConfirmDialog-MTC7QMUJ.js +4 -0
  7. package/dist/{ConfirmDialog-LJXMB45A.js.map → ConfirmDialog-MTC7QMUJ.js.map} +1 -1
  8. package/dist/{CurrencyConverter-CASQMKLO.js → CurrencyConverter-WSJ2BBSX.js} +12 -9
  9. package/dist/CurrencyConverter-WSJ2BBSX.js.map +1 -0
  10. package/dist/{Documents-YX67YTZS.js → Documents-ONGSXUJI.js} +14 -11
  11. package/dist/Documents-ONGSXUJI.js.map +1 -0
  12. package/dist/{Files-QB63GO6J.js → Files-GQZMFWXQ.js} +17 -14
  13. package/dist/Files-GQZMFWXQ.js.map +1 -0
  14. package/dist/{Notepad-3SUD5XUE.js → Notepad-HM73XAM4.js} +15 -12
  15. package/dist/Notepad-HM73XAM4.js.map +1 -0
  16. package/dist/PdfViewer-M2YGZIYX.js +5 -0
  17. package/dist/{PdfViewer-LSP3YNFC.js.map → PdfViewer-M2YGZIYX.js.map} +1 -1
  18. package/dist/{PomodoroTimer-4EHD2QC4.js → PomodoroTimer-DG3LI5CX.js} +14 -11
  19. package/dist/PomodoroTimer-DG3LI5CX.js.map +1 -0
  20. package/dist/{Preview-4ZHR4SKP.js → Preview-TUSV4UMO.js} +16 -13
  21. package/dist/Preview-TUSV4UMO.js.map +1 -0
  22. package/dist/{Sidebar-J76PV4QF.js → Sidebar-BPU7RCZP.js} +5 -4
  23. package/dist/Sidebar-BPU7RCZP.js.map +1 -0
  24. package/dist/{Spreadsheet-G3SC7XJ5.js → Spreadsheet-3WTXCHYQ.js} +14 -11
  25. package/dist/Spreadsheet-3WTXCHYQ.js.map +1 -0
  26. package/dist/{Stock-TLT4VN2R.js → Stock-YMPOMUAW.js} +12 -9
  27. package/dist/Stock-YMPOMUAW.js.map +1 -0
  28. package/dist/{Weather-FRKX3DAK.js → Weather-NZO3CWLH.js} +12 -9
  29. package/dist/Weather-NZO3CWLH.js.map +1 -0
  30. package/dist/{WorldClock-GKCITO3M.js → WorldClock-HUC6D3AD.js} +12 -9
  31. package/dist/WorldClock-HUC6D3AD.js.map +1 -0
  32. package/dist/apps/index.js +26 -22
  33. package/dist/apps/index.js.map +1 -1
  34. package/dist/brand.css +1 -1
  35. package/dist/chunk-7IN7YZAL.js +547 -0
  36. package/dist/chunk-7IN7YZAL.js.map +1 -0
  37. package/dist/{chunk-YNGPTOL6.js → chunk-7MA5XBWD.js} +3 -3
  38. package/dist/{chunk-YNGPTOL6.js.map → chunk-7MA5XBWD.js.map} +1 -1
  39. package/dist/{chunk-WK7FV7VV.js → chunk-BJ34QOR3.js} +827 -625
  40. package/dist/chunk-BJ34QOR3.js.map +1 -0
  41. package/dist/{chunk-HNH7FOYB.js → chunk-CGXMWHQW.js} +7 -6
  42. package/dist/chunk-CGXMWHQW.js.map +1 -0
  43. package/dist/{chunk-XUCIYMN7.js → chunk-CJYO4RVV.js} +4 -4
  44. package/dist/{chunk-XUCIYMN7.js.map → chunk-CJYO4RVV.js.map} +1 -1
  45. package/dist/{chunk-N3CG5HG5.js → chunk-CLGNTGC5.js} +12 -11
  46. package/dist/chunk-CLGNTGC5.js.map +1 -0
  47. package/dist/chunk-D276EMXS.js +57 -0
  48. package/dist/chunk-D276EMXS.js.map +1 -0
  49. package/dist/{chunk-7KIOEKHS.js → chunk-E3TWIN7Z.js} +21 -135
  50. package/dist/chunk-E3TWIN7Z.js.map +1 -0
  51. package/dist/{chunk-2VM2FRZJ.js → chunk-EENVXIVU.js} +47 -67
  52. package/dist/chunk-EENVXIVU.js.map +1 -0
  53. package/dist/{chunk-YG45DAUT.js → chunk-FONEQRBH.js} +17 -11
  54. package/dist/chunk-FONEQRBH.js.map +1 -0
  55. package/dist/chunk-IPXN646P.js +14 -0
  56. package/dist/chunk-IPXN646P.js.map +1 -0
  57. package/dist/chunk-IRWSRJ4K.js +48 -0
  58. package/dist/chunk-IRWSRJ4K.js.map +1 -0
  59. package/dist/chunk-JMKYYPOF.js +7 -0
  60. package/dist/{chunk-CJZXR7MG.js.map → chunk-JMKYYPOF.js.map} +1 -1
  61. package/dist/{chunk-JFQGPY7W.js → chunk-KWEX4ZAF.js} +20 -17
  62. package/dist/chunk-KWEX4ZAF.js.map +1 -0
  63. package/dist/{chunk-OKEFO6TI.js → chunk-LQTVTOSY.js} +10 -10
  64. package/dist/chunk-LQTVTOSY.js.map +1 -0
  65. package/dist/{chunk-G4KA3SAX.js → chunk-MQPX6ERJ.js} +4 -4
  66. package/dist/{chunk-G4KA3SAX.js.map → chunk-MQPX6ERJ.js.map} +1 -1
  67. package/dist/{chunk-FQB24B42.js → chunk-SEWF4O3H.js} +3 -3
  68. package/dist/{chunk-FQB24B42.js.map → chunk-SEWF4O3H.js.map} +1 -1
  69. package/dist/{chunk-AXWC3332.js → chunk-TFXJRHJV.js} +3 -3
  70. package/dist/{chunk-AXWC3332.js.map → chunk-TFXJRHJV.js.map} +1 -1
  71. package/dist/{chunk-G3HQLQYZ.js → chunk-TQURTLK2.js} +59 -34
  72. package/dist/chunk-TQURTLK2.js.map +1 -0
  73. package/dist/{chunk-6XQHH7D2.js → chunk-TRCWINHF.js} +5 -5
  74. package/dist/{chunk-6XQHH7D2.js.map → chunk-TRCWINHF.js.map} +1 -1
  75. package/dist/{chunk-NPJCMM6V.js → chunk-VWZIGZFF.js} +4 -4
  76. package/dist/{chunk-NPJCMM6V.js.map → chunk-VWZIGZFF.js.map} +1 -1
  77. package/dist/{chunk-HYZ5QMVO.js → chunk-XH3ELPQZ.js} +22 -19
  78. package/dist/chunk-XH3ELPQZ.js.map +1 -0
  79. package/dist/{chunk-PPUZJFCI.js → chunk-ZKHD4J73.js} +8 -7
  80. package/dist/chunk-ZKHD4J73.js.map +1 -0
  81. package/dist/file-intake/index.js +3 -1
  82. package/dist/file-intake/index.js.map +1 -1
  83. package/dist/{index-dQia6NpB.d.ts → index-COwksV6G.d.ts} +699 -199
  84. package/dist/index.d.ts +2 -2
  85. package/dist/index.js +37 -32
  86. package/dist/index.js.map +1 -1
  87. package/dist/map/index.js +5 -3
  88. package/dist/map/index.js.map +1 -1
  89. package/dist/markdown/index.js +4 -3
  90. package/dist/markdown/index.js.map +1 -1
  91. package/dist/page-tabs/index.js +12 -8
  92. package/dist/page-tabs/index.js.map +1 -1
  93. package/dist/shell.css +17 -17
  94. package/dist/themes.css +15 -0
  95. package/dist/ui/index.d.ts +1 -1
  96. package/dist/ui/index.js +17 -13
  97. package/dist/ui/index.js.map +1 -1
  98. package/dist/ui.css +691 -283
  99. package/package.json +1 -1
  100. package/dist/Browser-F5TKMP4K.js.map +0 -1
  101. package/dist/Calculator-T3XG7S73.js.map +0 -1
  102. package/dist/ConfirmDialog-LJXMB45A.js +0 -3
  103. package/dist/CurrencyConverter-CASQMKLO.js.map +0 -1
  104. package/dist/Documents-YX67YTZS.js.map +0 -1
  105. package/dist/Files-QB63GO6J.js.map +0 -1
  106. package/dist/Notepad-3SUD5XUE.js.map +0 -1
  107. package/dist/PdfViewer-LSP3YNFC.js +0 -4
  108. package/dist/PomodoroTimer-4EHD2QC4.js.map +0 -1
  109. package/dist/Preview-4ZHR4SKP.js.map +0 -1
  110. package/dist/Sidebar-J76PV4QF.js.map +0 -1
  111. package/dist/Spreadsheet-G3SC7XJ5.js.map +0 -1
  112. package/dist/Stock-TLT4VN2R.js.map +0 -1
  113. package/dist/Weather-FRKX3DAK.js.map +0 -1
  114. package/dist/WorldClock-GKCITO3M.js.map +0 -1
  115. package/dist/chunk-2VM2FRZJ.js.map +0 -1
  116. package/dist/chunk-3CUSBZWG.js +0 -59
  117. package/dist/chunk-3CUSBZWG.js.map +0 -1
  118. package/dist/chunk-7KIOEKHS.js.map +0 -1
  119. package/dist/chunk-CJZXR7MG.js +0 -7
  120. package/dist/chunk-G3HQLQYZ.js.map +0 -1
  121. package/dist/chunk-HNH7FOYB.js.map +0 -1
  122. package/dist/chunk-HYZ5QMVO.js.map +0 -1
  123. package/dist/chunk-JFQGPY7W.js.map +0 -1
  124. package/dist/chunk-N3CG5HG5.js.map +0 -1
  125. package/dist/chunk-OKEFO6TI.js.map +0 -1
  126. package/dist/chunk-PPUZJFCI.js.map +0 -1
  127. package/dist/chunk-WK7FV7VV.js.map +0 -1
  128. package/dist/chunk-YG45DAUT.js.map +0 -1
@@ -1,14 +1,16 @@
1
- import { Breadcrumbs } from './chunk-HNH7FOYB.js';
2
- import { SidebarLayout } from './chunk-YG45DAUT.js';
3
- import { BrandMark } from './chunk-6XQHH7D2.js';
4
- import { toast_default } from './chunk-2VM2FRZJ.js';
5
- import { useIsMobile, useShellPrefs } from './chunk-G3HQLQYZ.js';
6
- import { useFileIntake, humanSize, FileIntakeAlert } from './chunk-N3CG5HG5.js';
7
- import { BUTTON_BASE, BUTTON_VARIANTS, Button_default } from './chunk-OKEFO6TI.js';
8
- import { useShellStrings, PopupMenu, PopupMenuItem } from './chunk-7KIOEKHS.js';
9
- import { registerModalEscapeInterceptor, onOverlayOpen, Z_LAYERS, useDismissPopupsOnOpen, useFocusTrap, useScrollLock } from './chunk-HYZ5QMVO.js';
10
- import { glassStyle } from './chunk-3CUSBZWG.js';
11
- import { forwardRef, useCallback, useState, useRef, useId, useEffect, createContext, useLayoutEffect, useMemo, Children, isValidElement, cloneElement, useContext, Fragment as Fragment$1, Component, useSyncExternalStore } from 'react';
1
+ import { Breadcrumbs } from './chunk-CGXMWHQW.js';
2
+ import { SidebarLayout } from './chunk-FONEQRBH.js';
3
+ import { BrandMark } from './chunk-TRCWINHF.js';
4
+ import { toast_default } from './chunk-EENVXIVU.js';
5
+ import { useIsMobile, useShortcutLabel, useShellPrefs } from './chunk-TQURTLK2.js';
6
+ import { useFileIntake, humanSize, FileIntakeAlert } from './chunk-CLGNTGC5.js';
7
+ import { BUTTON_BASE, BUTTON_VARIANTS, Button_default } from './chunk-LQTVTOSY.js';
8
+ import { PopupMenu, PopupMenuItem } from './chunk-E3TWIN7Z.js';
9
+ import { registerModalEscapeInterceptor, onOverlayOpen, Z_LAYERS, useDismissPopupsOnOpen, useFocusTrap, useScrollLock } from './chunk-XH3ELPQZ.js';
10
+ import { glassStyle } from './chunk-D276EMXS.js';
11
+ import { useShellLocale, useShellStrings, DEFAULT_SHELL_STRINGS, currentShellStrings, formatPlain, formatFixed, formatPercent, splitAround } from './chunk-7IN7YZAL.js';
12
+ import { useBrowserLayoutEffect, useBrowserValue, useBrowserValueOnce } from './chunk-IRWSRJ4K.js';
13
+ import { forwardRef, useCallback, useState, useRef, useId, useEffect, createContext, useMemo, Children, isValidElement, cloneElement, useContext, Fragment as Fragment$1, Component, useSyncExternalStore, useLayoutEffect } from 'react';
12
14
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
13
15
  import { createPortal } from 'react-dom';
14
16
 
@@ -37,7 +39,7 @@ var IconButton_default = IconButton;
37
39
 
38
40
  // src/forms/styles.ts
39
41
  var INPUT_SHAPE_PRE = "block w-full rounded-md border border-gray-300 bg-white";
40
- var INPUT_SHAPE_POST = "text-gray-800 placeholder:text-gray-400 shadow-sm focus:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-400/30";
42
+ var INPUT_SHAPE_POST = "text-gray-800 placeholder:text-gray-400 shadow-sm focus:border-blue-400 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring)]";
41
43
  var INPUT_SIZES = {
42
44
  // The desktop rungs carry no explicit height: a text control's height should
43
45
  // follow its own font, or a long label wraps out of a box that was pinned.
@@ -53,7 +55,7 @@ var INPUT_SIZES = {
53
55
  touch: "h-14 px-4 text-base"
54
56
  };
55
57
  var INPUT_BASE = `${INPUT_SHAPE_PRE} ${INPUT_SIZES.md} ${INPUT_SHAPE_POST}`;
56
- var INPUT_INVALID = "border-red-300 focus:border-red-400 focus:ring-red-400/30";
58
+ var INPUT_INVALID = "border-red-300 focus:border-red-400 focus-visible:ring-[color:var(--rosh-focus-ring-invalid)]";
57
59
  var INPUT_DISABLED = "disabled:cursor-not-allowed disabled:opacity-60";
58
60
  function inputClasses(opts) {
59
61
  const shape = opts?.size && opts.size !== "md" ? `${INPUT_SHAPE_PRE} ${INPUT_SIZES[opts.size]} ${INPUT_SHAPE_POST}` : INPUT_BASE;
@@ -135,7 +137,7 @@ function useDropdownPosition(triggerRef, open, options = {}) {
135
137
  preferredMaxHeight = MENU_MAX_HEIGHT
136
138
  } = options;
137
139
  const [pos, setPos] = useState(null);
138
- useLayoutEffect(() => {
140
+ useBrowserLayoutEffect(() => {
139
141
  if (!open) {
140
142
  setPos(null);
141
143
  return;
@@ -494,7 +496,7 @@ var Select = forwardRef(function Select2({ touchSize = "touch", ...props }, ref)
494
496
  return isMobile ? /* @__PURE__ */ jsx(NativeSelect, { ref, ...props, size: touchSize }) : /* @__PURE__ */ jsx(ListboxSelect, { ref, ...props });
495
497
  });
496
498
  var Select_default = Select;
497
- var BOX = "h-4 w-4 shrink-0 rounded border-gray-300 accent-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-400/30 disabled:cursor-not-allowed disabled:opacity-60";
499
+ var BOX = "h-4 w-4 shrink-0 rounded border-gray-300 accent-blue-600 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring)] disabled:cursor-not-allowed disabled:opacity-60";
498
500
  var Checkbox = forwardRef(function Checkbox2({ checked, onChange, indeterminate = false, label, description, disabled, className = "", ...rest }, ref) {
499
501
  const inner = useRef(null);
500
502
  useEffect(() => {
@@ -529,7 +531,7 @@ var Checkbox = forwardRef(function Checkbox2({ checked, onChange, indeterminate
529
531
  ] });
530
532
  });
531
533
  var Checkbox_default = Checkbox;
532
- var DOT = "h-4 w-4 shrink-0 border-gray-300 accent-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-400/30 disabled:cursor-not-allowed disabled:opacity-60";
534
+ var DOT = "h-4 w-4 shrink-0 border-gray-300 accent-blue-600 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring)] disabled:cursor-not-allowed disabled:opacity-60";
533
535
  var Radio = forwardRef(function Radio2({ checked, onChange, label, description, disabled, className = "", ...rest }, ref) {
534
536
  if (!label && !description) {
535
537
  return /* @__PURE__ */ jsx(
@@ -687,7 +689,7 @@ function FormErrorSummary({ errors, title, autoFocus = true, className = "" }) {
687
689
  ref: boxRef,
688
690
  role: "alert",
689
691
  tabIndex: -1,
690
- className: `rounded-md border border-red-300 bg-red-50 px-4 py-3 focus:outline-none focus-visible:ring-2 focus-visible:ring-red-400 ${className}`.trim(),
692
+ className: `rounded-md border border-red-300 bg-red-50 px-4 py-3 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-invalid-solid)] ${className}`.trim(),
691
693
  children: [
692
694
  /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-red-800", children: title ?? strings.form.errorSummaryTitle }),
693
695
  /* @__PURE__ */ jsx("ul", { className: "mt-1.5 space-y-1", children: errors.map((e, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
@@ -738,9 +740,9 @@ function mediaKindWord(accept) {
738
740
  if (acceptsImage && !acceptsVideo) return "image";
739
741
  return "file";
740
742
  }
741
- function mediaPromptLine(accept) {
743
+ function mediaPromptLine(accept, strings = DEFAULT_SHELL_STRINGS.media) {
742
744
  const kind = mediaKindWord(accept);
743
- return `Upload ${kind === "image" ? "an image" : `a ${kind}`}`;
745
+ return kind === "image" ? strings.uploadImage : kind === "video" ? strings.uploadVideo : strings.uploadFile;
744
746
  }
745
747
  function dropzoneClass(dragOver, locked, extra = "") {
746
748
  return [
@@ -750,7 +752,9 @@ function dropzoneClass(dragOver, locked, extra = "") {
750
752
  extra
751
753
  ].filter(Boolean).join(" ");
752
754
  }
753
- var FOCUS_RING = "0 0 0 2px rgba(59,130,246,0.45)";
755
+ function zoneFocusClass(locked) {
756
+ return locked ? "rosh-focus-ring" : "rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-zone)]";
757
+ }
754
758
  function BusyOverlay({ label, rounded = "rounded-lg" }) {
755
759
  return /* @__PURE__ */ jsxs(
756
760
  "div",
@@ -769,7 +773,7 @@ function FilenameBadge({ children }) {
769
773
  "span",
770
774
  {
771
775
  className: "absolute inset-x-1 bottom-1 truncate rounded px-1.5 py-0.5 text-xs text-white",
772
- style: { background: "rgba(0,0,0,0.6)" },
776
+ style: { background: "var(--rosh-media-scrim)" },
773
777
  children
774
778
  }
775
779
  );
@@ -826,18 +830,22 @@ function MediaUploadField({
826
830
  fit = "cover",
827
831
  height = 112,
828
832
  placeholder,
829
- cta = "Choose from library or upload",
833
+ cta: ctaProp,
830
834
  acceptHint,
831
835
  showFilename = true,
832
836
  allowReplace = true,
833
837
  allowRemove = true,
834
- replaceLabel = "Replace",
835
- removeLabel = "Remove",
838
+ replaceLabel: replaceLabelProp,
839
+ removeLabel: removeLabelProp,
836
840
  busy = false,
837
- busyLabel = "Uploading\u2026",
841
+ busyLabel: busyLabelProp,
838
842
  disabled = false
839
843
  }) {
840
- const [focused, setFocused] = useState(false);
844
+ const strings = useShellStrings().media;
845
+ const cta = ctaProp ?? strings.cta;
846
+ const replaceLabel = replaceLabelProp ?? strings.replace;
847
+ const removeLabel = removeLabelProp ?? strings.remove;
848
+ const busyLabel = busyLabelProp ?? strings.uploading;
841
849
  const fieldId = useId();
842
850
  const objectUrlRef = useRef(null);
843
851
  const [videoBlobUrl, setVideoBlobUrl] = useState(null);
@@ -846,7 +854,7 @@ function MediaUploadField({
846
854
  }, []);
847
855
  const locked = disabled || busy;
848
856
  const isVideo = !!value && (isVideoUrl(value, accept) || value === videoBlobUrl);
849
- const dimLine = placeholder ?? mediaPromptLine(accept);
857
+ const dimLine = placeholder ?? mediaPromptLine(accept, strings);
850
858
  const emitObjectUrl = (file) => {
851
859
  if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
852
860
  const url = URL.createObjectURL(file);
@@ -922,20 +930,14 @@ function MediaUploadField({
922
930
  id: fieldId,
923
931
  onClick: () => pick(),
924
932
  disabled: locked,
925
- "aria-label": typeof dimLine === "string" ? dimLine : "Upload media",
933
+ "aria-label": typeof dimLine === "string" ? dimLine : strings.upload,
926
934
  "aria-busy": busy || void 0,
927
935
  "aria-invalid": error ? true : void 0,
928
936
  "aria-required": required || void 0,
929
937
  "aria-describedby": error ? `${fieldId}-error` : hint ? `${fieldId}-hint` : void 0,
930
- onFocus: () => setFocused(true),
931
- onBlur: () => setFocused(false),
932
938
  ...intake.zoneProps,
933
- className: dropzoneClass(dragOver, locked),
934
- style: {
935
- minHeight: height,
936
- outline: "none",
937
- boxShadow: focused && !locked ? FOCUS_RING : void 0
938
- },
939
+ className: dropzoneClass(dragOver, locked, zoneFocusClass(locked)),
940
+ style: { minHeight: height },
939
941
  children: busy ? /* @__PURE__ */ jsxs("span", { role: "status", className: "flex flex-col items-center gap-1.5", children: [
940
942
  /* @__PURE__ */ jsx(Spinner, {}),
941
943
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: busyLabel })
@@ -984,6 +986,7 @@ function BrandAssetEditor({
984
986
  const [saving, setSaving] = useState(false);
985
987
  const [removing, setRemoving] = useState(false);
986
988
  const noun = actionNoun(assetName);
989
+ const strings = useShellStrings().brandAsset;
987
990
  const visibleUrl = stagedUrl || committedUrl || fallbackUrl || "";
988
991
  const visibleHasAlpha = stagedFile ? null : committedUrl ? committedHasAlpha : fallbackHasAlpha;
989
992
  const visibleIsLight = stagedFile ? null : committedUrl ? committedIsLight : fallbackIsLight;
@@ -1014,7 +1017,7 @@ function BrandAssetEditor({
1014
1017
  setStagedFile(null);
1015
1018
  setStagedUrl("");
1016
1019
  } catch (cause) {
1017
- setError(cause instanceof Error ? cause.message : `Could not save ${noun}.`);
1020
+ setError(cause instanceof Error ? cause.message : strings.saveFailed(noun, assetName));
1018
1021
  } finally {
1019
1022
  setSaving(false);
1020
1023
  }
@@ -1034,12 +1037,12 @@ function BrandAssetEditor({
1034
1037
  try {
1035
1038
  await onRemove();
1036
1039
  } catch (cause) {
1037
- setError(cause instanceof Error ? cause.message : `Could not remove ${noun}.`);
1040
+ setError(cause instanceof Error ? cause.message : strings.removeFailed(noun, assetName));
1038
1041
  } finally {
1039
1042
  setRemoving(false);
1040
1043
  }
1041
1044
  };
1042
- return /* @__PURE__ */ jsxs("section", { "aria-label": `${assetName} for ${subjectName}`, className: "grid gap-4 lg:grid-cols-2", children: [
1045
+ return /* @__PURE__ */ jsxs("section", { "aria-label": strings.region(assetName, subjectName), className: "grid gap-4 lg:grid-cols-2", children: [
1043
1046
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-4", children: [
1044
1047
  /* @__PURE__ */ jsx(
1045
1048
  MediaUploadField,
@@ -1049,7 +1052,7 @@ function BrandAssetEditor({
1049
1052
  },
1050
1053
  onFile: stage,
1051
1054
  label: assetName,
1052
- hint: error || `Used for ${subjectName}.`,
1055
+ hint: error || strings.usedFor(subjectName),
1053
1056
  error: error || void 0,
1054
1057
  accept,
1055
1058
  acceptHint,
@@ -1062,21 +1065,18 @@ function BrandAssetEditor({
1062
1065
  }
1063
1066
  ),
1064
1067
  /* @__PURE__ */ jsxs("div", { className: "mt-3 flex flex-wrap gap-2", children: [
1065
- /* @__PURE__ */ jsxs(Button_default, { onClick: save, disabled: !stagedFile || disabled, loading: saving, children: [
1066
- "Save ",
1067
- noun
1068
- ] }),
1069
- (stagedFile || committedUrl) && /* @__PURE__ */ jsx(Button_default, { variant: "ghost-danger", onClick: remove, disabled, loading: removing, children: stagedFile ? "Discard change" : `Remove ${noun}` })
1068
+ /* @__PURE__ */ jsx(Button_default, { onClick: save, disabled: !stagedFile || disabled, loading: saving, children: strings.save(noun, assetName) }),
1069
+ (stagedFile || committedUrl) && /* @__PURE__ */ jsx(Button_default, { variant: "ghost-danger", onClick: remove, disabled, loading: removing, children: stagedFile ? strings.discard : strings.remove(noun, assetName) })
1070
1070
  ] })
1071
1071
  ] }),
1072
- previews.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-4", "aria-label": `${assetName} previews`, children: [
1073
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900", children: "Preview" }),
1072
+ previews.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-gray-200 bg-white p-4", "aria-label": strings.previews(assetName), children: [
1073
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-gray-900", children: strings.preview }),
1074
1074
  /* @__PURE__ */ jsx("div", { className: "mt-3 grid gap-3", children: previews.map((preview) => {
1075
1075
  const mark = /* @__PURE__ */ jsx(
1076
1076
  BrandMark,
1077
1077
  {
1078
1078
  src: visibleUrl,
1079
- alt: `${subjectName} ${assetName}`,
1079
+ alt: strings.alt(subjectName, assetName),
1080
1080
  slot: preview.slot,
1081
1081
  surface: preview.surface,
1082
1082
  adaptive: true,
@@ -1231,28 +1231,27 @@ function MediaUploadGrid({
1231
1231
  fit = "cover",
1232
1232
  thumbSize = 96,
1233
1233
  placeholder,
1234
- cta = "Choose from library or upload",
1234
+ cta: ctaProp,
1235
1235
  acceptHint,
1236
1236
  showCover = false,
1237
- coverLabel = "Cover",
1237
+ coverLabel: coverLabelProp,
1238
1238
  busy = false,
1239
- busyLabel = "Uploading\u2026",
1239
+ busyLabel: busyLabelProp,
1240
1240
  disabled = false,
1241
1241
  readOnly = false
1242
1242
  }) {
1243
+ const strings = useShellStrings().media;
1244
+ const cta = ctaProp ?? strings.cta;
1245
+ const coverLabel = coverLabelProp ?? strings.cover;
1246
+ const busyLabel = busyLabelProp ?? strings.uploading;
1243
1247
  const gridId = useId();
1244
1248
  const [draggingIndex, setDraggingIndex] = useState(null);
1245
- const [focusedKey, setFocusedKey] = useState(null);
1246
- const ringStyle = (key) => ({
1247
- outline: "none",
1248
- ...focusedKey === key && !locked ? { boxShadow: FOCUS_RING } : {}
1249
- });
1250
- const blurKey = (key) => setFocusedKey((k) => k === key ? null : k);
1251
1249
  const locked = disabled || busy;
1252
1250
  const canAdd = (!!onPick || !!onFiles) && !locked && !readOnly;
1253
1251
  const canRemove = !!onRemove && !locked && !readOnly;
1254
1252
  const reorderEnabled = !!onReorder && !locked && !readOnly;
1255
- const dimLine = placeholder ?? mediaPromptLine(accept);
1253
+ const focusRing = zoneFocusClass(locked);
1254
+ const dimLine = placeholder ?? mediaPromptLine(accept, strings);
1256
1255
  const intake = useFileIntake({
1257
1256
  accept,
1258
1257
  maxSizeBytes,
@@ -1300,7 +1299,7 @@ function MediaUploadGrid({
1300
1299
  setDraggingIndex(null);
1301
1300
  } : void 0,
1302
1301
  tabIndex: reorderEnabled ? 0 : void 0,
1303
- "aria-label": reorderEnabled ? `${name}, item ${index + 1} of ${items.length}. Use arrow keys to reorder.` : void 0,
1302
+ "aria-label": reorderEnabled ? strings.reorder(name, index + 1, items.length) : void 0,
1304
1303
  onKeyDown: reorderEnabled ? (e) => {
1305
1304
  if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
1306
1305
  if (index > 0) {
@@ -1314,17 +1313,12 @@ function MediaUploadGrid({
1314
1313
  }
1315
1314
  }
1316
1315
  } : void 0,
1317
- onFocus: reorderEnabled ? (e) => {
1318
- if (e.target === e.currentTarget) setFocusedKey(`thumb:${item.id}`);
1319
- } : void 0,
1320
- onBlur: reorderEnabled ? (e) => {
1321
- if (e.target === e.currentTarget) blurKey(`thumb:${item.id}`);
1322
- } : void 0,
1323
1316
  className: [
1324
1317
  "relative shrink-0 overflow-hidden rounded-md border border-gray-200 bg-gray-100",
1318
+ focusRing,
1325
1319
  draggingIndex === index ? "opacity-50" : ""
1326
1320
  ].join(" "),
1327
- style: { ...square, cursor: reorderEnabled ? "move" : void 0, ...ringStyle(`thumb:${item.id}`) },
1321
+ style: { ...square, cursor: reorderEnabled ? "move" : void 0 },
1328
1322
  children: [
1329
1323
  video ? /* @__PURE__ */ jsx("video", { src: item.url, muted: true, playsInline: true, preload: "metadata", "aria-label": name, className: "h-full w-full", style: { objectFit: fit } }) : /* @__PURE__ */ jsx("img", { src: item.url, alt: name, className: "h-full w-full", style: { objectFit: fit } }),
1330
1324
  isCover && /* @__PURE__ */ jsx("span", { className: "absolute left-1 top-1 rounded bg-blue-600 px-1.5 py-0.5 text-xs font-medium text-white", children: coverLabel }),
@@ -1334,11 +1328,9 @@ function MediaUploadGrid({
1334
1328
  {
1335
1329
  type: "button",
1336
1330
  onClick: () => onRemove(item.id),
1337
- "aria-label": `Remove ${name}`,
1338
- onFocus: () => setFocusedKey(`rm:${item.id}`),
1339
- onBlur: () => blurKey(`rm:${item.id}`),
1340
- className: "absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full text-white",
1341
- style: { background: "rgba(0,0,0,0.55)", ...ringStyle(`rm:${item.id}`) },
1331
+ "aria-label": strings.removeItem(name),
1332
+ className: `absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full text-white ${focusRing}`,
1333
+ style: { background: "var(--rosh-media-scrim-button)" },
1342
1334
  children: REMOVE_ICON
1343
1335
  }
1344
1336
  )
@@ -1352,14 +1344,12 @@ function MediaUploadGrid({
1352
1344
  {
1353
1345
  type: "button",
1354
1346
  onClick: () => pick(),
1355
- "aria-label": "Add media",
1356
- onFocus: () => setFocusedKey("add"),
1357
- onBlur: () => blurKey("add"),
1358
- className: "flex shrink-0 flex-col items-center justify-center gap-1 rounded-md border-2 border-dashed border-gray-300 bg-gray-50 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-500",
1359
- style: { ...square, ...ringStyle("add") },
1347
+ "aria-label": strings.addMedia,
1348
+ className: `flex shrink-0 flex-col items-center justify-center gap-1 rounded-md border-2 border-dashed border-gray-300 bg-gray-50 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-500 ${focusRing}`,
1349
+ style: square,
1360
1350
  children: [
1361
1351
  /* @__PURE__ */ jsx(UploadGlyph, { className: "h-5 w-5" }),
1362
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: "Add" })
1352
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: strings.add })
1363
1353
  ]
1364
1354
  }
1365
1355
  );
@@ -1368,11 +1358,9 @@ function MediaUploadGrid({
1368
1358
  {
1369
1359
  type: "button",
1370
1360
  onClick: () => pick(),
1371
- "aria-label": typeof dimLine === "string" ? dimLine : "Upload media",
1372
- onFocus: () => setFocusedKey("zone"),
1373
- onBlur: () => blurKey("zone"),
1374
- className: dropzoneClass(zoneDragOver, false),
1375
- style: { minHeight: thumbSize + 32, ...ringStyle("zone") },
1361
+ "aria-label": typeof dimLine === "string" ? dimLine : strings.upload,
1362
+ className: dropzoneClass(zoneDragOver, false, focusRing),
1363
+ style: { minHeight: thumbSize + 32 },
1376
1364
  children: [
1377
1365
  /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: /* @__PURE__ */ jsx(UploadGlyph, {}) }),
1378
1366
  /* @__PURE__ */ jsx("span", { className: "px-3 text-xs text-gray-500", children: dimLine }),
@@ -1385,7 +1373,7 @@ function MediaUploadGrid({
1385
1373
  {
1386
1374
  className: "flex w-full items-center justify-center rounded-lg border border-gray-200 bg-gray-50 px-3 text-xs text-gray-400",
1387
1375
  style: { minHeight: thumbSize + 32 },
1388
- children: "No media."
1376
+ children: strings.empty
1389
1377
  }
1390
1378
  );
1391
1379
  return /* @__PURE__ */ jsxs(FormField, { label, labelId: label ? `${gridId}-label` : void 0, htmlFor: gridId, hint, error, required, className, children: [
@@ -1401,9 +1389,9 @@ function MediaUploadGrid({
1401
1389
  className: "relative rounded-lg transition-colors",
1402
1390
  style: {
1403
1391
  ...locked ? { opacity: 0.6 } : {},
1404
- // Inline box-shadow, not `ring-*` — the kit's shipped stylesheet has no
1405
- // Tailwind ring machinery (see MediaUploadField's focus ring).
1406
- ...zoneDragOver ? { boxShadow: "0 0 0 2px rgba(59,130,246,0.5)" } : {}
1392
+ // The drop halo, beside the lock's opacity in one style object. Its
1393
+ // colour is `--rosh-drop-ring`, the value it always was.
1394
+ ...zoneDragOver ? { boxShadow: "0 0 0 2px var(--rosh-drop-ring)" } : {}
1407
1395
  },
1408
1396
  children: [
1409
1397
  items.length === 0 ? emptyDropzone : /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
@@ -1418,7 +1406,10 @@ function MediaUploadGrid({
1418
1406
  /* @__PURE__ */ jsx(FileIntakeAlert, { rejections: intake.rejections })
1419
1407
  ] });
1420
1408
  }
1421
- var MONTH_NAMES = [
1409
+
1410
+ // src/utils/date.ts
1411
+ var MONTH_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1412
+ var MONTH_FULL = [
1422
1413
  "January",
1423
1414
  "February",
1424
1415
  "March",
@@ -1432,8 +1423,118 @@ var MONTH_NAMES = [
1432
1423
  "November",
1433
1424
  "December"
1434
1425
  ];
1435
- var DAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1436
- var DAY_SHORT = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
1426
+ function monthAbbr(mm) {
1427
+ return MONTH_ABBR[parseInt(mm, 10) - 1] ?? mm;
1428
+ }
1429
+ function monthFull(mm) {
1430
+ return MONTH_FULL[parseInt(mm, 10) - 1] ?? mm;
1431
+ }
1432
+ function getUserDateFormat() {
1433
+ if (typeof window === "undefined") return "DD/MM/YYYY";
1434
+ try {
1435
+ return localStorage.getItem("user_date_format") || "DD/MM/YYYY";
1436
+ } catch {
1437
+ return "DD/MM/YYYY";
1438
+ }
1439
+ }
1440
+ var DEFAULT_DATE_STYLE = { dateStyle: "medium" };
1441
+ var dateFormats = /* @__PURE__ */ new Map();
1442
+ function dateFormat(locale, options) {
1443
+ const key = `${locale}|${JSON.stringify(options)}`;
1444
+ let format2 = dateFormats.get(key);
1445
+ if (!format2) {
1446
+ format2 = new Intl.DateTimeFormat(locale, { ...options, timeZone: "UTC", calendar: "gregory" });
1447
+ dateFormats.set(key, format2);
1448
+ }
1449
+ return format2;
1450
+ }
1451
+ function formatDate(value, options) {
1452
+ if (!value) return "\u2014";
1453
+ const dateStr = value.includes("T") ? value.split("T")[0] : value;
1454
+ const [y, m, d] = dateStr.split("-").map(Number);
1455
+ if (!y || !m || !d) return value;
1456
+ if (options?.locale) {
1457
+ return dateFormat(options.locale, options.style ?? DEFAULT_DATE_STYLE).format(new Date(Date.UTC(y, m - 1, d)));
1458
+ }
1459
+ const dd = String(d).padStart(2, "0");
1460
+ const mm = String(m).padStart(2, "0");
1461
+ const yyyy = String(y);
1462
+ const fmt2 = getUserDateFormat();
1463
+ switch (fmt2) {
1464
+ case "MM/DD/YYYY":
1465
+ return `${mm}/${dd}/${yyyy}`;
1466
+ case "YYYY-MM-DD":
1467
+ return `${yyyy}-${mm}-${dd}`;
1468
+ case "DD-MM-YYYY":
1469
+ return `${dd}-${mm}-${yyyy}`;
1470
+ case "MM-DD-YYYY":
1471
+ return `${mm}-${dd}-${yyyy}`;
1472
+ case "DD.MM.YYYY":
1473
+ return `${dd}.${mm}.${yyyy}`;
1474
+ case "YYYY.MM.DD":
1475
+ return `${yyyy}.${mm}.${dd}`;
1476
+ case "YYYY/MM/DD":
1477
+ return `${yyyy}/${mm}/${dd}`;
1478
+ case "DD/MM/YY":
1479
+ return `${dd}/${mm}/${yyyy.slice(-2)}`;
1480
+ case "MM/DD/YY":
1481
+ return `${mm}/${dd}/${yyyy.slice(-2)}`;
1482
+ case "DD/MMM/YYYY":
1483
+ return `${dd}/${monthAbbr(mm)}/${yyyy}`;
1484
+ case "DD/MMM/YY":
1485
+ return `${dd}/${monthAbbr(mm)}/${yyyy.slice(-2)}`;
1486
+ case "DD MMM YYYY":
1487
+ return `${dd} ${monthAbbr(mm)} ${yyyy}`;
1488
+ case "MMM DD, YYYY":
1489
+ return `${monthAbbr(mm)} ${dd}, ${yyyy}`;
1490
+ case "DD MMMM YYYY":
1491
+ return `${dd} ${monthFull(mm)} ${yyyy}`;
1492
+ case "MMMM DD, YYYY":
1493
+ return `${monthFull(mm)} ${dd}, ${yyyy}`;
1494
+ case "DD/MM/YYYY":
1495
+ default:
1496
+ return `${dd}/${mm}/${yyyy}`;
1497
+ }
1498
+ }
1499
+ var WEEKDAY_FULL = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
1500
+ var WEEKDAY_TWO = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
1501
+ var ENGLISH_NAMES = {
1502
+ months: MONTH_FULL,
1503
+ monthsShort: MONTH_ABBR,
1504
+ weekdays: WEEKDAY_FULL,
1505
+ weekdaysShort: WEEKDAY_TWO,
1506
+ monthYear: (year, month) => `${MONTH_FULL[month]} ${year}`,
1507
+ shortMonthYear: (year, month) => `${MONTH_ABBR[month]} ${year}`,
1508
+ longDate: (year, month, day) => `${day} ${MONTH_FULL[month]} ${year}`
1509
+ };
1510
+ var localeNames = /* @__PURE__ */ new Map();
1511
+ function calendarNames(locale) {
1512
+ if (!locale) return ENGLISH_NAMES;
1513
+ const cached = localeNames.get(locale);
1514
+ if (cached) return cached;
1515
+ const fmt2 = (options) => dateFormat(locale, options);
1516
+ const at = (year, month, day = 1) => new Date(Date.UTC(year, month, day));
1517
+ const monthLong = fmt2({ month: "long" });
1518
+ const monthShort = fmt2({ month: "short" });
1519
+ const weekdayLong = fmt2({ weekday: "long" });
1520
+ const weekdayShort = fmt2({ weekday: "short" });
1521
+ const monthYearFmt = fmt2({ month: "long", year: "numeric" });
1522
+ const shortMonthYearFmt = fmt2({ month: "short", year: "numeric" });
1523
+ const longDateFmt = fmt2({ day: "numeric", month: "long", year: "numeric" });
1524
+ const twelve = Array.from({ length: 12 }, (_, m) => m);
1525
+ const seven = Array.from({ length: 7 }, (_, i) => i);
1526
+ const names = {
1527
+ months: twelve.map((m) => monthLong.format(at(2e3, m))),
1528
+ monthsShort: twelve.map((m) => monthShort.format(at(2e3, m))),
1529
+ weekdays: seven.map((i) => weekdayLong.format(at(2e3, 0, 2 + i))),
1530
+ weekdaysShort: seven.map((i) => weekdayShort.format(at(2e3, 0, 2 + i))),
1531
+ monthYear: (year, month) => monthYearFmt.format(at(year, month)),
1532
+ shortMonthYear: (year, month) => shortMonthYearFmt.format(at(year, month)),
1533
+ longDate: (year, month, day) => longDateFmt.format(at(year, month, day))
1534
+ };
1535
+ localeNames.set(locale, names);
1536
+ return names;
1537
+ }
1437
1538
  var pad = (n) => String(n).padStart(2, "0");
1438
1539
  function toKey(d) {
1439
1540
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
@@ -1448,10 +1549,11 @@ function fromKey(key) {
1448
1549
  var monthKey = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}`;
1449
1550
  var YEARS_PER_PAGE = 12;
1450
1551
  var yearPageStart = (y) => Math.floor(y / YEARS_PER_PAGE) * YEARS_PER_PAGE;
1451
- function monthStart(key) {
1552
+ function monthStart(key, today) {
1452
1553
  const m = /^(\d{4})-(\d{2})$/.exec(key);
1453
- return m ? new Date(Number(m[1]), Number(m[2]) - 1, 1) : /* @__PURE__ */ new Date();
1554
+ return m ? new Date(Number(m[1]), Number(m[2]) - 1, 1) : today;
1454
1555
  }
1556
+ var readTodayKey = () => toKey(/* @__PURE__ */ new Date());
1455
1557
  function buildCells(view, weekStartsOn) {
1456
1558
  const first = new Date(view.getFullYear(), view.getMonth(), 1);
1457
1559
  const lead = (first.getDay() - weekStartsOn + 7) % 7;
@@ -1480,32 +1582,45 @@ function Calendar({
1480
1582
  className = ""
1481
1583
  }) {
1482
1584
  const selected = fromKey(value);
1483
- const today = /* @__PURE__ */ new Date();
1484
- const todayKey = toKey(today);
1485
- const [innerMonth, setInnerMonth] = useState(() => {
1486
- if (defaultMonth) return defaultMonth;
1487
- if (selected) return monthKey(selected);
1585
+ const todayKey = useBrowserValue(readTodayKey, null);
1586
+ const today = fromKey(todayKey);
1587
+ const strings = useShellStrings().calendar;
1588
+ const names = calendarNames(useShellLocale());
1589
+ const openingMonth = () => {
1590
+ if (!today) return null;
1488
1591
  const here = monthKey(today);
1489
1592
  if (min && here < min.slice(0, 7)) return min.slice(0, 7);
1490
1593
  if (max && here > max.slice(0, 7)) return max.slice(0, 7);
1491
1594
  return here;
1595
+ };
1596
+ const [innerMonth, setInnerMonth] = useState(() => {
1597
+ if (defaultMonth) return defaultMonth;
1598
+ if (selected) return monthKey(selected);
1599
+ return openingMonth();
1492
1600
  });
1493
- const viewMonth = month ?? innerMonth;
1494
- const view = monthStart(viewMonth);
1601
+ const viewMonth = month ?? innerMonth ?? openingMonth();
1602
+ const view = viewMonth === null ? null : monthStart(viewMonth, today);
1495
1603
  const [active, setActive] = useState(() => value ?? todayKey);
1496
1604
  const gridRef = useRef(null);
1497
1605
  const shouldFocus = useRef(false);
1498
1606
  const [panel, setPanel] = useState("days");
1499
- const cells = useMemo(() => buildCells(view, weekStartsOn), [viewMonth, weekStartsOn]);
1607
+ const cells = useMemo(() => view ? buildCells(view, weekStartsOn) : [], [viewMonth, weekStartsOn]);
1500
1608
  const outOfRange = (key) => !!min && key < min || !!max && key > max;
1501
- const activeInView = cells.some((c) => toKey(c) === active);
1502
- const fallbackActive = toKey(cells.find((c) => c.getMonth() === view.getMonth()) ?? cells[0]);
1503
- const activeKey = activeInView ? active : fallbackActive;
1609
+ const cursor = active ?? todayKey;
1610
+ const activeInView = cells.some((c) => toKey(c) === cursor);
1611
+ const fallbackCell = view ? cells.find((c) => c.getMonth() === view.getMonth()) ?? cells[0] : null;
1612
+ const activeKey = activeInView && cursor ? cursor : fallbackCell ? toKey(fallbackCell) : "";
1504
1613
  useEffect(() => {
1505
1614
  if (!shouldFocus.current) return;
1506
1615
  shouldFocus.current = false;
1507
1616
  gridRef.current?.querySelector('[data-active="true"]')?.focus();
1508
1617
  }, [activeKey, viewMonth]);
1618
+ if (!view) {
1619
+ return /* @__PURE__ */ jsxs("div", { className, "aria-hidden": "true", "data-calendar-pending": "", children: [
1620
+ /* @__PURE__ */ jsx("div", { className: "mb-2 h-6" }),
1621
+ Array.from({ length: 7 }, (_, row) => /* @__PURE__ */ jsx("div", { className: row === 0 ? "py-1 text-xs" : "py-1.5 min-h-11 sm:min-h-0 text-sm", children: "\xA0" }, row))
1622
+ ] });
1623
+ }
1509
1624
  const pageBy = (n) => {
1510
1625
  if (panel === "years") return new Date(view.getFullYear() + n * YEARS_PER_PAGE, view.getMonth(), 1);
1511
1626
  if (panel === "months") return new Date(view.getFullYear() + n, view.getMonth(), 1);
@@ -1524,7 +1639,7 @@ function Calendar({
1524
1639
  if (monthKey(next) !== viewMonth) goToMonth(next);
1525
1640
  };
1526
1641
  const onKeyDown = (e) => {
1527
- const current = fromKey(activeKey) ?? today;
1642
+ const current = fromKey(activeKey) ?? today ?? view;
1528
1643
  const step = {
1529
1644
  ArrowLeft: () => addDays(current, -1),
1530
1645
  ArrowRight: () => addDays(current, 1),
@@ -1551,16 +1666,16 @@ function Calendar({
1551
1666
  if (mode !== "range" || !value || !endValue) return false;
1552
1667
  return key > value && key < endValue;
1553
1668
  };
1554
- const label = ariaLabel ?? `${MONTH_NAMES[view.getMonth()]} ${view.getFullYear()}`;
1669
+ const label = ariaLabel ?? names.monthYear(view.getFullYear(), view.getMonth());
1555
1670
  return /* @__PURE__ */ jsxs("div", { className, children: [
1556
1671
  /* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between px-1", children: [
1557
1672
  /* @__PURE__ */ jsx(
1558
1673
  "button",
1559
1674
  {
1560
1675
  type: "button",
1561
- "aria-label": panel === "years" ? "Previous years" : panel === "months" ? "Previous year" : "Previous month",
1676
+ "aria-label": panel === "years" ? strings.previousYears : panel === "months" ? strings.previousYear : strings.previousMonth,
1562
1677
  onClick: () => goToMonth(pageBack()),
1563
- className: "rounded-full p-1 text-gray-600 hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
1678
+ className: "rounded-full p-1 text-gray-600 hover:bg-gray-100 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
1564
1679
  children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 19l-7-7 7-7" }) })
1565
1680
  }
1566
1681
  ),
@@ -1569,39 +1684,35 @@ function Calendar({
1569
1684
  "button",
1570
1685
  {
1571
1686
  type: "button",
1572
- "aria-label": `Choose a month, currently ${MONTH_NAMES[view.getMonth()]}`,
1687
+ "aria-label": strings.chooseMonth(names.months[view.getMonth()]),
1573
1688
  onClick: () => setPanel((p) => p === "months" ? "days" : "months"),
1574
- className: "rounded-md px-1.5 py-0.5 transition-colors hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
1575
- children: MONTH_NAMES[view.getMonth()]
1689
+ className: "rounded-md px-1.5 py-0.5 transition-colors hover:bg-gray-100 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
1690
+ children: names.months[view.getMonth()]
1576
1691
  }
1577
1692
  ),
1578
1693
  /* @__PURE__ */ jsx(
1579
1694
  "button",
1580
1695
  {
1581
1696
  type: "button",
1582
- "aria-label": `Choose a year, currently ${view.getFullYear()}`,
1697
+ "aria-label": strings.chooseYear(view.getFullYear()),
1583
1698
  onClick: () => setPanel((p) => p === "years" ? "days" : "years"),
1584
- className: "rounded-md px-1.5 py-0.5 transition-colors hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
1699
+ className: "rounded-md px-1.5 py-0.5 transition-colors hover:bg-gray-100 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
1585
1700
  children: view.getFullYear()
1586
1701
  }
1587
1702
  )
1588
- ] }) : /* @__PURE__ */ jsxs("span", { children: [
1589
- MONTH_NAMES[view.getMonth()],
1590
- " ",
1591
- view.getFullYear()
1592
- ] }) }),
1703
+ ] }) : /* @__PURE__ */ jsx("span", { children: names.monthYear(view.getFullYear(), view.getMonth()) }) }),
1593
1704
  /* @__PURE__ */ jsx(
1594
1705
  "button",
1595
1706
  {
1596
1707
  type: "button",
1597
- "aria-label": panel === "years" ? "Next years" : panel === "months" ? "Next year" : "Next month",
1708
+ "aria-label": panel === "years" ? strings.nextYears : panel === "months" ? strings.nextYear : strings.nextMonth,
1598
1709
  onClick: () => goToMonth(pageForward()),
1599
- className: "rounded-full p-1 text-gray-600 hover:bg-gray-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
1710
+ className: "rounded-full p-1 text-gray-600 hover:bg-gray-100 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
1600
1711
  children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9 5l7 7-7 7" }) })
1601
1712
  }
1602
1713
  )
1603
1714
  ] }),
1604
- panel === "months" && /* @__PURE__ */ jsx("div", { role: "group", "aria-label": "Choose a month", className: "grid grid-cols-3 gap-1 text-center text-sm", children: MONTH_NAMES.map((name, m) => /* @__PURE__ */ jsx(
1715
+ panel === "months" && /* @__PURE__ */ jsx("div", { role: "group", "aria-label": strings.monthPanel, className: "grid grid-cols-3 gap-1 text-center text-sm", children: names.monthsShort.map((name, m) => /* @__PURE__ */ jsx(
1605
1716
  "button",
1606
1717
  {
1607
1718
  type: "button",
@@ -1611,14 +1722,14 @@ function Calendar({
1611
1722
  setPanel("days");
1612
1723
  },
1613
1724
  className: [
1614
- "rounded-md py-4 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
1615
- m === view.getMonth() ? "bg-blue-600 font-semibold text-white" : m === today.getMonth() && view.getFullYear() === today.getFullYear() ? "font-medium text-blue-600 hover:bg-gray-100" : "text-gray-700 hover:bg-gray-100"
1725
+ "rounded-md py-4 transition-colors rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
1726
+ m === view.getMonth() ? "bg-blue-600 font-semibold text-white" : today && m === today.getMonth() && view.getFullYear() === today.getFullYear() ? "font-medium text-blue-600 hover:bg-gray-100" : "text-gray-700 hover:bg-gray-100"
1616
1727
  ].join(" "),
1617
- children: name.slice(0, 3)
1728
+ children: name
1618
1729
  },
1619
1730
  name
1620
1731
  )) }),
1621
- panel === "years" && /* @__PURE__ */ jsx("div", { role: "group", "aria-label": "Choose a year", className: "grid grid-cols-3 gap-1 text-center text-sm", children: Array.from({ length: YEARS_PER_PAGE }, (_, i) => yearPageStart(view.getFullYear()) + i).map((y) => /* @__PURE__ */ jsx(
1732
+ panel === "years" && /* @__PURE__ */ jsx("div", { role: "group", "aria-label": strings.yearPanel, className: "grid grid-cols-3 gap-1 text-center text-sm", children: Array.from({ length: YEARS_PER_PAGE }, (_, i) => yearPageStart(view.getFullYear()) + i).map((y) => /* @__PURE__ */ jsx(
1622
1733
  "button",
1623
1734
  {
1624
1735
  type: "button",
@@ -1628,8 +1739,8 @@ function Calendar({
1628
1739
  setPanel("months");
1629
1740
  },
1630
1741
  className: [
1631
- "rounded-md py-4 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
1632
- y === view.getFullYear() ? "bg-blue-600 font-semibold text-white" : y === today.getFullYear() ? "font-medium text-blue-600 hover:bg-gray-100" : "text-gray-700 hover:bg-gray-100"
1742
+ "rounded-md py-4 transition-colors rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
1743
+ y === view.getFullYear() ? "bg-blue-600 font-semibold text-white" : y === today?.getFullYear() ? "font-medium text-blue-600 hover:bg-gray-100" : "text-gray-700 hover:bg-gray-100"
1633
1744
  ].join(" "),
1634
1745
  children: y
1635
1746
  },
@@ -1646,7 +1757,7 @@ function Calendar({
1646
1757
  children: [
1647
1758
  /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7 text-center text-xs font-medium text-gray-500", children: Array.from({ length: 7 }, (_, i) => (i + weekStartsOn) % 7).map((d) => (
1648
1759
  // abbr carries the full name: "Su" is read as a word otherwise.
1649
- /* @__PURE__ */ jsx("div", { role: "columnheader", "aria-label": DAY_NAMES[d], className: "py-1", children: /* @__PURE__ */ jsx("abbr", { title: DAY_NAMES[d], className: "no-underline", children: DAY_SHORT[d] }) }, d)
1760
+ /* @__PURE__ */ jsx("div", { role: "columnheader", "aria-label": names.weekdays[d], className: "py-1", children: /* @__PURE__ */ jsx("abbr", { title: names.weekdays[d], className: "no-underline", children: names.weekdaysShort[d] }) }, d)
1650
1761
  )) }),
1651
1762
  Array.from({ length: 6 }, (_, row) => /* @__PURE__ */ jsx("div", { role: "row", className: "grid grid-cols-7 text-center text-sm", children: cells.slice(row * 7, row * 7 + 7).map((cell) => {
1652
1763
  const key = toKey(cell);
@@ -1661,7 +1772,7 @@ function Calendar({
1661
1772
  "data-active": key === activeKey,
1662
1773
  tabIndex: key === activeKey ? 0 : -1,
1663
1774
  disabled,
1664
- "aria-label": `${cell.getDate()} ${MONTH_NAMES[cell.getMonth()]} ${cell.getFullYear()}`,
1775
+ "aria-label": names.longDate(cell.getFullYear(), cell.getMonth(), cell.getDate()),
1665
1776
  "aria-current": key === todayKey ? "date" : void 0,
1666
1777
  onClick: () => {
1667
1778
  setActive(key);
@@ -1672,7 +1783,7 @@ function Calendar({
1672
1783
  // every touch-target guideline there is, and a date grid
1673
1784
  // is seven of them side by side — the case where missing
1674
1785
  // by four pixels picks the wrong day rather than nothing.
1675
- "w-full rounded-md py-1.5 min-h-11 sm:min-h-0 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
1786
+ "w-full rounded-md py-1.5 min-h-11 sm:min-h-0 transition-colors rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
1676
1787
  disabled ? "cursor-not-allowed text-gray-300" : isSelected ? "bg-blue-600 font-semibold text-white" : between ? "bg-blue-100 text-blue-800" : outside ? "text-gray-300 hover:bg-gray-100" : "text-gray-700 hover:bg-gray-100",
1677
1788
  key === todayKey && !isSelected ? "font-semibold text-blue-600" : ""
1678
1789
  ].filter(Boolean).join(" "),
@@ -1685,7 +1796,7 @@ function Calendar({
1685
1796
  )
1686
1797
  ] });
1687
1798
  }
1688
- var PRESET_LABELS = ["Last 2 Weeks", "Last Month", "Last 3 Months", "Last 6 Months", "Last 12 Months"];
1799
+ var PRESETS = ["last2Weeks", "lastMonth", "last3Months", "last6Months", "last12Months"];
1689
1800
  var monthKeyOf = (d) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}`;
1690
1801
  var pad2 = (n) => String(n).padStart(2, "0");
1691
1802
  function toISODate(d) {
@@ -1699,15 +1810,38 @@ function defaultFormatDisplay(s) {
1699
1810
  const [y, m, d] = s.split("-");
1700
1811
  return y && m && d ? `${d}/${m}/${y}` : s;
1701
1812
  }
1813
+ var numericDates = /* @__PURE__ */ new Map();
1814
+ function localeFormatDisplay(locale) {
1815
+ let format2 = numericDates.get(locale);
1816
+ if (!format2) {
1817
+ format2 = new Intl.DateTimeFormat(locale, {
1818
+ year: "numeric",
1819
+ month: "2-digit",
1820
+ day: "2-digit",
1821
+ timeZone: "UTC",
1822
+ calendar: "gregory"
1823
+ });
1824
+ numericDates.set(locale, format2);
1825
+ }
1826
+ const intl = format2;
1827
+ return (s) => {
1828
+ const [y, m, d] = s.split("-").map(Number);
1829
+ return y && m && d ? intl.format(new Date(Date.UTC(y, m - 1, d))) : s;
1830
+ };
1831
+ }
1702
1832
  function DateRangePicker({
1703
1833
  from,
1704
1834
  to,
1705
1835
  onChange,
1706
- formatDisplay = defaultFormatDisplay,
1836
+ formatDisplay: formatDisplayProp,
1707
1837
  clearable = true,
1708
- placeholder = "Date Range",
1838
+ placeholder: placeholderProp,
1709
1839
  fullWidth = false
1710
1840
  }) {
1841
+ const strings = useShellStrings().dateRange;
1842
+ const locale = useShellLocale();
1843
+ const formatDisplay = formatDisplayProp ?? (locale ? localeFormatDisplay(locale) : defaultFormatDisplay);
1844
+ const placeholder = placeholderProp ?? strings.placeholder;
1711
1845
  const [open, setOpen] = useState(false);
1712
1846
  const [tempFrom, setTempFrom] = useState(from);
1713
1847
  const [tempTo, setTempTo] = useState(to);
@@ -1742,27 +1876,27 @@ function DateRangePicker({
1742
1876
  setViewMonth(monthKeyOf(from ? parseDate(from) : now));
1743
1877
  setOpen(true);
1744
1878
  };
1745
- const handlePreset = (label) => {
1879
+ const handlePreset = (preset) => {
1746
1880
  let start, end;
1747
- switch (label) {
1748
- case "Last 2 Weeks":
1881
+ switch (preset) {
1882
+ case "last2Weeks":
1749
1883
  end = /* @__PURE__ */ new Date();
1750
1884
  start = /* @__PURE__ */ new Date();
1751
1885
  start.setDate(start.getDate() - 14);
1752
1886
  break;
1753
- case "Last Month":
1887
+ case "lastMonth":
1754
1888
  start = new Date(now.getFullYear(), now.getMonth() - 1, 1);
1755
1889
  end = new Date(now.getFullYear(), now.getMonth(), 0);
1756
1890
  break;
1757
- case "Last 3 Months":
1891
+ case "last3Months":
1758
1892
  start = new Date(now.getFullYear(), now.getMonth() - 3, 1);
1759
1893
  end = new Date(now.getFullYear(), now.getMonth(), 0);
1760
1894
  break;
1761
- case "Last 6 Months":
1895
+ case "last6Months":
1762
1896
  start = new Date(now.getFullYear(), now.getMonth() - 6, 1);
1763
1897
  end = new Date(now.getFullYear(), now.getMonth(), 0);
1764
1898
  break;
1765
- case "Last 12 Months":
1899
+ case "last12Months":
1766
1900
  start = new Date(now.getFullYear(), now.getMonth() - 12, 1);
1767
1901
  end = new Date(now.getFullYear(), now.getMonth(), 0);
1768
1902
  break;
@@ -1771,7 +1905,7 @@ function DateRangePicker({
1771
1905
  }
1772
1906
  setTempFrom(toISODate(start));
1773
1907
  setTempTo(toISODate(end));
1774
- setActivePreset(label);
1908
+ setActivePreset(preset);
1775
1909
  setViewMonth(monthKeyOf(start));
1776
1910
  };
1777
1911
  const handleCalendarSelect = (date) => {
@@ -1796,12 +1930,12 @@ function DateRangePicker({
1796
1930
  onChange("", "");
1797
1931
  setOpen(false);
1798
1932
  };
1799
- const displayText = from && to ? `${displayDate(from)} \u2014 ${displayDate(to)}` : from ? `From ${displayDate(from)}` : to ? `To ${displayDate(to)}` : "";
1933
+ const displayText = from && to ? `${displayDate(from)} \u2014 ${displayDate(to)}` : from ? strings.fromOnly(displayDate(from)) : to ? strings.toOnly(displayDate(to)) : "";
1800
1934
  return /* @__PURE__ */ jsxs("div", { className: `relative${fullWidth ? " w-full" : ""}`, ref, children: [
1801
1935
  /* @__PURE__ */ jsxs(
1802
1936
  "div",
1803
1937
  {
1804
- className: `${fullWidth ? "flex w-full" : "inline-flex"} items-center gap-2 border rounded-lg px-2.5 py-1.5 text-sm focus-within:border-blue-500 focus-within:ring-1 focus-within:ring-blue-500 ${from || to ? "border-blue-400 bg-blue-50 text-blue-700" : "border-gray-300 text-gray-500"}`,
1938
+ className: `${fullWidth ? "flex w-full" : "inline-flex"} items-center gap-2 border rounded-lg px-2.5 py-1.5 text-sm focus-within:border-blue-500 has-focus-visible:ring-1 has-focus-visible:ring-[color:var(--rosh-focus-ring-deep)] ${from || to ? "border-blue-400 bg-blue-50 text-blue-700" : "border-gray-300 text-gray-500"}`,
1805
1939
  children: [
1806
1940
  /* @__PURE__ */ jsxs(
1807
1941
  "button",
@@ -1810,7 +1944,7 @@ function DateRangePicker({
1810
1944
  onClick: handleOpen,
1811
1945
  "aria-haspopup": "dialog",
1812
1946
  "aria-expanded": open,
1813
- className: `inline-flex items-center gap-2 focus:outline-none${fullWidth ? " min-w-0 flex-1 justify-start" : ""}`,
1947
+ className: `inline-flex items-center gap-2 rosh-focus-ring${fullWidth ? " min-w-0 flex-1 justify-start" : ""}`,
1814
1948
  children: [
1815
1949
  /* @__PURE__ */ jsx("svg", { className: "w-4 h-4 shrink-0", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" }) }),
1816
1950
  /* @__PURE__ */ jsx("span", { className: "truncate", children: displayText || placeholder })
@@ -1822,8 +1956,8 @@ function DateRangePicker({
1822
1956
  {
1823
1957
  type: "button",
1824
1958
  onClick: handleClear,
1825
- "aria-label": "Clear date range",
1826
- className: "text-blue-400 hover:text-red-500 focus:outline-none focus-visible:ring-1 focus-visible:ring-blue-500 rounded",
1959
+ "aria-label": strings.clearRange,
1960
+ className: "text-blue-400 hover:text-red-500 rosh-focus-ring focus-visible:ring-1 focus-visible:ring-[color:var(--rosh-focus-ring-deep)] rounded",
1827
1961
  children: "\xD7"
1828
1962
  }
1829
1963
  )
@@ -1836,7 +1970,7 @@ function DateRangePicker({
1836
1970
  {
1837
1971
  ref: panelRef,
1838
1972
  role: "dialog",
1839
- "aria-label": "Date range",
1973
+ "aria-label": strings.dialog,
1840
1974
  className: "fixed rounded-2xl p-4",
1841
1975
  style: {
1842
1976
  zIndex: Z_LAYERS.popup,
@@ -1859,13 +1993,13 @@ function DateRangePicker({
1859
1993
  children: [
1860
1994
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
1861
1995
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1862
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: "From" }),
1863
- /* @__PURE__ */ jsx("span", { className: `text-sm px-2 py-1 rounded border min-w-[90px] ${tempFrom ? "border-blue-300 bg-blue-50 text-blue-700" : "border-gray-200 bg-gray-50 text-gray-400"}`, children: tempFrom ? displayDate(tempFrom) : "Start" })
1996
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: strings.from }),
1997
+ /* @__PURE__ */ jsx("span", { className: `text-sm px-2 py-1 rounded border min-w-[90px] ${tempFrom ? "border-blue-300 bg-blue-50 text-blue-700" : "border-gray-200 bg-gray-50 text-gray-400"}`, children: tempFrom ? displayDate(tempFrom) : strings.start })
1864
1998
  ] }),
1865
1999
  /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: "\u2014" }),
1866
2000
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1867
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: "To" }),
1868
- /* @__PURE__ */ jsx("span", { className: `text-sm px-2 py-1 rounded border min-w-[90px] ${tempTo ? "border-blue-300 bg-blue-50 text-blue-700" : "border-gray-200 bg-gray-50 text-gray-400"}`, children: tempTo ? displayDate(tempTo) : "End" })
2001
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: strings.to }),
2002
+ /* @__PURE__ */ jsx("span", { className: `text-sm px-2 py-1 rounded border min-w-[90px] ${tempTo ? "border-blue-300 bg-blue-50 text-blue-700" : "border-gray-200 bg-gray-50 text-gray-400"}`, children: tempTo ? displayDate(tempTo) : strings.end })
1869
2003
  ] })
1870
2004
  ] }),
1871
2005
  /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
@@ -1878,17 +2012,17 @@ function DateRangePicker({
1878
2012
  month: viewMonth,
1879
2013
  onMonthChange: setViewMonth,
1880
2014
  onSelect: handleCalendarSelect,
1881
- "aria-label": "Date range"
2015
+ "aria-label": strings.dialog
1882
2016
  }
1883
2017
  ) }),
1884
2018
  /* @__PURE__ */ jsxs("div", { className: "border-l border-gray-200 pl-4 flex flex-col gap-1 min-w-[130px]", children: [
1885
- PRESET_LABELS.map((label) => /* @__PURE__ */ jsx(
2019
+ PRESETS.map((label) => /* @__PURE__ */ jsx(
1886
2020
  "button",
1887
2021
  {
1888
2022
  type: "button",
1889
2023
  onClick: () => handlePreset(label),
1890
2024
  className: `text-left px-3 py-1.5 text-sm rounded-md transition-colors ${activePreset === label ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-50"}`,
1891
- children: label
2025
+ children: strings[label]
1892
2026
  },
1893
2027
  label
1894
2028
  )),
@@ -1898,14 +2032,14 @@ function DateRangePicker({
1898
2032
  type: "button",
1899
2033
  onClick: () => setActivePreset("Custom"),
1900
2034
  className: `text-left px-3 py-1.5 text-sm rounded-md transition-colors ${activePreset === "Custom" ? "bg-blue-50 text-blue-700 font-medium" : "text-gray-700 hover:bg-gray-50"}`,
1901
- children: "Custom"
2035
+ children: strings.custom
1902
2036
  }
1903
2037
  )
1904
2038
  ] })
1905
2039
  ] }),
1906
2040
  /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 mt-4 pt-3 border-t border-gray-200", children: [
1907
- clearable && /* @__PURE__ */ jsx("button", { type: "button", onClick: handleClear, className: "px-3 py-1.5 text-sm text-gray-500 hover:text-gray-700", children: "Clear" }),
1908
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setOpen(false), className: "px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50", children: "Cancel" }),
2041
+ clearable && /* @__PURE__ */ jsx("button", { type: "button", onClick: handleClear, className: "px-3 py-1.5 text-sm text-gray-500 hover:text-gray-700", children: strings.clear }),
2042
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => setOpen(false), className: "px-3 py-1.5 text-sm text-gray-600 border border-gray-300 rounded-md hover:bg-gray-50", children: strings.cancel }),
1909
2043
  /* @__PURE__ */ jsx(
1910
2044
  "button",
1911
2045
  {
@@ -1913,7 +2047,7 @@ function DateRangePicker({
1913
2047
  onClick: handleApply,
1914
2048
  disabled: !tempFrom || !tempTo,
1915
2049
  className: "px-4 py-1.5 text-sm font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-50",
1916
- children: "Apply"
2050
+ children: strings.apply
1917
2051
  }
1918
2052
  )
1919
2053
  ] })
@@ -1937,11 +2071,13 @@ function toInputValue(value, withSeconds) {
1937
2071
  }
1938
2072
  var TimePicker = forwardRef(function TimePicker2({ value, onChange, min, max, invalid, size, className = "", step, ...rest }, ref) {
1939
2073
  const withSeconds = step != null && Number(step) < 60;
2074
+ const locale = useShellLocale();
1940
2075
  return /* @__PURE__ */ jsx(
1941
2076
  "input",
1942
2077
  {
1943
2078
  ref,
1944
2079
  type: "time",
2080
+ lang: locale,
1945
2081
  step,
1946
2082
  "aria-invalid": invalid || void 0,
1947
2083
  value: toInputValue(value, withSeconds),
@@ -1955,20 +2091,6 @@ var TimePicker = forwardRef(function TimePicker2({ value, onChange, min, max, in
1955
2091
  });
1956
2092
  var TimePicker_default = TimePicker;
1957
2093
  var ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
1958
- var MONTH_NAMES2 = [
1959
- "January",
1960
- "February",
1961
- "March",
1962
- "April",
1963
- "May",
1964
- "June",
1965
- "July",
1966
- "August",
1967
- "September",
1968
- "October",
1969
- "November",
1970
- "December"
1971
- ];
1972
2094
  function toInputValue2(value) {
1973
2095
  if (value === null || value === void 0 || value === "") return "";
1974
2096
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? "" : toISODate(value);
@@ -1982,9 +2104,9 @@ function fromInputValue(raw) {
1982
2104
  const date = new Date(year, month - 1, day);
1983
2105
  return date.getMonth() === month - 1 && date.getDate() === day ? date : null;
1984
2106
  }
1985
- function display(key) {
2107
+ function display(key, names) {
1986
2108
  const d = fromInputValue(key);
1987
- return d ? `${d.getDate()} ${MONTH_NAMES2[d.getMonth()]} ${d.getFullYear()}` : "";
2109
+ return d ? names.longDate(d.getFullYear(), d.getMonth(), d.getDate()) : "";
1988
2110
  }
1989
2111
  var DatePicker = forwardRef(function DatePicker2({
1990
2112
  value,
@@ -2007,6 +2129,9 @@ var DatePicker = forwardRef(function DatePicker2({
2007
2129
  const current = toInputValue2(value);
2008
2130
  const lo = toInputValue2(min);
2009
2131
  const hi = toInputValue2(max);
2132
+ const strings = useShellStrings().datePicker;
2133
+ const locale = useShellLocale();
2134
+ const names = calendarNames(locale);
2010
2135
  const [open, setOpen] = useState(false);
2011
2136
  const wrapRef = useRef(null);
2012
2137
  const triggerRef = useRef(null);
@@ -2042,6 +2167,7 @@ var DatePicker = forwardRef(function DatePicker2({
2042
2167
  {
2043
2168
  ref,
2044
2169
  type: "date",
2170
+ lang: locale,
2045
2171
  id,
2046
2172
  name,
2047
2173
  disabled,
@@ -2071,11 +2197,11 @@ var DatePicker = forwardRef(function DatePicker2({
2071
2197
  "aria-controls": open ? panelId : void 0,
2072
2198
  "aria-describedby": describedBy,
2073
2199
  "aria-invalid": invalid || void 0,
2074
- "aria-label": ariaLabel ? `${ariaLabel}${current ? `, ${display(current)}` : ""}` : void 0,
2200
+ "aria-label": ariaLabel ? `${ariaLabel}${current ? `, ${display(current, names)}` : ""}` : void 0,
2075
2201
  onClick: () => setOpen((o) => !o),
2076
2202
  className: `${inputClasses({ invalid, size, className })} flex items-center justify-between text-left`,
2077
2203
  children: [
2078
- /* @__PURE__ */ jsx("span", { className: current ? "text-gray-900" : "text-gray-400", children: current ? display(current) : placeholder ?? "Select a date" }),
2204
+ /* @__PURE__ */ jsx("span", { className: current ? "text-gray-900" : "text-gray-400", children: current ? display(current, names) : placeholder ?? strings.placeholder }),
2079
2205
  /* @__PURE__ */ jsx("svg", { className: "h-4 w-4 shrink-0 text-gray-400", fill: "none", stroke: "currentColor", strokeWidth: "1.75", viewBox: "0 0 24 24", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8 7V3m8 4V3M4 11h16M5 7h14a1 1 0 011 1v11a1 1 0 01-1 1H5a1 1 0 01-1-1V8a1 1 0 011-1z" }) })
2080
2206
  ]
2081
2207
  }
@@ -2087,7 +2213,7 @@ var DatePicker = forwardRef(function DatePicker2({
2087
2213
  ref: panelRef,
2088
2214
  id: panelId,
2089
2215
  role: "dialog",
2090
- "aria-label": ariaLabel ?? "Choose a date",
2216
+ "aria-label": ariaLabel ?? strings.dialog,
2091
2217
  className: "fixed w-72 rounded-xl border border-gray-200 bg-white p-3 shadow-lg",
2092
2218
  style: {
2093
2219
  zIndex: Z_LAYERS.popup,
@@ -2125,7 +2251,7 @@ var DatePicker = forwardRef(function DatePicker2({
2125
2251
  onChange?.(null);
2126
2252
  },
2127
2253
  className: "rounded-md px-2 py-1 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-700",
2128
- children: "Clear"
2254
+ children: strings.clear
2129
2255
  }
2130
2256
  ),
2131
2257
  /* @__PURE__ */ jsx(
@@ -2138,7 +2264,7 @@ var DatePicker = forwardRef(function DatePicker2({
2138
2264
  onChange?.(/* @__PURE__ */ new Date());
2139
2265
  },
2140
2266
  className: "rounded-md px-2 py-1 text-sm font-medium text-blue-600 hover:bg-blue-50",
2141
- children: "Today"
2267
+ children: strings.today
2142
2268
  }
2143
2269
  )
2144
2270
  ] })
@@ -2171,11 +2297,13 @@ function fromInputValue2(raw) {
2171
2297
  return date.getMonth() === month - 1 && date.getDate() === day ? date : null;
2172
2298
  }
2173
2299
  var DateTimePicker = forwardRef(function DateTimePicker2({ value, onChange, min, max, invalid, size, className = "", ...rest }, ref) {
2300
+ const locale = useShellLocale();
2174
2301
  return /* @__PURE__ */ jsx(
2175
2302
  "input",
2176
2303
  {
2177
2304
  ref,
2178
2305
  type: "datetime-local",
2306
+ lang: locale,
2179
2307
  "aria-invalid": invalid || void 0,
2180
2308
  value: toInputValue3(value),
2181
2309
  min: toInputValue3(min) || void 0,
@@ -2249,8 +2377,9 @@ async function writeClipboard(text, html) {
2249
2377
  }
2250
2378
  async function copyToClipboard(label, text, html) {
2251
2379
  const ok = await writeClipboard(text, html);
2252
- if (ok) toast_default.success(`${label} copied`);
2253
- else toast_default.error("Could not copy to the clipboard");
2380
+ const strings = currentShellStrings().copy;
2381
+ if (ok) toast_default.success(strings.copiedLabel(label));
2382
+ else toast_default.error(strings.failed);
2254
2383
  return ok;
2255
2384
  }
2256
2385
  var COPIED_MS = 2e3;
@@ -2284,13 +2413,14 @@ function Snippet({
2284
2413
  }) {
2285
2414
  const [copied, setCopied] = useState(false);
2286
2415
  const timer = useRef(null);
2416
+ const strings = useShellStrings().copy;
2287
2417
  useEffect(() => () => {
2288
2418
  if (timer.current) clearTimeout(timer.current);
2289
2419
  }, []);
2290
2420
  const copy = useCallback(async () => {
2291
2421
  const ok = await writeClipboard(value);
2292
2422
  if (!ok) {
2293
- toast_default.error("Could not copy to the clipboard");
2423
+ toast_default.error(strings.failed);
2294
2424
  return;
2295
2425
  }
2296
2426
  setCopied(true);
@@ -2300,7 +2430,7 @@ function Snippet({
2300
2430
  timer.current = null;
2301
2431
  }, COPIED_MS);
2302
2432
  onCopy?.(value);
2303
- }, [value, onCopy]);
2433
+ }, [value, onCopy, strings]);
2304
2434
  const { box, button } = SIZES[size];
2305
2435
  const onButton = !hideCopyButton;
2306
2436
  return /* @__PURE__ */ jsxs(
@@ -2324,14 +2454,14 @@ function Snippet({
2324
2454
  IconButton_default,
2325
2455
  {
2326
2456
  id,
2327
- "aria-label": label ? `Copy ${label}` : "Copy",
2457
+ "aria-label": label ? strings.copyLabel(label) : strings.copy,
2328
2458
  "aria-describedby": describedBy,
2329
2459
  onClick: copy,
2330
2460
  className: `${button} text-gray-400`,
2331
2461
  children: copied ? /* @__PURE__ */ jsx(CheckGlyph, {}) : /* @__PURE__ */ jsx(CopyGlyph, {})
2332
2462
  }
2333
2463
  ),
2334
- /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", children: copied ? "Copied" : "" })
2464
+ /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", children: copied ? strings.copied : "" })
2335
2465
  ] })
2336
2466
  ]
2337
2467
  }
@@ -2359,10 +2489,11 @@ function NumericKeypad({
2359
2489
  value,
2360
2490
  onChange,
2361
2491
  onEnter,
2362
- enterLabel = "Enter",
2492
+ enterLabel,
2363
2493
  enterDisabled,
2364
2494
  className = ""
2365
2495
  }) {
2496
+ const strings = useShellStrings().keypad;
2366
2497
  const press = (next) => {
2367
2498
  if (next !== value) onChange(next);
2368
2499
  };
@@ -2382,7 +2513,7 @@ function NumericKeypad({
2382
2513
  {
2383
2514
  size: "touch-xl",
2384
2515
  variant: "secondary",
2385
- "aria-label": "Backspace",
2516
+ "aria-label": strings.backspace,
2386
2517
  onClick: () => press(backspace(value)),
2387
2518
  children: "\u232B"
2388
2519
  }
@@ -2395,7 +2526,7 @@ function NumericKeypad({
2395
2526
  className: "col-span-3",
2396
2527
  disabled: enterDisabled,
2397
2528
  onClick: onEnter,
2398
- children: enterLabel
2529
+ children: enterLabel ?? strings.enter
2399
2530
  }
2400
2531
  )
2401
2532
  ] });
@@ -2413,10 +2544,13 @@ var TileButton = forwardRef(function TileButton2({ title, subtitle, size = "lg",
2413
2544
  disabled,
2414
2545
  "aria-pressed": selected || void 0,
2415
2546
  className: [
2416
- "flex w-full flex-col justify-between rounded-xl border text-left transition-colors",
2417
- "focus:outline-none focus:ring-2 focus:ring-blue-400/40",
2547
+ "flex w-full flex-col justify-between rounded-xl border text-left transition-colors rosh-focus-ring",
2418
2548
  "disabled:cursor-not-allowed disabled:opacity-50",
2419
- selected ? "border-blue-600 bg-blue-50 ring-2 ring-blue-400/40" : "border-gray-200 bg-white hover:bg-gray-50",
2549
+ // The focus ring only on a tile that is NOT chosen. A chosen tile already
2550
+ // wears the same ring, so in the kit's own focus nothing changes; under
2551
+ // `rosh-host-focus` the focus token goes transparent, and a focus ring
2552
+ // declared here as well would paint over the chosen one with nothing.
2553
+ selected ? "border-blue-600 bg-blue-50 ring-2 ring-blue-400/40" : "border-gray-200 bg-white hover:bg-gray-50 focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-strong)]",
2420
2554
  SIZES2[size],
2421
2555
  className
2422
2556
  ].filter(Boolean).join(" "),
@@ -2558,7 +2692,7 @@ var Switch = forwardRef(function Switch2({
2558
2692
  ...rest,
2559
2693
  className: [
2560
2694
  "relative inline-flex shrink-0 items-center rounded-full p-0.5 transition-colors",
2561
- "focus:outline-none focus:ring-2 focus:ring-blue-400/40",
2695
+ "rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-strong)]",
2562
2696
  "disabled:cursor-not-allowed disabled:opacity-40",
2563
2697
  TRACK[size],
2564
2698
  checked ? "bg-blue-600" : "bg-gray-300"
@@ -2706,7 +2840,7 @@ var FilePicker = forwardRef(function FilePicker2({
2706
2840
  className = ""
2707
2841
  }, ref) {
2708
2842
  const zoneId = useId();
2709
- const [focused, setFocused] = useState(false);
2843
+ const strings = useShellStrings().files;
2710
2844
  const intake = useFileIntake({
2711
2845
  accept,
2712
2846
  acceptHint,
@@ -2727,25 +2861,15 @@ var FilePicker = forwardRef(function FilePicker2({
2727
2861
  id: zoneId,
2728
2862
  disabled,
2729
2863
  onClick: intake.open,
2730
- onFocus: () => setFocused(true),
2731
- onBlur: () => setFocused(false),
2732
2864
  "aria-invalid": error ? true : void 0,
2733
2865
  "aria-required": required || void 0,
2734
2866
  "aria-describedby": describedBy,
2735
2867
  ...intake.zoneProps,
2736
- className: dropzoneClass(intake.dragOver, disabled, "px-4 py-6"),
2737
- style: { outline: "none", boxShadow: focused && !disabled ? FOCUS_RING : void 0 },
2868
+ className: dropzoneClass(intake.dragOver, disabled, `px-4 py-6 ${zoneFocusClass(disabled)}`),
2738
2869
  children: [
2739
2870
  /* @__PURE__ */ jsxs("span", { children: [
2740
- /* @__PURE__ */ jsxs("span", { className: "text-sm font-medium text-blue-600", children: [
2741
- "Choose ",
2742
- multiple ? "files" : "a file"
2743
- ] }),
2744
- /* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-500", children: [
2745
- " or drag ",
2746
- multiple ? "them" : "it",
2747
- " here"
2748
- ] })
2871
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-blue-600", children: multiple ? strings.chooseMany : strings.chooseOne }),
2872
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-500", children: ` ${multiple ? strings.dragMany : strings.dragOne}` })
2749
2873
  ] }),
2750
2874
  acceptHint && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: acceptHint })
2751
2875
  ]
@@ -2754,7 +2878,7 @@ var FilePicker = forwardRef(function FilePicker2({
2754
2878
  /* @__PURE__ */ jsx("input", { ...intake.inputProps }),
2755
2879
  files.length > 0 && /* @__PURE__ */ jsx("ul", { className: "mt-2 flex flex-col gap-1", children: files.map((file, i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-3 rounded-md bg-gray-50 px-3 py-2", children: [
2756
2880
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-sm text-gray-800", title: file.name, children: file.name }),
2757
- /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs tabular-nums text-gray-500", children: humanSize(file.size) }),
2881
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs tabular-nums text-gray-500", children: humanSize(file.size, strings) }),
2758
2882
  /* @__PURE__ */ jsx(
2759
2883
  "button",
2760
2884
  {
@@ -2764,7 +2888,7 @@ var FilePicker = forwardRef(function FilePicker2({
2764
2888
  intake.clearRejections();
2765
2889
  onChange(files.filter((_, j) => j !== i));
2766
2890
  },
2767
- "aria-label": `Remove ${file.name}`,
2891
+ "aria-label": strings.remove(file.name),
2768
2892
  className: "shrink-0 text-gray-400 hover:text-gray-600",
2769
2893
  children: /* @__PURE__ */ jsx(CloseGlyph, {})
2770
2894
  }
@@ -2809,7 +2933,8 @@ function FileGlyph({ className = "h-4 w-4" }) {
2809
2933
  }
2810
2934
  );
2811
2935
  }
2812
- function AttachmentDropZone({ intake, dropLabel = "Drop files to attach", className = "", children }) {
2936
+ function AttachmentDropZone({ intake, dropLabel, className = "", children }) {
2937
+ const strings = useShellStrings().files;
2813
2938
  return /* @__PURE__ */ jsxs("div", { ...intake.zoneProps, ...intake.pasteProps, className: `relative ${className}`, children: [
2814
2939
  children,
2815
2940
  /* @__PURE__ */ jsx("input", { ...intake.inputProps }),
@@ -2818,13 +2943,14 @@ function AttachmentDropZone({ intake, dropLabel = "Drop files to attach", classN
2818
2943
  {
2819
2944
  "aria-hidden": "true",
2820
2945
  className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-lg border-2 border-dashed border-blue-500 bg-blue-50/80 text-sm font-medium text-blue-600",
2821
- children: dropLabel
2946
+ children: dropLabel ?? strings.drop
2822
2947
  }
2823
2948
  )
2824
2949
  ] });
2825
2950
  }
2826
2951
  var canPreview = () => typeof URL !== "undefined" && typeof URL.createObjectURL === "function";
2827
2952
  function AttachmentList({ files, onRemove, disabled = false, className = "" }) {
2953
+ const strings = useShellStrings().files;
2828
2954
  const urls = useRef(/* @__PURE__ */ new Map());
2829
2955
  useEffect(() => {
2830
2956
  const live = new Set(files);
@@ -2858,14 +2984,14 @@ function AttachmentList({ files, onRemove, disabled = false, className = "" }) {
2858
2984
  children: [
2859
2985
  thumb ? /* @__PURE__ */ jsx("img", { src: thumb, alt: "", className: "h-6 w-6 shrink-0 rounded object-cover" }) : /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: /* @__PURE__ */ jsx(FileGlyph, {}) }),
2860
2986
  /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", title: file.name, children: file.name }),
2861
- /* @__PURE__ */ jsx("span", { className: "shrink-0 tabular-nums text-gray-400", children: humanSize(file.size) }),
2987
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 tabular-nums text-gray-400", children: humanSize(file.size, strings) }),
2862
2988
  onRemove && /* @__PURE__ */ jsx(
2863
2989
  "button",
2864
2990
  {
2865
2991
  type: "button",
2866
2992
  disabled,
2867
2993
  onClick: () => onRemove(i),
2868
- "aria-label": `Remove ${file.name}`,
2994
+ "aria-label": strings.remove(file.name),
2869
2995
  className: "shrink-0 rounded p-0.5 text-gray-400 hover:bg-gray-200 hover:text-gray-600 disabled:cursor-not-allowed",
2870
2996
  children: /* @__PURE__ */ jsx(CloseGlyph, { className: "h-3.5 w-3.5" })
2871
2997
  }
@@ -2876,7 +3002,9 @@ function AttachmentList({ files, onRemove, disabled = false, className = "" }) {
2876
3002
  );
2877
3003
  }) });
2878
3004
  }
2879
- function AttachButton({ onClick, count = 0, variant = "button", label = "Attach files", disabled = false }) {
3005
+ function AttachButton({ onClick, count = 0, variant = "button", label: labelProp, disabled = false }) {
3006
+ const strings = useShellStrings().files;
3007
+ const label = labelProp ?? strings.attach;
2880
3008
  const counted = count > 0 ? `${label} (${count})` : label;
2881
3009
  if (variant === "icon") {
2882
3010
  return /* @__PURE__ */ jsxs("span", { className: "relative inline-flex", children: [
@@ -3000,7 +3128,7 @@ function SearchableSelect({
3000
3128
  setSearch("");
3001
3129
  };
3002
3130
  const [adornPad, setAdornPad] = useState(null);
3003
- useLayoutEffect(() => {
3131
+ useBrowserLayoutEffect(() => {
3004
3132
  if (!rightAdornment || open) {
3005
3133
  setAdornPad(null);
3006
3134
  return;
@@ -3244,7 +3372,7 @@ function TagInput({
3244
3372
  },
3245
3373
  className: inputClasses({
3246
3374
  invalid,
3247
- className: `flex flex-wrap items-center gap-1 cursor-text focus-within:border-blue-400 focus-within:ring-2 focus-within:ring-blue-400/30 ${disabled ? "bg-gray-50 cursor-not-allowed" : ""} ${className}`
3375
+ className: `flex flex-wrap items-center gap-1 cursor-text focus-within:border-blue-400 has-focus-visible:ring-2 has-focus-visible:ring-[color:var(--rosh-focus-ring)] ${disabled ? "bg-gray-50 cursor-not-allowed" : ""} ${className}`
3248
3376
  }),
3249
3377
  children: [
3250
3378
  value.map((v) => /* @__PURE__ */ jsxs("span", { className: "inline-flex max-w-full items-center gap-1 rounded bg-blue-50 px-1.5 py-0.5 text-xs font-medium text-blue-700", children: [
@@ -3253,9 +3381,9 @@ function TagInput({
3253
3381
  "button",
3254
3382
  {
3255
3383
  type: "button",
3256
- "aria-label": `${strings.select.remove} ${labelFor(v)}`,
3384
+ "aria-label": strings.select.removeItem?.(labelFor(v)) ?? `${strings.select.remove} ${labelFor(v)}`,
3257
3385
  onClick: () => remove(v),
3258
- className: "text-blue-400 hover:text-red-500 focus:outline-none focus-visible:ring-1 focus-visible:ring-blue-500 rounded leading-none",
3386
+ className: "text-blue-400 hover:text-red-500 rosh-focus-ring focus-visible:ring-1 focus-visible:ring-[color:var(--rosh-focus-ring-deep)] rounded leading-none",
3259
3387
  children: "\xD7"
3260
3388
  }
3261
3389
  )
@@ -3298,7 +3426,7 @@ function TagInput({
3298
3426
  setOpen(false);
3299
3427
  }
3300
3428
  },
3301
- className: "min-w-[8ch] flex-1 border-none bg-transparent p-0 text-sm text-gray-800 placeholder:text-gray-400 focus:outline-none focus:ring-0 disabled:cursor-not-allowed"
3429
+ className: "min-w-[8ch] flex-1 border-none bg-transparent p-0 text-sm text-gray-800 placeholder:text-gray-400 rosh-focus-ring focus-visible:ring-0 disabled:cursor-not-allowed"
3302
3430
  }
3303
3431
  )
3304
3432
  ]
@@ -3397,6 +3525,8 @@ function Card({
3397
3525
  );
3398
3526
  }
3399
3527
  function StatCard({ label, value, icon, delta, className = "" }) {
3528
+ const locale = useShellLocale();
3529
+ const shown = typeof value === "number" && locale ? formatPlain(value, locale) : value;
3400
3530
  const deltaColor = delta?.direction === "up" ? "text-green-600" : delta?.direction === "down" ? "text-red-600" : "text-gray-500";
3401
3531
  const arrow = delta?.direction === "up" ? "\u25B2" : delta?.direction === "down" ? "\u25BC" : "\u2192";
3402
3532
  return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border border-gray-200 bg-white p-4 shadow-sm ${className}`.trim(), children: [
@@ -3405,7 +3535,7 @@ function StatCard({ label, value, icon, delta, className = "" }) {
3405
3535
  icon && /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: icon })
3406
3536
  ] }),
3407
3537
  /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-baseline gap-2", children: [
3408
- /* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold text-gray-900", children: value }),
3538
+ /* @__PURE__ */ jsx("span", { className: "text-2xl font-semibold text-gray-900", children: shown }),
3409
3539
  delta && /* @__PURE__ */ jsxs("span", { className: `text-xs font-medium ${deltaColor}`, children: [
3410
3540
  arrow,
3411
3541
  " ",
@@ -3688,7 +3818,8 @@ function CountBadge({
3688
3818
  className = ""
3689
3819
  }) {
3690
3820
  const visible = dot || count > 0 || count === 0 && showZero;
3691
- const label = count > max ? `${max}+` : String(count);
3821
+ const locale = useShellLocale();
3822
+ const label = count > max ? `${formatPlain(max, locale)}+` : formatPlain(count, locale);
3692
3823
  const badge = visible ? dot ? /* @__PURE__ */ jsx(
3693
3824
  "span",
3694
3825
  {
@@ -3729,7 +3860,8 @@ function Statistic({
3729
3860
  size = "md",
3730
3861
  className = ""
3731
3862
  }) {
3732
- const shown = typeof value === "number" && precision != null ? value.toFixed(precision) : value;
3863
+ const locale = useShellLocale();
3864
+ const shown = typeof value !== "number" ? value : precision != null ? formatFixed(value, precision, locale) : locale ? formatPlain(value, locale) : value;
3733
3865
  return /* @__PURE__ */ jsxs("div", { className, children: [
3734
3866
  title && /* @__PURE__ */ jsx("div", { className: "text-xs font-medium uppercase tracking-wide text-gray-500", children: title }),
3735
3867
  /* @__PURE__ */ jsxs("div", { className: `mt-1 flex items-baseline gap-1 ${TONE3[tone]}`, children: [
@@ -3807,6 +3939,7 @@ function AvatarGroup({ children, max, size = "md", className = "" }) {
3807
3939
  const overflow = max ? items.length - shown.length : 0;
3808
3940
  const px = SIZE_PX[size];
3809
3941
  const overlap = Math.round(px * 0.3);
3942
+ const locale = useShellLocale();
3810
3943
  return /* @__PURE__ */ jsxs("div", { className: `flex items-center ${className}`.trim(), children: [
3811
3944
  shown.map((child, i) => /* @__PURE__ */ jsx("span", { className: "rounded-full ring-2 ring-white", style: { marginLeft: i === 0 ? 0 : -overlap }, children: child }, i)),
3812
3945
  overflow > 0 && /* @__PURE__ */ jsxs(
@@ -3816,7 +3949,7 @@ function AvatarGroup({ children, max, size = "md", className = "" }) {
3816
3949
  style: { width: px, height: px, marginLeft: -overlap, fontSize: Math.round(px * 0.36) },
3817
3950
  children: [
3818
3951
  "+",
3819
- overflow
3952
+ formatPlain(overflow, locale)
3820
3953
  ]
3821
3954
  }
3822
3955
  )
@@ -3860,6 +3993,7 @@ function Banner({
3860
3993
  }) {
3861
3994
  const t = TONE4[tone];
3862
3995
  const solid = emphasis === "solid";
3996
+ const strings = useShellStrings().banner;
3863
3997
  const box = solid ? TONE_SOLID[tone] : t.box;
3864
3998
  const stick = sticky ? "sticky top-0 z-30" : "";
3865
3999
  return /* @__PURE__ */ jsxs(
@@ -3879,7 +4013,7 @@ function Banner({
3879
4013
  {
3880
4014
  type: "button",
3881
4015
  onClick: onDismiss,
3882
- "aria-label": "Dismiss",
4016
+ "aria-label": strings.dismiss,
3883
4017
  className: `shrink-0 ${solid ? "opacity-80 hover:opacity-100" : "text-gray-400 hover:text-gray-600"}`,
3884
4018
  children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 6l8 8M14 6l-8 8" }) })
3885
4019
  }
@@ -4026,7 +4160,7 @@ function Stepper({
4026
4160
  {
4027
4161
  type: "button",
4028
4162
  onClick: () => onChange?.(step.id),
4029
- className: "relative z-10 flex min-w-0 flex-col items-center gap-1.5 rounded px-2 text-center focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
4163
+ className: "relative z-10 flex min-w-0 flex-col items-center gap-1.5 rounded px-2 text-center rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
4030
4164
  children: [
4031
4165
  circle,
4032
4166
  text
@@ -4200,7 +4334,8 @@ function ColoredBadge({
4200
4334
  const colour = colorClass ?? GROUP_COLORS[tone ?? "neutral"];
4201
4335
  const sizeCls = size === "xs" ? "px-2 py-0.5 text-[10px]" : size === "md" ? "px-2.5 py-0.5 text-xs" : "px-2 py-0.5 text-xs";
4202
4336
  const capCls = capitalize ? "capitalize" : "";
4203
- const closeName = closeLabel ?? (typeof children === "string" ? `Remove ${children}` : "Remove");
4337
+ const strings = useShellStrings().badge;
4338
+ const closeName = closeLabel ?? (typeof children === "string" ? strings.removeItem(children) : strings.remove);
4204
4339
  const closeSize = size === "xs" ? "h-2.5 w-2.5" : "h-3 w-3";
4205
4340
  return /* @__PURE__ */ jsxs("span", { className: ["inline-flex items-center rounded-full font-medium", sizeCls, capCls, colour, className].filter(Boolean).join(" "), children: [
4206
4341
  children,
@@ -4210,7 +4345,7 @@ function ColoredBadge({
4210
4345
  type: "button",
4211
4346
  onClick: onClose,
4212
4347
  "aria-label": closeName,
4213
- className: "-mr-0.5 ml-1 inline-flex shrink-0 items-center justify-center rounded-full opacity-70 transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-blue-400/40",
4348
+ className: "-mr-0.5 ml-1 inline-flex shrink-0 items-center justify-center rounded-full opacity-70 transition-opacity hover:opacity-100 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-strong)]",
4214
4349
  children: /* @__PURE__ */ jsx("svg", { className: closeSize, viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z" }) })
4215
4350
  }
4216
4351
  )
@@ -4229,7 +4364,8 @@ function EmptyState({
4229
4364
  const v = frameless ? "none" : variant ?? "dashed";
4230
4365
  const custom = typeof icon !== "boolean" && icon != null ? icon : null;
4231
4366
  const showIcon = custom ? false : icon ?? v !== "card";
4232
- const primary = message ?? (title ? void 0 : "Nothing here yet.");
4367
+ const strings = useShellStrings().emptyState;
4368
+ const primary = message ?? (title ? void 0 : strings.message);
4233
4369
  const secondary = description ?? hint;
4234
4370
  const frame = v === "card" ? "rounded-md border border-gray-200 bg-white px-6 py-12" : v === "none" ? "px-6 py-12" : "rounded-lg border-2 border-dashed border-gray-300 px-6 py-12";
4235
4371
  return /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center justify-center text-center ${frame}`, children: [
@@ -4331,9 +4467,13 @@ function resolveSeverity(severity, component) {
4331
4467
  tone: null,
4332
4468
  fill: SEVERITY_UNKNOWN_FILL,
4333
4469
  ink: SEVERITY_UNKNOWN_INK,
4334
- word: `unrecognised severity ${described}`
4470
+ word: `unrecognised severity ${described}`,
4471
+ token: described
4335
4472
  };
4336
4473
  }
4474
+ function severityWord(style, words) {
4475
+ return style.tone ? words[style.tone] : words.unrecognised(style.token ?? "");
4476
+ }
4337
4477
  function warnOnce(key, message) {
4338
4478
  if (reported.has(key)) return;
4339
4479
  reported.add(key);
@@ -4359,11 +4499,14 @@ function MetricBar({
4359
4499
  severity,
4360
4500
  detail,
4361
4501
  formatValue,
4362
- emptyLabel = "no data",
4502
+ emptyLabel: emptyLabelProp,
4363
4503
  size = "sm",
4364
4504
  ariaLabel,
4365
4505
  className = ""
4366
4506
  }) {
4507
+ const strings = useShellStrings().meter;
4508
+ const locale = useShellLocale();
4509
+ const emptyLabel = emptyLabelProp ?? strings.noData;
4367
4510
  const reading2 = value != null && Number.isFinite(value) ? value : null;
4368
4511
  const scale = Number.isFinite(max) && max > 0 ? max : null;
4369
4512
  if (scale == null) {
@@ -4377,7 +4520,7 @@ function MetricBar({
4377
4520
  const warnAt = boundOf(warn);
4378
4521
  const critAt = boundOf(crit);
4379
4522
  const width = reading2 != null && scale != null ? clampPct(reading2 / scale * 100) : 0;
4380
- const format2 = formatValue ?? ((v) => scale === 100 ? `${v.toFixed(1)}%` : String(v));
4523
+ const format2 = formatValue ?? ((v) => scale === 100 ? formatPercent(v, 1, locale) : formatPlain(v, locale));
4381
4524
  const printed = reading2 != null ? format2(reading2) : "\u2014";
4382
4525
  const showTicks = reading2 != null && scale != null && (warnAt != null || critAt != null);
4383
4526
  const meterLabel = ariaLabel ?? (typeof label === "string" ? label : void 0);
@@ -4408,8 +4551,8 @@ function MetricBar({
4408
4551
  style: { width: `${width}%` }
4409
4552
  }
4410
4553
  ),
4411
- showTicks && scale != null && warnAt != null && /* @__PURE__ */ jsx(Tick, { at: clampPct(warnAt / scale * 100), title: `warning \u2265 ${warnAt}`, className: "bg-gray-400 opacity-75" }),
4412
- showTicks && scale != null && critAt != null && /* @__PURE__ */ jsx(Tick, { at: clampPct(critAt / scale * 100), title: `critical \u2265 ${critAt}`, className: "bg-gray-500" })
4554
+ showTicks && scale != null && warnAt != null && /* @__PURE__ */ jsx(Tick, { at: clampPct(warnAt / scale * 100), title: strings.warningAt(warnAt), className: "bg-gray-400 opacity-75" }),
4555
+ showTicks && scale != null && critAt != null && /* @__PURE__ */ jsx(Tick, { at: clampPct(critAt / scale * 100), title: strings.criticalAt(critAt), className: "bg-gray-500" })
4413
4556
  ]
4414
4557
  }
4415
4558
  );
@@ -4443,14 +4586,14 @@ function budgetState(elapsed, budget) {
4443
4586
  if (allowed == null || allowed === 0) return "no-budget";
4444
4587
  return spent > allowed ? "over" : "within";
4445
4588
  }
4446
- function defaultDuration(seconds) {
4589
+ function defaultDuration(seconds, words = DEFAULT_SHELL_STRINGS.meter) {
4447
4590
  const total = Math.round(seconds);
4448
4591
  const h = Math.floor(total / 3600);
4449
4592
  const m = Math.floor(total % 3600 / 60);
4450
4593
  const s = total % 60;
4451
- if (h > 0) return `${h}h ${m}m`;
4452
- if (m > 0) return `${m}m ${s}s`;
4453
- return `${s}s`;
4594
+ if (h > 0) return words.hoursMinutes(h, m);
4595
+ if (m > 0) return words.minutesSeconds(m, s);
4596
+ return words.seconds(s);
4454
4597
  }
4455
4598
  var clampPct2 = (n) => Math.min(100, Math.max(0, n));
4456
4599
  function BudgetBar({
@@ -4459,13 +4602,18 @@ function BudgetBar({
4459
4602
  budget,
4460
4603
  grace,
4461
4604
  detail,
4462
- formatDuration = defaultDuration,
4463
- emptyLabel = "no reading",
4464
- noBudgetLabel = "no budget",
4605
+ formatDuration: formatDurationProp,
4606
+ emptyLabel: emptyLabelProp,
4607
+ noBudgetLabel: noBudgetLabelProp,
4465
4608
  size = "sm",
4466
4609
  ariaLabel,
4467
4610
  className = ""
4468
4611
  }) {
4612
+ const strings = useShellStrings().meter;
4613
+ const locale = useShellLocale();
4614
+ const formatDuration = formatDurationProp ?? ((seconds) => defaultDuration(seconds, strings));
4615
+ const emptyLabel = emptyLabelProp ?? strings.noReading;
4616
+ const noBudgetLabel = noBudgetLabelProp ?? strings.noBudget;
4469
4617
  const spent = reading(elapsed);
4470
4618
  const allowed = reading(budget);
4471
4619
  const budgetAt = allowed != null && allowed > 0 ? allowed : null;
@@ -4480,7 +4628,7 @@ function BudgetBar({
4480
4628
  const hatchEnd = !over ? 0 : scaledOverrun && span != null && spent != null ? clampPct2(spent / span * 100) : 100;
4481
4629
  const hatchWidth = Math.max(0, hatchEnd - hatchStart);
4482
4630
  const percent = spent != null && budgetAt != null ? spent / budgetAt * 100 : null;
4483
- const printed = percent != null ? `${Math.round(percent)}%` : spent != null ? formatDuration(spent) : "\u2014";
4631
+ const printed = percent != null ? formatPercent(Math.round(percent), 0, locale) : spent != null ? formatDuration(spent) : "\u2014";
4484
4632
  const trailing = detail ?? (spent == null ? emptyLabel : budgetAt == null ? noBudgetLabel : `${formatDuration(spent)} / ${formatDuration(budgetAt)}`);
4485
4633
  const meterLabel = ariaLabel ?? (typeof label === "string" ? label : void 0);
4486
4634
  const barHeight = size === "md" ? "h-2" : "h-1.5";
@@ -4494,7 +4642,7 @@ function BudgetBar({
4494
4642
  "aria-valuemin": 0,
4495
4643
  "aria-valuemax": span,
4496
4644
  "aria-valuenow": Math.min(span, spent),
4497
- "aria-valuetext": `${printed} of budget`,
4645
+ "aria-valuetext": strings.ofBudget(printed),
4498
4646
  className: `relative mt-2 ${barHeight} overflow-hidden rounded-full bg-gray-200`,
4499
4647
  children: [
4500
4648
  (!over || scaledOverrun) && /* @__PURE__ */ jsx(
@@ -4514,7 +4662,7 @@ function BudgetBar({
4514
4662
  graceAt > 0 && deadlineAt != null && /* @__PURE__ */ jsx(
4515
4663
  "div",
4516
4664
  {
4517
- title: "budget",
4665
+ title: strings.budget,
4518
4666
  className: "absolute inset-y-0 w-0.5 bg-gray-700",
4519
4667
  style: { left: `${deadlineAt}%` }
4520
4668
  }
@@ -4542,77 +4690,6 @@ function BudgetBar({
4542
4690
  ] });
4543
4691
  }
4544
4692
 
4545
- // src/utils/date.ts
4546
- var MONTH_ABBR = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
4547
- var MONTH_FULL = [
4548
- "January",
4549
- "February",
4550
- "March",
4551
- "April",
4552
- "May",
4553
- "June",
4554
- "July",
4555
- "August",
4556
- "September",
4557
- "October",
4558
- "November",
4559
- "December"
4560
- ];
4561
- function monthAbbr(mm) {
4562
- return MONTH_ABBR[parseInt(mm, 10) - 1] ?? mm;
4563
- }
4564
- function monthFull(mm) {
4565
- return MONTH_FULL[parseInt(mm, 10) - 1] ?? mm;
4566
- }
4567
- function getUserDateFormat() {
4568
- return localStorage.getItem("user_date_format") || "DD/MM/YYYY";
4569
- }
4570
- function formatDate(value) {
4571
- if (!value) return "\u2014";
4572
- const dateStr = value.includes("T") ? value.split("T")[0] : value;
4573
- const [y, m, d] = dateStr.split("-").map(Number);
4574
- if (!y || !m || !d) return value;
4575
- const dd = String(d).padStart(2, "0");
4576
- const mm = String(m).padStart(2, "0");
4577
- const yyyy = String(y);
4578
- const fmt2 = getUserDateFormat();
4579
- switch (fmt2) {
4580
- case "MM/DD/YYYY":
4581
- return `${mm}/${dd}/${yyyy}`;
4582
- case "YYYY-MM-DD":
4583
- return `${yyyy}-${mm}-${dd}`;
4584
- case "DD-MM-YYYY":
4585
- return `${dd}-${mm}-${yyyy}`;
4586
- case "MM-DD-YYYY":
4587
- return `${mm}-${dd}-${yyyy}`;
4588
- case "DD.MM.YYYY":
4589
- return `${dd}.${mm}.${yyyy}`;
4590
- case "YYYY.MM.DD":
4591
- return `${yyyy}.${mm}.${dd}`;
4592
- case "YYYY/MM/DD":
4593
- return `${yyyy}/${mm}/${dd}`;
4594
- case "DD/MM/YY":
4595
- return `${dd}/${mm}/${yyyy.slice(-2)}`;
4596
- case "MM/DD/YY":
4597
- return `${mm}/${dd}/${yyyy.slice(-2)}`;
4598
- case "DD/MMM/YYYY":
4599
- return `${dd}/${monthAbbr(mm)}/${yyyy}`;
4600
- case "DD/MMM/YY":
4601
- return `${dd}/${monthAbbr(mm)}/${yyyy.slice(-2)}`;
4602
- case "DD MMM YYYY":
4603
- return `${dd} ${monthAbbr(mm)} ${yyyy}`;
4604
- case "MMM DD, YYYY":
4605
- return `${monthAbbr(mm)} ${dd}, ${yyyy}`;
4606
- case "DD MMMM YYYY":
4607
- return `${dd} ${monthFull(mm)} ${yyyy}`;
4608
- case "MMMM DD, YYYY":
4609
- return `${monthFull(mm)} ${dd}, ${yyyy}`;
4610
- case "DD/MM/YYYY":
4611
- default:
4612
- return `${dd}/${mm}/${yyyy}`;
4613
- }
4614
- }
4615
-
4616
4693
  // src/shell/timelineDates.ts
4617
4694
  var DAY_MS = 864e5;
4618
4695
  function toDayMs(dateStr) {
@@ -4623,8 +4700,8 @@ function toDayMs(dateStr) {
4623
4700
  if (year < 2e3 || year > 2100) return null;
4624
4701
  return t;
4625
4702
  }
4626
- function fmtSliderDate(ms) {
4627
- return formatDate(new Date(ms).toISOString().slice(0, 10));
4703
+ function fmtSliderDate(ms, locale) {
4704
+ return formatDate(new Date(ms).toISOString().slice(0, 10), { locale });
4628
4705
  }
4629
4706
  var fillId = (id, index) => `${id}-fill-${index}`;
4630
4707
  var glowId = (id) => `${id}-glow`;
@@ -5212,7 +5289,6 @@ function prefersReducedMotion() {
5212
5289
  return false;
5213
5290
  }
5214
5291
  }
5215
- var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
5216
5292
  var monoWidth = (text) => text.length * MONO_CHAR_PX;
5217
5293
  var KIND_STYLES = {
5218
5294
  // No glyph by default: a flag belongs on the milestone that OPENED the
@@ -5264,8 +5340,8 @@ function accessibleName(mark) {
5264
5340
  const detail = detailText(mark.detail);
5265
5341
  return `${mark.label} \xB7 ${mark.dateText}${detail ? ` \xB7 ${detail}` : ""}`;
5266
5342
  }
5267
- function foldName(members) {
5268
- return `${members.length} events \xB7 ${members.map(accessibleName).join(", ")}`;
5343
+ function foldName(members, words) {
5344
+ return words.events(members.length, members.map(accessibleName));
5269
5345
  }
5270
5346
  function foldDates(members) {
5271
5347
  const first = members[0].dateText;
@@ -5381,6 +5457,8 @@ function MeasuringRow({ marks, innerRef }) {
5381
5457
  ] }, mark.key)) });
5382
5458
  }
5383
5459
  function Ruler({ startMs, endMs, axis, geo, reveal }) {
5460
+ const strings = useShellStrings().timeline;
5461
+ const names = calendarNames(useShellLocale());
5384
5462
  const y = geo.ruler;
5385
5463
  const ticks = [];
5386
5464
  const labels = [];
@@ -5404,7 +5482,7 @@ function Ruler({ startMs, endMs, axis, geo, reveal }) {
5404
5482
  }
5405
5483
  const bands = cuts.map((gap) => {
5406
5484
  const centre = (gap.fromPx + gap.toPx) / 2;
5407
- const half = monoWidth(`${Math.round(gap.gapMs / DAY_MS)} days`) / 2 + 6;
5485
+ const half = monoWidth(strings.days(Math.round(gap.gapMs / DAY_MS))) / 2 + 6;
5408
5486
  return [centre - half, centre + half];
5409
5487
  });
5410
5488
  const blocked = (from, to) => bands.some(([low, high]) => from < high && to > low);
@@ -5433,7 +5511,7 @@ function Ruler({ startMs, endMs, axis, geo, reveal }) {
5433
5511
  );
5434
5512
  if (x - lastLabelX >= MONTH_LABEL_PITCH_PX || firstLabel) {
5435
5513
  const withYear = firstLabel || cursor.getUTCMonth() === 0;
5436
- const text = `${MONTHS[cursor.getUTCMonth()]}${withYear ? ` ${cursor.getUTCFullYear()}` : ""}`;
5514
+ const text = withYear ? names.shortMonthYear(cursor.getUTCFullYear(), cursor.getUTCMonth()) : names.monthsShort[cursor.getUTCMonth()];
5437
5515
  if (!blocked(x, x + monoWidth(text))) {
5438
5516
  labels.push(
5439
5517
  /* @__PURE__ */ jsx(
@@ -5473,10 +5551,7 @@ function Ruler({ startMs, endMs, axis, geo, reveal }) {
5473
5551
  /* @__PURE__ */ jsx("i", {}),
5474
5552
  /* @__PURE__ */ jsx("i", {})
5475
5553
  ] }),
5476
- /* @__PURE__ */ jsxs("span", { children: [
5477
- days.toLocaleString(),
5478
- " days"
5479
- ] })
5554
+ /* @__PURE__ */ jsx("span", { children: strings.days(days) })
5480
5555
  ]
5481
5556
  },
5482
5557
  `cut-${gap.fromMs}`
@@ -5485,12 +5560,13 @@ function Ruler({ startMs, endMs, axis, geo, reveal }) {
5485
5560
  ] });
5486
5561
  }
5487
5562
  function PendingColumn({ pending, reveal }) {
5563
+ const strings = useShellStrings().timeline;
5488
5564
  const [expanded, setExpanded] = useState(false);
5489
5565
  const shown = expanded ? pending : pending.slice(0, PENDING_ROW_LIMIT);
5490
5566
  const folded = pending.length - shown.length;
5491
5567
  return /* @__PURE__ */ jsxs("div", { className: "rosh-tl-pending", "data-timeline-part": "pending", children: [
5492
- /* @__PURE__ */ jsx("p", { className: "rosh-tl-pending-head", children: "Not yet reached" }),
5493
- /* @__PURE__ */ jsx("ol", { className: "rosh-tl-ghosts", "aria-label": "Not yet reached", children: shown.map((entry, index) => /* @__PURE__ */ jsxs(
5568
+ /* @__PURE__ */ jsx("p", { className: "rosh-tl-pending-head", children: strings.notReached }),
5569
+ /* @__PURE__ */ jsx("ol", { className: "rosh-tl-ghosts", "aria-label": strings.notReached, children: shown.map((entry, index) => /* @__PURE__ */ jsxs(
5494
5570
  "li",
5495
5571
  {
5496
5572
  className: reveal ? "rosh-tl-fade" : void 0,
@@ -5504,23 +5580,20 @@ function PendingColumn({ pending, reveal }) {
5504
5580
  }
5505
5581
  ),
5506
5582
  /* @__PURE__ */ jsxs("span", { children: [
5507
- index === 0 && /* @__PURE__ */ jsx("b", { children: "Next \xB7 " }),
5583
+ index === 0 && /* @__PURE__ */ jsx("b", { children: `${strings.next} \xB7 ` }),
5508
5584
  entry.label
5509
5585
  ] })
5510
5586
  ]
5511
5587
  },
5512
5588
  entry.key
5513
5589
  )) }),
5514
- folded > 0 && /* @__PURE__ */ jsxs("button", { type: "button", className: "rosh-tl-more text-gray-500", onClick: () => setExpanded(true), children: [
5515
- "+",
5516
- folded,
5517
- " more"
5518
- ] })
5590
+ folded > 0 && /* @__PURE__ */ jsx("button", { type: "button", className: "rosh-tl-more text-gray-500", onClick: () => setExpanded(true), children: strings.more(folded) })
5519
5591
  ] });
5520
5592
  }
5521
5593
  function VerticalTrack({ marks, pending, currentKey, todayMs, reveal, ariaLabel, step, nodeProps, onKeyDown }) {
5594
+ const strings = useShellStrings().timeline;
5522
5595
  const currentIndex = marks.findIndex((mark) => mark.key === currentKey);
5523
- const gapDays = marks.length > 0 ? Math.round((todayMs - marks[marks.length - 1].ms) / DAY_MS) : 0;
5596
+ const gapDays = marks.length > 0 && todayMs !== null ? Math.round((todayMs - marks[marks.length - 1].ms) / DAY_MS) : 0;
5524
5597
  return /* @__PURE__ */ jsxs("div", { "data-timeline-part": "vertical", children: [
5525
5598
  /* @__PURE__ */ jsx("ol", { className: "rosh-tl-v", "aria-label": ariaLabel, onKeyDown, children: marks.map((mark, index) => {
5526
5599
  const dressing = nodeDressing(mark.kind, mark.role);
@@ -5563,10 +5636,7 @@ function VerticalTrack({ marks, pending, currentKey, todayMs, reveal, ariaLabel,
5563
5636
  }) }),
5564
5637
  gapDays > 0 && /* @__PURE__ */ jsxs("p", { className: "rosh-tl-vgap", "aria-hidden": "true", children: [
5565
5638
  /* @__PURE__ */ jsx("i", { className: "rosh-tl-vline is-dash" }),
5566
- /* @__PURE__ */ jsxs("span", { children: [
5567
- gapDays.toLocaleString(),
5568
- " days to today"
5569
- ] })
5639
+ /* @__PURE__ */ jsx("span", { children: strings.daysToToday(gapDays) })
5570
5640
  ] }),
5571
5641
  pending.length > 0 && /* @__PURE__ */ jsx(PendingColumn, { pending, reveal })
5572
5642
  ] });
@@ -5640,6 +5710,7 @@ function StartMark({ geo, caption, endCaption, around, reveal, trackPx }) {
5640
5710
  }
5641
5711
  function TodayMark({ x, geo, trackPx, label, reveal }) {
5642
5712
  const tagWidth = 48;
5713
+ const strings = useShellStrings().timeline;
5643
5714
  return /* @__PURE__ */ jsxs("div", { "aria-hidden": "true", "data-timeline-part": "today", children: [
5644
5715
  /* @__PURE__ */ jsx(
5645
5716
  "div",
@@ -5657,13 +5728,13 @@ function TodayMark({ x, geo, trackPx, label, reveal }) {
5657
5728
  "span",
5658
5729
  {
5659
5730
  className: `rosh-tl-todaytag${reveal ? " rosh-tl-fade" : ""}`,
5660
- title: `Today \u2014 ${label}`,
5731
+ title: strings.todayTitle(label),
5661
5732
  style: {
5662
5733
  left: `${clampLabelLeft(x, tagWidth, trackPx)}px`,
5663
5734
  top: `${geo.tag}px`,
5664
5735
  ...reveal ? { animationDelay: "420ms" } : {}
5665
5736
  },
5666
- children: "Today"
5737
+ children: strings.today
5667
5738
  }
5668
5739
  )
5669
5740
  ] });
@@ -5732,6 +5803,7 @@ function PhaseBracket({ phase, geo, xOf }) {
5732
5803
  ] });
5733
5804
  }
5734
5805
  function ActiveLabel({ nodes, trackPx, geo, activeKey, hoveredKey, currentKey }) {
5806
+ const strings = useShellStrings().timeline;
5735
5807
  const holds = (node2, key) => key !== null && (node2.key === key || node2.members.some((member) => member.key === key));
5736
5808
  const node = nodes.find((n) => n.key === hoveredKey) ?? nodes.find((n) => holds(n, activeKey)) ?? null;
5737
5809
  if (!node) return null;
@@ -5758,7 +5830,7 @@ function ActiveLabel({ nodes, trackPx, geo, activeKey, hoveredKey, currentKey })
5758
5830
  type: "button",
5759
5831
  className: "rosh-tl-name rosh-tl-open",
5760
5832
  "data-timeline-part": "open",
5761
- "aria-label": `Open ${name}`,
5833
+ "aria-label": strings.open(name),
5762
5834
  onClick: onOpen,
5763
5835
  children: name
5764
5836
  }
@@ -6022,6 +6094,8 @@ function ThumbLayer({
6022
6094
  nodes,
6023
6095
  onProgress
6024
6096
  }) {
6097
+ const strings = useShellStrings().timeline;
6098
+ const locale = useShellLocale();
6025
6099
  const draggingRef = useRef(false);
6026
6100
  const x = glide ? glide.px : axis.xByMs(value);
6027
6101
  const at = glide ? glide.ms : value;
@@ -6073,7 +6147,7 @@ function ThumbLayer({
6073
6147
  if (key === "End") return endMs;
6074
6148
  return null;
6075
6149
  };
6076
- const valueText = thumb.valueText ? thumb.valueText(at) : fmtSliderDate(at);
6150
+ const valueText = thumb.valueText ? thumb.valueText(at) : fmtSliderDate(at, locale);
6077
6151
  const days = Math.max(1, Math.round((endMs - startMs) / DAY_MS));
6078
6152
  return /* @__PURE__ */ jsxs(Fragment, { children: [
6079
6153
  /* @__PURE__ */ jsx(
@@ -6098,7 +6172,7 @@ function ThumbLayer({
6098
6172
  className: "rosh-tl-chip",
6099
6173
  "data-timeline-part": "chip",
6100
6174
  style: { left: `${clampLabelLeft(x, chipWidth, trackPx) + chipWidth / 2}px`, top: `${geo.tag}px` },
6101
- children: thumb.chip ? thumb.chip(at) : fmtSliderDate(at)
6175
+ children: thumb.chip ? thumb.chip(at) : fmtSliderDate(at, locale)
6102
6176
  }
6103
6177
  ),
6104
6178
  /* @__PURE__ */ jsx(
@@ -6108,7 +6182,7 @@ function ThumbLayer({
6108
6182
  tabIndex: 0,
6109
6183
  className: "rosh-tl-thumb text-blue-600",
6110
6184
  "data-timeline-part": "thumb",
6111
- "aria-label": thumb.ariaLabel ?? "Scrub the timeline",
6185
+ "aria-label": thumb.ariaLabel ?? strings.scrub,
6112
6186
  "aria-valuemin": 0,
6113
6187
  "aria-valuemax": stops && stops.length > 0 ? stops.length - 1 : days,
6114
6188
  "aria-valuenow": stops && stops.length > 0 ? Math.max(0, index) : Math.round((value - startMs) / DAY_MS),
@@ -6163,10 +6237,13 @@ function TimelineTrack({
6163
6237
  endMark = null
6164
6238
  }) {
6165
6239
  const bare = labels === "none";
6166
- const [mountedToday] = useState(() => Date.now());
6167
- const [stillness] = useState(prefersReducedMotion);
6240
+ const mountedToday = useBrowserValueOnce(Date.now, null);
6241
+ const stillness = useBrowserValueOnce(prefersReducedMotion, false);
6168
6242
  const now = todayMs ?? mountedToday;
6169
6243
  const rootRef = useRef(null);
6244
+ const strings = useShellStrings().timeline;
6245
+ const locale = useShellLocale();
6246
+ const fmtDate = (ms) => fmtSliderDate(ms, locale);
6170
6247
  const layerRef = useRef(null);
6171
6248
  const measureRef = useRef(null);
6172
6249
  const tipRef = useRef(null);
@@ -6207,7 +6284,7 @@ function TimelineTrack({
6207
6284
  const happened = dated.filter((item) => !item.provisional);
6208
6285
  const resolvedCurrent = currentKey ?? (happened.length > 0 ? happened[happened.length - 1].key : null);
6209
6286
  const itemMarks = dated.map((item, index) => {
6210
- const dateText = item.date ?? fmtSliderDate(item.ms);
6287
+ const dateText = item.date ?? fmtDate(item.ms);
6211
6288
  const ordinary = (item.priority ?? 0) > 0 || item.kind === "report";
6212
6289
  const kind = resolveKind(item);
6213
6290
  return {
@@ -6236,7 +6313,7 @@ function TimelineTrack({
6236
6313
  });
6237
6314
  const markerMarks = markers.map((marker) => ({ marker, ms: new Date(marker.date).getTime() })).filter(({ ms }) => Number.isFinite(ms) && inWindow(ms)).map(({ marker, ms }) => {
6238
6315
  const key = `marker-${marker.id}`;
6239
- const dateText = fmtSliderDate(ms);
6316
+ const dateText = fmtDate(ms);
6240
6317
  return {
6241
6318
  key,
6242
6319
  ms,
@@ -6271,8 +6348,8 @@ function TimelineTrack({
6271
6348
  baseX: trackPx,
6272
6349
  kind: cap.kind ?? "default",
6273
6350
  label: cap.label,
6274
- dateText: "Not yet reached",
6275
- detail: others.length > 0 ? `Also to come: ${others.join(", ")}` : void 0,
6351
+ dateText: strings.notReached,
6352
+ detail: others.length > 0 ? strings.alsoToCome(others) : void 0,
6276
6353
  glyph: void 0,
6277
6354
  role: "key",
6278
6355
  widthPx: MARK_OVERLAP_PX,
@@ -6289,7 +6366,7 @@ function TimelineTrack({
6289
6366
  baseX: axisPx,
6290
6367
  kind: "default",
6291
6368
  label: endMark.label,
6292
- dateText: endMark.dateText ?? fmtSliderDate(endMs),
6369
+ dateText: endMark.dateText ?? fmtDate(endMs),
6293
6370
  detail: endMark.detail,
6294
6371
  glyph: void 0,
6295
6372
  role: "key",
@@ -6300,7 +6377,7 @@ function TimelineTrack({
6300
6377
  isCap: true
6301
6378
  }] : [];
6302
6379
  const marks = [...itemMarks, ...markerMarks, ...capMarks, ...endMarks].sort((a, b) => a.x - b.x || a.ms - b.ms);
6303
- const startCaption = edgeCaptions?.start ?? `Start \xB7 ${fmtSliderDate(startMs)}`;
6380
+ const startCaption = edgeCaptions?.start ?? strings.start(fmtDate(startMs));
6304
6381
  const startCaptionPx = startCaption == null || startCaption === false ? 0 : typeof startCaption === "string" ? Math.ceil(monoWidth(startCaption)) : START_CAPTION_FALLBACK_PX;
6305
6382
  const groups = labels === "lanes" ? clusterMarks(marks) : [];
6306
6383
  const groupKey = (group) => group.type === "cluster" ? `cluster-${group.members[0].key}` : group.mark.key;
@@ -6483,7 +6560,7 @@ function TimelineTrack({
6483
6560
  };
6484
6561
  }, [playing, endPlayback, reportProgress]);
6485
6562
  const reached = itemMarks.filter((mark) => !mark.provisional);
6486
- const fillTo = thumb ? glide ? glide.ms : thumbAt : fillToMs !== void 0 ? fillToMs : reached.length > 0 ? Math.min(reached[reached.length - 1].ms, now) : null;
6563
+ const fillTo = thumb ? glide ? glide.ms : thumbAt : fillToMs !== void 0 ? fillToMs : reached.length > 0 ? now === null ? reached[reached.length - 1].ms : Math.min(reached[reached.length - 1].ms, now) : null;
6487
6564
  const litKeys = new Set(highlightKeys ?? []);
6488
6565
  const litXs = marks.filter((mark) => litKeys.has(mark.key)).map((mark) => mark.x);
6489
6566
  const band = litXs.length > 0 ? { from: Math.min(...litXs), to: Math.max(...litXs) } : null;
@@ -6647,7 +6724,7 @@ function TimelineTrack({
6647
6724
  "data-timeline-node": "fold",
6648
6725
  "data-timeline-key": node.key,
6649
6726
  "data-timeline-count": String(node.members.length),
6650
- "aria-label": foldName(node.members),
6727
+ "aria-label": foldName(node.members, strings),
6651
6728
  "aria-describedby": bubbleKey === node.key ? tipId : void 0,
6652
6729
  tabIndex: node.key === tabKey ? 0 : -1,
6653
6730
  ref: (el) => {
@@ -6779,13 +6856,13 @@ function TimelineTrack({
6779
6856
  }
6780
6857
  ),
6781
6858
  bare && /* @__PURE__ */ jsx(Captions, { nodes: railNodes, geo, reveal }),
6782
- now > startMs && now <= endMs + DAY_MS && /* @__PURE__ */ jsx(
6859
+ now !== null && now > startMs && now <= endMs + DAY_MS && /* @__PURE__ */ jsx(
6783
6860
  TodayMark,
6784
6861
  {
6785
6862
  x: view.xByMs(Math.min(now, endMs)),
6786
6863
  geo,
6787
6864
  trackPx,
6788
- label: fmtSliderDate(now),
6865
+ label: fmtDate(now),
6789
6866
  reveal
6790
6867
  }
6791
6868
  ),
@@ -6929,7 +7006,7 @@ function TimelineTrack({
6929
7006
  className: "rosh-tl-bubble-open text-blue-600",
6930
7007
  "data-timeline-part": "bubble-open",
6931
7008
  onClick: () => (bubbleNode.mark?.onOpen ?? bubbleNode.mark?.onClick)?.(),
6932
- children: "Open"
7009
+ children: strings.openButton
6933
7010
  }
6934
7011
  )
6935
7012
  ] }) : (
@@ -7032,9 +7109,11 @@ function MilestoneTimeline({
7032
7109
  highlightKeys = null,
7033
7110
  onHoverChange
7034
7111
  }) {
7035
- const [today] = useState(() => Date.now());
7112
+ const today = useBrowserValueOnce(Date.now, null);
7036
7113
  const dated = [];
7037
7114
  const pending = [];
7115
+ const strings = useShellStrings().timeline;
7116
+ const locale = useShellLocale();
7038
7117
  for (const m of milestones) {
7039
7118
  const ms = toDayMs(m.date ?? null);
7040
7119
  if (ms === null) pending.push({ key: m.key, label: m.label, kind: m.kind });
@@ -7044,22 +7123,24 @@ function MilestoneTimeline({
7044
7123
  const explicitEnd = toDayMs(endDate ?? null);
7045
7124
  let startMs;
7046
7125
  let endMs;
7126
+ let anchored = true;
7047
7127
  if (dated.length === 0) {
7048
- endMs = explicitEnd ?? today;
7128
+ endMs = explicitEnd ?? today ?? 0;
7049
7129
  startMs = endMs - 30 * DAY_MS;
7130
+ anchored = explicitEnd !== null || today !== null;
7050
7131
  } else {
7051
7132
  startMs = dated[0].ms;
7052
7133
  endMs = explicitEnd ?? dated[dated.length - 1].ms;
7053
7134
  if (endMs - startMs < DAY_MS) endMs = startMs + DAY_MS;
7054
7135
  }
7055
7136
  const totalLeadDays = dated.length > 0 ? Math.max(0, Math.round((endMs - startMs) / DAY_MS)) : 0;
7056
- const duration = summary ?? (totalLeadDays > 0 ? `${totalLeadDays.toLocaleString()} day${totalLeadDays === 1 ? "" : "s"}` : void 0);
7137
+ const duration = summary ?? (totalLeadDays > 0 ? strings.duration(totalLeadDays) : void 0);
7057
7138
  const meta = [
7058
- dated.length > 0 ? `started ${fmtSliderDate(dated[0].ms)}` : void 0,
7139
+ dated.length > 0 ? strings.started(fmtSliderDate(dated[0].ms, locale)) : void 0,
7059
7140
  duration,
7060
7141
  // Only where something HAS not happened: "7 of 7 milestones" is a count of
7061
7142
  // nothing, and the reader can see the dots.
7062
- pending.length > 0 ? `${dated.length} of ${milestones.length} milestones` : void 0
7143
+ pending.length > 0 ? strings.reached(dated.length, milestones.length) : void 0
7063
7144
  ];
7064
7145
  const items = dated.map((m) => ({
7065
7146
  key: m.key,
@@ -7087,7 +7168,7 @@ function MilestoneTimeline({
7087
7168
  maxMs: Math.max(...ms)
7088
7169
  }));
7089
7170
  const rail = variant === "rail";
7090
- const track = /* @__PURE__ */ jsx("div", { className: "select-none", children: /* @__PURE__ */ jsx(
7171
+ const track = anchored && /* @__PURE__ */ jsx("div", { className: "select-none", children: /* @__PURE__ */ jsx(
7091
7172
  TimelineTrack,
7092
7173
  {
7093
7174
  axis: rail ? "spread" : "compressed",
@@ -7097,11 +7178,11 @@ function MilestoneTimeline({
7097
7178
  phases,
7098
7179
  startMs,
7099
7180
  endMs,
7100
- todayMs: today,
7181
+ todayMs: today ?? void 0,
7101
7182
  edgeCaptions,
7102
7183
  highlightKeys,
7103
7184
  onHoverChange,
7104
- ariaLabel: `${title} milestones`
7185
+ ariaLabel: strings.milestones(title)
7105
7186
  }
7106
7187
  ) });
7107
7188
  if (rail) {
@@ -7162,6 +7243,7 @@ function useProductionTimeline(opts) {
7162
7243
  markers = []
7163
7244
  } = opts;
7164
7245
  const [scrubMs, setScrubMs] = useState(null);
7246
+ const strings = useShellStrings().production;
7165
7247
  useEffect(() => {
7166
7248
  if (currentReportDate) {
7167
7249
  setScrubMs(new Date(currentReportDate).getTime());
@@ -7183,8 +7265,16 @@ function useProductionTimeline(opts) {
7183
7265
  stopPlay();
7184
7266
  }, [currentReportId]);
7185
7267
  const reportTimes = reports.map((r) => toDayMs(r.date)).filter((t) => t != null);
7186
- const earliestReport = reportTimes.length ? Math.min(...reportTimes) : Date.now();
7187
- const latestReport = reportTimes.length ? Math.max(...reportTimes) : Date.now();
7268
+ const today = useBrowserValueOnce(Date.now, null);
7269
+ const now = today ?? Math.max(
7270
+ 0,
7271
+ ...reportTimes,
7272
+ ...markers.map((m) => toDayMs(m.date) ?? 0),
7273
+ toDayMs(poProductionStartDate ?? null) ?? 0,
7274
+ toDayMs(poEstCompletionDate ?? null) ?? 0
7275
+ );
7276
+ const earliestReport = reportTimes.length ? Math.min(...reportTimes) : now;
7277
+ const latestReport = reportTimes.length ? Math.max(...reportTimes) : now;
7188
7278
  const startMs = toDayMs(poProductionStartDate ?? null) ?? earliestReport;
7189
7279
  const estComplete = toDayMs(poEstCompletionDate ?? null) ?? 0;
7190
7280
  const isPostProd = POST_PRODUCTION_STATUSES.has((poStatus || "").toLowerCase());
@@ -7194,7 +7284,7 @@ function useProductionTimeline(opts) {
7194
7284
  }, 0);
7195
7285
  const endMs = (() => {
7196
7286
  const candidates = [estComplete, latestReport, latestMarker, startMs + DAY_MS];
7197
- if (!isPostProd) candidates.push(Date.now());
7287
+ if (!isPostProd) candidates.push(now);
7198
7288
  return Math.max(...candidates);
7199
7289
  })();
7200
7290
  const totalLeadDays = Math.max(1, Math.round((endMs - startMs) / DAY_MS));
@@ -7229,7 +7319,7 @@ function useProductionTimeline(opts) {
7229
7319
  }));
7230
7320
  return {
7231
7321
  id: `zero-${poNumber}`,
7232
- progress_number: "Start",
7322
+ progress_number: strings.start,
7233
7323
  date: poProductionStartDate || new Date(startMs).toISOString().slice(0, 10),
7234
7324
  items: zeroItems,
7235
7325
  est_completion_date: poEstCompletionDate ?? null,
@@ -7282,7 +7372,7 @@ function useProductionTimeline(opts) {
7282
7372
  }
7283
7373
  displayed = {
7284
7374
  id: `interp-${effectiveScrub}`,
7285
- progress_number: "Estimated",
7375
+ progress_number: strings.estimated,
7286
7376
  date: new Date(effectiveScrub).toISOString().slice(0, 10),
7287
7377
  created_by_name: "",
7288
7378
  est_completion_date: estCompletion,
@@ -7318,9 +7408,9 @@ function useProductionTimeline(opts) {
7318
7408
  setScrubMs(startMs);
7319
7409
  let lastTime = performance.now();
7320
7410
  let cur = startMs;
7321
- const step = (now) => {
7322
- const dt = now - lastTime;
7323
- lastTime = now;
7411
+ const step = (now2) => {
7412
+ const dt = now2 - lastTime;
7413
+ lastTime = now2;
7324
7414
  cur = cur + dt / msPerDayOfPlayback * DAY_MS;
7325
7415
  if (cur >= playStopMs) {
7326
7416
  setScrubMs(playStopMs);
@@ -7385,6 +7475,8 @@ function TimelineScrubber({
7385
7475
  bare = false,
7386
7476
  endMark = null
7387
7477
  }) {
7478
+ const strings = useShellStrings().production;
7479
+ const locale = useShellLocale();
7388
7480
  const dated = reports.map((r) => ({ report: r, ms: new Date(r.date).getTime() })).filter(({ ms }) => Number.isFinite(ms)).sort((a, b) => a.ms - b.ms);
7389
7481
  const items = dated.map(({ report, ms }) => ({
7390
7482
  key: report.id,
@@ -7424,13 +7516,13 @@ function TimelineScrubber({
7424
7516
  onChange,
7425
7517
  onDragStart,
7426
7518
  stops,
7427
- ariaLabel: "Scrub the production timeline",
7519
+ ariaLabel: strings.scrub,
7428
7520
  valueText: (ms) => {
7429
7521
  const report = reportAt(ms);
7430
- return report ? `${reportLabel(report)} \xB7 ${fmtSliderDate(ms)}` : fmtSliderDate(ms);
7522
+ return report ? `${reportLabel(report)} \xB7 ${fmtSliderDate(ms, locale)}` : fmtSliderDate(ms, locale);
7431
7523
  }
7432
7524
  },
7433
- ariaLabel: `Production reports and events, ${fmtSliderDate(startMs)} to ${fmtSliderDate(endMs)}`
7525
+ ariaLabel: strings.label(fmtSliderDate(startMs, locale), fmtSliderDate(endMs, locale))
7434
7526
  }
7435
7527
  ) });
7436
7528
  }
@@ -7463,7 +7555,7 @@ function ProductionTimeline({
7463
7555
  onOpenMarker,
7464
7556
  renderReportPreview,
7465
7557
  onScrubProgress,
7466
- heading = "Production progress",
7558
+ heading: headingProp,
7467
7559
  edgeCaptions,
7468
7560
  reportLabel = (report) => report.progress_number,
7469
7561
  resetLabel,
@@ -7488,6 +7580,10 @@ function ProductionTimeline({
7488
7580
  } = snapshot;
7489
7581
  const stops = reports.map((report) => toDayMs(report.date)).filter((ms) => ms != null).sort((a, b) => a - b);
7490
7582
  const play = useReportPlayback(stops, setScrubMs, onPlayStart);
7583
+ const strings = useShellStrings().production;
7584
+ const locale = useShellLocale();
7585
+ const heading = headingProp ?? strings.heading;
7586
+ const fmtDate = (ms) => fmtSliderDate(ms, locale);
7491
7587
  if (reports.length === 0 && markers.length === 0) return null;
7492
7588
  const shipmentMarkers = markers.filter((m) => m.kind === "shipment");
7493
7589
  const inspectionMarkers = markers.filter((m) => m.kind === "inspection");
@@ -7506,11 +7602,11 @@ function ProductionTimeline({
7506
7602
  type: "button",
7507
7603
  onClick: play.toggle,
7508
7604
  "aria-pressed": play.playing,
7509
- title: play.playing ? "Pause" : "Play the timeline, report by report",
7605
+ title: play.playing ? strings.pause : strings.playTitle,
7510
7606
  className: "rosh-tl-play text-gray-700 border-gray-300 hover:text-blue-600 hover:border-blue-600",
7511
7607
  children: [
7512
7608
  play.playing ? /* @__PURE__ */ jsx("svg", { viewBox: "0 0 10 10", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2 1.5h2.3v7H2zM5.7 1.5H8v7H5.7z" }) }) : /* @__PURE__ */ jsx("svg", { viewBox: "0 0 10 10", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M2 1.2 8.4 5 2 8.8z" }) }),
7513
- /* @__PURE__ */ jsx("span", { children: play.playing ? "Pause" : "Play" })
7609
+ /* @__PURE__ */ jsx("span", { children: play.playing ? strings.pause : strings.play })
7514
7610
  ]
7515
7611
  }
7516
7612
  )
@@ -7524,26 +7620,26 @@ function ProductionTimeline({
7524
7620
  resetToCurrent();
7525
7621
  },
7526
7622
  className: "text-xs text-blue-600 underline-offset-4 hover:underline font-medium",
7527
- children: resetLabel ?? `Back to ${currentLabel}`
7623
+ children: resetLabel ?? strings.backTo(String(currentLabel))
7528
7624
  }
7529
7625
  ) : null;
7626
+ const [showingBefore, showingAfter] = splitAround(strings.showing);
7530
7627
  const statusLine = (showing) => /* @__PURE__ */ jsx("p", { className: "rosh-tl-status text-gray-800", children: displayed ? /* @__PURE__ */ jsxs(Fragment, { children: [
7531
- showing && "Showing ",
7628
+ showing && showingBefore,
7532
7629
  /* @__PURE__ */ jsx("b", { className: "font-medium", children: reportLabel(displayed) }),
7630
+ showing && showingAfter,
7533
7631
  /* @__PURE__ */ jsxs("span", { className: "text-gray-500", children: [
7534
7632
  " \xB7 ",
7535
- fmtSliderDate(new Date(displayed.date).getTime()),
7633
+ fmtDate(new Date(displayed.date).getTime()),
7536
7634
  " \xB7 ",
7537
- Math.round(overall),
7538
- "% overall",
7635
+ strings.overall(Math.round(overall)),
7539
7636
  " \xB7 ",
7540
- totalStock.toLocaleString(),
7541
- " pc in stock"
7637
+ strings.inStock(totalStock)
7542
7638
  ] })
7543
- ] }) : /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: "No production report filed yet" }) });
7639
+ ] }) : /* @__PURE__ */ jsx("span", { className: "text-gray-500", children: strings.noReport }) });
7544
7640
  if (variant === "rail") {
7545
7641
  const estimateAhead = estCompletionMs != null && estCompletionMs >= endMs && endMs > startMs;
7546
- const endMark = estimateAhead ? { key: "__estimate__", label: "Estimated completion", dateText: fmtSliderDate(estCompletionMs) } : null;
7642
+ const endMark = estimateAhead ? { key: "__estimate__", label: strings.estimatedCompletion, dateText: fmtDate(estCompletionMs) } : null;
7547
7643
  return /* @__PURE__ */ jsxs("div", { className: "shrink-0", "data-timeline-variant": "rail", children: [
7548
7644
  /* @__PURE__ */ jsx(
7549
7645
  TimelineScrubber,
@@ -7563,8 +7659,8 @@ function ProductionTimeline({
7563
7659
  renderReportPreview,
7564
7660
  reportLabel,
7565
7661
  edgeCaptions: {
7566
- start: edgeCaptions?.start ?? "Start",
7567
- end: edgeCaptions?.end ?? (estimateAhead ? "Est. done" : void 0)
7662
+ start: edgeCaptions?.start ?? strings.start,
7663
+ end: edgeCaptions?.end ?? (estimateAhead ? strings.estDone : void 0)
7568
7664
  },
7569
7665
  onDragStart: play.stop,
7570
7666
  playback: {
@@ -7589,8 +7685,8 @@ function ProductionTimeline({
7589
7685
  heading,
7590
7686
  subject: poNumber,
7591
7687
  meta: [
7592
- `${fmtSliderDate(startMs)} \u2192 ${fmtSliderDate(endMs)}`,
7593
- `${totalLeadDays.toLocaleString()} day${totalLeadDays === 1 ? "" : "s"} lead time`
7688
+ `${fmtDate(startMs)} \u2192 ${fmtDate(endMs)}`,
7689
+ strings.leadTime(totalLeadDays)
7594
7690
  ],
7595
7691
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [
7596
7692
  resetButton,
@@ -7601,7 +7697,7 @@ function ProductionTimeline({
7601
7697
  /* @__PURE__ */ jsxs("div", { className: "rosh-tl-legend", children: [
7602
7698
  reports.length > 0 && /* @__PURE__ */ jsxs("span", { className: "border-gray-200 text-gray-500", children: [
7603
7699
  /* @__PURE__ */ jsx("i", { "aria-hidden": "true", className: "rosh-tl-glyph is-ring border-blue-600" }),
7604
- "Production report"
7700
+ strings.report
7605
7701
  ] }),
7606
7702
  shipmentMarkers.length > 0 && /* @__PURE__ */ jsxs("span", { className: "border-gray-200 text-gray-500", children: [
7607
7703
  /* @__PURE__ */ jsx(
@@ -7613,7 +7709,7 @@ function ProductionTimeline({
7613
7709
  children: /* @__PURE__ */ jsx(TimelineGlyph, { name: "truck" })
7614
7710
  }
7615
7711
  ),
7616
- "Shipment"
7712
+ strings.shipment
7617
7713
  ] }),
7618
7714
  inspectionMarkers.length > 0 && /* @__PURE__ */ jsxs("span", { className: "border-gray-200 text-gray-500", children: [
7619
7715
  /* @__PURE__ */ jsx(
@@ -7625,7 +7721,7 @@ function ProductionTimeline({
7625
7721
  children: /* @__PURE__ */ jsx(TimelineGlyph, { name: "flask" })
7626
7722
  }
7627
7723
  ),
7628
- "Inspection"
7724
+ strings.inspection
7629
7725
  ] }),
7630
7726
  invoiceMarkers.length > 0 && /* @__PURE__ */ jsxs("span", { className: "border-gray-200 text-gray-500", children: [
7631
7727
  /* @__PURE__ */ jsx(
@@ -7637,7 +7733,7 @@ function ProductionTimeline({
7637
7733
  children: /* @__PURE__ */ jsx(TimelineGlyph, { name: "receipt" })
7638
7734
  }
7639
7735
  ),
7640
- "Invoice"
7736
+ strings.invoice
7641
7737
  ] })
7642
7738
  ] })
7643
7739
  ] }),
@@ -7686,6 +7782,7 @@ function ContainerFillChart({
7686
7782
  className,
7687
7783
  style
7688
7784
  }) {
7785
+ const strings = useShellStrings().containers;
7689
7786
  const filledLines = isFilled ? items.filter(isFilled) : items;
7690
7787
  const qtyInstr = (l) => toInt(getInstructionQty(l));
7691
7788
  const qtyActual = (l) => toInt(getActualQty(l));
@@ -7715,24 +7812,12 @@ function ContainerFillChart({
7715
7812
  return /* @__PURE__ */ jsxs("div", { className: `mt-2 px-1 space-y-2 ${className ?? ""}`.trim(), style, children: [
7716
7813
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-gray-500", children: [
7717
7814
  /* @__PURE__ */ jsxs("span", { children: [
7718
- filledLines.length,
7719
- " items \xB7 ",
7720
- totalQty,
7721
- " pcs",
7722
- hasActual && totalQtyInstr !== totalQtyActual && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-gray-400", children: [
7723
- "(",
7724
- totalQtyInstr,
7725
- " instr / ",
7726
- totalQtyActual,
7727
- " loaded)"
7728
- ] })
7815
+ `${strings.items(filledLines.length)} \xB7 ${strings.pieces(totalQty)}`,
7816
+ hasActual && totalQtyInstr !== totalQtyActual && /* @__PURE__ */ jsx("span", { className: "ml-1 text-gray-400", children: strings.split(totalQtyInstr, totalQtyActual) })
7729
7817
  ] }),
7730
7818
  showNewIndicator && newCount > 0 && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 text-green-600", children: [
7731
7819
  /* @__PURE__ */ jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-green-500" }),
7732
- newCount,
7733
- " new (",
7734
- newQty,
7735
- " pcs)"
7820
+ strings.added(newCount, newQty)
7736
7821
  ] })
7737
7822
  ] }),
7738
7823
  totalVolume > 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
@@ -7751,28 +7836,23 @@ function ContainerFillChart({
7751
7836
  /* @__PURE__ */ jsx("div", { className: `absolute inset-y-0 left-0 ${longerColor} transition-all`, style: { width: `${longerPct}%` } }),
7752
7837
  dual && shorterPct > 0 && /* @__PURE__ */ jsx("div", { className: `absolute inset-y-0 left-0 ${shorterColor} transition-all`, style: { width: `${shorterPct}%` } })
7753
7838
  ] }),
7754
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-500 w-16 text-right", children: dual ? `${Math.min(totalVolumeActual - i * containerCap, containerCap).toFixed(1)} / ${containerCap} m\xB3` : `${Math.min(totalVolumeInstr - i * containerCap, containerCap).toFixed(1)} / ${containerCap} m\xB3` })
7839
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-500 w-16 text-right", children: dual ? strings.fill(Math.min(totalVolumeActual - i * containerCap, containerCap), containerCap) : strings.fill(Math.min(totalVolumeInstr - i * containerCap, containerCap), containerCap) })
7755
7840
  ] }, `bar-${i}`);
7756
7841
  }),
7757
7842
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs pt-0.5", children: [
7758
7843
  /* @__PURE__ */ jsxs("span", { className: "text-gray-500 inline-flex items-center gap-1", children: [
7759
- "Total:",
7844
+ strings.total,
7760
7845
  hasActual ? /* @__PURE__ */ jsxs(Fragment, { children: [
7761
7846
  /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-blue-700", children: [
7762
7847
  /* @__PURE__ */ jsx("span", { className: "inline-block w-2 h-2 rounded-full bg-blue-500" }),
7763
- totalVolumeInstr.toFixed(2),
7764
- " m\xB3 instruction"
7848
+ strings.instructed(totalVolumeInstr)
7765
7849
  ] }),
7766
7850
  /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: "/" }),
7767
7851
  /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-green-700", children: [
7768
7852
  /* @__PURE__ */ jsx("span", { className: "inline-block w-2 h-2 rounded-full bg-green-500" }),
7769
- totalVolumeActual.toFixed(2),
7770
- " m\xB3 loaded"
7853
+ strings.loaded(totalVolumeActual)
7771
7854
  ] })
7772
- ] }) : /* @__PURE__ */ jsxs("b", { className: "text-gray-700", children: [
7773
- totalVolume.toFixed(2),
7774
- " m\xB3"
7775
- ] })
7855
+ ] }) : /* @__PURE__ */ jsx("b", { className: "text-gray-700", children: strings.volume(totalVolume) })
7776
7856
  ] }),
7777
7857
  /* @__PURE__ */ jsxs("span", { className: `font-medium ${isOptimal ? "text-green-600" : "text-gray-500"}`, children: [
7778
7858
  isOptimal && "\u2713 ",
@@ -7781,7 +7861,7 @@ function ContainerFillChart({
7781
7861
  useType
7782
7862
  ] })
7783
7863
  ] })
7784
- ] }) : /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-400", children: "Volume: N/A (no volume data for these part numbers)" })
7864
+ ] }) : /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-400", children: strings.noVolume })
7785
7865
  ] });
7786
7866
  }
7787
7867
  var INLINE_RE = /!\[([^\]]*)\]\(([^)]+)\)|\[([^\]]+)\]\(([^)]+)\)|\*\*([^*]+)\*\*|`([^`]+)`|\*([^*]+)\*/g;
@@ -7851,6 +7931,7 @@ function renderInline(text) {
7851
7931
  }
7852
7932
  function ScreenshotPlaceholder({ alt }) {
7853
7933
  const label = alt.replace(/^screenshot:\s*/i, "");
7934
+ const strings = useShellStrings().markdown;
7854
7935
  return /* @__PURE__ */ jsxs("div", { className: "my-1 flex items-start gap-2 rounded-lg border border-dashed border-gray-300 bg-gray-50 px-3 py-2.5", children: [
7855
7936
  /* @__PURE__ */ jsx(
7856
7937
  "svg",
@@ -7863,7 +7944,7 @@ function ScreenshotPlaceholder({ alt }) {
7863
7944
  }
7864
7945
  ),
7865
7946
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500", children: [
7866
- /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-600", children: "Screenshot \u2014 " }),
7947
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-600", children: `${strings.screenshot} \u2014 ` }),
7867
7948
  label
7868
7949
  ] })
7869
7950
  ] });
@@ -8086,7 +8167,7 @@ function HelpCenter({
8086
8167
  value: query,
8087
8168
  onChange: (e) => setQuery(e.target.value),
8088
8169
  placeholder: strings.help.searchPlaceholder,
8089
- className: "w-full rounded-md border border-gray-300 bg-white py-1.5 pl-8 pr-2 text-sm shadow-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
8170
+ className: "w-full rounded-md border border-gray-300 bg-white py-1.5 pl-8 pr-2 text-sm shadow-sm focus:border-blue-500 rosh-focus-ring focus-visible:ring-1 focus-visible:ring-[color:var(--rosh-focus-ring-accent)]"
8090
8171
  }
8091
8172
  )
8092
8173
  ] }) }),
@@ -8164,6 +8245,7 @@ function SidebarActionButton({
8164
8245
  title,
8165
8246
  className = ""
8166
8247
  }) {
8248
+ const hotkeyLabel = useShortcutLabel(hotkey ?? "");
8167
8249
  return /* @__PURE__ */ jsxs(
8168
8250
  "button",
8169
8251
  {
@@ -8174,7 +8256,7 @@ function SidebarActionButton({
8174
8256
  className: `w-full inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium transition-colors disabled:opacity-50 ${VARIANTS2[variant]} ${className}`,
8175
8257
  children: [
8176
8258
  children,
8177
- hotkey && /* @__PURE__ */ jsx("kbd", { className: "rounded border border-blue-400/50 bg-blue-500/30 px-1.5 py-0.5 text-[10px] font-medium", children: hotkey })
8259
+ hotkey && /* @__PURE__ */ jsx("kbd", { className: "rounded border border-blue-400/50 bg-blue-500/30 px-1.5 py-0.5 text-[10px] font-medium", children: hotkeyLabel })
8178
8260
  ]
8179
8261
  }
8180
8262
  );
@@ -8184,6 +8266,8 @@ function SidebarGroupLabel({ children }) {
8184
8266
  }
8185
8267
  function SidebarNavItem({ label, count, active, onClick, icon, trailing, severity }) {
8186
8268
  const marker = resolveSeverity(severity, "SidebarNavItem");
8269
+ const severityWords = useShellStrings().severity;
8270
+ const word = marker ? severityWord(marker, severityWords) : void 0;
8187
8271
  return /* @__PURE__ */ jsxs(
8188
8272
  "button",
8189
8273
  {
@@ -8199,7 +8283,7 @@ function SidebarNavItem({ label, count, active, onClick, icon, trailing, severit
8199
8283
  "span",
8200
8284
  {
8201
8285
  "aria-hidden": "true",
8202
- title: marker.word,
8286
+ title: word,
8203
8287
  className: `shrink-0 h-1.5 w-1.5 rounded-full ${marker.fill}`
8204
8288
  }
8205
8289
  ),
@@ -8211,7 +8295,7 @@ function SidebarNavItem({ label, count, active, onClick, icon, trailing, severit
8211
8295
  children: label
8212
8296
  }
8213
8297
  ),
8214
- marker && /* @__PURE__ */ jsx("span", { className: "sr-only", children: marker.word }),
8298
+ marker && /* @__PURE__ */ jsx("span", { className: "sr-only", children: word }),
8215
8299
  count != null && count > 0 && /* @__PURE__ */ jsx("span", { className: `shrink-0 inline-flex items-center justify-center min-w-[1.25rem] px-1.5 h-5 rounded-full text-[11px] font-medium ${active ? "bg-blue-100 text-blue-700" : "bg-gray-100 text-gray-600"}`, children: count }),
8216
8300
  trailing != null && /* @__PURE__ */ jsx("span", { className: "shrink-0", children: trailing })
8217
8301
  ]
@@ -8344,7 +8428,7 @@ function DropdownMenu({
8344
8428
  disabled,
8345
8429
  onClick: () => open ? close(false) : openAt(enabledFrom(0, 1)),
8346
8430
  onKeyDown: onTriggerKeyDown,
8347
- className: "inline-flex items-center justify-center rounded-md text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400 disabled:cursor-not-allowed disabled:opacity-60",
8431
+ className: "inline-flex items-center justify-center rounded-md text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)] disabled:cursor-not-allowed disabled:opacity-60",
8348
8432
  children: trigger
8349
8433
  }
8350
8434
  ),
@@ -8463,13 +8547,14 @@ var SIDE_POSITION = {
8463
8547
  bottom: "inset-x-0 bottom-0"
8464
8548
  };
8465
8549
  function CloseButton({ onClose }) {
8550
+ const strings = useShellStrings().dialog;
8466
8551
  return /* @__PURE__ */ jsx(
8467
8552
  "button",
8468
8553
  {
8469
8554
  type: "button",
8470
8555
  onClick: onClose,
8471
- "aria-label": "Close",
8472
- className: "rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-400",
8556
+ "aria-label": strings.close,
8557
+ className: "rounded-md p-1 text-gray-400 hover:bg-gray-100 hover:text-gray-600 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-solid)]",
8473
8558
  children: /* @__PURE__ */ jsx("svg", { className: "h-5 w-5", viewBox: "0 0 20 20", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 6l8 8M14 6l-8 8" }) })
8474
8559
  }
8475
8560
  );
@@ -8541,7 +8626,10 @@ function Drawer({
8541
8626
  ] });
8542
8627
  }
8543
8628
  var DEFAULT_MAGNIFIER = "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z";
8544
- function GlobalSearch({ providers, typeIcons, placeholder = "Search...", onSelect } = {}) {
8629
+ var MIN_QUERY = 2;
8630
+ function GlobalSearch({ providers, typeIcons, placeholder: placeholderProp, onSelect } = {}) {
8631
+ const strings = useShellStrings().search;
8632
+ const placeholder = placeholderProp ?? strings.placeholder;
8545
8633
  const [open, setOpen] = useState(false);
8546
8634
  const [query, setQuery] = useState("");
8547
8635
  const [results, setResults] = useState([]);
@@ -8573,7 +8661,7 @@ function GlobalSearch({ providers, typeIcons, placeholder = "Search...", onSelec
8573
8661
  }, [open]);
8574
8662
  useEffect(() => {
8575
8663
  if (debounceRef.current) clearTimeout(debounceRef.current);
8576
- if (query.length < 2) {
8664
+ if (query.length < MIN_QUERY) {
8577
8665
  setResults([]);
8578
8666
  return;
8579
8667
  }
@@ -8627,14 +8715,14 @@ function GlobalSearch({ providers, typeIcons, placeholder = "Search...", onSelec
8627
8715
  onChange: (e) => setQuery(e.target.value),
8628
8716
  onKeyDown: handleKeyDown,
8629
8717
  placeholder,
8630
- className: "flex-1 text-sm bg-transparent border-0 outline-none placeholder-gray-400"
8718
+ className: "flex-1 text-sm bg-transparent border-0 rosh-focus-ring placeholder-gray-400"
8631
8719
  }
8632
8720
  ),
8633
- /* @__PURE__ */ jsx("kbd", { className: "hidden sm:inline-flex items-center rounded border border-gray-300 px-1.5 py-0.5 text-[10px] font-medium text-gray-400", children: "ESC" })
8721
+ /* @__PURE__ */ jsx("kbd", { className: "hidden sm:inline-flex items-center rounded border border-gray-300 px-1.5 py-0.5 text-[10px] font-medium text-gray-400", children: strings.escBadge })
8634
8722
  ] }),
8635
8723
  /* @__PURE__ */ jsxs("div", { className: "max-h-[50vh] overflow-y-auto", children: [
8636
- loading && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "Searching..." }),
8637
- !loading && query.length >= 2 && results.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "No results found." }),
8724
+ loading && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: strings.searching }),
8725
+ !loading && query.length >= MIN_QUERY && results.length === 0 && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: strings.noResults }),
8638
8726
  !loading && results.length > 0 && /* @__PURE__ */ jsx("ul", { className: "py-2", children: results.map((r, i) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
8639
8727
  "button",
8640
8728
  {
@@ -8651,23 +8739,23 @@ function GlobalSearch({ providers, typeIcons, placeholder = "Search...", onSelec
8651
8739
  ]
8652
8740
  }
8653
8741
  ) }, `${r.type}-${r.label}-${i}`)) }),
8654
- !loading && query.length < 2 && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: "Type at least 2 characters to search..." })
8742
+ !loading && query.length < MIN_QUERY && /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-sm text-gray-400", children: strings.minChars(MIN_QUERY) })
8655
8743
  ] }),
8656
8744
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-t border-gray-100 bg-gray-50 text-[11px] text-gray-400", children: [
8657
8745
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
8658
8746
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
8659
8747
  /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "\u2191" }),
8660
8748
  /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "\u2193" }),
8661
- " navigate"
8749
+ ` ${strings.navigate}`
8662
8750
  ] }),
8663
8751
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
8664
- /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "Enter" }),
8665
- " open"
8752
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: strings.enterKey }),
8753
+ ` ${strings.open}`
8666
8754
  ] })
8667
8755
  ] }),
8668
8756
  /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
8669
- /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: "Esc" }),
8670
- " close"
8757
+ /* @__PURE__ */ jsx("kbd", { className: "rounded border border-gray-300 px-1 py-0.5 font-medium", children: strings.escKey }),
8758
+ ` ${strings.close}`
8671
8759
  ] })
8672
8760
  ] })
8673
8761
  ] })
@@ -8685,13 +8773,18 @@ function Kanban({
8685
8773
  onCardClick,
8686
8774
  sortInColumn,
8687
8775
  isLoading = false,
8688
- loadingText = "Loading\u2026",
8776
+ loadingText: loadingTextProp,
8689
8777
  emptyState,
8690
8778
  showColumnsWhenEmpty = false,
8691
- columnEmptyText = "Drop here",
8779
+ columnEmptyText: columnEmptyTextProp,
8692
8780
  onAddItem,
8693
- addItemText = "Add item"
8781
+ addItemText: addItemTextProp
8694
8782
  }) {
8783
+ const strings = useShellStrings().kanban;
8784
+ const locale = useShellLocale();
8785
+ const loadingText = loadingTextProp ?? strings.loading;
8786
+ const columnEmptyText = columnEmptyTextProp ?? strings.dropHere;
8787
+ const addItemText = addItemTextProp ?? strings.addItem;
8695
8788
  const [dragId, setDragId] = useState(null);
8696
8789
  const [over, setOver] = useState(null);
8697
8790
  const grouped = useMemo(() => {
@@ -8707,7 +8800,7 @@ function Kanban({
8707
8800
  };
8708
8801
  const boardRef = useRef(null);
8709
8802
  const prevRects = useRef(/* @__PURE__ */ new Map());
8710
- useLayoutEffect(() => {
8803
+ useBrowserLayoutEffect(() => {
8711
8804
  if (dragId !== null) return;
8712
8805
  const board = boardRef.current;
8713
8806
  if (!board) return;
@@ -8760,7 +8853,7 @@ function Kanban({
8760
8853
  };
8761
8854
  if (isLoading) return /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500 p-4", children: loadingText });
8762
8855
  if (items.length === 0 && !showColumnsWhenEmpty) {
8763
- return /* @__PURE__ */ jsx(Fragment, { children: emptyState ?? /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500 p-4", children: "No items." }) });
8856
+ return /* @__PURE__ */ jsx(Fragment, { children: emptyState ?? /* @__PURE__ */ jsx("div", { className: "text-sm text-gray-500 p-4", children: strings.empty }) });
8764
8857
  }
8765
8858
  return /* @__PURE__ */ jsx("div", { ref: boardRef, className: "flex-1 overflow-x-auto grid-scroll", children: /* @__PURE__ */ jsx("div", { className: "flex gap-3 h-full min-w-max pb-2", children: columns.map((col) => {
8766
8859
  const colItems = grouped[col.value] ?? [];
@@ -8792,7 +8885,7 @@ function Kanban({
8792
8885
  /* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${col.dot ?? "bg-gray-400"}` }),
8793
8886
  col.label
8794
8887
  ] }),
8795
- /* @__PURE__ */ jsx("span", { className: "text-xs opacity-70", children: colItems.length })
8888
+ /* @__PURE__ */ jsx("span", { className: "text-xs opacity-70", children: formatPlain(colItems.length, locale) })
8796
8889
  ]
8797
8890
  }
8798
8891
  ),
@@ -8891,14 +8984,16 @@ function Pagination({
8891
8984
  showEdges = false,
8892
8985
  className = ""
8893
8986
  }) {
8987
+ const strings = useShellStrings().pagination;
8988
+ const locale = useShellLocale();
8894
8989
  if (pageCount <= 1) return null;
8895
8990
  const pages = buildPages(page, pageCount, siblingCount);
8896
8991
  const go = (p) => {
8897
8992
  if (p >= 1 && p <= pageCount && p !== page) onPageChange(p);
8898
8993
  };
8899
- return /* @__PURE__ */ jsxs("nav", { "aria-label": "Pagination", className: `flex items-center gap-1 ${className}`.trim(), children: [
8900
- showEdges && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "First page", disabled: page === 1, onClick: () => go(1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\xAB" }),
8901
- /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Previous page", disabled: page === 1, onClick: () => go(page - 1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\u2039" }),
8994
+ return /* @__PURE__ */ jsxs("nav", { "aria-label": strings.label, className: `flex items-center gap-1 ${className}`.trim(), children: [
8995
+ showEdges && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": strings.first, disabled: page === 1, onClick: () => go(1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\xAB" }),
8996
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": strings.previous, disabled: page === 1, onClick: () => go(page - 1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\u2039" }),
8902
8997
  pages.map(
8903
8998
  (p, i) => p === "\u2026" ? /* @__PURE__ */ jsx("span", { className: "px-1 text-gray-400", children: "\u2026" }, `gap-${i}`) : /* @__PURE__ */ jsx(
8904
8999
  "button",
@@ -8907,13 +9002,13 @@ function Pagination({
8907
9002
  "aria-current": p === page ? "page" : void 0,
8908
9003
  onClick: () => go(p),
8909
9004
  className: `${BTN} ${p === page ? "bg-blue-600 font-medium text-white" : "text-gray-700 hover:bg-gray-100"}`,
8910
- children: p
9005
+ children: formatPlain(p, locale)
8911
9006
  },
8912
9007
  p
8913
9008
  )
8914
9009
  ),
8915
- /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Next page", disabled: page === pageCount, onClick: () => go(page + 1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\u203A" }),
8916
- showEdges && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Last page", disabled: page === pageCount, onClick: () => go(pageCount), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\xBB" })
9010
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": strings.next, disabled: page === pageCount, onClick: () => go(page + 1), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\u203A" }),
9011
+ showEdges && /* @__PURE__ */ jsx("button", { type: "button", "aria-label": strings.last, disabled: page === pageCount, onClick: () => go(pageCount), className: `${BTN} text-gray-600 hover:bg-gray-100`, children: "\xBB" })
8917
9012
  ] });
8918
9013
  }
8919
9014
  var isGroup = (h) => Array.isArray(h.columns);
@@ -8968,7 +9063,7 @@ function DataTable({
8968
9063
  const bottomPad = virtualized ? (data.length - end) * virtualized.rowHeight : 0;
8969
9064
  const headRowRef = useRef(null);
8970
9065
  const [headRowH, setHeadRowH] = useState(0);
8971
- useLayoutEffect(() => {
9066
+ useBrowserLayoutEffect(() => {
8972
9067
  if (!virtualized) return;
8973
9068
  const h = headRowRef.current?.offsetHeight ?? 0;
8974
9069
  setHeadRowH((prev) => prev === h ? prev : h);
@@ -9149,7 +9244,7 @@ function DataTable({
9149
9244
  } : {},
9150
9245
  className: [
9151
9246
  "border-b border-gray-100 last:border-b-0",
9152
- rowProps?.onClick ? "cursor-pointer hover:bg-gray-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-blue-400" : "",
9247
+ rowProps?.onClick ? "cursor-pointer hover:bg-gray-50 rosh-focus-ring focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[color:var(--rosh-focus-ring-solid)]" : "",
9153
9248
  rowClassName?.(row, i) ?? "",
9154
9249
  rowProps?.className ?? ""
9155
9250
  ].filter(Boolean).join(" "),
@@ -9224,27 +9319,29 @@ function ListFooter({
9224
9319
  isFetchingMore,
9225
9320
  extra
9226
9321
  }) {
9322
+ const strings = useShellStrings().list;
9227
9323
  const total = totalCount ?? loadedCount;
9228
9324
  const allLoaded = loadedCount >= total;
9229
9325
  return /* @__PURE__ */ jsxs(Fragment, { children: [
9230
9326
  extra,
9231
- selectedCount > 0 ? `${selectedCount} selected \u2014 ` : "",
9232
- allLoaded ? totalCount != null ? `All ${total.toLocaleString()} ${label} loaded` : `${total.toLocaleString()} ${label}` : `${loadedCount.toLocaleString()} of ${total.toLocaleString()} ${label}`,
9233
- isFetchingMore ? " \xB7 Loading more..." : ""
9327
+ selectedCount > 0 ? `${strings.selected(selectedCount)} \u2014 ` : "",
9328
+ allLoaded ? totalCount != null ? strings.allLoaded(total, label) : strings.count(total, label) : strings.loadedOf(loadedCount, total, label),
9329
+ isFetchingMore ? ` \xB7 ${strings.loadingMore}` : ""
9234
9330
  ] });
9235
9331
  }
9236
9332
  function ListLoadError({
9237
- title = "Couldn't load this list",
9238
- message = "Something went wrong fetching these records. Check your connection and try again.",
9333
+ title,
9334
+ message,
9239
9335
  onRetry,
9240
9336
  variant = "dashed"
9241
9337
  }) {
9338
+ const strings = useShellStrings().list;
9242
9339
  const frame = variant === "card" ? "rounded-md border border-gray-200 bg-white px-6 py-12" : variant === "none" ? "px-6 py-12" : "rounded-lg border-2 border-dashed border-red-200 px-6 py-12";
9243
9340
  return /* @__PURE__ */ jsxs("div", { className: `flex flex-col items-center justify-center text-center ${frame}`, role: "alert", children: [
9244
9341
  /* @__PURE__ */ jsx("svg", { className: "h-10 w-10 text-red-300 mb-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1, "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 9v3.75m0 3.75h.008M10.34 3.94l-8.4 14.55A1.5 1.5 0 003.24 21h17.52a1.5 1.5 0 001.3-2.51l-8.4-14.55a1.5 1.5 0 00-2.6 0z" }) }),
9245
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900", children: title }),
9246
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: message }),
9247
- onRetry && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Button_default, { variant: "secondary", size: "sm", onClick: onRetry, children: "Try again" }) })
9342
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium text-gray-900", children: title ?? strings.loadError }),
9343
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 mt-2", children: message ?? strings.loadErrorMessage }),
9344
+ onRetry && /* @__PURE__ */ jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsx(Button_default, { variant: "secondary", size: "sm", onClick: onRetry, children: strings.retry }) })
9248
9345
  ] });
9249
9346
  }
9250
9347
  function Sparkline({
@@ -9297,6 +9394,7 @@ function BarChart({
9297
9394
  className,
9298
9395
  style
9299
9396
  }) {
9397
+ const locale = useShellLocale();
9300
9398
  if (data.length === 0) return null;
9301
9399
  const peak = max ?? (Math.max(...data, 0) || 1);
9302
9400
  return /* @__PURE__ */ jsxs("div", { className, style, children: [
@@ -9309,13 +9407,22 @@ function BarChart({
9309
9407
  minHeight: v > 0 ? 2 : 0,
9310
9408
  backgroundColor: colors?.[i] ?? color
9311
9409
  },
9312
- title: labels?.[i] != null ? `${labels[i]}: ${v}` : String(v)
9410
+ title: labels?.[i] != null ? `${labels[i]}: ${formatPlain(v, locale)}` : formatPlain(v, locale)
9313
9411
  }
9314
9412
  ) }, i)) }),
9315
9413
  labels && /* @__PURE__ */ jsx("div", { className: "mt-1 flex", style: { gap }, children: labels.map((l, i) => /* @__PURE__ */ jsx("div", { className: "flex-1 truncate text-center text-[10px] text-gray-400", children: l }, i)) })
9316
9414
  ] });
9317
9415
  }
9318
- var PALETTE = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#6366f1", "#14b8a6", "#a855f7", "#64748b"];
9416
+ var DONUT_PALETTE = [
9417
+ "var(--rosh-donut-1)",
9418
+ "var(--rosh-donut-2)",
9419
+ "var(--rosh-donut-3)",
9420
+ "var(--rosh-donut-4)",
9421
+ "var(--rosh-donut-5)",
9422
+ "var(--rosh-donut-6)",
9423
+ "var(--rosh-donut-7)",
9424
+ "var(--rosh-donut-8)"
9425
+ ];
9319
9426
  function DonutChart({ segments, size = 120, thickness = 16, centerLabel, className, style }) {
9320
9427
  const total = segments.reduce((s, x) => s + Math.max(0, x.value), 0) || 1;
9321
9428
  const r = (size - thickness) / 2;
@@ -9329,7 +9436,7 @@ function DonutChart({ segments, size = 120, thickness = 16, centerLabel, classNa
9329
9436
  style: { width: size, height: size, ...style },
9330
9437
  children: [
9331
9438
  /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: `0 0 ${size} ${size}`, role: "img", "aria-hidden": "true", style: { transform: "rotate(-90deg)" }, children: [
9332
- /* @__PURE__ */ jsx("circle", { cx: c, cy: c, r, fill: "none", stroke: "rgba(148,163,184,0.25)", strokeWidth: thickness }),
9439
+ /* @__PURE__ */ jsx("circle", { cx: c, cy: c, r, fill: "none", stroke: "var(--rosh-donut-track)", strokeWidth: thickness }),
9333
9440
  segments.map((s, i) => {
9334
9441
  const len2 = Math.max(0, s.value) / total * circ;
9335
9442
  const el = /* @__PURE__ */ jsx(
@@ -9339,7 +9446,7 @@ function DonutChart({ segments, size = 120, thickness = 16, centerLabel, classNa
9339
9446
  cy: c,
9340
9447
  r,
9341
9448
  fill: "none",
9342
- stroke: s.color ?? PALETTE[i % PALETTE.length],
9449
+ stroke: s.color ?? DONUT_PALETTE[i % DONUT_PALETTE.length],
9343
9450
  strokeWidth: thickness,
9344
9451
  strokeDasharray: `${len2} ${circ - len2}`,
9345
9452
  strokeDashoffset: -offset
@@ -9362,7 +9469,7 @@ function toPoints(data, min, span) {
9362
9469
  function pathFor(pts) {
9363
9470
  return pts.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(2)},${y.toFixed(2)}`).join(" ");
9364
9471
  }
9365
- var fmt = (v) => Number.isInteger(v) ? String(v) : v.toFixed(1);
9472
+ var fmt = (v, locale) => Number.isInteger(v) ? formatPlain(v, locale) : formatFixed(v, 1, locale);
9366
9473
  function LineChart({
9367
9474
  series,
9368
9475
  labels,
@@ -9376,6 +9483,7 @@ function LineChart({
9376
9483
  className,
9377
9484
  style
9378
9485
  }) {
9486
+ const locale = useShellLocale();
9379
9487
  const drawn = series.filter((s) => s.data.length > 0);
9380
9488
  if (drawn.length === 0) return null;
9381
9489
  const all = drawn.flatMap((s) => s.data);
@@ -9390,9 +9498,9 @@ function LineChart({
9390
9498
  ] }, i)) }),
9391
9499
  /* @__PURE__ */ jsxs("div", { className: "flex", children: [
9392
9500
  showScale && /* @__PURE__ */ jsxs("div", { className: "flex flex-col justify-between pr-1.5 text-right text-[10px] leading-none text-gray-400", style: { height }, children: [
9393
- /* @__PURE__ */ jsx("span", { children: fmt(top) }),
9394
- /* @__PURE__ */ jsx("span", { children: fmt(bottom + span / 2) }),
9395
- /* @__PURE__ */ jsx("span", { children: fmt(bottom) })
9501
+ /* @__PURE__ */ jsx("span", { children: fmt(top, locale) }),
9502
+ /* @__PURE__ */ jsx("span", { children: fmt(bottom + span / 2, locale) }),
9503
+ /* @__PURE__ */ jsx("span", { children: fmt(bottom, locale) })
9396
9504
  ] }),
9397
9505
  /* @__PURE__ */ jsxs(
9398
9506
  "svg",
@@ -9412,7 +9520,7 @@ function LineChart({
9412
9520
  return /* @__PURE__ */ jsxs("g", { children: [
9413
9521
  s.fill && /* @__PURE__ */ jsx("path", { d: `${line} L100,100 L0,100 Z`, fill: s.fill, stroke: "none" }),
9414
9522
  /* @__PURE__ */ jsx("path", { d: line, fill: "none", stroke: color(s), strokeWidth, strokeLinejoin: "round", strokeLinecap: "round", vectorEffect: "non-scaling-stroke" }),
9415
- showDots && s.data.length > 1 && pts.map(([x, y], i) => /* @__PURE__ */ jsx("path", { d: `M${x.toFixed(2)},${y.toFixed(2)} h0.01`, stroke: color(s), strokeWidth: strokeWidth * 2.5, strokeLinecap: "round", vectorEffect: "non-scaling-stroke", children: /* @__PURE__ */ jsx("title", { children: `${labels?.[i] != null ? `${labels[i]}: ` : ""}${s.label ? `${s.label} ` : ""}${s.data[i]}` }) }, i))
9523
+ showDots && s.data.length > 1 && pts.map(([x, y], i) => /* @__PURE__ */ jsx("path", { d: `M${x.toFixed(2)},${y.toFixed(2)} h0.01`, stroke: color(s), strokeWidth: strokeWidth * 2.5, strokeLinecap: "round", vectorEffect: "non-scaling-stroke", children: /* @__PURE__ */ jsx("title", { children: `${labels?.[i] != null ? `${labels[i]}: ` : ""}${s.label ? `${s.label} ` : ""}${formatPlain(s.data[i], locale)}` }) }, i))
9416
9524
  ] }, si);
9417
9525
  })
9418
9526
  ]
@@ -9430,7 +9538,7 @@ var STATUS_GROUPS = {
9430
9538
  overdue: "danger"
9431
9539
  };
9432
9540
  var REVENUE = [12, 18, 14, 22, 19, 27, 24, 31, 28, 35, 30, 38];
9433
- var MONTHS2 = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
9541
+ var MONTHS = ["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"];
9434
9542
  var ORDERS = [
9435
9543
  { id: "#3201", customer: "Alice Nguyen", total: "$1,204", status: "paid" },
9436
9544
  { id: "#3202", customer: "Marco Reyes", total: "$642", status: "shipped" },
@@ -9457,7 +9565,7 @@ function DashboardTemplate() {
9457
9565
  /* @__PURE__ */ jsx(StatCard, { label: "Refunds", value: "$1.1k", delta: { value: "0%", direction: "flat" } })
9458
9566
  ] }),
9459
9567
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 lg:grid-cols-3", children: [
9460
- /* @__PURE__ */ jsx(Card, { header: "Revenue", className: "lg:col-span-2", children: /* @__PURE__ */ jsx("div", { className: "text-blue-600", children: /* @__PURE__ */ jsx(BarChart, { data: REVENUE, labels: MONTHS2, height: 160 }) }) }),
9568
+ /* @__PURE__ */ jsx(Card, { header: "Revenue", className: "lg:col-span-2", children: /* @__PURE__ */ jsx("div", { className: "text-blue-600", children: /* @__PURE__ */ jsx(BarChart, { data: REVENUE, labels: MONTHS, height: 160 }) }) }),
9461
9569
  /* @__PURE__ */ jsx(Card, { header: "Traffic sources", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
9462
9570
  /* @__PURE__ */ jsx(
9463
9571
  DonutChart,
@@ -9474,19 +9582,19 @@ function DashboardTemplate() {
9474
9582
  ),
9475
9583
  /* @__PURE__ */ jsxs("ul", { className: "space-y-1.5 text-sm text-gray-600", children: [
9476
9584
  /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
9477
- /* @__PURE__ */ jsx(Dot, { c: "#3b82f6" }),
9585
+ /* @__PURE__ */ jsx(Dot, { c: DONUT_PALETTE[0] }),
9478
9586
  "Direct \xB7 45%"
9479
9587
  ] }),
9480
9588
  /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
9481
- /* @__PURE__ */ jsx(Dot, { c: "#22c55e" }),
9589
+ /* @__PURE__ */ jsx(Dot, { c: DONUT_PALETTE[1] }),
9482
9590
  "Search \xB7 30%"
9483
9591
  ] }),
9484
9592
  /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
9485
- /* @__PURE__ */ jsx(Dot, { c: "#f59e0b" }),
9593
+ /* @__PURE__ */ jsx(Dot, { c: DONUT_PALETTE[2] }),
9486
9594
  "Social \xB7 15%"
9487
9595
  ] }),
9488
9596
  /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2", children: [
9489
- /* @__PURE__ */ jsx(Dot, { c: "#ef4444" }),
9597
+ /* @__PURE__ */ jsx(Dot, { c: DONUT_PALETTE[3] }),
9490
9598
  "Email \xB7 10%"
9491
9599
  ] })
9492
9600
  ] })
@@ -9972,12 +10080,12 @@ function AuthScreen({ mode = "login" }) {
9972
10080
  ] }) });
9973
10081
  }
9974
10082
  var COPY2 = {
9975
- 403: { title: "Access denied", body: "You don't have permission to view this page." },
9976
- 404: { title: "Page not found", body: "The page you're looking for doesn't exist or was moved." },
9977
- 500: { title: "Something went wrong", body: "An unexpected error occurred. Please try again." }
10083
+ 403: (errors) => ({ title: errors.forbidden, body: errors.forbiddenBody }),
10084
+ 404: (errors) => ({ title: errors.notFound, body: errors.notFoundBody }),
10085
+ 500: (errors) => ({ title: errors.serverError, body: errors.serverErrorBody })
9978
10086
  };
9979
10087
  function ErrorPage({ code = 404, actions }) {
9980
- const c = COPY2[code];
10088
+ const c = COPY2[code](useShellStrings().errors);
9981
10089
  return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center bg-gray-50 p-6 text-center", children: [
9982
10090
  /* @__PURE__ */ jsx("div", { className: "text-7xl font-bold tracking-tight text-gray-400", children: code }),
9983
10091
  /* @__PURE__ */ jsx("h1", { className: "mt-2 text-xl font-semibold text-gray-900", children: c.title }),
@@ -9985,6 +10093,9 @@ function ErrorPage({ code = 404, actions }) {
9985
10093
  actions && /* @__PURE__ */ jsx("div", { className: "mt-6 flex items-center gap-2", children: actions })
9986
10094
  ] });
9987
10095
  }
10096
+ function ErrorWord({ pick }) {
10097
+ return /* @__PURE__ */ jsx(Fragment, { children: pick(useShellStrings().errors) });
10098
+ }
9988
10099
  var ErrorBoundary = class extends Component {
9989
10100
  state = { error: null };
9990
10101
  static getDerivedStateFromError(error) {
@@ -10019,26 +10130,34 @@ var ErrorBoundary = class extends Component {
10019
10130
  {
10020
10131
  code: 500,
10021
10132
  actions: /* @__PURE__ */ jsxs(Fragment, { children: [
10022
- /* @__PURE__ */ jsx(Button_default, { onClick: this.reset, children: "Try again" }),
10133
+ /* @__PURE__ */ jsx(Button_default, { onClick: this.reset, children: /* @__PURE__ */ jsx(ErrorWord, { pick: (errors) => errors.retry }) }),
10023
10134
  this.props.actions
10024
10135
  ] })
10025
10136
  }
10026
10137
  ) }),
10027
10138
  this.props.showDetails && /* @__PURE__ */ jsxs("details", { className: "mx-auto w-full max-w-2xl shrink-0 px-6 pb-6", children: [
10028
- /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-sm font-medium text-gray-600", children: "Error details" }),
10139
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-sm font-medium text-gray-600", children: /* @__PURE__ */ jsx(ErrorWord, { pick: (errors) => errors.details }) }),
10029
10140
  /* @__PURE__ */ jsx("pre", { className: "mt-2 overflow-auto rounded-md bg-gray-100 p-3 text-xs text-gray-700", children: error.stack ?? error.message })
10030
10141
  ] })
10031
10142
  ] })
10032
10143
  );
10033
10144
  }
10034
10145
  };
10146
+ function darkQuery() {
10147
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(prefers-color-scheme: dark)") : null;
10148
+ }
10035
10149
  function subscribeMediaQuery(cb) {
10036
- const mq = window.matchMedia("(prefers-color-scheme: dark)");
10150
+ const mq = darkQuery();
10151
+ if (!mq) return () => {
10152
+ };
10037
10153
  mq.addEventListener("change", cb);
10038
10154
  return () => mq.removeEventListener("change", cb);
10039
10155
  }
10040
10156
  function getSystemIsDark() {
10041
- return window.matchMedia("(prefers-color-scheme: dark)").matches;
10157
+ return darkQuery()?.matches ?? false;
10158
+ }
10159
+ function getServerIsDark() {
10160
+ return false;
10042
10161
  }
10043
10162
  function hexToHsl(hex) {
10044
10163
  const r = parseInt(hex.slice(1, 3), 16) / 255;
@@ -10069,7 +10188,7 @@ function hslToHex(h, s, l) {
10069
10188
  }
10070
10189
  function resolveTheme(theme) {
10071
10190
  const t = theme || "system";
10072
- if (t === "system") return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
10191
+ if (t === "system") return getSystemIsDark() ? "dark" : "light";
10073
10192
  return t;
10074
10193
  }
10075
10194
  function applyAccent(accentColor) {
@@ -10127,7 +10246,7 @@ function applyThemePrefs(p) {
10127
10246
  }
10128
10247
  function useTheme() {
10129
10248
  const { prefs } = useShellPrefs();
10130
- const systemDark = useSyncExternalStore(subscribeMediaQuery, getSystemIsDark);
10249
+ const systemDark = useSyncExternalStore(subscribeMediaQuery, getSystemIsDark, getServerIsDark);
10131
10250
  const saved = prefs.theme || "system";
10132
10251
  const accentColor = prefs.accent_color || null;
10133
10252
  const customBgColor = prefs.custom_bg_color || null;
@@ -10559,7 +10678,7 @@ function usePlotWidth(ref, fallback = DEFAULT_PLOT_WIDTH) {
10559
10678
  function RankedBars({
10560
10679
  rows,
10561
10680
  max,
10562
- formatValue = (v) => String(v),
10681
+ formatValue: formatValueProp,
10563
10682
  emphasisKey,
10564
10683
  highlight = "none",
10565
10684
  emphasisTone,
@@ -10567,8 +10686,12 @@ function RankedBars({
10567
10686
  rowGap = 30,
10568
10687
  animate = true,
10569
10688
  className,
10570
- emptyLabel = "Nothing to rank in this window."
10689
+ emptyLabel: emptyLabelProp
10571
10690
  }) {
10691
+ const strings = useShellStrings().charts;
10692
+ const locale = useShellLocale();
10693
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
10694
+ const emptyLabel = emptyLabelProp ?? strings.nothingToRank;
10572
10695
  if (rows.length === 0) {
10573
10696
  return /* @__PURE__ */ jsx("p", { className: `text-sm text-gray-500 ${className ?? ""}`, children: emptyLabel });
10574
10697
  }
@@ -10585,7 +10708,7 @@ function RankedBars({
10585
10708
  /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between gap-3", children: [
10586
10709
  /* @__PURE__ */ jsxs("span", { className: `truncate font-mono text-xs text-gray-600 ${emphasised ? "font-semibold" : ""}`, title: row.label, children: [
10587
10710
  row.label,
10588
- emphasised && /* @__PURE__ */ jsx("span", { className: "sr-only", children: " (emphasised)" })
10711
+ emphasised && /* @__PURE__ */ jsx("span", { className: "sr-only", children: ` ${strings.emphasised}` })
10589
10712
  ] }),
10590
10713
  /* @__PURE__ */ jsx(
10591
10714
  "span",
@@ -10619,11 +10742,15 @@ function Meter({
10619
10742
  objective,
10620
10743
  label,
10621
10744
  detail,
10622
- formatValue = (v) => `${(v * 100).toFixed(1)}%`,
10745
+ formatValue: formatValueProp,
10623
10746
  tone,
10624
10747
  className,
10625
- unavailableLabel = "Not enough eligible requests to judge."
10748
+ unavailableLabel: unavailableLabelProp
10626
10749
  }) {
10750
+ const strings = useShellStrings().charts;
10751
+ const locale = useShellLocale();
10752
+ const formatValue = formatValueProp ?? ((v) => formatPercent(v * 100, 1, locale));
10753
+ const unavailableLabel = unavailableLabelProp ?? strings.notEnough;
10627
10754
  if (value == null || !Number.isFinite(value)) {
10628
10755
  return /* @__PURE__ */ jsxs("div", { className, children: [
10629
10756
  label && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-gray-700", children: label }),
@@ -10637,7 +10764,7 @@ function Meter({
10637
10764
  const caption = [
10638
10765
  segments?.length ? segments.map((seg) => `${seg.label} ${formatValue(seg.value)}`).join(" \xB7 ") : "",
10639
10766
  detail ?? "",
10640
- objective != null ? `objective ${formatValue(objective)}${met ? " met" : " missed"}` : ""
10767
+ objective != null ? (met ? strings.objectiveMet : strings.objectiveMissed)(formatValue(objective)) : ""
10641
10768
  ].filter(Boolean).join(" \xB7 ");
10642
10769
  return /* @__PURE__ */ jsxs("div", { className, children: [
10643
10770
  /* @__PURE__ */ jsxs("div", { className: "mb-1.5 flex items-baseline justify-between gap-3", children: [
@@ -10705,9 +10832,9 @@ function Meter({
10705
10832
  ] });
10706
10833
  }
10707
10834
  var DIRECTION = {
10708
- up: { glyph: "\u25B2", word: "up" },
10709
- down: { glyph: "\u25BC", word: "down" },
10710
- flat: { glyph: "\u25A0", word: "flat" }
10835
+ up: { glyph: "\u25B2", word: "trendUp" },
10836
+ down: { glyph: "\u25BC", word: "trendDown" },
10837
+ flat: { glyph: "\u25A0", word: "trendFlat" }
10711
10838
  };
10712
10839
  function StatTile({
10713
10840
  label,
@@ -10720,6 +10847,8 @@ function StatTile({
10720
10847
  footnote,
10721
10848
  className
10722
10849
  }) {
10850
+ const strings = useShellStrings().charts;
10851
+ const locale = useShellLocale();
10723
10852
  const direction = delta == null ? null : delta > 0 ? "up" : delta < 0 ? "down" : "flat";
10724
10853
  const deltaColour = deltaTone ? STATUS_VARS[deltaTone] : void 0;
10725
10854
  const trendColour = trendTone ? STATUS_VARS[trendTone] : resolveSeriesColor(0);
@@ -10732,10 +10861,10 @@ function StatTile({
10732
10861
  direction && /* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", style: deltaColour ? { color: deltaColour } : void 0, children: [
10733
10862
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: DIRECTION[direction].glyph }),
10734
10863
  " ",
10735
- Math.abs(delta),
10864
+ formatPlain(Math.abs(delta), locale),
10736
10865
  /* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
10737
10866
  " ",
10738
- DIRECTION[direction].word
10867
+ strings[DIRECTION[direction].word]
10739
10868
  ] })
10740
10869
  ] }),
10741
10870
  trend && trend.length > 1 && /* @__PURE__ */ jsx(
@@ -10756,9 +10885,11 @@ function ChartSkeleton({
10756
10885
  width = 720,
10757
10886
  variant = "area",
10758
10887
  bars = 9,
10759
- label = "Loading chart data",
10888
+ label: labelProp,
10760
10889
  className
10761
10890
  }) {
10891
+ const strings = useShellStrings().charts;
10892
+ const label = labelProp ?? strings.loading;
10762
10893
  const id = useId();
10763
10894
  const shimmerId = `${id}-shimmer`;
10764
10895
  const left = 48;
@@ -10893,7 +11024,7 @@ function ChartLegend({
10893
11024
  onMouseLeave: () => setHighlighted(null),
10894
11025
  onFocus: () => setHighlighted(entry.key),
10895
11026
  onBlur: () => setHighlighted(null),
10896
- className: `flex items-center gap-2 rounded text-xs text-gray-600 transition-opacity focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 ${dimmed ? "opacity-40" : ""}`,
11027
+ className: `flex items-center gap-2 rounded text-xs text-gray-600 transition-opacity rosh-focus-outline ${dimmed ? "opacity-40" : ""}`,
10897
11028
  children: swatch
10898
11029
  }
10899
11030
  ) : /* @__PURE__ */ jsx("span", { className: "flex items-center gap-2 text-xs text-gray-600", children: swatch }) }, entry.key);
@@ -10904,7 +11035,7 @@ function ChartFrame({
10904
11035
  subtitle,
10905
11036
  legend,
10906
11037
  table,
10907
- tableLabel = "Show the data as a table",
11038
+ tableLabel,
10908
11039
  busy = false,
10909
11040
  loading = false,
10910
11041
  skeletonVariant = "area",
@@ -10915,6 +11046,7 @@ function ChartFrame({
10915
11046
  children,
10916
11047
  className
10917
11048
  }) {
11049
+ const strings = useShellStrings().charts;
10918
11050
  return /* @__PURE__ */ jsx(ChartHighlightProvider, { children: /* @__PURE__ */ jsxs(
10919
11051
  "section",
10920
11052
  {
@@ -10931,7 +11063,7 @@ function ChartFrame({
10931
11063
  loading ? /* @__PURE__ */ jsx(ChartSkeleton, { variant: skeletonVariant, height: skeletonHeight }) : /* @__PURE__ */ jsx("div", { className: busy ? "opacity-60 transition-opacity" : "transition-opacity", children }),
10932
11064
  !loading && legend && legend.length > 1 && /* @__PURE__ */ jsx(ChartLegend, { entries: legend, interactive: interactiveLegend, fallbackSwatch: legendSwatch }),
10933
11065
  !loading && table && /* @__PURE__ */ jsxs("details", { className: "border-t border-gray-100 pt-3", children: [
10934
- /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-sm font-medium text-gray-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500", children: tableLabel }),
11066
+ /* @__PURE__ */ jsx("summary", { className: "cursor-pointer text-sm font-medium text-gray-600 rosh-focus-outline", children: tableLabel ?? strings.showTable }),
10935
11067
  /* @__PURE__ */ jsx("div", { className: "mt-3 overflow-x-auto", style: { color: CHART_INK.label }, children: table })
10936
11068
  ] })
10937
11069
  ]
@@ -11027,6 +11159,7 @@ function ChartBrush({
11027
11159
  color,
11028
11160
  className
11029
11161
  }) {
11162
+ const strings = useShellStrings().charts;
11030
11163
  const track = useRef(null);
11031
11164
  const last = labels.length - 1;
11032
11165
  const [from, to] = range;
@@ -11104,10 +11237,10 @@ function ChartBrush({
11104
11237
  height,
11105
11238
  rx: 3,
11106
11239
  fill: hue,
11107
- className: "cursor-ew-resize focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
11240
+ className: "cursor-ew-resize rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-deep)]",
11108
11241
  tabIndex: 0,
11109
11242
  role: "slider",
11110
- "aria-label": edge === "from" ? "Window start" : "Window end",
11243
+ "aria-label": edge === "from" ? strings.windowStart : strings.windowEnd,
11111
11244
  "aria-valuemin": 0,
11112
11245
  "aria-valuemax": last,
11113
11246
  "aria-valuenow": index,
@@ -11130,7 +11263,7 @@ function ChartBrush({
11130
11263
  height,
11131
11264
  viewBox: `0 0 ${width} ${height}`,
11132
11265
  role: "group",
11133
- "aria-label": `Time window \u2014 showing ${labels[from]} to ${labels[to]} of ${labels.length} intervals`,
11266
+ "aria-label": strings.window(labels[from], labels[to], labels.length),
11134
11267
  children: [
11135
11268
  /* @__PURE__ */ jsx("path", { d: outline, fill: CHART_INK.grid }),
11136
11269
  /* @__PURE__ */ jsx("rect", { x: 0, y: 0, width: xAt(from), height, fill: CHART_INK.surface, fillOpacity: 0.72 }),
@@ -11162,15 +11295,19 @@ function CartesianPlot({
11162
11295
  min,
11163
11296
  max,
11164
11297
  yTickCount = 4,
11165
- formatValue = (v) => String(v),
11298
+ formatValue: formatValueProp,
11166
11299
  yAxisLabel,
11167
11300
  xTickAnchor = "center",
11168
11301
  children,
11169
11302
  tooltip,
11170
11303
  ariaLabel,
11171
11304
  className,
11172
- emptyLabel = "No data in this window."
11305
+ emptyLabel: emptyLabelProp
11173
11306
  }) {
11307
+ const strings = useShellStrings().charts;
11308
+ const locale = useShellLocale();
11309
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
11310
+ const emptyLabel = emptyLabelProp ?? strings.noData;
11174
11311
  const host = useRef(null);
11175
11312
  const measured = usePlotWidth(host);
11176
11313
  const width = widthProp ?? measured;
@@ -11215,7 +11352,7 @@ function CartesianPlot({
11215
11352
  role: "img",
11216
11353
  "aria-label": ariaLabel,
11217
11354
  tabIndex: 0,
11218
- className: "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
11355
+ className: "rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-deep)]",
11219
11356
  onMouseLeave: () => setActive(null),
11220
11357
  onBlur: () => setActive(null),
11221
11358
  onKeyDown: onKey,
@@ -11269,7 +11406,7 @@ function TimeSeriesChart({
11269
11406
  tension = 0,
11270
11407
  max,
11271
11408
  yTickCount = 4,
11272
- formatValue = (v) => String(v),
11409
+ formatValue: formatValueProp,
11273
11410
  yAxisLabel,
11274
11411
  referenceLines,
11275
11412
  labelEndpoints = false,
@@ -11286,8 +11423,12 @@ function TimeSeriesChart({
11286
11423
  gridStyle = "solid",
11287
11424
  width: widthProp,
11288
11425
  className,
11289
- emptyLabel = "No data in this window."
11426
+ emptyLabel: emptyLabelProp
11290
11427
  }) {
11428
+ const strings = useShellStrings().charts;
11429
+ const locale = useShellLocale();
11430
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
11431
+ const emptyLabel = emptyLabelProp ?? strings.noData;
11291
11432
  const host = useRef(null);
11292
11433
  const measured = usePlotWidth(host);
11293
11434
  const width = widthProp ?? measured;
@@ -11342,7 +11483,7 @@ function TimeSeriesChart({
11342
11483
  const y = linearScale([0, top], { from: plot.bottom, to: plot.top });
11343
11484
  const scaleY = (value) => ladder ? ladder(value) : y(value);
11344
11485
  const isPercent = stacked && stackMode === "percent";
11345
- const formatTick = isPercent ? (v) => `${Math.round(v * 100)}%` : formatValue;
11486
+ const formatTick = isPercent ? (v) => formatPercent(Math.round(v * 100), 0, locale) : formatValue;
11346
11487
  const yTicks = ladder ? ladder.levels.map((level) => ({ value: level, y: ladder(level) })) : y.ticks(yTickCount + 1).map((value) => ({ value, y: y(value) }));
11347
11488
  const move = (delta) => (event) => {
11348
11489
  event.preventDefault();
@@ -11360,9 +11501,9 @@ function TimeSeriesChart({
11360
11501
  height,
11361
11502
  viewBox: `0 0 ${width} ${height}`,
11362
11503
  role: "img",
11363
- "aria-label": `${drawn.map((s) => s.label).join(", ")} over ${labels.length} intervals`,
11504
+ "aria-label": strings.overIntervals(drawn.map((s) => s.label), labels.length),
11364
11505
  tabIndex: 0,
11365
- className: "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
11506
+ className: "rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-deep)]",
11366
11507
  onMouseLeave: () => setActive(null),
11367
11508
  onBlur: () => setActive(null),
11368
11509
  onKeyDown: (event) => {
@@ -11632,9 +11773,9 @@ function TimeSeriesChart({
11632
11773
  // carries it back: the share the chart drew AND the count it was
11633
11774
  // taken over. A share without its denominator is the thing this
11634
11775
  // whole family keeps refusing to ship.
11635
- value: !isValue(s.data[active]) ? void 0 : stacked && stackMode === "percent" ? `${((share(s.data[active], active) ?? 0) * 100).toFixed(1)}% \xB7 ${formatValue(s.data[active])}` : formatValue(s.data[active])
11776
+ value: !isValue(s.data[active]) ? void 0 : stacked && stackMode === "percent" ? `${formatPercent((share(s.data[active], active) ?? 0) * 100, 1, locale)} \xB7 ${formatValue(s.data[active])}` : formatValue(s.data[active])
11636
11777
  })),
11637
- footnote: stacked && stackMode === "percent" ? `${formatValue(bucketTotals[active])} in this bucket` : void 0
11778
+ footnote: stacked && stackMode === "percent" ? strings.inBucket(formatValue(bucketTotals[active])) : void 0
11638
11779
  }
11639
11780
  )
11640
11781
  }
@@ -11650,7 +11791,7 @@ function ColumnChart({
11650
11791
  height = 220,
11651
11792
  width,
11652
11793
  max,
11653
- formatValue = (v) => String(v),
11794
+ formatValue: formatValueProp,
11654
11795
  yAxisLabel,
11655
11796
  className,
11656
11797
  emptyLabel,
@@ -11664,6 +11805,9 @@ function ColumnChart({
11664
11805
  }) {
11665
11806
  const defsId = useId();
11666
11807
  const { highlighted } = useHighlight();
11808
+ const strings = useShellStrings().charts;
11809
+ const locale = useShellLocale();
11810
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
11667
11811
  if (loading) return /* @__PURE__ */ jsx(ChartSkeleton, { height, width, variant: "bars", className });
11668
11812
  const drawn = series.map((s, slot) => ({ ...s, slot })).filter((s) => s.data.some((v) => Number.isFinite(v)));
11669
11813
  const finiteAt = (s, i) => {
@@ -11684,11 +11828,11 @@ function ColumnChart({
11684
11828
  height,
11685
11829
  width,
11686
11830
  max: percent ? 1 : max,
11687
- formatValue: percent ? (v) => `${Math.round(v * 100)}%` : formatValue,
11831
+ formatValue: percent ? (v) => formatPercent(Math.round(v * 100), 0, locale) : formatValue,
11688
11832
  yAxisLabel,
11689
11833
  className,
11690
11834
  emptyLabel,
11691
- ariaLabel: `${drawn.map((s) => s.label).join(", ")} across ${labels.length} categories`,
11835
+ ariaLabel: strings.acrossCategories(drawn.map((s) => s.label), labels.length),
11692
11836
  tooltip: (index) => /* @__PURE__ */ jsx(
11693
11837
  ChartTooltip,
11694
11838
  {
@@ -11699,9 +11843,9 @@ function ColumnChart({
11699
11843
  color: resolveSeriesColor(s.slot, s.color, s.tone),
11700
11844
  // Percent mode drops volume from the plot, so the tooltip carries
11701
11845
  // it back — the share AND the count it was taken over.
11702
- value: !Number.isFinite(s.data[index]) ? void 0 : percent ? `${((share(s.data[index], index) ?? 0) * 100).toFixed(1)}% \xB7 ${formatValue(s.data[index])}` : formatValue(s.data[index])
11846
+ value: !Number.isFinite(s.data[index]) ? void 0 : percent ? `${formatPercent((share(s.data[index], index) ?? 0) * 100, 1, locale)} \xB7 ${formatValue(s.data[index])}` : formatValue(s.data[index])
11703
11847
  })),
11704
- footnote: percent ? `${formatValue(totals[index])} in this category` : void 0
11848
+ footnote: percent ? strings.inCategory(formatValue(totals[index])) : void 0
11705
11849
  }
11706
11850
  ),
11707
11851
  children: ({ x, y, bottom, active }) => {
@@ -11777,16 +11921,21 @@ function ScatterChart({
11777
11921
  width: widthProp,
11778
11922
  xLabel,
11779
11923
  yLabel,
11780
- formatX = (v) => String(v),
11781
- formatY = (v) => String(v),
11924
+ formatX: formatXProp,
11925
+ formatY: formatYProp,
11782
11926
  xDomain,
11783
11927
  yDomain,
11784
11928
  xScale = "linear",
11785
11929
  yScale = "linear",
11786
11930
  radiusRange = [4, 22],
11787
11931
  className,
11788
- emptyLabel = "No observations in this window."
11932
+ emptyLabel: emptyLabelProp
11789
11933
  }) {
11934
+ const strings = useShellStrings().charts;
11935
+ const locale = useShellLocale();
11936
+ const formatX = formatXProp ?? ((v) => formatPlain(v, locale));
11937
+ const formatY = formatYProp ?? ((v) => formatPlain(v, locale));
11938
+ const emptyLabel = emptyLabelProp ?? strings.noObservations;
11790
11939
  const host = useRef(null);
11791
11940
  const measured = usePlotWidth(host);
11792
11941
  const width = widthProp ?? measured;
@@ -11839,9 +11988,9 @@ function ScatterChart({
11839
11988
  height,
11840
11989
  viewBox: `0 0 ${width} ${height}`,
11841
11990
  role: "img",
11842
- "aria-label": `${drawn.map((s) => s.label).join(", ")}: ${yLabel ?? "y"} against ${xLabel ?? "x"}` + (omitted > 0 ? `. ${omitted} ${omitted === 1 ? "point" : "points"} outside the shown range` : ""),
11991
+ "aria-label": strings.scatter(drawn.map((s) => s.label), yLabel ?? strings.yAxis, xLabel ?? strings.xAxis) + (omitted > 0 ? `. ${strings.outsideRange(omitted)}` : ""),
11843
11992
  tabIndex: 0,
11844
- className: "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
11993
+ className: "rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-deep)]",
11845
11994
  onMouseLeave: () => setHover(null),
11846
11995
  onBlur: () => setHover(null),
11847
11996
  onKeyDown: onKey,
@@ -11879,9 +12028,9 @@ function ScatterChart({
11879
12028
  {
11880
12029
  title: hoveredPoint.label ?? hoveredSeries.label,
11881
12030
  rows: [
11882
- { key: "x", label: xLabel ?? "x", color: resolveSeriesColor(hover.s, hoveredSeries.color, hoveredSeries.tone), value: formatX(hoveredPoint.x) },
11883
- { key: "y", label: yLabel ?? "y", color: resolveSeriesColor(hover.s, hoveredSeries.color, hoveredSeries.tone), value: formatY(hoveredPoint.y) },
11884
- ...hoveredPoint.size != null ? [{ key: "size", label: "Size", color: CHART_INK.muted, value: String(hoveredPoint.size) }] : []
12031
+ { key: "x", label: xLabel ?? strings.xAxis, color: resolveSeriesColor(hover.s, hoveredSeries.color, hoveredSeries.tone), value: formatX(hoveredPoint.x) },
12032
+ { key: "y", label: yLabel ?? strings.yAxis, color: resolveSeriesColor(hover.s, hoveredSeries.color, hoveredSeries.tone), value: formatY(hoveredPoint.y) },
12033
+ ...hoveredPoint.size != null ? [{ key: "size", label: strings.size, color: CHART_INK.muted, value: formatPlain(hoveredPoint.size, locale) }] : []
11885
12034
  ],
11886
12035
  footnote: hoveredPoint.label ? hoveredSeries.label : void 0
11887
12036
  }
@@ -11897,15 +12046,19 @@ function RangeChart({
11897
12046
  width,
11898
12047
  max,
11899
12048
  min,
11900
- formatValue = (v) => String(v),
12049
+ formatValue: formatValueProp,
11901
12050
  yAxisLabel,
11902
12051
  color,
11903
12052
  tone,
11904
- label = "Range",
12053
+ label: labelProp,
11905
12054
  className,
11906
12055
  emptyLabel,
11907
12056
  radius = 3
11908
12057
  }) {
12058
+ const strings = useShellStrings().charts;
12059
+ const locale = useShellLocale();
12060
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
12061
+ const label = labelProp ?? strings.range;
11909
12062
  const values = rows.flatMap((r) => [r.low, r.high]).filter(Number.isFinite);
11910
12063
  const colour = resolveSeriesColor(0, color, tone);
11911
12064
  return /* @__PURE__ */ jsx(
@@ -11922,15 +12075,15 @@ function RangeChart({
11922
12075
  className,
11923
12076
  emptyLabel,
11924
12077
  bandPadding: variant === "bar" ? 0.34 : 0,
11925
- ariaLabel: `${label}: low and high across ${labels.length} categories`,
12078
+ ariaLabel: strings.rangeLabel(label, labels.length),
11926
12079
  tooltip: (index) => /* @__PURE__ */ jsx(
11927
12080
  ChartTooltip,
11928
12081
  {
11929
12082
  title: labels[index],
11930
12083
  rows: [
11931
12084
  // undefined renders the em dash: "no row here" is not "zero".
11932
- { key: "high", label: "High", color: colour, value: rows[index] == null ? void 0 : formatValue(rows[index].high) },
11933
- { key: "low", label: "Low", color: colour, value: rows[index] == null ? void 0 : formatValue(rows[index].low) }
12085
+ { key: "high", label: strings.high, color: colour, value: rows[index] == null ? void 0 : formatValue(rows[index].high) },
12086
+ { key: "low", label: strings.low, color: colour, value: rows[index] == null ? void 0 : formatValue(rows[index].low) }
11934
12087
  ]
11935
12088
  }
11936
12089
  ),
@@ -11965,13 +12118,16 @@ function WaterfallChart({
11965
12118
  steps,
11966
12119
  height = 240,
11967
12120
  width,
11968
- formatValue = (v) => String(v),
12121
+ formatValue: formatValueProp,
11969
12122
  yAxisLabel,
11970
12123
  className,
11971
12124
  emptyLabel,
11972
12125
  radius = 3,
11973
12126
  connectors = true
11974
12127
  }) {
12128
+ const strings = useShellStrings().charts;
12129
+ const locale = useShellLocale();
12130
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
11975
12131
  let running = 0;
11976
12132
  const bars = steps.map((step) => {
11977
12133
  if (step.total) {
@@ -11995,7 +12151,7 @@ function WaterfallChart({
11995
12151
  className,
11996
12152
  emptyLabel,
11997
12153
  bandPadding: 0.36,
11998
- ariaLabel: `Waterfall of ${steps.length} steps from ${formatValue(bars[0]?.from ?? 0)} to ${formatValue(running)}`,
12154
+ ariaLabel: strings.waterfall(steps.length, formatValue(bars[0]?.from ?? 0), formatValue(running)),
11999
12155
  tooltip: (index) => {
12000
12156
  const bar = bars[index];
12001
12157
  const tone = bar?.total ? STATUS_VARS.neutral : bar.value >= 0 ? STATUS_VARS.good : STATUS_VARS.critical;
@@ -12007,12 +12163,12 @@ function WaterfallChart({
12007
12163
  {
12008
12164
  key: "step",
12009
12165
  color: tone,
12010
- label: bar?.total ? "Balance" : bar.value >= 0 ? "Increase" : "Decrease",
12166
+ label: bar?.total ? strings.balance : bar.value >= 0 ? strings.increase : strings.decrease,
12011
12167
  // The sign is written out: colour alone does not survive
12012
12168
  // greyscale, and polarity is the whole point of this chart.
12013
12169
  value: bar?.total ? formatValue(bar.to) : `${bar.value >= 0 ? "+" : "\u2212"}${formatValue(Math.abs(bar.value))}`
12014
12170
  },
12015
- ...bar?.total ? [] : [{ key: "running", label: "Running total", color: STATUS_VARS.neutral, value: formatValue(bar.to) }]
12171
+ ...bar?.total ? [] : [{ key: "running", label: strings.runningTotal, color: STATUS_VARS.neutral, value: formatValue(bar.to) }]
12016
12172
  ]
12017
12173
  }
12018
12174
  );
@@ -12066,13 +12222,19 @@ function HistogramChart({
12066
12222
  width,
12067
12223
  color,
12068
12224
  tone,
12069
- formatBound = (v) => Number.isInteger(v) ? String(v) : v.toFixed(1),
12070
- formatCount = (v) => String(v),
12071
- yAxisLabel = "count",
12072
- label = "Distribution",
12225
+ formatBound: formatBoundProp,
12226
+ formatCount: formatCountProp,
12227
+ yAxisLabel: yAxisLabelProp,
12228
+ label: labelProp,
12073
12229
  className,
12074
12230
  emptyLabel
12075
12231
  }) {
12232
+ const strings = useShellStrings().charts;
12233
+ const locale = useShellLocale();
12234
+ const formatBound = formatBoundProp ?? ((v) => Number.isInteger(v) ? formatPlain(v, locale) : formatFixed(v, 1, locale));
12235
+ const formatCount = formatCountProp ?? ((v) => formatPlain(v, locale));
12236
+ const yAxisLabel = yAxisLabelProp ?? strings.countAxis;
12237
+ const label = labelProp ?? strings.distribution;
12076
12238
  const computed = precomputed ?? binValues(values ?? [], bins);
12077
12239
  const colour = resolveSeriesColor(0, color, tone);
12078
12240
  const labels = computed.map((b) => formatBound(b.from));
@@ -12090,12 +12252,12 @@ function HistogramChart({
12090
12252
  yAxisLabel,
12091
12253
  className,
12092
12254
  emptyLabel,
12093
- ariaLabel: `${label}: ${computed.length} bins over ${(values ?? []).length} observations`,
12255
+ ariaLabel: strings.histogram(label, computed.length, (values ?? []).length),
12094
12256
  tooltip: (index) => /* @__PURE__ */ jsx(
12095
12257
  ChartTooltip,
12096
12258
  {
12097
12259
  title: `${formatBound(computed[index].from)} \u2013 ${formatBound(computed[index].to)}`,
12098
- rows: [{ key: "count", label: "Observations", color: colour, value: formatCount(computed[index].count) }]
12260
+ rows: [{ key: "count", label: strings.observations, color: colour, value: formatCount(computed[index].count) }]
12099
12261
  }
12100
12262
  ),
12101
12263
  children: ({ x, y, bottom, active }) => /* @__PURE__ */ jsx("g", { children: computed.map((bin, i) => /* @__PURE__ */ jsx(
@@ -12119,12 +12281,16 @@ function BoxPlotChart({
12119
12281
  width,
12120
12282
  color,
12121
12283
  tone,
12122
- formatValue = (v) => String(v),
12284
+ formatValue: formatValueProp,
12123
12285
  yAxisLabel,
12124
- label = "Distribution",
12286
+ label: labelProp,
12125
12287
  className,
12126
12288
  emptyLabel
12127
12289
  }) {
12290
+ const strings = useShellStrings().charts;
12291
+ const locale = useShellLocale();
12292
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
12293
+ const label = labelProp ?? strings.distribution;
12128
12294
  const colour = resolveSeriesColor(0, color, tone);
12129
12295
  const values = boxes.flatMap((b) => [b.min, b.max, ...b.outliers ?? []]).filter(Number.isFinite);
12130
12296
  return /* @__PURE__ */ jsx(
@@ -12139,7 +12305,7 @@ function BoxPlotChart({
12139
12305
  yAxisLabel,
12140
12306
  className,
12141
12307
  emptyLabel,
12142
- ariaLabel: `${label}: five-number summary for ${boxes.length} categories`,
12308
+ ariaLabel: strings.fiveNumber(label, boxes.length),
12143
12309
  tooltip: (index) => {
12144
12310
  const b = boxes[index];
12145
12311
  return /* @__PURE__ */ jsx(
@@ -12147,14 +12313,14 @@ function BoxPlotChart({
12147
12313
  {
12148
12314
  title: b.label,
12149
12315
  rows: [
12150
- { key: "max", label: "Maximum", color: colour, value: formatValue(b.max) },
12151
- { key: "q3", label: "Q3", color: colour, value: formatValue(b.q3) },
12152
- { key: "median", label: "Median", color: colour, value: formatValue(b.median) },
12153
- { key: "q1", label: "Q1", color: colour, value: formatValue(b.q1) },
12154
- { key: "min", label: "Minimum", color: colour, value: formatValue(b.min) }
12316
+ { key: "max", label: strings.maximum, color: colour, value: formatValue(b.max) },
12317
+ { key: "q3", label: strings.q3, color: colour, value: formatValue(b.q3) },
12318
+ { key: "median", label: strings.median, color: colour, value: formatValue(b.median) },
12319
+ { key: "q1", label: strings.q1, color: colour, value: formatValue(b.q1) },
12320
+ { key: "min", label: strings.minimum, color: colour, value: formatValue(b.min) }
12155
12321
  ],
12156
12322
  emphasisKey: "median",
12157
- footnote: b.outliers?.length ? `${b.outliers.length} outlier${b.outliers.length > 1 ? "s" : ""} beyond the whiskers` : void 0
12323
+ footnote: b.outliers?.length ? strings.outliers(b.outliers.length) : void 0
12158
12324
  }
12159
12325
  );
12160
12326
  },
@@ -12195,12 +12361,16 @@ function CandlestickChart({
12195
12361
  width,
12196
12362
  // Not `String(v)`: the axis domain is padded off the data, so its ticks are
12197
12363
  // almost never integers, and a raw float prints as 102.72999999999999.
12198
- formatValue = (v) => Number.isInteger(v) ? String(v) : v.toFixed(2),
12364
+ formatValue: formatValueProp,
12199
12365
  yAxisLabel,
12200
- label = "Price",
12366
+ label: labelProp,
12201
12367
  className,
12202
12368
  emptyLabel
12203
12369
  }) {
12370
+ const strings = useShellStrings().charts;
12371
+ const locale = useShellLocale();
12372
+ const formatValue = formatValueProp ?? ((v) => Number.isInteger(v) ? formatPlain(v, locale) : formatFixed(v, 2, locale));
12373
+ const label = labelProp ?? strings.price;
12204
12374
  const values = candles.flatMap((c) => [c.low, c.high]).filter(Number.isFinite);
12205
12375
  const lo = Math.min(...values);
12206
12376
  const hi = Math.max(...values);
@@ -12219,7 +12389,7 @@ function CandlestickChart({
12219
12389
  yAxisLabel,
12220
12390
  className,
12221
12391
  emptyLabel,
12222
- ariaLabel: `${label}: open, high, low and close across ${candles.length} periods`,
12392
+ ariaLabel: strings.ohlc(label, candles.length),
12223
12393
  tooltip: (index) => {
12224
12394
  const c = candles[index];
12225
12395
  const rose = c.close >= c.open;
@@ -12229,13 +12399,13 @@ function CandlestickChart({
12229
12399
  {
12230
12400
  title: c.label,
12231
12401
  rows: [
12232
- { key: "open", label: "Open", color: tone, value: formatValue(c.open) },
12233
- { key: "high", label: "High", color: tone, value: formatValue(c.high) },
12234
- { key: "low", label: "Low", color: tone, value: formatValue(c.low) },
12235
- { key: "close", label: "Close", color: tone, value: formatValue(c.close) }
12402
+ { key: "open", label: strings.open, color: tone, value: formatValue(c.open) },
12403
+ { key: "high", label: strings.high, color: tone, value: formatValue(c.high) },
12404
+ { key: "low", label: strings.low, color: tone, value: formatValue(c.low) },
12405
+ { key: "close", label: strings.close, color: tone, value: formatValue(c.close) }
12236
12406
  ],
12237
12407
  emphasisKey: "close",
12238
- footnote: rose ? "Closed above its open" : "Closed below its open"
12408
+ footnote: rose ? strings.closedAbove : strings.closedBelow
12239
12409
  }
12240
12410
  );
12241
12411
  },
@@ -12279,13 +12449,18 @@ function HeatmapChart({
12279
12449
  width: widthProp,
12280
12450
  color,
12281
12451
  max,
12282
- formatValue = (v) => String(v),
12283
- label = "Intensity",
12452
+ formatValue: formatValueProp,
12453
+ label: labelProp,
12284
12454
  className,
12285
- emptyLabel = "No activity in this window.",
12455
+ emptyLabel: emptyLabelProp,
12286
12456
  cellGap = 2,
12287
12457
  cellRadius = 2
12288
12458
  }) {
12459
+ const strings = useShellStrings().charts;
12460
+ const locale = useShellLocale();
12461
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
12462
+ const label = labelProp ?? strings.intensity;
12463
+ const emptyLabel = emptyLabelProp ?? strings.noActivity;
12289
12464
  const host = useRef(null);
12290
12465
  const measured = usePlotWidth(host);
12291
12466
  const width = widthProp ?? measured;
@@ -12332,9 +12507,9 @@ function HeatmapChart({
12332
12507
  height: totalHeight,
12333
12508
  viewBox: `0 0 ${width} ${totalHeight}`,
12334
12509
  role: "img",
12335
- "aria-label": `${label} across ${rows.length} rows and ${columns.length} columns`,
12510
+ "aria-label": strings.heatmap(label, rows.length, columns.length),
12336
12511
  tabIndex: 0,
12337
- className: "focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500",
12512
+ className: "rosh-focus-ring focus-visible:ring-2 focus-visible:ring-[color:var(--rosh-focus-ring-deep)]",
12338
12513
  onMouseLeave: () => setHover(null),
12339
12514
  onBlur: () => setHover(null),
12340
12515
  onKeyDown: onKey,
@@ -12399,11 +12574,15 @@ function RadarChart({
12399
12574
  mode = "area",
12400
12575
  grid = "polygon",
12401
12576
  rings = 4,
12402
- formatValue = (v) => String(v),
12577
+ formatValue: formatValueProp,
12403
12578
  animate = true,
12404
12579
  className,
12405
- emptyLabel = "Nothing to compare."
12580
+ emptyLabel: emptyLabelProp
12406
12581
  }) {
12582
+ const strings = useShellStrings().charts;
12583
+ const locale = useShellLocale();
12584
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
12585
+ const emptyLabel = emptyLabelProp ?? strings.nothingToCompare;
12407
12586
  const titleId = useId();
12408
12587
  const [hover, setHover] = useState(null);
12409
12588
  const { highlighted } = useHighlight();
@@ -12438,7 +12617,7 @@ function RadarChart({
12438
12617
  "aria-labelledby": titleId,
12439
12618
  onMouseLeave: () => setHover(null),
12440
12619
  children: [
12441
- /* @__PURE__ */ jsx("title", { id: titleId, children: `${drawn.map((s) => s.label).join(", ")} across ${axes.length} measures` }),
12620
+ /* @__PURE__ */ jsx("title", { id: titleId, children: strings.acrossMeasures(drawn.map((s) => s.label), axes.length) }),
12442
12621
  Array.from({ length: rings }, (_, r) => {
12443
12622
  const rr = radius * (r + 1) / rings;
12444
12623
  return grid === "circle" ? /* @__PURE__ */ jsx("circle", { cx, cy, r: rr, fill: "none", stroke: CHART_INK.grid, strokeWidth: 1 }, r) : /* @__PURE__ */ jsx("path", { d: ring(polygonPoints(cx, cy, rr, axes.length)), fill: "none", stroke: CHART_INK.grid, strokeWidth: 1 }, r);
@@ -12501,8 +12680,8 @@ function RadarChart({
12501
12680
  /* @__PURE__ */ jsx(
12502
12681
  AccessibleTable,
12503
12682
  {
12504
- caption: `${drawn.map((s) => s.label).join(", ")} across ${axes.length} measures`,
12505
- head: ["Series", ...axes.map((axis) => axis.label)],
12683
+ caption: strings.acrossMeasures(drawn.map((s) => s.label), axes.length),
12684
+ head: [strings.tableSeries, ...axes.map((axis) => axis.label)],
12506
12685
  rows: drawn.map((s) => [s.label, ...axes.map((_, i) => formatValue(s.values[i] ?? 0))])
12507
12686
  }
12508
12687
  )
@@ -12515,15 +12694,20 @@ function PieChart({
12515
12694
  innerRadius,
12516
12695
  padAngle = 0.012,
12517
12696
  maxSegments = 6,
12518
- otherLabel = "Other",
12519
- formatValue = (v) => String(v),
12697
+ otherLabel: otherLabelProp,
12698
+ formatValue: formatValueProp,
12520
12699
  centerLabel,
12521
12700
  animate = true,
12522
12701
  labels = "none",
12523
12702
  labelMinShare = 0.04,
12524
12703
  className,
12525
- emptyLabel = "Nothing to break down."
12704
+ emptyLabel: emptyLabelProp
12526
12705
  }) {
12706
+ const strings = useShellStrings().charts;
12707
+ const locale = useShellLocale();
12708
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
12709
+ const otherLabel = otherLabelProp ?? strings.other;
12710
+ const emptyLabel = emptyLabelProp ?? strings.nothingToBreakDown;
12527
12711
  const titleId = useId();
12528
12712
  const [hover, setHover] = useState(null);
12529
12713
  const { highlighted } = useHighlight();
@@ -12587,7 +12771,7 @@ function PieChart({
12587
12771
  "aria-labelledby": titleId,
12588
12772
  onMouseLeave: () => setHover(null),
12589
12773
  children: [
12590
- /* @__PURE__ */ jsx("title", { id: titleId, children: `Breakdown of ${formatValue(total)} across ${shown.length} segments` }),
12774
+ /* @__PURE__ */ jsx("title", { id: titleId, children: strings.breakdown(formatValue(total), shown.length) }),
12591
12775
  shown.map((segment, i) => {
12592
12776
  const colour = resolveSeriesColor(i, segment.color, segment.tone);
12593
12777
  const start = variant === "rose" ? angle(i) : cursor;
@@ -12644,7 +12828,7 @@ function PieChart({
12644
12828
  fill: CHART_INK.label,
12645
12829
  textAnchor: entry.right ? "end" : "start",
12646
12830
  style: { fontVariantNumeric: "tabular-nums" },
12647
- children: `${(entry.share * 100).toFixed(1)}%`
12831
+ children: formatPercent(entry.share * 100, 1, locale)
12648
12832
  }
12649
12833
  )
12650
12834
  ] }, `lbl-${entry.key}`);
@@ -12658,18 +12842,15 @@ function PieChart({
12658
12842
  {
12659
12843
  title: hoveredSegment.label,
12660
12844
  rows: [
12661
- { key: "value", label: "Value", color: resolveSeriesColor(hover, hoveredSegment.color, hoveredSegment.tone), value: formatValue(hoveredSegment.value) },
12662
- { key: "share", label: "Share", color: CHART_INK.muted, value: `${(hoveredSegment.value / total * 100).toFixed(1)}%` }
12845
+ { key: "value", label: strings.value, color: resolveSeriesColor(hover, hoveredSegment.color, hoveredSegment.tone), value: formatValue(hoveredSegment.value) },
12846
+ { key: "share", label: strings.share, color: CHART_INK.muted, value: formatPercent(hoveredSegment.value / total * 100, 1, locale) }
12663
12847
  ]
12664
12848
  }
12665
12849
  ) }),
12666
12850
  /* @__PURE__ */ jsx("ul", { className: "m-0 mt-3 grid gap-1 p-0", style: { gridTemplateColumns: "repeat(auto-fit,minmax(140px,1fr))" }, children: shown.map((segment, i) => /* @__PURE__ */ jsxs("li", { className: "flex list-none items-center gap-2 text-xs text-gray-600", children: [
12667
12851
  /* @__PURE__ */ jsx("span", { className: "h-2.5 w-2.5 shrink-0 rounded-sm", style: { backgroundColor: resolveSeriesColor(i, segment.color, segment.tone) } }),
12668
12852
  /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: segment.label }),
12669
- /* @__PURE__ */ jsxs("span", { className: "tabular-nums text-gray-800", children: [
12670
- (segment.value / total * 100).toFixed(1),
12671
- "%"
12672
- ] })
12853
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums text-gray-800", children: formatPercent(segment.value / total * 100, 1, locale) })
12673
12854
  ] }, segment.key)) })
12674
12855
  ] });
12675
12856
  }
@@ -12679,12 +12860,16 @@ function RadialBarChart({
12679
12860
  variant = "track",
12680
12861
  size = 300,
12681
12862
  max,
12682
- formatValue = (v) => String(v),
12863
+ formatValue: formatValueProp,
12683
12864
  animate = true,
12684
12865
  className,
12685
- emptyLabel = "Nothing to plot.",
12866
+ emptyLabel: emptyLabelProp,
12686
12867
  trackGap = 6
12687
12868
  }) {
12869
+ const strings = useShellStrings().charts;
12870
+ const locale = useShellLocale();
12871
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
12872
+ const emptyLabel = emptyLabelProp ?? strings.nothingToPlot;
12688
12873
  const titleId = useId();
12689
12874
  const [hover, setHover] = useState(null);
12690
12875
  const { highlighted } = useHighlight();
@@ -12715,7 +12900,7 @@ function RadialBarChart({
12715
12900
  "aria-labelledby": titleId,
12716
12901
  onMouseLeave: () => setHover(null),
12717
12902
  children: [
12718
- /* @__PURE__ */ jsx("title", { id: titleId, children: `${drawnRows.length} categories, ${variant === "track" ? "as concentric proportions" : "around a cycle"}` }),
12903
+ /* @__PURE__ */ jsx("title", { id: titleId, children: variant === "track" ? strings.radialTrack(drawnRows.length) : strings.radialCycle(drawnRows.length) }),
12719
12904
  drawnRows.map((row, i) => {
12720
12905
  const colour = resolveSeriesColor(i, row.color, row.tone);
12721
12906
  const share = Math.max(0, Math.min(1, row.value / top));
@@ -12755,7 +12940,7 @@ function RadialBarChart({
12755
12940
  ChartTooltip,
12756
12941
  {
12757
12942
  title: hoveredRow.label,
12758
- rows: [{ key: "value", label: "Value", color: resolveSeriesColor(hover, hoveredRow.color, hoveredRow.tone), value: formatValue(hoveredRow.value) }]
12943
+ rows: [{ key: "value", label: strings.value, color: resolveSeriesColor(hover, hoveredRow.color, hoveredRow.tone), value: formatValue(hoveredRow.value) }]
12759
12944
  }
12760
12945
  ) }),
12761
12946
  /* @__PURE__ */ jsx("ul", { className: "m-0 mt-3 grid gap-1 p-0", style: { gridTemplateColumns: "repeat(auto-fit,minmax(150px,1fr))" }, children: rows.map((row, i) => /* @__PURE__ */ jsxs("li", { className: "flex list-none items-center gap-2 text-xs text-gray-600", children: [
@@ -12771,11 +12956,15 @@ function FunnelChart({
12771
12956
  height,
12772
12957
  width = 560,
12773
12958
  gap = 4,
12774
- formatValue = (v) => String(v),
12959
+ formatValue: formatValueProp,
12775
12960
  color,
12776
12961
  className,
12777
- emptyLabel = "No stages to chart."
12962
+ emptyLabel: emptyLabelProp
12778
12963
  }) {
12964
+ const strings = useShellStrings().charts;
12965
+ const locale = useShellLocale();
12966
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
12967
+ const emptyLabel = emptyLabelProp ?? strings.noStages;
12779
12968
  const titleId = useId();
12780
12969
  const [hover, setHover] = useState(null);
12781
12970
  const { highlighted } = useHighlight();
@@ -12808,7 +12997,7 @@ function FunnelChart({
12808
12997
  "aria-labelledby": titleId,
12809
12998
  onMouseLeave: () => setHover(null),
12810
12999
  children: [
12811
- /* @__PURE__ */ jsx("title", { id: titleId, children: `Funnel of ${stages.length} stages, ${formatValue(stages[0].value)} down to ${formatValue(stages[stages.length - 1].value)}` }),
13000
+ /* @__PURE__ */ jsx("title", { id: titleId, children: strings.funnel(stages.length, formatValue(stages[0].value), formatValue(stages[stages.length - 1].value)) }),
12812
13001
  stages.map((stage, i) => {
12813
13002
  const y0 = i * total / stages.length + gap / 2;
12814
13003
  const y1 = (i + 1) * total / stages.length - gap / 2;
@@ -12827,12 +13016,9 @@ function FunnelChart({
12827
13016
  /* @__PURE__ */ jsx("text", { x: labelWidth - 14, y: (y0 + y1) / 2 - 2, textAnchor: "end", fontSize: 12, fill: CHART_INK.label, children: stage.label }),
12828
13017
  /* @__PURE__ */ jsxs("text", { x: labelWidth - 14, y: (y0 + y1) / 2 + 14, textAnchor: "end", fontSize: 11, fill: CHART_INK.muted, style: { fontVariantNumeric: "tabular-nums" }, children: [
12829
13018
  formatValue(stage.value),
12830
- dropped != null ? ` \xB7 \u2212${dropped.toFixed(1)}%` : ""
13019
+ dropped != null ? ` \xB7 \u2212${formatPercent(dropped, 1, locale)}` : ""
12831
13020
  ] }),
12832
- /* @__PURE__ */ jsxs("text", { x: cx, y: (y0 + y1) / 2 + 4, textAnchor: "middle", fontSize: 11.5, fontWeight: 600, fill: bandInk(i), style: { fontVariantNumeric: "tabular-nums" }, children: [
12833
- (stage.value / (stages[0].value || 1) * 100).toFixed(0),
12834
- "%"
12835
- ] })
13021
+ /* @__PURE__ */ jsx("text", { x: cx, y: (y0 + y1) / 2 + 4, textAnchor: "middle", fontSize: 11.5, fontWeight: 600, fill: bandInk(i), style: { fontVariantNumeric: "tabular-nums" }, children: formatPercent(stage.value / (stages[0].value || 1) * 100, 0, locale) })
12836
13022
  ] }, stage.key);
12837
13023
  })
12838
13024
  ]
@@ -12843,9 +13029,9 @@ function FunnelChart({
12843
13029
  {
12844
13030
  title: hoveredStage.label,
12845
13031
  rows: [
12846
- { key: "value", label: "Count", color: shade(hover), value: formatValue(hoveredStage.value) },
12847
- { key: "share", label: "Of first stage", color: CHART_INK.muted, value: `${(hoveredStage.value / (stages[0].value || 1) * 100).toFixed(0)}%` },
12848
- ...hover > 0 && stages[hover - 1].value > 0 ? [{ key: "drop", label: "Dropped here", color: STATUS_VARS.critical, value: `\u2212${((1 - hoveredStage.value / stages[hover - 1].value) * 100).toFixed(1)}%` }] : []
13032
+ { key: "value", label: strings.count, color: shade(hover), value: formatValue(hoveredStage.value) },
13033
+ { key: "share", label: strings.ofFirstStage, color: CHART_INK.muted, value: formatPercent(hoveredStage.value / (stages[0].value || 1) * 100, 0, locale) },
13034
+ ...hover > 0 && stages[hover - 1].value > 0 ? [{ key: "drop", label: strings.droppedHere, color: STATUS_VARS.critical, value: `\u2212${formatPercent((1 - hoveredStage.value / stages[hover - 1].value) * 100, 1, locale)}` }] : []
12849
13035
  ]
12850
13036
  }
12851
13037
  ) })
@@ -12858,11 +13044,15 @@ function TreemapChart({
12858
13044
  width: widthProp,
12859
13045
  gap = 2,
12860
13046
  radius = 3,
12861
- formatValue = (v) => String(v),
13047
+ formatValue: formatValueProp,
12862
13048
  color,
12863
13049
  className,
12864
- emptyLabel = "Nothing to break down."
13050
+ emptyLabel: emptyLabelProp
12865
13051
  }) {
13052
+ const strings = useShellStrings().charts;
13053
+ const locale = useShellLocale();
13054
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
13055
+ const emptyLabel = emptyLabelProp ?? strings.nothingToBreakDown;
12866
13056
  const host = useRef(null);
12867
13057
  const measured = usePlotWidth(host);
12868
13058
  const width = widthProp ?? measured;
@@ -12887,7 +13077,7 @@ function TreemapChart({
12887
13077
  height,
12888
13078
  viewBox: `0 0 ${width} ${height}`,
12889
13079
  role: "img",
12890
- "aria-label": `Treemap of ${items.length} items`,
13080
+ "aria-label": strings.treemap(items.length),
12891
13081
  onMouseLeave: () => setHover(null),
12892
13082
  children: tiles.map((tile) => {
12893
13083
  const w = Math.max(0, tile.width - gap);
@@ -12916,14 +13106,14 @@ function TreemapChart({
12916
13106
  ChartTooltip,
12917
13107
  {
12918
13108
  title: hoveredTile.label,
12919
- rows: [{ key: "value", label: "Value", color: shade(hoveredTile.value), value: formatValue(hoveredTile.value) }]
13109
+ rows: [{ key: "value", label: strings.value, color: shade(hoveredTile.value), value: formatValue(hoveredTile.value) }]
12920
13110
  }
12921
13111
  ) }),
12922
13112
  /* @__PURE__ */ jsx(
12923
13113
  AccessibleTable,
12924
13114
  {
12925
- caption: `Treemap of ${items.length} items`,
12926
- head: ["Item", "Value"],
13115
+ caption: strings.treemap(items.length),
13116
+ head: [strings.tableItem, strings.value],
12927
13117
  rows: tiles.map((tile) => [tile.label, formatValue(tile.value)])
12928
13118
  }
12929
13119
  )
@@ -12964,11 +13154,15 @@ function SunburstChart({
12964
13154
  size = 320,
12965
13155
  innerRadius = 44,
12966
13156
  ringGap = 2,
12967
- formatValue = (v) => String(v),
13157
+ formatValue: formatValueProp,
12968
13158
  centerLabel,
12969
13159
  className,
12970
- emptyLabel = "Nothing to break down."
13160
+ emptyLabel: emptyLabelProp
12971
13161
  }) {
13162
+ const strings = useShellStrings().charts;
13163
+ const locale = useShellLocale();
13164
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
13165
+ const emptyLabel = emptyLabelProp ?? strings.nothingToBreakDown;
12972
13166
  const [hover, setHover] = useState(null);
12973
13167
  const { highlighted } = useHighlight();
12974
13168
  const wedges = [];
@@ -12987,7 +13181,7 @@ function SunburstChart({
12987
13181
  return true;
12988
13182
  };
12989
13183
  const hovered = hover ? wedges.find((w) => w.id === hover) : void 0;
12990
- const share = (w) => `${(w.value / total * 100).toFixed(1)}%`;
13184
+ const share = (w) => formatPercent(w.value / total * 100, 1, locale);
12991
13185
  return /* @__PURE__ */ jsxs("div", { className: `relative ${className ?? ""}`, children: [
12992
13186
  /* @__PURE__ */ jsxs(
12993
13187
  "svg",
@@ -12996,7 +13190,7 @@ function SunburstChart({
12996
13190
  height: size,
12997
13191
  viewBox: `0 0 ${size} ${size}`,
12998
13192
  role: "img",
12999
- "aria-label": `Hierarchy of ${formatValue(total)} across ${depth} levels`,
13193
+ "aria-label": strings.sunburst(formatValue(total), depth),
13000
13194
  onMouseLeave: () => setHover(null),
13001
13195
  children: [
13002
13196
  wedges.map((w) => /* @__PURE__ */ jsx(
@@ -13020,16 +13214,16 @@ function SunburstChart({
13020
13214
  {
13021
13215
  title: hovered.label,
13022
13216
  rows: [
13023
- { key: "value", label: "Value", color: shade(hovered), value: formatValue(hovered.value) },
13024
- { key: "share", label: "Share", color: CHART_INK.muted, value: share(hovered) }
13217
+ { key: "value", label: strings.value, color: shade(hovered), value: formatValue(hovered.value) },
13218
+ { key: "share", label: strings.share, color: CHART_INK.muted, value: share(hovered) }
13025
13219
  ]
13026
13220
  }
13027
13221
  ) }),
13028
13222
  /* @__PURE__ */ jsx(
13029
13223
  AccessibleTable,
13030
13224
  {
13031
- caption: `Hierarchy of ${formatValue(total)} across ${depth} levels`,
13032
- head: ["Level", "Item", "Value", "Share"],
13225
+ caption: strings.sunburst(formatValue(total), depth),
13226
+ head: [strings.tableLevel, strings.tableItem, strings.value, strings.share],
13033
13227
  rows: wedges.map((w) => [w.depth + 1, w.label, formatValue(w.value), share(w)])
13034
13228
  }
13035
13229
  )
@@ -13042,10 +13236,14 @@ function SankeyChart({
13042
13236
  width: widthProp,
13043
13237
  nodeWidth = 14,
13044
13238
  nodePadding = 12,
13045
- formatValue = (v) => String(v),
13239
+ formatValue: formatValueProp,
13046
13240
  className,
13047
- emptyLabel = "No flows to chart."
13241
+ emptyLabel: emptyLabelProp
13048
13242
  }) {
13243
+ const strings = useShellStrings().charts;
13244
+ const locale = useShellLocale();
13245
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
13246
+ const emptyLabel = emptyLabelProp ?? strings.noFlows;
13049
13247
  const host = useRef(null);
13050
13248
  const measured = usePlotWidth(host);
13051
13249
  const width = widthProp ?? measured;
@@ -13103,7 +13301,7 @@ function SankeyChart({
13103
13301
  height,
13104
13302
  viewBox: `0 0 ${width} ${height}`,
13105
13303
  role: "img",
13106
- "aria-label": `Flow between ${nodes.length} nodes over ${links.length} links`,
13304
+ "aria-label": strings.sankey(nodes.length, links.length),
13107
13305
  onMouseLeave: () => setHover(null),
13108
13306
  children: [
13109
13307
  links.map((link, i) => {
@@ -13162,21 +13360,21 @@ function SankeyChart({
13162
13360
  ChartTooltip,
13163
13361
  {
13164
13362
  title: `${hoveredLink.from} \u2192 ${hoveredLink.to}`,
13165
- rows: [{ key: "value", label: "Flow", color: resolveSeriesColor(placed.get(hoveredLink.from)?.index ?? 0, hoveredLink.color), value: formatValue(hoveredLink.value) }]
13363
+ rows: [{ key: "value", label: strings.flow, color: resolveSeriesColor(placed.get(hoveredLink.from)?.index ?? 0, hoveredLink.color), value: formatValue(hoveredLink.value) }]
13166
13364
  }
13167
13365
  ) }),
13168
13366
  hoveredNode && /* @__PURE__ */ jsx("div", { className: "absolute top-2 left-2 z-10", children: /* @__PURE__ */ jsx(
13169
13367
  ChartTooltip,
13170
13368
  {
13171
13369
  title: hoveredNode.label,
13172
- rows: [{ key: "value", label: "Throughput", color: resolveSeriesColor(placed.get(hoveredNode.key)?.index ?? 0, hoveredNode.color, hoveredNode.tone), value: formatValue(throughput.get(hoveredNode.key) ?? 0) }]
13370
+ rows: [{ key: "value", label: strings.throughput, color: resolveSeriesColor(placed.get(hoveredNode.key)?.index ?? 0, hoveredNode.color, hoveredNode.tone), value: formatValue(throughput.get(hoveredNode.key) ?? 0) }]
13173
13371
  }
13174
13372
  ) }),
13175
13373
  /* @__PURE__ */ jsx(
13176
13374
  AccessibleTable,
13177
13375
  {
13178
- caption: `Flow between ${nodes.length} nodes over ${links.length} links`,
13179
- head: ["From", "To", "Value"],
13376
+ caption: strings.sankey(nodes.length, links.length),
13377
+ head: [strings.tableFrom, strings.tableTo, strings.value],
13180
13378
  rows: links.map((link) => [link.from, link.to, formatValue(link.value)])
13181
13379
  }
13182
13380
  )
@@ -13189,10 +13387,14 @@ function ChordChart({
13189
13387
  maxNodes = 8,
13190
13388
  padAngle = 0.04,
13191
13389
  arcWidth = 12,
13192
- formatValue = (v) => String(v),
13390
+ formatValue: formatValueProp,
13193
13391
  className,
13194
- emptyLabel = "No flows to chart."
13392
+ emptyLabel: emptyLabelProp
13195
13393
  }) {
13394
+ const strings = useShellStrings().charts;
13395
+ const locale = useShellLocale();
13396
+ const formatValue = formatValueProp ?? ((v) => formatPlain(v, locale));
13397
+ const emptyLabel = emptyLabelProp ?? strings.noFlows;
13196
13398
  const [hover, setHover] = useState(null);
13197
13399
  const { highlighted } = useHighlight();
13198
13400
  const cell = (row, col) => {
@@ -13253,7 +13455,7 @@ function ChordChart({
13253
13455
  height: size,
13254
13456
  viewBox: `0 0 ${size} ${size}`,
13255
13457
  role: "img",
13256
- "aria-label": `Flows between ${order.length} participants`,
13458
+ "aria-label": strings.chord(order.length),
13257
13459
  onMouseLeave: () => setHover(null),
13258
13460
  children: [
13259
13461
  ribbons.map((ribbon, i) => {
@@ -13309,21 +13511,21 @@ function ChordChart({
13309
13511
  ChartTooltip,
13310
13512
  {
13311
13513
  title: labels[hoveredArc],
13312
- rows: [{ key: "total", label: "Total flow", color: resolveSeriesColor(spans.get(hoveredArc).slot), value: formatValue(totals[hoveredArc]) }]
13514
+ rows: [{ key: "total", label: strings.totalFlow, color: resolveSeriesColor(spans.get(hoveredArc).slot), value: formatValue(totals[hoveredArc]) }]
13313
13515
  }
13314
13516
  ) }),
13315
13517
  hoveredRibbon && /* @__PURE__ */ jsx("div", { className: "absolute top-2 left-2 z-10", children: /* @__PURE__ */ jsx(
13316
13518
  ChartTooltip,
13317
13519
  {
13318
13520
  title: `${labels[hoveredRibbon.from]} \u2192 ${labels[hoveredRibbon.to]}`,
13319
- rows: [{ key: "value", label: "Flow", color: resolveSeriesColor(spans.get(hoveredRibbon.from).slot), value: formatValue(hoveredRibbon.value) }]
13521
+ rows: [{ key: "value", label: strings.flow, color: resolveSeriesColor(spans.get(hoveredRibbon.from).slot), value: formatValue(hoveredRibbon.value) }]
13320
13522
  }
13321
13523
  ) }),
13322
13524
  /* @__PURE__ */ jsx(
13323
13525
  AccessibleTable,
13324
13526
  {
13325
- caption: `Flows between ${order.length} participants`,
13326
- head: ["From", "To", "Value"],
13527
+ caption: strings.chord(order.length),
13528
+ head: [strings.tableFrom, strings.tableTo, strings.value],
13327
13529
  rows: ribbons.map((ribbon) => [labels[ribbon.from], labels[ribbon.to], formatValue(ribbon.value)])
13328
13530
  }
13329
13531
  )
@@ -13331,5 +13533,5 @@ function ChordChart({
13331
13533
  }
13332
13534
 
13333
13535
  export { APP_UPDATE_POLL_MS, Accordion, AppUpdateBanner, AttachButton, AttachmentDropZone, AttachmentList, AuthScreen, Avatar, AvatarGroup, Banner, BarChart, BoxPlotChart, BrandAssetEditor, BudgetBar, CHART_INK, Calendar, CandlestickChart, Card, CartesianPlot, ChartBrush, ChartDefs, ChartDot, ChartFrame, ChartHighlightProvider, ChartSkeleton, ChartTooltip, ChatTemplate, Checkbox_default, CheckoutTemplate, ChordChart, ColoredBadge, ColumnChart, ComposerAttachments, ContainerFillChart, CountBadge, DAY_MS, DashboardTemplate, DataTable, DataTablePage, DatePicker_default, DateRangePicker, DateTimePicker_default, DescriptionList, Divider, DonutChart, Drawer, DropdownMenu, EmailTemplate, EmptyState, ErrorBoundary, ErrorPage, FilePicker_default, FormErrorSummary, FormField, FormLayoutPage, FunnelChart, GROUP_COLORS, GROUP_COLORS_SOLID, GalleryTemplate, GlobalSearch, Grid, HeatmapChart, HelpCenter, HistogramChart, INPUT_BASE, INPUT_SIZES, IconButton_default, Inline, InputNumber_default, Input_default, Kanban, Label, LineChart, ListFooter, ListLoadError, MAX_FRACTION_DIGITS, MENU_MAX_HEIGHT, MOTION, Markdown, MediaUploadField, MediaUploadGrid, Meter, MetricBar, MilestoneTimeline, NativeSelect, NumericKeypad, POPUP_MAX_WIDTH, POST_PRODUCTION_STATUSES, PageHeader, Pagination, PaperclipGlyph, Paragraph, PieChart, PortalBrandingProvider, ProductionTimeline, RADAR_SERIES_CAP, RadarChart, RadialBarChart, RadioGroup, Radio_default, RangeChart, RankedBars, SCATTER_SERIES_CAP, SERIES_SLOT_COUNT, SERIES_VARS, STAGES, STATUS_VARS, SankeyChart, ScatterChart, SearchableSelect, Segmented, Select_default, SettingRow, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, Skeleton, Snippet, Sparkline, Stack, StatCard, StatTile, Statistic, StatusBadge, StatusBadgeProvider, Stepper, SunburstChart, Switch_default, Tabs, TagInput, Text, Textarea_default, TileButton_default, TimePicker_default, TimeSeriesChart, TimelineTrack, Title, Tooltip, TopNav, TreemapChart, WaterfallChart, angleScale, appendKey, applyThemePrefs, arcPath, areaBetween, areaFrom, autoHighlightIndex, backspace, bandScale, binValues, budgetState, bumpPath, calcOverall, calcReportOverall, copyToClipboard, curvePath, dashFor, fillFor, fillId, firstEnabledIndex, fmtSliderDate, formatDate, fromKey, glowId, groundId, groupColors, highlightOpacity, inputClasses, isSeverityTone, ladderScale, lastEnabledIndex, linearPath, linearScale, logScale, maskId, matchTypeahead, mediaFileName, monotonePath, nextEnabledIndex, niceMax, polygonPoints, popupBounds, radiusScale, resolvePortalBrandAsset, resolveSeriesColor, resolveTheme, selectionAt, seriesColor, severityOf, splinePath, squarify, stagger, stepPath, tabButtonId, tabPanelId, toDayMs, toISODate, toKey, useAppUpdate, useClickOutside, useDropdownPosition, useHighlight, usePlotWidth, usePortalBranding, useProductionTimeline, useTheme };
13334
- //# sourceMappingURL=chunk-WK7FV7VV.js.map
13335
- //# sourceMappingURL=chunk-WK7FV7VV.js.map
13536
+ //# sourceMappingURL=chunk-BJ34QOR3.js.map
13537
+ //# sourceMappingURL=chunk-BJ34QOR3.js.map