@longsightgroup/qti3-player 0.4.0 → 0.5.4

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 (291) hide show
  1. package/README.md +146 -13
  2. package/dist/content/content-dom.d.ts +11 -0
  3. package/dist/content/content-dom.d.ts.map +1 -0
  4. package/dist/content/content-dom.js +262 -0
  5. package/dist/content/content-dom.js.map +1 -0
  6. package/dist/content/content-renderer.d.ts +17 -0
  7. package/dist/content/content-renderer.d.ts.map +1 -0
  8. package/dist/content/content-renderer.js +82 -0
  9. package/dist/content/content-renderer.js.map +1 -0
  10. package/dist/controls/remove-button.d.ts +3 -0
  11. package/dist/controls/remove-button.d.ts.map +1 -0
  12. package/dist/controls/remove-button.js +12 -0
  13. package/dist/controls/remove-button.js.map +1 -0
  14. package/dist/index.d.ts +13 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/interactions/choice-interaction.d.ts +4 -0
  19. package/dist/interactions/choice-interaction.d.ts.map +1 -0
  20. package/dist/interactions/choice-interaction.js +81 -0
  21. package/dist/interactions/choice-interaction.js.map +1 -0
  22. package/dist/interactions/drawing-interaction.d.ts +6 -0
  23. package/dist/interactions/drawing-interaction.d.ts.map +1 -0
  24. package/dist/interactions/drawing-interaction.js +401 -0
  25. package/dist/interactions/drawing-interaction.js.map +1 -0
  26. package/dist/interactions/end-attempt-interaction.d.ts +4 -0
  27. package/dist/interactions/end-attempt-interaction.d.ts.map +1 -0
  28. package/dist/interactions/end-attempt-interaction.js +13 -0
  29. package/dist/interactions/end-attempt-interaction.js.map +1 -0
  30. package/dist/interactions/gap-match-interaction.d.ts +4 -0
  31. package/dist/interactions/gap-match-interaction.d.ts.map +1 -0
  32. package/dist/interactions/gap-match-interaction.js +277 -0
  33. package/dist/interactions/gap-match-interaction.js.map +1 -0
  34. package/dist/interactions/graphic-associate-interaction.d.ts +4 -0
  35. package/dist/interactions/graphic-associate-interaction.d.ts.map +1 -0
  36. package/dist/interactions/graphic-associate-interaction.js +297 -0
  37. package/dist/interactions/graphic-associate-interaction.js.map +1 -0
  38. package/dist/interactions/graphic-context.d.ts +3 -0
  39. package/dist/interactions/graphic-context.d.ts.map +1 -0
  40. package/dist/interactions/graphic-context.js +35 -0
  41. package/dist/interactions/graphic-context.js.map +1 -0
  42. package/dist/interactions/hotspot-interaction.d.ts +4 -0
  43. package/dist/interactions/hotspot-interaction.d.ts.map +1 -0
  44. package/dist/interactions/hotspot-interaction.js +68 -0
  45. package/dist/interactions/hotspot-interaction.js.map +1 -0
  46. package/dist/interactions/hottext-interaction.d.ts +3 -0
  47. package/dist/interactions/hottext-interaction.d.ts.map +1 -0
  48. package/dist/interactions/hottext-interaction.js +66 -0
  49. package/dist/interactions/hottext-interaction.js.map +1 -0
  50. package/dist/interactions/inline-choice-interaction.d.ts +4 -0
  51. package/dist/interactions/inline-choice-interaction.d.ts.map +1 -0
  52. package/dist/interactions/inline-choice-interaction.js +31 -0
  53. package/dist/interactions/inline-choice-interaction.js.map +1 -0
  54. package/dist/interactions/inline-controls.d.ts +6 -0
  55. package/dist/interactions/inline-controls.d.ts.map +1 -0
  56. package/dist/interactions/inline-controls.js +15 -0
  57. package/dist/interactions/inline-controls.js.map +1 -0
  58. package/dist/interactions/interaction-diagnostics.d.ts +7 -0
  59. package/dist/interactions/interaction-diagnostics.d.ts.map +1 -0
  60. package/dist/interactions/interaction-diagnostics.js +137 -0
  61. package/dist/interactions/interaction-diagnostics.js.map +1 -0
  62. package/dist/interactions/interaction-dispatch.d.ts +2 -0
  63. package/dist/interactions/interaction-dispatch.d.ts.map +1 -0
  64. package/dist/interactions/interaction-dispatch.js +2 -0
  65. package/dist/interactions/interaction-dispatch.js.map +1 -0
  66. package/dist/interactions/interaction-label.d.ts +4 -0
  67. package/dist/interactions/interaction-label.d.ts.map +1 -0
  68. package/dist/interactions/interaction-label.js +8 -0
  69. package/dist/interactions/interaction-label.js.map +1 -0
  70. package/dist/interactions/interaction-registry.d.ts +24 -0
  71. package/dist/interactions/interaction-registry.d.ts.map +1 -0
  72. package/dist/interactions/interaction-registry.js +138 -0
  73. package/dist/interactions/interaction-registry.js.map +1 -0
  74. package/dist/interactions/match-interaction.d.ts +4 -0
  75. package/dist/interactions/match-interaction.d.ts.map +1 -0
  76. package/dist/interactions/match-interaction.js +188 -0
  77. package/dist/interactions/match-interaction.js.map +1 -0
  78. package/dist/interactions/object-asset.d.ts +8 -0
  79. package/dist/interactions/object-asset.d.ts.map +1 -0
  80. package/dist/interactions/object-asset.js +182 -0
  81. package/dist/interactions/object-asset.js.map +1 -0
  82. package/dist/interactions/pair-interaction.d.ts +4 -0
  83. package/dist/interactions/pair-interaction.d.ts.map +1 -0
  84. package/dist/interactions/pair-interaction.js +125 -0
  85. package/dist/interactions/pair-interaction.js.map +1 -0
  86. package/dist/interactions/point-value.d.ts +16 -0
  87. package/dist/interactions/point-value.d.ts.map +1 -0
  88. package/dist/interactions/point-value.js +37 -0
  89. package/dist/interactions/point-value.js.map +1 -0
  90. package/dist/interactions/portable-custom-interaction.d.ts +16 -0
  91. package/dist/interactions/portable-custom-interaction.d.ts.map +1 -0
  92. package/dist/interactions/portable-custom-interaction.js +97 -0
  93. package/dist/interactions/portable-custom-interaction.js.map +1 -0
  94. package/dist/interactions/position-object-interaction.d.ts +4 -0
  95. package/dist/interactions/position-object-interaction.d.ts.map +1 -0
  96. package/dist/interactions/position-object-interaction.js +177 -0
  97. package/dist/interactions/position-object-interaction.js.map +1 -0
  98. package/dist/interactions/routing.d.ts +5 -0
  99. package/dist/interactions/routing.d.ts.map +1 -0
  100. package/dist/interactions/routing.js +24 -0
  101. package/dist/interactions/routing.js.map +1 -0
  102. package/dist/interactions/select-point-interaction.d.ts +4 -0
  103. package/dist/interactions/select-point-interaction.d.ts.map +1 -0
  104. package/dist/interactions/select-point-interaction.js +166 -0
  105. package/dist/interactions/select-point-interaction.js.map +1 -0
  106. package/dist/interactions/shared.d.ts +12 -0
  107. package/dist/interactions/shared.d.ts.map +1 -0
  108. package/dist/interactions/shared.js +65 -0
  109. package/dist/interactions/shared.js.map +1 -0
  110. package/dist/interactions/text-interaction.d.ts +6 -0
  111. package/dist/interactions/text-interaction.d.ts.map +1 -0
  112. package/dist/interactions/text-interaction.js +110 -0
  113. package/dist/interactions/text-interaction.js.map +1 -0
  114. package/dist/interactions/unsupported-interaction.d.ts +4 -0
  115. package/dist/interactions/unsupported-interaction.d.ts.map +1 -0
  116. package/dist/interactions/unsupported-interaction.js +23 -0
  117. package/dist/interactions/unsupported-interaction.js.map +1 -0
  118. package/dist/interactions/upload-interaction.d.ts +4 -0
  119. package/dist/interactions/upload-interaction.d.ts.map +1 -0
  120. package/dist/interactions/upload-interaction.js +10 -0
  121. package/dist/interactions/upload-interaction.js.map +1 -0
  122. package/dist/player/attempt-availability.d.ts +6 -0
  123. package/dist/player/attempt-availability.d.ts.map +1 -0
  124. package/dist/player/attempt-availability.js +27 -0
  125. package/dist/player/attempt-availability.js.map +1 -0
  126. package/dist/player/content-state.d.ts +19 -0
  127. package/dist/player/content-state.d.ts.map +1 -0
  128. package/dist/player/content-state.js +39 -0
  129. package/dist/player/content-state.js.map +1 -0
  130. package/dist/player/dynamic-body.d.ts +7 -0
  131. package/dist/player/dynamic-body.d.ts.map +1 -0
  132. package/dist/player/dynamic-body.js +28 -0
  133. package/dist/player/dynamic-body.js.map +1 -0
  134. package/dist/player/feedback-panel.d.ts +3 -0
  135. package/dist/player/feedback-panel.d.ts.map +1 -0
  136. package/dist/player/feedback-panel.js +14 -0
  137. package/dist/player/feedback-panel.js.map +1 -0
  138. package/dist/player/fetch-xml.d.ts +2 -0
  139. package/dist/player/fetch-xml.d.ts.map +1 -0
  140. package/dist/player/fetch-xml.js +10 -0
  141. package/dist/player/fetch-xml.js.map +1 -0
  142. package/dist/player/interaction-render.d.ts +14 -0
  143. package/dist/player/interaction-render.d.ts.map +1 -0
  144. package/dist/player/interaction-render.js +51 -0
  145. package/dist/player/interaction-render.js.map +1 -0
  146. package/dist/player/render-shell.d.ts +8 -0
  147. package/dist/player/render-shell.d.ts.map +1 -0
  148. package/dist/player/render-shell.js +37 -0
  149. package/dist/player/render-shell.js.map +1 -0
  150. package/dist/player/resolve-assets.d.ts +3 -0
  151. package/dist/player/resolve-assets.d.ts.map +1 -0
  152. package/dist/player/resolve-assets.js +12 -0
  153. package/dist/player/resolve-assets.js.map +1 -0
  154. package/dist/player/validation-messages.d.ts +17 -0
  155. package/dist/player/validation-messages.d.ts.map +1 -0
  156. package/dist/player/validation-messages.js +33 -0
  157. package/dist/player/validation-messages.js.map +1 -0
  158. package/dist/player-adapter.d.ts +62 -0
  159. package/dist/player-adapter.d.ts.map +1 -0
  160. package/dist/player-adapter.js +119 -0
  161. package/dist/player-adapter.js.map +1 -0
  162. package/dist/player-dev.d.ts +4 -0
  163. package/dist/player-dev.d.ts.map +1 -0
  164. package/dist/player-dev.js +14 -0
  165. package/dist/player-dev.js.map +1 -0
  166. package/dist/player-element.d.ts +14 -1
  167. package/dist/player-element.d.ts.map +1 -1
  168. package/dist/player-element.js +57 -5
  169. package/dist/player-element.js.map +1 -1
  170. package/dist/player-locale.d.ts +8 -3
  171. package/dist/player-locale.d.ts.map +1 -1
  172. package/dist/player-locale.js +16 -175
  173. package/dist/player-locale.js.map +1 -1
  174. package/dist/player-message-catalog-default.d.ts +4 -0
  175. package/dist/player-message-catalog-default.d.ts.map +1 -0
  176. package/dist/player-message-catalog-default.js +118 -0
  177. package/dist/player-message-catalog-default.js.map +1 -0
  178. package/dist/player-message-catalog-validate.d.ts +31 -0
  179. package/dist/player-message-catalog-validate.d.ts.map +1 -0
  180. package/dist/player-message-catalog-validate.js +327 -0
  181. package/dist/player-message-catalog-validate.js.map +1 -0
  182. package/dist/player-message-catalog.d.ts +18 -0
  183. package/dist/player-message-catalog.d.ts.map +1 -0
  184. package/dist/player-message-catalog.js +40 -0
  185. package/dist/player-message-catalog.js.map +1 -0
  186. package/dist/player-message-keys.d.ts +6 -0
  187. package/dist/player-message-keys.d.ts.map +1 -0
  188. package/dist/player-message-keys.js +7 -0
  189. package/dist/player-message-keys.js.map +1 -0
  190. package/dist/player-message-manifest.d.ts +272 -0
  191. package/dist/player-message-manifest.d.ts.map +1 -0
  192. package/dist/player-message-manifest.js +83 -0
  193. package/dist/player-message-manifest.js.map +1 -0
  194. package/dist/player-message-overrides.d.ts +3 -0
  195. package/dist/player-message-overrides.d.ts.map +1 -0
  196. package/dist/player-message-overrides.js +28 -0
  197. package/dist/player-message-overrides.js.map +1 -0
  198. package/dist/player-message-resolver.d.ts +31 -0
  199. package/dist/player-message-resolver.d.ts.map +1 -0
  200. package/dist/player-message-resolver.js +110 -0
  201. package/dist/player-message-resolver.js.map +1 -0
  202. package/dist/player-messages.d.ts +0 -38
  203. package/dist/player-messages.d.ts.map +1 -1
  204. package/dist/player-types.d.ts +12 -2
  205. package/dist/player-types.d.ts.map +1 -1
  206. package/dist/reorder/a11y.d.ts +7 -0
  207. package/dist/reorder/a11y.d.ts.map +1 -0
  208. package/dist/reorder/a11y.js +34 -0
  209. package/dist/reorder/a11y.js.map +1 -0
  210. package/dist/reorder/graphic-order-interaction.d.ts +4 -0
  211. package/dist/reorder/graphic-order-interaction.d.ts.map +1 -0
  212. package/dist/reorder/graphic-order-interaction.js +205 -0
  213. package/dist/reorder/graphic-order-interaction.js.map +1 -0
  214. package/dist/reorder/list-controls.d.ts +22 -0
  215. package/dist/reorder/list-controls.d.ts.map +1 -0
  216. package/dist/reorder/list-controls.js +75 -0
  217. package/dist/reorder/list-controls.js.map +1 -0
  218. package/dist/reorder/order-interaction.d.ts +4 -0
  219. package/dist/reorder/order-interaction.d.ts.map +1 -0
  220. package/dist/reorder/order-interaction.js +54 -0
  221. package/dist/reorder/order-interaction.js.map +1 -0
  222. package/dist/styles/base-styles.d.ts +2 -0
  223. package/dist/styles/base-styles.d.ts.map +1 -0
  224. package/dist/styles/base-styles.js +133 -0
  225. package/dist/styles/base-styles.js.map +1 -0
  226. package/dist/styles/choice-hottext-styles.d.ts +2 -0
  227. package/dist/styles/choice-hottext-styles.d.ts.map +1 -0
  228. package/dist/styles/choice-hottext-styles.js +76 -0
  229. package/dist/styles/choice-hottext-styles.js.map +1 -0
  230. package/dist/styles/control-styles.d.ts +2 -0
  231. package/dist/styles/control-styles.d.ts.map +1 -0
  232. package/dist/styles/control-styles.js +114 -0
  233. package/dist/styles/control-styles.js.map +1 -0
  234. package/dist/styles/drawing-styles.d.ts +2 -0
  235. package/dist/styles/drawing-styles.d.ts.map +1 -0
  236. package/dist/styles/drawing-styles.js +30 -0
  237. package/dist/styles/drawing-styles.js.map +1 -0
  238. package/dist/styles/gap-match-styles.d.ts +2 -0
  239. package/dist/styles/gap-match-styles.d.ts.map +1 -0
  240. package/dist/styles/gap-match-styles.js +33 -0
  241. package/dist/styles/gap-match-styles.js.map +1 -0
  242. package/dist/styles/graphic-styles.d.ts +2 -0
  243. package/dist/styles/graphic-styles.d.ts.map +1 -0
  244. package/dist/styles/graphic-styles.js +289 -0
  245. package/dist/styles/graphic-styles.js.map +1 -0
  246. package/dist/styles/match-pair-styles.d.ts +2 -0
  247. package/dist/styles/match-pair-styles.d.ts.map +1 -0
  248. package/dist/styles/match-pair-styles.js +62 -0
  249. package/dist/styles/match-pair-styles.js.map +1 -0
  250. package/dist/styles/text-slider-styles.d.ts +2 -0
  251. package/dist/styles/text-slider-styles.d.ts.map +1 -0
  252. package/dist/styles/text-slider-styles.js +35 -0
  253. package/dist/styles/text-slider-styles.js.map +1 -0
  254. package/package.json +8 -8
  255. package/src/controls/remove-button.ts +8 -5
  256. package/src/index.ts +61 -5
  257. package/src/interactions/choice-interaction.ts +6 -2
  258. package/src/interactions/drawing-interaction.ts +14 -9
  259. package/src/interactions/end-attempt-interaction.ts +3 -3
  260. package/src/interactions/gap-match-interaction.ts +32 -13
  261. package/src/interactions/graphic-associate-interaction.ts +15 -10
  262. package/src/interactions/hotspot-interaction.ts +10 -6
  263. package/src/interactions/inline-choice-interaction.ts +4 -4
  264. package/src/interactions/interaction-registry.ts +12 -12
  265. package/src/interactions/match-interaction.ts +9 -6
  266. package/src/interactions/pair-interaction.ts +22 -14
  267. package/src/interactions/position-object-interaction.ts +22 -13
  268. package/src/interactions/select-point-interaction.ts +25 -13
  269. package/src/interactions/shared.ts +21 -4
  270. package/src/interactions/text-interaction.ts +14 -4
  271. package/src/interactions/upload-interaction.ts +6 -3
  272. package/src/player/interaction-render.ts +4 -4
  273. package/src/player-adapter.ts +253 -0
  274. package/src/player-dev.ts +14 -0
  275. package/src/player-element.ts +78 -8
  276. package/src/player-locale.ts +28 -199
  277. package/src/player-message-catalog-default.ts +119 -0
  278. package/src/player-message-catalog-validate.ts +425 -0
  279. package/src/player-message-catalog.ts +72 -0
  280. package/src/player-message-keys.ts +12 -0
  281. package/src/player-message-manifest.ts +103 -0
  282. package/src/player-message-overrides.ts +38 -0
  283. package/src/player-message-resolver.ts +205 -0
  284. package/src/player-messages.ts +0 -30
  285. package/src/player-types.ts +15 -4
  286. package/src/reorder/a11y.ts +22 -7
  287. package/src/reorder/graphic-order-interaction.ts +23 -16
  288. package/src/reorder/list-controls.ts +8 -6
  289. package/src/reorder/order-interaction.ts +7 -5
  290. package/src/styles/base-styles.ts +20 -5
  291. package/src/styles/graphic-styles.ts +0 -6
