@seed-ship/mcp-ui-solid 6.19.1 → 6.21.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 (282) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/README.md +911 -4
  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/FooterRenderer.cjs +21 -17
  86. package/dist/components/FooterRenderer.cjs.map +1 -1
  87. package/dist/components/FooterRenderer.d.ts.map +1 -1
  88. package/dist/components/FooterRenderer.js +21 -17
  89. package/dist/components/FooterRenderer.js.map +1 -1
  90. package/dist/components/FormFieldRenderer.cjs +116 -93
  91. package/dist/components/FormFieldRenderer.cjs.map +1 -1
  92. package/dist/components/FormFieldRenderer.d.ts.map +1 -1
  93. package/dist/components/FormFieldRenderer.js +116 -93
  94. package/dist/components/FormFieldRenderer.js.map +1 -1
  95. package/dist/components/FormRenderer.cjs +55 -31
  96. package/dist/components/FormRenderer.cjs.map +1 -1
  97. package/dist/components/FormRenderer.d.ts.map +1 -1
  98. package/dist/components/FormRenderer.js +55 -31
  99. package/dist/components/FormRenderer.js.map +1 -1
  100. package/dist/components/GenerativeUIErrorBoundary.cjs +20 -15
  101. package/dist/components/GenerativeUIErrorBoundary.cjs.map +1 -1
  102. package/dist/components/GenerativeUIErrorBoundary.d.ts.map +1 -1
  103. package/dist/components/GenerativeUIErrorBoundary.js +20 -15
  104. package/dist/components/GenerativeUIErrorBoundary.js.map +1 -1
  105. package/dist/components/GraphRenderer.cjs +56 -33
  106. package/dist/components/GraphRenderer.cjs.map +1 -1
  107. package/dist/components/GraphRenderer.d.ts.map +1 -1
  108. package/dist/components/GraphRenderer.js +56 -33
  109. package/dist/components/GraphRenderer.js.map +1 -1
  110. package/dist/components/IframeFallbackLink.cjs +37 -0
  111. package/dist/components/IframeFallbackLink.cjs.map +1 -0
  112. package/dist/components/IframeFallbackLink.d.ts +23 -0
  113. package/dist/components/IframeFallbackLink.d.ts.map +1 -0
  114. package/dist/components/IframeFallbackLink.js +37 -0
  115. package/dist/components/IframeFallbackLink.js.map +1 -0
  116. package/dist/components/ImageGalleryRenderer.cjs +13 -4
  117. package/dist/components/ImageGalleryRenderer.cjs.map +1 -1
  118. package/dist/components/ImageGalleryRenderer.d.ts.map +1 -1
  119. package/dist/components/ImageGalleryRenderer.js +13 -4
  120. package/dist/components/ImageGalleryRenderer.js.map +1 -1
  121. package/dist/components/LightboxOverlay.cjs +16 -8
  122. package/dist/components/LightboxOverlay.cjs.map +1 -1
  123. package/dist/components/LightboxOverlay.d.ts.map +1 -1
  124. package/dist/components/LightboxOverlay.js +17 -9
  125. package/dist/components/LightboxOverlay.js.map +1 -1
  126. package/dist/components/MapRenderer.cjs +59 -29
  127. package/dist/components/MapRenderer.cjs.map +1 -1
  128. package/dist/components/MapRenderer.d.ts +10 -2
  129. package/dist/components/MapRenderer.d.ts.map +1 -1
  130. package/dist/components/MapRenderer.js +60 -30
  131. package/dist/components/MapRenderer.js.map +1 -1
  132. package/dist/components/ModalRenderer.cjs +4 -1
  133. package/dist/components/ModalRenderer.cjs.map +1 -1
  134. package/dist/components/ModalRenderer.d.ts.map +1 -1
  135. package/dist/components/ModalRenderer.js +5 -2
  136. package/dist/components/ModalRenderer.js.map +1 -1
  137. package/dist/components/ScratchpadPanel.cjs +578 -535
  138. package/dist/components/ScratchpadPanel.cjs.map +1 -1
  139. package/dist/components/ScratchpadPanel.d.ts.map +1 -1
  140. package/dist/components/ScratchpadPanel.js +578 -535
  141. package/dist/components/ScratchpadPanel.js.map +1 -1
  142. package/dist/components/StreamingUIRenderer.cjs +92 -28
  143. package/dist/components/StreamingUIRenderer.cjs.map +1 -1
  144. package/dist/components/StreamingUIRenderer.d.ts.map +1 -1
  145. package/dist/components/StreamingUIRenderer.js +92 -28
  146. package/dist/components/StreamingUIRenderer.js.map +1 -1
  147. package/dist/components/UIResourceRenderer.cjs +481 -365
  148. package/dist/components/UIResourceRenderer.cjs.map +1 -1
  149. package/dist/components/UIResourceRenderer.d.ts +21 -0
  150. package/dist/components/UIResourceRenderer.d.ts.map +1 -1
  151. package/dist/components/UIResourceRenderer.js +484 -368
  152. package/dist/components/UIResourceRenderer.js.map +1 -1
  153. package/dist/components/VerifiedText.cjs +39 -20
  154. package/dist/components/VerifiedText.cjs.map +1 -1
  155. package/dist/components/VerifiedText.d.ts +2 -1
  156. package/dist/components/VerifiedText.d.ts.map +1 -1
  157. package/dist/components/VerifiedText.js +39 -20
  158. package/dist/components/VerifiedText.js.map +1 -1
  159. package/dist/components/VideoRenderer.cjs +51 -20
  160. package/dist/components/VideoRenderer.cjs.map +1 -1
  161. package/dist/components/VideoRenderer.d.ts.map +1 -1
  162. package/dist/components/VideoRenderer.js +51 -20
  163. package/dist/components/VideoRenderer.js.map +1 -1
  164. package/dist/components/chart-data-table.cjs +38 -17
  165. package/dist/components/chart-data-table.cjs.map +1 -1
  166. package/dist/components/chart-data-table.d.ts +30 -1
  167. package/dist/components/chart-data-table.d.ts.map +1 -1
  168. package/dist/components/chart-data-table.js +38 -17
  169. package/dist/components/chart-data-table.js.map +1 -1
  170. package/dist/context/MCPActionContext.cjs +3 -1
  171. package/dist/context/MCPActionContext.cjs.map +1 -1
  172. package/dist/context/MCPActionContext.d.ts.map +1 -1
  173. package/dist/context/MCPActionContext.js +3 -1
  174. package/dist/context/MCPActionContext.js.map +1 -1
  175. package/dist/context/MCPUIConfigContext.cjs +29 -0
  176. package/dist/context/MCPUIConfigContext.cjs.map +1 -0
  177. package/dist/context/MCPUIConfigContext.d.ts +124 -0
  178. package/dist/context/MCPUIConfigContext.d.ts.map +1 -0
  179. package/dist/context/MCPUIConfigContext.js +29 -0
  180. package/dist/context/MCPUIConfigContext.js.map +1 -0
  181. package/dist/context/MCPUIStringsContext.cjs +287 -2
  182. package/dist/context/MCPUIStringsContext.cjs.map +1 -1
  183. package/dist/context/MCPUIStringsContext.d.ts +607 -3
  184. package/dist/context/MCPUIStringsContext.d.ts.map +1 -1
  185. package/dist/context/MCPUIStringsContext.js +287 -2
  186. package/dist/context/MCPUIStringsContext.js.map +1 -1
  187. package/dist/hooks/index.d.ts +2 -2
  188. package/dist/hooks/index.d.ts.map +1 -1
  189. package/dist/hooks/useStreamingUI.cjs +28 -10
  190. package/dist/hooks/useStreamingUI.cjs.map +1 -1
  191. package/dist/hooks/useStreamingUI.d.ts +37 -0
  192. package/dist/hooks/useStreamingUI.d.ts.map +1 -1
  193. package/dist/hooks/useStreamingUI.js +28 -10
  194. package/dist/hooks/useStreamingUI.js.map +1 -1
  195. package/dist/hooks.cjs +1 -0
  196. package/dist/hooks.cjs.map +1 -1
  197. package/dist/hooks.js +2 -1
  198. package/dist/index.cjs +29 -8
  199. package/dist/index.cjs.map +1 -1
  200. package/dist/index.d.cts +12 -4
  201. package/dist/index.d.ts +12 -4
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js +24 -3
  204. package/dist/index.js.map +1 -1
  205. package/dist/services/index.d.ts +1 -1
  206. package/dist/services/index.d.ts.map +1 -1
  207. package/dist/services/validation.cjs +52 -26
  208. package/dist/services/validation.cjs.map +1 -1
  209. package/dist/services/validation.d.ts +67 -2
  210. package/dist/services/validation.d.ts.map +1 -1
  211. package/dist/services/validation.js +52 -26
  212. package/dist/services/validation.js.map +1 -1
  213. package/dist/utils/degraded-projections.cjs +37 -12
  214. package/dist/utils/degraded-projections.cjs.map +1 -1
  215. package/dist/utils/degraded-projections.d.ts +39 -3
  216. package/dist/utils/degraded-projections.d.ts.map +1 -1
  217. package/dist/utils/degraded-projections.js +37 -12
  218. package/dist/utils/degraded-projections.js.map +1 -1
  219. package/dist/utils/format-string.cjs +10 -0
  220. package/dist/utils/format-string.cjs.map +1 -0
  221. package/dist/utils/format-string.d.ts +26 -0
  222. package/dist/utils/format-string.d.ts.map +1 -0
  223. package/dist/utils/format-string.js +10 -0
  224. package/dist/utils/format-string.js.map +1 -0
  225. package/dist/utils/iframe-coep.cjs +30 -0
  226. package/dist/utils/iframe-coep.cjs.map +1 -0
  227. package/dist/utils/iframe-coep.d.ts +46 -0
  228. package/dist/utils/iframe-coep.d.ts.map +1 -0
  229. package/dist/utils/iframe-coep.js +30 -0
  230. package/dist/utils/iframe-coep.js.map +1 -0
  231. package/dist/validation.cjs +5 -0
  232. package/dist/validation.cjs.map +1 -1
  233. package/dist/validation.d.cts +2 -1
  234. package/dist/validation.d.ts +2 -1
  235. package/dist/validation.d.ts.map +1 -1
  236. package/dist/validation.js +6 -1
  237. package/package.json +1 -1
  238. package/src/adapters/connector.ts +58 -4
  239. package/src/adapters/index.ts +12 -2
  240. package/src/adapters/macro-run.ts +71 -11
  241. package/src/components/ActionGroupRenderer.tsx +3 -1
  242. package/src/components/AgentCard.tsx +27 -9
  243. package/src/components/AgentHandoff.tsx +4 -1
  244. package/src/components/ArtifactRenderer.tsx +17 -5
  245. package/src/components/AutocompleteDropdown.tsx +8 -6
  246. package/src/components/AutocompleteFormField.tsx +5 -1
  247. package/src/components/BriefingDiff.tsx +11 -3
  248. package/src/components/CarouselRenderer.tsx +4 -2
  249. package/src/components/ChartJSRenderer.tsx +18 -9
  250. package/src/components/ChatPrompt.tsx +13 -8
  251. package/src/components/CodeBlockRenderer.tsx +12 -10
  252. package/src/components/DataPreviewSection.tsx +92 -26
  253. package/src/components/DegradedFallback.tsx +4 -2
  254. package/src/components/FooterRenderer.tsx +3 -1
  255. package/src/components/FormFieldRenderer.tsx +34 -19
  256. package/src/components/FormRenderer.tsx +38 -10
  257. package/src/components/GenerativeUIErrorBoundary.tsx +8 -3
  258. package/src/components/GraphRenderer.tsx +31 -16
  259. package/src/components/IframeFallbackLink.tsx +57 -0
  260. package/src/components/ImageGalleryRenderer.tsx +8 -4
  261. package/src/components/LightboxOverlay.tsx +6 -4
  262. package/src/components/MapRenderer.tsx +57 -20
  263. package/src/components/ModalRenderer.tsx +3 -1
  264. package/src/components/ScratchpadPanel.tsx +95 -44
  265. package/src/components/StreamingUIRenderer.tsx +41 -11
  266. package/src/components/UIResourceRenderer.tsx +176 -67
  267. package/src/components/VerifiedText.tsx +19 -9
  268. package/src/components/VideoRenderer.tsx +26 -4
  269. package/src/components/chart-data-table.ts +68 -14
  270. package/src/context/MCPActionContext.tsx +4 -1
  271. package/src/context/MCPUIConfigContext.tsx +151 -0
  272. package/src/context/MCPUIStringsContext.tsx +1014 -1
  273. package/src/hooks/index.ts +2 -1
  274. package/src/hooks/useStreamingUI.ts +64 -10
  275. package/src/index.ts +38 -0
  276. package/src/services/index.ts +1 -0
  277. package/src/services/validation.ts +131 -32
  278. package/src/solid-jsx-attrs.d.ts +24 -0
  279. package/src/utils/degraded-projections.ts +75 -12
  280. package/src/utils/format-string.ts +33 -0
  281. package/src/utils/iframe-coep.ts +74 -0
  282. package/src/validation.ts +9 -0
