@agent-native/core 0.155.0 → 0.157.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 (95) hide show
  1. package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +3 -1
  2. package/corpus/templates/assets/app/i18n-data.ts +21 -1
  3. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +17 -13
  4. package/corpus/templates/clips/app/components/player/share-dialog.tsx +102 -59
  5. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +15 -1
  6. package/corpus/templates/clips/app/routes/share.$shareId.tsx +16 -2
  7. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +98 -29
  8. package/corpus/templates/slides/app/components/editor/EditorActionCluster.tsx +45 -91
  9. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +94 -2
  10. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +17 -29
  11. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +1 -1
  12. package/corpus/templates/slides/app/context/DeckContext.tsx +42 -2
  13. package/corpus/templates/slides/app/global.css +7 -40
  14. package/corpus/templates/slides/app/i18n/en-US.ts +4 -0
  15. package/corpus/templates/slides/app/pages/DeckEditor.tsx +130 -45
  16. package/dist/agent/index.d.ts +1 -1
  17. package/dist/agent/production-agent.d.ts +30 -0
  18. package/dist/agent/production-agent.js +100 -8
  19. package/dist/agent/types.d.ts +9 -0
  20. package/dist/client/AgentPanel.js +12 -9
  21. package/dist/client/AssistantChat.d.ts +15 -1
  22. package/dist/client/AssistantChat.js +110 -43
  23. package/dist/client/MultiTabAssistantChat.js +44 -27
  24. package/dist/client/chat/markdown-renderer.js +4 -2
  25. package/dist/client/chat/message-components.d.ts +2 -2
  26. package/dist/client/chat/message-components.js +69 -35
  27. package/dist/client/chat/run-recovery.d.ts +4 -1
  28. package/dist/client/chat/run-recovery.js +62 -30
  29. package/dist/client/chat/tool-call-display.d.ts +12 -3
  30. package/dist/client/chat/tool-call-display.js +79 -40
  31. package/dist/client/chat/widgets/DataChartRenderer.js +3 -1
  32. package/dist/client/chat/widgets/DataChartWidget.js +8 -2
  33. package/dist/client/chat/widgets/DataInsightsWidget.js +3 -1
  34. package/dist/client/chat/widgets/DataTableWidget.js +23 -9
  35. package/dist/client/chat/widgets/builtin-tool-renderers.js +3 -1
  36. package/dist/client/components/ui/message-scroller.d.ts +1 -1
  37. package/dist/client/components/ui/message-scroller.js +7 -3
  38. package/dist/client/composer/runtime-adapters.js +3 -2
  39. package/dist/client/context-xray/ContextMeter.js +5 -1
  40. package/dist/client/context-xray/ContextXRayPanel.js +5 -5
  41. package/dist/client/error-format.d.ts +4 -0
  42. package/dist/client/error-format.js +93 -5
  43. package/dist/client/i18n.js +83 -17
  44. package/dist/client/observability/ThumbsFeedback.js +10 -8
  45. package/dist/client/sharing/ShareButton.js +160 -54
  46. package/dist/coding-tools/run-code.js +2 -1
  47. package/dist/coding-tools/sandbox/background.d.ts +1 -0
  48. package/dist/coding-tools/sandbox/background.js +8 -1
  49. package/dist/coding-tools/sandbox/executions-store.d.ts +2 -0
  50. package/dist/coding-tools/sandbox/executions-store.js +41 -3
  51. package/dist/collab/routes.d.ts +1 -1
  52. package/dist/index.d.ts +1 -1
  53. package/dist/localization/core-messages/ar-SA.d.ts +3 -0
  54. package/dist/localization/core-messages/ar-SA.js +541 -0
  55. package/dist/localization/core-messages/de-DE.d.ts +3 -0
  56. package/dist/localization/core-messages/de-DE.js +513 -0
  57. package/dist/localization/core-messages/en-US.d.ts +513 -0
  58. package/dist/localization/core-messages/en-US.js +513 -0
  59. package/dist/localization/core-messages/es-ES.d.ts +3 -0
  60. package/dist/localization/core-messages/es-ES.js +520 -0
  61. package/dist/localization/core-messages/fr-FR.d.ts +3 -0
  62. package/dist/localization/core-messages/fr-FR.js +520 -0
  63. package/dist/localization/core-messages/hi-IN.d.ts +3 -0
  64. package/dist/localization/core-messages/hi-IN.js +513 -0
  65. package/dist/localization/core-messages/ja-JP.d.ts +3 -0
  66. package/dist/localization/core-messages/ja-JP.js +506 -0
  67. package/dist/localization/core-messages/ko-KR.d.ts +3 -0
  68. package/dist/localization/core-messages/ko-KR.js +506 -0
  69. package/dist/localization/core-messages/pt-BR.d.ts +3 -0
  70. package/dist/localization/core-messages/pt-BR.js +520 -0
  71. package/dist/localization/core-messages/zh-CN.d.ts +3 -0
  72. package/dist/localization/core-messages/zh-CN.js +506 -0
  73. package/dist/localization/core-messages/zh-TW.d.ts +3 -0
  74. package/dist/localization/core-messages/zh-TW.js +506 -0
  75. package/dist/localization/core-messages.d.ts +524 -0
  76. package/dist/localization/core-messages.js +124 -0
  77. package/dist/localization/default-messages.d.ts +2 -0
  78. package/dist/localization/default-messages.js +2 -0
  79. package/dist/notifications/routes.d.ts +1 -1
  80. package/dist/observability/routes.d.ts +5 -5
  81. package/dist/resources/handlers.d.ts +1 -1
  82. package/dist/secrets/routes.d.ts +9 -9
  83. package/dist/server/agent-chat/plugin-options.d.ts +7 -2
  84. package/dist/server/agent-chat-plugin.d.ts +10 -0
  85. package/dist/server/agent-chat-plugin.js +144 -41
  86. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  87. package/dist/server/agent-run-context.d.ts +7 -1
  88. package/dist/server/agent-run-context.js +9 -2
  89. package/dist/server/agent-teams-run-queue.d.ts +3 -0
  90. package/dist/server/agent-teams.js +38 -9
  91. package/dist/server/index.d.ts +1 -1
  92. package/dist/server/request-context.d.ts +7 -0
  93. package/dist/server/request-context.js +15 -0
  94. package/docs/content/agent-surfaces.mdx +30 -0
  95. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useT } from "../../i18n.js";