@@ -6,10 +6,10 @@ import {
6
6
  objectHeight,
7
7
  objectWidth,
8
8
  placeHotspotButton,
9
- readableType,
10
9
  responseGroup,
11
10
  valueToStrings,
12
11
  } from "../interaction-support.js";
12
+ import type { PlayerMessageResolver } from "../player-message-resolver.js";
13
13
  import { parseUnlimitedMaximum } from "../response-limits.js";
14
14
  import { appendGraphicContext } from "./graphic-context.js";
15
15
  import { appendInlineControl, normalizeInlineSegmentText } from "./inline-controls.js";
@@ -33,13 +33,14 @@ export function renderGapMatchResponse(
33
33
  interaction: QtiInteraction,
34
34
  update: (value: QtiValue) => void,
35
35
  currentValue: QtiValue,
36
+ messages: PlayerMessageResolver,
36
37
  ): HTMLElement {
37
38
  if (
38
39
  interaction.type === "graphicGapMatch" &&
39
40
  interaction.object &&
40
41
  interaction.choices.some((choice) => choice.role === "hotspot")
41
42
  ) {
42
- return renderGraphicGapMatchResponse(interaction, update, currentValue);
43
+ return renderGraphicGapMatchResponse(interaction, update, currentValue, messages);
43
44
  }
44
45
 
45
46
  const group = responseGroup();
@@ -54,11 +55,16 @@ export function renderGapMatchResponse(
54
55
  let selectedSource: QtiChoice | undefined;
55
56
  let draggedSource: string | undefined;
56
57
 
57
- const sourceRegion = tokenRegion(`${readableType(interaction.type)} choices`);
58
+ const sourceRegion = tokenRegion(
59
+ messages.message("interactionChoicesBank", { type: interaction.type }),
60
+ );
58
61
  const gapRegion = document.createElement("div");
59
62
  gapRegion.className = "qti3-gap-region qti3-gap-passage";
60
63
  gapRegion.role = "group";
61
- gapRegion.setAttribute("aria-label", `${readableType(interaction.type)} targets`);
64
+ gapRegion.setAttribute(
65
+ "aria-label",
66
+ messages.message("interactionGapTargets", { type: interaction.type }),
67
+ );
62
68
  for (const pair of valueToStrings(currentValue)) {
63
69
  const [sourceIdentifier, gapIdentifier] = pair.split(/\s+/);
64
70
  const source = sources.find((choice) => choice.identifier === sourceIdentifier);
@@ -91,7 +97,7 @@ export function renderGapMatchResponse(
91
97
  };
92
98
  const gapControl = (gap: QtiChoice, index: number) => {
93
99
  const assigned = assignments.get(gap.identifier);
94
- const gapLabel = `Gap ${index + 1}`;
100
+ const gapLabel = messages.message("gapLabel", { index: index + 1 });
95
101
  const target = document.createElement("span");
96
102
  target.className = "qti3-gap-target";
97
103
  target.dataset.gapIdentifier = gap.identifier;
@@ -112,7 +118,9 @@ export function renderGapMatchResponse(
112
118
  button.textContent = assigned ? assigned.text : "";
113
119
  button.setAttribute(
114
120
  "aria-label",
115
- assigned ? `${gapLabel}, assigned ${assigned.text}` : `${gapLabel}, empty`,
121
+ assigned
122
+ ? messages.message("gapAssignedState", { label: gapLabel, assigned: assigned.text })
123
+ : messages.message("gapEmptyState", { label: gapLabel }),
116
124
  );
117
125
  button.addEventListener("click", () => assign(gap, selectedSource?.identifier));
118
126
  button.addEventListener("keydown", (event) => {
@@ -173,6 +181,7 @@ function renderGraphicGapMatchResponse(
173
181
  interaction: QtiInteraction,
174
182
  update: (value: QtiValue) => void,
175
183
  currentValue: QtiValue,
184
+ messages: PlayerMessageResolver,
176
185
  ): HTMLElement {
177
186
  const group = responseGroup();
178
187
  const width = objectWidth(interaction);
@@ -202,7 +211,10 @@ function renderGraphicGapMatchResponse(
202
211
  "qti3-graphic-gap-match-surface",
203
212
  );
204
213
  surface.role = "group";
205
- surface.setAttribute("aria-label", `${readableType(interaction.type)} target image`);
214
+ surface.setAttribute(
215
+ "aria-label",
216
+ messages.message("interactionTargetImage", { type: interaction.type }),
217
+ );
206
218
  surface.style.overflow = "visible";
207
219
  surface.style.setProperty(
208
220
  "--qti3-graphic-gap-label-block-size",
@@ -213,11 +225,14 @@ function renderGraphicGapMatchResponse(
213
225
  appendGraphicObjectImage(
214
226
  surface,
215
227
  interaction.object,
216
- interaction.object.text || `${readableType(interaction.type)} image`,
228
+ interaction.object.text ||
229
+ messages.message("interactionImageAlt", { type: interaction.type }),
217
230
  );
218
231
  }
219
232
 
220
- const sourceRegion = tokenRegion(`${readableType(interaction.type)} choices`);
233
+ const sourceRegion = tokenRegion(
234
+ messages.message("interactionChoicesBank", { type: interaction.type }),
235
+ );
221
236
  sourceRegion.classList.add("qti3-graphic-gap-source-region");
222
237
  const choicesWidth = positivePixelValue(interaction.attributes["data-choices-container-width"]);
223
238
  if (choicesWidth !== undefined) sourceRegion.style.maxInlineSize = `${choicesWidth}px`;
@@ -260,7 +275,9 @@ function renderGraphicGapMatchResponse(
260
275
  commit();
261
276
  };
262
277
  const targetLabel = (gap: QtiChoice, index: number) =>
263
- gap.attributes["aria-label"] || gap.attributes["hotspot-label"] || `Target ${index + 1}`;
278
+ gap.attributes["aria-label"] ||
279
+ gap.attributes["hotspot-label"] ||
280
+ messages.message("graphicGapTargetLabel", { index: index + 1 });
264
281
  const renderTargetButton = (gap: QtiChoice, index: number): HTMLButtonElement => {
265
282
  const assigned = assignments.get(gap.identifier);
266
283
  const label = targetLabel(gap, index);
@@ -271,7 +288,9 @@ function renderGraphicGapMatchResponse(
271
288
  button.dataset.selected = assigned ? "true" : "false";
272
289
  button.setAttribute(
273
290
  "aria-label",
274
- assigned ? `${label}, assigned ${assigned.text}` : `${label}, empty`,
291
+ assigned
292
+ ? messages.message("gapAssignedState", { label, assigned: assigned.text })
293
+ : messages.message("gapEmptyState", { label }),
275
294
  );
276
295
  button.addEventListener("dragover", (event) => {
277
296
  event.preventDefault();
@@ -308,8 +327,8 @@ function renderGraphicGapMatchResponse(
308
327
  }
309
328
  summary.textContent =
310
329
  assignments.size > 0
311
- ? `${assignments.size} ${assignments.size === 1 ? "label" : "labels"} placed.`
312
- : "No labels placed.";
330
+ ? messages.message("gapLabelsPlacedCount", { count: assignments.size })
331
+ : messages.message("gapNoLabelsPlaced");
313
332
  };
314
333
 
315
334
  for (const source of sources) {
@@ -11,18 +11,17 @@ import {
11
11
  objectHeight,
12
12
  objectWidth,
13
13
  placeHotspotButton,
14
- readableType,
15
14
  responseGroup,
16
15
  valueToStrings,
17
16
  } from "../interaction-support.js";
18
- import type { QtiPlayerMessages } from "../player-messages.js";
17
+ import type { PlayerMessageResolver } from "../player-message-resolver.js";
19
18
  import { exceedsHotspotMatchMax, maximumAllowedResponses } from "../response-limits.js";
20
19
 
21
20
  export function renderGraphicAssociateResponse(
22
21
  interaction: QtiInteraction,
23
22
  update: (value: QtiValue) => void,
24
23
  currentValue: QtiValue,
25
- messages: QtiPlayerMessages,
24
+ messages: PlayerMessageResolver,
26
25
  ): HTMLElement {
27
26
  const group = responseGroup();
28
27
 
@@ -47,14 +46,17 @@ export function renderGraphicAssociateResponse(
47
46
  const surface = document.createElement("div");
48
47
  applyGraphicSurfaceLayout(surface, width, height, "qti3-graphic-associate-surface");
49
48
  surface.role = "group";
50
- surface.setAttribute("aria-label", `${readableType(interaction.type)} hotspots`);
49
+ surface.setAttribute(
50
+ "aria-label",
51
+ messages.message("interactionHotspots", { type: interaction.type }),
52
+ );
51
53
 
52
54
  const object = interaction.object;
53
55
  if (object) {
54
56
  appendGraphicObjectImage(
55
57
  surface,
56
58
  object,
57
- object.text || `${readableType(interaction.type)} image`,
59
+ object.text || messages.message("interactionImageAlt", { type: interaction.type }),
58
60
  );
59
61
  }
60
62
 
@@ -69,7 +71,10 @@ export function renderGraphicAssociateResponse(
69
71
  summary.setAttribute("aria-live", "polite");
70
72
  const pairList = document.createElement("ul");
71
73
  pairList.className = "qti3-pair-list";
72
- pairList.setAttribute("aria-label", `${readableType(interaction.type)} selected pairs`);
74
+ pairList.setAttribute(
75
+ "aria-label",
76
+ messages.message("interactionSelectedPairsList", { type: interaction.type }),
77
+ );
73
78
 
74
79
  const commit = () => {
75
80
  if (interaction.responseCardinality === "single") update(selectedPairs[0] ?? null);
@@ -214,18 +219,18 @@ export function renderGraphicAssociateResponse(
214
219
  button.dataset.selected = isActive || isPaired ? "true" : "false";
215
220
  }
216
221
  summary.textContent = selectedHotspot
217
- ? messages.hotspotSelectedChooseAnother({
222
+ ? messages.message("hotspotSelectedChooseAnother", {
218
223
  label: hotspotDisplayLabel(selectedHotspot, choices),
219
224
  })
220
225
  : selectedPairs.length > 0
221
- ? messages.associationsMade({ count: selectedPairs.length })
222
- : messages.noAssociationsMade();
226
+ ? messages.message("associationsMade", { count: selectedPairs.length })
227
+ : messages.message("noAssociationsMade");
223
228
  pairList.replaceChildren(
224
229
  ...selectedPairs.map((pair) => {
225
230
  const [source = "", target = ""] = pair.split(" ");
226
231
  const sourceChoice = choices.find((choice) => choice.identifier === source);
227
232
  const targetChoice = choices.find((choice) => choice.identifier === target);
228
- const pairLabel = messages.associationPairLabel({
233
+ const pairLabel = messages.message("associationPairLabel", {
229
234
  source: sourceChoice ? hotspotDisplayLabel(sourceChoice, choices) : source,
230
235
  target: targetChoice ? hotspotDisplayLabel(targetChoice, choices) : target,
231
236
  });
@@ -7,17 +7,16 @@ import {
7
7
  objectHeight,
8
8
  objectWidth,
9
9
  placeHotspotButton,
10
- readableType,
11
10
  responseGroup,
12
11
  valueToStrings,
13
12
  } from "../interaction-support.js";
14
- import type { QtiPlayerMessages } from "../player-messages.js";
13
+ import type { PlayerMessageResolver } from "../player-message-resolver.js";
15
14
 
16
15
  export function renderHotspotResponse(
17
16
  interaction: QtiInteraction,
18
17
  update: (value: QtiValue) => void,
19
18
  currentValue: QtiValue,
20
- messages: QtiPlayerMessages,
19
+ messages: PlayerMessageResolver,
21
20
  ): HTMLElement {
22
21
  const group = responseGroup();
23
22
 
@@ -37,7 +36,7 @@ export function renderHotspotResponse(
37
36
  appendGraphicObjectImage(
38
37
  surface,
39
38
  object,
40
- object.text || `${readableType(interaction.type)} image`,
39
+ object.text || messages.message("interactionImageAlt", { type: interaction.type }),
41
40
  );
42
41
  }
43
42
 
@@ -46,7 +45,7 @@ export function renderHotspotResponse(
46
45
  const selectedSummary = document.createElement("p");
47
46
  selectedSummary.className = "qti3-selection-summary";
48
47
  selectedSummary.setAttribute("aria-live", "polite");
49
- selectedSummary.textContent = messages.noRegionSelected();
48
+ selectedSummary.textContent = messages.message("noRegionSelected");
50
49
  const syncSelected = () => {
51
50
  for (const button of surface.querySelectorAll<HTMLButtonElement>("button")) {
52
51
  const isSelected = selected.has(button.dataset.choiceIdentifier ?? "");
@@ -54,7 +53,12 @@ export function renderHotspotResponse(
54
53
  button.dataset.selected = isSelected ? "true" : "false";
55
54
  }
56
55
  selectedSummary.textContent =
57
- selected.size > 0 ? `Selected ${[...selected].join(", ")}` : messages.noRegionSelected();
56
+ selected.size > 0
57
+ ? messages.message("hotspotSelectionSummary", {
58
+ selection: [...selected].join(", "),
59
+ count: selected.size,
60
+ })
61
+ : messages.message("noRegionSelected");
58
62
  };
59
63
  for (const choice of choices) {
60
64
  const button = document.createElement("button");
@@ -4,17 +4,17 @@ import {
4
4
  missingChoicesMessage,
5
5
  valueToStrings,
6
6
  } from "../interaction-support.js";
7
- import type { QtiPlayerMessages } from "../player-messages.js";
7
+ import type { PlayerMessageResolver } from "../player-message-resolver.js";
8
8
  import { interactionLabel } from "./interaction-label.js";
9
9
 
10
10
  function appendOptions(
11
11
  select: HTMLSelectElement,
12
12
  choices: QtiChoice[],
13
- messages: QtiPlayerMessages,
13
+ messages: PlayerMessageResolver,
14
14
  ): void {
15
15
  const empty = document.createElement("option");
16
16
  empty.value = "";
17
- empty.textContent = messages.inlineChoicePrompt();
17
+ empty.textContent = messages.message("inlineChoicePrompt");
18
18
  select.append(empty);
19
19
  for (const choice of choices) {
20
20
  const option = document.createElement("option");
@@ -28,7 +28,7 @@ export function renderSelect(
28
28
  interaction: QtiInteraction,
29
29
  update: (value: QtiValue) => void,
30
30
  currentValue: QtiValue,
31
- messages: QtiPlayerMessages,
31
+ messages: PlayerMessageResolver,
32
32
  ): HTMLElement {
33
33
  const choices = interactionChoices(interaction);
34
34
  if (choices.length === 0) return missingChoicesMessage(interaction);
@@ -1,5 +1,5 @@
1
1
  import type { QtiInteraction, QtiValue } from "@longsightgroup/qti3-core";
2
- import type { QtiPlayerMessages } from "../player-messages.js";
2
+ import type { PlayerMessageResolver } from "../player-message-resolver.js";
3
3
  import { renderGraphicOrderResponse } from "../reorder/graphic-order-interaction.js";
4
4
  import { renderOrderedResponse } from "../reorder/order-interaction.js";
5
5
  import { renderChoice } from "./choice-interaction.js";
@@ -24,7 +24,7 @@ export interface InteractionResponseContext {
24
24
  interaction: QtiInteraction;
25
25
  update: (value: QtiValue) => void;
26
26
  currentValue: QtiValue;
27
- messages: QtiPlayerMessages;
27
+ messages: PlayerMessageResolver;
28
28
  isCompleted: () => boolean;
29
29
  endAttempt: () => void;
30
30
  renderPortableCustom: (
@@ -81,8 +81,8 @@ export const interactionRegistry: InteractionRegistryEntry[] = [
81
81
  id: "gapMatch",
82
82
  matches: (interaction) =>
83
83
  interaction.type === "gapMatch" || interaction.type === "graphicGapMatch",
84
- render: ({ interaction, update, currentValue }) =>
85
- renderGapMatchResponse(interaction, update, currentValue),
84
+ render: ({ interaction, update, currentValue, messages }) =>
85
+ renderGapMatchResponse(interaction, update, currentValue, messages),
86
86
  },
87
87
  {
88
88
  id: "graphicAssociate",
@@ -117,8 +117,8 @@ export const interactionRegistry: InteractionRegistryEntry[] = [
117
117
  {
118
118
  id: "choice",
119
119
  matches: usesChoiceSet,
120
- render: ({ interaction, update, currentValue }) =>
121
- renderChoice(interaction, update, currentValue),
120
+ render: ({ interaction, update, currentValue, messages }) =>
121
+ renderChoice(interaction, update, currentValue, messages),
122
122
  },
123
123
  {
124
124
  id: "inlineChoice",
@@ -129,8 +129,8 @@ export const interactionRegistry: InteractionRegistryEntry[] = [
129
129
  {
130
130
  id: "extendedText",
131
131
  matches: (interaction) => interaction.type === "extendedText",
132
- render: ({ interaction, update, currentValue }) =>
133
- renderTextResponse(interaction, update, "extended", currentValue),
132
+ render: ({ interaction, update, currentValue, messages }) =>
133
+ renderTextResponse(interaction, update, "extended", currentValue, messages),
134
134
  },
135
135
  {
136
136
  id: "selectPoint",
@@ -159,14 +159,14 @@ export const interactionRegistry: InteractionRegistryEntry[] = [
159
159
  {
160
160
  id: "textEntry",
161
161
  matches: (interaction) => interaction.type === "textEntry",
162
- render: ({ interaction, update, currentValue }) =>
163
- renderTextResponse(interaction, update, "entry", currentValue),
162
+ render: ({ interaction, update, currentValue, messages }) =>
163
+ renderTextResponse(interaction, update, "entry", currentValue, messages),
164
164
  },
165
165
  {
166
166
  id: "slider",
167
167
  matches: (interaction) => interaction.type === "slider",
168
- render: ({ interaction, update, currentValue }) =>
169
- renderSliderResponse(interaction, update, currentValue),
168
+ render: ({ interaction, update, currentValue, messages }) =>
169
+ renderSliderResponse(interaction, update, currentValue, messages),
170
170
  },
171
171
  {
172
172
  id: "upload",
@@ -1,7 +1,7 @@
1
1
  import type { QtiChoice, QtiInteraction, QtiValue } from "@longsightgroup/qti3-core";
2
2
  import { removeButton } from "../controls/remove-button.js";
3
3
  import { missingChoicesMessage, responseGroup, valueToStrings } from "../interaction-support.js";
4
- import type { QtiPlayerMessages } from "../player-messages.js";
4
+ import type { PlayerMessageResolver } from "../player-message-resolver.js";
5
5
  import { parseUnlimitedMaximum } from "../response-limits.js";
6
6
  import { choiceText, sourceChoices, targetChoices, tokenButton, tokenRegion } from "./shared.js";
7
7
 
@@ -9,7 +9,7 @@ export function renderMatchResponse(
9
9
  interaction: QtiInteraction,
10
10
  update: (value: QtiValue) => void,
11
11
  currentValue: QtiValue,
12
- messages: QtiPlayerMessages,
12
+ messages: PlayerMessageResolver,
13
13
  ): HTMLElement {
14
14
  const group = responseGroup();
15
15
 
@@ -26,13 +26,13 @@ export function renderMatchResponse(
26
26
 
27
27
  const selector = document.createElement("div");
28
28
  selector.className = "qti3-match-selector";
29
- const sourceRegion = tokenRegion("Match sources");
29
+ const sourceRegion = tokenRegion(messages.message("matchSourcesBank"));
30
30
  sourceRegion.classList.add("qti3-match-source-bank");
31
- const targetRegion = tokenRegion("Match targets");
31
+ const targetRegion = tokenRegion(messages.message("matchTargetsBank"));
32
32
  targetRegion.classList.add("qti3-match-target-bank");
33
33
  const pairList = document.createElement("ul");
34
34
  pairList.className = "qti3-pair-list";
35
- pairList.setAttribute("aria-label", "Match selected pairs");
35
+ pairList.setAttribute("aria-label", messages.message("matchSelectedPairsList"));
36
36
 
37
37
  const commit = () => {
38
38
  if (interaction.responseCardinality === "single") update(selectedPairs[0] ?? null);
@@ -68,7 +68,10 @@ export function renderMatchResponse(
68
68
  pairList.replaceChildren(
69
69
  ...selectedPairs.map((pair) => {
70
70
  const [source, target] = pair.split(" ");
71
- const label = `${choiceText(sources, source)} to ${choiceText(targets, target)}`;
71
+ const label = messages.message("associationPairLabel", {
72
+ source: choiceText(sources, source),
73
+ target: choiceText(targets, target),
74
+ });
72
75
  const item = document.createElement("li");
73
76
  item.className = "qti3-pair-chip";
74
77
  const text = document.createElement("span");
@@ -1,12 +1,7 @@
1
1
  import type { QtiChoice, QtiInteraction, QtiValue } from "@longsightgroup/qti3-core";
2
2
  import { removeButton } from "../controls/remove-button.js";
3
- import {
4
- missingChoicesMessage,
5
- readableType,
6
- responseGroup,
7
- valueToStrings,
8
- } from "../interaction-support.js";
9
- import type { QtiPlayerMessages } from "../player-messages.js";
3
+ import { missingChoicesMessage, responseGroup, valueToStrings } from "../interaction-support.js";
4
+ import type { PlayerMessageResolver } from "../player-message-resolver.js";
10
5
  import {
11
6
  choiceText,
12
7
  pairRegionLabels,
@@ -20,7 +15,7 @@ export function renderPairResponse(
20
15
  interaction: QtiInteraction,
21
16
  update: (value: QtiValue) => void,
22
17
  currentValue: QtiValue,
23
- messages: QtiPlayerMessages,
18
+ messages: PlayerMessageResolver,
24
19
  ): HTMLElement {
25
20
  const group = responseGroup();
26
21
 
@@ -33,15 +28,24 @@ export function renderPairResponse(
33
28
  const selectedPairs: string[] = valueToStrings(currentValue);
34
29
  let selectedSource: QtiChoice | undefined;
35
30
  let selectedTarget: QtiChoice | undefined;
36
- const labels = pairRegionLabels(interaction);
31
+ const labels = pairRegionLabels(interaction, messages);
37
32
 
38
- const sourceRegion = tokenRegion(`${readableType(interaction.type)} sources`, labels.source);
39
- const targetRegion = tokenRegion(`${readableType(interaction.type)} targets`, labels.target);
33
+ const sourceRegion = tokenRegion(
34
+ messages.message("interactionSourcesBank", { type: interaction.type }),
35
+ labels.source,
36
+ );
37
+ const targetRegion = tokenRegion(
38
+ messages.message("interactionTargetsBank", { type: interaction.type }),
39
+ labels.target,
40
+ );
40
41
  const selector = document.createElement("div");
41
42
  selector.className = "qti3-pair-selector";
42
43
  const pairList = document.createElement("ul");
43
44
  pairList.className = "qti3-pair-list";
44
- pairList.setAttribute("aria-label", `${readableType(interaction.type)} selected pairs`);
45
+ pairList.setAttribute(
46
+ "aria-label",
47
+ messages.message("interactionSelectedPairsList", { type: interaction.type }),
48
+ );
45
49
  let draggedSource: string | undefined;
46
50
 
47
51
  const commit = () => {
@@ -84,11 +88,15 @@ export function renderPairResponse(
84
88
  pairList.replaceChildren(
85
89
  ...selectedPairs.map((pair) => {
86
90
  const [source, target] = pair.split(" ");
91
+ const label = messages.message("associationPairLabel", {
92
+ source: choiceText(sources, source),
93
+ target: choiceText(targets, target),
94
+ });
87
95
  const item = document.createElement("li");
88
96
  item.className = "qti3-pair-chip";
89
97
  const text = document.createElement("span");
90
- text.textContent = `${choiceText(sources, source)} to ${choiceText(targets, target)}`;
91
- const remove = removeButton(text.textContent, messages);
98
+ text.textContent = label;
99
+ const remove = removeButton(label, messages);
92
100
  remove.addEventListener("click", () => {
93
101
  const index = selectedPairs.indexOf(pair);
94
102
  if (index >= 0) selectedPairs.splice(index, 1);
@@ -6,11 +6,10 @@ import {
6
6
  appendGraphicObjectImage,
7
7
  objectIsImage,
8
8
  percent,
9
- readableType,
10
9
  responseGroup,
11
10
  } from "../interaction-support.js";
12
11
  import { movementButton } from "../movement.js";
13
- import type { QtiPlayerMessages } from "../player-messages.js";
12
+ import type { PlayerMessageResolver } from "../player-message-resolver.js";
14
13
  import {
15
14
  objectAssetHeight,
16
15
  objectAssetWidth,
@@ -22,11 +21,14 @@ export function renderPositionObjectResponse(
22
21
  interaction: QtiInteraction,
23
22
  update: (value: QtiValue) => void,
24
23
  currentValue: QtiValue,
25
- messages: QtiPlayerMessages,
24
+ messages: PlayerMessageResolver,
26
25
  ): HTMLElement {
27
26
  const group = responseGroup();
28
27
  group.role = "group";
29
- group.setAttribute("aria-label", `${readableType(interaction.type)} object placement response`);
28
+ group.setAttribute(
29
+ "aria-label",
30
+ messages.message("interactionPlacementResponse", { type: interaction.type }),
31
+ );
30
32
 
31
33
  const stageObject = interaction.positionObjectStage ?? interaction.object;
32
34
  const movableObject = interaction.positionObjectStage ? interaction.object : undefined;
@@ -43,7 +45,10 @@ export function renderPositionObjectResponse(
43
45
  stage.style.setProperty("--qti3-position-object-marker-block-size", `${movableHeight}px`);
44
46
  stage.tabIndex = 0;
45
47
  stage.role = "group";
46
- stage.setAttribute("aria-label", `${readableType(interaction.type)} placement stage`);
48
+ stage.setAttribute(
49
+ "aria-label",
50
+ messages.message("interactionPlacementStage", { type: interaction.type }),
51
+ );
47
52
 
48
53
  if (stageObject?.data && objectIsImage(stageObject)) {
49
54
  appendGraphicObjectImage(stage, stageObject, stageObject.text || "");
@@ -52,7 +57,7 @@ export function renderPositionObjectResponse(
52
57
  const marker = document.createElement("button");
53
58
  marker.type = "button";
54
59
  marker.className = "qti3-position-object-marker";
55
- marker.setAttribute("aria-label", messages.movableObject());
60
+ marker.setAttribute("aria-label", messages.message("movableObject"));
56
61
  applyPositionObjectMarkerSize(marker, movableWidth, movableHeight);
57
62
  marker.draggable = false;
58
63
 
@@ -62,12 +67,13 @@ export function renderPositionObjectResponse(
62
67
  image.alt = "";
63
68
  marker.append(image);
64
69
  } else {
65
- marker.textContent = messages.placeObject();
70
+ marker.textContent = messages.message("placeObject");
66
71
  }
67
72
  stage.append(marker);
68
73
 
69
74
  const coordinate = document.createElement("output");
70
- coordinate.className = "qti3-coordinate-output";
75
+ coordinate.className = "qti3-coordinate-output qti-visually-hidden";
76
+ coordinate.setAttribute("aria-live", "polite");
71
77
  const clamp = () => {
72
78
  point.x = Math.max(0, Math.min(width, point.x));
73
79
  point.y = Math.max(0, Math.min(height, point.y));
@@ -90,10 +96,10 @@ export function renderPositionObjectResponse(
90
96
  `calc(100% + ${Math.round(movableHeight / 2 + 8)}px)`,
91
97
  );
92
98
  coordinate.value = "";
93
- coordinate.textContent = "Object not placed";
99
+ coordinate.textContent = messages.message("objectNotPlaced");
94
100
  stage.setAttribute(
95
101
  "aria-label",
96
- `${readableType(interaction.type)} placement stage, object not placed`,
102
+ messages.message("interactionPlacementStageEmpty", { type: interaction.type }),
97
103
  );
98
104
  return;
99
105
  }
@@ -107,10 +113,11 @@ export function renderPositionObjectResponse(
107
113
  `${percent(point.y, height)}%`,
108
114
  );
109
115
  coordinate.value = pointToString(point);
110
- coordinate.textContent = `Object positioned at ${pointToString(point)}`;
116
+ const coordinates = pointToString(point);
117
+ coordinate.textContent = messages.message("objectPositionedAt", { coordinates });
111
118
  stage.setAttribute(
112
119
  "aria-label",
113
- `${readableType(interaction.type)} placement stage, object at ${pointToString(point)}`,
120
+ messages.message("interactionPlacementStageAt", { type: interaction.type, coordinates }),
114
121
  );
115
122
  };
116
123
  const pointFromPointer = (event: MouseEvent | PointerEvent) => {
@@ -200,7 +207,9 @@ export function renderPositionObjectResponse(
200
207
  ["down", 0, 1],
201
208
  ] as const) {
202
209
  controls.append(
203
- movementButton(direction, messages.moveObject({ direction }), () => moveBy(dx, dy)),
210
+ movementButton(direction, messages.message("moveObject", { direction }), () =>
211
+ moveBy(dx, dy),
212
+ ),
204
213
  );
205
214
  }
206
215