@seed-ship/mcp-ui-solid 6.19.0 → 6.20.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 (262) hide show
  1. package/CHANGELOG.md +532 -0
  2. package/README.md +798 -1
  3. package/dist/adapters/connector.cjs +15 -4
  4. package/dist/adapters/connector.cjs.map +1 -1
  5. package/dist/adapters/connector.d.ts +38 -0
  6. package/dist/adapters/connector.d.ts.map +1 -1
  7. package/dist/adapters/connector.js +15 -4
  8. package/dist/adapters/connector.js.map +1 -1
  9. package/dist/adapters/index.d.ts +4 -3
  10. package/dist/adapters/index.d.ts.map +1 -1
  11. package/dist/adapters/macro-run.cjs +25 -13
  12. package/dist/adapters/macro-run.cjs.map +1 -1
  13. package/dist/adapters/macro-run.d.ts +37 -2
  14. package/dist/adapters/macro-run.d.ts.map +1 -1
  15. package/dist/adapters/macro-run.js +25 -13
  16. package/dist/adapters/macro-run.js.map +1 -1
  17. package/dist/adapters.cjs +2 -0
  18. package/dist/adapters.cjs.map +1 -1
  19. package/dist/adapters.js +4 -2
  20. package/dist/components/ActionGroupRenderer.cjs +3 -1
  21. package/dist/components/ActionGroupRenderer.cjs.map +1 -1
  22. package/dist/components/ActionGroupRenderer.d.ts.map +1 -1
  23. package/dist/components/ActionGroupRenderer.js +3 -1
  24. package/dist/components/ActionGroupRenderer.js.map +1 -1
  25. package/dist/components/AgentCard.cjs +11 -8
  26. package/dist/components/AgentCard.cjs.map +1 -1
  27. package/dist/components/AgentCard.d.ts.map +1 -1
  28. package/dist/components/AgentCard.js +11 -8
  29. package/dist/components/AgentCard.js.map +1 -1
  30. package/dist/components/AgentHandoff.cjs +6 -1
  31. package/dist/components/AgentHandoff.cjs.map +1 -1
  32. package/dist/components/AgentHandoff.d.ts.map +1 -1
  33. package/dist/components/AgentHandoff.js +6 -1
  34. package/dist/components/AgentHandoff.js.map +1 -1
  35. package/dist/components/ArtifactRenderer.cjs +25 -6
  36. package/dist/components/ArtifactRenderer.cjs.map +1 -1
  37. package/dist/components/ArtifactRenderer.d.ts.map +1 -1
  38. package/dist/components/ArtifactRenderer.js +25 -6
  39. package/dist/components/ArtifactRenderer.js.map +1 -1
  40. package/dist/components/AutocompleteDropdown.cjs +30 -24
  41. package/dist/components/AutocompleteDropdown.cjs.map +1 -1
  42. package/dist/components/AutocompleteDropdown.d.ts.map +1 -1
  43. package/dist/components/AutocompleteDropdown.js +30 -24
  44. package/dist/components/AutocompleteDropdown.js.map +1 -1
  45. package/dist/components/AutocompleteFormField.cjs +3 -1
  46. package/dist/components/AutocompleteFormField.cjs.map +1 -1
  47. package/dist/components/AutocompleteFormField.d.ts.map +1 -1
  48. package/dist/components/AutocompleteFormField.js +4 -2
  49. package/dist/components/AutocompleteFormField.js.map +1 -1
  50. package/dist/components/BriefingDiff.cjs +45 -39
  51. package/dist/components/BriefingDiff.cjs.map +1 -1
  52. package/dist/components/BriefingDiff.d.ts.map +1 -1
  53. package/dist/components/BriefingDiff.js +45 -39
  54. package/dist/components/BriefingDiff.js.map +1 -1
  55. package/dist/components/CarouselRenderer.cjs +8 -2
  56. package/dist/components/CarouselRenderer.cjs.map +1 -1
  57. package/dist/components/CarouselRenderer.d.ts.map +1 -1
  58. package/dist/components/CarouselRenderer.js +8 -2
  59. package/dist/components/CarouselRenderer.js.map +1 -1
  60. package/dist/components/ChartJSRenderer.cjs +60 -36
  61. package/dist/components/ChartJSRenderer.cjs.map +1 -1
  62. package/dist/components/ChartJSRenderer.d.ts.map +1 -1
  63. package/dist/components/ChartJSRenderer.js +61 -37
  64. package/dist/components/ChartJSRenderer.js.map +1 -1
  65. package/dist/components/ChatPrompt.cjs +15 -9
  66. package/dist/components/ChatPrompt.cjs.map +1 -1
  67. package/dist/components/ChatPrompt.d.ts.map +1 -1
  68. package/dist/components/ChatPrompt.js +15 -9
  69. package/dist/components/ChatPrompt.js.map +1 -1
  70. package/dist/components/CodeBlockRenderer.cjs +34 -16
  71. package/dist/components/CodeBlockRenderer.cjs.map +1 -1
  72. package/dist/components/CodeBlockRenderer.d.ts.map +1 -1
  73. package/dist/components/CodeBlockRenderer.js +34 -16
  74. package/dist/components/CodeBlockRenderer.js.map +1 -1
  75. package/dist/components/DataPreviewSection.cjs +91 -44
  76. package/dist/components/DataPreviewSection.cjs.map +1 -1
  77. package/dist/components/DataPreviewSection.d.ts.map +1 -1
  78. package/dist/components/DataPreviewSection.js +92 -45
  79. package/dist/components/DataPreviewSection.js.map +1 -1
  80. package/dist/components/DegradedFallback.cjs +20 -16
  81. package/dist/components/DegradedFallback.cjs.map +1 -1
  82. package/dist/components/DegradedFallback.d.ts.map +1 -1
  83. package/dist/components/DegradedFallback.js +20 -16
  84. package/dist/components/DegradedFallback.js.map +1 -1
  85. package/dist/components/FeedbackInline.cjs +19 -3
  86. package/dist/components/FeedbackInline.cjs.map +1 -1
  87. package/dist/components/FeedbackInline.js +19 -3
  88. package/dist/components/FeedbackInline.js.map +1 -1
  89. package/dist/components/FooterRenderer.cjs +21 -17
  90. package/dist/components/FooterRenderer.cjs.map +1 -1
  91. package/dist/components/FooterRenderer.d.ts.map +1 -1
  92. package/dist/components/FooterRenderer.js +21 -17
  93. package/dist/components/FooterRenderer.js.map +1 -1
  94. package/dist/components/FormFieldRenderer.cjs +116 -93
  95. package/dist/components/FormFieldRenderer.cjs.map +1 -1
  96. package/dist/components/FormFieldRenderer.d.ts.map +1 -1
  97. package/dist/components/FormFieldRenderer.js +116 -93
  98. package/dist/components/FormFieldRenderer.js.map +1 -1
  99. package/dist/components/FormRenderer.cjs +55 -31
  100. package/dist/components/FormRenderer.cjs.map +1 -1
  101. package/dist/components/FormRenderer.d.ts.map +1 -1
  102. package/dist/components/FormRenderer.js +55 -31
  103. package/dist/components/FormRenderer.js.map +1 -1
  104. package/dist/components/GenerativeUIErrorBoundary.cjs +20 -15
  105. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  106. package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
  107. package/dist/components/GenerativeUIErrorBoundary.js +20 -15
  108. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  109. package/dist/components/GraphRenderer.cjs +56 -33
  110. package/dist/components/GraphRenderer.cjs.map +1 -1
  111. package/dist/components/GraphRenderer.d.ts.map +1 -1
  112. package/dist/components/GraphRenderer.js +56 -33
  113. package/dist/components/GraphRenderer.js.map +1 -1
  114. package/dist/components/ImageGalleryRenderer.cjs +13 -4
  115. package/dist/components/ImageGalleryRenderer.cjs.map +1 -1
  116. package/dist/components/ImageGalleryRenderer.d.ts.map +1 -1
  117. package/dist/components/ImageGalleryRenderer.js +13 -4
  118. package/dist/components/ImageGalleryRenderer.js.map +1 -1
  119. package/dist/components/LightboxOverlay.cjs +16 -8
  120. package/dist/components/LightboxOverlay.cjs.map +1 -1
  121. package/dist/components/LightboxOverlay.d.ts.map +1 -1
  122. package/dist/components/LightboxOverlay.js +17 -9
  123. package/dist/components/LightboxOverlay.js.map +1 -1
  124. package/dist/components/MapRenderer.cjs +59 -29
  125. package/dist/components/MapRenderer.cjs.map +1 -1
  126. package/dist/components/MapRenderer.d.ts +10 -2
  127. package/dist/components/MapRenderer.d.ts.map +1 -1
  128. package/dist/components/MapRenderer.js +60 -30
  129. package/dist/components/MapRenderer.js.map +1 -1
  130. package/dist/components/ModalRenderer.cjs +4 -1
  131. package/dist/components/ModalRenderer.cjs.map +1 -1
  132. package/dist/components/ModalRenderer.d.ts.map +1 -1
  133. package/dist/components/ModalRenderer.js +5 -2
  134. package/dist/components/ModalRenderer.js.map +1 -1
  135. package/dist/components/ScratchpadPanel.cjs +578 -535
  136. package/dist/components/ScratchpadPanel.cjs.map +1 -1
  137. package/dist/components/ScratchpadPanel.d.ts.map +1 -1
  138. package/dist/components/ScratchpadPanel.js +578 -535
  139. package/dist/components/ScratchpadPanel.js.map +1 -1
  140. package/dist/components/StreamingUIRenderer.cjs +92 -28
  141. package/dist/components/StreamingUIRenderer.cjs.map +1 -1
  142. package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
  143. package/dist/components/StreamingUIRenderer.js +92 -28
  144. package/dist/components/StreamingUIRenderer.js.map +1 -1
  145. package/dist/components/UIResourceRenderer.cjs +456 -363
  146. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  147. package/dist/components/UIResourceRenderer.d.ts +21 -0
  148. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  149. package/dist/components/UIResourceRenderer.js +458 -365
  150. package/dist/components/UIResourceRenderer.js.map +1 -1
  151. package/dist/components/VerifiedText.cjs +39 -20
  152. package/dist/components/VerifiedText.cjs.map +1 -1
  153. package/dist/components/VerifiedText.d.ts +2 -1
  154. package/dist/components/VerifiedText.d.ts.map +1 -1
  155. package/dist/components/VerifiedText.js +39 -20
  156. package/dist/components/VerifiedText.js.map +1 -1
  157. package/dist/components/VideoRenderer.cjs +10 -5
  158. package/dist/components/VideoRenderer.cjs.map +1 -1
  159. package/dist/components/VideoRenderer.d.ts.map +1 -1
  160. package/dist/components/VideoRenderer.js +10 -5
  161. package/dist/components/VideoRenderer.js.map +1 -1
  162. package/dist/components/chart-data-table.cjs +38 -17
  163. package/dist/components/chart-data-table.cjs.map +1 -1
  164. package/dist/components/chart-data-table.d.ts +30 -1
  165. package/dist/components/chart-data-table.d.ts.map +1 -1
  166. package/dist/components/chart-data-table.js +38 -17
  167. package/dist/components/chart-data-table.js.map +1 -1
  168. package/dist/context/MCPActionContext.cjs +3 -1
  169. package/dist/context/MCPActionContext.cjs.map +1 -1
  170. package/dist/context/MCPActionContext.d.ts.map +1 -1
  171. package/dist/context/MCPActionContext.js +3 -1
  172. package/dist/context/MCPActionContext.js.map +1 -1
  173. package/dist/context/MCPUIStringsContext.cjs +285 -2
  174. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  175. package/dist/context/MCPUIStringsContext.d.ts +600 -3
  176. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  177. package/dist/context/MCPUIStringsContext.js +285 -2
  178. package/dist/context/MCPUIStringsContext.js.map +1 -1
  179. package/dist/hooks/index.d.ts +2 -2
  180. package/dist/hooks/index.d.ts.map +1 -1
  181. package/dist/hooks/useStreamingUI.cjs +28 -10
  182. package/dist/hooks/useStreamingUI.cjs.map +1 -1
  183. package/dist/hooks/useStreamingUI.d.ts +37 -0
  184. package/dist/hooks/useStreamingUI.d.ts.map +1 -1
  185. package/dist/hooks/useStreamingUI.js +28 -10
  186. package/dist/hooks/useStreamingUI.js.map +1 -1
  187. package/dist/hooks.cjs +1 -0
  188. package/dist/hooks.cjs.map +1 -1
  189. package/dist/hooks.js +2 -1
  190. package/dist/index.cjs +23 -8
  191. package/dist/index.cjs.map +1 -1
  192. package/dist/index.d.cts +9 -3
  193. package/dist/index.d.ts +9 -3
  194. package/dist/index.d.ts.map +1 -1
  195. package/dist/index.js +18 -3
  196. package/dist/index.js.map +1 -1
  197. package/dist/services/validation.cjs +41 -16
  198. package/dist/services/validation.cjs.map +1 -1
  199. package/dist/services/validation.d.ts +47 -2
  200. package/dist/services/validation.d.ts.map +1 -1
  201. package/dist/services/validation.js +41 -16
  202. package/dist/services/validation.js.map +1 -1
  203. package/dist/utils/degraded-projections.cjs +37 -12
  204. package/dist/utils/degraded-projections.cjs.map +1 -1
  205. package/dist/utils/degraded-projections.d.ts +39 -3
  206. package/dist/utils/degraded-projections.d.ts.map +1 -1
  207. package/dist/utils/degraded-projections.js +37 -12
  208. package/dist/utils/degraded-projections.js.map +1 -1
  209. package/dist/utils/format-string.cjs +10 -0
  210. package/dist/utils/format-string.cjs.map +1 -0
  211. package/dist/utils/format-string.d.ts +26 -0
  212. package/dist/utils/format-string.d.ts.map +1 -0
  213. package/dist/utils/format-string.js +10 -0
  214. package/dist/utils/format-string.js.map +1 -0
  215. package/dist/validation.cjs +3 -0
  216. package/dist/validation.cjs.map +1 -1
  217. package/dist/validation.d.cts +2 -1
  218. package/dist/validation.d.ts +2 -1
  219. package/dist/validation.d.ts.map +1 -1
  220. package/dist/validation.js +4 -1
  221. package/package.json +1 -1
  222. package/src/adapters/connector.ts +58 -4
  223. package/src/adapters/index.ts +12 -2
  224. package/src/adapters/macro-run.ts +71 -11
  225. package/src/components/ActionGroupRenderer.tsx +3 -1
  226. package/src/components/AgentCard.tsx +27 -9
  227. package/src/components/AgentHandoff.tsx +4 -1
  228. package/src/components/ArtifactRenderer.tsx +17 -5
  229. package/src/components/AutocompleteDropdown.tsx +8 -6
  230. package/src/components/AutocompleteFormField.tsx +5 -1
  231. package/src/components/BriefingDiff.tsx +11 -3
  232. package/src/components/CarouselRenderer.tsx +4 -2
  233. package/src/components/ChartJSRenderer.tsx +18 -9
  234. package/src/components/ChatPrompt.tsx +13 -8
  235. package/src/components/CodeBlockRenderer.tsx +12 -10
  236. package/src/components/DataPreviewSection.tsx +92 -26
  237. package/src/components/DegradedFallback.tsx +4 -2
  238. package/src/components/FeedbackInline.tsx +2 -2
  239. package/src/components/FooterRenderer.tsx +3 -1
  240. package/src/components/FormFieldRenderer.tsx +34 -19
  241. package/src/components/FormRenderer.tsx +38 -10
  242. package/src/components/GenerativeUIErrorBoundary.tsx +8 -3
  243. package/src/components/GraphRenderer.tsx +31 -16
  244. package/src/components/ImageGalleryRenderer.tsx +8 -4
  245. package/src/components/LightboxOverlay.tsx +6 -4
  246. package/src/components/MapRenderer.tsx +57 -20
  247. package/src/components/ModalRenderer.tsx +3 -1
  248. package/src/components/ScratchpadPanel.tsx +95 -44
  249. package/src/components/StreamingUIRenderer.tsx +41 -11
  250. package/src/components/UIResourceRenderer.tsx +145 -63
  251. package/src/components/VerifiedText.tsx +19 -9
  252. package/src/components/VideoRenderer.tsx +6 -4
  253. package/src/components/chart-data-table.ts +68 -14
  254. package/src/context/MCPActionContext.tsx +4 -1
  255. package/src/context/MCPUIStringsContext.tsx +1004 -1
  256. package/src/hooks/index.ts +2 -1
  257. package/src/hooks/useStreamingUI.ts +64 -10
  258. package/src/index.ts +23 -0
  259. package/src/services/validation.ts +93 -16
  260. package/src/utils/degraded-projections.ts +75 -12
  261. package/src/utils/format-string.ts +33 -0
  262. package/src/validation.ts +5 -0
