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