@khanacademy/perseus 21.5.0 → 22.0.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 (65) hide show
  1. package/dist/article-renderer.d.ts +6 -0
  2. package/dist/components/highlighting/ui/highlight-set-renderer.d.ts +14 -0
  3. package/dist/components/highlighting/ui/highlighting-ui.d.ts +17 -0
  4. package/dist/components/i18n-context.d.ts +16 -0
  5. package/dist/components/input-with-examples.d.ts +6 -0
  6. package/dist/components/math-input.d.ts +22 -2
  7. package/dist/components/number-input.d.ts +6 -0
  8. package/dist/es/index.css +3 -0
  9. package/dist/es/index.js +953 -530
  10. package/dist/es/index.js.map +1 -1
  11. package/dist/es/strings.js +312 -0
  12. package/dist/es/strings.js.map +1 -0
  13. package/dist/hint-renderer.d.ts +6 -0
  14. package/dist/hints-renderer.d.ts +6 -0
  15. package/dist/index.css +3 -0
  16. package/dist/index.d.ts +3 -2
  17. package/dist/index.js +1175 -565
  18. package/dist/index.js.map +1 -1
  19. package/dist/multi-items/multi-renderer.d.ts +6 -0
  20. package/dist/perseus-api.d.ts +5 -3
  21. package/dist/perseus-types.d.ts +18 -8
  22. package/dist/renderer.d.ts +2 -0
  23. package/dist/server-item-renderer.d.ts +11 -3
  24. package/dist/strings.d.ts +190 -0
  25. package/dist/strings.js +395 -119
  26. package/dist/strings.js.map +1 -0
  27. package/dist/types.d.ts +10 -6
  28. package/dist/util/answer-types.d.ts +4 -12
  29. package/dist/util.d.ts +2 -1
  30. package/dist/widgets/categorizer.d.ts +8 -1
  31. package/dist/widgets/definition.d.ts +6 -0
  32. package/dist/widgets/deprecated-standin/index.d.ts +6 -0
  33. package/dist/widgets/explanation.d.ts +6 -0
  34. package/dist/widgets/expression.d.ts +42 -19
  35. package/dist/widgets/graded-group-answer-bar.d.ts +6 -0
  36. package/dist/widgets/graded-group.d.ts +6 -0
  37. package/dist/widgets/group.d.ts +6 -0
  38. package/dist/widgets/image.d.ts +6 -0
  39. package/dist/widgets/input-number.d.ts +8 -1
  40. package/dist/widgets/interactive-graph.d.ts +6 -0
  41. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +21 -0
  42. package/dist/widgets/interactive-graphs/locked-line.d.ts +4 -0
  43. package/dist/widgets/interactive-graphs/locked-point.d.ts +4 -0
  44. package/dist/widgets/interactive-graphs/mafs-graph.d.ts +1 -0
  45. package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +1 -0
  46. package/dist/widgets/label-image/marker.d.ts +6 -0
  47. package/dist/widgets/label-image.d.ts +16 -6
  48. package/dist/widgets/matcher.d.ts +6 -0
  49. package/dist/widgets/matrix.d.ts +9 -1
  50. package/dist/widgets/molecule.d.ts +6 -0
  51. package/dist/widgets/number-line.d.ts +6 -0
  52. package/dist/widgets/numeric-input.d.ts +8 -1
  53. package/dist/widgets/passage-ref-target.d.ts +6 -0
  54. package/dist/widgets/passage-ref.d.ts +6 -0
  55. package/dist/widgets/passage.d.ts +6 -0
  56. package/dist/widgets/plotter.d.ts +6 -0
  57. package/dist/widgets/radio/choice-a11y-text.d.ts +2 -1
  58. package/dist/widgets/radio/radio.d.ts +8 -1
  59. package/dist/widgets/radio/util.d.ts +2 -1
  60. package/dist/widgets/table.d.ts +7 -0
  61. package/dist/widgets/unit.d.ts +8 -1
  62. package/dist/widgets/video.d.ts +6 -0
  63. package/dist/widgets.d.ts +2 -1
  64. package/package.json +50 -39
  65. package/dist/widgets/label-image/strings.d.ts +0 -3
package/dist/index.js CHANGED
@@ -6,7 +6,6 @@ var KAS = require('@khanacademy/kas');
6
6
  var mathInput = require('@khanacademy/math-input');
7
7
  var PerseusLinter = require('@khanacademy/perseus-linter');
8
8
  var wonderBlocksCore = require('@khanacademy/wonder-blocks-core');
9
- var i18n = require('@khanacademy/wonder-blocks-i18n');
10
9
  var Tooltip$1 = require('@khanacademy/wonder-blocks-tooltip');
11
10
  var classNames = require('classnames');
12
11
  var React = require('react');