2
3
  const DEFAULT_COLORS = [
3
4
  "hsl(var(--primary))",
4
5
  "hsl(199 89% 48%)",
@@ -105,5 +106,6 @@ function ChartBody({ chart }) {
105
106
  return (_jsxs(_Fragment, { children: [axes, paths.map(({ series, color, areaPath }) => chart.type === "area" && areaPath ? (_jsx("path", { d: areaPath, fill: color, opacity: "0.18", stroke: "none" }, series.key)) : null), paths.map(({ series, color, linePath, points }) => (_jsxs("g", { children: [_jsx("path", { d: linePath, fill: "none", stroke: color, strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round" }), points.map((point, index) => (_jsx("circle", { cx: point.x, cy: point.y, r: "3", fill: "hsl(var(--background))", stroke: color, strokeWidth: "2" }, index)))] }, series.key)))] }));
106
107
  }
107
108
  export function DataChartRenderer({ chart }) {
108
- return (_jsx("div", { className: "h-60 w-full min-w-0 overflow-hidden", children: _jsx("svg", { className: "agent-data-chart-renderer h-full w-full", viewBox: `0 0 ${WIDTH} ${HEIGHT}`, role: "img", "aria-label": chart.title ?? "Data chart", preserveAspectRatio: "none", children: _jsx(ChartBody, { chart: chart }) }) }));
109
+ const t = useT();
110
+ return (_jsx("div", { className: "h-60 w-full min-w-0 overflow-hidden", children: _jsx("svg", { className: "agent-data-chart-renderer h-full w-full", viewBox: `0 0 ${WIDTH} ${HEIGHT}`, role: "img", "aria-label": chart.title ?? t("agentChat.widget.dataChart"), preserveAspectRatio: "none", children: _jsx(ChartBody, { chart: chart }) }) }));
109
111
  }
@@ -1,14 +1,20 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconChartBar } from "@tabler/icons-react";
3
3
  import { lazy, Suspense, useEffect, useState } from "react";
4
+ import { DEFAULT_LOCALE, useOptionalLocale, useT } from "../../i18n.js";
4
5
  const LazyDataChartRenderer = lazy(() => import("./DataChartRenderer.js").then((module) => ({
5
6
  default: module.DataChartRenderer,
6
7
  })));
7
8
  export function DataChartWidget({ chart }) {
9
+ const t = useT();
10
+ const locale = useOptionalLocale()?.locale ?? DEFAULT_LOCALE;
8
11
  const [mounted, setMounted] = useState(false);
9
12
  useEffect(() => {
10
13
  setMounted(true);
11
14
  }, []);
12
- const fallback = (_jsx("div", { className: "flex h-60 items-center justify-center rounded-md bg-muted/30 text-xs text-muted-foreground", children: "Chart" }));
13
- return (_jsxs("div", { className: "my-1.5 overflow-hidden rounded-lg border border-border bg-background text-foreground shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2", children: [_jsx(IconChartBar, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium", children: chart.title ?? "Data chart" }), _jsxs("div", { className: "text-[11px] text-muted-foreground", children: [chart.data.length.toLocaleString(), " point", chart.data.length === 1 ? "" : "s", chart.sampled ? " sampled" : ""] })] })] }), _jsx("div", { className: "p-3", children: !mounted ? (fallback) : (_jsx(Suspense, { fallback: fallback, children: _jsx(LazyDataChartRenderer, { chart: chart }) })) })] }));
15
+ const fallback = (_jsx("div", { className: "flex h-60 items-center justify-center rounded-md bg-muted/30 text-xs text-muted-foreground", children: t("agentChat.widget.chart") }));
16
+ return (_jsxs("div", { className: "my-1.5 overflow-hidden rounded-lg border border-border bg-background text-foreground shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2", children: [_jsx(IconChartBar, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium", children: chart.title ?? t("agentChat.widget.dataChart") }), _jsxs("div", { className: "text-[11px] text-muted-foreground", children: [t("agentChat.widget.points", {
17
+ count: chart.data.length,
18
+ formattedCount: chart.data.length.toLocaleString(locale),
19
+ }), chart.sampled ? ` ${t("agentChat.widget.sampled")}` : ""] })] })] }), _jsx("div", { className: "p-3", children: !mounted ? (fallback) : (_jsx(Suspense, { fallback: fallback, children: _jsx(LazyDataChartRenderer, { chart: chart }) })) })] }));
14
20
  }
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { IconDatabase } from "@tabler/icons-react";
3
+ import { useT } from "../../i18n.js";
3
4
  import { DataChartWidget } from "./DataChartWidget.js";
4
5
  import { DataTableWidget } from "./DataTableWidget.js";
5
6
  function SummaryPill({ label, value }) {
@@ -8,7 +9,8 @@ function SummaryPill({ label, value }) {
8
9
  return (_jsxs("div", { className: "rounded-md border border-border bg-muted/30 px-2 py-1", children: [_jsx("div", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: label }), _jsx("div", { className: "text-xs font-medium text-foreground", children: typeof value === "number" ? value.toLocaleString() : String(value) })] }));
9
10
  }
10
11
  export function DataInsightsWidget({ result }) {
11
- const title = result.display?.title ?? result.title ?? "Data insights";
12
+ const t = useT();
13
+ const title = result.display?.title ?? result.title ?? t("agentChat.widget.dataInsights");
12
14
  const summary = result.summary ?? {};
13
15
  return (_jsxs("div", { className: "my-1.5 space-y-2", children: [_jsxs("div", { className: "rounded-lg border border-border bg-background p-3 text-foreground shadow-sm", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx(IconDatabase, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate text-sm font-medium", children: title }), result.display?.description ? (_jsx("div", { className: "text-xs text-muted-foreground", children: result.display.description })) : null] })] }), _jsx("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4", children: Object.entries(summary)
14
16
  .filter(([, value]) => typeof value !== "object")
@@ -4,23 +4,26 @@ import { requestAgentSidebarOpen } from "../../agent-sidebar-state.js";
4
4
  import { appPath } from "../../api-path.js";
5
5
  import { startAgentChatViewTransition } from "../../chat-view-transition.js";
6
6
  import { downloadFile, toCSVTable } from "../../db-admin/export-utils.js";
7
+ import { DEFAULT_LOCALE, useOptionalLocale, useT } from "../../i18n.js";
7
8
  import { cn } from "../../utils.js";
8
9
  const SAFE_ACTION_PROTOCOLS = new Set(["http:", "https:", "mailto:"]);
9
- function formatCell(value) {
10
+ function formatCell(value, locale, booleanLabels = { yes: "Yes", no: "No" }) {
10
11
  if (value === undefined || value === null || value === "")
11
12
  return "—";
12
13
  if (typeof value === "boolean")
13
- return value ? "Yes" : "No";
14
+ return value ? booleanLabels.yes : booleanLabels.no;
14
15
  if (typeof value === "number")
15
- return value.toLocaleString();
16
+ return value.toLocaleString(locale);
16
17
  if (Array.isArray(value))
17
- return value.map(formatCell).join(", ");
18
+ return value
19
+ .map((item) => formatCell(item, locale, booleanLabels))
20
+ .join(", ");
18
21
  if (typeof value === "object")
19
22
  return JSON.stringify(value);
20
23
  const text = String(value);
21
24
  const timestamp = Date.parse(text);
22
25
  if (Number.isFinite(timestamp) && /^\d{4}-\d{2}-\d{2}T/.test(text)) {
23
- return new Intl.DateTimeFormat(undefined, {
26
+ return new Intl.DateTimeFormat(locale, {
24
27
  month: "short",
25
28
  day: "numeric",
26
29
  hour: "numeric",
@@ -83,6 +86,8 @@ function slugifyFilenamePart(value) {
83
86
  return slug || "data-table";
84
87
  }
85
88
  export function DataTableWidget({ table, action, }) {
89
+ const t = useT();
90
+ const locale = useOptionalLocale()?.locale ?? DEFAULT_LOCALE;
86
91
  const rows = table.rows ?? [];
87
92
  const actionHref = action ? normalizeActionHref(action.href) : null;
88
93
  const canExportCsv = table.columns.length > 0;
@@ -92,9 +97,15 @@ export function DataTableWidget({ table, action, }) {
92
97
  const stamp = new Date().toISOString().slice(0, 10);
93
98
  downloadFile(`${slugifyFilenamePart(table.title)}-${stamp}.csv`, "text/csv;charset=utf-8", toCSVTable(table.columns.map((column) => column.label), rows.map((row) => table.columns.map((column) => row[column.key]))));
94
99
  };
95
- return (_jsxs("div", { className: "my-1.5 overflow-hidden rounded-lg border border-border bg-background text-foreground shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2", children: [_jsx(IconTable, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium", children: table.title ?? "Data table" }), _jsxs("div", { className: "text-[11px] text-muted-foreground", children: [typeof table.totalRows === "number"
96
- ? `${table.totalRows.toLocaleString()} row${table.totalRows === 1 ? "" : "s"}`
97
- : `${rows.length.toLocaleString()} row${rows.length === 1 ? "" : "s"}`, table.truncated ? " sampled" : ""] })] }), action && actionHref ? (_jsxs("a", { href: actionHref, onClick: (event) => {
100
+ return (_jsxs("div", { className: "my-1.5 overflow-hidden rounded-lg border border-border bg-background text-foreground shadow-sm", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2", children: [_jsx(IconTable, { className: "h-4 w-4 shrink-0 text-muted-foreground" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "truncate text-sm font-medium", children: table.title ?? t("agentChat.widget.dataTable") }), _jsxs("div", { className: "text-[11px] text-muted-foreground", children: [typeof table.totalRows === "number"
101
+ ? t("agentChat.widget.rows", {
102
+ count: table.totalRows,
103
+ formattedCount: table.totalRows.toLocaleString(locale),
104
+ })
105
+ : t("agentChat.widget.rows", {
106
+ count: rows.length,
107
+ formattedCount: rows.length.toLocaleString(locale),
108
+ }), table.truncated ? ` ${t("agentChat.widget.sampled")}` : ""] })] }), action && actionHref ? (_jsxs("a", { href: actionHref, onClick: (event) => {
98
109
  if (event.defaultPrevented ||
99
110
  event.button !== 0 ||
100
111
  event.metaKey ||
@@ -106,5 +117,8 @@ export function DataTableWidget({ table, action, }) {
106
117
  }
107
118
  event.preventDefault();
108
119
  navigateSameAppHref(actionHref);
109
- }, className: "inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] font-medium text-foreground no-underline hover:bg-muted hover:no-underline", children: [action.label, _jsx(IconExternalLink, { className: "h-3 w-3" })] })) : null, _jsxs("button", { type: "button", onClick: exportCsv, disabled: !canExportCsv, className: "inline-flex shrink-0 items-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50", children: [_jsx(IconDownload, { className: "h-3 w-3" }), "Download CSV"] })] }), _jsx("div", { className: "max-h-[360px] overflow-auto", children: _jsxs("table", { className: "w-full min-w-[420px] border-collapse text-xs", children: [_jsx("thead", { className: "sticky top-0 z-10 bg-muted/70 backdrop-blur", children: _jsx("tr", { children: table.columns.map((column) => (_jsx("th", { className: cn("border-b border-border px-3 py-2 font-medium text-muted-foreground", column.align === "right" ? "text-right" : "text-left"), children: column.label }, column.key))) }) }), _jsx("tbody", { children: rows.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: Math.max(1, table.columns.length), className: "px-3 py-8 text-center text-muted-foreground", children: "No rows" }) })) : (rows.map((row, rowIndex) => (_jsx("tr", { className: "border-b border-border/70 last:border-0", children: table.columns.map((column) => (_jsx("td", { className: cn("max-w-[260px] px-3 py-2 align-top text-foreground", column.align === "right" ? "text-right" : "text-left"), children: _jsx("span", { className: "line-clamp-3 break-words", children: formatCell(row[column.key]) }) }, `${rowIndex}-${column.key}`))) }, String(row.id ?? rowIndex))))) })] }) })] }));
120
+ }, className: "inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] font-medium text-foreground no-underline hover:bg-muted hover:no-underline", children: [action.label, _jsx(IconExternalLink, { className: "h-3 w-3" })] })) : null, _jsxs("button", { type: "button", onClick: exportCsv, disabled: !canExportCsv, className: "inline-flex shrink-0 items-center gap-1 rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50", children: [_jsx(IconDownload, { className: "h-3 w-3" }), t("agentChat.widget.downloadCsv")] })] }), _jsx("div", { className: "max-h-[360px] overflow-auto", children: _jsxs("table", { className: "w-full min-w-[420px] border-collapse text-xs", children: [_jsx("thead", { className: "sticky top-0 z-10 bg-muted/70 backdrop-blur", children: _jsx("tr", { children: table.columns.map((column) => (_jsx("th", { className: cn("border-b border-border px-3 py-2 font-medium text-muted-foreground", column.align === "right" ? "text-right" : "text-left"), children: column.label }, column.key))) }) }), _jsx("tbody", { children: rows.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: Math.max(1, table.columns.length), className: "px-3 py-8 text-center text-muted-foreground", children: t("agentChat.widget.noRows") }) })) : (rows.map((row, rowIndex) => (_jsx("tr", { className: "border-b border-border/70 last:border-0", children: table.columns.map((column) => (_jsx("td", { className: cn("max-w-[260px] px-3 py-2 align-top text-foreground", column.align === "right" ? "text-right" : "text-left"), children: _jsx("span", { className: "line-clamp-3 break-words", children: formatCell(row[column.key], locale, {
121
+ yes: t("agentChat.common.yes"),
122
+ no: t("agentChat.common.no"),
123
+ }) }) }, `${rowIndex}-${column.key}`))) }, String(row.id ?? rowIndex))))) })] }) })] }));
110
124
  }
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { lazy, Suspense } from "react";
3
3
  import { ACTION_CHAT_UI_DATA_CHART_RENDERER, ACTION_CHAT_UI_DATA_INSIGHTS_RENDERER, ACTION_CHAT_UI_DATA_TABLE_RENDERER, ACTION_CHAT_UI_DATA_WIDGET_RENDERER, ACTION_CHAT_UI_INLINE_EXTENSION_RENDERER, ACTION_CHAT_UI_WORKSPACE_FILE_RENDERER, } from "../../../action-ui.js";