@@ -5,8 +5,13 @@
5
5
  * @experimental
6
6
  */
7
7
 
8
- import { createSignal, createMemo, createEffect, For, Show } from 'solid-js'
8
+ import { createSignal, createMemo, createEffect, For, Show, onMount } from 'solid-js'
9
9
  import type { DataPreviewContent, DataPreviewColumn } from '../types/chat-bus'
10
+ import {
11
+ DEFAULT_MCPUI_STRINGS,
12
+ formatMCPUIString,
13
+ useMCPUIStrings,
14
+ } from '../context/MCPUIStringsContext'
10
15
 
11
16
  export interface DataPreviewSectionProps {
12
17
  content: DataPreviewContent
@@ -16,24 +21,62 @@ type SortDir = 'asc' | 'desc' | null
16
21
 
17
22
  // ─── Formatting helpers ─────────────────────────────────────
18
23
 
19
- function formatNumber(value: unknown, format?: string): string {
24
+ /**
25
+ * `locale` — BCP-47 tag (`MCPUIStrings.locale`, default `'en-US'`). Before
26
+ * 6.20.0 these helpers hardcoded `'fr-FR'` / `'fr'`.
27
+ */
28
+ function formatNumber(
29
+ value: unknown,
30
+ format?: string,
31
+ locale: string = DEFAULT_MCPUI_STRINGS.locale
32
+ ): string {
20
33
  if (typeof value !== 'number' || !isFinite(value)) return String(value ?? '')
21
- if (format === 'percent') return `${(value * 100).toFixed(1)}%`
22
- if (format === 'currency') return `${value.toLocaleString('fr-FR')} EUR`
23
- if (Number.isInteger(value)) return value.toLocaleString('fr-FR')
24
- return value.toLocaleString('fr-FR', { maximumFractionDigits: 2 })
34
+ if (format === 'percent') {
35
+ return new Intl.NumberFormat(locale, {
36
+ style: 'percent',
37
+ minimumFractionDigits: 1,
38
+ maximumFractionDigits: 1,
39
+ useGrouping: false,
40
+ }).format(value)
41
+ }
42
+ if (format === 'currency') return `${value.toLocaleString(locale)} EUR`
43
+ if (Number.isInteger(value)) return value.toLocaleString(locale)
44
+ return value.toLocaleString(locale, { maximumFractionDigits: 2 })
25
45
  }
26
46
 
27
- function formatCell(value: unknown, col: DataPreviewColumn): string {
47
+ function formatCell(
48
+ value: unknown,
49
+ col: DataPreviewColumn,
50
+ locale: string = DEFAULT_MCPUI_STRINGS.locale,
51
+ /** False before mount: date-time values stay raw so SSR and hydration match. */
52
+ zoneAware = true
53
+ ): string {
28
54
  if (value == null) return '\u2014'
29
- if (col.type === 'number') return formatNumber(value, col.format)
55
+ if (col.type === 'number') return formatNumber(value, col.format, locale)
30
56
  if (col.type === 'date' && typeof value === 'string') {
31
- try { return new Date(value).toLocaleDateString('fr-FR') } catch { return value }
57
+ const date = new Date(value)
58
+ if (Number.isNaN(date.getTime())) return value
59
+ // A date-only value (`2026-09-17`) has no time zone: format it in UTC so
60
+ // every viewer, and the server, shows the same calendar date.
61
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value)
62
+ // A date-time's calendar date depends on the viewer's time zone, which the
63
+ // server cannot know: keep the raw value until the component has mounted.
64
+ if (!dateOnly && !zoneAware) return value
65
+ try {
66
+ return date.toLocaleDateString(locale, dateOnly ? { timeZone: 'UTC' } : undefined)
67
+ } catch {
68
+ return value
69
+ }
32
70
  }
33
71
  return String(value)
34
72
  }
35
73
 
36
- function compareValues(a: unknown, b: unknown, type?: string): number {
74
+ function compareValues(
75
+ a: unknown,
76
+ b: unknown,
77
+ type?: string,
78
+ locale: string = DEFAULT_MCPUI_STRINGS.locale
79
+ ): number {
37
80
  if (a == null && b == null) return 0
38
81
  if (a == null) return 1
39
82
  if (b == null) return -1
@@ -53,7 +96,7 @@ function compareValues(a: unknown, b: unknown, type?: string): number {
53
96
  if (isNaN(db)) return -1
54
97
  return da - db
55
98
  }
56
- return String(a).localeCompare(String(b), 'fr', { sensitivity: 'base' })
99
+ return String(a).localeCompare(String(b), locale, { sensitivity: 'base' })
57
100
  }
58
101
 
59
102
  // ─── Export helpers ─────────────────────────────────────────
@@ -101,6 +144,7 @@ function resolveContent(raw: unknown): DataPreviewContent | null {
101
144
  // ─── Component ──────────────────────────────────────────────
102
145
 
103
146
  export function DataPreviewSection(props: DataPreviewSectionProps) {
147
+ const strings = useMCPUIStrings()
104
148
  const content = createMemo(() => {
105
149
  const resolved = resolveContent(props.content)
106
150
  if (!resolved) {
@@ -115,6 +159,8 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
115
159
  const showPageInfo = () => content()?.showPageInfo !== false
116
160
 
117
161
  const [page, setPage] = createSignal(content()?.initialPage ?? 0)
162
+ const [mounted, setMounted] = createSignal(false)
163
+ onMount(() => setMounted(true))
118
164
  const [sortKey, setSortKey] = createSignal<string | null>(null)
119
165
  const [sortDir, setSortDir] = createSignal<SortDir>(null)
120
166
 
@@ -142,7 +188,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
142
188
  if (!key || !dir) return r
143
189
  const col = columns().find(c => c.key === key)
144
190
  return [...r].sort((a, b) => {
145
- const cmp = compareValues(a[key], b[key], col?.type)
191
+ const cmp = compareValues(a[key], b[key], col?.type, strings.locale)
146
192
  return dir === 'desc' ? -cmp : cmp
147
193
  })
148
194
  })
@@ -186,7 +232,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
186
232
  return (
187
233
  <Show when={content()} fallback={
188
234
  <div class="text-xs text-amber-600 dark:text-amber-400 p-2">
189
- [DataPreviewSection] Invalid content format
235
+ {strings.previewInvalidContent}
190
236
  </div>
191
237
  }>
192
238
  {(c) => (
@@ -208,14 +254,14 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
208
254
  <button
209
255
  class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
210
256
  onClick={handleExportCSV}
211
- title={`Export CSV (${sortedRows().length} rows)`}
257
+ title={formatMCPUIString(strings.exportCsvRows, { count: sortedRows().length })}
212
258
  >
213
259
  CSV
214
260
  </button>
215
261
  <button
216
262
  class="px-2 py-1 text-xs rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
217
263
  onClick={handleExportJSON}
218
- title={`Export JSON (${sortedRows().length} rows)`}
264
+ title={formatMCPUIString(strings.exportJsonRows, { count: sortedRows().length })}
219
265
  >
220
266
  JSON
221
267
  </button>
@@ -234,7 +280,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
234
280
  class="px-3 py-2 font-medium text-xs text-gray-600 dark:text-gray-400 uppercase tracking-wider border-b border-gray-200 dark:border-gray-700 cursor-pointer select-none hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"
235
281
  style={{ "text-align": columnAlign(col) }}
236
282
  onClick={() => handleSort(col.key)}
237
- title={`Sort by ${col.label}`}
283
+ title={formatMCPUIString(strings.sortBy, { column: col.label })}
238
284
  >
239
285
  <span class="inline-flex items-center gap-1">
240
286
  {col.label}
@@ -266,7 +312,7 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
266
312
  class="px-3 py-2 text-gray-800 dark:text-gray-200"
267
313
  style={{ "text-align": columnAlign(col) }}
268
314
  >
269
- {formatCell(row[col.key], col)}
315
+ {formatCell(row[col.key], col, strings.locale, mounted())}
270
316
  </td>
271
317
  )}
272
318
  </For>
@@ -282,13 +328,22 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
282
328
  <Show when={showPageInfo()}>
283
329
  <span>
284
330
  {isPaginated()
285
- ? `Showing ${rangeStart()}\u2013${rangeEnd()} of ${sortedRows().length.toLocaleString('fr-FR')}`
286
- : `${sortedRows().length} row${sortedRows().length !== 1 ? 's' : ''}`
287
- }
331
+ ? formatMCPUIString(strings.previewShowingRange, {
332
+ start: rangeStart().toLocaleString(strings.locale),
333
+ end: rangeEnd().toLocaleString(strings.locale),
334
+ // `strings.locale` formats the count, so the chrome
335
+ // string and the digits agree (and SSR matches the client).
336
+ total: sortedRows().length.toLocaleString(strings.locale),
337
+ })
338
+ : formatMCPUIString(
339
+ sortedRows().length === 1 ? strings.previewRowsOne : strings.previewRowsMany,
340
+ { count: sortedRows().length.toLocaleString(strings.locale) }
341
+ )}
288
342
  {c().totalRows && c().totalRows! > sortedRows().length
289
- ? ` (${c().totalRows!.toLocaleString('fr-FR')} total)`
290
- : ''
291
- }
343
+ ? formatMCPUIString(strings.previewTotalSuffix, {
344
+ total: c().totalRows!.toLocaleString(strings.locale),
345
+ })
346
+ : ''}
292
347
  </span>
293
348
  </Show>
294
349
  <Show when={!showPageInfo()}><span /></Show>
@@ -296,19 +351,30 @@ export function DataPreviewSection(props: DataPreviewSectionProps) {
296
351
  <Show when={isPaginated()}>
297
352
  <div class="flex items-center gap-1">
298
353
  <button
354
+ type="button"
299
355
  class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
300
356
  disabled={page() === 0}
301
357
  onClick={() => setPage(p => p - 1)}
358
+ aria-label={strings.paginationPrevious}
359
+ data-mcp-ui-action="page-prev"
302
360
  >
303
- &#x25C0; Prev
361
+ <span aria-hidden="true">&#x25C0;</span> {strings.previewPrev}
304
362
  </button>
305
- <span class="px-2">Page {page() + 1} / {totalPages()}</span>
363
+ <span class="px-2" aria-live="polite">
364
+ {formatMCPUIString(strings.previewPageIndicator, {
365
+ page: page() + 1,
366
+ total: totalPages(),
367
+ })}
368
+ </span>
306
369
  <button
370
+ type="button"
307
371
  class="px-2 py-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
308
372
  disabled={page() >= totalPages() - 1}
309
373
  onClick={() => setPage(p => p + 1)}
374
+ aria-label={strings.paginationNext}
375
+ data-mcp-ui-action="page-next"
310
376
  >
311
- Next &#x25B6;
377
+ {strings.previewNext} <span aria-hidden="true">&#x25B6;</span>
312
378
  </button>
313
379
  </div>
314
380
  </Show>
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { Component, For, Show } from 'solid-js';
19
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext';
19
20
 
20
21
  export interface DegradedFallbackProps {
21
22
  /** Short, human-readable reason the native render was skipped/failed. */
@@ -37,6 +38,7 @@ export interface DegradedFallbackProps {
37
38
  }
38
39
 
39
40
  export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
41
+ const strings = useMCPUIStrings();
40
42
  const maxRows = () => props.maxRows ?? 50;
41
43
  const allRows = () => props.rows ?? [];
42
44
  const shownRows = () => allRows().slice(0, maxRows());
@@ -50,7 +52,7 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
50
52
  >
51
53
  <p class="text-sm font-medium text-amber-900 dark:text-amber-100">{props.message}</p>
52
54
  <p class="mt-0.5 text-xs text-amber-700 dark:text-amber-300">
53
- {props.caption ?? 'Showing the underlying data — the interactive view is unavailable.'}
55
+ {props.caption ?? strings.degradedCaption}
54
56
  </p>
55
57
 
56
58
  <Show when={hasTable()}>
@@ -84,7 +86,7 @@ export const DegradedFallback: Component<DegradedFallbackProps> = (props) => {
84
86
  </div>
85
87
  <Show when={hiddenCount() > 0}>
86
88
  <p class="mt-1 text-[10px] text-amber-600 dark:text-amber-400">
87
- +{hiddenCount()} more rows not shown.
89
+ {formatMCPUIString(strings.degradedMoreRows, { count: hiddenCount() })}
88
90
  </p>
89
91
  </Show>
90
92
  </Show>
@@ -110,7 +110,7 @@ export const FeedbackInline: Component<FeedbackInlineProps> = (props) => {
110
110
  onClick={() => handle('positive')}
111
111
  class="p-1 rounded hover:bg-green-500/10 text-deposium-slate-500 hover:text-green-500 transition-colors"
112
112
  title={strings.feedbackUseful}
113
- aria-label="Mark response as useful"
113
+ aria-label={strings.feedbackUseful}
114
114
  data-feedback-inline-rating="positive"
115
115
  >
116
116
  <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -127,7 +127,7 @@ export const FeedbackInline: Component<FeedbackInlineProps> = (props) => {
127
127
  onClick={() => handle('negative')}
128
128
  class="p-1 rounded hover:bg-red-500/10 text-deposium-slate-500 hover:text-red-500 transition-colors"
129
129
  title={strings.feedbackNotUseful}
130
- aria-label="Mark response as not useful"
130
+ aria-label={strings.feedbackNotUseful}
131
131
  data-feedback-inline-rating="negative"
132
132
  >
133
133
  <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -1,5 +1,6 @@
1
1
  import { Component, Show, For } from 'solid-js'
2
2
  import { safeUrl } from '../utils/safe-url'
3
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
3
4
 
4
5
  export interface FooterComponentParams {
5
6
  poweredBy?: string
@@ -11,6 +12,7 @@ export interface FooterComponentParams {
11
12
  }
12
13
 
13
14
  export const FooterRenderer: Component<{ params: FooterComponentParams }> = (props) => {
15
+ const strings = useMCPUIStrings()
14
16
  return (
15
17
  <div class="mt-4 pt-3 border-t border-gray-200 dark:border-gray-700 text-xs text-gray-500 dark:text-gray-400 flex flex-wrap items-center gap-4">
16
18
  <Show when={props.params.poweredBy}>
@@ -40,7 +42,7 @@ export const FooterRenderer: Component<{ params: FooterComponentParams }> = (pro
40
42
  <svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
41
43
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
42
44
  </svg>
43
- {props.params.sourceCount} sources
45
+ {formatMCPUIString(strings.footerSources, { count: props.params.sourceCount! })}
44
46
  </span>
45
47
  </Show>
46
48
 
@@ -7,6 +7,7 @@
7
7
  import { Component, Show, For, Switch, Match, Accessor, createSignal, createEffect, onCleanup } from 'solid-js'
8
8
  import type { FormFieldParams, PrefillSource } from '../types'
9
9
  import { useConditionalField } from '../hooks/useConditionalField'
10
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
10
11
 
11
12
  export interface FormFieldRendererProps {
12
13
  field: FormFieldParams
@@ -20,15 +21,24 @@ export interface FormFieldRendererProps {
20
21
  formData?: Accessor<Record<string, any>>
21
22
  }
22
23
 
23
- /** Badge config by prefill source */
24
- const SOURCE_BADGES: Record<PrefillSource, { icon: string; title: string } | null> = {
25
- detected: { icon: '\u2705', title: 'Detected from message' },
26
- inferred: { icon: '\uD83D\uDD17', title: 'Inferred from context' },
27
- user: { icon: '\u270F\uFE0F', title: 'Previously provided' },
24
+ /**
25
+ * Badge config by prefill source. Module-level, so the title cannot be read
26
+ * from the context here — each badge carries the `MCPUIStrings` key instead
27
+ * and the rendering component resolves it at render time.
28
+ */
29
+ const SOURCE_BADGES: Record<
30
+ PrefillSource,
31
+ { icon: string; titleKey: 'sourceDetected' | 'sourceInferred' | 'sourcePrevious' } | null
32
+ > = {
33
+ detected: { icon: '\u2705', titleKey: 'sourceDetected' },
34
+ inferred: { icon: '\uD83D\uDD17', titleKey: 'sourceInferred' },
35
+ user: { icon: '\u270F\uFE0F', titleKey: 'sourcePrevious' },
28
36
  default: null,
29
37
  }
30
38
 
31
39
  export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
40
+ const strings = useMCPUIStrings()
41
+
32
42
  // Conditional visibility based on showWhen
33
43
  const { isVisible } = useConditionalField({
34
44
  condition: props.field.showWhen,
@@ -76,14 +86,14 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
76
86
  class={`block text-sm font-medium ${isUnsupported() ? 'text-gray-400 dark:text-gray-500' : isMuted() ? 'text-gray-400 dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}`}
77
87
  >
78
88
  <Show when={sourceBadge()}>
79
- <span class="mr-1" title={sourceBadge()!.title}>{sourceBadge()!.icon}</span>
89
+ <span class="mr-1" title={strings[sourceBadge()!.titleKey]}>{sourceBadge()!.icon}</span>
80
90
  </Show>
81
91
  {props.field.label}
82
92
  <Show when={props.field.required || status() === 'required'}>
83
93
  <span class="text-red-500 ml-1" aria-hidden="true">*</span>
84
94
  </Show>
85
95
  <Show when={isUnsupported()}>
86
- <span class="ml-2 text-[10px] font-medium bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 px-1.5 py-0.5 rounded">Not supported</span>
96
+ <span class="ml-2 text-[10px] font-medium bg-orange-100 dark:bg-orange-900/30 text-orange-600 dark:text-orange-400 px-1.5 py-0.5 rounded">{strings.fieldNotSupported}</span>
87
97
  </Show>
88
98
  <Show when={status() === 'unknown'}>
89
99
  <span class="ml-2 text-[10px] font-medium bg-yellow-100 dark:bg-yellow-900/30 text-yellow-600 dark:text-yellow-400 px-1.5 py-0.5 rounded">?</span>
@@ -316,7 +326,7 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
316
326
  <Match when={props.field.type === 'fieldset'}>
317
327
  <fieldset class="border border-gray-200 dark:border-gray-600 rounded-lg p-3">
318
328
  <legend class="text-xs font-medium text-gray-500 dark:text-gray-400 px-1">{props.field.label}</legend>
319
- <p class="text-xs text-gray-400">{props.field.helpText || 'Group container'}</p>
329
+ <p class="text-xs text-gray-400">{props.field.helpText || strings.fieldGroupContainer}</p>
320
330
  </fieldset>
321
331
  </Match>
322
332
 
@@ -332,7 +342,9 @@ export const FormFieldRenderer: Component<FormFieldRendererProps> = (props) => {
332
342
  disabled={props.disabled}
333
343
  class={baseInputClass()}
334
344
  />
335
- <p class="text-xs text-amber-500 mt-0.5">Unknown field type: {props.field.type}</p>
345
+ <p class="text-xs text-amber-500 mt-0.5">
346
+ {formatMCPUIString(strings.fieldUnknownType, { type: props.field.type })}
347
+ </p>
336
348
  </Match>
337
349
  </Switch>
338
350
 
@@ -376,6 +388,7 @@ const MultiSelectField: Component<{
376
388
  disabled?: boolean
377
389
  baseClass: string
378
390
  }> = (props) => {
391
+ const strings = useMCPUIStrings()
379
392
  const [open, setOpen] = createSignal(false)
380
393
  const [filter, setFilter] = createSignal('')
381
394
 
@@ -416,7 +429,7 @@ const MultiSelectField: Component<{
416
429
  type="button"
417
430
  onClick={() => removeChip(val)}
418
431
  class="hover:text-blue-900 dark:hover:text-blue-100"
419
- aria-label={`Remove ${getLabel(val)}`}
432
+ aria-label={formatMCPUIString(strings.removeItem, { name: getLabel(val) })}
420
433
  >
421
434
  &times;
422
435
  </button>
@@ -435,8 +448,8 @@ const MultiSelectField: Component<{
435
448
  >
436
449
  <span class={props.value.length ? 'text-gray-900 dark:text-white' : 'text-gray-400'}>
437
450
  {props.value.length
438
- ? `${props.value.length} selected`
439
- : props.field.placeholder || 'Select...'}
451
+ ? formatMCPUIString(strings.fieldSelectedCount, { count: props.value.length })
452
+ : props.field.placeholder || strings.fieldSelectPlaceholder}
440
453
  </span>
441
454
  <svg class="w-4 h-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
442
455
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
@@ -456,7 +469,7 @@ const MultiSelectField: Component<{
456
469
  type="text"
457
470
  value={filter()}
458
471
  onInput={(e) => setFilter(e.currentTarget.value)}
459
- placeholder="Filter..."
472
+ placeholder={strings.filterPlaceholder}
460
473
  class="w-full px-2 py-1 text-sm border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 outline-none"
461
474
  autofocus
462
475
  />
@@ -478,7 +491,7 @@ const MultiSelectField: Component<{
478
491
  )}
479
492
  </For>
480
493
  <Show when={filteredOptions().length === 0}>
481
- <p class="px-3 py-2 text-sm text-gray-400">No matches</p>
494
+ <p class="px-3 py-2 text-sm text-gray-400">{strings.fieldNoMatches}</p>
482
495
  </Show>
483
496
  </div>
484
497
  </div>
@@ -496,6 +509,7 @@ const AutocompleteField: Component<{
496
509
  disabled?: boolean
497
510
  baseClass: string
498
511
  }> = (props) => {
512
+ const strings = useMCPUIStrings()
499
513
  const [query, setQuery] = createSignal('')
500
514
  const [suggestions, setSuggestions] = createSignal<Array<{ label: string; value: string }>>([])
501
515
  const [isOpen, setIsOpen] = createSignal(false)
@@ -669,7 +683,7 @@ const AutocompleteField: Component<{
669
683
  <Show when={resolving()}>
670
684
  <div class="flex items-center gap-1 mb-1 text-xs text-gray-400">
671
685
  <span class="animate-spin h-3 w-3 border border-gray-400 border-t-transparent rounded-full" />
672
- Resolving...
686
+ {strings.fieldResolving}
673
687
  </div>
674
688
  </Show>
675
689
 
@@ -684,7 +698,7 @@ const AutocompleteField: Component<{
684
698
  type="button"
685
699
  onClick={() => removeChip(val)}
686
700
  class="hover:text-blue-900 dark:hover:text-blue-100"
687
- aria-label={`Remove ${getLabel(val)}`}
701
+ aria-label={formatMCPUIString(strings.removeItem, { name: getLabel(val) })}
688
702
  >
689
703
  &times;
690
704
  </button>
@@ -701,7 +715,7 @@ const AutocompleteField: Component<{
701
715
  onFocus={() => { if (suggestions().length) setIsOpen(true) }}
702
716
  onBlur={handleBlur}
703
717
  placeholder={isMultiple() && selectedValues().length
704
- ? 'Add more...'
718
+ ? strings.fieldAddMorePlaceholder
705
719
  : props.field.placeholder}
706
720
  disabled={props.disabled || resolving()}
707
721
  class={props.baseClass}
@@ -746,6 +760,7 @@ const TagsField: Component<{
746
760
  disabled?: boolean
747
761
  baseClass: string
748
762
  }> = (props) => {
763
+ const strings = useMCPUIStrings()
749
764
  const [input, setInput] = createSignal('')
750
765
 
751
766
  const addTag = () => {
@@ -778,7 +793,7 @@ const TagsField: Component<{
778
793
  {(tag) => (
779
794
  <span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 rounded-full">
780
795
  {tag}
781
- <button type="button" onClick={() => removeTag(tag)} class="hover:text-blue-900 dark:hover:text-blue-100" aria-label={`Remove ${tag}`}>&times;</button>
796
+ <button type="button" onClick={() => removeTag(tag)} class="hover:text-blue-900 dark:hover:text-blue-100" aria-label={formatMCPUIString(strings.removeItem, { name: tag })}>&times;</button>
782
797
  </span>
783
798
  )}
784
799
  </For>
@@ -790,7 +805,7 @@ const TagsField: Component<{
790
805
  onInput={(e) => setInput(e.currentTarget.value)}
791
806
  onKeyDown={handleKeyDown}
792
807
  onBlur={addTag}
793
- placeholder={props.placeholder || 'Type and press Enter...'}
808
+ placeholder={props.placeholder || strings.fieldTagsPlaceholder}
794
809
  disabled={props.disabled}
795
810
  class={props.baseClass}
796
811
  />
@@ -13,6 +13,7 @@ import { validateFormData } from '../services/validation'
13
13
  import { evaluateCondition } from '../hooks/useConditionalField'
14
14
  import { useFormPersistence } from '../hooks/useFormPersistence'
15
15
  import { useTelemetry } from '../context/MCPUITelemetryContext'
16
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
16
17
 
17
18
  export interface FormRendererProps {
18
19
  component: UIComponent
@@ -38,6 +39,26 @@ function getFieldDefault(type: FormFieldParams['type']): any {
38
39
  }
39
40
 
40
41
  export const FormRenderer: Component<FormRendererProps> = (props) => {
42
+ const strings = useMCPUIStrings()
43
+ /**
44
+ * The end-user validation wording, read from the chrome strings and
45
+ * injected into the pure validators (which cannot reach the context).
46
+ */
47
+ const validationMessages = () => ({
48
+ required: strings.fieldRequired,
49
+ mustBeChecked: strings.fieldMustBeChecked,
50
+ minLength: strings.fieldMinLength,
51
+ maxLength: strings.fieldMaxLength,
52
+ invalidPattern: strings.fieldInvalidPattern,
53
+ invalidEmail: strings.fieldInvalidEmail,
54
+ invalidNumber: strings.fieldInvalidNumber,
55
+ minValue: strings.fieldMinValue,
56
+ maxValue: strings.fieldMaxValue,
57
+ minDate: strings.fieldMinDate,
58
+ maxDate: strings.fieldMaxDate,
59
+ invalidOption: strings.fieldInvalidOption,
60
+ invalidFormat: strings.fieldInvalidFormat,
61
+ })
41
62
  const params = () => props.component.params as FormComponentParams
42
63
  const [formData, setFormData] = createSignal<Record<string, any>>({})
43
64
  const [errors, setErrors] = createSignal<Record<string, string>>({})
@@ -177,7 +198,7 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
177
198
  }
178
199
 
179
200
  // Validate only visible fields
180
- const validationResult = validateFormData(visibleFormData, visibleFields)
201
+ const validationResult = validateFormData(visibleFormData, visibleFields, validationMessages())
181
202
  if (!validationResult.valid) {
182
203
  setErrors(validationResult.errors)
183
204
  setIsSubmitting(false)
@@ -193,13 +214,14 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
193
214
  formData: visibleFormData,
194
215
  })
195
216
  if (!result.success) {
196
- setErrors({ _form: result.error || 'Submission failed' })
217
+ setErrors({ _form: result.error || strings.formSubmissionFailed })
197
218
  setIsSubmitting(false)
198
- props.onError?.({ _form: result.error || 'Submission failed' })
219
+ props.onError?.({ _form: result.error || strings.formSubmissionFailed })
199
220
  return
200
221
  }
201
222
  } catch (error) {
202
- const errorMessage = error instanceof Error ? error.message : 'Submission failed'
223
+ const errorMessage =
224
+ error instanceof Error ? error.message : strings.formSubmissionFailed
203
225
  setErrors({ _form: errorMessage })
204
226
  setIsSubmitting(false)
205
227
  props.onError?.({ _form: errorMessage })
@@ -255,7 +277,10 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
255
277
  const total = params().fields.length
256
278
  return (
257
279
  <p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
258
- {prefilled} champ{prefilled > 1 ? 's' : ''} pré-rempli{prefilled > 1 ? 's' : ''} sur {total}
280
+ {formatMCPUIString(
281
+ prefilled === 1 ? strings.formPrefilledOne : strings.formPrefilledMany,
282
+ { count: prefilled, total }
283
+ )}
259
284
  </p>
260
285
  )
261
286
  })()}
@@ -287,14 +312,17 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
287
312
  <Show when={countdown() != null}>
288
313
  <div class="mt-4 flex items-center gap-3 p-3 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-md">
289
314
  <span class="text-sm text-blue-700 dark:text-blue-300">
290
- {params().submitLabel || 'Submit'} in {countdown()}s...
315
+ {formatMCPUIString(strings.formSubmitCountdown, {
316
+ label: params().submitLabel || strings.submit,
317
+ seconds: countdown()!,
318
+ })}
291
319
  </span>
292
320
  <button
293
321
  type="button"
294
322
  onClick={() => { cancelCountdown(); setUserInteracted(true) }}
295
323
  class="text-sm text-blue-600 dark:text-blue-400 underline hover:text-blue-800 dark:hover:text-blue-200"
296
324
  >
297
- Cancel
325
+ {strings.cancel}
298
326
  </button>
299
327
  </div>
300
328
  </Show>
@@ -308,10 +336,10 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
308
336
  {isSubmitting() ? (
309
337
  <span class="flex items-center gap-2">
310
338
  <span class="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full" />
311
- Submitting...
339
+ {strings.formSubmitting}
312
340
  </span>
313
341
  ) : (
314
- params().submitLabel || 'Submit'
342
+ params().submitLabel || strings.submit
315
343
  )}
316
344
  </button>
317
345
  <Show when={params().showReset}>
@@ -321,7 +349,7 @@ export const FormRenderer: Component<FormRendererProps> = (props) => {
321
349
  disabled={isSubmitting()}
322
350
  class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
323
351
  >
324
- Reset
352
+ {strings.formReset}
325
353
  </button>
326
354
  </Show>
327
355
  </div>
@@ -15,6 +15,7 @@ import { isServer } from 'solid-js/web'
15
15
  import { createLogger } from '../utils/logger'
16
16
  import type { RendererError, ComponentType } from '../types'
17
17
  import { useTelemetry } from '../context/MCPUITelemetryContext'
18
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
18
19
 
19
20
  const logger = createLogger('generative-ui')
20
21
 
@@ -63,6 +64,7 @@ function DefaultErrorFallback(props: {
63
64
  allowRetry?: boolean
64
65
  onRetry?: () => void
65
66
  }) {
67
+ const strings = useMCPUIStrings()
66
68
  return (
67
69
  <div class="w-full h-full bg-yellow-50 dark:bg-yellow-900/20 border border-yellow-200 dark:border-yellow-800 rounded-lg p-4">
68
70
  <div class="flex items-start gap-3">
@@ -83,10 +85,13 @@ function DefaultErrorFallback(props: {
83
85
  </div>
84
86
  <div class="flex-1 min-w-0">
85
87
  <p class="text-sm font-medium text-yellow-900 dark:text-yellow-100">
86
- Component Failed to Render
88
+ {strings.errorBoundaryTitle}
87
89
  </p>
88
90
  <p class="text-xs text-yellow-700 dark:text-yellow-300 mt-1">
89
- Type: {props.componentType || 'unknown'} | ID: {props.componentId?.slice(0, 8) || 'unknown'}...
91
+ {formatMCPUIString(strings.errorBoundaryMeta, {
92
+ type: props.componentType || strings.unknown,
93
+ id: props.componentId?.slice(0, 8) || strings.unknown,
94
+ })}
90
95
  </p>
91
96
  <Show when={import.meta.env.DEV}>
92
97
  <p class="text-xs text-yellow-600 dark:text-yellow-400 mt-2 font-mono">
@@ -98,7 +103,7 @@ function DefaultErrorFallback(props: {
98
103
  onClick={props.onRetry}
99
104
  class="mt-3 text-xs font-medium text-yellow-800 dark:text-yellow-200 hover:text-yellow-900 dark:hover:text-yellow-100 underline"
100
105
  >
101
- Retry Rendering
106
+ {strings.errorBoundaryRetry}
102
107
  </button>
103
108
  </Show>
104
109
  </div>