@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
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { Show, For } from 'solid-js'
9
9
  import type { AgentCardContent } from '../types/chat-bus'
10
+ import { useMCPUIStrings } from '../context/MCPUIStringsContext'
10
11
 
11
12
  export interface AgentCardProps {
12
13
  content: AgentCardContent
@@ -23,15 +24,31 @@ const AVATAR_MAP: Record<string, string> = {
23
24
  robot: '\uD83E\uDD16',
24
25
  }
25
26
 
26
- const STATUS_CONFIG: Record<string, { label: string; color: string; pulse: boolean }> = {
27
- idle: { label: 'Idle', color: 'bg-gray-400', pulse: false },
28
- running: { label: 'Running', color: 'bg-blue-500', pulse: true },
29
- waiting: { label: 'Waiting', color: 'bg-amber-500', pulse: true },
30
- done: { label: 'Done', color: 'bg-green-500', pulse: false },
31
- error: { label: 'Error', color: 'bg-red-500', pulse: false },
27
+ /**
28
+ * Status styling. The record stays module-level, but it carries a
29
+ * `labelKey` rather than English: the label is resolved from
30
+ * `useMCPUIStrings()` at render time so a host provider can localize it.
31
+ */
32
+ type AgentStatusLabelKey =
33
+ | 'agentStatusIdle'
34
+ | 'agentStatusRunning'
35
+ | 'agentStatusWaiting'
36
+ | 'agentStatusDone'
37
+ | 'agentStatusError'
38
+
39
+ const STATUS_CONFIG: Record<
40
+ string,
41
+ { labelKey: AgentStatusLabelKey; color: string; pulse: boolean }
42
+ > = {
43
+ idle: { labelKey: 'agentStatusIdle', color: 'bg-gray-400', pulse: false },
44
+ running: { labelKey: 'agentStatusRunning', color: 'bg-blue-500', pulse: true },
45
+ waiting: { labelKey: 'agentStatusWaiting', color: 'bg-amber-500', pulse: true },
46
+ done: { labelKey: 'agentStatusDone', color: 'bg-green-500', pulse: false },
47
+ error: { labelKey: 'agentStatusError', color: 'bg-red-500', pulse: false },
32
48
  }
33
49
 
34
50
  export function AgentCard(props: AgentCardProps) {
51
+ const strings = useMCPUIStrings()
35
52
  const c = () => props.content
36
53
  const status = () => STATUS_CONFIG[c().status] || STATUS_CONFIG.idle
37
54
  const avatar = () => AVATAR_MAP[c().avatar || ''] || c().avatar || '\uD83E\uDD16'
@@ -54,8 +71,8 @@ export function AgentCard(props: AgentCardProps) {
54
71
  <div class="flex items-center gap-2">
55
72
  <span class="font-medium text-sm text-gray-900 dark:text-white truncate">{c().name}</span>
56
73
  {/* Status dot */}
57
- <span class={`inline-block w-2 h-2 rounded-full ${status().color} ${status().pulse ? 'animate-pulse' : ''}`} title={status().label} />
58
- <span class="text-xs text-gray-500 dark:text-gray-400">{status().label}</span>
74
+ <span class={`inline-block w-2 h-2 rounded-full ${status().color} ${status().pulse ? 'animate-pulse' : ''}`} title={strings[status().labelKey]} />
75
+ <span class="text-xs text-gray-500 dark:text-gray-400">{strings[status().labelKey]}</span>
59
76
  </div>
60
77
 
61
78
  {/* Model */}
@@ -97,13 +114,14 @@ export interface AgentStatusBadgeProps {
97
114
  }
98
115
 
99
116
  export function AgentStatusBadge(props: AgentStatusBadgeProps) {
117
+ const strings = useMCPUIStrings()
100
118
  const status = () => STATUS_CONFIG[props.status] || STATUS_CONFIG.idle
101
119
 
102
120
  return (
103
121
  <span class="inline-flex items-center gap-1.5 text-xs">
104
122
  <span class={`w-2 h-2 rounded-full ${status().color} ${status().pulse ? 'animate-pulse' : ''}`} />
105
123
  <span class="text-gray-600 dark:text-gray-300">{props.agentName}</span>
106
- <span class="text-gray-400 dark:text-gray-500">{status().label}</span>
124
+ <span class="text-gray-400 dark:text-gray-500">{strings[status().labelKey]}</span>
107
125
  </span>
108
126
  )
109
127
  }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { Show } from 'solid-js'
9
9
  import type { AgentHandoffContent } from '../types/chat-bus'
10
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
10
11
 
11
12
  export interface AgentHandoffProps {
12
13
  content: AgentHandoffContent
@@ -24,6 +25,7 @@ function getAvatar(key?: string): string {
24
25
  }
25
26
 
26
27
  export function AgentHandoff(props: AgentHandoffProps) {
28
+ const strings = useMCPUIStrings()
27
29
  const c = () => props.content
28
30
 
29
31
  if (typeof console !== 'undefined') {
@@ -49,7 +51,8 @@ export function AgentHandoff(props: AgentHandoffProps) {
49
51
  </div>
50
52
  <Show when={c().summary || c().itemCount}>
51
53
  <span class="text-[10px] text-gray-500 dark:text-gray-400 text-center">
52
- {c().summary || `${c().itemCount} items`}
54
+ {c().summary ||
55
+ formatMCPUIString(strings.handoffItems, { count: c().itemCount ?? 0 })}
53
56
  </span>
54
57
  </Show>
55
58
  </div>
@@ -1,5 +1,6 @@
1
1
  import { Component } from 'solid-js'
2
2
  import { safeUrl } from '../utils/safe-url'
3
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
3
4
 
4
5
  export interface ArtifactComponentParams {
5
6
  url: string
@@ -10,6 +11,7 @@ export interface ArtifactComponentParams {
10
11
  }
11
12
 
12
13
  export const ArtifactRenderer: Component<{ params: ArtifactComponentParams }> = (props) => {
14
+ const strings = useMCPUIStrings()
13
15
  const getIcon = () => {
14
16
  if (props.params.mimeType?.includes('csv')) return '📊'
15
17
  if (props.params.mimeType?.includes('json')) return '{}'
@@ -19,9 +21,19 @@ export const ArtifactRenderer: Component<{ params: ArtifactComponentParams }> =
19
21
 
20
22
  const formatSize = (bytes?: number) => {
21
23
  if (!bytes) return ''
22
- if (bytes < 1024) return `${bytes} B`
23
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
24
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
24
+ const oneDecimal = (n: number) =>
25
+ new Intl.NumberFormat(strings.locale, {
26
+ minimumFractionDigits: 1,
27
+ maximumFractionDigits: 1,
28
+ useGrouping: false,
29
+ }).format(n)
30
+ if (bytes < 1024) {
31
+ return formatMCPUIString(strings.sizeBytes, {
32
+ size: new Intl.NumberFormat(strings.locale, { maximumFractionDigits: 0 }).format(bytes),
33
+ })
34
+ }
35
+ if (bytes < 1024 * 1024) return formatMCPUIString(strings.sizeKilobytes, { size: oneDecimal(bytes / 1024) })
36
+ return formatMCPUIString(strings.sizeMegabytes, { size: oneDecimal(bytes / (1024 * 1024)) })
25
37
  }
26
38
 
27
39
  return (
@@ -35,7 +47,7 @@ export const ArtifactRenderer: Component<{ params: ArtifactComponentParams }> =
35
47
  {props.params.filename}
36
48
  </h4>
37
49
  <p class="text-xs text-gray-500 dark:text-gray-400">
38
- {formatSize(props.params.size)} • {props.params.description || 'Generated artifact'}
50
+ {formatSize(props.params.size)} • {props.params.description || strings.artifactDescription}
39
51
  </p>
40
52
  </div>
41
53
  </div>
@@ -48,7 +60,7 @@ export const ArtifactRenderer: Component<{ params: ArtifactComponentParams }> =
48
60
  <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
49
61
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
50
62
  </svg>
51
- Download
63
+ {strings.download}
52
64
  </a>
53
65
  </div>
54
66
  )
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { Component, For, Show, createMemo, JSX } from 'solid-js'
9
9
  import type { AutocompleteOption } from '../types'
10
+ import { useMCPUIStrings } from '../context/MCPUIStringsContext'
10
11
 
11
12
  /**
12
13
  * Props for AutocompleteDropdown
@@ -142,6 +143,7 @@ const DefaultOptionRenderer: Component<{
142
143
  * AutocompleteDropdown Component
143
144
  */
144
145
  export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props) => {
146
+ const strings = useMCPUIStrings()
145
147
  const positionStyles = createMemo((): JSX.CSSProperties => {
146
148
  if (props.position === 'top') {
147
149
  return {
@@ -175,7 +177,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
175
177
  class={`mcp-autocomplete-dropdown ${props.class || ''}`}
176
178
  style={containerStyles()}
177
179
  role="listbox"
178
- aria-label="Suggestions"
180
+ aria-label={strings.autocompleteSuggestions}
179
181
  >
180
182
  {/* Loading state */}
181
183
  <Show when={props.isLoading}>
@@ -201,7 +203,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
201
203
  animation: 'mcp-spin 0.6s linear infinite'
202
204
  }}
203
205
  />
204
- {props.loadingMessage || 'Loading...'}
206
+ {props.loadingMessage || strings.autocompleteLoading}
205
207
  </div>
206
208
  </Show>
207
209
 
@@ -216,7 +218,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
216
218
  'text-align': 'center'
217
219
  }}
218
220
  >
219
- {props.emptyMessage || 'No suggestions found'}
221
+ {props.emptyMessage || strings.autocompleteEmpty}
220
222
  </div>
221
223
  </Show>
222
224
 
@@ -302,7 +304,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
302
304
  'font-family': 'inherit',
303
305
  'font-size': '0.7rem'
304
306
  }}>↓</kbd>
305
- {' to navigate, '}
307
+ {strings.autocompleteHintNavigate}
306
308
  <kbd style={{
307
309
  'background-color': '#e5e7eb',
308
310
  padding: '1px 4px',
@@ -310,7 +312,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
310
312
  'font-family': 'inherit',
311
313
  'font-size': '0.7rem'
312
314
  }}>Enter</kbd>
313
- {' to select, '}
315
+ {strings.autocompleteHintSelect}
314
316
  <kbd style={{
315
317
  'background-color': '#e5e7eb',
316
318
  padding: '1px 4px',
@@ -318,7 +320,7 @@ export const AutocompleteDropdown: Component<AutocompleteDropdownProps> = (props
318
320
  'font-family': 'inherit',
319
321
  'font-size': '0.7rem'
320
322
  }}>Esc</kbd>
321
- {' to dismiss'}
323
+ {strings.autocompleteHintDismiss}
322
324
  </div>
323
325
  </Show>
324
326
  </div>
@@ -10,6 +10,7 @@ import type { FormFieldParams, FieldAutocompleteConfig, AutocompleteContext } fr
10
10
  import { useConditionalField } from '../hooks/useConditionalField'
11
11
  import { useAutocomplete } from '../hooks/useAutocomplete'
12
12
  import { useAutocompleteContextSafe } from '../context/AutocompleteContext'
13
+ import { useMCPUIStrings } from '../context/MCPUIStringsContext'
13
14
  import { GhostText } from './GhostText'
14
15
  import { AutocompleteDropdown } from './AutocompleteDropdown'
15
16
 
@@ -62,6 +63,7 @@ export interface AutocompleteFormFieldProps {
62
63
  * AutocompleteFormField Component
63
64
  */
64
65
  export const AutocompleteFormField: Component<AutocompleteFormFieldProps> = (props) => {
66
+ const strings = useMCPUIStrings()
65
67
  // Check if autocomplete context is available
66
68
  const autocompleteCtx = useAutocompleteContextSafe()
67
69
 
@@ -205,7 +207,9 @@ export const AutocompleteFormField: Component<AutocompleteFormFieldProps> = (pro
205
207
  inputValue={localValue()}
206
208
  ghostText={autocomplete.ghostText()}
207
209
  visible={autocomplete.isOpen()}
208
- hintText={autocomplete.ghostText() ? 'Tab to accept' : undefined}
210
+ hintText={
211
+ autocomplete.ghostText() ? strings.autocompleteTabToAccept : undefined
212
+ }
209
213
  isLoading={autocomplete.isLoading()}
210
214
  />
211
215
  </Show>
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { For, Show } from 'solid-js'
9
9
  import type { BriefingDiffContent } from '../types/chat-bus'
10
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext'
10
11
 
11
12
  export interface BriefingDiffProps {
12
13
  content: BriefingDiffContent
@@ -19,6 +20,7 @@ const CHANGE_CONFIG: Record<string, { icon: string; color: string; bg: string }>
19
20
  }
20
21
 
21
22
  export function BriefingDiff(props: BriefingDiffProps) {
23
+ const strings = useMCPUIStrings()
22
24
  const c = () => props.content
23
25
 
24
26
  if (typeof console !== 'undefined') {
@@ -45,13 +47,19 @@ export function BriefingDiff(props: BriefingDiffProps) {
45
47
  {(stats) => (
46
48
  <div class="flex items-center gap-3 text-xs">
47
49
  <Show when={stats().added > 0}>
48
- <span class="text-green-600 dark:text-green-400">+{stats().added} added</span>
50
+ <span class="text-green-600 dark:text-green-400">
51
+ {formatMCPUIString(strings.briefingAdded, { count: stats().added })}
52
+ </span>
49
53
  </Show>
50
54
  <Show when={stats().removed > 0}>
51
- <span class="text-red-600 dark:text-red-400">{stats().removed} removed</span>
55
+ <span class="text-red-600 dark:text-red-400">
56
+ {formatMCPUIString(strings.briefingRemoved, { count: stats().removed })}
57
+ </span>
52
58
  </Show>
53
59
  <Show when={stats().changed > 0}>
54
- <span class="text-amber-600 dark:text-amber-400">{stats().changed} changed</span>
60
+ <span class="text-amber-600 dark:text-amber-400">
61
+ {formatMCPUIString(strings.briefingChanged, { count: stats().changed })}
62
+ </span>
55
63
  </Show>
56
64
  </div>
57
65
  )}
@@ -3,6 +3,7 @@ import { isServer } from 'solid-js/web'
3
3
  import { useRenderContext } from './RenderContext'
4
4
  import type { UIComponent } from '../types'
5
5
  import { ExpandableWrapper, useExpanded } from './ExpandableWrapper'
6
+ import { useMCPUIStrings } from '../context/MCPUIStringsContext'
6
7
 
7
8
  export interface CarouselRendererProps {
8
9
  items: UIComponent[]
@@ -15,6 +16,7 @@ export interface CarouselRendererProps {
15
16
  }
16
17
 
17
18
  export const CarouselRenderer: Component<CarouselRendererProps> = (props) => {
19
+ const strings = useMCPUIStrings()
18
20
  let scrollContainer: HTMLDivElement | undefined
19
21
  const [canScrollLeft, setCanScrollLeft] = createSignal(false)
20
22
  const [canScrollRight, setCanScrollRight] = createSignal(true)
@@ -42,9 +44,9 @@ export const CarouselRenderer: Component<CarouselRendererProps> = (props) => {
42
44
 
43
45
  return (
44
46
  <ExpandableWrapper
45
- title={'Carousel'}
47
+ title={strings.carouselTitle}
46
48
  copyData={JSON.stringify(props.items, null, 2)}
47
- copyLabel="Copy items (JSON)"
49
+ copyLabel={strings.carouselCopy}
48
50
  toolbarVariant={props.toolbarVariant}
49
51
  >
50
52
  <div class={`relative group ${isExpanded() ? 'flex-1 min-h-0 flex flex-col' : ''}`}>
@@ -24,7 +24,7 @@ import { ExpandableWrapper, useExpanded } from './ExpandableWrapper';
24
24
  import { DegradedFallback } from './DegradedFallback';
25
25
  import { chartToDataTable } from './chart-data-table';
26
26
  import { useTelemetry } from '../context/MCPUITelemetryContext';
27
- import { useMCPUIStrings } from '../context/MCPUIStringsContext';
27
+ import { formatMCPUIString, useMCPUIStrings } from '../context/MCPUIStringsContext';
28
28
 
29
29
  // Lazy load Chart.js to avoid bundling if not used
30
30
  let ChartJS: any = null;
@@ -113,9 +113,18 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
113
113
  let renderVersion = 0;
114
114
 
115
115
  const params = () => props.component.params as ChartComponentParams;
116
- const tableData = createMemo(() => chartToDataTable(params()));
117
116
  const telemetry = useTelemetry();
118
117
  const strings = useMCPUIStrings();
118
+ // The accessible data table is a pure projection: its column headers come
119
+ // from the chrome strings, injected rather than hardcoded in the helper.
120
+ const tableData = createMemo(() =>
121
+ chartToDataTable(params(), {
122
+ series: strings.chartTableSeries,
123
+ point: strings.chartTablePoint,
124
+ label: strings.degradedColLabel,
125
+ seriesName: strings.degradedSeries,
126
+ })
127
+ );
119
128
  const descriptionId = createUniqueId();
120
129
  const title = () => params().title || strings.chartView;
121
130
 
@@ -205,7 +214,7 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
205
214
  setIsLoading(false);
206
215
  } catch (err) {
207
216
  if (version !== renderVersion) return;
208
- const error = err instanceof Error ? err : new Error('Chart rendering failed');
217
+ const error = err instanceof Error ? err : new Error(strings.chartRenderError);
209
218
  setError(error.message);
210
219
  setIsLoading(false);
211
220
  // Fallback ladder (P2.5): record the failure so it's observable, then
@@ -241,7 +250,7 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
241
250
  <ExpandableWrapper
242
251
  title={title()}
243
252
  copyData={copyDataJSON()}
244
- copyLabel="Copy chart data (JSON)"
253
+ copyLabel={strings.chartCopy}
245
254
  toolbarVariant={props.toolbarVariant}
246
255
  >
247
256
  <ContextAwareChartLayout
@@ -294,8 +303,8 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
294
303
  onClick={handleExportPNG}
295
304
  disabled={activeView() !== 'chart' || isLoading() || Boolean(error())}
296
305
  class="opacity-0 group-hover:opacity-60 hover:!opacity-100 disabled:!opacity-30 disabled:cursor-not-allowed px-2 py-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-all shadow-sm"
297
- title="Download PNG"
298
- aria-label="Download chart as PNG"
306
+ title={strings.chartDownloadPng}
307
+ aria-label={strings.chartDownloadPngAria}
299
308
  >
300
309
  <svg
301
310
  class="w-3 h-3 text-gray-500 dark:text-gray-400"
@@ -323,8 +332,8 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
323
332
  instead of a bare "Chart Error" message. */}
324
333
  <Show when={error() && activeView() === 'chart'}>
325
334
  <DegradedFallback
326
- message={`Chart rendering failed: ${error()}`}
327
- caption="Showing the chart data as a table — the interactive chart is unavailable."
335
+ message={formatMCPUIString(strings.chartRenderFailed, { error: String(error()) })}
336
+ caption={strings.chartDegradedCaption}
328
337
  {...tableData()}
329
338
  />
330
339
  </Show>
@@ -405,7 +414,7 @@ export const ChartJSRenderer: Component<ChartJSRendererProps> = (props) => {
405
414
  <div class="absolute inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-800/80">
406
415
  <div class="flex flex-col items-center gap-2">
407
416
  <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
408
- <span class="text-sm text-gray-500 dark:text-gray-400">Loading chart...</span>
417
+ <span class="text-sm text-gray-500 dark:text-gray-400">{strings.chartLoading}</span>
409
418
  </div>
410
419
  </div>
411
420
  </Show>
@@ -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">