4
+ import { useT } from "../../i18n.js";
4
5
  import { registerReservedActionChatRenderer, registerReservedFallbackToolRenderer, } from "../tool-render-registry.js";
5
6
  import { DATA_CHART_WIDGET, DATA_INSIGHTS_WIDGET, DATA_TABLE_WIDGET, normalizeDataWidgetKind, normalizeDataWidgetResult, } from "./data-widget-types.js";
6
7
  import { normalizeInlineExtensionToolResult } from "./inline-extension-result.js";
@@ -80,7 +81,8 @@ function renderDataWidget(context) {
80
81
  return null;
81
82
  }
82
83
  function BuiltinToolRendererSkeleton({ framed = true }) {
83
- return (_jsx("div", { "aria-label": "Loading tool result", className: framed
84
+ const t = useT();
85
+ return (_jsx("div", { "aria-label": t("agentChat.widget.loadingToolResult"), className: framed
84
86
  ? "my-1.5 h-24 animate-pulse rounded-lg border border-border bg-muted/30"
85
87
  : "h-24 animate-pulse bg-muted/30" }));
86
88
  }
@@ -8,7 +8,7 @@ type MessageScrollerItemProps = React.ComponentProps<typeof ShadcnMessageScrolle
8
8
  type MessageScrollerButtonProps = React.ComponentProps<typeof ShadcnMessageScroller.Button>;
9
9
  declare function MessageScrollerProvider(props: MessageScrollerProviderProps): React.JSX.Element;
10
10
  declare function MessageScroller({ className, ...props }: MessageScrollerRootProps): React.JSX.Element;
11
- declare function MessageScrollerViewport({ className, children, ...props }: MessageScrollerViewportProps): React.JSX.Element;
11
+ declare function MessageScrollerViewport({ className, children, "aria-label": ariaLabel, ...props }: MessageScrollerViewportProps): React.JSX.Element;
12
12
  declare function MessageScrollerContent({ className, spacerClassName, ...props }: MessageScrollerContentProps): React.JSX.Element;
13
13
  declare function MessageScrollerItem(props: MessageScrollerItemProps): React.JSX.Element;
14
14
  declare function MessageScrollerButton({ className, children, render, ...props }: MessageScrollerButtonProps): React.JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { MessageScroller as ShadcnMessageScroller, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility, } from "@shadcn/react/message-scroller";
3
3
  import { IconChevronDown } from "@tabler/icons-react";
4
+ import { useT } from "../../i18n.js";
4
5
  import { cn } from "../../utils.js";
5
6
  function MessageScrollerProvider(props) {
6
7
  return _jsx(ShadcnMessageScroller.Provider, { ...props });
@@ -8,12 +9,14 @@ function MessageScrollerProvider(props) {
8
9
  function MessageScroller({ className, ...props }) {
9
10
  return (_jsx(ShadcnMessageScroller.Root, { className: cn("relative flex min-h-0 flex-1 flex-col", className), ...props }));
10
11
  }
11
- function MessageScrollerViewport({ className, children, ...props }) {
12
+ function MessageScrollerViewport({ className, children, "aria-label": ariaLabel, ...props }) {
13
+ const t = useT();
12
14
  const { start: hasContentAbove } = useMessageScrollerScrollable();
13
15
  return (_jsx(ShadcnMessageScroller.Viewport, { className: cn("min-h-0 flex-1 overflow-y-auto overflow-x-hidden",
14
16
  // The base class carries the mask itself so it is never added or
15
17
  // removed; the modifier only widens the fade. See agent-native.css.
16
- "message-scroller-viewport", hasContentAbove && "message-scroller-viewport--top-fade", className), ...props, children: children }));
18
+ "message-scroller-viewport", hasContentAbove && "message-scroller-viewport--top-fade", className), "aria-label": ariaLabel ??
19
+ t("agentChat.message.messages", { defaultValue: "Messages" }), ...props, children: children }));
17
20
  }
18
21
  function MessageScrollerContent({ className, spacerClassName, ...props }) {
19
22
  return (_jsx(ShadcnMessageScroller.Content, { className: cn("flex flex-col", className), spacerClassName: cn("shrink-0", spacerClassName), ...props }));
@@ -22,7 +25,8 @@ function MessageScrollerItem(props) {
22
25
  return _jsx(ShadcnMessageScroller.Item, { ...props });
23
26
  }
24
27
  function MessageScrollerButton({ className, children, render, ...props }) {
28
+ const t = useT();
25
29
  return (_jsx(ShadcnMessageScroller.Button, { render: render ??
26
- ((buttonProps) => (_jsx("div", { className: "shrink-0 flex justify-center -mb-1", children: _jsx("button", { ...buttonProps, className: cn("flex h-7 w-7 items-center justify-center rounded-full border border-border bg-background shadow-sm hover:bg-accent", "data-[active=false]:hidden", className, buttonProps.className), "aria-label": "Scroll to bottom", children: children ?? (_jsx(IconChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })) }) }))), ...props }));
30
+ ((buttonProps) => (_jsx("div", { className: "shrink-0 flex justify-center -mb-1", children: _jsx("button", { ...buttonProps, className: cn("flex h-7 w-7 items-center justify-center rounded-full border border-border bg-background shadow-sm hover:bg-accent", "data-[active=false]:hidden", className, buttonProps.className), "aria-label": t("agentChat.composer.scrollToBottom"), children: children ?? (_jsx(IconChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })) }) }))), ...props }));
27
31
  }