@@ -64,7 +63,6 @@ function _interopNamespace(e) {
64
63
 
65
64
  var KAS__namespace = /*#__PURE__*/_interopNamespace(KAS);
66
65
  var PerseusLinter__namespace = /*#__PURE__*/_interopNamespace(PerseusLinter);
67
- var i18n__namespace = /*#__PURE__*/_interopNamespace(i18n);
68
66
  var Tooltip__default = /*#__PURE__*/_interopDefaultLegacy(Tooltip$1);
69
67
  var classNames__default = /*#__PURE__*/_interopDefaultLegacy(classNames);
70
68
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
@@ -102,6 +100,290 @@ function _extends() {
102
100
  return _extends.apply(this, arguments);
103
101
  }
104
102
 
103
+ /**
104
+ * The translated strings that are used to render Perseus.
105
+ */
106
+
107
+ /**
108
+ * Mock strings for the Perseus package, to be used for tests and Storybook.
109
+ */
110
+ const mockStrings = {
111
+ closeKeypad: "close math keypad",
112
+ openKeypad: "open math keypad",
113
+ removeHighlight: "Remove highlight",
114
+ addHighlight: "Add highlight",
115
+ hintPos: _ref => {
116
+ let {
117
+ pos
118
+ } = _ref;
119
+ return `Hint #${pos}`;
120
+ },
121
+ errorRendering: _ref2 => {
122
+ let {
123
+ error
124
+ } = _ref2;
125
+ return `Error rendering: ${error}`;
126
+ },
127
+ APPROXIMATED_PI_ERROR: "Your answer is close, but you may " + "have approximated pi. Enter your " + "answer as a multiple of pi, like " + "<code>12\\ \\text{pi}</code> or " + "<code>2/3\\ \\text{pi}</code>",
128
+ EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
129
+ NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
130
+ MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\%</code> at the end.",
131
+ MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
132
+ WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
133
+ WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
134
+ invalidSelection: "Make sure you select something for every row.",
135
+ ERROR_TITLE: "Oops!",
136
+ ERROR_MESSAGE: "Sorry, I don't understand that!",
137
+ hints: "Hints",
138
+ getAnotherHint: "Get another hint",
139
+ deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
140
+ keepTrying: "Keep trying",
141
+ tryAgain: "Try again",
142
+ check: "Check",
143
+ correctExcited: "Correct!",
144
+ nextQuestion: "Next question",
145
+ skipToTitle: _ref3 => {
146
+ let {
147
+ title
148
+ } = _ref3;
149
+ return `Skip to ${title}`;
150
+ },
151
+ current: "Current",
152
+ correct: "Correct",
153
+ correctSelected: "Correct (selected)",
154
+ correctCrossedOut: "Correct (but you crossed it out)",
155
+ incorrect: "Incorrect",
156
+ incorrectSelected: "Incorrect (selected)",
157
+ hideExplanation: "Hide explanation",
158
+ explain: "Explain",
159
+ INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
160
+ DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
161
+ DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
162
+ integerExample: "an integer, like $6$",
163
+ properExample: "a *proper* fraction, like $1/2$ or $6/10$",
164
+ simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
165
+ improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
166
+ simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
167
+ mixedExample: "a mixed number, like $1\\ 3/4$",
168
+ decimalExample: "an *exact* decimal, like $0.75$",
169
+ percentExample: "a percent, like $12.34\\%$",
170
+ piExample: "a multiple of pi, like $12\\ \\text{pi}$ or $2/3\\ \\text{pi}$",
171
+ yourAnswer: "**Your answer should be** ",
172
+ yourAnswerLabel: "Your answer:",
173
+ addPoints: "Click to add points",
174
+ addVertices: "Click to add vertices",
175
+ tapMultiple: "Tap each dot on the image to select all answers that apply.",
176
+ tapSingle: "Tap each dot on the image to select an answer.",
177
+ clickMultiple: "Click each dot on the image to select all answers that apply.",
178
+ clickSingle: "Click each dot on the image to select an answer.",
179
+ choices: "Choices:",
180
+ answers: _ref4 => {
181
+ let {
182
+ num
183
+ } = _ref4;
184
+ return num === 1 ? `${num} answer` : `${num} answers`;
185
+ },
186
+ hideAnswersToggleLabel: "Hide answer choices",
187
+ moves: _ref5 => {
188
+ let {
189
+ num
190
+ } = _ref5;
191
+ return num === 1 ? `Moves: ${num}` : `Moves: ${num}`;
192
+ },
193
+ clickTiles: "Click on the tiles to change the lights.",
194
+ turnOffLights: "You must turn on all of the lights to continue.",
195
+ fillAllCells: "Make sure you fill in all cells in the matrix.",
196
+ molecularDrawing: _ref6 => {
197
+ let {
198
+ content
199
+ } = _ref6;
200
+ return `A molecular structure drawing. SMILES notation: ${content}`;
201
+ },
202
+ switchDirection: "Switch direction",
203
+ circleOpen: "Make circle open",
204
+ circleFilled: "Make circle filled",
205
+ numDivisions: "Number of divisions:",
206
+ divisions: _ref7 => {
207
+ let {
208
+ divRangeString
209
+ } = _ref7;
210
+ return `Please make sure the number of divisions is in the range ${divRangeString}.`;
211
+ },
212
+ lineRange: _ref8 => {
213
+ let {
214
+ lineRange
215
+ } = _ref8;
216
+ return `lines ${lineRange}`;
217
+ },
218
+ lineNumber: _ref9 => {
219
+ let {
220
+ lineNumber
221
+ } = _ref9;
222
+ return `line ${lineNumber}`;
223
+ },
224
+ symbolPassage: _ref10 => {
225
+ let {
226
+ questionSymbol,
227
+ questionNumber
228
+ } = _ref10;
229
+ return `The symbol ${questionSymbol} indicates that question ${questionNumber} references this portion of the passage.`;
230
+ },
231
+ symbolQuestion: _ref11 => {
232
+ let {
233
+ sentenceSymbol
234
+ } = _ref11;
235
+ return ` The symbol ${sentenceSymbol} indicates that the following sentence is referenced in a question.`;
236
+ },
237
+ lineLabel: "Line",
238
+ beginningPassage: "Beginning of reading passage.",
239
+ beginningFootnotes: "Beginning of reading passage footnotes.",
240
+ endPassage: "End of reading passage.",
241
+ questionMarker: _ref12 => {
242
+ let {
243
+ number
244
+ } = _ref12;
245
+ return `[Marker for question ${number}]`;
246
+ },
247
+ circleMarker: _ref13 => {
248
+ let {
249
+ number
250
+ } = _ref13;
251
+ return `[Circle marker ${number}]`;
252
+ },
253
+ sentenceMarker: _ref14 => {
254
+ let {
255
+ number
256
+ } = _ref14;
257
+ return `[Sentence ${number}]`;
258
+ },
259
+ dragHandles: "Drag handles to make graph",
260
+ tapAddPoints: "Tap to add points",
261
+ false: "False",
262
+ true: "True",
263
+ no: "No",
264
+ yes: "Yes",
265
+ chooseCorrectNum: "Please choose the correct number of answers.",
266
+ notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
267
+ noneOfTheAbove: "None of the above",
268
+ chooseNumAnswers: _ref15 => {
269
+ let {
270
+ numCorrect
271
+ } = _ref15;
272
+ return `Choose ${numCorrect} answers:`;
273
+ },
274
+ chooseAllAnswers: "Choose all answers that apply:",
275
+ chooseOneAnswer: "Choose 1 answer:",
276
+ choiceCheckedCorrect: _ref16 => {
277
+ let {
278
+ letter
279
+ } = _ref16;
280
+ return `(Choice ${letter}, Checked, Correct)`;
281
+ },
282
+ choiceCrossedOutCorrect: _ref17 => {
283
+ let {
284
+ letter
285
+ } = _ref17;
286
+ return `(Choice ${letter}, Crossed out, Correct)`;
287
+ },
288
+ choiceCorrect: _ref18 => {
289
+ let {
290
+ letter
291
+ } = _ref18;
292
+ return `(Choice ${letter}, Correct)`;
293
+ },
294
+ choiceCheckedIncorrect: _ref19 => {
295
+ let {
296
+ letter
297
+ } = _ref19;
298
+ return `(Choice ${letter}, Checked, Incorrect)`;
299
+ },
300
+ choiceCrossedOutIncorrect: _ref20 => {
301
+ let {
302
+ letter
303
+ } = _ref20;
304
+ return `(Choice ${letter}, Crossed out, Incorrect)`;
305
+ },
306
+ choiceIncorrect: _ref21 => {
307
+ let {
308
+ letter
309
+ } = _ref21;
310
+ return `(Choice ${letter}, Incorrect)`;
311
+ },
312
+ choiceChecked: _ref22 => {
313
+ let {
314
+ letter
315
+ } = _ref22;
316
+ return `(Choice ${letter}, Checked)`;
317
+ },
318
+ choiceCrossedOut: _ref23 => {
319
+ let {
320
+ letter
321
+ } = _ref23;
322
+ return `(Choice ${letter}, Crossed out)`;
323
+ },
324
+ choice: _ref24 => {
325
+ let {
326
+ letter
327
+ } = _ref24;
328
+ return `(Choice ${letter})`;
329
+ },
330
+ crossOut: "Cross out",
331
+ crossOutOption: "Cross out option",
332
+ crossOutChoice: _ref25 => {
333
+ let {
334
+ letter
335
+ } = _ref25;
336
+ return `Cross out Choice ${letter}`;
337
+ },
338
+ bringBack: "Bring back",
339
+ openMenuForChoice: _ref26 => {
340
+ let {
341
+ letter
342
+ } = _ref26;
343
+ return `Open menu for Choice ${letter}`;
344
+ },
345
+ letters: "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z",
346
+ rightArrow: "Reaction arrow pointing to the right.",
347
+ dontUnderstandUnits: "I couldn't understand those units.",
348
+ checkSigFigs: "Check your significant figures.",
349
+ answerNumericallyIncorrect: "That answer is numerically incorrect.",
350
+ checkUnits: "Check your units.",
351
+ dontUnderstand: "I don't understand that",
352
+ loading: "Loading...",
353
+ videoTranscript: "See video transcript",
354
+ somethingWrong: "Something went wrong.",
355
+ videoWrapper: "Khan Academy video wrapper"
356
+ };
357
+
358
+ /**
359
+ * PerseusI18nContext provides a way to set the strings and locale that are
360
+ * used inside the Perseus package.
361
+ *
362
+ */
363
+ // @ts-expect-error - TS2322 - Type 'Context<{ strings: {}; locale: string; }>' is not assignable to type 'Context<I18nContextType>'.
364
+ const PerseusI18nContext = /*#__PURE__*/React__namespace.createContext(process.env.NODE_ENV === "test" || process.env.STORYBOOK ? {
365
+ strings: mockStrings,
366
+ locale: "en"
367
+ } :
368
+ // We want to return null here, not an empty object, so that we
369
+ // are will throw an error when attempting to access the
370
+ // undefined locale or strings, making it easier to debug.
371
+ null);
372
+ function PerseusI18nContextProvider(_ref) {
373
+ let {
374
+ children,
375
+ strings,
376
+ locale
377
+ } = _ref;
378
+ return /*#__PURE__*/React__namespace.createElement(PerseusI18nContext.Provider, {
379
+ value: {
380
+ strings,
381
+ locale
382
+ }
383
+ }, children);
384
+ }
385
+ const usePerseusI18n = () => React.useContext(PerseusI18nContext);
386
+
105
387
  const debounce = (func, delay) => {
106
388
  let timer = null;
107
389
  return function () {
@@ -117,24 +399,11 @@ const debounce = (func, delay) => {
117
399
  };
118
400
  };
119
401
 
120
- const customKeyTranslator = {
121
- ...mathInput.keyTranslator,
122
- // If there's something in the input that can become part of a
123
- // fraction, typing "/" puts it in the numerator. If not, typing
124
- // "/" does nothing. In that case, enter a \frac.
125
- FRAC: mathQuill => {
126
- const contents = mathQuill.latex();
127
- mathQuill.typedText("/");
128
- if (mathQuill.latex() === contents) {
129
- mathQuill.cmd("\\frac");
130
- }
131
- }
132
- };
133
-
134
402
  // A WYSIWYG math input that calls `onChange(LaTeX-string)`
135
- class MathInput extends React__namespace.Component {
403
+ class InnerMathInput extends React__namespace.Component {
404
+ static contextType = PerseusI18nContext;
136
405
  // @ts-expect-error - TS2564 - Property 'mouseDown' has no initializer and is not definitely assigned in the constructor.
137
-
406
+ mouseDown;
138
407
  __mathFieldWrapperRef = null;
139
408
  __mathField = null;
140
409
  static defaultProps = {
@@ -166,6 +435,22 @@ class MathInput extends React__namespace.Component {
166
435
  }
167
436
  insert = value => {
168
437
  const input = this.mathField();
438
+ const {
439
+ locale
440
+ } = this.context;
441
+ const customKeyTranslator = {
442
+ ...mathInput.getKeyTranslator(locale),
443
+ // If there's something in the input that can become part of a
444
+ // fraction, typing "/" puts it in the numerator. If not, typing
445
+ // "/" does nothing. In that case, enter a \frac.
446
+ FRAC: mathQuill => {
447
+ const contents = mathQuill.latex();
448
+ mathQuill.typedText("/");
449
+ if (mathQuill.latex() === contents) {
450
+ mathQuill.cmd("\\frac");
451
+ }
452
+ }
453
+ };
169
454
  const inputModifier = customKeyTranslator[value];
170
455
  if (inputModifier) {
171
456
  inputModifier(input, value);
@@ -188,8 +473,11 @@ class MathInput extends React__namespace.Component {
188
473
  };
189
474
  mathField = () => {
190
475
  if (!this.__mathField && this.__mathFieldWrapperRef) {
476
+ const {
477
+ locale
478
+ } = this.context;
191
479
  // Initialize MathQuill.MathField instance
192
- this.__mathField = mathInput.createMathField(this.__mathFieldWrapperRef, baseConfig => ({
480
+ this.__mathField = mathInput.createMathField(this.__mathFieldWrapperRef, this.props.mathInputStrings, baseConfig => ({
193
481
  ...baseConfig,
194
482
  handlers: {
195
483
  edit: debounce(mathField => {
@@ -206,7 +494,7 @@ class MathInput extends React__namespace.Component {
206
494
  // Use the specified symbol to represent multiplication
207
495
  // TODO(alex): Add an option to disallow variables, in
208
496
  // which case 'x' should get converted to '\\times'
209
- if (mathInput.convertDotToTimesByLocale(this.props.convertDotToTimes)) {
497
+ if (mathInput.convertDotToTimesByLocale(locale, this.props.convertDotToTimes)) {
210
498
  value = value.replace(/\\cdot/g, "\\times");
211
499
 
212
500
  // Preserve cursor position in the common case:
@@ -266,7 +554,10 @@ class MathInput extends React__namespace.Component {
266
554
  focused: false
267
555
  });
268
556
  handleKeypadPress = (key, e) => {
269
- const translator = mathInput.keyTranslator[key];
557
+ const {
558
+ locale
559
+ } = this.context;
560
+ const translator = mathInput.getKeyTranslator(locale)[key];
270
561
  const mathField = this.mathField();
271
562
  if (mathField) {
272
563
  if (translator) {
@@ -342,7 +633,7 @@ class MathInput extends React__namespace.Component {
342
633
  focused: false,
343
634
  active: false
344
635
  }) : /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
345
- "aria-label": this.state.keypadOpen ? i18n__namespace._("close math keypad") : i18n__namespace._("open math keypad"),
636
+ "aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
346
637
  "aria-checked": this.state.keypadOpen,
347
638
  role: "switch",
348
639
  onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
@@ -351,6 +642,26 @@ class MathInput extends React__namespace.Component {
351
642
  }, props))))));
352
643
  }
353
644
  }
645
+
646
+ // We need to have two contexts (one for Perseus and one for MathInput), so we
647
+ // add a wrapper around the MathInput component to provide the MathInput context
648
+ // to it.
649
+ class MathInput extends React__namespace.Component {
650
+ static contextType = mathInput.MathInputI18nContext;
651
+ inputRef = /*#__PURE__*/React__namespace.createRef();
652
+ focus() {
653
+ this.inputRef.current?.focus();
654
+ }
655
+ insert(value) {
656
+ this.inputRef.current?.insert(value);
657
+ }
658
+ render() {
659
+ return /*#__PURE__*/React__namespace.createElement(InnerMathInput, _extends({}, this.props, {
660
+ ref: this.inputRef,
661
+ mathInputStrings: this.context.strings
662
+ }));
663
+ }
664
+ }
354
665
  const MathInputIcon = _ref => {
355
666
  let {
356
667
  hovered,
@@ -547,6 +858,8 @@ const Log = {
547
858
  };
548
859
 
549
860
  class PerseusError extends Error {
861
+ kind;
862
+ metadata;
550
863
  constructor(message, kind, options) {
551
864
  super(message);
552
865
  this.kind = kind;
@@ -1264,16 +1577,6 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
1264
1577
  // TOOD(kevinb): Figure out how this relates to KEScore in
1265
1578
  // perseus-all-package/types.js and see if there's a way to
1266
1579
  // unify these types.
1267
- const errors = {
1268
- APPROXIMATED_PI_ERROR: i18n__namespace._("Your answer is close, but you may " + "have approximated pi. Enter your " + "answer as a multiple of pi, like " + "<code>12\\ \\text{pi}</code> or " + "<code>2/3\\ \\text{pi}</code>"),
1269
- EXTRA_SYMBOLS_ERROR: i18n__namespace._("We could not understand your " + "answer. Please check your answer for extra " + "text or symbols."),
1270
- NEEDS_TO_BE_SIMPLFIED_ERROR: i18n__namespace._("Your answer is almost correct, " + "but it needs to be simplified."),
1271
- MISSING_PERCENT_ERROR: i18n__namespace._("Your answer is almost correct, " + "but it is missing a " + "<code>\\%</code> at the end."),
1272
- MULTIPLICATION_SIGN_ERROR: i18n__namespace._("I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign."),
1273
- WRONG_CASE_ERROR: i18n__namespace._("Your answer includes use of a variable with the wrong case."),
1274
- WRONG_LETTER_ERROR: i18n__namespace._("Your answer includes a wrong variable letter.")
1275
- };
1276
-
1277
1580
  /*
1278
1581
  * Answer types
1279
1582
  *
@@ -1346,7 +1649,7 @@ const KhanAnswerTypes = {
1346
1649
  */
1347
1650
  predicate: {
1348
1651
  defaultForms: "integer, proper, improper, mixed, decimal",
1349
- createValidatorFunctional: function (predicate, options) {
1652
+ createValidatorFunctional: function (predicate, options, strings) {
1350
1653
  // Extract the options from the given solution object
1351
1654
  options = ___default["default"].extend({
1352
1655
  simplify: "required",
@@ -1735,12 +2038,12 @@ const KhanAnswerTypes = {
1735
2038
  } else if (form === "percent") {
1736
2039
  // Otherwise, an error was returned
1737
2040
  score.empty = true;
1738
- score.message = errors.MISSING_PERCENT_ERROR;
2041
+ score.message = strings.MISSING_PERCENT_ERROR;
1739
2042
  } else {
1740
2043
  if (options.simplify !== "enforced") {
1741
2044
  score.empty = true;
1742
2045
  }
1743
- score.message = errors.NEEDS_TO_BE_SIMPLFIED_ERROR;
2046
+ score.message = strings.NEEDS_TO_BE_SIMPLFIED_ERROR;
1744
2047
  }
1745
2048
  // The return false below stops the looping of the
1746
2049
  // callback since predicate check succeeded.
@@ -1749,7 +2052,7 @@ const KhanAnswerTypes = {
1749
2052
  }
1750
2053
  if (piApprox && predicate(val, Math.abs(val * 0.001))) {
1751
2054
  score.empty = true;
1752
- score.message = errors.APPROXIMATED_PI_ERROR;
2055
+ score.message = strings.APPROXIMATED_PI_ERROR;
1753
2056
  }
1754
2057
  }
1755
2058
  });
@@ -1765,7 +2068,7 @@ const KhanAnswerTypes = {
1765
2068
  });
1766
2069
  if (!interpretedGuess) {
1767
2070
  score.empty = true;
1768
- score.message = errors.EXTRA_SYMBOLS_ERROR;
2071
+ score.message = strings.EXTRA_SYMBOLS_ERROR;
1769
2072
  return score;
1770
2073
  }
1771
2074
  }
@@ -1788,10 +2091,10 @@ const KhanAnswerTypes = {
1788
2091
  type: "predicate"
1789
2092
  })];
1790
2093
  },
1791
- createValidatorFunctional: function (correctAnswer, options) {
2094
+ createValidatorFunctional: function (correctAnswer, options, strings) {
1792
2095
  return KhanAnswerTypes.predicate.createValidatorFunctional(
1793
2096
  // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1794
- ...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options));
2097
+ ...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
1795
2098
  }
1796
2099
  },
1797
2100
  /*
@@ -1856,7 +2159,7 @@ const KhanAnswerTypes = {
1856
2159
  }
1857
2160
  return solution;
1858
2161
  },
1859
- createValidatorFunctional: function (solution, options) {
2162
+ createValidatorFunctional: function (solution, options, strings) {
1860
2163
  return function (guess) {
1861
2164
  const score = {
1862
2165
  empty: false,
@@ -1907,7 +2210,7 @@ const KhanAnswerTypes = {
1907
2210
  // @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
1908
2211
  score.ungraded = true;
1909
2212
  // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
1910
- score.message = result.wrongVariableCase ? errors.WRONG_CASE_ERROR : errors.WRONG_LETTER_ERROR;
2213
+ score.message = result.wrongVariableCase ? strings.WRONG_CASE_ERROR : strings.WRONG_LETTER_ERROR;
1911
2214
  // Don't tell the use they're "almost there" in this case, that may not be true and isn't helpful.
1912
2215
  // @ts-expect-error - TS2339 - Property 'suppressAlmostThere' does not exist on type '{ readonly empty: false; readonly correct: false; readonly message: string | null | undefined; readonly guess: any; readonly ungraded: false; }'.
1913
2216
  score.suppressAlmostThere = true;
@@ -1933,7 +2236,7 @@ const KhanAnswerTypes = {
1933
2236
  // @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
1934
2237
  score.ungraded = true;
1935
2238
  // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
1936
- score.message = errors.MULTIPLICATION_SIGN_ERROR;
2239
+ score.message = strings.MULTIPLICATION_SIGN_ERROR;
1937
2240
  } else if (resultX.message) {
1938
2241
  // TODO(aasmund): I18nize `score.message`
1939
2242
  // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
@@ -1947,9 +2250,6 @@ const KhanAnswerTypes = {
1947
2250
  }
1948
2251
  };
1949
2252
 
1950
- const ERROR_TITLE = i18n__namespace._("Oops!");
1951
- const ERROR_MESSAGE = i18n__namespace._("Sorry, I don't understand that!");
1952
-
1953
2253
  // Map of international operator names to their English equivalents
1954
2254
  const englishOperators = {
1955
2255
  arctg: "arctan",
@@ -1976,8 +2276,34 @@ const deriveKeypadVersion = apiOptions => {
1976
2276
  // can remove this `virtualKeypadVersion` field entirely.
1977
2277
  return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
1978
2278
  };
2279
+ /**
2280
+ * Get the character used for separating decimals.
2281
+ */
2282
+ const getDecimalSeparator = locale => {
2283
+ switch (locale) {
2284
+ // TODO(somewhatabstract): Remove this when Chrome supports the `ka`
2285
+ // locale properly.
2286
+ // https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
2287
+ //
2288
+ // Supported locales in Chrome:
2289
+ // https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
2290
+ case "ka":
2291
+ return ",";
2292
+ default:
2293
+ const numberWithDecimalSeparator = 1.1;
2294
+ // TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
2295
+ // support Safari 12.
2296
+ const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
2297
+ // 0x661 is ARABIC-INDIC DIGIT ONE
2298
+ // 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
2299
+ .match(/[^\d\u0661\u06F1]/);
2300
+ return match?.[0] ?? ".";
2301
+ }
2302
+ };
2303
+
1979
2304
  // The new, MathQuill input expression widget
1980
2305
  class Expression extends React__namespace.Component {
2306
+ static contextType = PerseusI18nContext;
1981
2307
  _isMounted = false;
1982
2308
 
1983
2309
  //#region Previously a class extension
@@ -2001,9 +2327,11 @@ class Expression extends React__namespace.Component {
2001
2327
  */
2002
2328
  static validate(userInput, rubric) {
2003
2329
  let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {};
2330
+ let strings = arguments.length > 3 ? arguments[3] : undefined;
2331
+ let locale = arguments.length > 4 ? arguments[4] : undefined;
2004
2332
  const options = ___default["default"].clone(rubric);
2005
2333
  ___default["default"].extend(options, {
2006
- decimal_separator: i18n__namespace.getDecimalSeparator()
2334
+ decimal_separator: getDecimalSeparator(locale)
2007
2335
  });
2008
2336
  const createValidator = answer => {
2009
2337
  // We give options to KAS.parse here because it is parsing the
@@ -2026,7 +2354,7 @@ class Expression extends React__namespace.Component {
2026
2354
  return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, ___default["default"]({}).extend(options, {
2027
2355
  simplify: answer.simplify,
2028
2356
  form: answer.form
2029
- }));
2357
+ }), strings);
2030
2358
  };
2031
2359
 
2032
2360
  // Find the first answer form that matches the user's input and that
@@ -2173,7 +2501,7 @@ class Expression extends React__namespace.Component {
2173
2501
  if (!this.parse(this.props.value, this.props).parsed) {
2174
2502
  const apiResult = this.props.apiOptions.onInputError(null,
2175
2503
  // reserved for some widget identifier
2176
- this.props.value, ERROR_MESSAGE);
2504
+ this.props.value, this.context.strings.ERROR_TITLE);
2177
2505
  if (apiResult !== false) {
2178
2506
  this.setState({
2179
2507
  invalid: true
@@ -2197,7 +2525,7 @@ class Expression extends React__namespace.Component {
2197
2525
  scoring,
2198
2526
  ...rubric
2199
2527
  } = _ref;
2200
- const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
2528
+ const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
2201
2529
 
2202
2530
  // "scoring" is a flag that indicates when we are checking answers.
2203
2531
  // otherwise, we may just be checking validity after changes.
@@ -2229,7 +2557,7 @@ class Expression extends React__namespace.Component {
2229
2557
  // make it so that solution answers with ','s or '.'s work
2230
2558
  const options = ___default["default"].pick(props || this.props, "functions");
2231
2559
  ___default["default"].extend(options, {
2232
- decimal_separator: i18n__namespace.getDecimalSeparator()
2560
+ decimal_separator: getDecimalSeparator(this.context.locale)
2233
2561
  });
2234
2562
  return KAS__namespace.parse(normalizeTex(value), options);
2235
2563
  };
@@ -2318,6 +2646,10 @@ class Expression extends React__namespace.Component {
2318
2646
  "perseus-widget-expression": true,
2319
2647
  "show-error-tooltip": this.state.showErrorTooltip
2320
2648
  });
2649
+ const {
2650
+ ERROR_MESSAGE,
2651
+ ERROR_TITLE
2652
+ } = this.context.strings;
2321
2653
  return /*#__PURE__*/React__namespace.createElement("div", {
2322
2654
  className: className,
2323
2655
  onBlur: () => this.state.invalid && this.setState({
@@ -2744,7 +3076,7 @@ function keScoreFromPerseusScore(score, guess, state) {
2744
3076
  * Return the first valid interpretation of 'text' as a number, in the form
2745
3077
  * {value: 2.3, exact: true}.
2746
3078
  */
2747
- function firstNumericalParse$1(text) {
3079
+ function firstNumericalParse$1(text, strings) {
2748
3080
  // TODO(alpert): This is sort of hacky...
2749
3081
  let first;
2750
3082
  const val = KhanAnswerTypes.predicate.createValidatorFunctional(function (ans) {
@@ -2754,7 +3086,7 @@ function firstNumericalParse$1(text) {
2754
3086
  simplify: "optional",
2755
3087
  inexact: true,
2756
3088
  forms: "integer, proper, improper, pi, log, mixed, decimal"
2757
- });
3089
+ }, strings);
2758
3090
  val(text);
2759
3091
  return first;
2760
3092
  }
@@ -8566,8 +8898,12 @@ const KhanColors = {
8566
8898
  // DrawingTransform is responsible for transforming from "math coordinates"
8567
8899
  // (on the Cartesian plane) to "canvas coordinates" in pixels.
8568
8900
  class DrawingTransform {
8569
- // Scale is measured in pixels per unit on the Cartesian plane.
8901
+ raphael;
8570
8902
 
8903
+ // Scale is measured in pixels per unit on the Cartesian plane.
8904
+ xScale;
8905
+ yScale;
8906
+ bounds;
8571
8907
  constructor(raphael, initialScale, bounds) {
8572
8908
  this.raphael = raphael;
8573
8909
  this.bounds = bounds;
@@ -8632,6 +8968,10 @@ class DrawingTransform {
8632
8968
  // Defines the bounds of the visible area of a graph, in Cartesian coordinates.
8633
8969
  // GraphBounds is immutable.
8634
8970
  class GraphBounds {
8971
+ xMin;
8972
+ xMax;
8973
+ yMin;
8974
+ yMax;
8635
8975
  constructor(xRange, yRange) {
8636
8976
  this.xMin = xRange[0];
8637
8977
  this.xMax = xRange[1];
@@ -8735,26 +9075,35 @@ function polar(r, th) {
8735
9075
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
8736
9076
  }
8737
9077
  class Graphie$1 {
9078
+ el;
8738
9079
  #bounds;
8739
9080
  #drawingTransform;
8740
9081
 
8741
9082
  // The primary drawing layer
8742
-
9083
+ raphael;
8743
9084
  // The layer that all visuals with mouse handlers are drawn into
8744
-
9085
+ mouselayer;
9086
+ _mouselayerWrapper;
9087
+ _visiblelayerWrapper;
8745
9088
  isMobile = false;
8746
9089
  // Set up some reasonable defaults
8747
9090
  currentStyle = {
8748
9091
  "stroke-width": 2,
8749
9092
  fill: "none"
8750
9093
  };
8751
-
9094
+ range;
9095
+ scale;
9096
+ dimensions;
8752
9097
  // TODO(benchristel): xpixels and ypixels are never used by this class, but
8753
9098
  // other code reaches in to access them :(
8754
9099
  // Refactor to accessor methods that look at this.dimensions.
9100
+ xpixels;
9101
+ ypixels;
9102
+
8755
9103
  // set by Movables
8756
9104
  // TODO(benchristel): consider putting this state on the Movable instead of
8757
9105
  // on the Graphie
9106
+ isDragging;
8758
9107
  constructor(el) {
8759
9108
  this.el = el;
8760
9109
  $__default["default"](el).css("position", "relative");
@@ -10286,6 +10635,8 @@ const getClipPoint = function (graph, coord, angle) {
10286
10635
  */
10287
10636
 
10288
10637
  class WrappedDrawing {
10638
+ graphie;
10639
+ initialPoint;
10289
10640
  constructor(graphie, initialPoint) {
10290
10641
  this.graphie = graphie;
10291
10642
  this.initialPoint = initialPoint;
@@ -10346,6 +10697,8 @@ const DEFAULT_OPTIONS$2 = {
10346
10697
  mouselayer: false
10347
10698
  };
10348
10699
  class WrappedPath extends WrappedDrawing {
10700
+ wrapper;
10701
+ visibleShape;
10349
10702
  constructor(graphie, points, options) {
10350
10703
  const initialPoint = graphie.scalePoint(___default["default"].head(points));
10351
10704
  super(graphie, initialPoint);
@@ -10368,6 +10721,7 @@ class WrappedPath extends WrappedDrawing {
10368
10721
 
10369
10722
  class Arrowhead$1 extends WrappedPath {
10370
10723
  static scale = 1.4;
10724
+ center;
10371
10725
  constructor(graphie, style) {
10372
10726
  // Points that define the arrowhead
10373
10727
  const center = [0.75, 0];
@@ -10413,6 +10767,9 @@ const DEFAULT_OPTIONS$1 = {
10413
10767
  disableMouseEventsOnWrapper: false
10414
10768
  };
10415
10769
  class WrappedEllipse extends WrappedDrawing {
10770
+ initialPoint;
10771
+ wrapper;
10772
+ visibleShape;
10416
10773
  constructor(graphie, center, radii, options) {
10417
10774
  super(graphie, center);
10418
10775
  options = ___default["default"].extend({}, DEFAULT_OPTIONS$1, options);
@@ -10460,6 +10817,9 @@ const DEFAULT_OPTIONS = {
10460
10817
  mouselayer: false
10461
10818
  };
10462
10819
  class WrappedLine extends WrappedDrawing {
10820
+ initialLength;
10821
+ wrapper;
10822
+ visibleShape;
10463
10823
  constructor(graphie, start, end, options) {
10464
10824
  // Always make the line as large as possible and horizontal; this
10465
10825
  // simplifies a lot of the transforms, e.g., we can rotate by exactly the
@@ -13753,6 +14113,9 @@ const DEFAULT_STATE$3 = {
13753
14113
  mouseTarget: null
13754
14114
  };
13755
14115
  class Movable {
14116
+ graphie;
14117
+ state;
14118
+ prevState;
13756
14119
  // TODO(benchristel): delete _listenerMap; it's unused
13757
14120
  _listenerMap = {};
13758
14121
  constructor(graphie, options) {
@@ -13878,12 +14241,12 @@ class Movable {
13878
14241
  * Fire an onSomething type event to all functions in listeners
13879
14242
  */
13880
14243
  _fireEvent(listeners) {
13881
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
13882
- args[_key - 1] = arguments[_key];
13883
- }
13884
14244
  if (listeners == null) {
13885
14245
  return;
13886
14246
  }
14247
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
14248
+ args[_key - 1] = arguments[_key];
14249
+ }
13887
14250
  for (const listener of listeners) {
13888
14251
  listener.call(this, ...args);
13889
14252
  }
@@ -15016,8 +15379,12 @@ const DEFAULT_STATE$1 = {
15016
15379
  // tooltipResetFunctions, but never remove them.
15017
15380
  const tooltipResetFunctions = [];
15018
15381
  class MovablePoint$4 {
15382
+ graphie;
15383
+ movable;
15384
+ state;
15019
15385
  // @ts-expect-error - TS2564: Property 'prevState' has no initializer and is not definitely assigned in the constructor.
15020
-
15386
+ prevState;
15387
+ _tooltip;
15021
15388
  _listenerMap = {};
15022
15389
  constructor(graphie, movable, options) {
15023
15390
  this.graphie = graphie;
@@ -16706,6 +17073,7 @@ const Status = {
16706
17073
  FAILED: "failed"
16707
17074
  };
16708
17075
  class ImageLoader extends React__namespace.Component {
17076
+ img;
16709
17077
  constructor(props) {
16710
17078
  super(props);
16711
17079
  this.state = {
@@ -16942,6 +17310,7 @@ function defaultPreloader(dimensions) {
16942
17310
  }));
16943
17311
  }
16944
17312
  class SvgImage extends React__namespace.Component {
17313
+ _isMounted;
16945
17314
  static defaultProps = {
16946
17315
  constrainHeight: false,
16947
17316
  onUpdate: () => {},
@@ -17387,6 +17756,7 @@ class SvgImage extends React__namespace.Component {
17387
17756
  * have been loaded. This tracking is facilitated by the AssetContext.
17388
17757
  */
17389
17758
  class Tex extends React__namespace.Component {
17759
+ _hasRendered;
17390
17760
  static defaultProps = {
17391
17761
  onRender: () => {},
17392
17762
  setAssetStatus: (src, status) => {}
@@ -17428,13 +17798,14 @@ class Tex extends React__namespace.Component {
17428
17798
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
17429
17799
  class Zoomable extends React__namespace.Component {
17430
17800
  // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
17431
-
17801
+ _isMounted;
17432
17802
  // @ts-expect-error - TS2564 - Property '_observer' has no initializer and is not definitely assigned in the constructor.
17433
-
17803
+ _observer;
17434
17804
  // @ts-expect-error - TS2564 - Property '_measuringInitialized' has no initializer and is not definitely assigned in the constructor.
17435
-
17805
+ _measuringInitialized;
17806
+ _originalWidth;
17436
17807
  // @ts-expect-error - TS2564 - Property '_node' has no initializer and is not definitely assigned in the constructor.
17437
-
17808
+ _node;
17438
17809
  static defaultProps = {
17439
17810
  animateHeight: false,
17440
17811
  readyToMeasure: true,
@@ -17793,13 +18164,15 @@ function _noop() {}
17793
18164
  */
17794
18165
  class InteractionTracker {
17795
18166
  // @ts-expect-error - TS2564 - Property '_tracked' has no initializer and is not definitely assigned in the constructor.
17796
-
18167
+ _tracked;
17797
18168
  // @ts-expect-error - TS2564 - Property 'setting' has no initializer and is not definitely assigned in the constructor.
17798
-
18169
+ setting;
18170
+ track;
18171
+ trackApi;
17799
18172
  // @ts-expect-error - TS2564 - Property 'widgetID' has no initializer and is not definitely assigned in the constructor.
17800
-
18173
+ widgetID;
17801
18174
  // @ts-expect-error - TS2564 - Property 'widgetType' has no initializer and is not definitely assigned in the constructor.
17802
-
18175
+ widgetType;
17803
18176
  constructor(trackApi, widgetType, widgetID, setting) {
17804
18177
  if (!trackApi) {
17805
18178
  this.track = _noop;
@@ -17911,6 +18284,7 @@ var Severity = /*#__PURE__*/function (Severity) {
17911
18284
  * to the left of where it belongs. And if there is more
17912
18285
  **/
17913
18286
  class Lint extends React__namespace.Component {
18287
+ _positionTimeout;
17914
18288
  state = {
17915
18289
  tooltipAbove: true
17916
18290
  };
@@ -18367,7 +18741,7 @@ const rules$1 = {
18367
18741
  // just a stub for testing.
18368
18742
  return /*#__PURE__*/React__namespace.createElement("em", {
18369
18743
  key: state.key
18370
- }, i18n__namespace.doNotTranslate("[Widget: ", node.id, "]"));
18744
+ }, `[Widget: ${node.id}]`);
18371
18745
  }
18372
18746
  },
18373
18747
  blockMath: {
@@ -18855,9 +19229,8 @@ const getWidget = name => {
18855
19229
  }
18856
19230
 
18857
19231
  // Allow widgets to specify a widget directly or via a function
18858
- if (widgets[name].getWidget) {
18859
- // @ts-expect-error - TS2722 - Cannot invoke an object which is possibly 'undefined'.
18860
- return widgets[name].getWidget();
19232
+ if (widgets[name]?.getWidget) {
19233
+ return widgets[name].getWidget?.();
18861
19234
  }
18862
19235
  return widgets[name].widget;
18863
19236
  };
@@ -19009,7 +19382,7 @@ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
19009
19382
  options: newEditorProps
19010
19383
  });
19011
19384
  };
19012
- const getRendererPropsForWidgetInfo = (widgetInfo, problemNum) => {
19385
+ const getRendererPropsForWidgetInfo = (widgetInfo, strings, problemNum) => {
19013
19386
  const type = widgetInfo.type;
19014
19387
  const widgetExports = widgets[type];
19015
19388
  if (widgetExports == null) {
@@ -19030,7 +19403,7 @@ const getRendererPropsForWidgetInfo = (widgetInfo, problemNum) => {
19030
19403
  transform = widgetExports.transform || ___default["default"].identity;
19031
19404
  }
19032
19405
  // widgetInfo.options are the widgetEditor's props:
19033
- return transform(widgetInfo.options, problemNum);
19406
+ return transform(widgetInfo.options, strings, problemNum);
19034
19407
  };
19035
19408
  const traverseChildWidgets$3 = (widgetInfo, traverseRenderer) => {
19036
19409
  if (!traverseRenderer) {
@@ -19374,24 +19747,26 @@ const isIdPathPrefix = function (prefixArray, wholeArray) {
19374
19747
  * scoring, state serialization/deserialization, and focus management.
19375
19748
  */
19376
19749
  class Renderer extends React__namespace.Component {
19750
+ _currentFocus;
19377
19751
  // @ts-expect-error - TS2564 - Property '_foundTextNodes' has no initializer and is not definitely assigned in the constructor.
19378
-
19752
+ _foundTextNodes;
19379
19753
  // @ts-expect-error - TS2564 - Property '_interactionTrackers' has no initializer and is not definitely assigned in the constructor.
19380
-
19754
+ _interactionTrackers;
19381
19755
  // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
19382
-
19756
+ _isMounted;
19383
19757
  // @ts-expect-error - TS2564 - Property '_isTwoColumn' has no initializer and is not definitely assigned in the constructor.
19758
+ _isTwoColumn;
19384
19759
 
19385
19760
  // The i18n linter.
19386
-
19761
+ _translationLinter;
19387
19762
  _widgetContainers = new Map();
19388
-
19763
+ lastRenderedMarkdown;
19389
19764
  // @ts-expect-error - TS2564 - Property 'reuseMarkdown' has no initializer and is not definitely assigned in the constructor.
19390
-
19765
+ reuseMarkdown;
19391
19766
  // @ts-expect-error - TS2564 - Property 'translationIndex' has no initializer and is not definitely assigned in the constructor.
19392
-
19767
+ translationIndex;
19393
19768
  // @ts-expect-error - TS2564 - Property 'widgetIds' has no initializer and is not definitely assigned in the constructor.
19394
-
19769
+ widgetIds;
19395
19770
  static defaultProps = {
19396
19771
  content: "",
19397
19772
  widgets: {},
@@ -19590,9 +19965,12 @@ class Renderer extends React__namespace.Component {
19590
19965
  problemNum
19591
19966
  } = props;
19592
19967
  const widgetsStartProps = {};
19968
+ const {
19969
+ strings
19970
+ } = this.props;
19593
19971
  wonderStuffCore.entries(allWidgetInfo).forEach(_ref => {
19594
19972
  let [key, widgetInfo] = _ref;
19595
- widgetsStartProps[key] = getRendererPropsForWidgetInfo(widgetInfo, problemNum);
19973
+ widgetsStartProps[key] = getRendererPropsForWidgetInfo(widgetInfo, strings, problemNum);
19596
19974
  });
19597
19975
 
19598
19976
  // Call the onWidgetStartProps callback if it exists
@@ -21058,6 +21436,7 @@ class TextInput extends React__namespace.Component {
21058
21436
  value: "",
21059
21437
  disabled: false
21060
21438
  };
21439
+ id;
21061
21440
  constructor(props) {
21062
21441
  super(props);
21063
21442
  if (props.id) {
@@ -21383,6 +21762,7 @@ const {
21383
21762
  captureScratchpadTouchStart: captureScratchpadTouchStart$2
21384
21763
  } = Util;
21385
21764
  class InputWithExamples extends React__namespace.Component {
21765
+ static contextType = PerseusI18nContext;
21386
21766
  static defaultProps = {
21387
21767
  type: "text",
21388
21768
  shouldShowExamples: true,
@@ -21505,7 +21885,8 @@ class InputWithExamples extends React__namespace.Component {
21505
21885
  id: this._getUniqueId()
21506
21886
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
21507
21887
  content: examplesContent,
21508
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "input-with-examples")
21888
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "input-with-examples"),
21889
+ strings: this.context.strings
21509
21890
  })));
21510
21891
  }
21511
21892
  _handleFocus = () => {
@@ -21653,35 +22034,36 @@ const answerTypes = {
21653
22034
  }
21654
22035
  };
21655
22036
  const formExamples$1 = {
21656
- integer: function (options) {
21657
- return i18n__namespace._("an integer, like $6$");
22037
+ integer: function (options, strings) {
22038
+ return strings.integerExample;
21658
22039
  },
21659
- proper: function (options) {
22040
+ proper: function (options, strings) {
21660
22041
  if (options.simplify === "optional") {
21661
- return i18n__namespace._("a *proper* fraction, like $1/2$ or $6/10$");
22042
+ return strings.properExample;
21662
22043
  }
21663
- return i18n__namespace._("a *simplified proper* fraction, like $3/5$");
22044
+ return strings.simplifiedProperExample;
21664
22045
  },
21665
- improper: function (options) {
22046
+ improper: function (options, strings) {
21666
22047
  if (options.simplify === "optional") {
21667
- return i18n__namespace._("an *improper* fraction, like $10/7$ or $14/8$");
22048
+ return strings.improperExample;
21668
22049
  }
21669
- return i18n__namespace._("a *simplified improper* fraction, like $7/4$");
22050
+ return strings.simplifiedImproperExample;
21670
22051
  },
21671
- mixed: function (options) {
21672
- return i18n__namespace._("a mixed number, like $1\\ 3/4$");
22052
+ mixed: function (options, strings) {
22053
+ return strings.mixedExample;
21673
22054
  },
21674
- decimal: function (options) {
21675
- return i18n__namespace._("an *exact* decimal, like $0.75$");
22055
+ decimal: function (options, strings) {
22056
+ return strings.decimalExample;
21676
22057
  },
21677
- percent: function (options) {
21678
- return i18n__namespace._("a percent, like $12.34\\%$");
22058
+ percent: function (options, strings) {
22059
+ return strings.percentExample;
21679
22060
  },
21680
- pi: function (options) {
21681
- return i18n__namespace._("a multiple of pi, like $12\\ \\text{pi}$ or " + "$2/3\\ \\text{pi}$");
22061
+ pi: function (options, strings) {
22062
+ return strings.piExample;
21682
22063
  }
21683
22064
  };
21684
22065
  class InputNumber extends React__namespace.Component {
22066
+ static contextType = PerseusI18nContext;
21685
22067
  static defaultProps = {
21686
22068
  currentValue: "",
21687
22069
  size: "normal",
@@ -21795,21 +22177,19 @@ class InputNumber extends React__namespace.Component {
21795
22177
  };
21796
22178
  simpleValidate = (rubric, onInputError) => {
21797
22179
  onInputError = onInputError || function () {};
21798
- return InputNumber.validate(this.getUserInput(), rubric, onInputError);
22180
+ return InputNumber.validate(this.getUserInput(), rubric, this.context.strings, onInputError);
21799
22181
  };
21800
22182
  examples = () => {
22183
+ const {
22184
+ strings
22185
+ } = this.context;
21801
22186
  const type = this.props.answerType;
21802
22187
  const forms = answerTypes[type].forms.split(/\s*,\s*/);
21803
- const examples = ___default["default"].map(forms, function (form) {
21804
- // eslint-disable-next-line @babel/no-invalid-this
21805
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
21806
- // eslint-disable-next-line @babel/no-invalid-this
21807
- return formExamples$1[form](this.props);
21808
- }, this);
21809
- return [i18n__namespace._("**Your answer should be** ")].concat(examples);
22188
+ const examples = ___default["default"].map(forms, form => formExamples$1[form](this.props, strings));
22189
+ return [strings.yourAnswer].concat(examples);
21810
22190
  };
21811
- static validate(state, rubric) {
21812
- let onInputError = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : () => {};
22191
+ static validate(state, rubric, strings) {
22192
+ let onInputError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : () => {};
21813
22193
  if (rubric.answerType == null) {
21814
22194
  rubric.answerType = "number";
21815
22195
  }
@@ -21823,7 +22203,7 @@ class InputNumber extends React__namespace.Component {
21823
22203
  inexact: rubric.inexact || undefined,
21824
22204
  maxError: rubric.maxError,
21825
22205
  forms: answerTypes[rubric.answerType].forms
21826
- });
22206
+ }, strings);
21827
22207
 
21828
22208
  // We may have received TeX; try to parse it before grading.
21829
22209
  // If `currentValue` is not TeX, this should be a no-op.
@@ -21938,14 +22318,16 @@ const answerFormButtons = [{
21938
22318
  content: "\u03C0"
21939
22319
  }];
21940
22320
  const formExamples = {
21941
- integer: () => i18n__namespace._("an integer, like $6$"),
21942
- proper: form => form.simplify === "optional" ? i18n__namespace._("a *proper* fraction, like $1/2$ or $6/10$") : i18n__namespace._("a *simplified proper* fraction, like $3/5$"),
21943
- improper: form => form.simplify === "optional" ? i18n__namespace._("an *improper* fraction, like $10/7$ or $14/8$") : i18n__namespace._("a *simplified improper* fraction, like $7/4$"),
21944
- mixed: () => i18n__namespace._("a mixed number, like $1\\ 3/4$"),
21945
- decimal: () => i18n__namespace._("an *exact* decimal, like $0.75$"),
21946
- pi: () => i18n__namespace._("a multiple of pi, like $12\\ \\text{pi}$ or " + "$2/3\\ \\text{pi}$")
22321
+ integer: (form, strings) => strings.integerExample,
22322
+ proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
22323
+ improper: (form, strings) => form.simplify === "optional" ? strings.improperExample : strings.simplifiedImproperExample,
22324
+ mixed: (form, strings) => strings.mixedExample,
22325
+ decimal: (form, strings) => strings.decimalExample,
22326
+ pi: (form, strings) => strings.piExample
21947
22327
  };
21948
22328
  class NumericInput extends React__namespace.Component {
22329
+ static contextType = PerseusI18nContext;
22330
+ inputRef;
21949
22331
  static defaultProps = {
21950
22332
  currentValue: "",
21951
22333
  size: "normal",
@@ -21989,7 +22371,7 @@ class NumericInput extends React__namespace.Component {
21989
22371
  }
21990
22372
  return answerStrings[0];
21991
22373
  }
21992
- static validate(userInput, rubric) {
22374
+ static validate(userInput, rubric, strings) {
21993
22375
  const defaultAnswerForms = answerFormButtons.map(e => e["value"])
21994
22376
  // Don't default to validating the answer as a pi answer
21995
22377
  // if answerForm isn't set on the answer
@@ -22015,7 +22397,7 @@ class NumericInput extends React__namespace.Component {
22015
22397
  // TODO(merlob) backfill / delete
22016
22398
  maxError: answer.maxError,
22017
22399
  forms: validatorForms
22018
- });
22400
+ }, strings);
22019
22401
  };
22020
22402
 
22021
22403
  // We may have received TeX; try to parse it before grading.
@@ -22098,13 +22480,12 @@ class NumericInput extends React__namespace.Component {
22098
22480
  };
22099
22481
  });
22100
22482
  let examples = ___default["default"].map(forms, form => {
22101
- // @ts-expect-error - TS2345 - Argument of type 'PerseusNumericInputAnswerForm | { name: string; simplify: string; }' is not assignable to parameter of type 'PerseusNumericInputAnswerForm'.
22102
- return formExamples[form.name](form);
22483
+ return formExamples[form.name](form, this.context.strings);
22103
22484
  });
22104
22485
  // Ensure no duplicate tooltip text from simplified and unsimplified
22105
22486
  // versions of the same format
22106
22487
  examples = ___default["default"].uniq(examples);
22107
- return [i18n__namespace._("**Your answer should be** ")].concat(examples);
22488
+ return [this.context.strings.yourAnswer].concat(examples);
22108
22489
  };
22109
22490
  shouldShowExamples = () => {
22110
22491
  const noFormsAccepted = this.props.answerForms?.length === 0;
@@ -22116,7 +22497,7 @@ class NumericInput extends React__namespace.Component {
22116
22497
  return !noFormsAccepted && !allFormsAccepted;
22117
22498
  };
22118
22499
  simpleValidate = rubric => {
22119
- return NumericInput.validate(this.getUserInput(), rubric);
22500
+ return NumericInput.validate(this.getUserInput(), rubric, this.context.strings);
22120
22501
  };
22121
22502
  focus = () => {
22122
22503
  this.inputRef?.focus();
@@ -22162,7 +22543,7 @@ class NumericInput extends React__namespace.Component {
22162
22543
  render() {
22163
22544
  let labelText = this.props.labelText;
22164
22545
  if (labelText == null || labelText === "") {
22165
- labelText = i18n__namespace._("Your answer:");
22546
+ labelText = this.context.strings.yourAnswerLabel;
22166
22547
  }
22167
22548
 
22168
22549
  // To right align a custom keypad we need to wrap it.
@@ -22309,11 +22690,12 @@ var NumericInput$1 = {
22309
22690
  /* eslint-disable react/sort-comp */
22310
22691
  const EN_DASH$1 = "\u2013";
22311
22692
  class PassageRef extends React__namespace.Component {
22693
+ static contextType = PerseusI18nContext;
22312
22694
  displayName = "PassageRef";
22313
22695
  // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
22314
-
22696
+ _isMounted;
22315
22697
  // @ts-expect-error - TS2564 - Property '_throttledUpdateRange' has no initializer and is not definitely assigned in the constructor.
22316
-
22698
+ _throttledUpdateRange;
22317
22699
  static defaultProps = {
22318
22700
  passageNumber: 1,
22319
22701
  referenceNumber: 1,
@@ -22333,18 +22715,21 @@ class PassageRef extends React__namespace.Component {
22333
22715
  return removeDenylistProps(this.props);
22334
22716
  };
22335
22717
  render() {
22718
+ const {
22719
+ strings
22720
+ } = this.context;
22336
22721
  const lineRange = this.state.lineRange;
22337
22722
  let lineRangeOutput;
22338
22723
  if (!lineRange) {
22339
- lineRangeOutput = i18n__namespace.$_("lines %(lineRange)s", {
22724
+ lineRangeOutput = strings.lineRange({
22340
22725
  lineRange: `?${EN_DASH$1}?`
22341
22726
  });
22342
22727
  } else if (lineRange[0] === lineRange[1]) {
22343
- lineRangeOutput = i18n__namespace.$_("line %(lineNumber)s", {
22344
- lineNumber: lineRange[0]
22728
+ lineRangeOutput = strings.lineNumber({
22729
+ lineNumber: String(lineRange[0])
22345
22730
  });
22346
22731
  } else {
22347
- lineRangeOutput = i18n__namespace.$_("lines %(lineRange)s", {
22732
+ lineRangeOutput = strings.lineRange({
22348
22733
  lineRange: lineRange[0] + EN_DASH$1 + lineRange[1]
22349
22734
  });
22350
22735
  }
@@ -22784,7 +23169,7 @@ class Icon extends React__namespace.Component {
22784
23169
  }
22785
23170
  }
22786
23171
 
22787
- function getA11yText(letter, checked, correct, crossedOut, showCorrectness) {
23172
+ function getA11yText(letter, checked, correct, crossedOut, showCorrectness, strings) {
22788
23173
  // There are two pieces of metadata we want to add to each a11yText:
22789
23174
  // whether the answer was checked/crossed-out/neither, and whether the
22790
23175
  // answer is correct/incorrect/not-yet-revealed.
@@ -22793,45 +23178,45 @@ function getA11yText(letter, checked, correct, crossedOut, showCorrectness) {
22793
23178
  // we've just enumerated all 9 possibilities as separate strings.
22794
23179
  if (showCorrectness && correct) {
22795
23180
  if (checked) {
22796
- return i18n__namespace._("(Choice %(letter)s, Checked, Correct)", {
23181
+ return strings.choiceCheckedCorrect({
22797
23182
  letter
22798
23183
  });
22799
23184
  }
22800
23185
  if (crossedOut) {
22801
- return i18n__namespace._("(Choice %(letter)s, Crossed out, Correct)", {
23186
+ return strings.choiceCrossedOutCorrect({
22802
23187
  letter
22803
23188
  });
22804
23189
  }
22805
- return i18n__namespace._("(Choice %(letter)s, Correct)", {
23190
+ return strings.choiceCorrect({
22806
23191
  letter
22807
23192
  });
22808
23193
  }
22809
23194
  if (showCorrectness && !correct) {
22810
23195
  if (checked) {
22811
- return i18n__namespace._("(Choice %(letter)s, Checked, Incorrect)", {
23196
+ return strings.choiceCheckedIncorrect({
22812
23197
  letter
22813
23198
  });
22814
23199
  }
22815
23200
  if (crossedOut) {
22816
- return i18n__namespace._("(Choice %(letter)s, Crossed out, Incorrect)", {
23201
+ return strings.choiceCrossedOutIncorrect({
22817
23202
  letter
22818
23203
  });
22819
23204
  }
22820
- return i18n__namespace._("(Choice %(letter)s, Incorrect)", {
23205
+ return strings.choiceIncorrect({
22821
23206
  letter
22822
23207
  });
22823
23208
  }
22824
23209
  if (checked) {
22825
- return i18n__namespace._("(Choice %(letter)s, Checked)", {
23210
+ return strings.choiceChecked({
22826
23211
  letter
22827
23212
  });
22828
23213
  }
22829
23214
  if (crossedOut) {
22830
- return i18n__namespace._("(Choice %(letter)s, Crossed out)", {
23215
+ return strings.choiceCrossedOut({
22831
23216
  letter
22832
23217
  });
22833
23218
  }
22834
- return i18n__namespace._("(Choice %(letter)s)", {
23219
+ return strings.choice({
22835
23220
  letter
22836
23221
  });
22837
23222
  }
@@ -22853,7 +23238,7 @@ const FocusRing = function (props) {
22853
23238
  borderRadius
22854
23239
  };
22855
23240
  return /*#__PURE__*/React__namespace.createElement("span", {
22856
- "data-test-id": "focus-ring",
23241
+ "data-testid": "focus-ring",
22857
23242
  className: aphrodite.css(styles$z.ring),
22858
23243
  style: style
22859
23244
  }, children);
@@ -22878,18 +23263,8 @@ const styles$z = aphrodite.StyleSheet.create({
22878
23263
  * "choice letter". (For example, in English, `getChoiceLetter(0)` is "A",
22879
23264
  * `getChoiceLetter(1)` is "B", etc.)
22880
23265
  */
22881
- function getChoiceLetter(pos) {
22882
- /* I18N: This is a list of single-character labels that will appear in
22883
- * front of multiple-choice options. For instance, a multiple-choice
22884
- * question with three options would display
22885
- * (A) first option
22886
- * (B) second option
22887
- * (C) third option
22888
- * There must be spaces between each of the different characters. The
22889
- * characters will show up next to options in the order that they are
22890
- * listed here. Most multiple choice questions have 5 or fewer options.
22891
- */
22892
- const lettersString = i18n__namespace._("A B C D E F G H I J K L M N O P Q R S T U V W X Y Z");
23266
+ function getChoiceLetter(pos, strings) {
23267
+ const lettersString = strings.letters;
22893
23268
  const letters = lettersString.split(" ");
22894
23269
  if (pos < letters.length) {
22895
23270
  // If the position we need is listed in the localized string, use that.
@@ -22924,7 +23299,7 @@ const CrossOutLine = function (props) {
22924
23299
  height: CROSS_OUT_LINE_SIZE,
22925
23300
  viewBox: `0 0 ${CROSS_OUT_LINE_SIZE} ${CROSS_OUT_LINE_SIZE}`,
22926
23301
  className: aphrodite.css(styles$x.crossOutLine),
22927
- "data-test-id": "choice-icon__cross-out-line"
23302
+ "data-testid": "choice-icon__cross-out-line"
22928
23303
  }, /*#__PURE__*/React__namespace.createElement("line", {
22929
23304
  // The line stretches from the bottom-left to top-right.
22930
23305
  // We don't quite go to the _very_ corner, because the cute
@@ -22953,7 +23328,10 @@ function ChoiceInner(props) {
22953
23328
  showCorrectness,
22954
23329
  correct
22955
23330
  } = props;
22956
- const letter = getChoiceLetter(pos);
23331
+ const {
23332
+ strings
23333
+ } = usePerseusI18n();
23334
+ const letter = getChoiceLetter(pos, strings);
22957
23335
  if (!showCorrectness) {
22958
23336
  return letter;
22959
23337
  }
@@ -23027,7 +23405,7 @@ const ChoiceIcon = function (props) {
23027
23405
  }, /*#__PURE__*/React__namespace.createElement("div", {
23028
23406
  // @ts-expect-error - TS2322 - Type '{ backgroundColor: string | null | undefined; borderColor: string; color: string; borderRadius: number; }' is not assignable to type 'Properties<string | number, string & {}>'.
23029
23407
  style: dynamicStyles,
23030
- "data-test-id": "choice-icon__library-choice-icon",
23408
+ "data-testid": "choice-icon__library-choice-icon",
23031
23409
  className: aphrodite.css(styles$w.circle, showCorrectness && correct && styles$w.circleCorrect, showCorrectness && !correct && styles$w.circleIncorrect, showCorrectness && !correct && (checked || previouslyAnswered) && styles$w.circleIncorrectAnswered)
23032
23410
  // used in BaseRadio editMode to check
23033
23411
  // if we actually clicked on the radio icon
@@ -23099,24 +23477,24 @@ const styles$w = aphrodite.StyleSheet.create({
23099
23477
  * ChoiceIcon visualizations but is meant to make the distinctions
23100
23478
  * between the states more immediately clear to users.
23101
23479
  */
23102
- function renderText(checked, correct, crossedOut) {
23480
+ function renderText(checked, correct, crossedOut, strings) {
23103
23481
  if (correct) {
23104
23482
  // For correct answers, we surface checked _or_ crossedOut state,
23105
23483
  // because any interaction with the correct answer is noteworthy!
23106
23484
  if (checked) {
23107
- return i18n__namespace._("Correct (selected)");
23485
+ return strings.correctSelected;
23108
23486
  }
23109
23487
  if (crossedOut) {
23110
- return i18n__namespace._("Correct (but you crossed it out)");
23488
+ return strings.correctCrossedOut;
23111
23489
  }
23112
- return i18n__namespace._("Correct");
23490
+ return strings.correct;
23113
23491
  }
23114
23492
  // But, for incorrect answers, we only surface checked state,
23115
23493
  // because crossing out an incorrect answer is not noteworthy.
23116
23494
  if (checked) {
23117
- return i18n__namespace._("Incorrect (selected)");
23495
+ return strings.incorrectSelected;
23118
23496
  }
23119
- return i18n__namespace._("Incorrect");
23497
+ return strings.incorrect;
23120
23498
  }
23121
23499
  const OptionStatus = function (props) {
23122
23500
  const {
@@ -23126,6 +23504,9 @@ const OptionStatus = function (props) {
23126
23504
  previouslyAnswered,
23127
23505
  reviewMode
23128
23506
  } = props;
23507
+ const {
23508
+ strings
23509
+ } = usePerseusI18n();
23129
23510
 
23130
23511
  // Option status is shown only in review mode, or for incorrectly
23131
23512
  // answered items.
@@ -23145,7 +23526,7 @@ const OptionStatus = function (props) {
23145
23526
  }
23146
23527
  return /*#__PURE__*/React__namespace.createElement("div", {
23147
23528
  className: aphrodite.css(styles$v.text, textStyle)
23148
- }, renderText(checked, correct, crossedOut));
23529
+ }, renderText(checked, correct, crossedOut, strings));
23149
23530
  };
23150
23531
  const styles$v = aphrodite.StyleSheet.create({
23151
23532
  text: {
@@ -23192,6 +23573,9 @@ const Choice = function (props) {
23192
23573
  forwardedRef
23193
23574
  } = props;
23194
23575
  const [isInputFocused, setIsInputFocused] = React.useState(false);
23576
+ const {
23577
+ strings
23578
+ } = usePerseusI18n();
23195
23579
  React.useEffect(() => {
23196
23580
  if (isInputFocused && disabled) {
23197
23581
  setIsInputFocused(false);
@@ -23220,8 +23604,8 @@ const Choice = function (props) {
23220
23604
  // However, we don't want to do this when we're in review mode in the
23221
23605
  // content library.
23222
23606
  const showDimmed = !reviewMode && apiOptions.readOnly || crossedOut;
23223
- const letter = getChoiceLetter(pos);
23224
- const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
23607
+ const letter = getChoiceLetter(pos, strings);
23608
+ const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness, strings);
23225
23609
  const idFactory = wonderBlocksCore.useUniqueIdWithMock(`choice`);
23226
23610
  const choiceId = idFactory.get(letter);
23227
23611
  return /*#__PURE__*/React__namespace.createElement("div", {
@@ -23326,15 +23710,15 @@ const Choice = function (props) {
23326
23710
  close
23327
23711
  } = _ref2;
23328
23712
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksPopover.PopoverContent, {
23329
- title: i18n__namespace._("Cross out"),
23330
- content: i18n__namespace._("Cross out option"),
23713
+ title: strings.crossOut,
23714
+ content: strings.crossOutOption,
23331
23715
  closeButtonVisible: true,
23332
23716
  actions: /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksLayout.Strut, {
23333
23717
  size: wonderBlocksTokens.spacing.medium_16
23334
23718
  }), /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
23335
23719
  kind: "primary",
23336
- "aria-label": i18n__namespace._("Cross out Choice %(letter)s", {
23337
- letter: getChoiceLetter(pos)
23720
+ "aria-label": strings.crossOutChoice({
23721
+ letter: getChoiceLetter(pos, strings)
23338
23722
  }),
23339
23723
  disabled: apiOptions.readOnly || reviewMode,
23340
23724
  onClick: () => {
@@ -23354,7 +23738,7 @@ const Choice = function (props) {
23354
23738
  }
23355
23739
  close();
23356
23740
  }
23357
- }, crossedOut ? i18n__namespace._("Bring back") : i18n__namespace._("Cross out")))
23741
+ }, crossedOut ? strings.bringBack : strings.crossOut))
23358
23742
  });
23359
23743
  }
23360
23744
  }, _ref3 => {
@@ -23363,8 +23747,8 @@ const Choice = function (props) {
23363
23747
  } = _ref3;
23364
23748
  return /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
23365
23749
  onClick: open,
23366
- "aria-label": i18n__namespace._(`Open menu for Choice %(letter)s`, {
23367
- letter: getChoiceLetter(pos)
23750
+ "aria-label": strings.openMenuForChoice({
23751
+ letter: getChoiceLetter(pos, strings)
23368
23752
  }),
23369
23753
  style: {
23370
23754
  alignSelf: "center",
@@ -23383,7 +23767,7 @@ const Choice = function (props) {
23383
23767
  });
23384
23768
  })), showRationale && /*#__PURE__*/React__namespace.createElement("div", {
23385
23769
  className: rationaleClassName,
23386
- "data-test-id": `perseus-radio-rationale-content-${pos}`
23770
+ "data-testid": `perseus-radio-rationale-content-${pos}`
23387
23771
  }, rationale));
23388
23772
  };
23389
23773
  const styles$u = aphrodite.StyleSheet.create({
@@ -23414,6 +23798,9 @@ const ChoiceNoneAbove = function (props) {
23414
23798
  forwardedRef,
23415
23799
  ...rest
23416
23800
  } = props;
23801
+ const {
23802
+ strings
23803
+ } = usePerseusI18n();
23417
23804
  const choiceProps = {
23418
23805
  ...rest,
23419
23806
  content: showContent ? content :
@@ -23430,7 +23817,8 @@ const ChoiceNoneAbove = function (props) {
23430
23817
  // just to render a string
23431
23818
  React__namespace.createElement(Renderer, {
23432
23819
  key: "noneOfTheAboveRenderer",
23433
- content: i18n__namespace._("None of the above")
23820
+ content: strings.noneOfTheAbove,
23821
+ strings: strings
23434
23822
  })
23435
23823
  };
23436
23824
  return /*#__PURE__*/React__namespace.createElement(Choice$1, _extends({}, choiceProps, {
@@ -23450,16 +23838,16 @@ const {
23450
23838
 
23451
23839
  // exported for tests
23452
23840
 
23453
- function getInstructionsText(multipleSelect, countChoices, numCorrect) {
23841
+ function getInstructionsText(multipleSelect, countChoices, numCorrect, strings) {
23454
23842
  if (multipleSelect) {
23455
23843
  if (countChoices) {
23456
- return i18n__namespace._("Choose %(numCorrect)s answers:", {
23457
- numCorrect: numCorrect
23844
+ return strings.chooseNumAnswers({
23845
+ numCorrect: String(numCorrect)
23458
23846
  });
23459
23847
  }
23460
- return i18n__namespace._("Choose all answers that apply:");
23848
+ return strings.chooseAllAnswers;
23461
23849
  }
23462
- return i18n__namespace._("Choose 1 answer:");
23850
+ return strings.chooseOneAnswer;
23463
23851
  }
23464
23852
  const BaseRadio = function (props) {
23465
23853
  const {
@@ -23474,6 +23862,9 @@ const BaseRadio = function (props) {
23474
23862
  isLastUsedWidget,
23475
23863
  registerFocusFunction
23476
23864
  } = props;
23865
+ const {
23866
+ strings
23867
+ } = usePerseusI18n();
23477
23868
 
23478
23869
  // useEffect doesn't have previous props
23479
23870
  const prevReviewModeRubric = React.useRef();
@@ -23580,7 +23971,7 @@ const BaseRadio = function (props) {
23580
23971
  const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
23581
23972
  const className = classNames__default["default"]("perseus-widget-radio", !editMode && "perseus-rendered-radio", aphrodite.css(styles$t.radio, styles$t.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$t.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$t.radioContainerLastHighlighted));
23582
23973
  const instructionsClassName = classNames__default["default"]("instructions", aphrodite.css(styles$t.instructions, isMobile && styles$t.instructionsMobile));
23583
- const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect);
23974
+ const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect, strings);
23584
23975
  const responsiveClassName = aphrodite.css(styles$t.responsiveFieldset);
23585
23976
  const fieldset = /*#__PURE__*/React__namespace.createElement("fieldset", {
23586
23977
  className: `perseus-widget-radio-fieldset ${responsiveClassName}`
@@ -23803,8 +24194,9 @@ const styles$t = aphrodite.StyleSheet.create({
23803
24194
  // RenderProps is the return type for radio.jsx#transform
23804
24195
 
23805
24196
  class Radio$1 extends React__namespace.Component {
24197
+ static contextType = PerseusI18nContext;
23806
24198
  // @ts-expect-error - TS2564 - Property 'focusFunction' has no initializer and is not definitely assigned in the constructor.
23807
-
24199
+ focusFunction;
23808
24200
  static defaultProps = {
23809
24201
  choices: [],
23810
24202
  multipleSelect: false,
@@ -23813,7 +24205,7 @@ class Radio$1 extends React__namespace.Component {
23813
24205
  linterContext: PerseusLinter.linterContextDefault,
23814
24206
  showSolutions: "none"
23815
24207
  };
23816
- static validate(userInput, rubric) {
24208
+ static validate(userInput, rubric, strings) {
23817
24209
  const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
23818
24210
  return sum + (selected ? 1 : 0);
23819
24211
  }, 0);
@@ -23826,7 +24218,7 @@ class Radio$1 extends React__namespace.Component {
23826
24218
  if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
23827
24219
  return {
23828
24220
  type: "invalid",
23829
- message: i18n__namespace._("Please choose the correct number of answers.")
24221
+ message: strings.chooseCorrectNum
23830
24222
  };
23831
24223
  // If NOTA and some other answer are checked, ...
23832
24224
  }
@@ -23834,7 +24226,7 @@ class Radio$1 extends React__namespace.Component {
23834
24226
  if (userInput.noneOfTheAboveSelected && numSelected > 1) {
23835
24227
  return {
23836
24228
  type: "invalid",
23837
- message: i18n__namespace._("'None of the above' may not be selected " + "when other answers are selected.")
24229
+ message: strings.notNoneOfTheAbove
23838
24230
  };
23839
24231
  }
23840
24232
  const correct = userInput.choicesSelected.every((selected, i) => {
@@ -23968,7 +24360,8 @@ class Radio$1 extends React__namespace.Component {
23968
24360
  ..._this.props.linterContext,
23969
24361
  // @ts-expect-error - TS2322 - Type '{ blockHighlight: true; contentType: string; highlightLint: boolean; paths: readonly string[]; stack: readonly string[]; }' is not assignable to type 'LinterContextProps'.
23970
24362
  blockHighlight: true
23971
- }
24363
+ },
24364
+ strings: _this.context.strings
23972
24365
  });
23973
24366
  };
23974
24367
  })();
@@ -24041,7 +24434,7 @@ class Radio$1 extends React__namespace.Component {
24041
24434
  return Radio$1.getUserInputFromProps(this.props);
24042
24435
  };
24043
24436
  simpleValidate = rubric => {
24044
- return Radio$1.validate(this.getUserInput(), rubric);
24437
+ return Radio$1.validate(this.getUserInput(), rubric, this.context.strings);
24045
24438
  };
24046
24439
 
24047
24440
  /**
@@ -24110,6 +24503,9 @@ class Radio$1 extends React__namespace.Component {
24110
24503
  const {
24111
24504
  choices
24112
24505
  } = this.props;
24506
+ const {
24507
+ strings
24508
+ } = this.context;
24113
24509
  let choiceStates;
24114
24510
  if (this.props.static) {
24115
24511
  choiceStates = choices.map(choice => ({
@@ -24164,11 +24560,7 @@ class Radio$1 extends React__namespace.Component {
24164
24560
  }));
24165
24561
  }
24166
24562
  const choicesProp = choices.map((choice, i) => {
24167
- const content = choice.isNoneOfTheAbove && !choice.content ?
24168
- // we use i18n._ instead of $_ here because the content
24169
- // sent to a renderer needs to be a string, not a react
24170
- // node (/renderable/fragment).
24171
- i18n__namespace._("None of the above") : choice.content;
24563
+ const content = choice.isNoneOfTheAbove && !choice.content ? strings.noneOfTheAbove : choice.content;
24172
24564
  const {
24173
24565
  selected,
24174
24566
  crossedOut,
@@ -24226,14 +24618,8 @@ const {
24226
24618
  random
24227
24619
  } = Util;
24228
24620
 
24229
- // Represents choices that we automatically re-order if encountered.
24230
- // Note: these are in the reversed (incorrect) order that we will swap, if
24231
- // found.
24232
- // Note 2: these are internationalized when compared later on.
24233
- const ReversedChoices = [[i18n__namespace._("False"), i18n__namespace._("True")], [i18n__namespace._("No"), i18n__namespace._("Yes")]];
24234
-
24235
24621
  // Transforms the choices for display.
24236
- const _choiceTransform = (widgetOptions, problemNum) => {
24622
+ const _choiceTransform = (widgetOptions, strings, problemNum) => {
24237
24623
  const _maybeRandomize = function (array) {
24238
24624
  const randomSeed = problemNum === undefined ? random : problemNum;
24239
24625
  // NOTE: `problemNum` will only be set when the radio widget is
@@ -24262,6 +24648,11 @@ const _choiceTransform = (widgetOptions, problemNum) => {
24262
24648
  return newChoices;
24263
24649
  };
24264
24650
  const enforceOrdering = choices => {
24651
+ // Represents choices that we automatically re-order if encountered.
24652
+ // Note: these are in the reversed (incorrect) order that we will swap, if
24653
+ // found.
24654
+ // Note 2: these are internationalized when compared later on.
24655
+ const ReversedChoices = [[strings.false, strings.true], [strings.no, strings.yes]];
24265
24656
  const content = choices.map(c => c.content);
24266
24657
  if (ReversedChoices.some(reversed => ___default["default"].isEqual(content, reversed))) {
24267
24658
  return [choices[1], choices[0]];
@@ -24288,8 +24679,8 @@ const _choiceTransform = (widgetOptions, problemNum) => {
24288
24679
  // 1) we randomize the order first
24289
24680
  _maybeRandomize(choices)));
24290
24681
  };
24291
- const transform = (widgetOptions, problemNum) => {
24292
- const choices = _choiceTransform(widgetOptions, problemNum);
24682
+ const transform = (widgetOptions, strings, problemNum) => {
24683
+ const choices = _choiceTransform(widgetOptions, strings, problemNum);
24293
24684
  const numCorrect = ___default["default"].reduce(widgetOptions.choices, function (memo, choice) {
24294
24685
  return choice.correct ? memo + 1 : memo;
24295
24686
  }, 0);
@@ -24362,6 +24753,7 @@ const WidgetJsonifyDeprecated = {
24362
24753
 
24363
24754
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
24364
24755
  class Categorizer extends React__namespace.Component {
24756
+ static contextType = PerseusI18nContext;
24365
24757
  static defaultProps = {
24366
24758
  items: [],
24367
24759
  categories: [],
@@ -24410,7 +24802,8 @@ class Categorizer extends React__namespace.Component {
24410
24802
  key: i
24411
24803
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
24412
24804
  content: category,
24413
- linterContext: this.props.linterContext
24805
+ linterContext: this.props.linterContext,
24806
+ strings: this.context.strings
24414
24807
  }));
24415
24808
  }))), /*#__PURE__*/React__namespace.createElement("tbody", null, indexedItems.map(indexedItem => {
24416
24809
  const item = indexedItem[0];
@@ -24422,7 +24815,8 @@ class Categorizer extends React__namespace.Component {
24422
24815
  // @ts-expect-error - TS2322 - Type 'string | number' is not assignable to type 'string | undefined'.
24423
24816
  , {
24424
24817
  content: item,
24425
- linterContext: this.props.linterContext
24818
+ linterContext: this.props.linterContext,
24819
+ strings: this.context.strings
24426
24820
  })), self.props.categories.map((catName, catNum) => {
24427
24821
  const selected = self.props.values[itemNum] === catNum;
24428
24822
  return /*#__PURE__*/React__namespace.createElement("td", {
@@ -24466,9 +24860,9 @@ class Categorizer extends React__namespace.Component {
24466
24860
  this.props.trackInteraction();
24467
24861
  }
24468
24862
  simpleValidate = rubric => {
24469
- return Categorizer.validate(this.getUserInput(), rubric);
24863
+ return Categorizer.validate(this.getUserInput(), rubric, this.context.strings);
24470
24864
  };
24471
- static validate(userInput, rubric) {
24865
+ static validate(userInput, rubric, strings) {
24472
24866
  let completed = true;
24473
24867
  let allCorrect = true;
24474
24868
  rubric.values.forEach((value, i) => {
@@ -24482,7 +24876,7 @@ class Categorizer extends React__namespace.Component {
24482
24876
  if (!completed) {
24483
24877
  return {
24484
24878
  type: "invalid",
24485
- message: i18n__namespace._("Make sure you select something for every row.")
24879
+ message: strings.invalidSelection
24486
24880
  };
24487
24881
  }
24488
24882
  return {
@@ -24796,6 +25190,7 @@ var CSProgram$1 = {
24796
25190
  // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
24797
25191
 
24798
25192
  class Definition extends React__namespace.Component {
25193
+ static contextType = PerseusI18nContext;
24799
25194
  static defaultProps = {
24800
25195
  togglePrompt: "define me",
24801
25196
  definition: "definition goes here"
@@ -24828,7 +25223,8 @@ class Definition extends React__namespace.Component {
24828
25223
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
24829
25224
  apiOptions: this.props.apiOptions,
24830
25225
  content: this.props.definition,
24831
- widgets: this.props.widgets
25226
+ widgets: this.props.widgets,
25227
+ strings: this.context.strings
24832
25228
  })),
24833
25229
  opened: activeDefinitionId === this.props.widgetId,
24834
25230
  onClose: () => setActiveDefinitionId(null),
@@ -24874,6 +25270,7 @@ var Definition$1 = {
24874
25270
  // The props are type `any` on purpose so that this can receive props
24875
25271
  // from any deprecated widget
24876
25272
  class DeprecatedStandin extends React__default["default"].Component {
25273
+ static contextType = PerseusI18nContext;
24877
25274
  static validate(userInput, rubric) {
24878
25275
  // Since this mean to replace an existing widget the learner
24879
25276
  // WILL earn points for this widget
@@ -24897,7 +25294,7 @@ class DeprecatedStandin extends React__default["default"].Component {
24897
25294
  paddingBottom: 8
24898
25295
  }
24899
25296
  }, /*#__PURE__*/React__default["default"].createElement(Banner__default["default"], {
24900
- text: i18n__namespace._("Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!"),
25297
+ text: this.context.strings.deprecatedStandin,
24901
25298
  kind: "info",
24902
25299
  layout: "full-width"
24903
25300
  }));
@@ -25014,6 +25411,7 @@ function mediaQueryIsMatched(mediaQuery) {
25014
25411
  return window.matchMedia(mediaQuery).matches;
25015
25412
  }
25016
25413
  class Explanation extends React__namespace.Component {
25414
+ static contextType = PerseusI18nContext;
25017
25415
  static defaultProps = {
25018
25416
  showPrompt: "Explain",
25019
25417
  hidePrompt: "Hide explanation",
@@ -25098,7 +25496,8 @@ class Explanation extends React__namespace.Component {
25098
25496
  apiOptions: this.props.apiOptions,
25099
25497
  content: this.props.explanation,
25100
25498
  widgets: this.props.widgets,
25101
- linterContext: this.props.linterContext
25499
+ linterContext: this.props.linterContext,
25500
+ strings: this.context.strings
25102
25501
  })))));
25103
25502
  }
25104
25503
  getUserInput = () => {
@@ -25161,6 +25560,7 @@ var Explanation$1 = {
25161
25560
  };
25162
25561
 
25163
25562
  class GradedGroupAnswerBar extends React__namespace.Component {
25563
+ static contextType = PerseusI18nContext;
25164
25564
  render() {
25165
25565
  const {
25166
25566
  apiOptions,
@@ -25168,6 +25568,13 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25168
25568
  onCheckAnswer,
25169
25569
  onNextQuestion
25170
25570
  } = this.props;
25571
+ const {
25572
+ keepTrying,
25573
+ tryAgain,
25574
+ check,
25575
+ correctExcited,
25576
+ nextQuestion
25577
+ } = this.context.strings;
25171
25578
  const answerBarStyle = {
25172
25579
  ...styles$o.answerBar,
25173
25580
  backgroundColor: answerBarState === "CORRECT" ? wonderBlocksTokens.color.offWhite : "white",
@@ -25182,10 +25589,10 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25182
25589
  style: {
25183
25590
  marginLeft: 8
25184
25591
  }
25185
- }, i18n__namespace._("Keep trying"))) : /*#__PURE__*/React__namespace.createElement("span", null); // empty span keeps the button on the right side
25592
+ }, keepTrying)) : /*#__PURE__*/React__namespace.createElement("span", null); // empty span keeps the button on the right side
25186
25593
 
25187
25594
  if (answerBarState !== "CORRECT") {
25188
- const buttonLabel = answerBarState === "INCORRECT" ? i18n__namespace._("Try again") : i18n__namespace._("Check");
25595
+ const buttonLabel = answerBarState === "INCORRECT" ? tryAgain : check;
25189
25596
  return /*#__PURE__*/React__namespace.createElement("div", {
25190
25597
  style: answerBarStyle
25191
25598
  }, message, /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
@@ -25208,13 +25615,13 @@ class GradedGroupAnswerBar extends React__namespace.Component {
25208
25615
  }
25209
25616
  }))), /*#__PURE__*/React__namespace.createElement("span", {
25210
25617
  role: "alert",
25211
- "aria-label": i18n__namespace._("Correct!"),
25618
+ "aria-label": correctExcited,
25212
25619
  style: {
25213
25620
  marginLeft: 8
25214
25621
  }
25215
- }, i18n__namespace._("Correct!"))), onNextQuestion && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
25622
+ }, correctExcited)), onNextQuestion && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
25216
25623
  onClick: onNextQuestion
25217
- }, i18n__namespace._("Next question")));
25624
+ }, nextQuestion));
25218
25625
  }
25219
25626
  }
25220
25627
  const fontSize = 17;
@@ -25270,10 +25677,6 @@ const getNextState = (currentState, answerable) => {
25270
25677
  }
25271
25678
  };
25272
25679
 
25273
- // Prepended to all invalid messages to make the widget messages a bit clearer
25274
- const INVALID_MESSAGE_PREFIX = i18n__namespace._("We couldn't grade your answer.");
25275
- const DEFAULT_INVALID_MESSAGE = i18n__namespace._("It looks like you left something blank or ") + i18n__namespace._("entered in an invalid answer.");
25276
-
25277
25680
  // exports has no 'transform'
25278
25681
 
25279
25682
  // A Graded Group is more or less a Group widget that displays a check
@@ -25281,6 +25684,7 @@ const DEFAULT_INVALID_MESSAGE = i18n__namespace._("It looks like you left someth
25281
25684
  // the stuff inside and displays feedback about whether the inputted answer was
25282
25685
  // correct or not.
25283
25686
  class GradedGroup extends React__namespace.Component {
25687
+ static contextType = PerseusI18nContext;
25284
25688
  static defaultProps = {
25285
25689
  title: "",
25286
25690
  content: "",
@@ -25342,8 +25746,13 @@ class GradedGroup extends React__namespace.Component {
25342
25746
  // eslint-disable-next-line react/no-string-refs
25343
25747
  // @ts-expect-error - TS2339 - Property 'score' does not exist on type 'ReactInstance'.
25344
25748
  const score = this.refs.renderer.score();
25749
+ const {
25750
+ INVALID_MESSAGE_PREFIX,
25751
+ DEFAULT_INVALID_MESSAGE_1,
25752
+ DEFAULT_INVALID_MESSAGE_2
25753
+ } = this.context.strings;
25345
25754
  const status = score.type === "points" ? score.total === score.earned ? GRADING_STATUSES.correct : GRADING_STATUSES.incorrect : GRADING_STATUSES.invalid;
25346
- const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${score.message}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE}`;
25755
+ const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${score.message}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE_1}${DEFAULT_INVALID_MESSAGE_2}`;
25347
25756
  this.setState({
25348
25757
  status: status,
25349
25758
  message: message,
@@ -25405,8 +25814,7 @@ class GradedGroup extends React__namespace.Component {
25405
25814
  color: "#526f03"
25406
25815
  }
25407
25816
  }));
25408
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
25409
- gradeStatus = i18n__namespace._("Correct");
25817
+ gradeStatus = this.context.strings.correct;
25410
25818
  } else if (this.state.status === GRADING_STATUSES.incorrect) {
25411
25819
  // TODO(jeremy): update to a WB colour
25412
25820
  // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
@@ -25415,8 +25823,7 @@ class GradedGroup extends React__namespace.Component {
25415
25823
  color: "#ff5454"
25416
25824
  }
25417
25825
  }));
25418
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
25419
- gradeStatus = i18n__namespace._("Incorrect");
25826
+ gradeStatus = this.context.strings.incorrect;
25420
25827
  }
25421
25828
  const mobileClass = this.props.inGradedGroupSet ? aphrodite.css(styles$n.gradedGroupInSet) : aphrodite.css(styles$n.gradedGroup);
25422
25829
  const classes = classNames__default["default"]({
@@ -25446,7 +25853,8 @@ class GradedGroup extends React__namespace.Component {
25446
25853
  readOnly
25447
25854
  },
25448
25855
  onInteractWithWidget: this._onInteractWithWidget,
25449
- linterContext: this.props.linterContext
25856
+ linterContext: this.props.linterContext,
25857
+ strings: this.context.strings
25450
25858
  })), !apiOptions.isMobile && icon && /*#__PURE__*/React__namespace.createElement("div", {
25451
25859
  className: "group-icon"
25452
25860
  }, icon), !apiOptions.isMobile && gradeStatus && /*#__PURE__*/React__namespace.createElement("div", {
@@ -25460,14 +25868,14 @@ class GradedGroup extends React__namespace.Component {
25460
25868
  kind: "secondary",
25461
25869
  disabled: this.props.apiOptions.readOnly,
25462
25870
  onClick: this._checkAnswer
25463
- }, i18n__namespace._("Check")), !apiOptions.isMobile && isCorrect && this.props.onNextQuestion && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
25871
+ }, this.context.strings.check), !apiOptions.isMobile && isCorrect && this.props.onNextQuestion && /*#__PURE__*/React__namespace.createElement(Button__default["default"], {
25464
25872
  kind: "secondary",
25465
25873
  disabled: this.props.apiOptions.readOnly,
25466
25874
  onClick: this.props.onNextQuestion,
25467
25875
  style: {
25468
25876
  marginLeft: 5
25469
25877
  }
25470
- }, i18n__namespace._("Next question")), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("button", {
25878
+ }, this.context.strings.nextQuestion), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("button", {
25471
25879
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
25472
25880
  tabIndex: "0",
25473
25881
  className: aphrodite.css(styles$n.explanationTitle),
@@ -25481,11 +25889,12 @@ class GradedGroup extends React__namespace.Component {
25481
25889
  showHint: false
25482
25890
  });
25483
25891
  }
25484
- }, i18n__namespace._("Hide explanation")), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props.hint, {
25892
+ }, this.context.strings.hideExplanation), /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, this.props.hint, {
25485
25893
  // eslint-disable-next-line react/no-string-refs
25486
25894
  ref: "hints-renderer",
25487
25895
  apiOptions: apiOptions,
25488
- linterContext: this.props.linterContext
25896
+ linterContext: this.props.linterContext,
25897
+ strings: this.context.strings
25489
25898
  }))) :
25490
25899
  /*#__PURE__*/
25491
25900
  // Not using Button here bc the styles won't work.
@@ -25503,7 +25912,7 @@ class GradedGroup extends React__namespace.Component {
25503
25912
  });
25504
25913
  },
25505
25914
  className: aphrodite.css(styles$n.showHintLink)
25506
- }, i18n__namespace._("Explain"))), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React__namespace.createElement(GradedGroupAnswerBar, {
25915
+ }, this.context.strings.explain)), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React__namespace.createElement(GradedGroupAnswerBar, {
25507
25916
  apiOptions: apiOptions,
25508
25917
  answerBarState: answerBarState,
25509
25918
  onCheckAnswer: this._checkAnswer,
@@ -25575,6 +25984,7 @@ var GradedGroup$1 = {
25575
25984
  };
25576
25985
 
25577
25986
  class Indicators extends React__namespace.Component {
25987
+ static contextType = PerseusI18nContext;
25578
25988
  handleKeyDown = (e, i) => {
25579
25989
  if (e.key === "Enter" || e.key === " ") {
25580
25990
  this.props.onChangeCurrentGroup(i);
@@ -25594,7 +26004,7 @@ class Indicators extends React__namespace.Component {
25594
26004
  key: title
25595
26005
  }, /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
25596
26006
  role: "button",
25597
- "aria-label": i18n__namespace._("Skip to %(title)s", {
26007
+ "aria-label": this.context.strings.skipToTitle({
25598
26008
  title
25599
26009
  }),
25600
26010
  style: styles$m.indicatorButton,
@@ -25612,7 +26022,7 @@ class Indicators extends React__namespace.Component {
25612
26022
  style: styles$m.indicatorDotActive
25613
26023
  }, /*#__PURE__*/React__namespace.createElement("span", {
25614
26024
  className: aphrodite.css(a11y.srOnly)
25615
- }, i18n__namespace._("Current"))));
26025
+ }, this.context.strings.current)));
25616
26026
  }));
25617
26027
  }));
25618
26028
  }
@@ -25624,7 +26034,7 @@ class Indicators extends React__namespace.Component {
25624
26034
  // are currently a little confusing.
25625
26035
  class GradedGroupSet extends React__namespace.Component {
25626
26036
  // @ts-expect-error - TS2564 - Property '_childGroup' has no initializer and is not definitely assigned in the constructor.
25627
-
26037
+ _childGroup;
25628
26038
  static defaultProps = {
25629
26039
  gradedGroups: [],
25630
26040
  linterContext: PerseusLinter.linterContextDefault
@@ -25703,7 +26113,7 @@ class GradedGroupSet extends React__namespace.Component {
25703
26113
  }
25704
26114
  const currentGroup = this.props.gradedGroups[this.state.currentGroup];
25705
26115
  if (!currentGroup) {
25706
- return /*#__PURE__*/React__namespace.createElement("span", null, i18n__namespace.doNotTranslate("No current group..."));
26116
+ return /*#__PURE__*/React__namespace.createElement("span", null, "No current group...");
25707
26117
  }
25708
26118
  const numGroups = this.props.gradedGroups.length;
25709
26119
  const handleNextQuestion = this.state.currentGroup < numGroups - 1 ? this.handleNextQuestion : null;
@@ -25826,6 +26236,7 @@ const styles$m = aphrodite.StyleSheet.create({
25826
26236
  * ButtonGroup is an aesthetically pleasing group of buttons.
25827
26237
  */
25828
26238
  class ButtonGroup extends React__namespace.Component {
26239
+ container;
25829
26240
  static defaultProps = {
25830
26241
  value: null,
25831
26242
  allowEmpty: true
@@ -26675,6 +27086,8 @@ class FunctionGrapher extends React__namespace.Component {
26675
27086
 
26676
27087
  /* Widget and editor. */
26677
27088
  class Grapher extends React__namespace.Component {
27089
+ horizHairline;
27090
+ vertHairline;
26678
27091
  static defaultProps = DEFAULT_GRAPHER_PROPS;
26679
27092
  render() {
26680
27093
  const type = this.props.plot.type;
@@ -26881,6 +27294,8 @@ var Grapher$1 = {
26881
27294
  // exports has no 'transform'
26882
27295
 
26883
27296
  class Group extends React__namespace.Component {
27297
+ static contextType = PerseusI18nContext;
27298
+ rendererRef;
26884
27299
  static defaultProps = {
26885
27300
  content: "",
26886
27301
  widgets: {},
@@ -26997,7 +27412,8 @@ class Group extends React__namespace.Component {
26997
27412
  findExternalWidgets: this.props.findWidgets,
26998
27413
  reviewMode: !!this.props.reviewModeRubric,
26999
27414
  onInteractWithWidget: onInteractWithWidget,
27000
- linterContext: this.props.linterContext
27415
+ linterContext: this.props.linterContext,
27416
+ strings: this.context.strings
27001
27417
  }), this.props.icon &&
27002
27418
  /*#__PURE__*/
27003
27419
  // @ts-expect-error - TS2339 - Property 'icon' does not exist on type 'Readonly<Props> & Readonly<{ children?: ReactNode; }>'.
@@ -27202,6 +27618,7 @@ const DEFAULT_ALIGNMENT = "block";
27202
27618
  // there is no transform as part of exports
27203
27619
 
27204
27620
  class ImageWidget extends React__namespace.Component {
27621
+ static contextType = PerseusI18nContext;
27205
27622
  static defaultProps = {
27206
27623
  alignment: DEFAULT_ALIGNMENT,
27207
27624
  title: "",
@@ -27290,7 +27707,8 @@ class ImageWidget extends React__namespace.Component {
27290
27707
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
27291
27708
  content: this.props.alt,
27292
27709
  apiOptions: apiOptions,
27293
- linterContext: this.props.linterContext
27710
+ linterContext: this.props.linterContext,
27711
+ strings: this.context.strings
27294
27712
  }));
27295
27713
  }
27296
27714
 
@@ -27350,7 +27768,8 @@ class ImageWidget extends React__namespace.Component {
27350
27768
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
27351
27769
  content: title + this.props.caption,
27352
27770
  apiOptions: apiOptions,
27353
- linterContext: this.props.linterContext
27771
+ linterContext: this.props.linterContext,
27772
+ strings: this.context.strings
27354
27773
  })));
27355
27774
  }
27356
27775
  return /*#__PURE__*/React__namespace.createElement("figure", {
@@ -27368,7 +27787,8 @@ class ImageWidget extends React__namespace.Component {
27368
27787
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
27369
27788
  content: this.props.title,
27370
27789
  apiOptions: apiOptions,
27371
- linterContext: this.props.linterContext
27790
+ linterContext: this.props.linterContext,
27791
+ strings: this.context.strings
27372
27792
  }));
27373
27793
  }
27374
27794
  if (this.props.caption) {
@@ -27380,7 +27800,8 @@ class ImageWidget extends React__namespace.Component {
27380
27800
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
27381
27801
  content: this.props.caption,
27382
27802
  apiOptions: apiOptions,
27383
- linterContext: this.props.linterContext
27803
+ linterContext: this.props.linterContext,
27804
+ strings: this.context.strings
27384
27805
  }));
27385
27806
  }
27386
27807
  return /*#__PURE__*/React__namespace.createElement("figure", {
@@ -28050,10 +28471,13 @@ function numSteps$1(range, step) {
28050
28471
  return Math.floor((range[1] - range[0]) / step);
28051
28472
  }
28052
28473
  class Graph extends React__namespace.Component {
28474
+ protractor;
28475
+ ruler;
28476
+ _graphie;
28053
28477
  // @ts-expect-error - TS2564 - Property '_hasSetupGraphieThisUpdate' has no initializer and is not definitely assigned in the constructor.
28054
-
28478
+ _hasSetupGraphieThisUpdate;
28055
28479
  // @ts-expect-error - TS2564 - Property '_shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
28056
-
28480
+ _shouldSetupGraphie;
28057
28481
  static defaultProps = {
28058
28482
  labels: ["x", "y"],
28059
28483
  range: [[-10, 10], [-10, 10]],
@@ -28322,44 +28746,216 @@ class Graph extends React__namespace.Component {
28322
28746
  }
28323
28747
  }
28324
28748
 
28325
- /**
28326
- * Takes a style object with color names and converts them to actual
28327
- * Wonder Blocks color values. This way, we can use colors and their
28328
- * names in the editor UI without having to convert them back and forth.
28329
- */
28330
- function convertStyle(style) {
28331
- return {
28332
- ...style,
28333
- stroke: wonderBlocksTokens.color[style?.stroke || "blue"],
28334
- fill: wonderBlocksTokens.color[style?.fill || "blue"]
28335
- };
28336
- }
28749
+ // TODO(FEI-4010): Remove `Perseus` prefix for all types here
28750
+ // TODO(FEI-4011): Use types generated by https://github.com/jaredly/generate-perseus-flowtypes
28751
+
28752
+ // Range is replaced within this file with Interval, but it is used elsewhere
28753
+ // and exported from the package, so we need to keep it around.
28754
+ // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
28755
+ const ItemExtras = [
28756
+ // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
28757
+ "calculator",
28758
+ // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
28759
+ "chi2Table",
28760
+ // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
28761
+ "financialCalculatorMonthlyPayment",
28762
+ // The user might benefit from a total amount calculator. Provided on Khan Academy when true
28763
+ "financialCalculatorTotalAmount",
28764
+ // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
28765
+ "financialCalculatorTimeToPayOff",
28766
+ // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
28767
+ "periodicTable",
28768
+ // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
28769
+ "periodicTableWithKey",
28770
+ // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
28771
+ "tTable",
28772
+ // The user might benefit from using a statistics Z Table like https://www.ztable.net/
28773
+ "zTable"];
28774
+
28775
+ // prettier-ignore
28776
+
28777
+ // prettier-ignore
28778
+
28779
+ // prettier-ignore
28780
+
28781
+ // prettier-ignore
28782
+
28783
+ // prettier-ignore
28784
+
28785
+ // prettier-ignore
28786
+
28787
+ // prettier-ignore
28788
+
28789
+ // prettier-ignore
28790
+
28791
+ // prettier-ignore
28792
+
28793
+ // prettier-ignore
28794
+
28795
+ // prettier-ignore
28796
+
28797
+ // prettier-ignore
28798
+
28799
+ // prettier-ignore
28800
+
28801
+ // prettier-ignore
28802
+
28803
+ // prettier-ignore
28804
+
28805
+ // prettier-ignore
28806
+
28807
+ // prettier-ignore
28808
+
28809
+ // prettier-ignore
28810
+
28811
+ // prettier-ignore
28812
+
28813
+ // prettier-ignore
28814
+
28815
+ // prettier-ignore
28816
+
28817
+ // prettier-ignore
28818
+
28819
+ // prettier-ignore
28820
+
28821
+ // prettier-ignore
28822
+
28823
+ // prettier-ignore
28824
+
28825
+ // prettier-ignore
28826
+
28827
+ // prettier-ignore
28828
+
28829
+ // prettier-ignore
28830
+
28831
+ // prettier-ignore
28832
+
28833
+ // prettier-ignore
28834
+
28835
+ // prettier-ignore
28836
+
28837
+ // prettier-ignore
28838
+
28839
+ // prettier-ignore
28840
+
28841
+ // prettier-ignore
28842
+
28843
+ // prettier-ignore
28844
+
28845
+ // prettier-ignore
28846
+
28847
+ //prettier-ignore
28848
+
28849
+ // A background image applied to various widgets.
28850
+
28851
+ const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28852
+ const lockedFigureColors = {
28853
+ blue: "#3D7586",
28854
+ green: "#447A53",
28855
+ gray: "#5D5F66",
28856
+ grayH: "#3B3D45",
28857
+ grayI: "#21242C",
28858
+ purple: "#594094",
28859
+ purpleD: "#8351E8",
28860
+ pink: "#B25071",
28861
+ orange: "#946700",
28862
+ red: "#D92916"
28863
+ };
28864
+ const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28865
+
28866
+ const LockedLine = props => {
28867
+ const {
28868
+ color,
28869
+ lineStyle,
28870
+ kind,
28871
+ points,
28872
+ showStartPoint,
28873
+ showEndPoint
28874
+ } = props;
28875
+ const [point1, point2] = points;
28876
+ let line;
28877
+ if (kind === "line" || kind === "segment") {
28878
+ const LineType = kind === "segment" ? mafs.Line.Segment : mafs.Line.ThroughPoints;
28879
+ line = /*#__PURE__*/React__namespace.createElement(LineType, {
28880
+ point1: point1.coord,
28881
+ point2: point2.coord,
28882
+ color: lockedFigureColors[color],
28883
+ style: lineStyle
28884
+ });
28885
+ } else {
28886
+ // TODO(LEMS-1928): Implement rays
28887
+ return null;
28888
+ }
28889
+ return /*#__PURE__*/React__namespace.createElement("g", {
28890
+ className: "locked-line"
28891
+ }, line, showStartPoint && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
28892
+ x: point1.coord[0],
28893
+ y: point1.coord[1],
28894
+ svgCircleProps: {
28895
+ style: {
28896
+ fill: point1.filled ? lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
28897
+ stroke: lockedFigureColors[point1.color],
28898
+ strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
28899
+ }
28900
+ }
28901
+ }), showEndPoint && /*#__PURE__*/React__namespace.createElement(mafs.Point, {
28902
+ x: point2.coord[0],
28903
+ y: point2.coord[1],
28904
+ svgCircleProps: {
28905
+ style: {
28906
+ fill: point2.filled ? lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
28907
+ stroke: lockedFigureColors[point2.color],
28908
+ strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
28909
+ }
28910
+ }
28911
+ }));
28912
+ };
28913
+
28914
+ const LockedPoint = props => {
28915
+ const {
28916
+ color,
28917
+ coord,
28918
+ filled
28919
+ } = props;
28920
+ const [x, y] = coord;
28921
+ return /*#__PURE__*/React__namespace.createElement(mafs.Point, {
28922
+ x: x,
28923
+ y: y,
28924
+ svgCircleProps: {
28925
+ style: {
28926
+ fill: filled ? lockedFigureColors[color] : wonderBlocksTokens.color.white,
28927
+ stroke: lockedFigureColors[color],
28928
+ strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
28929
+ }
28930
+ }
28931
+ });
28932
+ };
28933
+
28337
28934
  const GraphLockedLayer = props => {
28338
28935
  const {
28339
28936
  lockedFigures
28340
28937
  } = props;
28341
28938
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lockedFigures.map((figure, index) => {
28939
+ let Figure;
28342
28940
  switch (figure.type) {
28343
28941
  case "point":
28344
- const [x, y] = figure.coord;
28345
- return /*#__PURE__*/React__namespace.createElement(mafs.Point, {
28346
- key: `${figure.type}-${index}`,
28347
- x: x,
28348
- y: y,
28349
- svgCircleProps: {
28350
- style: convertStyle(figure.style)
28351
- }
28352
- });
28353
- }
28354
-
28355
- /**
28356
- * Devlopment-time future-proofing: This `never` should
28357
- * fail during type-checking if we add a new locked
28358
- * shape type and forget to handle it in any other
28359
- * switch case here.
28360
- */
28361
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
28362
- throw new wonderStuffCore.UnreachableCaseError(figure.type);
28942
+ Figure = LockedPoint;
28943
+ break;
28944
+ case "line":
28945
+ Figure = LockedLine;
28946
+ break;
28947
+ default:
28948
+ /**
28949
+ * Devlopment-time future-proofing: This should
28950
+ * fail during type-checking if we add a new locked
28951
+ * shape type and forget to handle it in any other
28952
+ * switch case here.
28953
+ */
28954
+ throw new wonderStuffCore.UnreachableCaseError(figure);
28955
+ }
28956
+ return /*#__PURE__*/React__namespace.createElement(Figure, _extends({
28957
+ key: `${figure.type}-${index}`
28958
+ }, figure));
28363
28959
  }));
28364
28960
  };
28365
28961
 
@@ -28511,7 +29107,7 @@ function SVGLine(props) {
28511
29107
  y2: end[1],
28512
29108
  style: style,
28513
29109
  className: `${className}`,
28514
- "data-test-id": testId
29110
+ "data-testid": testId
28515
29111
  });
28516
29112
  }
28517
29113
 
@@ -28640,7 +29236,7 @@ const MovableLine = props => {
28640
29236
  ref: line,
28641
29237
  tabIndex: 0,
28642
29238
  className: "movable-line",
28643
- "data-test-id": "movable-line",
29239
+ "data-testid": "movable-line",
28644
29240
  style: {
28645
29241
  cursor: dragging ? "grabbing" : "grab"
28646
29242
  }
@@ -28684,7 +29280,8 @@ const MovableLine = props => {
28684
29280
  const defaultGraphConfig = {
28685
29281
  range: [[0, 1], [0, 1]],
28686
29282
  snapStep: [1, 1],
28687
- markings: "none"
29283
+ markings: "none",
29284
+ showTooltips: false
28688
29285
  };
28689
29286
  const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28690
29287
  function useGraphConfig() {
@@ -28698,7 +29295,8 @@ const StyledMovablePoint = props => {
28698
29295
  const {
28699
29296
  range,
28700
29297
  snapStep,
28701
- markings
29298
+ markings,
29299
+ showTooltips
28702
29300
  } = useGraphConfig();
28703
29301
  const hitboxRef = React.useRef(null);
28704
29302
  const {
@@ -28706,6 +29304,13 @@ const StyledMovablePoint = props => {
28706
29304
  onMove,
28707
29305
  color = wonderBlocksTokens.color.blue
28708
29306
  } = props;
29307
+
29308
+ // WB Tooltip requires a color name for the background color.
29309
+ // Since the color in props is a hex value, a reverse lookup is needed.
29310
+ const wbColorName = Object.entries(wonderBlocksTokens.color).find(_ref => {
29311
+ let [_, value] = _ref;
29312
+ return value === color;
29313
+ })?.[0] ?? "blue";
28709
29314
  const {
28710
29315
  dragging
28711
29316
  } = mafs.useMovable({
@@ -28723,26 +29328,27 @@ const StyledMovablePoint = props => {
28723
29328
  const [[_, horizontalStartY]] = useTransform([0, yMin]);
28724
29329
  const [[__, horizontalEndY]] = useTransform([0, yMax]);
28725
29330
  const showHairlines = dragging && markings !== "none";
28726
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
29331
+ const hairlines = /*#__PURE__*/React__namespace.createElement("g", null, /*#__PURE__*/React__namespace.createElement("line", {
28727
29332
  x1: verticalStartX,
28728
29333
  y1: y,
28729
29334
  x2: verticalEndX,
28730
29335
  y2: y,
28731
- stroke: wonderBlocksTokens.color.green
29336
+ stroke: color
28732
29337
  }), /*#__PURE__*/React__namespace.createElement("line", {
28733
29338
  x1: x,
28734
29339
  y1: horizontalStartY,
28735
29340
  x2: x,
28736
29341
  y2: horizontalEndY,
28737
- stroke: wonderBlocksTokens.color.green
28738
- })), /*#__PURE__*/React__namespace.createElement("g", {
29342
+ stroke: color
29343
+ }));
29344
+ const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
28739
29345
  ref: hitboxRef,
28740
29346
  className: pointClasses,
28741
29347
  tabIndex: 0,
28742
29348
  style: {
28743
29349
  "--movable-point-color": color
28744
29350
  },
28745
- "data-test-id": "movable-point"
29351
+ "data-testid": "movable-point"
28746
29352
  }, /*#__PURE__*/React__namespace.createElement("circle", {
28747
29353
  className: "movable-point-hitbox",
28748
29354
  r: hitboxSizePx / 2,
@@ -28767,8 +29373,16 @@ const StyledMovablePoint = props => {
28767
29373
  style: {
28768
29374
  fill: color
28769
29375
  },
28770
- "data-test-id": "movable-point__center"
28771
- })));
29376
+ "data-testid": "movable-point__center"
29377
+ }));
29378
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React__namespace.createElement(Tooltip__default["default"], {
29379
+ autoUpdate: true,
29380
+ backgroundColor: wbColorName,
29381
+ content: `(${point[0]}, ${point[1]})`,
29382
+ contentStyle: {
29383
+ color: "white"
29384
+ }
29385
+ }, svgForPoint) : svgForPoint);
28772
29386
  };
28773
29387
 
28774
29388
  const SegmentGraph = props => {
@@ -28786,8 +29400,7 @@ const SegmentGraph = props => {
28786
29400
  },
28787
29401
  onMovePoint: (endpointIndex, destination) => {
28788
29402
  dispatch(moveControlPoint(endpointIndex, destination, i));
28789
- },
28790
- "data-testid": "segment" + i
29403
+ }
28791
29404
  })));
28792
29405
  };
28793
29406
  const SegmentView = props => {
@@ -28817,8 +29430,7 @@ const LinearGraph = props => {
28817
29430
  dispatch
28818
29431
  } = props;
28819
29432
  const {
28820
- coords: lines,
28821
- type
29433
+ coords: lines
28822
29434
  } = props.graphState;
28823
29435
  const colors = ["var(--movable-line-stroke-color)", "var(--mafs-violet)"];
28824
29436
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, lines?.map((line, i) => /*#__PURE__*/React__namespace.createElement(LineView, {
@@ -28827,10 +29439,7 @@ const LinearGraph = props => {
28827
29439
  onMoveLine: delta => {
28828
29440
  dispatch(moveLine(i, delta));
28829
29441
  },
28830
- onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i))
28831
- // "linear" or "linear-system" + index
28832
- ,
28833
- "data-testid": type + i,
29442
+ onMovePoint: (endpointIndex, destination) => dispatch(moveControlPoint(endpointIndex, destination, i)),
28834
29443
  stroke: colors[i]
28835
29444
  })));
28836
29445
  };
@@ -29077,7 +29686,6 @@ const PolygonGraph = props => {
29077
29686
  } = props;
29078
29687
  const {
29079
29688
  coords,
29080
- type,
29081
29689
  showAngles,
29082
29690
  showSides,
29083
29691
  range,
@@ -29149,8 +29757,7 @@ const PolygonGraph = props => {
29149
29757
  }), points.map((point, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
29150
29758
  key: "point-" + i,
29151
29759
  point: point,
29152
- onMove: destination => dispatch(movePoint(i, destination)),
29153
- "data-testid": type + i
29760
+ onMove: destination => dispatch(movePoint(i, destination))
29154
29761
  })));
29155
29762
  };
29156
29763
 
@@ -29679,6 +30286,7 @@ const getNumericFormat = KhanMath.getNumericFormat;
29679
30286
  * Optionally takes a size ("mini", "small", "normal")
29680
30287
  */
29681
30288
  class NumberInput extends React__namespace.Component {
30289
+ static contextType = PerseusI18nContext;
29682
30290
  static propTypes = {
29683
30291
  value: PropTypes__default["default"].number,
29684
30292
  format: PropTypes__default["default"].string,
@@ -29765,7 +30373,7 @@ class NumberInput extends React__namespace.Component {
29765
30373
  const placeholder = this.props.placeholder;
29766
30374
  return ___default["default"].isFinite(placeholder) ? +placeholder : null;
29767
30375
  }
29768
- const result = firstNumericalParse(value);
30376
+ const result = firstNumericalParse(value, this.context.strings);
29769
30377
  return ___default["default"].isFinite(result) ? result : this.props.value;
29770
30378
  };
29771
30379
 
@@ -29796,7 +30404,7 @@ class NumberInput extends React__namespace.Component {
29796
30404
  if (value == null) {
29797
30405
  return true;
29798
30406
  }
29799
- const val = firstNumericalParse(value);
30407
+ const val = firstNumericalParse(value, this.context.strings);
29800
30408
  const checkValidity = this.props.checkValidity;
29801
30409
  return ___default["default"].isFinite(val) && checkValidity(val);
29802
30410
  };
@@ -30595,7 +31203,8 @@ const MafsGraph = props => {
30595
31203
  value: {
30596
31204
  range: state.range,
30597
31205
  snapStep: state.snapStep,
30598
- markings: props.markings
31206
+ markings: props.markings,
31207
+ showTooltips: !!props.showTooltips
30599
31208
  }
30600
31209
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30601
31210
  style: {
@@ -30696,10 +31305,25 @@ const makeInvalidTypeError = (functionName, graphType) => {
30696
31305
  // There's no transform function in exports
30697
31306
 
30698
31307
  class LegacyInteractiveGraph extends React__namespace.Component {
31308
+ static contextType = PerseusI18nContext;
31309
+ angle;
31310
+ circle;
31311
+ graphie;
31312
+ horizHairline;
31313
+ line;
31314
+ lines;
31315
+ parabola;
31316
+ pointA;
31317
+ pointB;
31318
+ pointC;
30699
31319
  // @ts-expect-error - TS2564 - Property 'points' has no initializer and is not definitely assigned in the constructor.
30700
-
31320
+ points;
31321
+ polygon;
30701
31322
  // @ts-expect-error - TS2564 - Property 'shouldResetGraphie' has no initializer and is not definitely assigned in the constructor.
30702
-
31323
+ shouldResetGraphie;
31324
+ sinusoid;
31325
+ trashCan;
31326
+ vertHairline;
30703
31327
  static defaultProps = {
30704
31328
  labels: ["x", "y"],
30705
31329
  range: [[-10, 10], [-10, 10]],
@@ -31837,9 +32461,9 @@ class LegacyInteractiveGraph extends React__namespace.Component {
31837
32461
  // isClickToAddPoints() only applies to points and polygons
31838
32462
  if (this.isClickToAddPoints() && this.state.shouldShowInstructions) {
31839
32463
  if (this.props.graph.type === "point") {
31840
- instructions = i18n__namespace._("Click to add points");
32464
+ instructions = this.context.strings.addPoints;
31841
32465
  } else if (this.props.graph.type === "polygon") {
31842
- instructions = i18n__namespace._("Click to add vertices");
32466
+ instructions = this.context.strings.addVertices;
31843
32467
  }
31844
32468
  } else {
31845
32469
  instructions = undefined;
@@ -31917,7 +32541,8 @@ class InteractiveGraph extends React__namespace.Component {
31917
32541
  ref: this.mafsRef,
31918
32542
  gridStep: gridStep,
31919
32543
  snapStep: snapStep,
31920
- box: box
32544
+ box: box,
32545
+ showTooltips: !!this.props.showTooltips
31921
32546
  }));
31922
32547
  }
31923
32548
  return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
@@ -33444,6 +34069,9 @@ var globalStyles = /*#__PURE__*/Object.freeze({
33444
34069
  });
33445
34070
 
33446
34071
  const AnswerChoices = props => {
34072
+ const {
34073
+ strings
34074
+ } = usePerseusI18n();
33447
34075
  const onAnswerChange = selected => {
33448
34076
  const {
33449
34077
  choices,
@@ -33463,6 +34091,7 @@ const AnswerChoices = props => {
33463
34091
  value: content,
33464
34092
  label: /*#__PURE__*/React__namespace.createElement(Renderer, {
33465
34093
  content: content,
34094
+ strings: strings,
33466
34095
  inline: true
33467
34096
  })
33468
34097
  });
@@ -33493,14 +34122,13 @@ const AnswerChoices = props => {
33493
34122
  }));
33494
34123
  };
33495
34124
 
33496
- const strings = {
33497
- hideAnswersToggleLabel: i18n__namespace._("Hide answer choices")
33498
- };
33499
-
33500
34125
  const HideAnswersToggle = props => {
33501
34126
  const idFactory = wonderBlocksCore.useUniqueIdWithMock();
33502
34127
  const switchId = idFactory.get("hide-answers-toggle");
33503
34128
  const labelId = idFactory.get("hide-answers-label");
34129
+ const {
34130
+ strings
34131
+ } = usePerseusI18n();
33504
34132
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
33505
34133
  style: styles$g.switchWrapper
33506
34134
  }, /*#__PURE__*/React__namespace.createElement(Switch__default["default"], {
@@ -33541,9 +34169,12 @@ const AnswerPill = props => {
33541
34169
  hovered
33542
34170
  } = props;
33543
34171
  const idFactory = wonderBlocksCore.useUniqueIdWithMock();
33544
- const answerString = selectedAnswers.length > 1 ?
33545
- // always need `ngettext` for variable numbers even if we don't use the singular, see https://khanacademy.slack.com/archives/C0918TZ5G/p1700163024293079
33546
- i18n__namespace.ngettext("%(num)s answer", "%(num)s answers", selectedAnswers.length) : selectedAnswers[0];
34172
+ const {
34173
+ strings
34174
+ } = usePerseusI18n();
34175
+ const answerString = selectedAnswers.length > 1 ? strings.answers({
34176
+ num: selectedAnswers.length
34177
+ }) : selectedAnswers[0];
33547
34178
 
33548
34179
  // It should no longer be possible to interact with an answer after it
33549
34180
  // has been labeled (answered) correctly.
@@ -33572,6 +34203,7 @@ const AnswerPill = props => {
33572
34203
  style: [style, popperStyle, styles$f.pill, correct && styles$f.correct, incorrect && styles$f.incorrect, (focused || hovered) && BringToFront]
33573
34204
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
33574
34205
  content: answerString,
34206
+ strings: strings,
33575
34207
  inline: true
33576
34208
  }));
33577
34209
  });
@@ -33598,8 +34230,9 @@ function shouldReduceMotion() {
33598
34230
  }
33599
34231
  const MARKER_SIZE = 24;
33600
34232
  class Marker extends React__namespace.Component {
34233
+ static contextType = PerseusI18nContext;
33601
34234
  // The marker icon element.
33602
-
34235
+ _icon;
33603
34236
  static defaultProps = {
33604
34237
  selected: []
33605
34238
  };
@@ -33669,10 +34302,8 @@ class Marker extends React__namespace.Component {
33669
34302
  const markerDisabled = showCorrectness === "correct";
33670
34303
  const active = hovered || focused;
33671
34304
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
33672
- style: [styles$e.marker, active && !markerDisabled && styles$e.markerActive]
33673
- // already translated in: /widgets/graded-group.tsx
33674
- ,
33675
- "aria-label": markerDisabled ? i18n__namespace._("Correct!") : label
34305
+ style: [styles$e.marker, active && !markerDisabled && styles$e.markerActive],
34306
+ "aria-label": markerDisabled ? this.context.strings.correctExcited : label
33676
34307
  }, this.renderIcon()), !!selected && showAnswer && /*#__PURE__*/React__namespace.createElement(AnswerPill, {
33677
34308
  selectedAnswers: selected,
33678
34309
  showCorrectness: showCorrectness,
@@ -33772,8 +34403,9 @@ const styles$e = aphrodite.StyleSheet.create({
33772
34403
  */
33773
34404
 
33774
34405
  class LabelImage extends React__namespace.Component {
34406
+ static contextType = PerseusI18nContext;
33775
34407
  // The rendered markers on the question image for labeling.
33776
-
34408
+ _markers;
33777
34409
  static gradeMarker(marker) {
33778
34410
  const score = {
33779
34411
  hasAnswers: false,
@@ -34259,8 +34891,11 @@ class LabelImage extends React__namespace.Component {
34259
34891
  multipleAnswers,
34260
34892
  hideChoicesFromInstructions: hideChoices
34261
34893
  } = this.props;
34262
- const promptString = isMobile ? multipleAnswers ? i18n__namespace._("Tap each dot on the image to select all answers that apply.") : i18n__namespace._("Tap each dot on the image to select an answer.") : multipleAnswers ? i18n__namespace._("Click each dot on the image to select all answers that apply.") : i18n__namespace._("Click each dot on the image to select an answer.");
34263
- const choicesString = i18n__namespace._("Choices:");
34894
+ const {
34895
+ strings
34896
+ } = this.context;
34897
+ const promptString = isMobile ? multipleAnswers ? strings.tapMultiple : strings.tapSingle : multipleAnswers ? strings.clickMultiple : strings.clickSingle;
34898
+ const choicesString = strings.choices;
34264
34899
  return /*#__PURE__*/React__namespace.createElement("div", {
34265
34900
  className: classNames__default["default"]("perseus-label-image-widget-instructions", aphrodite.css(styles$d.instructions))
34266
34901
  }, /*#__PURE__*/React__namespace.createElement("div", {
@@ -34271,7 +34906,8 @@ class LabelImage extends React__namespace.Component {
34271
34906
  className: aphrodite.css(styles$d.instructionsChoice),
34272
34907
  key: index
34273
34908
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
34274
- content: choice
34909
+ content: choice,
34910
+ strings: strings
34275
34911
  })))));
34276
34912
  }
34277
34913
  render() {
@@ -34446,12 +35082,13 @@ class Placeholder extends React__namespace.Component {
34446
35082
  // Usual flow: Static -> Dragging -> Animating -> Static
34447
35083
  // [Dis|en]abling: Static|Dragging|Animating -> Disabled -> Static
34448
35084
  class Draggable extends React__namespace.Component {
35085
+ static contextType = PerseusI18nContext;
34449
35086
  // Handler returned by requestAnimationFrame.
34450
35087
  animationFrameRequest = null;
34451
35088
  // @ts-expect-error - TS2564 - Property 'isMouseMoveUpBound' has no initializer and is not definitely assigned in the constructor.
34452
-
35089
+ isMouseMoveUpBound;
34453
35090
  // @ts-expect-error - TS2564 - Property '_mounted' has no initializer and is not definitely assigned in the constructor.
34454
-
35091
+ _mounted;
34455
35092
  static defaultProps = {
34456
35093
  includePadding: true,
34457
35094
  type: ItemState.STATIC,
@@ -34554,7 +35191,8 @@ class Draggable extends React__namespace.Component {
34554
35191
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
34555
35192
  content: this.props.content,
34556
35193
  linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "draggable"),
34557
- onRender: this.props.onRender
35194
+ onRender: this.props.onRender,
35195
+ strings: this.context.strings
34558
35196
  }));
34559
35197
  }
34560
35198
  componentDidUpdate(prevProps) {
@@ -35108,6 +35746,7 @@ const {
35108
35746
  } = Util;
35109
35747
  const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
35110
35748
  class Matcher extends React__namespace.Component {
35749
+ static contextType = PerseusI18nContext;
35111
35750
  static propTypes = {
35112
35751
  apiOptions: ApiOptions.propTypes,
35113
35752
  labels: PropTypes__default["default"].array,
@@ -35187,12 +35826,14 @@ class Matcher extends React__namespace.Component {
35187
35826
  className: aphrodite.css(styles$b.column, styles$b.columnLabel)
35188
35827
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
35189
35828
  content: this.props.labels[0] || "...",
35190
- linterContext: this.props.linterContext
35829
+ linterContext: this.props.linterContext,
35830
+ strings: this.context.strings
35191
35831
  })), /*#__PURE__*/React__namespace.createElement("th", {
35192
35832
  className: aphrodite.css(styles$b.column, styles$b.columnRight, styles$b.columnLabel)
35193
35833
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
35194
35834
  content: this.props.labels[1] || "...",
35195
- linterContext: this.props.linterContext
35835
+ linterContext: this.props.linterContext,
35836
+ strings: this.context.strings
35196
35837
  }))), /*#__PURE__*/React__namespace.createElement("tr", {
35197
35838
  className: aphrodite.css(styles$b.row)
35198
35839
  }, /*#__PURE__*/React__namespace.createElement("td", {
@@ -35404,8 +36045,9 @@ const getMatrixSize = function (matrix) {
35404
36045
  return matrixSize;
35405
36046
  };
35406
36047
  class Matrix extends React__namespace.Component {
36048
+ static contextType = PerseusI18nContext;
35407
36049
  // @ts-expect-error - TS2564 - Property 'cursorPosition' has no initializer and is not definitely assigned in the constructor.
35408
-
36050
+ cursorPosition;
35409
36051
  static defaultProps = {
35410
36052
  matrixBoardSize: [3, 3],
35411
36053
  answers: [[]],
@@ -35415,6 +36057,57 @@ class Matrix extends React__namespace.Component {
35415
36057
  apiOptions: ApiOptions.defaults,
35416
36058
  linterContext: PerseusLinter.linterContextDefault
35417
36059
  };
36060
+ static validate(state, rubric, strings) {
36061
+ const solution = rubric.answers;
36062
+ const supplied = state.answers;
36063
+ const solutionSize = getMatrixSize(solution);
36064
+ const suppliedSize = getMatrixSize(supplied);
36065
+ const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
36066
+ const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
36067
+ let message = null;
36068
+ let hasEmptyCell = false;
36069
+ let incorrect = false;
36070
+ ___default["default"](suppliedSize[0]).times(row => {
36071
+ ___default["default"](suppliedSize[1]).times(col => {
36072
+ if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
36073
+ hasEmptyCell = true;
36074
+ }
36075
+ if (!incorrectSize) {
36076
+ const validator = createValidator(solution[row][col], {
36077
+ simplify: true
36078
+ }, strings);
36079
+ const result = validator(supplied[row][col]);
36080
+ if (result.message) {
36081
+ // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
36082
+ message = result.message;
36083
+ }
36084
+ if (!result.correct) {
36085
+ incorrect = true;
36086
+ }
36087
+ }
36088
+ });
36089
+ });
36090
+ if (hasEmptyCell) {
36091
+ return {
36092
+ type: "invalid",
36093
+ message: strings.fillAllCells
36094
+ };
36095
+ }
36096
+ if (incorrectSize) {
36097
+ return {
36098
+ type: "points",
36099
+ earned: 0,
36100
+ total: 1,
36101
+ message: null
36102
+ };
36103
+ }
36104
+ return {
36105
+ type: "points",
36106
+ earned: incorrect ? 0 : 1,
36107
+ total: 1,
36108
+ message: message
36109
+ };
36110
+ }
35418
36111
  state = {
35419
36112
  enterTheMatrix: 0
35420
36113
  };
@@ -35457,7 +36150,8 @@ class Matrix extends React__namespace.Component {
35457
36150
  className: "matrix-prefix"
35458
36151
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
35459
36152
  content: this.props.prefix,
35460
- linterContext: this.props.linterContext
36153
+ linterContext: this.props.linterContext,
36154
+ strings: this.context.strings
35461
36155
  })), /*#__PURE__*/React__namespace.createElement("div", {
35462
36156
  className: "matrix-input"
35463
36157
  }, /*#__PURE__*/React__namespace.createElement("div", {
@@ -35572,7 +36266,8 @@ class Matrix extends React__namespace.Component {
35572
36266
  className: "matrix-suffix"
35573
36267
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
35574
36268
  content: this.props.suffix,
35575
- linterContext: this.props.linterContext
36269
+ linterContext: this.props.linterContext,
36270
+ strings: this.context.strings
35576
36271
  })));
35577
36272
  }
35578
36273
  getInputPaths = () => {
@@ -35711,63 +36406,9 @@ class Matrix extends React__namespace.Component {
35711
36406
  };
35712
36407
  };
35713
36408
  simpleValidate = rubric => {
35714
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Matrix'.
35715
- return Matrix.validate(this.getUserInput(), rubric);
36409
+ return Matrix.validate(this.getUserInput(), rubric, this.context.strings);
35716
36410
  };
35717
36411
  }
35718
- ___default["default"].extend(Matrix, {
35719
- validate: function (state, rubric) {
35720
- const solution = rubric.answers;
35721
- const supplied = state.answers;
35722
- const solutionSize = getMatrixSize(solution);
35723
- const suppliedSize = getMatrixSize(supplied);
35724
- const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
35725
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
35726
- let message = null;
35727
- let hasEmptyCell = false;
35728
- let incorrect = false;
35729
- ___default["default"](suppliedSize[0]).times(row => {
35730
- ___default["default"](suppliedSize[1]).times(col => {
35731
- if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
35732
- hasEmptyCell = true;
35733
- }
35734
- if (!incorrectSize) {
35735
- const validator = createValidator(solution[row][col], {
35736
- simplify: true
35737
- });
35738
- const result = validator(supplied[row][col]);
35739
- if (result.message) {
35740
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
35741
- message = result.message;
35742
- }
35743
- if (!result.correct) {
35744
- incorrect = true;
35745
- }
35746
- }
35747
- });
35748
- });
35749
- if (hasEmptyCell) {
35750
- return {
35751
- type: "invalid",
35752
- message: i18n__namespace._("Make sure you fill in all cells in the matrix.")
35753
- };
35754
- }
35755
- if (incorrectSize) {
35756
- return {
35757
- type: "points",
35758
- earned: 0,
35759
- total: 1,
35760
- message: null
35761
- };
35762
- }
35763
- return {
35764
- type: "points",
35765
- earned: incorrect ? 0 : 1,
35766
- total: 1,
35767
- message: message
35768
- };
35769
- }
35770
- });
35771
36412
  const propTransform$1 = editorProps => {
35772
36413
  // Remove answers before passing to widget
35773
36414
  const blankAnswers = ___default["default"](editorProps.matrixBoardSize[0]).times(function () {
@@ -36731,6 +37372,7 @@ const parse$1 = SmilesParser.parse;
36731
37372
  const ParseError = SmilesParser.ParseError;
36732
37373
  const borderSize = 30;
36733
37374
  class Molecule extends React__namespace.Component {
37375
+ static contextType = PerseusI18nContext;
36734
37376
  state = {
36735
37377
  parsedSmiles: null,
36736
37378
  error: null
@@ -36807,8 +37449,8 @@ class Molecule extends React__namespace.Component {
36807
37449
  // eslint-disable-next-line react/no-string-refs
36808
37450
  ,
36809
37451
  ref: "canvas"
36810
- }, i18n__namespace.$_("A molecular structure drawing. SMILES notation: %(content)s", {
36811
- content: this.props.smiles
37452
+ }, this.context.strings.molecularDrawing({
37453
+ content: this.props.smiles || ""
36812
37454
  }));
36813
37455
  if (this.state.error) {
36814
37456
  content = /*#__PURE__*/React__namespace.createElement("div", {
@@ -36987,6 +37629,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
36987
37629
  return results;
36988
37630
  });
36989
37631
  class NumberLine extends React__namespace.Component {
37632
+ static contextType = PerseusI18nContext;
36990
37633
  static defaultProps = {
36991
37634
  range: [0, 10],
36992
37635
  labelStyle: "decimal",
@@ -37299,18 +37942,21 @@ class NumberLine extends React__namespace.Component {
37299
37942
  return NumberLine.validate(this.getUserInput(), rubric);
37300
37943
  };
37301
37944
  render() {
37945
+ const {
37946
+ strings
37947
+ } = this.context;
37302
37948
  const divisionRange = this.props.divisionRange;
37303
37949
  const divRangeString = divisionRange[0] + EN_DASH + divisionRange[1];
37304
37950
  const invalidNumDivisions = this.props.numDivisions < divisionRange[0] || this.props.numDivisions > divisionRange[1];
37305
37951
  const inequalityControls = /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("input", {
37306
37952
  type: "button",
37307
37953
  className: "simple-button",
37308
- value: i18n__namespace._("Switch direction"),
37954
+ value: strings.switchDirection,
37309
37955
  onClick: this.handleReverse
37310
37956
  }), /*#__PURE__*/React__namespace.createElement("input", {
37311
37957
  type: "button",
37312
37958
  className: "simple-button",
37313
- value: ___default["default"](["le", "ge"]).contains(this.props.rel) ? i18n__namespace._("Make circle open") : i18n__namespace._("Make circle filled"),
37959
+ value: ___default["default"](["le", "ge"]).contains(this.props.rel) ? strings.circleOpen : strings.circleFilled,
37314
37960
  onClick: this.handleToggleStrict
37315
37961
  }));
37316
37962
  let tickCtrl;
@@ -37321,7 +37967,7 @@ class NumberLine extends React__namespace.Component {
37321
37967
  } else {
37322
37968
  Input = NumberInput;
37323
37969
  }
37324
- tickCtrl = /*#__PURE__*/React__namespace.createElement("label", null, i18n__namespace._("Number of divisions:"), " ", /*#__PURE__*/React__namespace.createElement(Input
37970
+ tickCtrl = /*#__PURE__*/React__namespace.createElement("label", null, strings.numDivisions, " ", /*#__PURE__*/React__namespace.createElement(Input
37325
37971
  // eslint-disable-next-line react/no-string-refs
37326
37972
  , {
37327
37973
  ref: "tick-ctrl",
@@ -37338,9 +37984,9 @@ class NumberLine extends React__namespace.Component {
37338
37984
  className: "perseus-widget " + "perseus-widget-interactive-number-line"
37339
37985
  }, tickCtrl, !this.isValid() ? /*#__PURE__*/React__namespace.createElement("div", {
37340
37986
  className: "perseus-error"
37341
- }, i18n__namespace.doNotTranslate("Invalid number line configuration.")) : this.props.isTickCtrl && invalidNumDivisions ? /*#__PURE__*/React__namespace.createElement("div", {
37987
+ }, "Invalid number line configuration.") : this.props.isTickCtrl && invalidNumDivisions ? /*#__PURE__*/React__namespace.createElement("div", {
37342
37988
  className: "perseus-error"
37343
- }, i18n__namespace.$_("Please make sure the number of divisions is " + "in the range %(divRangeString)s.", {
37989
+ }, strings.divisions({
37344
37990
  divRangeString: divRangeString
37345
37991
  })) : this._renderGraphie(), !this.props.static && this.props.isInequality && inequalityControls);
37346
37992
  }
@@ -37472,8 +38118,9 @@ const PropTypePosition = PropTypes__default["default"].shape({
37472
38118
  top: PropTypes__default["default"].number
37473
38119
  });
37474
38120
  class Card extends React__namespace.Component {
38121
+ static contextType = PerseusI18nContext;
37475
38122
  // @ts-expect-error - TS2564 - Property 'mouseMoveUpBound' has no initializer and is not definitely assigned in the constructor.
37476
-
38123
+ mouseMoveUpBound;
37477
38124
  static propTypes = {
37478
38125
  floating: PropTypes__default["default"].bool.isRequired,
37479
38126
  animating: PropTypes__default["default"].bool,
@@ -37532,7 +38179,8 @@ class Card extends React__namespace.Component {
37532
38179
  }, /*#__PURE__*/React__namespace.createElement("div", {
37533
38180
  className: className.join(" ")
37534
38181
  }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, rendererProps, {
37535
- linterContext: this.props.linterContext
38182
+ linterContext: this.props.linterContext,
38183
+ strings: this.context.strings
37536
38184
  }))));
37537
38185
  }
37538
38186
  shouldComponentUpdate(nextProps, nextState) {
@@ -38805,6 +39453,7 @@ const styles$8 = aphrodite.StyleSheet.create({
38805
39453
 
38806
39454
  /* eslint-disable react/no-unsafe */
38807
39455
  class HighlightSetRenderer extends React__namespace.PureComponent {
39456
+ static contextType = PerseusI18nContext;
38808
39457
  state = {
38809
39458
  hoveredHighlightKey: null,
38810
39459
  hoveringTooltipFor: null
@@ -38894,7 +39543,7 @@ class HighlightSetRenderer extends React__namespace.PureComponent {
38894
39543
  }
38895
39544
  const hoveredHighlight = this.props.highlights[hoveredHighlightKey];
38896
39545
  return /*#__PURE__*/React__namespace.createElement(HighlightTooltip, {
38897
- label: i18n__namespace._("Remove highlight"),
39546
+ label: this.context.strings.removeHighlight,
38898
39547
  focusNode: hoveredHighlight.domRange.endContainer,
38899
39548
  focusOffset: hoveredHighlight.domRange.endOffset,
38900
39549
  offsetParent: this.props.offsetParent,
@@ -39065,6 +39714,7 @@ class SelectionTracker extends React__namespace.PureComponent {
39065
39714
  * `HighlightingUI`.
39066
39715
  */
39067
39716
  class HighlightingUI extends React__namespace.PureComponent {
39717
+ static contextType = PerseusI18nContext;
39068
39718
  _handleAddHighlight(highlightToAdd) {
39069
39719
  this.props.onAddHighlight(highlightToAdd);
39070
39720
 
@@ -39121,7 +39771,7 @@ class HighlightingUI extends React__namespace.PureComponent {
39121
39771
  onRemoveHighlight: this.props.onRemoveHighlight,
39122
39772
  zIndexes: this.props.zIndexes
39123
39773
  }), this._selectionIsValid(trackedSelection) && !userIsMouseSelecting && /*#__PURE__*/React__namespace.createElement(HighlightTooltip, {
39124
- label: i18n__namespace._("Add highlight"),
39774
+ label: this.context.strings.addHighlight,
39125
39775
  onClick: () => this._handleAddHighlight(trackedSelection.proposedHighlight),
39126
39776
  focusNode: trackedSelection.focusNode,
39127
39777
  focusOffset: trackedSelection.focusOffset,
@@ -39144,6 +39794,7 @@ class HighlightingUI extends React__namespace.PureComponent {
39144
39794
  * them amenable to binary search - see `findBoundaryWordIndex` in ranges.js.
39145
39795
  */
39146
39796
  class WordIndexer extends React__namespace.PureComponent {
39797
+ _container;
39147
39798
  /* eslint-enable react/sort-comp */
39148
39799
 
39149
39800
  componentDidMount() {
@@ -39263,7 +39914,8 @@ class WordIndexer extends React__namespace.PureComponent {
39263
39914
  */
39264
39915
  class HighlightableContent extends React__namespace.PureComponent {
39265
39916
  // References to the mounted container and content divs.
39266
-
39917
+ _container;
39918
+ _content;
39267
39919
  state = {
39268
39920
  wordRanges: []
39269
39921
  };
@@ -39424,7 +40076,7 @@ class RefStart extends React__namespace.Component {
39424
40076
  render() {
39425
40077
  return /*#__PURE__*/React__namespace.createElement("span", {
39426
40078
  style: REF_STYLE
39427
- }, i18n__namespace.doNotTranslate("_"));
40079
+ }, "_");
39428
40080
  }
39429
40081
 
39430
40082
  // @ts-expect-error - TS2322 - Type '() => React.ReactNode' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>>'.
@@ -39436,9 +40088,24 @@ class RefEnd extends React__namespace.Component {
39436
40088
  render() {
39437
40089
  return /*#__PURE__*/React__namespace.createElement("span", {
39438
40090
  style: REF_STYLE
39439
- }, i18n__namespace.doNotTranslate("_"));
40091
+ }, "_");
39440
40092
  }
39441
40093
  }
40094
+ const AltText = _ref => {
40095
+ let {
40096
+ id,
40097
+ number
40098
+ } = _ref;
40099
+ const {
40100
+ strings
40101
+ } = usePerseusI18n();
40102
+ return /*#__PURE__*/React__namespace.createElement("span", {
40103
+ key: "alt-text",
40104
+ className: "perseus-sr-only"
40105
+ }, strings[id]({
40106
+ number
40107
+ }));
40108
+ };
39442
40109
  const rules = {
39443
40110
  newline: SimpleMarkdown__default["default"].defaultRules.newline,
39444
40111
  paragraph: SimpleMarkdown__default["default"].defaultRules.paragraph,
@@ -39607,12 +40274,10 @@ const rules = {
39607
40274
  "aria-hidden": "true"
39608
40275
  }, /*#__PURE__*/React__namespace.createElement("span", {
39609
40276
  style: SQUARE_LABEL_STYLE
39610
- }, node.content)), /*#__PURE__*/React__namespace.createElement("span", {
39611
- key: "alt-text",
39612
- className: "perseus-sr-only"
39613
- }, i18n__namespace.$_("[Marker for question %(number)s]", {
40277
+ }, node.content)), /*#__PURE__*/React__namespace.createElement(AltText, {
40278
+ id: "questionMarker",
39614
40279
  number: node.content
39615
- })), node.space ? "\u00A0" : null];
40280
+ }), node.space ? "\u00A0" : null];
39616
40281
  }
39617
40282
  },
39618
40283
  circleLabel: {
@@ -39633,12 +40298,10 @@ const rules = {
39633
40298
  "aria-hidden": true
39634
40299
  }, /*#__PURE__*/React__namespace.createElement("span", {
39635
40300
  style: CIRCLE_LABEL_STYLE
39636
- }, node.content)), /*#__PURE__*/React__namespace.createElement("span", {
39637
- key: "alt-text",
39638
- className: "perseus-sr-only"
39639
- }, i18n__namespace.$_("[Circle marker %(number)s]", {
40301
+ }, node.content)), /*#__PURE__*/React__namespace.createElement(AltText, {
40302
+ id: "circleMarker",
39640
40303
  number: node.content
39641
- })), node.space ? "\u00A0" : null];
40304
+ }), node.space ? "\u00A0" : null];
39642
40305
  }
39643
40306
  },
39644
40307
  squareBracketRef: {
@@ -39658,12 +40321,10 @@ const rules = {
39658
40321
  key: "visual-brackets",
39659
40322
  className: "perseus-passage-bracket-label",
39660
40323
  "aria-hidden": "true"
39661
- }, "[", node.content, "]"), /*#__PURE__*/React__namespace.createElement("span", {
39662
- key: "alt-text",
39663
- className: "perseus-sr-only"
39664
- }, i18n__namespace.$_("[Sentence %(number)s]", {
40324
+ }, "[", node.content, "]"), /*#__PURE__*/React__namespace.createElement(AltText, {
40325
+ id: "sentenceMarker",
39665
40326
  number: node.content
39666
- })), node.space ? "\u00A0" : null];
40327
+ }), node.space ? "\u00A0" : null];
39667
40328
  }
39668
40329
  },
39669
40330
  highlight: {
@@ -39763,6 +40424,7 @@ var PassageMarkdown = {
39763
40424
  // A fake paragraph to measure the line height of the passage,
39764
40425
  // so we can adapt to browser zoom
39765
40426
  class LineHeightMeasurer extends React__namespace.Component {
40427
+ _line;
39766
40428
  measureLineHeight() {
39767
40429
  if (!this._line) {
39768
40430
  return 0;
@@ -39795,10 +40457,13 @@ const styles$6 = aphrodite.StyleSheet.create({
39795
40457
  // Information about a passage reference, used in inter-widgets.
39796
40458
 
39797
40459
  class Passage extends React__namespace.Component {
40460
+ static contextType = PerseusI18nContext;
40461
+ _contentRef;
40462
+ _lineHeightMeasurerRef;
39798
40463
  // @ts-expect-error - TS2564 - Property '_onResize' has no initializer and is not definitely assigned in the constructor.
39799
-
40464
+ _onResize;
39800
40465
  // @ts-expect-error - TS2564 - Property '_stylesAppiedTimer' has no initializer and is not definitely assigned in the constructor.
39801
-
40466
+ _stylesAppiedTimer;
39802
40467
  static defaultProps = {
39803
40468
  passageTitle: "",
39804
40469
  passageText: "",
@@ -40053,15 +40718,18 @@ class Passage extends React__namespace.Component {
40053
40718
  _renderInstructions(parseState) {
40054
40719
  const firstQuestionNumber = parseState.firstQuestionRef;
40055
40720
  const firstSentenceRef = parseState.firstSentenceRef;
40721
+ const {
40722
+ strings
40723
+ } = this.context;
40056
40724
  let instructions = "";
40057
40725
  if (firstQuestionNumber) {
40058
- instructions += i18n__namespace._("The symbol %(questionSymbol)s indicates that question " + "%(questionNumber)s references this portion of the " + "passage.", {
40726
+ instructions += strings.symbolPassage({
40059
40727
  questionSymbol: "[[" + firstQuestionNumber + "]]",
40060
40728
  questionNumber: firstQuestionNumber
40061
40729
  });
40062
40730
  }
40063
40731
  if (firstSentenceRef) {
40064
- instructions += i18n__namespace._(" The symbol %(sentenceSymbol)s indicates that the " + "following sentence is referenced in a question.", {
40732
+ instructions += strings.symbolQuestion({
40065
40733
  sentenceSymbol: "[" + firstSentenceRef + "]"
40066
40734
  });
40067
40735
  }
@@ -40107,6 +40775,9 @@ class Passage extends React__namespace.Component {
40107
40775
  return PassageMarkdown.output(parsed);
40108
40776
  }
40109
40777
  render() {
40778
+ const {
40779
+ strings
40780
+ } = this.context;
40110
40781
  let lineNumbers;
40111
40782
  const nLines = this.state.nLines;
40112
40783
  if (this.props.showLineNumbers && nLines) {
@@ -40115,9 +40786,7 @@ class Passage extends React__namespace.Component {
40115
40786
  lineNumbers = ___default["default"].range(1, nLines + 1).map(lineN => {
40116
40787
  const lineAt = lineN + this.state.startLineNumbersAfter;
40117
40788
  if (lineAt === 4) {
40118
- // we leave a space in i18n to disambiguate translations, see TP-5392
40119
- // I18N: a label next to a reading passage to denote the line number
40120
- const translatedLine = i18n__namespace._("Line ");
40789
+ const translatedLine = strings.lineLabel;
40121
40790
  return /*#__PURE__*/React__namespace.createElement("span", {
40122
40791
  key: "line-marker",
40123
40792
  className: "line-marker"
@@ -40147,13 +40816,14 @@ class Passage extends React__namespace.Component {
40147
40816
  className: "passage-title"
40148
40817
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
40149
40818
  content: this.props.passageTitle,
40150
- linterContext: this.props.linterContext
40819
+ linterContext: this.props.linterContext,
40820
+ strings: strings
40151
40821
  })), lineNumbers && /*#__PURE__*/React__namespace.createElement("div", {
40152
40822
  className: "line-numbers",
40153
40823
  "aria-hidden": true
40154
40824
  }, lineNumbers), !hasTitle && /*#__PURE__*/React__namespace.createElement("h3", {
40155
40825
  className: "perseus-sr-only"
40156
- }, i18n__namespace._("Beginning of reading passage.")), /*#__PURE__*/React__namespace.createElement("div", {
40826
+ }, strings.beginningPassage), /*#__PURE__*/React__namespace.createElement("div", {
40157
40827
  className: "passage-text"
40158
40828
  }, this.shouldRenderJipt() ?
40159
40829
  /*#__PURE__*/
@@ -40161,16 +40831,17 @@ class Passage extends React__namespace.Component {
40161
40831
  // content to a <Renderer /> which knows how
40162
40832
  // to handle rendering JIPT text.
40163
40833
  React__namespace.createElement(Renderer, {
40164
- content: this.props.passageText
40834
+ content: this.props.passageText,
40835
+ strings: strings
40165
40836
  }) : this._renderContent(parsedContent)), this._hasFootnotes() && [/*#__PURE__*/React__namespace.createElement("h4", {
40166
40837
  key: "footnote-start",
40167
40838
  className: "perseus-sr-only"
40168
- }, i18n__namespace._("Beginning of reading passage footnotes.")), /*#__PURE__*/React__namespace.createElement("div", {
40839
+ }, strings.beginningFootnotes), /*#__PURE__*/React__namespace.createElement("div", {
40169
40840
  key: "footnotes",
40170
40841
  className: "footnotes"
40171
40842
  }, this._renderFootnotes())], /*#__PURE__*/React__namespace.createElement("div", {
40172
40843
  className: "perseus-sr-only"
40173
- }, i18n__namespace._("End of reading passage.")))));
40844
+ }, strings.endPassage))));
40174
40845
  }
40175
40846
  }
40176
40847
  var Passage$1 = {
@@ -40185,6 +40856,7 @@ var Passage$1 = {
40185
40856
 
40186
40857
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
40187
40858
  class PassageRefTarget extends React__namespace.Component {
40859
+ static contextType = PerseusI18nContext;
40188
40860
  static defaultProps = {
40189
40861
  content: "",
40190
40862
  linterContext: PerseusLinter.linterContextDefault
@@ -40222,7 +40894,8 @@ class PassageRefTarget extends React__namespace.Component {
40222
40894
  // inline={true}
40223
40895
  ,
40224
40896
  apiOptions: this.props.apiOptions,
40225
- linterContext: this.props.linterContext
40897
+ linterContext: this.props.linterContext,
40898
+ strings: this.context.strings
40226
40899
  });
40227
40900
  }
40228
40901
  }
@@ -40247,9 +40920,13 @@ const {
40247
40920
  deepEq
40248
40921
  } = Util;
40249
40922
  class Plotter extends React__namespace.Component {
40923
+ static contextType = PerseusI18nContext;
40250
40924
  // @ts-expect-error - TS2564 - Property 'shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
40251
-
40925
+ shouldSetupGraphie;
40252
40926
  _isMounted = false;
40927
+ horizHairline;
40928
+ hairlineRange;
40929
+ graphie;
40253
40930
  static defaultProps = {
40254
40931
  type: "bar",
40255
40932
  labels: ["", ""],
@@ -40436,11 +41113,11 @@ class Plotter extends React__namespace.Component {
40436
41113
  }
40437
41114
  }
40438
41115
  if ((isBar || isLine) && isMobile && !this.props.static) {
40439
- self.graphie.dragPrompt = graphie.label([c.dimX / 2, c.dimY / 2], i18n__namespace._("Drag handles to make graph"), "center", false).css("font-weight", "bold").css("color", KhanColors.KA_GREEN).css("display", "none");
41116
+ self.graphie.dragPrompt = graphie.label([c.dimX / 2, c.dimY / 2], this.context.strings.dragHandles, "center", false).css("font-weight", "bold").css("color", KhanColors.KA_GREEN).css("display", "none");
40440
41117
  }
40441
41118
  self.setupCategories(config);
40442
41119
  if (isTiledPlot && isMobile) {
40443
- self.graphie.dotPrompt = graphie.label([c.dimX / 2, c.dimY / 2], i18n__namespace._("Tap to add points"), "center", false).css("font-weight", "bold").css("color", KhanColors.KA_GREEN).css("display", "none");
41120
+ self.graphie.dotPrompt = graphie.label([c.dimX / 2, c.dimY / 2], this.context.strings.tapAddPoints, "center", false).css("font-weight", "bold").css("color", KhanColors.KA_GREEN).css("display", "none");
40444
41121
  }
40445
41122
  if (isTiledPlot) {
40446
41123
  self.drawPicHeights(self.state.values, prevState.values);
@@ -41115,7 +41792,7 @@ const HORIZONTAL = "horizontal";
41115
41792
  const VERTICAL = "vertical";
41116
41793
  class Sorter extends React__namespace.Component {
41117
41794
  // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
41118
-
41795
+ _isMounted;
41119
41796
  static propTypes = {
41120
41797
  apiOptions: ApiOptions.propTypes,
41121
41798
  // eslint-disable-next-line react/forbid-prop-types
@@ -41258,6 +41935,7 @@ const getRefForPath = function (path) {
41258
41935
  return "answer" + row + "," + column;
41259
41936
  };
41260
41937
  class Table extends React__namespace.Component {
41938
+ static contextType = PerseusI18nContext;
41261
41939
  static propTypes = {
41262
41940
  answers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].arrayOf(PropTypes__default["default"].string)),
41263
41941
  editableHeaders: PropTypes__default["default"].bool,
@@ -41327,7 +42005,8 @@ class Table extends React__namespace.Component {
41327
42005
  key: i
41328
42006
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
41329
42007
  content: header,
41330
- linterContext: this.props.linterContext
42008
+ linterContext: this.props.linterContext,
42009
+ strings: this.context.strings
41331
42010
  }));
41332
42011
  }))), /*#__PURE__*/React__namespace.createElement("tbody", null, ___default["default"](rows).times(r => {
41333
42012
  return /*#__PURE__*/React__namespace.createElement("tr", {
@@ -41372,7 +42051,7 @@ class Table extends React__namespace.Component {
41372
42051
  };
41373
42052
  simpleValidate = rubric => {
41374
42053
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Table'.
41375
- return Table.validate(this.getUserInput(), rubric);
42054
+ return Table.validate(this.getUserInput(), rubric, this.context.strings);
41376
42055
  };
41377
42056
  _handleFocus = inputPath => {
41378
42057
  this.props.onFocus(inputPath);
@@ -41441,7 +42120,7 @@ class Table extends React__namespace.Component {
41441
42120
  };
41442
42121
  }
41443
42122
  ___default["default"].extend(Table, {
41444
- validate: function (state, rubric) {
42123
+ validate: function (state, rubric, strings) {
41445
42124
  const filterNonEmpty = function (table) {
41446
42125
  return ___default["default"].filter(table, function (row) {
41447
42126
  // Check if row has a cell that is nonempty
@@ -41479,7 +42158,7 @@ ___default["default"].extend(Table, {
41479
42158
  const cellSolution = rowSolution[i];
41480
42159
  const validator = createValidator(cellSolution, {
41481
42160
  simplify: true
41482
- });
42161
+ }, strings);
41483
42162
  const result = validator(cellSupplied);
41484
42163
  if (result.message) {
41485
42164
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
@@ -41766,6 +42445,11 @@ const str = {
41766
42445
  * @param s A string representation of a floating point.
41767
42446
  */
41768
42447
  class SignificantFigures {
42448
+ order;
42449
+ mantissa;
42450
+ positive;
42451
+ sigFigs;
42452
+ sigDecs;
41769
42453
  constructor(s) {
41770
42454
  this.order = parseOrder(s);
41771
42455
  this.mantissa = parseMantissa(s);
@@ -42174,17 +42858,19 @@ const countSigfigs = function (value) {
42174
42858
  * rolling two second delay, but hidden immediately on further typing.
42175
42859
  */
42176
42860
  class OldUnitInput extends React__namespace.Component {
42861
+ static contextType = PerseusI18nContext;
42862
+ _errorTimeout;
42177
42863
  static defaultProps = {
42178
42864
  apiOptions: ApiOptions.defaults,
42179
42865
  value: ""
42180
42866
  };
42181
- static validate(userInput, rubric) {
42867
+ static validate(userInput, rubric, strings) {
42182
42868
  const answer = KAS__namespace.unitParse(rubric.value).expr;
42183
42869
  const guess = KAS__namespace.unitParse(userInput);
42184
42870
  if (!guess.parsed) {
42185
42871
  return {
42186
42872
  type: "invalid",
42187
- message: i18n__namespace._("I couldn't understand those units.")
42873
+ message: strings.dontUnderstandUnits
42188
42874
  };
42189
42875
  }
42190
42876
 
@@ -42199,8 +42885,7 @@ class OldUnitInput extends React__namespace.Component {
42199
42885
  const sigfigs = rubric.sigfigs;
42200
42886
  const sigfigsCorrect = countSigfigs(guess.coefficient) === sigfigs;
42201
42887
  if (!sigfigsCorrect) {
42202
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42203
- message = i18n__namespace._("Check your significant figures.");
42888
+ message = strings.checkSigFigs;
42204
42889
  }
42205
42890
 
42206
42891
  // now we need to check that the answer is correct to the precision we
@@ -42219,8 +42904,7 @@ class OldUnitInput extends React__namespace.Component {
42219
42904
  numericallyCorrect = false;
42220
42905
  }
42221
42906
  if (!numericallyCorrect) {
42222
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42223
- message = i18n__namespace._("That answer is numerically incorrect.");
42907
+ message = strings.answerNumericallyIncorrect;
42224
42908
  }
42225
42909
  let kasCorrect;
42226
42910
  const guessUnit = primUnits(guess.expr.simplify());
@@ -42240,8 +42924,7 @@ class OldUnitInput extends React__namespace.Component {
42240
42924
  });
42241
42925
  }
42242
42926
  if (!kasCorrect) {
42243
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42244
- message = i18n__namespace._("Check your units.");
42927
+ message = strings.checkUnits;
42245
42928
  }
42246
42929
  const correct = kasCorrect && numericallyCorrect && sigfigsCorrect;
42247
42930
  return {
@@ -42337,7 +43020,7 @@ class OldUnitInput extends React__namespace.Component {
42337
43020
  });
42338
43021
  };
42339
43022
  simpleValidate = (rubric, onInputError) => {
42340
- return OldUnitInput.validate(this.getUserInput(), rubric);
43023
+ return OldUnitInput.validate(this.getUserInput(), rubric, this.context.strings);
42341
43024
  };
42342
43025
  getUserInput = () => {
42343
43026
  return this.props.value;
@@ -42412,7 +43095,7 @@ class OldUnitInput extends React__namespace.Component {
42412
43095
  style: {
42413
43096
  display: "none"
42414
43097
  }
42415
- }, i18n__namespace._("I don't understand that")));
43098
+ }, this.context.strings.dontUnderstand));
42416
43099
  }
42417
43100
  }
42418
43101
 
@@ -42464,9 +43147,12 @@ const VideoTranscriptLink = props => {
42464
43147
  // which is used by our GraphQL framework.
42465
43148
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'VideoKind'.
42466
43149
  const result = useVideo(id, kind);
43150
+ const {
43151
+ strings
43152
+ } = usePerseusI18n();
42467
43153
  switch (result.status) {
42468
43154
  case "loading":
42469
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, i18n__namespace._("Loading..."));
43155
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.loading);
42470
43156
  case "success":
42471
43157
  {
42472
43158
  const video = result.data?.video;
@@ -42478,14 +43164,14 @@ const VideoTranscriptLink = props => {
42478
43164
  href: "/transcript/" + (video?.contentId || "videoNotFound"),
42479
43165
  target: "_blank",
42480
43166
  className: "visited-no-recolor"
42481
- }, i18n__namespace._("See video transcript")));
43167
+ }, strings.videoTranscript));
42482
43168
  }
42483
43169
  case "error":
42484
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, i18n__namespace._("Something went wrong."));
43170
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
42485
43171
  case "aborted":
42486
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, i18n__namespace._("Something went wrong."));
43172
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
42487
43173
  default:
42488
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, i18n__namespace._("Something went wrong."));
43174
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
42489
43175
  }
42490
43176
  };
42491
43177
  const styles$4 = aphrodite.StyleSheet.create({
@@ -42513,6 +43199,7 @@ const IS_VIMEO = /(vimeo\.com)/;
42513
43199
  * Video renderer.
42514
43200
  */
42515
43201
  class Video extends React__namespace.Component {
43202
+ static contextType = PerseusI18nContext;
42516
43203
  /**
42517
43204
  * This is the widget's grading function.
42518
43205
  * Points for videos are tallied by the embedded video itself, in the case
@@ -42580,7 +43267,7 @@ class Video extends React__namespace.Component {
42580
43267
  key: location + this.props.alignment
42581
43268
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
42582
43269
  style: a11y.srOnly
42583
- }, i18n__namespace._("Khan Academy video wrapper")), /*#__PURE__*/React__namespace.createElement("iframe", {
43270
+ }, this.context.strings.videoWrapper), /*#__PURE__*/React__namespace.createElement("iframe", {
42584
43271
  // TODO(joshuan): Consider not using iframes when we're
42585
43272
  // loading this from webapp. This iframe is problematic
42586
43273
  // for screenreaders.
@@ -42644,7 +43331,7 @@ const init = function (options) {
42644
43331
 
42645
43332
  // This file is processed by a Rollup plugin (replace) to inject the production
42646
43333
  const libName = "@khanacademy/perseus";
42647
- const libVersion = "21.5.0";
43334
+ const libVersion = "22.0.0";
42648
43335
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
42649
43336
 
42650
43337
  const apiVersion = {
@@ -42663,6 +43350,8 @@ const ItemVersion = getVersionVector();
42663
43350
  ItemVersion["::renderer::"] = itemDataVersion;
42664
43351
 
42665
43352
  class ArticleRenderer extends React__namespace.Component {
43353
+ static contextType = PerseusI18nContext;
43354
+ _currentFocus;
42666
43355
  sectionRenderers = [];
42667
43356
  static defaultProps = {
42668
43357
  apiOptions: ApiOptions.defaults,
@@ -42818,8 +43507,9 @@ class ArticleRenderer extends React__namespace.Component {
42818
43507
  }
42819
43508
  },
42820
43509
  linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "article"),
42821
- legacyPerseusLint: this.props.legacyPerseusLint
42822
- }, this.props.dependencies)));
43510
+ legacyPerseusLint: this.props.legacyPerseusLint,
43511
+ strings: this.context.strings
43512
+ })));
42823
43513
  });
42824
43514
  return /*#__PURE__*/React__namespace.createElement("div", {
42825
43515
  className: classes
@@ -42831,6 +43521,7 @@ class ArticleRenderer extends React__namespace.Component {
42831
43521
 
42832
43522
  /* Renders just a hint preview */
42833
43523
  class HintRenderer extends React__namespace.Component {
43524
+ static contextType = PerseusI18nContext;
42834
43525
  static defaultProps = {
42835
43526
  linterContext: PerseusLinter__namespace.linterContextDefault
42836
43527
  };
@@ -42876,7 +43567,7 @@ class HintRenderer extends React__namespace.Component {
42876
43567
  tabIndex: "-1"
42877
43568
  }, !apiOptions.isMobile && /*#__PURE__*/React__namespace.createElement("span", {
42878
43569
  className: "perseus-sr-only"
42879
- }, i18n__namespace._("Hint #%(pos)s", {
43570
+ }, this.context.strings.hintPos({
42880
43571
  pos: pos + 1
42881
43572
  })), !apiOptions.isMobile && totalHints != null && pos != null && /*#__PURE__*/React__namespace.createElement("span", {
42882
43573
  className: "perseus-hint-label",
@@ -42893,7 +43584,8 @@ class HintRenderer extends React__namespace.Component {
42893
43584
  images: hint.images,
42894
43585
  apiOptions: rendererApiOptions,
42895
43586
  findExternalWidgets: this.props.findExternalWidgets,
42896
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hint")
43587
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hint"),
43588
+ strings: this.context.strings
42897
43589
  }))
42898
43590
  );
42899
43591
  }
@@ -42928,6 +43620,7 @@ const styles$3 = aphrodite.StyleSheet.create({
42928
43620
  // Use of UNSAFE_componentWillReceiveProps:
42929
43621
  // eslint-disable-next-line react/no-unsafe
42930
43622
  class HintsRenderer extends React__namespace.Component {
43623
+ static contextType = PerseusI18nContext;
42931
43624
  static defaultProps = {
42932
43625
  linterContext: PerseusLinter__namespace.linterContextDefault
42933
43626
  };
@@ -43080,7 +43773,7 @@ class HintsRenderer extends React__namespace.Component {
43080
43773
  className: classNames
43081
43774
  }, apiOptions.isMobile && hintsVisible > 0 && /*#__PURE__*/React__namespace.createElement("div", {
43082
43775
  className: aphrodite.css(styles$2.mobileHintStylesHintTitle, sharedStyles.responsiveLabel)
43083
- }, i18n__namespace._("Hints")), hints, showGetAnotherHint && /*#__PURE__*/React__namespace.createElement("button", {
43776
+ }, this.context.strings.hints), hints, showGetAnotherHint && /*#__PURE__*/React__namespace.createElement("button", {
43084
43777
  // @ts-expect-error - TS2322 - Type '{ children: Element[]; rel: string; className: string; onClick: (evt: MouseEvent<HTMLButtonElement, MouseEvent>) => void; }' is not assignable to type 'DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>'.
43085
43778
  rel: "button",
43086
43779
  className: aphrodite.css(styles$2.linkButton, styles$2.getAnotherHintButton, apiOptions.isMobile && styles$2.mobileHintStylesGetAnotherHintButton),
@@ -43095,7 +43788,7 @@ class HintsRenderer extends React__namespace.Component {
43095
43788
  className: aphrodite.css(styles$2.plusText, apiOptions.isMobile && styles$2.mobileHintStylesPlusText)
43096
43789
  }, "+"), /*#__PURE__*/React__namespace.createElement("span", {
43097
43790
  className: aphrodite.css(styles$2.getAnotherHintText)
43098
- }, i18n__namespace._("Get another hint"), " ", hintRatioCopy)));
43791
+ }, this.context.strings.getAnotherHint, " ", hintRatioCopy)));
43099
43792
  }
43100
43793
  }
43101
43794
  const hintIndentation = baseUnitPx + hintBorderWidth;
@@ -43198,8 +43891,13 @@ const {
43198
43891
  } = objective_;
43199
43892
  /* eslint-disable-next-line react/no-unsafe */
43200
43893
  class ServerItemRenderer extends React__namespace.Component {
43894
+ static contextType = PerseusI18nContext;
43201
43895
  // @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
43202
-
43896
+ questionRenderer;
43897
+ hintsRenderer;
43898
+ _currentFocus;
43899
+ _fullyRendered;
43900
+ blurTimeoutID;
43203
43901
  static defaultProps = {
43204
43902
  apiOptions: {},
43205
43903
  // a deep default is done in `this.update()`
@@ -43491,14 +44189,16 @@ class ServerItemRenderer extends React__namespace.Component {
43491
44189
  content: this.props.item.question.content,
43492
44190
  widgets: this.props.item.question.widgets,
43493
44191
  images: this.props.item.question.images,
43494
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "question")
44192
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "question"),
44193
+ strings: this.context.strings
43495
44194
  }, this.props.dependencies)));
43496
44195
  const hintsRenderer = /*#__PURE__*/React__namespace.createElement(HintsRenderer, {
43497
44196
  hints: this.props.item.hints,
43498
44197
  hintsVisible: this.props.hintsVisible,
43499
44198
  apiOptions: apiOptions,
43500
44199
  ref: elem => this.hintsRenderer = elem,
43501
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hints")
44200
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hints"),
44201
+ strings: this.context.strings
43502
44202
  });
43503
44203
  return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
43504
44204
  value: this.props.dependencies
@@ -43671,114 +44371,6 @@ var constants = /*#__PURE__*/Object.freeze({
43671
44371
  perseusFrameBorderWidth: perseusFrameBorderWidth
43672
44372
  });
43673
44373
 
43674
- // TODO(FEI-4010): Remove `Perseus` prefix for all types here
43675
- // TODO(FEI-4011): Use types generated by https://github.com/jaredly/generate-perseus-flowtypes
43676
-
43677
- // Range is replaced within this file with Interval, but it is used elsewhere
43678
- // and exported from the package, so we need to keep it around.
43679
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
43680
- const ItemExtras = [
43681
- // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
43682
- "calculator",
43683
- // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
43684
- "chi2Table",
43685
- // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
43686
- "financialCalculatorMonthlyPayment",
43687
- // The user might benefit from a total amount calculator. Provided on Khan Academy when true
43688
- "financialCalculatorTotalAmount",
43689
- // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
43690
- "financialCalculatorTimeToPayOff",
43691
- // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
43692
- "periodicTable",
43693
- // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
43694
- "periodicTableWithKey",
43695
- // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
43696
- "tTable",
43697
- // The user might benefit from using a statistics Z Table like https://www.ztable.net/
43698
- "zTable"];
43699
-
43700
- // prettier-ignore
43701
-
43702
- // prettier-ignore
43703
-
43704
- // prettier-ignore
43705
-
43706
- // prettier-ignore
43707
-
43708
- // prettier-ignore
43709
-
43710
- // prettier-ignore
43711
-
43712
- // prettier-ignore
43713
-
43714
- // prettier-ignore
43715
-
43716
- // prettier-ignore
43717
-
43718
- // prettier-ignore
43719
-
43720
- // prettier-ignore
43721
-
43722
- // prettier-ignore
43723
-
43724
- // prettier-ignore
43725
-
43726
- // prettier-ignore
43727
-
43728
- // prettier-ignore
43729
-
43730
- // prettier-ignore
43731
-
43732
- // prettier-ignore
43733
-
43734
- // prettier-ignore
43735
-
43736
- // prettier-ignore
43737
-
43738
- // prettier-ignore
43739
-
43740
- // prettier-ignore
43741
-
43742
- // prettier-ignore
43743
-
43744
- // prettier-ignore
43745
-
43746
- // prettier-ignore
43747
-
43748
- // prettier-ignore
43749
-
43750
- // prettier-ignore
43751
-
43752
- // prettier-ignore
43753
-
43754
- // prettier-ignore
43755
-
43756
- // prettier-ignore
43757
-
43758
- // prettier-ignore
43759
-
43760
- // prettier-ignore
43761
-
43762
- // prettier-ignore
43763
-
43764
- // prettier-ignore
43765
-
43766
- // prettier-ignore
43767
-
43768
- // prettier-ignore
43769
-
43770
- // prettier-ignore
43771
-
43772
- //prettier-ignore
43773
-
43774
- // A background image applied to various widgets.
43775
-
43776
- const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
43777
-
43778
- // 2D range: xMin, xMax, yMin, yMax
43779
-
43780
- const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
43781
-
43782
44374
  /**
43783
44375
  * Traverses a {content, widgets, images} renderer props object,
43784
44376
  * such as `itemData.question`
@@ -44019,6 +44611,10 @@ var shapes = {
44019
44611
  * TreeMapperForLeavesAndCollections.
44020
44612
  */
44021
44613
  class TreeMapperJustForLeaves {
44614
+ content;
44615
+ hint;
44616
+ tags;
44617
+ array;
44022
44618
  constructor(content, hint, tags) {
44023
44619
  this.content = content;
44024
44620
  this.hint = hint;
@@ -44049,6 +44645,10 @@ class TreeMapperJustForLeaves {
44049
44645
  * ContentMapper and HintMapper are now locked in.
44050
44646
  */
44051
44647
  class TreeMapperForLeavesAndCollections {
44648
+ content;
44649
+ hint;
44650
+ tags;
44651
+ array;
44052
44652
  constructor(content, hint, tags, array) {
44053
44653
  this.content = content;
44054
44654
  this.hint = hint;
@@ -44307,6 +44907,9 @@ function treeToItem(node) {
44307
44907
  // @ts-expect-error - TS2315 - Type 'Tree' is not generic.
44308
44908
  // @ts-expect-error - TS2315 - Type 'Tree' is not generic.
44309
44909
  class MultiRenderer extends React__namespace.Component {
44910
+ static contextType = PerseusI18nContext;
44911
+ rendererDataTreeMapper;
44912
+ getRenderersMapper;
44310
44913
  constructor(props) {
44311
44914
  super(props);
44312
44915
  this.rendererDataTreeMapper = buildMapper()
@@ -44381,7 +44984,10 @@ class MultiRenderer extends React__namespace.Component {
44381
44984
  onSerializedStateUpdated: _____,
44382
44985
  ...otherProps
44383
44986
  } = this.props;
44384
- return otherProps;
44987
+ return {
44988
+ ...otherProps,
44989
+ strings: this.context.strings
44990
+ };
44385
44991
  }
44386
44992
 
44387
44993
  /**
@@ -44630,7 +45236,7 @@ class MultiRenderer extends React__namespace.Component {
44630
45236
  if (this.state.renderError) {
44631
45237
  return /*#__PURE__*/React__namespace.createElement("div", {
44632
45238
  className: aphrodite.css(styles.error)
44633
- }, i18n__namespace.$_("Error rendering: %(error)s", {
45239
+ }, this.context.strings.errorRendering({
44634
45240
  error: String(this.state.renderError)
44635
45241
  }));
44636
45242
  }
@@ -45772,6 +46378,8 @@ exports.MultiItems = MultiItems;
45772
46378
  exports.NumericInput = NumericInput$1;
45773
46379
  exports.PerseusError = PerseusError;
45774
46380
  exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
46381
+ exports.PerseusI18nContext = PerseusI18nContext;
46382
+ exports.PerseusI18nContextProvider = PerseusI18nContextProvider;
45775
46383
  exports.PerseusMarkdown = PerseusMarkdown;
45776
46384
  exports.PlotterWidget = Plotter$1;
45777
46385
  exports.QUESTION_WIDGETS = QUESTION_WIDGETS;
@@ -45823,6 +46431,7 @@ exports.itemDataVersion = itemDataVersion;
45823
46431
  exports.itemToTree = itemToTree;
45824
46432
  exports.itemVersion = ItemVersion;
45825
46433
  exports.libVersion = libVersion;
46434
+ exports.lockedFigureColors = lockedFigureColors;
45826
46435
  exports.mediaQueries = mediaQueries;
45827
46436
  exports.parsePerseusItem = parsePerseusItem;
45828
46437
  exports.plotterPlotTypes = plotterPlotTypes;
@@ -45831,6 +46440,7 @@ exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
45831
46440
  exports.shapes = shapes;
45832
46441
  exports.shouldHaveIndividualAnswer = shouldHaveIndividualAnswer;
45833
46442
  exports.traverse = traverse;
46443
+ exports.usePerseusI18n = usePerseusI18n;
45834
46444
  exports.violatingWidgets = violatingWidgets;
45835
46445
  exports.widgets = allWidgets;
45836
46446
  //# sourceMappingURL=index.js.map