@@ -52,6 +52,7 @@ import type {
52
52
  FormPromptConfig,
53
53
  } from '../types/chat-bus'
54
54
  import { FormFieldRenderer } from './FormFieldRenderer'
55
+ import { useMCPUIStrings } from '../context/MCPUIStringsContext'
55
56
  import type { FormFieldParams } from '../types'
56
57
 
57
58
  export interface ChatPromptProps {
@@ -78,6 +79,8 @@ export interface ChatPromptProps {
78
79
  * />
79
80
  */
80
81
  export const ChatPrompt: Component<ChatPromptProps> = (props) => {
82
+ const strings = useMCPUIStrings()
83
+
81
84
  // F1: Guard against null/undefined config (e.g. after dismiss clears state)
82
85
  if (!props.config) return null
83
86
 
@@ -100,7 +103,7 @@ export const ChatPrompt: Component<ChatPromptProps> = (props) => {
100
103
  ? 'px-3 py-1 text-xs font-medium text-blue-600 dark:text-blue-400 hover:bg-blue-50 dark:hover:bg-blue-900/20 rounded-lg transition-colors'
101
104
  : 'p-1 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors'
102
105
  }
103
- aria-label={props.dismissLabel || 'Dismiss'}
106
+ aria-label={props.dismissLabel || strings.dismiss}
104
107
  >
105
108
  <Show when={props.dismissLabel} fallback={
106
109
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -124,10 +127,10 @@ export const ChatPrompt: Component<ChatPromptProps> = (props) => {
124
127
  <Match when={props.config.type === 'confirm'}>
125
128
  <ConfirmBody
126
129
  config={props.config.config as ConfirmPromptConfig}
127
- onConfirm={() => props.onSubmit({ type: 'confirm', value: 'confirmed', label: (props.config.config as ConfirmPromptConfig).confirmLabel || 'Confirmed' })}
130
+ onConfirm={() => props.onSubmit({ type: 'confirm', value: 'confirmed', label: (props.config.config as ConfirmPromptConfig).confirmLabel || strings.promptConfirmed })}
128
131
  onCancel={() => {
129
132
  props.onDismiss?.()
130
- props.onSubmit({ type: 'confirm', value: 'cancelled', label: (props.config.config as ConfirmPromptConfig).cancelLabel || 'Cancelled', dismissed: true })
133
+ props.onSubmit({ type: 'confirm', value: 'cancelled', label: (props.config.config as ConfirmPromptConfig).cancelLabel || strings.promptCancelled, dismissed: true })
131
134
  }}
132
135
  />
133
136
  </Match>
@@ -213,6 +216,7 @@ const ConfirmBody: Component<{
213
216
  onConfirm: () => void
214
217
  onCancel: () => void
215
218
  }> = (props) => {
219
+ const strings = useMCPUIStrings()
216
220
  const isDanger = () => props.config.variant === 'danger'
217
221
 
218
222
  return (
@@ -225,7 +229,7 @@ const ConfirmBody: Component<{
225
229
  onClick={props.onCancel}
226
230
  class="px-4 py-2 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors"
227
231
  >
228
- {props.config.cancelLabel || 'Cancel'}
232
+ {props.config.cancelLabel || strings.cancel}
229
233
  </button>
230
234
  <button
231
235
  onClick={props.onConfirm}
@@ -235,7 +239,7 @@ const ConfirmBody: Component<{
235
239
  : 'bg-blue-600 hover:bg-blue-700'
236
240
  }`}
237
241
  >
238
- {props.config.confirmLabel || 'Confirm'}
242
+ {props.config.confirmLabel || strings.confirm}
239
243
  </button>
240
244
  </div>
241
245
  </div>
@@ -248,6 +252,7 @@ const FormBody: Component<{
248
252
  config: FormPromptConfig
249
253
  onSubmit: (data: Record<string, unknown>, label: string) => void
250
254
  }> = (props) => {
255
+ const strings = useMCPUIStrings()
251
256
  const [formData, setFormData] = createSignal<Record<string, any>>({})
252
257
  const [dynamicOptions, setDynamicOptions] = createSignal<Record<string, Array<{ label: string; value: string }>>>({})
253
258
  const [previewText, setPreviewText] = createSignal<string>('')
@@ -336,7 +341,7 @@ const FormBody: Component<{
336
341
  .filter(([, v]) => v !== undefined && v !== '' && !(Array.isArray(v) && v.length === 0))
337
342
  .map(([k, v]) => `${k}: ${Array.isArray(v) ? v.join(', ') : v}`)
338
343
  .join(', ')
339
- props.onSubmit(data, label || 'Form submitted')
344
+ props.onSubmit(data, label || strings.promptFormSubmitted)
340
345
  }
341
346
 
342
347
  const isValid = () => {
@@ -379,7 +384,7 @@ const FormBody: Component<{
379
384
  <Show when={previewLoading()} fallback={
380
385
  <p class="text-blue-700 dark:text-blue-300">{previewText()}</p>
381
386
  }>
382
- <p class="text-blue-400 animate-pulse">Loading preview...</p>
387
+ <p class="text-blue-400 animate-pulse">{strings.promptLoadingPreview}</p>
383
388
  </Show>
384
389
  </div>
385
390
  </Show>
@@ -390,7 +395,7 @@ const FormBody: Component<{
390
395
  disabled={!isValid()}
391
396
  class="px-4 py-2 text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed transition-colors"
392
397
  >
393
- {props.config.submitLabel || 'Submit'}
398
+ {props.config.submitLabel || strings.submit}
394
399
  </button>
395
400
  </div>
396
401
  </form>
@@ -10,6 +10,7 @@ import type { UIComponent, CodeComponentParams } from '../types'
10
10
  import { ExpandableWrapper, useExpanded } from './ExpandableWrapper'
11
11
  import { highlightQuery } from './UIResourceRenderer'
12
12
  import { escapeHtml } from '../utils/escape-html'
13
+ import { useMCPUIStrings } from '../context/MCPUIStringsContext'
13
14
 
14
15
  /** Map of `params.language` → file extension for the v6.2.0 download button. */
15
16
  const LANGUAGE_EXTENSIONS: Record<string, string> = {
@@ -45,6 +46,7 @@ export interface CodeBlockRendererProps {
45
46
  }
46
47
 
47
48
  export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
49
+ const strings = useMCPUIStrings()
48
50
  const [highlightedCode, setHighlightedCode] = createSignal<string>('')
49
51
  const [isCopied, setIsCopied] = createSignal(false)
50
52
  const [isHljsLoaded, setIsHljsLoaded] = createSignal(false)
@@ -201,12 +203,12 @@ export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
201
203
  }
202
204
 
203
205
  return (
204
- <ExpandableWrapper title={params()?.filename || params()?.language || 'Code'} copyData={params()?.code} copyLabel="Copy code" toolbarVariant={props.toolbarVariant}>
206
+ <ExpandableWrapper title={params()?.filename || params()?.language || strings.codeTitle} copyData={params()?.code} copyLabel={strings.codeCopy} toolbarVariant={props.toolbarVariant}>
205
207
  <div class={`w-full bg-gray-50 dark:bg-gray-900 rounded-lg border border-gray-200 dark:border-gray-700 overflow-hidden text-sm flex flex-col ${isExpanded() ? 'flex-1 min-h-0' : ''}`}>
206
208
  {/* Header */}
207
209
  <div class="flex items-center justify-between px-4 py-2 bg-gray-100 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700 shrink-0">
208
210
  <div class="font-mono text-xs text-gray-600 dark:text-gray-400">
209
- {params()?.filename || params()?.language || 'Code'}
211
+ {params()?.filename || params()?.language || strings.codeTitle}
210
212
  </div>
211
213
  <div class="flex items-center gap-2">
212
214
  {/* Search input (v6.2.0) */}
@@ -214,16 +216,16 @@ export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
214
216
  type="text"
215
217
  value={searchQuery()}
216
218
  onInput={(e) => setSearchQuery(e.currentTarget.value)}
217
- placeholder="Search…"
219
+ placeholder={strings.codeSearchPlaceholder}
218
220
  class="px-2 py-0.5 text-xs border border-gray-200 dark:border-gray-600 rounded bg-white dark:bg-gray-700 text-gray-900 dark:text-white placeholder-gray-400 focus:border-blue-400 focus:ring-1 focus:ring-blue-400 outline-none w-32"
219
- aria-label="Search in code"
221
+ aria-label={strings.codeSearchAria}
220
222
  />
221
223
  {/* Download button (v6.2.0) */}
222
224
  <button
223
225
  onClick={handleDownload}
224
226
  class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 focus:outline-none transition-colors"
225
- aria-label="Download code as file"
226
- title="Download code"
227
+ aria-label={strings.codeDownloadAria}
228
+ title={strings.codeDownload}
227
229
  >
228
230
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
229
231
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 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" />
@@ -233,8 +235,8 @@ export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
233
235
  <button
234
236
  onClick={() => setWordWrap(!wordWrap())}
235
237
  class={`focus:outline-none transition-colors ${wordWrap() ? 'text-blue-500 dark:text-blue-400' : 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'}`}
236
- aria-label="Toggle word wrap"
237
- title={wordWrap() ? 'Disable word wrap' : 'Enable word wrap'}
238
+ aria-label={strings.codeToggleWordWrap}
239
+ title={wordWrap() ? strings.codeWordWrapDisable : strings.codeWordWrapEnable}
238
240
  >
239
241
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
240
242
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a4 4 0 010 8H9m4 0l-3-3m3 3l-3 3M3 6h18M3 14h4" />
@@ -244,8 +246,8 @@ export const CodeBlockRenderer: Component<CodeBlockRendererProps> = (props) => {
244
246
  <button
245
247
  onClick={handleCopy}
246
248
  class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 focus:outline-none transition-colors"
247
- aria-label="Copy code"
248
- title="Copy code"
249
+ aria-label={strings.codeCopy}
250
+ title={strings.codeCopy}
249
251
  >
250
252
  <Show when={isCopied()} fallback={
251
253
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -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>
@@ -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
  />