28
32
  export { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility, };
@@ -12,7 +12,7 @@ import { getBrowserTabId } from "../browser-tab-id.js";
12
12
  import { isTrustedBuilderMessage, tryDelegateBuildRequestToBuilder, } from "../builder-frame.js";
13
13
  import { BuilderSetupCard, BuilderSetupContent } from "../chat/run-recovery.js";
14
14
  import { isTrustedFrameMessage } from "../frame.js";
15
- import { useT } from "../i18n.js";
15
+ import { useFormatters, useT } from "../i18n.js";
16
16
  import { useOrg } from "../org/hooks.js";
17
17
  import { isMcpIntegrationCatalogAvailable } from "../resources/mcp-integration-catalog.js";
18
18
  import { McpIntegrationDialog } from "../resources/McpIntegrationDialog.js";
@@ -79,6 +79,7 @@ const coreComposerAdapters = {
79
79
  };
80
80
  export function CoreComposerRuntimeProvider({ children, }) {
81
81
  const translate = useT();
82
- const adapters = useMemo(() => ({ ...coreComposerAdapters, translate }), [translate]);
82
+ const { formatNumber } = useFormatters();
83
+ const adapters = useMemo(() => ({ ...coreComposerAdapters, formatNumber, translate }), [formatNumber, translate]);
83
84
  return (_jsx(ComposerRuntimeAdaptersProvider, { adapters: adapters, children: children }));
84
85
  }
@@ -2,12 +2,14 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { ContextMeterView } from "@agent-native/toolkit/context-ui";
3
3
  import { lazy, Suspense, useEffect, useRef, useState } from "react";
4
4
  import { manifestConversationTokens, manifestSystemTokens, } from "../../shared/context-xray.js";
5
+ import { useT } from "../i18n.js";
5
6
  import { useActionMutation, useActionQuery } from "../use-action.js";
6
7
  import { resolveContextWindow } from "./format.js";
7
8
  const ContextXRayPanel = lazy(() => import("./ContextXRayPanel.js").then((m) => ({
8
9
  default: m.ContextXRayPanel,
9
10
  })));
10
11
  export function ContextMeter({ threadId, manifest: providedManifest, enabled = true, }) {
12
+ const t = useT();
11
13
  const [open, setOpen] = useState(false);
12
14
  const [optimistic, setOptimistic] = useState(new Map());
13
15
  const currentThreadId = useRef(threadId);
@@ -61,5 +63,7 @@ export function ContextMeter({ threadId, manifest: providedManifest, enabled = t
61
63
  ...manifest,
62
64
  systemTokens: manifestSystemTokens(manifest),
63
65
  conversationTokens: manifestConversationTokens(manifest),
64
- }, contextWindow: resolveContextWindow(manifest.model), open: open, onOpenChange: setOpen, children: open ? (_jsx(Suspense, { fallback: _jsx("div", { className: "flex h-52 items-center justify-center text-xs text-muted-foreground", children: "Loading context view\u2026" }), children: _jsx(ContextXRayPanel, { manifest: manifest, optimistic: optimistic, onPin: (segmentId) => mutateStatus(segmentId, "pinned", "pin"), onEvict: (segmentId) => mutateStatus(segmentId, "evicted", "evict"), onRestore: (segmentId) => mutateStatus(segmentId, "active", "restore") }) })) : null }));
66
+ }, contextWindow: resolveContextWindow(manifest.model), open: open, onOpenChange: setOpen, translate: t, children: open ? (_jsx(Suspense, { fallback: _jsx("div", { className: "flex h-52 items-center justify-center text-xs text-muted-foreground", children: t("agentChat.contextXray.loading", {
67
+ defaultValue: "Loading context view…",
68
+ }) }), children: _jsx(ContextXRayPanel, { manifest: manifest, optimistic: optimistic, onPin: (segmentId) => mutateStatus(segmentId, "pinned", "pin"), onEvict: (segmentId) => mutateStatus(segmentId, "evicted", "evict"), onRestore: (segmentId) => mutateStatus(segmentId, "active", "restore") }) })) : null }));
65
69
  }
@@ -9,18 +9,18 @@ export function ContextXRayPanel({ manifest, optimistic, onPin, onEvict, onResto
9
9
  ...manifest,
10
10
  systemTokens: manifestSystemTokens(manifest),
11
11
  conversationTokens: manifestConversationTokens(manifest),
12
- }, contextWindow: resolveContextWindow(manifest.model), optimistic: optimistic, onPin: onPin, onEvict: onEvict, onRestore: onRestore, titleLabel: t("contextXray.panelTitle", {
12
+ }, contextWindow: resolveContextWindow(manifest.model), optimistic: optimistic, onPin: onPin, onEvict: onEvict, onRestore: onRestore, translate: t, titleLabel: t("agentChat.contextXray.panelTitle", {
13
13
  defaultValue: "Context X-Ray",
14
- }), systemOrderedLabel: t("contextXray.systemOrdered", {
14
+ }), systemOrderedLabel: t("agentChat.contextXray.systemOrdered", {
15
15
  defaultValue: "System · ordered, not evictable",
16
16
  }), governanceLabels: {
17
- required: t("contextXray.governance.required", {
17
+ required: t("agentChat.contextXray.governance.required", {
18
18
  defaultValue: "Required",
19
19
  }),
20
- inherited: t("contextXray.governance.inherited", {
20
+ inherited: t("agentChat.contextXray.governance.inherited", {
21
21
  defaultValue: "Inherited",
22
22
  }),
23
- user: t("contextXray.governance.user", {
23
+ user: t("agentChat.contextXray.governance.user", {
24
24
  defaultValue: "Your context",
25
25
  }),
26
26
  } }));
@@ -18,5 +18,9 @@ export interface NormalizedChatError {
18
18
  message: string;
19
19
  details?: string;
20
20
  }
21
+ type ErrorTranslate = (key: string, options?: Record<string, unknown>) => string;
22
+ /** Localize only Core's own normalized error copy; preserve provider details. */
23
+ export declare function localizeKnownChatErrorText(text: string, t: ErrorTranslate): string;
21
24
  export declare function isProviderAuthenticationError(text: string, errorCode?: string): boolean;
22
25
  export declare function normalizeChatError(errorMessage: string, errorCode?: string): NormalizedChatError;
26
+ export {};
@@ -16,6 +16,10 @@ export const BUILDER_SPACE_SETTINGS_URL = "https://builder.io/account/space?utm_
16
16
  // error message. The chat renderer intercepts this href and renders a button
17
17
  // that dispatches the `agent-native:new-chat` CustomEvent instead of navigating.
18
18
  export const NEW_CHAT_ACTION_HREF = "agent-native:new-chat";
19
+ const OPEN_BUILDER_SPACE_SETTINGS_LABEL = "Open Builder space settings";
20
+ const START_NEW_CHAT_LABEL = "Start new chat";
21
+ const UPGRADE_AT_BUILDER_LABEL = "Upgrade at builder.io";
22
+ const BUILDER_AUTHENTICATION_ERROR = "Builder rejected the connected credentials. Reconnect Builder.io (free tier available) in Settings, then retry.";
19
23
  function isSafeUpgradeUrl(url) {
20
24
  try {
21
25
  const parsed = new URL(url);
@@ -31,19 +35,103 @@ export function formatChatErrorText(errorMessage, upgradeUrl, errorCode) {
31
35
  const normalized = normalizeChatError(errorMessage, errorCode);
32
36
  if (errorCode === "gateway_not_enabled" ||
33
37
  /space has not enabled the LLM gateway/i.test(normalized.message)) {
34
- return `Error: ${normalized.message}\n\n[Open Builder space settings](${BUILDER_SPACE_SETTINGS_URL})`;
38
+ return `Error: ${normalized.message}\n\n[${OPEN_BUILDER_SPACE_SETTINGS_LABEL}](${BUILDER_SPACE_SETTINGS_URL})`;
35
39
  }
36
40
  if (errorCode === "builder_gateway_error") {
37
- return `Error: ${normalized.message}\n\n[Start new chat](${NEW_CHAT_ACTION_HREF})`;
41
+ return `Error: ${normalized.message}\n\n[${START_NEW_CHAT_LABEL}](${NEW_CHAT_ACTION_HREF})`;
38
42
  }
39
43
  if (errorCode === "context_length_exceeded" ||
40
44
  errorCode === "input_too_long") {
41
- return `Error: ${normalized.message}\n\n[Start new chat](${NEW_CHAT_ACTION_HREF})`;
45
+ return `Error: ${normalized.message}\n\n[${START_NEW_CHAT_LABEL}](${NEW_CHAT_ACTION_HREF})`;
42
46
  }
43
47
  if (!upgradeUrl || !isSafeUpgradeUrl(upgradeUrl)) {
44
48
  return `Error: ${normalized.message}`;
45
49
  }
46
- return `Error: ${normalized.message}\n\n[Upgrade at builder.io](${upgradeUrl})`;
50
+ return `Error: ${normalized.message}\n\n[${UPGRADE_AT_BUILDER_LABEL}](${upgradeUrl})`;
51
+ }
52
+ const KNOWN_CHAT_ERROR_KEYS = new Map([
53
+ [
54
+ "No LLM provider is connected. Open this app's Manage agent > LLM, then connect Builder.io or add a provider key.",
55
+ "agentChat.errorMessages.noProviderConnected",
56
+ ],
57
+ [
58
+ "The provider behind this model rejected the request. Pick a different model, then retry.",
59
+ "agentChat.errorMessages.builderModelUnauthorized",
60
+ ],
61
+ [
62
+ BUILDER_AUTHENTICATION_ERROR,
63
+ "agentChat.errorMessages.builderAuthentication",
64
+ ],
65
+ [
66
+ "This model can't use tools with the current settings. Switch models in Settings, then retry.",
67
+ "agentChat.errorMessages.providerConfiguration",
68
+ ],
69
+ [
70
+ "The model provider is rate-limiting this chat right now. Wait a moment, then retry.",
71
+ "agentChat.errorMessages.providerRateLimit",
72
+ ],
73
+ [
74
+ "The model provider rejected the saved API key. Update the key in Settings → Integrations → API keys, then retry.",
75
+ "agentChat.errorMessages.providerAuthentication",
76
+ ],
77
+ [
78
+ "The model provider could not be reached. Check your connection and retry.",
79
+ "agentChat.errorMessages.providerNetwork",
80
+ ],
81
+ [
82
+ "The agent connection was interrupted. Check your connection and retry.",
83
+ "agentChat.errorMessages.agentConnection",
84
+ ],
85
+ [
86
+ "The model gateway returned no error details and the chat couldn't recover. Wait a moment and retry, or start a new chat if it keeps happening.",
87
+ "agentChat.errorMessages.gatewayNoDetails",
88
+ ],
89
+ [
90
+ "The agent connection timed out before it could finish. You can continue from the partial work or retry.",
91
+ "agentChat.errorMessages.inactivityTimeout",
92
+ ],
93
+ [
94
+ "A tool schema was invalid, so the model rejected the request before it started. The invalid tool can be skipped and the request retried.",
95
+ "agentChat.errorMessages.invalidToolSchema",
96
+ ],
97
+ [
98
+ "The provider returned an HTML error page.",
99
+ "agentChat.errorMessages.providerHtml",
100
+ ],
101
+ ]);
102
+ const KNOWN_CHAT_ERROR_ACTION_KEYS = new Map([
103
+ [
104
+ "Open Builder space settings",
105
+ "agentChat.errorMessages.openBuilderSpaceSettings",
106
+ ],
107
+ ["Start new chat", "agentChat.errorMessages.startNewChat"],
108
+ ["Upgrade at builder.io", "agentChat.errorMessages.upgradeAtBuilder"],
109
+ ]);
110
+ /** Localize only Core's own normalized error copy; preserve provider details. */
111
+ export function localizeKnownChatErrorText(text, t) {
112
+ const newlineIndex = text.indexOf("\n");
113
+ const firstLine = newlineIndex >= 0 ? text.slice(0, newlineIndex) : text;
114
+ const suffix = newlineIndex >= 0 ? text.slice(newlineIndex) : "";
115
+ const hasPrefix = firstLine.startsWith("Error: ");
116
+ const message = hasPrefix ? firstLine.slice("Error: ".length) : firstLine;
117
+ const key = KNOWN_CHAT_ERROR_KEYS.get(message);
118
+ let localizedFirstLine = firstLine;
119
+ if (key) {
120
+ const translated = t(key, { defaultValue: message });
121
+ localizedFirstLine = hasPrefix
122
+ ? t("agentChat.errorMessages.errorPrefix", {
123
+ defaultValue: "Error: {{message}}",
124
+ message: translated,
125
+ })
126
+ : translated;
127
+ }
128
+ const localizedSuffix = suffix.replace(/\[([^\]]+)]\(([^)]+)\)/g, (link, label, href) => {
129
+ const actionKey = KNOWN_CHAT_ERROR_ACTION_KEYS.get(label);
130
+ if (!actionKey)
131
+ return link;
132
+ return `[${t(actionKey, { defaultValue: label })}](${href})`;
133
+ });
134
+ return `${localizedFirstLine}${localizedSuffix}`;
47
135
  }
48
136
  function normalizeErrorCode(errorCode) {
49
137
  return String(errorCode ?? "")
@@ -124,7 +212,7 @@ export function normalizeChatError(errorMessage, errorCode) {
124
212
  }
125
213
  if (code === "builder_auth_error") {
126
214
  return {
127
- message: "Builder rejected the connected credentials. Reconnect Builder.io (free tier available) in Settings, then retry.",
215
+ message: BUILDER_AUTHENTICATION_ERROR,
128
216
  details: text,
129
217
  };
130
218
  }
@@ -5,6 +5,7 @@ import { IconCheck, IconChevronDown, IconLanguage } from "@tabler/icons-react";
5
5
  import i18next from "i18next";
6
6
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from "react";
7
7
  import { I18nextProvider, initReactI18next, useTranslation, } from "react-i18next";
8
+ import { coreMessagesForLocale, englishAgentChatMessages, loadCoreMessagesForLocale, normalizeCoreMessageOverrides, } from "../localization/core-messages.js";
8
9
  import defaultEnglishMessages from "../localization/default-messages.js";
9
10
  import { DEFAULT_LOCALE, LOCALE_HYDRATION_GLOBAL, LOCALE_METADATA, LOCALE_STORAGE_KEY, SUPPORTED_LOCALES, localeDirection, normalizeLocalizationPreference, resolveLocaleFromCandidates, resolveLocaleFromPreference, } from "../localization/shared.js";
10
11
  import { injectedAgentNativeConfig } from "./app-config.js";
@@ -138,8 +139,7 @@ function resolveInitialState(args) {
138
139
  }
139
140
  function resolveSupportedLocales(args) {
140
141
  const configured = injectedAgentNativeConfig().translations?.locales;
141
- const candidates = configured ??
142
- args.catalog?.supportedLocales ?? [args.sourceLocale];
142
+ const candidates = configured ?? args.catalog?.supportedLocales ?? SUPPORTED_LOCALES;
143
143
  const supported = candidates.filter((locale) => SUPPORTED_LOCALES.includes(locale));
144
144
  return [
145
145
  args.sourceLocale,
@@ -160,16 +160,39 @@ function normalizeLoadedMessages(value) {
160
160
  }
161
161
  return value;
162
162
  }
163
+ function mergeLocaleMessages(base, overrides) {
164
+ if (!overrides)
165
+ return base;
166
+ const merged = { ...base };
167
+ for (const [key, value] of Object.entries(overrides)) {
168
+ const current = merged[key];
169
+ if (current &&
170
+ value &&
171
+ typeof current === "object" &&
172
+ typeof value === "object" &&
173
+ !Array.isArray(current) &&
174
+ !Array.isArray(value)) {
175
+ merged[key] = mergeLocaleMessages(current, value);
176
+ }
177
+ else {
178
+ merged[key] = value;
179
+ }
180
+ }
181
+ return merged;
182
+ }
183
+ function composeLocaleMessages(coreMessages, appMessages) {
184
+ return mergeLocaleMessages(mergeLocaleMessages(defaultEnglishMessages, coreMessages), appMessages ? normalizeCoreMessageOverrides(appMessages) : null);
185
+ }
163
186
  function createI18nInstance(args) {
164
187
  const instance = i18next.createInstance();
165
188
  const resources = {
166
189
  [args.sourceLocale]: {
167
- [args.namespace]: args.messages,
190
+ [args.namespace]: composeLocaleMessages(coreMessagesForLocale(args.sourceLocale), args.messages),
168
191
  },
169
192
  };
170
- if (args.initialLocale !== args.sourceLocale && args.initialMessages) {
193
+ if (args.initialLocale !== args.sourceLocale) {
171
194
  resources[args.initialLocale] = {
172
- [args.namespace]: args.initialMessages,
195
+ [args.namespace]: composeLocaleMessages(coreMessagesForLocale(args.initialLocale), args.initialMessages),
173
196
  };
174
197
  }
175
198
  void instance.use(initReactI18next).init({
@@ -202,24 +225,37 @@ export function AgentNativeI18nProvider({ children, catalog, initialLocale, init
202
225
  const [locale, setLocale] = useState(initialState.locale);
203
226
  const [loading, setLoading] = useState(false);
204
227
  const i18nRef = useRef(null);
228
+ const appMessagesLoadedRef = useRef(null);
229
+ const appMessagesRef = useRef(null);
230
+ const coreMessagesLoadedRef = useRef(null);
205
231
  if (!i18nRef.current) {
206
232
  const preloadedMessages = normalizeLoadedMessages(hydration.locale === initialState.locale && hydration.messages
207
233
  ? hydration.messages
208
234
  : initialMessages);
235
+ const resolvedSourceMessages = normalizeCoreMessageOverrides(hydration.locale === sourceLocale && hydration.messages
236
+ ? hydration.messages
237
+ : sourceMessages);
209
238
  i18nRef.current = createI18nInstance({
210
239
  namespace,
211
240
  sourceLocale,
212
- messages: hydration.locale === sourceLocale && hydration.messages
213
- ? hydration.messages
214
- : sourceMessages,
241
+ messages: resolvedSourceMessages,
215
242
  initialLocale: initialState.locale,
216
243
  initialMessages: preloadedMessages,
217
244
  });
245
+ appMessagesRef.current = new Map([[sourceLocale, resolvedSourceMessages]]);
246
+ appMessagesLoadedRef.current = new Set([sourceLocale]);
247
+ if (initialState.locale !== sourceLocale && preloadedMessages) {
248
+ appMessagesRef.current.set(initialState.locale, normalizeCoreMessageOverrides(preloadedMessages));
249
+ appMessagesLoadedRef.current.add(initialState.locale);
250
+ }
251
+ coreMessagesLoadedRef.current = new Set([sourceLocale, initialState.locale].filter((candidate) => candidate === DEFAULT_LOCALE));
218
252
  }
219
253
  const i18n = i18nRef.current;
220
254
  useEffect(() => {
221
255
  if (i18n.hasResourceBundle(sourceLocale, namespace)) {
222
- i18n.addResourceBundle(sourceLocale, namespace, sourceMessages, true, true);
256
+ const normalizedSourceMessages = normalizeCoreMessageOverrides(sourceMessages);
257
+ appMessagesRef.current?.set(sourceLocale, normalizedSourceMessages);
258
+ i18n.addResourceBundle(sourceLocale, namespace, normalizedSourceMessages, true, true);
223
259
  }
224
260
  }, [i18n, namespace, sourceLocale, sourceMessages]);
225
261
  useEffect(() => {
@@ -233,17 +269,37 @@ export function AgentNativeI18nProvider({ children, catalog, initialLocale, init
233
269
  async function applyLocale() {
234
270
  setLoading(true);
235
271
  try {
236
- if (locale !== sourceLocale &&
237
- !i18n.hasResourceBundle(locale, namespace)) {
272
+ const shouldLoadAppMessages = locale !== sourceLocale && !appMessagesLoadedRef.current?.has(locale);
273
+ const shouldLoadCoreMessages = !coreMessagesLoadedRef.current?.has(locale);
274
+ if (shouldLoadAppMessages || shouldLoadCoreMessages) {
238
275
  const preloaded = hydration.locale === locale && hydration.messages
239
276
  ? hydration.messages
240
277
  : initialLocale === locale && initialMessages
241
278
  ? initialMessages
242
279
  : null;
243
- const loaded = preloaded ?? (await loadMessages?.(locale));
244
- const messages = normalizeLoadedMessages(loaded);
245
- if (messages) {
246
- i18n.addResourceBundle(locale, namespace, messages, true, true);
280
+ const [coreMessages, loadedAppMessages] = await Promise.all([
281
+ shouldLoadCoreMessages
282
+ ? loadCoreMessagesForLocale(locale)
283
+ : Promise.resolve({}),
284
+ shouldLoadAppMessages
285
+ ? Promise.resolve(preloaded ?? loadMessages?.(locale))
286
+ : Promise.resolve(null),
287
+ ]);
288
+ const existingMessages = normalizeLoadedMessages(i18n.getResourceBundle(locale, namespace));
289
+ const loadedMessages = normalizeLoadedMessages(loadedAppMessages);
290
+ if (loadedMessages) {
291
+ appMessagesRef.current?.set(locale, normalizeCoreMessageOverrides(loadedMessages));
292
+ }
293
+ const appMessages = appMessagesRef.current?.get(locale) ?? null;
294
+ const nextMessages = shouldLoadCoreMessages
295
+ ? composeLocaleMessages(coreMessages, appMessages)
296
+ : mergeLocaleMessages(existingMessages ?? {}, appMessages);
297
+ i18n.addResourceBundle(locale, namespace, nextMessages, true, true);
298
+ if (shouldLoadCoreMessages) {
299
+ coreMessagesLoadedRef.current?.add(locale);
300
+ }
301
+ if (shouldLoadAppMessages) {
302
+ appMessagesLoadedRef.current?.add(locale);
247
303
  }
248
304
  }
249
305
  if (!cancelled) {
@@ -369,6 +425,10 @@ export function useOptionalLocale() {
369
425
  return useContext(LocaleContext);
370
426
  }
371
427
  const CORE_FALLBACK_MESSAGES = {
428
+ ...Object.fromEntries(Object.entries(englishAgentChatMessages).map(([key, value]) => [
429
+ `agentChat.${key}`,
430
+ value,
431
+ ])),
372
432
  "runsTray.runs": "Runs",
373
433
  "runsTray.agentRuns": "Agent runs",
374
434
  "runsTray.activeRun_one": "{{count}} active run",
@@ -497,6 +557,12 @@ function fallbackMessage(key, options) {
497
557
  ? interpolateFallbackMessage(template, options)
498
558
  : humanizeFallbackKey(key);
499
559
  }
560
+ function preserveTranslatedUserValues(translated, options) {
561
+ const defaultValue = options?.defaultValue;
562
+ return typeof defaultValue === "string" && translated === defaultValue
563
+ ? interpolateFallbackMessage(defaultValue, options)
564
+ : translated;
565
+ }
500
566
  export function useT() {
501
567
  const { i18n, t } = useTranslation();
502
568
  const context = useContext(LocaleContext);
@@ -504,11 +570,11 @@ export function useT() {
504
570
  return useCallback((key, options) => {
505
571
  const translated = t(key, options);
506
572
  if (translated !== key)
507
- return translated;
573
+ return preserveTranslatedUserValues(translated, options);
508
574
  const getFixedT = i18n.getFixedT;
509
575
  const sourceFallback = getFixedT?.(sourceLocale)(key, options);
510
576
  if (sourceFallback && sourceFallback !== key)
511
- return sourceFallback;
577
+ return preserveTranslatedUserValues(sourceFallback, options);
512
578
  return fallbackMessage(key, options);
513
579
  }, [i18n, sourceLocale, t]);
514
580
  }