@khanacademy/perseus 21.6.0 → 22.0.1

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 (62) 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.js +883 -496
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/es/strings.js +312 -0
  11. package/dist/es/strings.js.map +1 -0
  12. package/dist/hint-renderer.d.ts +6 -0
  13. package/dist/hints-renderer.d.ts +6 -0
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +1099 -534
  16. package/dist/index.js.map +1 -1
  17. package/dist/multi-items/multi-renderer.d.ts +6 -0
  18. package/dist/perseus-types.d.ts +3 -2
  19. package/dist/renderer.d.ts +2 -0
  20. package/dist/server-item-renderer.d.ts +6 -0
  21. package/dist/strings.d.ts +190 -0
  22. package/dist/strings.js +395 -119
  23. package/dist/strings.js.map +1 -0
  24. package/dist/types.d.ts +2 -1
  25. package/dist/util/answer-types.d.ts +4 -12
  26. package/dist/util.d.ts +2 -1
  27. package/dist/widgets/categorizer.d.ts +8 -1
  28. package/dist/widgets/definition.d.ts +6 -0
  29. package/dist/widgets/deprecated-standin/index.d.ts +6 -0
  30. package/dist/widgets/explanation.d.ts +6 -0
  31. package/dist/widgets/expression.d.ts +12 -1
  32. package/dist/widgets/graded-group-answer-bar.d.ts +6 -0
  33. package/dist/widgets/graded-group.d.ts +6 -0
  34. package/dist/widgets/group.d.ts +6 -0
  35. package/dist/widgets/image.d.ts +6 -0
  36. package/dist/widgets/input-number.d.ts +8 -1
  37. package/dist/widgets/interactive-graph.d.ts +6 -0
  38. package/dist/widgets/interactive-graphs/graphs/components/arrowhead.d.ts +1 -0
  39. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +21 -0
  40. package/dist/widgets/interactive-graphs/locked-point.d.ts +1 -1
  41. package/dist/widgets/interactive-graphs/mafs-graph.d.ts +1 -0
  42. package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +1 -0
  43. package/dist/widgets/label-image/marker.d.ts +6 -0
  44. package/dist/widgets/label-image.d.ts +6 -0
  45. package/dist/widgets/matcher.d.ts +6 -0
  46. package/dist/widgets/matrix.d.ts +9 -1
  47. package/dist/widgets/molecule.d.ts +6 -0
  48. package/dist/widgets/number-line.d.ts +6 -0
  49. package/dist/widgets/numeric-input.d.ts +8 -1
  50. package/dist/widgets/passage-ref-target.d.ts +6 -0
  51. package/dist/widgets/passage-ref.d.ts +6 -0
  52. package/dist/widgets/passage.d.ts +6 -0
  53. package/dist/widgets/plotter.d.ts +6 -0
  54. package/dist/widgets/radio/choice-a11y-text.d.ts +2 -1
  55. package/dist/widgets/radio/radio.d.ts +8 -1
  56. package/dist/widgets/radio/util.d.ts +2 -1
  57. package/dist/widgets/table.d.ts +7 -0
  58. package/dist/widgets/unit.d.ts +8 -1
  59. package/dist/widgets/video.d.ts +6 -0
  60. package/dist/widgets.d.ts +2 -1
  61. package/package.json +18 -6
  62. 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
  };
@@ -2266,7 +2594,7 @@ class Expression extends React__namespace.Component {
2266
2594
  blurInputPath = inputPath => {
2267
2595
  // eslint-disable-next-line react/no-string-refs
2268
2596
  // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
2269
- this.refs.input.blur();
2597
+ this.refs.input?.blur();
2270
2598
  };
2271
2599
 
2272
2600
  // HACK(joel)
@@ -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 = () => {
@@ -21564,7 +21945,7 @@ class SimpleKeypadInput extends React__namespace.Component {
21564
21945
 
21565
21946
  blur() {
21566
21947
  // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
21567
- this.refs.input.blur(); // eslint-disable-line react/no-string-refs
21948
+ this.refs.input?.blur(); // eslint-disable-line react/no-string-refs
21568
21949
  }
21569
21950
 
21570
21951
  getValue() {
@@ -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",
@@ -21770,7 +22152,7 @@ class InputNumber extends React__namespace.Component {
21770
22152
  blurInputPath = inputPath => {
21771
22153
  // eslint-disable-next-line react/no-string-refs
21772
22154
  // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
21773
- this.refs.input.blur();
22155
+ this.refs.input?.blur();
21774
22156
  };
21775
22157
  getInputPaths = () => {
21776
22158
  // The widget itself is an input, so we return a single empty list to
@@ -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
  }
@@ -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.
@@ -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
  }
@@ -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",
@@ -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,6 +28746,123 @@ class Graph extends React__namespace.Component {
28322
28746
  }
28323
28747
  }
28324
28748
 
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
+
28325
28866
  const LockedLine = props => {
28326
28867
  const {
28327
28868
  color,
@@ -28338,7 +28879,7 @@ const LockedLine = props => {
28338
28879
  line = /*#__PURE__*/React__namespace.createElement(LineType, {
28339
28880
  point1: point1.coord,
28340
28881
  point2: point2.coord,
28341
- color: wonderBlocksTokens.color[color],
28882
+ color: lockedFigureColors[color],
28342
28883
  style: lineStyle
28343
28884
  });
28344
28885
  } else {
@@ -28352,8 +28893,8 @@ const LockedLine = props => {
28352
28893
  y: point1.coord[1],
28353
28894
  svgCircleProps: {
28354
28895
  style: {
28355
- fill: point1.filled ? wonderBlocksTokens.color[point1.color] : wonderBlocksTokens.color.white,
28356
- stroke: wonderBlocksTokens.color[point1.color],
28896
+ fill: point1.filled ? lockedFigureColors[point1.color] : wonderBlocksTokens.color.white,
28897
+ stroke: lockedFigureColors[point1.color],
28357
28898
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
28358
28899
  }
28359
28900
  }
@@ -28362,8 +28903,8 @@ const LockedLine = props => {
28362
28903
  y: point2.coord[1],
28363
28904
  svgCircleProps: {
28364
28905
  style: {
28365
- fill: point2.filled ? wonderBlocksTokens.color[point2.color] : wonderBlocksTokens.color.white,
28366
- stroke: wonderBlocksTokens.color[point2.color],
28906
+ fill: point2.filled ? lockedFigureColors[point2.color] : wonderBlocksTokens.color.white,
28907
+ stroke: lockedFigureColors[point2.color],
28367
28908
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
28368
28909
  }
28369
28910
  }
@@ -28382,8 +28923,8 @@ const LockedPoint = props => {
28382
28923
  y: y,
28383
28924
  svgCircleProps: {
28384
28925
  style: {
28385
- fill: filled ? wonderBlocksTokens.color[color] : wonderBlocksTokens.color.white,
28386
- stroke: wonderBlocksTokens.color[color],
28926
+ fill: filled ? lockedFigureColors[color] : wonderBlocksTokens.color.white,
28927
+ stroke: lockedFigureColors[color],
28387
28928
  strokeWidth: wonderBlocksTokens.spacing.xxxxSmall_2
28388
28929
  }
28389
28930
  }
@@ -28629,7 +29170,7 @@ function Arrowhead(props) {
28629
29170
  d: arrowPath,
28630
29171
  fill: "none",
28631
29172
  style: {
28632
- stroke: "inherit"
29173
+ stroke: props.color ?? "inherit"
28633
29174
  },
28634
29175
  strokeLinejoin: "round",
28635
29176
  strokeLinecap: "round"
@@ -28739,7 +29280,8 @@ const MovableLine = props => {
28739
29280
  const defaultGraphConfig = {
28740
29281
  range: [[0, 1], [0, 1]],
28741
29282
  snapStep: [1, 1],
28742
- markings: "none"
29283
+ markings: "none",
29284
+ showTooltips: false
28743
29285
  };
28744
29286
  const GraphConfigContext = /*#__PURE__*/React.createContext(defaultGraphConfig);
28745
29287
  function useGraphConfig() {
@@ -28753,7 +29295,8 @@ const StyledMovablePoint = props => {
28753
29295
  const {
28754
29296
  range,
28755
29297
  snapStep,
28756
- markings
29298
+ markings,
29299
+ showTooltips
28757
29300
  } = useGraphConfig();
28758
29301
  const hitboxRef = React.useRef(null);
28759
29302
  const {
@@ -28761,6 +29304,13 @@ const StyledMovablePoint = props => {
28761
29304
  onMove,
28762
29305
  color = wonderBlocksTokens.color.blue
28763
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";
28764
29314
  const {
28765
29315
  dragging
28766
29316
  } = mafs.useMovable({
@@ -28778,19 +29328,20 @@ const StyledMovablePoint = props => {
28778
29328
  const [[_, horizontalStartY]] = useTransform([0, yMin]);
28779
29329
  const [[__, horizontalEndY]] = useTransform([0, yMax]);
28780
29330
  const showHairlines = dragging && markings !== "none";
28781
- 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", {
28782
29332
  x1: verticalStartX,
28783
29333
  y1: y,
28784
29334
  x2: verticalEndX,
28785
29335
  y2: y,
28786
- stroke: wonderBlocksTokens.color.green
29336
+ stroke: color
28787
29337
  }), /*#__PURE__*/React__namespace.createElement("line", {
28788
29338
  x1: x,
28789
29339
  y1: horizontalStartY,
28790
29340
  x2: x,
28791
29341
  y2: horizontalEndY,
28792
- stroke: wonderBlocksTokens.color.green
28793
- })), /*#__PURE__*/React__namespace.createElement("g", {
29342
+ stroke: color
29343
+ }));
29344
+ const svgForPoint = /*#__PURE__*/React__namespace.createElement("g", {
28794
29345
  ref: hitboxRef,
28795
29346
  className: pointClasses,
28796
29347
  tabIndex: 0,
@@ -28823,7 +29374,15 @@ const StyledMovablePoint = props => {
28823
29374
  fill: color
28824
29375
  },
28825
29376
  "data-testid": "movable-point__center"
28826
- })));
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);
28827
29386
  };
28828
29387
 
28829
29388
  const SegmentGraph = props => {
@@ -29217,22 +29776,27 @@ function AxisArrows() {
29217
29776
  const {
29218
29777
  range: [[xMin, xMax], [yMin, yMax]]
29219
29778
  } = useGraphConfig();
29779
+ const axisColor = "var(--mafs-fg)";
29220
29780
  return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29781
+ color: axisColor,
29782
+ x: xMin,
29783
+ y: 0,
29784
+ angle: 180
29785
+ }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29786
+ color: axisColor,
29221
29787
  x: xMax,
29222
29788
  y: 0,
29223
29789
  angle: 0
29224
29790
  }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29791
+ color: axisColor,
29225
29792
  x: 0,
29226
29793
  y: yMin,
29227
- angle: 90
29228
- }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29229
- x: xMin,
29230
- y: 0,
29231
- angle: 180
29794
+ angle: 270
29232
29795
  }), /*#__PURE__*/React__default["default"].createElement(Arrowhead, {
29796
+ color: axisColor,
29233
29797
  x: 0,
29234
29798
  y: yMax,
29235
- angle: 270
29799
+ angle: 90
29236
29800
  }));
29237
29801
  }
29238
29802
 
@@ -29360,7 +29924,7 @@ const Grid = props => {
29360
29924
  return props.markings === "none" ? null : /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Coordinates.Cartesian, {
29361
29925
  xAxis: axisOptions(props, 0),
29362
29926
  yAxis: axisOptions(props, 1)
29363
- }), props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(AxisArrows, null),
29927
+ }),
29364
29928
  // Only render the axis ticks and arrows if the markings are set to a full "graph"
29365
29929
  props.markings === "graph" && /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(AxisTicks, {
29366
29930
  range: props.range,
@@ -29727,6 +30291,7 @@ const getNumericFormat = KhanMath.getNumericFormat;
29727
30291
  * Optionally takes a size ("mini", "small", "normal")
29728
30292
  */
29729
30293
  class NumberInput extends React__namespace.Component {
30294
+ static contextType = PerseusI18nContext;
29730
30295
  static propTypes = {
29731
30296
  value: PropTypes__default["default"].number,
29732
30297
  format: PropTypes__default["default"].string,
@@ -29813,7 +30378,7 @@ class NumberInput extends React__namespace.Component {
29813
30378
  const placeholder = this.props.placeholder;
29814
30379
  return ___default["default"].isFinite(placeholder) ? +placeholder : null;
29815
30380
  }
29816
- const result = firstNumericalParse(value);
30381
+ const result = firstNumericalParse(value, this.context.strings);
29817
30382
  return ___default["default"].isFinite(result) ? result : this.props.value;
29818
30383
  };
29819
30384
 
@@ -29844,7 +30409,7 @@ class NumberInput extends React__namespace.Component {
29844
30409
  if (value == null) {
29845
30410
  return true;
29846
30411
  }
29847
- const val = firstNumericalParse(value);
30412
+ const val = firstNumericalParse(value, this.context.strings);
29848
30413
  const checkValidity = this.props.checkValidity;
29849
30414
  return ___default["default"].isFinite(val) && checkValidity(val);
29850
30415
  };
@@ -30643,7 +31208,8 @@ const MafsGraph = props => {
30643
31208
  value: {
30644
31209
  range: state.range,
30645
31210
  snapStep: state.snapStep,
30646
- markings: props.markings
31211
+ markings: props.markings,
31212
+ showTooltips: !!props.showTooltips
30647
31213
  }
30648
31214
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
30649
31215
  style: {
@@ -30744,10 +31310,25 @@ const makeInvalidTypeError = (functionName, graphType) => {
30744
31310
  // There's no transform function in exports
30745
31311
 
30746
31312
  class LegacyInteractiveGraph extends React__namespace.Component {
31313
+ static contextType = PerseusI18nContext;
31314
+ angle;
31315
+ circle;
31316
+ graphie;
31317
+ horizHairline;
31318
+ line;
31319
+ lines;
31320
+ parabola;
31321
+ pointA;
31322
+ pointB;
31323
+ pointC;
30747
31324
  // @ts-expect-error - TS2564 - Property 'points' has no initializer and is not definitely assigned in the constructor.
30748
-
31325
+ points;
31326
+ polygon;
30749
31327
  // @ts-expect-error - TS2564 - Property 'shouldResetGraphie' has no initializer and is not definitely assigned in the constructor.
30750
-
31328
+ shouldResetGraphie;
31329
+ sinusoid;
31330
+ trashCan;
31331
+ vertHairline;
30751
31332
  static defaultProps = {
30752
31333
  labels: ["x", "y"],
30753
31334
  range: [[-10, 10], [-10, 10]],
@@ -31885,9 +32466,9 @@ class LegacyInteractiveGraph extends React__namespace.Component {
31885
32466
  // isClickToAddPoints() only applies to points and polygons
31886
32467
  if (this.isClickToAddPoints() && this.state.shouldShowInstructions) {
31887
32468
  if (this.props.graph.type === "point") {
31888
- instructions = i18n__namespace._("Click to add points");
32469
+ instructions = this.context.strings.addPoints;
31889
32470
  } else if (this.props.graph.type === "polygon") {
31890
- instructions = i18n__namespace._("Click to add vertices");
32471
+ instructions = this.context.strings.addVertices;
31891
32472
  }
31892
32473
  } else {
31893
32474
  instructions = undefined;
@@ -31965,7 +32546,8 @@ class InteractiveGraph extends React__namespace.Component {
31965
32546
  ref: this.mafsRef,
31966
32547
  gridStep: gridStep,
31967
32548
  snapStep: snapStep,
31968
- box: box
32549
+ box: box,
32550
+ showTooltips: !!this.props.showTooltips
31969
32551
  }));
31970
32552
  }
31971
32553
  return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
@@ -33492,6 +34074,9 @@ var globalStyles = /*#__PURE__*/Object.freeze({
33492
34074
  });
33493
34075
 
33494
34076
  const AnswerChoices = props => {
34077
+ const {
34078
+ strings
34079
+ } = usePerseusI18n();
33495
34080
  const onAnswerChange = selected => {
33496
34081
  const {
33497
34082
  choices,
@@ -33511,6 +34096,7 @@ const AnswerChoices = props => {
33511
34096
  value: content,
33512
34097
  label: /*#__PURE__*/React__namespace.createElement(Renderer, {
33513
34098
  content: content,
34099
+ strings: strings,
33514
34100
  inline: true
33515
34101
  })
33516
34102
  });
@@ -33541,14 +34127,13 @@ const AnswerChoices = props => {
33541
34127
  }));
33542
34128
  };
33543
34129
 
33544
- const strings = {
33545
- hideAnswersToggleLabel: i18n__namespace._("Hide answer choices")
33546
- };
33547
-
33548
34130
  const HideAnswersToggle = props => {
33549
34131
  const idFactory = wonderBlocksCore.useUniqueIdWithMock();
33550
34132
  const switchId = idFactory.get("hide-answers-toggle");
33551
34133
  const labelId = idFactory.get("hide-answers-label");
34134
+ const {
34135
+ strings
34136
+ } = usePerseusI18n();
33552
34137
  return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
33553
34138
  style: styles$g.switchWrapper
33554
34139
  }, /*#__PURE__*/React__namespace.createElement(Switch__default["default"], {
@@ -33589,9 +34174,12 @@ const AnswerPill = props => {
33589
34174
  hovered
33590
34175
  } = props;
33591
34176
  const idFactory = wonderBlocksCore.useUniqueIdWithMock();
33592
- const answerString = selectedAnswers.length > 1 ?
33593
- // always need `ngettext` for variable numbers even if we don't use the singular, see https://khanacademy.slack.com/archives/C0918TZ5G/p1700163024293079
33594
- i18n__namespace.ngettext("%(num)s answer", "%(num)s answers", selectedAnswers.length) : selectedAnswers[0];
34177
+ const {
34178
+ strings
34179
+ } = usePerseusI18n();
34180
+ const answerString = selectedAnswers.length > 1 ? strings.answers({
34181
+ num: selectedAnswers.length
34182
+ }) : selectedAnswers[0];
33595
34183
 
33596
34184
  // It should no longer be possible to interact with an answer after it
33597
34185
  // has been labeled (answered) correctly.
@@ -33620,6 +34208,7 @@ const AnswerPill = props => {
33620
34208
  style: [style, popperStyle, styles$f.pill, correct && styles$f.correct, incorrect && styles$f.incorrect, (focused || hovered) && BringToFront]
33621
34209
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
33622
34210
  content: answerString,
34211
+ strings: strings,
33623
34212
  inline: true
33624
34213
  }));
33625
34214
  });
@@ -33646,8 +34235,9 @@ function shouldReduceMotion() {
33646
34235
  }
33647
34236
  const MARKER_SIZE = 24;
33648
34237
  class Marker extends React__namespace.Component {
34238
+ static contextType = PerseusI18nContext;
33649
34239
  // The marker icon element.
33650
-
34240
+ _icon;
33651
34241
  static defaultProps = {
33652
34242
  selected: []
33653
34243
  };
@@ -33717,10 +34307,8 @@ class Marker extends React__namespace.Component {
33717
34307
  const markerDisabled = showCorrectness === "correct";
33718
34308
  const active = hovered || focused;
33719
34309
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
33720
- style: [styles$e.marker, active && !markerDisabled && styles$e.markerActive]
33721
- // already translated in: /widgets/graded-group.tsx
33722
- ,
33723
- "aria-label": markerDisabled ? i18n__namespace._("Correct!") : label
34310
+ style: [styles$e.marker, active && !markerDisabled && styles$e.markerActive],
34311
+ "aria-label": markerDisabled ? this.context.strings.correctExcited : label
33724
34312
  }, this.renderIcon()), !!selected && showAnswer && /*#__PURE__*/React__namespace.createElement(AnswerPill, {
33725
34313
  selectedAnswers: selected,
33726
34314
  showCorrectness: showCorrectness,
@@ -33820,8 +34408,9 @@ const styles$e = aphrodite.StyleSheet.create({
33820
34408
  */
33821
34409
 
33822
34410
  class LabelImage extends React__namespace.Component {
34411
+ static contextType = PerseusI18nContext;
33823
34412
  // The rendered markers on the question image for labeling.
33824
-
34413
+ _markers;
33825
34414
  static gradeMarker(marker) {
33826
34415
  const score = {
33827
34416
  hasAnswers: false,
@@ -34307,8 +34896,11 @@ class LabelImage extends React__namespace.Component {
34307
34896
  multipleAnswers,
34308
34897
  hideChoicesFromInstructions: hideChoices
34309
34898
  } = this.props;
34310
- 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.");
34311
- const choicesString = i18n__namespace._("Choices:");
34899
+ const {
34900
+ strings
34901
+ } = this.context;
34902
+ const promptString = isMobile ? multipleAnswers ? strings.tapMultiple : strings.tapSingle : multipleAnswers ? strings.clickMultiple : strings.clickSingle;
34903
+ const choicesString = strings.choices;
34312
34904
  return /*#__PURE__*/React__namespace.createElement("div", {
34313
34905
  className: classNames__default["default"]("perseus-label-image-widget-instructions", aphrodite.css(styles$d.instructions))
34314
34906
  }, /*#__PURE__*/React__namespace.createElement("div", {
@@ -34319,7 +34911,8 @@ class LabelImage extends React__namespace.Component {
34319
34911
  className: aphrodite.css(styles$d.instructionsChoice),
34320
34912
  key: index
34321
34913
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
34322
- content: choice
34914
+ content: choice,
34915
+ strings: strings
34323
34916
  })))));
34324
34917
  }
34325
34918
  render() {
@@ -34494,12 +35087,13 @@ class Placeholder extends React__namespace.Component {
34494
35087
  // Usual flow: Static -> Dragging -> Animating -> Static
34495
35088
  // [Dis|en]abling: Static|Dragging|Animating -> Disabled -> Static
34496
35089
  class Draggable extends React__namespace.Component {
35090
+ static contextType = PerseusI18nContext;
34497
35091
  // Handler returned by requestAnimationFrame.
34498
35092
  animationFrameRequest = null;
34499
35093
  // @ts-expect-error - TS2564 - Property 'isMouseMoveUpBound' has no initializer and is not definitely assigned in the constructor.
34500
-
35094
+ isMouseMoveUpBound;
34501
35095
  // @ts-expect-error - TS2564 - Property '_mounted' has no initializer and is not definitely assigned in the constructor.
34502
-
35096
+ _mounted;
34503
35097
  static defaultProps = {
34504
35098
  includePadding: true,
34505
35099
  type: ItemState.STATIC,
@@ -34602,7 +35196,8 @@ class Draggable extends React__namespace.Component {
34602
35196
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
34603
35197
  content: this.props.content,
34604
35198
  linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "draggable"),
34605
- onRender: this.props.onRender
35199
+ onRender: this.props.onRender,
35200
+ strings: this.context.strings
34606
35201
  }));
34607
35202
  }
34608
35203
  componentDidUpdate(prevProps) {
@@ -35156,6 +35751,7 @@ const {
35156
35751
  } = Util;
35157
35752
  const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
35158
35753
  class Matcher extends React__namespace.Component {
35754
+ static contextType = PerseusI18nContext;
35159
35755
  static propTypes = {
35160
35756
  apiOptions: ApiOptions.propTypes,
35161
35757
  labels: PropTypes__default["default"].array,
@@ -35235,12 +35831,14 @@ class Matcher extends React__namespace.Component {
35235
35831
  className: aphrodite.css(styles$b.column, styles$b.columnLabel)
35236
35832
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
35237
35833
  content: this.props.labels[0] || "...",
35238
- linterContext: this.props.linterContext
35834
+ linterContext: this.props.linterContext,
35835
+ strings: this.context.strings
35239
35836
  })), /*#__PURE__*/React__namespace.createElement("th", {
35240
35837
  className: aphrodite.css(styles$b.column, styles$b.columnRight, styles$b.columnLabel)
35241
35838
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
35242
35839
  content: this.props.labels[1] || "...",
35243
- linterContext: this.props.linterContext
35840
+ linterContext: this.props.linterContext,
35841
+ strings: this.context.strings
35244
35842
  }))), /*#__PURE__*/React__namespace.createElement("tr", {
35245
35843
  className: aphrodite.css(styles$b.row)
35246
35844
  }, /*#__PURE__*/React__namespace.createElement("td", {
@@ -35452,8 +36050,9 @@ const getMatrixSize = function (matrix) {
35452
36050
  return matrixSize;
35453
36051
  };
35454
36052
  class Matrix extends React__namespace.Component {
36053
+ static contextType = PerseusI18nContext;
35455
36054
  // @ts-expect-error - TS2564 - Property 'cursorPosition' has no initializer and is not definitely assigned in the constructor.
35456
-
36055
+ cursorPosition;
35457
36056
  static defaultProps = {
35458
36057
  matrixBoardSize: [3, 3],
35459
36058
  answers: [[]],
@@ -35463,6 +36062,57 @@ class Matrix extends React__namespace.Component {
35463
36062
  apiOptions: ApiOptions.defaults,
35464
36063
  linterContext: PerseusLinter.linterContextDefault
35465
36064
  };
36065
+ static validate(state, rubric, strings) {
36066
+ const solution = rubric.answers;
36067
+ const supplied = state.answers;
36068
+ const solutionSize = getMatrixSize(solution);
36069
+ const suppliedSize = getMatrixSize(supplied);
36070
+ const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
36071
+ const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
36072
+ let message = null;
36073
+ let hasEmptyCell = false;
36074
+ let incorrect = false;
36075
+ ___default["default"](suppliedSize[0]).times(row => {
36076
+ ___default["default"](suppliedSize[1]).times(col => {
36077
+ if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
36078
+ hasEmptyCell = true;
36079
+ }
36080
+ if (!incorrectSize) {
36081
+ const validator = createValidator(solution[row][col], {
36082
+ simplify: true
36083
+ }, strings);
36084
+ const result = validator(supplied[row][col]);
36085
+ if (result.message) {
36086
+ // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
36087
+ message = result.message;
36088
+ }
36089
+ if (!result.correct) {
36090
+ incorrect = true;
36091
+ }
36092
+ }
36093
+ });
36094
+ });
36095
+ if (hasEmptyCell) {
36096
+ return {
36097
+ type: "invalid",
36098
+ message: strings.fillAllCells
36099
+ };
36100
+ }
36101
+ if (incorrectSize) {
36102
+ return {
36103
+ type: "points",
36104
+ earned: 0,
36105
+ total: 1,
36106
+ message: null
36107
+ };
36108
+ }
36109
+ return {
36110
+ type: "points",
36111
+ earned: incorrect ? 0 : 1,
36112
+ total: 1,
36113
+ message: message
36114
+ };
36115
+ }
35466
36116
  state = {
35467
36117
  enterTheMatrix: 0
35468
36118
  };
@@ -35505,7 +36155,8 @@ class Matrix extends React__namespace.Component {
35505
36155
  className: "matrix-prefix"
35506
36156
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
35507
36157
  content: this.props.prefix,
35508
- linterContext: this.props.linterContext
36158
+ linterContext: this.props.linterContext,
36159
+ strings: this.context.strings
35509
36160
  })), /*#__PURE__*/React__namespace.createElement("div", {
35510
36161
  className: "matrix-input"
35511
36162
  }, /*#__PURE__*/React__namespace.createElement("div", {
@@ -35620,7 +36271,8 @@ class Matrix extends React__namespace.Component {
35620
36271
  className: "matrix-suffix"
35621
36272
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
35622
36273
  content: this.props.suffix,
35623
- linterContext: this.props.linterContext
36274
+ linterContext: this.props.linterContext,
36275
+ strings: this.context.strings
35624
36276
  })));
35625
36277
  }
35626
36278
  getInputPaths = () => {
@@ -35759,63 +36411,9 @@ class Matrix extends React__namespace.Component {
35759
36411
  };
35760
36412
  };
35761
36413
  simpleValidate = rubric => {
35762
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Matrix'.
35763
- return Matrix.validate(this.getUserInput(), rubric);
36414
+ return Matrix.validate(this.getUserInput(), rubric, this.context.strings);
35764
36415
  };
35765
36416
  }
35766
- ___default["default"].extend(Matrix, {
35767
- validate: function (state, rubric) {
35768
- const solution = rubric.answers;
35769
- const supplied = state.answers;
35770
- const solutionSize = getMatrixSize(solution);
35771
- const suppliedSize = getMatrixSize(supplied);
35772
- const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
35773
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
35774
- let message = null;
35775
- let hasEmptyCell = false;
35776
- let incorrect = false;
35777
- ___default["default"](suppliedSize[0]).times(row => {
35778
- ___default["default"](suppliedSize[1]).times(col => {
35779
- if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
35780
- hasEmptyCell = true;
35781
- }
35782
- if (!incorrectSize) {
35783
- const validator = createValidator(solution[row][col], {
35784
- simplify: true
35785
- });
35786
- const result = validator(supplied[row][col]);
35787
- if (result.message) {
35788
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
35789
- message = result.message;
35790
- }
35791
- if (!result.correct) {
35792
- incorrect = true;
35793
- }
35794
- }
35795
- });
35796
- });
35797
- if (hasEmptyCell) {
35798
- return {
35799
- type: "invalid",
35800
- message: i18n__namespace._("Make sure you fill in all cells in the matrix.")
35801
- };
35802
- }
35803
- if (incorrectSize) {
35804
- return {
35805
- type: "points",
35806
- earned: 0,
35807
- total: 1,
35808
- message: null
35809
- };
35810
- }
35811
- return {
35812
- type: "points",
35813
- earned: incorrect ? 0 : 1,
35814
- total: 1,
35815
- message: message
35816
- };
35817
- }
35818
- });
35819
36417
  const propTransform$1 = editorProps => {
35820
36418
  // Remove answers before passing to widget
35821
36419
  const blankAnswers = ___default["default"](editorProps.matrixBoardSize[0]).times(function () {
@@ -36779,6 +37377,7 @@ const parse$1 = SmilesParser.parse;
36779
37377
  const ParseError = SmilesParser.ParseError;
36780
37378
  const borderSize = 30;
36781
37379
  class Molecule extends React__namespace.Component {
37380
+ static contextType = PerseusI18nContext;
36782
37381
  state = {
36783
37382
  parsedSmiles: null,
36784
37383
  error: null
@@ -36855,8 +37454,8 @@ class Molecule extends React__namespace.Component {
36855
37454
  // eslint-disable-next-line react/no-string-refs
36856
37455
  ,
36857
37456
  ref: "canvas"
36858
- }, i18n__namespace.$_("A molecular structure drawing. SMILES notation: %(content)s", {
36859
- content: this.props.smiles
37457
+ }, this.context.strings.molecularDrawing({
37458
+ content: this.props.smiles || ""
36860
37459
  }));
36861
37460
  if (this.state.error) {
36862
37461
  content = /*#__PURE__*/React__namespace.createElement("div", {
@@ -37035,6 +37634,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
37035
37634
  return results;
37036
37635
  });
37037
37636
  class NumberLine extends React__namespace.Component {
37637
+ static contextType = PerseusI18nContext;
37038
37638
  static defaultProps = {
37039
37639
  range: [0, 10],
37040
37640
  labelStyle: "decimal",
@@ -37347,18 +37947,21 @@ class NumberLine extends React__namespace.Component {
37347
37947
  return NumberLine.validate(this.getUserInput(), rubric);
37348
37948
  };
37349
37949
  render() {
37950
+ const {
37951
+ strings
37952
+ } = this.context;
37350
37953
  const divisionRange = this.props.divisionRange;
37351
37954
  const divRangeString = divisionRange[0] + EN_DASH + divisionRange[1];
37352
37955
  const invalidNumDivisions = this.props.numDivisions < divisionRange[0] || this.props.numDivisions > divisionRange[1];
37353
37956
  const inequalityControls = /*#__PURE__*/React__namespace.createElement("div", null, /*#__PURE__*/React__namespace.createElement("input", {
37354
37957
  type: "button",
37355
37958
  className: "simple-button",
37356
- value: i18n__namespace._("Switch direction"),
37959
+ value: strings.switchDirection,
37357
37960
  onClick: this.handleReverse
37358
37961
  }), /*#__PURE__*/React__namespace.createElement("input", {
37359
37962
  type: "button",
37360
37963
  className: "simple-button",
37361
- value: ___default["default"](["le", "ge"]).contains(this.props.rel) ? i18n__namespace._("Make circle open") : i18n__namespace._("Make circle filled"),
37964
+ value: ___default["default"](["le", "ge"]).contains(this.props.rel) ? strings.circleOpen : strings.circleFilled,
37362
37965
  onClick: this.handleToggleStrict
37363
37966
  }));
37364
37967
  let tickCtrl;
@@ -37369,7 +37972,7 @@ class NumberLine extends React__namespace.Component {
37369
37972
  } else {
37370
37973
  Input = NumberInput;
37371
37974
  }
37372
- tickCtrl = /*#__PURE__*/React__namespace.createElement("label", null, i18n__namespace._("Number of divisions:"), " ", /*#__PURE__*/React__namespace.createElement(Input
37975
+ tickCtrl = /*#__PURE__*/React__namespace.createElement("label", null, strings.numDivisions, " ", /*#__PURE__*/React__namespace.createElement(Input
37373
37976
  // eslint-disable-next-line react/no-string-refs
37374
37977
  , {
37375
37978
  ref: "tick-ctrl",
@@ -37386,9 +37989,9 @@ class NumberLine extends React__namespace.Component {
37386
37989
  className: "perseus-widget " + "perseus-widget-interactive-number-line"
37387
37990
  }, tickCtrl, !this.isValid() ? /*#__PURE__*/React__namespace.createElement("div", {
37388
37991
  className: "perseus-error"
37389
- }, i18n__namespace.doNotTranslate("Invalid number line configuration.")) : this.props.isTickCtrl && invalidNumDivisions ? /*#__PURE__*/React__namespace.createElement("div", {
37992
+ }, "Invalid number line configuration.") : this.props.isTickCtrl && invalidNumDivisions ? /*#__PURE__*/React__namespace.createElement("div", {
37390
37993
  className: "perseus-error"
37391
- }, i18n__namespace.$_("Please make sure the number of divisions is " + "in the range %(divRangeString)s.", {
37994
+ }, strings.divisions({
37392
37995
  divRangeString: divRangeString
37393
37996
  })) : this._renderGraphie(), !this.props.static && this.props.isInequality && inequalityControls);
37394
37997
  }
@@ -37520,8 +38123,9 @@ const PropTypePosition = PropTypes__default["default"].shape({
37520
38123
  top: PropTypes__default["default"].number
37521
38124
  });
37522
38125
  class Card extends React__namespace.Component {
38126
+ static contextType = PerseusI18nContext;
37523
38127
  // @ts-expect-error - TS2564 - Property 'mouseMoveUpBound' has no initializer and is not definitely assigned in the constructor.
37524
-
38128
+ mouseMoveUpBound;
37525
38129
  static propTypes = {
37526
38130
  floating: PropTypes__default["default"].bool.isRequired,
37527
38131
  animating: PropTypes__default["default"].bool,
@@ -37580,7 +38184,8 @@ class Card extends React__namespace.Component {
37580
38184
  }, /*#__PURE__*/React__namespace.createElement("div", {
37581
38185
  className: className.join(" ")
37582
38186
  }, /*#__PURE__*/React__namespace.createElement(Renderer, _extends({}, rendererProps, {
37583
- linterContext: this.props.linterContext
38187
+ linterContext: this.props.linterContext,
38188
+ strings: this.context.strings
37584
38189
  }))));
37585
38190
  }
37586
38191
  shouldComponentUpdate(nextProps, nextState) {
@@ -38853,6 +39458,7 @@ const styles$8 = aphrodite.StyleSheet.create({
38853
39458
 
38854
39459
  /* eslint-disable react/no-unsafe */
38855
39460
  class HighlightSetRenderer extends React__namespace.PureComponent {
39461
+ static contextType = PerseusI18nContext;
38856
39462
  state = {
38857
39463
  hoveredHighlightKey: null,
38858
39464
  hoveringTooltipFor: null
@@ -38942,7 +39548,7 @@ class HighlightSetRenderer extends React__namespace.PureComponent {
38942
39548
  }
38943
39549
  const hoveredHighlight = this.props.highlights[hoveredHighlightKey];
38944
39550
  return /*#__PURE__*/React__namespace.createElement(HighlightTooltip, {
38945
- label: i18n__namespace._("Remove highlight"),
39551
+ label: this.context.strings.removeHighlight,
38946
39552
  focusNode: hoveredHighlight.domRange.endContainer,
38947
39553
  focusOffset: hoveredHighlight.domRange.endOffset,
38948
39554
  offsetParent: this.props.offsetParent,
@@ -39113,6 +39719,7 @@ class SelectionTracker extends React__namespace.PureComponent {
39113
39719
  * `HighlightingUI`.
39114
39720
  */
39115
39721
  class HighlightingUI extends React__namespace.PureComponent {
39722
+ static contextType = PerseusI18nContext;
39116
39723
  _handleAddHighlight(highlightToAdd) {
39117
39724
  this.props.onAddHighlight(highlightToAdd);
39118
39725
 
@@ -39169,7 +39776,7 @@ class HighlightingUI extends React__namespace.PureComponent {
39169
39776
  onRemoveHighlight: this.props.onRemoveHighlight,
39170
39777
  zIndexes: this.props.zIndexes
39171
39778
  }), this._selectionIsValid(trackedSelection) && !userIsMouseSelecting && /*#__PURE__*/React__namespace.createElement(HighlightTooltip, {
39172
- label: i18n__namespace._("Add highlight"),
39779
+ label: this.context.strings.addHighlight,
39173
39780
  onClick: () => this._handleAddHighlight(trackedSelection.proposedHighlight),
39174
39781
  focusNode: trackedSelection.focusNode,
39175
39782
  focusOffset: trackedSelection.focusOffset,
@@ -39192,6 +39799,7 @@ class HighlightingUI extends React__namespace.PureComponent {
39192
39799
  * them amenable to binary search - see `findBoundaryWordIndex` in ranges.js.
39193
39800
  */
39194
39801
  class WordIndexer extends React__namespace.PureComponent {
39802
+ _container;
39195
39803
  /* eslint-enable react/sort-comp */
39196
39804
 
39197
39805
  componentDidMount() {
@@ -39311,7 +39919,8 @@ class WordIndexer extends React__namespace.PureComponent {
39311
39919
  */
39312
39920
  class HighlightableContent extends React__namespace.PureComponent {
39313
39921
  // References to the mounted container and content divs.
39314
-
39922
+ _container;
39923
+ _content;
39315
39924
  state = {
39316
39925
  wordRanges: []
39317
39926
  };
@@ -39472,7 +40081,7 @@ class RefStart extends React__namespace.Component {
39472
40081
  render() {
39473
40082
  return /*#__PURE__*/React__namespace.createElement("span", {
39474
40083
  style: REF_STYLE
39475
- }, i18n__namespace.doNotTranslate("_"));
40084
+ }, "_");
39476
40085
  }
39477
40086
 
39478
40087
  // @ts-expect-error - TS2322 - Type '() => React.ReactNode' is not assignable to type '() => ReactElement<any, string | JSXElementConstructor<any>>'.
@@ -39484,9 +40093,24 @@ class RefEnd extends React__namespace.Component {
39484
40093
  render() {
39485
40094
  return /*#__PURE__*/React__namespace.createElement("span", {
39486
40095
  style: REF_STYLE
39487
- }, i18n__namespace.doNotTranslate("_"));
40096
+ }, "_");
39488
40097
  }
39489
40098
  }
40099
+ const AltText = _ref => {
40100
+ let {
40101
+ id,
40102
+ number
40103
+ } = _ref;
40104
+ const {
40105
+ strings
40106
+ } = usePerseusI18n();
40107
+ return /*#__PURE__*/React__namespace.createElement("span", {
40108
+ key: "alt-text",
40109
+ className: "perseus-sr-only"
40110
+ }, strings[id]({
40111
+ number
40112
+ }));
40113
+ };
39490
40114
  const rules = {
39491
40115
  newline: SimpleMarkdown__default["default"].defaultRules.newline,
39492
40116
  paragraph: SimpleMarkdown__default["default"].defaultRules.paragraph,
@@ -39655,12 +40279,10 @@ const rules = {
39655
40279
  "aria-hidden": "true"
39656
40280
  }, /*#__PURE__*/React__namespace.createElement("span", {
39657
40281
  style: SQUARE_LABEL_STYLE
39658
- }, node.content)), /*#__PURE__*/React__namespace.createElement("span", {
39659
- key: "alt-text",
39660
- className: "perseus-sr-only"
39661
- }, i18n__namespace.$_("[Marker for question %(number)s]", {
40282
+ }, node.content)), /*#__PURE__*/React__namespace.createElement(AltText, {
40283
+ id: "questionMarker",
39662
40284
  number: node.content
39663
- })), node.space ? "\u00A0" : null];
40285
+ }), node.space ? "\u00A0" : null];
39664
40286
  }
39665
40287
  },
39666
40288
  circleLabel: {
@@ -39681,12 +40303,10 @@ const rules = {
39681
40303
  "aria-hidden": true
39682
40304
  }, /*#__PURE__*/React__namespace.createElement("span", {
39683
40305
  style: CIRCLE_LABEL_STYLE
39684
- }, node.content)), /*#__PURE__*/React__namespace.createElement("span", {
39685
- key: "alt-text",
39686
- className: "perseus-sr-only"
39687
- }, i18n__namespace.$_("[Circle marker %(number)s]", {
40306
+ }, node.content)), /*#__PURE__*/React__namespace.createElement(AltText, {
40307
+ id: "circleMarker",
39688
40308
  number: node.content
39689
- })), node.space ? "\u00A0" : null];
40309
+ }), node.space ? "\u00A0" : null];
39690
40310
  }
39691
40311
  },
39692
40312
  squareBracketRef: {
@@ -39706,12 +40326,10 @@ const rules = {
39706
40326
  key: "visual-brackets",
39707
40327
  className: "perseus-passage-bracket-label",
39708
40328
  "aria-hidden": "true"
39709
- }, "[", node.content, "]"), /*#__PURE__*/React__namespace.createElement("span", {
39710
- key: "alt-text",
39711
- className: "perseus-sr-only"
39712
- }, i18n__namespace.$_("[Sentence %(number)s]", {
40329
+ }, "[", node.content, "]"), /*#__PURE__*/React__namespace.createElement(AltText, {
40330
+ id: "sentenceMarker",
39713
40331
  number: node.content
39714
- })), node.space ? "\u00A0" : null];
40332
+ }), node.space ? "\u00A0" : null];
39715
40333
  }
39716
40334
  },
39717
40335
  highlight: {
@@ -39811,6 +40429,7 @@ var PassageMarkdown = {
39811
40429
  // A fake paragraph to measure the line height of the passage,
39812
40430
  // so we can adapt to browser zoom
39813
40431
  class LineHeightMeasurer extends React__namespace.Component {
40432
+ _line;
39814
40433
  measureLineHeight() {
39815
40434
  if (!this._line) {
39816
40435
  return 0;
@@ -39843,10 +40462,13 @@ const styles$6 = aphrodite.StyleSheet.create({
39843
40462
  // Information about a passage reference, used in inter-widgets.
39844
40463
 
39845
40464
  class Passage extends React__namespace.Component {
40465
+ static contextType = PerseusI18nContext;
40466
+ _contentRef;
40467
+ _lineHeightMeasurerRef;
39846
40468
  // @ts-expect-error - TS2564 - Property '_onResize' has no initializer and is not definitely assigned in the constructor.
39847
-
40469
+ _onResize;
39848
40470
  // @ts-expect-error - TS2564 - Property '_stylesAppiedTimer' has no initializer and is not definitely assigned in the constructor.
39849
-
40471
+ _stylesAppiedTimer;
39850
40472
  static defaultProps = {
39851
40473
  passageTitle: "",
39852
40474
  passageText: "",
@@ -40101,15 +40723,18 @@ class Passage extends React__namespace.Component {
40101
40723
  _renderInstructions(parseState) {
40102
40724
  const firstQuestionNumber = parseState.firstQuestionRef;
40103
40725
  const firstSentenceRef = parseState.firstSentenceRef;
40726
+ const {
40727
+ strings
40728
+ } = this.context;
40104
40729
  let instructions = "";
40105
40730
  if (firstQuestionNumber) {
40106
- instructions += i18n__namespace._("The symbol %(questionSymbol)s indicates that question " + "%(questionNumber)s references this portion of the " + "passage.", {
40731
+ instructions += strings.symbolPassage({
40107
40732
  questionSymbol: "[[" + firstQuestionNumber + "]]",
40108
40733
  questionNumber: firstQuestionNumber
40109
40734
  });
40110
40735
  }
40111
40736
  if (firstSentenceRef) {
40112
- instructions += i18n__namespace._(" The symbol %(sentenceSymbol)s indicates that the " + "following sentence is referenced in a question.", {
40737
+ instructions += strings.symbolQuestion({
40113
40738
  sentenceSymbol: "[" + firstSentenceRef + "]"
40114
40739
  });
40115
40740
  }
@@ -40155,6 +40780,9 @@ class Passage extends React__namespace.Component {
40155
40780
  return PassageMarkdown.output(parsed);
40156
40781
  }
40157
40782
  render() {
40783
+ const {
40784
+ strings
40785
+ } = this.context;
40158
40786
  let lineNumbers;
40159
40787
  const nLines = this.state.nLines;
40160
40788
  if (this.props.showLineNumbers && nLines) {
@@ -40163,9 +40791,7 @@ class Passage extends React__namespace.Component {
40163
40791
  lineNumbers = ___default["default"].range(1, nLines + 1).map(lineN => {
40164
40792
  const lineAt = lineN + this.state.startLineNumbersAfter;
40165
40793
  if (lineAt === 4) {
40166
- // we leave a space in i18n to disambiguate translations, see TP-5392
40167
- // I18N: a label next to a reading passage to denote the line number
40168
- const translatedLine = i18n__namespace._("Line ");
40794
+ const translatedLine = strings.lineLabel;
40169
40795
  return /*#__PURE__*/React__namespace.createElement("span", {
40170
40796
  key: "line-marker",
40171
40797
  className: "line-marker"
@@ -40195,13 +40821,14 @@ class Passage extends React__namespace.Component {
40195
40821
  className: "passage-title"
40196
40822
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
40197
40823
  content: this.props.passageTitle,
40198
- linterContext: this.props.linterContext
40824
+ linterContext: this.props.linterContext,
40825
+ strings: strings
40199
40826
  })), lineNumbers && /*#__PURE__*/React__namespace.createElement("div", {
40200
40827
  className: "line-numbers",
40201
40828
  "aria-hidden": true
40202
40829
  }, lineNumbers), !hasTitle && /*#__PURE__*/React__namespace.createElement("h3", {
40203
40830
  className: "perseus-sr-only"
40204
- }, i18n__namespace._("Beginning of reading passage.")), /*#__PURE__*/React__namespace.createElement("div", {
40831
+ }, strings.beginningPassage), /*#__PURE__*/React__namespace.createElement("div", {
40205
40832
  className: "passage-text"
40206
40833
  }, this.shouldRenderJipt() ?
40207
40834
  /*#__PURE__*/
@@ -40209,16 +40836,17 @@ class Passage extends React__namespace.Component {
40209
40836
  // content to a <Renderer /> which knows how
40210
40837
  // to handle rendering JIPT text.
40211
40838
  React__namespace.createElement(Renderer, {
40212
- content: this.props.passageText
40839
+ content: this.props.passageText,
40840
+ strings: strings
40213
40841
  }) : this._renderContent(parsedContent)), this._hasFootnotes() && [/*#__PURE__*/React__namespace.createElement("h4", {
40214
40842
  key: "footnote-start",
40215
40843
  className: "perseus-sr-only"
40216
- }, i18n__namespace._("Beginning of reading passage footnotes.")), /*#__PURE__*/React__namespace.createElement("div", {
40844
+ }, strings.beginningFootnotes), /*#__PURE__*/React__namespace.createElement("div", {
40217
40845
  key: "footnotes",
40218
40846
  className: "footnotes"
40219
40847
  }, this._renderFootnotes())], /*#__PURE__*/React__namespace.createElement("div", {
40220
40848
  className: "perseus-sr-only"
40221
- }, i18n__namespace._("End of reading passage.")))));
40849
+ }, strings.endPassage))));
40222
40850
  }
40223
40851
  }
40224
40852
  var Passage$1 = {
@@ -40233,6 +40861,7 @@ var Passage$1 = {
40233
40861
 
40234
40862
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
40235
40863
  class PassageRefTarget extends React__namespace.Component {
40864
+ static contextType = PerseusI18nContext;
40236
40865
  static defaultProps = {
40237
40866
  content: "",
40238
40867
  linterContext: PerseusLinter.linterContextDefault
@@ -40270,7 +40899,8 @@ class PassageRefTarget extends React__namespace.Component {
40270
40899
  // inline={true}
40271
40900
  ,
40272
40901
  apiOptions: this.props.apiOptions,
40273
- linterContext: this.props.linterContext
40902
+ linterContext: this.props.linterContext,
40903
+ strings: this.context.strings
40274
40904
  });
40275
40905
  }
40276
40906
  }
@@ -40295,9 +40925,13 @@ const {
40295
40925
  deepEq
40296
40926
  } = Util;
40297
40927
  class Plotter extends React__namespace.Component {
40928
+ static contextType = PerseusI18nContext;
40298
40929
  // @ts-expect-error - TS2564 - Property 'shouldSetupGraphie' has no initializer and is not definitely assigned in the constructor.
40299
-
40930
+ shouldSetupGraphie;
40300
40931
  _isMounted = false;
40932
+ horizHairline;
40933
+ hairlineRange;
40934
+ graphie;
40301
40935
  static defaultProps = {
40302
40936
  type: "bar",
40303
40937
  labels: ["", ""],
@@ -40484,11 +41118,11 @@ class Plotter extends React__namespace.Component {
40484
41118
  }
40485
41119
  }
40486
41120
  if ((isBar || isLine) && isMobile && !this.props.static) {
40487
- 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");
41121
+ 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");
40488
41122
  }
40489
41123
  self.setupCategories(config);
40490
41124
  if (isTiledPlot && isMobile) {
40491
- 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");
41125
+ 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");
40492
41126
  }
40493
41127
  if (isTiledPlot) {
40494
41128
  self.drawPicHeights(self.state.values, prevState.values);
@@ -41163,7 +41797,7 @@ const HORIZONTAL = "horizontal";
41163
41797
  const VERTICAL = "vertical";
41164
41798
  class Sorter extends React__namespace.Component {
41165
41799
  // @ts-expect-error - TS2564 - Property '_isMounted' has no initializer and is not definitely assigned in the constructor.
41166
-
41800
+ _isMounted;
41167
41801
  static propTypes = {
41168
41802
  apiOptions: ApiOptions.propTypes,
41169
41803
  // eslint-disable-next-line react/forbid-prop-types
@@ -41306,6 +41940,7 @@ const getRefForPath = function (path) {
41306
41940
  return "answer" + row + "," + column;
41307
41941
  };
41308
41942
  class Table extends React__namespace.Component {
41943
+ static contextType = PerseusI18nContext;
41309
41944
  static propTypes = {
41310
41945
  answers: PropTypes__default["default"].arrayOf(PropTypes__default["default"].arrayOf(PropTypes__default["default"].string)),
41311
41946
  editableHeaders: PropTypes__default["default"].bool,
@@ -41375,7 +42010,8 @@ class Table extends React__namespace.Component {
41375
42010
  key: i
41376
42011
  }, /*#__PURE__*/React__namespace.createElement(Renderer, {
41377
42012
  content: header,
41378
- linterContext: this.props.linterContext
42013
+ linterContext: this.props.linterContext,
42014
+ strings: this.context.strings
41379
42015
  }));
41380
42016
  }))), /*#__PURE__*/React__namespace.createElement("tbody", null, ___default["default"](rows).times(r => {
41381
42017
  return /*#__PURE__*/React__namespace.createElement("tr", {
@@ -41420,7 +42056,7 @@ class Table extends React__namespace.Component {
41420
42056
  };
41421
42057
  simpleValidate = rubric => {
41422
42058
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Table'.
41423
- return Table.validate(this.getUserInput(), rubric);
42059
+ return Table.validate(this.getUserInput(), rubric, this.context.strings);
41424
42060
  };
41425
42061
  _handleFocus = inputPath => {
41426
42062
  this.props.onFocus(inputPath);
@@ -41489,7 +42125,7 @@ class Table extends React__namespace.Component {
41489
42125
  };
41490
42126
  }
41491
42127
  ___default["default"].extend(Table, {
41492
- validate: function (state, rubric) {
42128
+ validate: function (state, rubric, strings) {
41493
42129
  const filterNonEmpty = function (table) {
41494
42130
  return ___default["default"].filter(table, function (row) {
41495
42131
  // Check if row has a cell that is nonempty
@@ -41527,7 +42163,7 @@ ___default["default"].extend(Table, {
41527
42163
  const cellSolution = rowSolution[i];
41528
42164
  const validator = createValidator(cellSolution, {
41529
42165
  simplify: true
41530
- });
42166
+ }, strings);
41531
42167
  const result = validator(cellSupplied);
41532
42168
  if (result.message) {
41533
42169
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
@@ -41814,6 +42450,11 @@ const str = {
41814
42450
  * @param s A string representation of a floating point.
41815
42451
  */
41816
42452
  class SignificantFigures {
42453
+ order;
42454
+ mantissa;
42455
+ positive;
42456
+ sigFigs;
42457
+ sigDecs;
41817
42458
  constructor(s) {
41818
42459
  this.order = parseOrder(s);
41819
42460
  this.mantissa = parseMantissa(s);
@@ -42222,17 +42863,19 @@ const countSigfigs = function (value) {
42222
42863
  * rolling two second delay, but hidden immediately on further typing.
42223
42864
  */
42224
42865
  class OldUnitInput extends React__namespace.Component {
42866
+ static contextType = PerseusI18nContext;
42867
+ _errorTimeout;
42225
42868
  static defaultProps = {
42226
42869
  apiOptions: ApiOptions.defaults,
42227
42870
  value: ""
42228
42871
  };
42229
- static validate(userInput, rubric) {
42872
+ static validate(userInput, rubric, strings) {
42230
42873
  const answer = KAS__namespace.unitParse(rubric.value).expr;
42231
42874
  const guess = KAS__namespace.unitParse(userInput);
42232
42875
  if (!guess.parsed) {
42233
42876
  return {
42234
42877
  type: "invalid",
42235
- message: i18n__namespace._("I couldn't understand those units.")
42878
+ message: strings.dontUnderstandUnits
42236
42879
  };
42237
42880
  }
42238
42881
 
@@ -42247,8 +42890,7 @@ class OldUnitInput extends React__namespace.Component {
42247
42890
  const sigfigs = rubric.sigfigs;
42248
42891
  const sigfigsCorrect = countSigfigs(guess.coefficient) === sigfigs;
42249
42892
  if (!sigfigsCorrect) {
42250
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42251
- message = i18n__namespace._("Check your significant figures.");
42893
+ message = strings.checkSigFigs;
42252
42894
  }
42253
42895
 
42254
42896
  // now we need to check that the answer is correct to the precision we
@@ -42267,8 +42909,7 @@ class OldUnitInput extends React__namespace.Component {
42267
42909
  numericallyCorrect = false;
42268
42910
  }
42269
42911
  if (!numericallyCorrect) {
42270
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42271
- message = i18n__namespace._("That answer is numerically incorrect.");
42912
+ message = strings.answerNumericallyIncorrect;
42272
42913
  }
42273
42914
  let kasCorrect;
42274
42915
  const guessUnit = primUnits(guess.expr.simplify());
@@ -42288,8 +42929,7 @@ class OldUnitInput extends React__namespace.Component {
42288
42929
  });
42289
42930
  }
42290
42931
  if (!kasCorrect) {
42291
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42292
- message = i18n__namespace._("Check your units.");
42932
+ message = strings.checkUnits;
42293
42933
  }
42294
42934
  const correct = kasCorrect && numericallyCorrect && sigfigsCorrect;
42295
42935
  return {
@@ -42385,7 +43025,7 @@ class OldUnitInput extends React__namespace.Component {
42385
43025
  });
42386
43026
  };
42387
43027
  simpleValidate = (rubric, onInputError) => {
42388
- return OldUnitInput.validate(this.getUserInput(), rubric);
43028
+ return OldUnitInput.validate(this.getUserInput(), rubric, this.context.strings);
42389
43029
  };
42390
43030
  getUserInput = () => {
42391
43031
  return this.props.value;
@@ -42460,7 +43100,7 @@ class OldUnitInput extends React__namespace.Component {
42460
43100
  style: {
42461
43101
  display: "none"
42462
43102
  }
42463
- }, i18n__namespace._("I don't understand that")));
43103
+ }, this.context.strings.dontUnderstand));
42464
43104
  }
42465
43105
  }
42466
43106
 
@@ -42512,9 +43152,12 @@ const VideoTranscriptLink = props => {
42512
43152
  // which is used by our GraphQL framework.
42513
43153
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'VideoKind'.
42514
43154
  const result = useVideo(id, kind);
43155
+ const {
43156
+ strings
43157
+ } = usePerseusI18n();
42515
43158
  switch (result.status) {
42516
43159
  case "loading":
42517
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, i18n__namespace._("Loading..."));
43160
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.loading);
42518
43161
  case "success":
42519
43162
  {
42520
43163
  const video = result.data?.video;
@@ -42526,14 +43169,14 @@ const VideoTranscriptLink = props => {
42526
43169
  href: "/transcript/" + (video?.contentId || "videoNotFound"),
42527
43170
  target: "_blank",
42528
43171
  className: "visited-no-recolor"
42529
- }, i18n__namespace._("See video transcript")));
43172
+ }, strings.videoTranscript));
42530
43173
  }
42531
43174
  case "error":
42532
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, i18n__namespace._("Something went wrong."));
43175
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
42533
43176
  case "aborted":
42534
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, i18n__namespace._("Something went wrong."));
43177
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
42535
43178
  default:
42536
- return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, i18n__namespace._("Something went wrong."));
43179
+ return /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, null, strings.somethingWrong);
42537
43180
  }
42538
43181
  };
42539
43182
  const styles$4 = aphrodite.StyleSheet.create({
@@ -42561,6 +43204,7 @@ const IS_VIMEO = /(vimeo\.com)/;
42561
43204
  * Video renderer.
42562
43205
  */
42563
43206
  class Video extends React__namespace.Component {
43207
+ static contextType = PerseusI18nContext;
42564
43208
  /**
42565
43209
  * This is the widget's grading function.
42566
43210
  * Points for videos are tallied by the embedded video itself, in the case
@@ -42628,7 +43272,7 @@ class Video extends React__namespace.Component {
42628
43272
  key: location + this.props.alignment
42629
43273
  }, /*#__PURE__*/React__namespace.createElement(wonderBlocksCore.View, {
42630
43274
  style: a11y.srOnly
42631
- }, i18n__namespace._("Khan Academy video wrapper")), /*#__PURE__*/React__namespace.createElement("iframe", {
43275
+ }, this.context.strings.videoWrapper), /*#__PURE__*/React__namespace.createElement("iframe", {
42632
43276
  // TODO(joshuan): Consider not using iframes when we're
42633
43277
  // loading this from webapp. This iframe is problematic
42634
43278
  // for screenreaders.
@@ -42692,7 +43336,7 @@ const init = function (options) {
42692
43336
 
42693
43337
  // This file is processed by a Rollup plugin (replace) to inject the production
42694
43338
  const libName = "@khanacademy/perseus";
42695
- const libVersion = "21.6.0";
43339
+ const libVersion = "22.0.1";
42696
43340
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
42697
43341
 
42698
43342
  const apiVersion = {
@@ -42711,6 +43355,8 @@ const ItemVersion = getVersionVector();
42711
43355
  ItemVersion["::renderer::"] = itemDataVersion;
42712
43356
 
42713
43357
  class ArticleRenderer extends React__namespace.Component {
43358
+ static contextType = PerseusI18nContext;
43359
+ _currentFocus;
42714
43360
  sectionRenderers = [];
42715
43361
  static defaultProps = {
42716
43362
  apiOptions: ApiOptions.defaults,
@@ -42866,8 +43512,9 @@ class ArticleRenderer extends React__namespace.Component {
42866
43512
  }
42867
43513
  },
42868
43514
  linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "article"),
42869
- legacyPerseusLint: this.props.legacyPerseusLint
42870
- }, this.props.dependencies)));
43515
+ legacyPerseusLint: this.props.legacyPerseusLint,
43516
+ strings: this.context.strings
43517
+ })));
42871
43518
  });
42872
43519
  return /*#__PURE__*/React__namespace.createElement("div", {
42873
43520
  className: classes
@@ -42879,6 +43526,7 @@ class ArticleRenderer extends React__namespace.Component {
42879
43526
 
42880
43527
  /* Renders just a hint preview */
42881
43528
  class HintRenderer extends React__namespace.Component {
43529
+ static contextType = PerseusI18nContext;
42882
43530
  static defaultProps = {
42883
43531
  linterContext: PerseusLinter__namespace.linterContextDefault
42884
43532
  };
@@ -42924,7 +43572,7 @@ class HintRenderer extends React__namespace.Component {
42924
43572
  tabIndex: "-1"
42925
43573
  }, !apiOptions.isMobile && /*#__PURE__*/React__namespace.createElement("span", {
42926
43574
  className: "perseus-sr-only"
42927
- }, i18n__namespace._("Hint #%(pos)s", {
43575
+ }, this.context.strings.hintPos({
42928
43576
  pos: pos + 1
42929
43577
  })), !apiOptions.isMobile && totalHints != null && pos != null && /*#__PURE__*/React__namespace.createElement("span", {
42930
43578
  className: "perseus-hint-label",
@@ -42941,7 +43589,8 @@ class HintRenderer extends React__namespace.Component {
42941
43589
  images: hint.images,
42942
43590
  apiOptions: rendererApiOptions,
42943
43591
  findExternalWidgets: this.props.findExternalWidgets,
42944
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hint")
43592
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hint"),
43593
+ strings: this.context.strings
42945
43594
  }))
42946
43595
  );
42947
43596
  }
@@ -42976,6 +43625,7 @@ const styles$3 = aphrodite.StyleSheet.create({
42976
43625
  // Use of UNSAFE_componentWillReceiveProps:
42977
43626
  // eslint-disable-next-line react/no-unsafe
42978
43627
  class HintsRenderer extends React__namespace.Component {
43628
+ static contextType = PerseusI18nContext;
42979
43629
  static defaultProps = {
42980
43630
  linterContext: PerseusLinter__namespace.linterContextDefault
42981
43631
  };
@@ -43128,7 +43778,7 @@ class HintsRenderer extends React__namespace.Component {
43128
43778
  className: classNames
43129
43779
  }, apiOptions.isMobile && hintsVisible > 0 && /*#__PURE__*/React__namespace.createElement("div", {
43130
43780
  className: aphrodite.css(styles$2.mobileHintStylesHintTitle, sharedStyles.responsiveLabel)
43131
- }, i18n__namespace._("Hints")), hints, showGetAnotherHint && /*#__PURE__*/React__namespace.createElement("button", {
43781
+ }, this.context.strings.hints), hints, showGetAnotherHint && /*#__PURE__*/React__namespace.createElement("button", {
43132
43782
  // @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>'.
43133
43783
  rel: "button",
43134
43784
  className: aphrodite.css(styles$2.linkButton, styles$2.getAnotherHintButton, apiOptions.isMobile && styles$2.mobileHintStylesGetAnotherHintButton),
@@ -43143,7 +43793,7 @@ class HintsRenderer extends React__namespace.Component {
43143
43793
  className: aphrodite.css(styles$2.plusText, apiOptions.isMobile && styles$2.mobileHintStylesPlusText)
43144
43794
  }, "+"), /*#__PURE__*/React__namespace.createElement("span", {
43145
43795
  className: aphrodite.css(styles$2.getAnotherHintText)
43146
- }, i18n__namespace._("Get another hint"), " ", hintRatioCopy)));
43796
+ }, this.context.strings.getAnotherHint, " ", hintRatioCopy)));
43147
43797
  }
43148
43798
  }
43149
43799
  const hintIndentation = baseUnitPx + hintBorderWidth;
@@ -43246,8 +43896,13 @@ const {
43246
43896
  } = objective_;
43247
43897
  /* eslint-disable-next-line react/no-unsafe */
43248
43898
  class ServerItemRenderer extends React__namespace.Component {
43899
+ static contextType = PerseusI18nContext;
43249
43900
  // @ts-expect-error - TS2564 - Property 'questionRenderer' has no initializer and is not definitely assigned in the constructor.
43250
-
43901
+ questionRenderer;
43902
+ hintsRenderer;
43903
+ _currentFocus;
43904
+ _fullyRendered;
43905
+ blurTimeoutID;
43251
43906
  static defaultProps = {
43252
43907
  apiOptions: {},
43253
43908
  // a deep default is done in `this.update()`
@@ -43539,14 +44194,16 @@ class ServerItemRenderer extends React__namespace.Component {
43539
44194
  content: this.props.item.question.content,
43540
44195
  widgets: this.props.item.question.widgets,
43541
44196
  images: this.props.item.question.images,
43542
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "question")
44197
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "question"),
44198
+ strings: this.context.strings
43543
44199
  }, this.props.dependencies)));
43544
44200
  const hintsRenderer = /*#__PURE__*/React__namespace.createElement(HintsRenderer, {
43545
44201
  hints: this.props.item.hints,
43546
44202
  hintsVisible: this.props.hintsVisible,
43547
44203
  apiOptions: apiOptions,
43548
44204
  ref: elem => this.hintsRenderer = elem,
43549
- linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hints")
44205
+ linterContext: PerseusLinter__namespace.pushContextStack(this.props.linterContext, "hints"),
44206
+ strings: this.context.strings
43550
44207
  });
43551
44208
  return /*#__PURE__*/React__namespace.createElement(DependenciesContext.Provider, {
43552
44209
  value: this.props.dependencies
@@ -43719,115 +44376,6 @@ var constants = /*#__PURE__*/Object.freeze({
43719
44376
  perseusFrameBorderWidth: perseusFrameBorderWidth
43720
44377
  });
43721
44378
 
43722
- // TODO(FEI-4010): Remove `Perseus` prefix for all types here
43723
- // TODO(FEI-4011): Use types generated by https://github.com/jaredly/generate-perseus-flowtypes
43724
-
43725
- // Range is replaced within this file with Interval, but it is used elsewhere
43726
- // and exported from the package, so we need to keep it around.
43727
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
43728
- const ItemExtras = [
43729
- // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
43730
- "calculator",
43731
- // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
43732
- "chi2Table",
43733
- // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
43734
- "financialCalculatorMonthlyPayment",
43735
- // The user might benefit from a total amount calculator. Provided on Khan Academy when true
43736
- "financialCalculatorTotalAmount",
43737
- // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
43738
- "financialCalculatorTimeToPayOff",
43739
- // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
43740
- "periodicTable",
43741
- // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
43742
- "periodicTableWithKey",
43743
- // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
43744
- "tTable",
43745
- // The user might benefit from using a statistics Z Table like https://www.ztable.net/
43746
- "zTable"];
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
- // prettier-ignore
43775
-
43776
- // prettier-ignore
43777
-
43778
- // prettier-ignore
43779
-
43780
- // prettier-ignore
43781
-
43782
- // prettier-ignore
43783
-
43784
- // prettier-ignore
43785
-
43786
- // prettier-ignore
43787
-
43788
- // prettier-ignore
43789
-
43790
- // prettier-ignore
43791
-
43792
- // prettier-ignore
43793
-
43794
- // prettier-ignore
43795
-
43796
- // prettier-ignore
43797
-
43798
- // prettier-ignore
43799
-
43800
- // prettier-ignore
43801
-
43802
- // prettier-ignore
43803
-
43804
- // prettier-ignore
43805
-
43806
- // prettier-ignore
43807
-
43808
- // prettier-ignore
43809
-
43810
- // prettier-ignore
43811
-
43812
- // prettier-ignore
43813
-
43814
- // prettier-ignore
43815
-
43816
- // prettier-ignore
43817
-
43818
- // prettier-ignore
43819
-
43820
- //prettier-ignore
43821
-
43822
- // A background image applied to various widgets.
43823
-
43824
- const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
43825
-
43826
- // 2D range: xMin, xMax, yMin, yMax
43827
-
43828
- const lockedFigureColors = ["purple", "blue", "teal", "green", "gold", "red", "pink"];
43829
- const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
43830
-
43831
44379
  /**
43832
44380
  * Traverses a {content, widgets, images} renderer props object,
43833
44381
  * such as `itemData.question`
@@ -44068,6 +44616,10 @@ var shapes = {
44068
44616
  * TreeMapperForLeavesAndCollections.
44069
44617
  */
44070
44618
  class TreeMapperJustForLeaves {
44619
+ content;
44620
+ hint;
44621
+ tags;
44622
+ array;
44071
44623
  constructor(content, hint, tags) {
44072
44624
  this.content = content;
44073
44625
  this.hint = hint;
@@ -44098,6 +44650,10 @@ class TreeMapperJustForLeaves {
44098
44650
  * ContentMapper and HintMapper are now locked in.
44099
44651
  */
44100
44652
  class TreeMapperForLeavesAndCollections {
44653
+ content;
44654
+ hint;
44655
+ tags;
44656
+ array;
44101
44657
  constructor(content, hint, tags, array) {
44102
44658
  this.content = content;
44103
44659
  this.hint = hint;
@@ -44356,6 +44912,9 @@ function treeToItem(node) {
44356
44912
  // @ts-expect-error - TS2315 - Type 'Tree' is not generic.
44357
44913
  // @ts-expect-error - TS2315 - Type 'Tree' is not generic.
44358
44914
  class MultiRenderer extends React__namespace.Component {
44915
+ static contextType = PerseusI18nContext;
44916
+ rendererDataTreeMapper;
44917
+ getRenderersMapper;
44359
44918
  constructor(props) {
44360
44919
  super(props);
44361
44920
  this.rendererDataTreeMapper = buildMapper()
@@ -44430,7 +44989,10 @@ class MultiRenderer extends React__namespace.Component {
44430
44989
  onSerializedStateUpdated: _____,
44431
44990
  ...otherProps
44432
44991
  } = this.props;
44433
- return otherProps;
44992
+ return {
44993
+ ...otherProps,
44994
+ strings: this.context.strings
44995
+ };
44434
44996
  }
44435
44997
 
44436
44998
  /**
@@ -44679,7 +45241,7 @@ class MultiRenderer extends React__namespace.Component {
44679
45241
  if (this.state.renderError) {
44680
45242
  return /*#__PURE__*/React__namespace.createElement("div", {
44681
45243
  className: aphrodite.css(styles.error)
44682
- }, i18n__namespace.$_("Error rendering: %(error)s", {
45244
+ }, this.context.strings.errorRendering({
44683
45245
  error: String(this.state.renderError)
44684
45246
  }));
44685
45247
  }
@@ -45821,6 +46383,8 @@ exports.MultiItems = MultiItems;
45821
46383
  exports.NumericInput = NumericInput$1;
45822
46384
  exports.PerseusError = PerseusError;
45823
46385
  exports.PerseusExpressionAnswerFormConsidered = PerseusExpressionAnswerFormConsidered;
46386
+ exports.PerseusI18nContext = PerseusI18nContext;
46387
+ exports.PerseusI18nContextProvider = PerseusI18nContextProvider;
45824
46388
  exports.PerseusMarkdown = PerseusMarkdown;
45825
46389
  exports.PlotterWidget = Plotter$1;
45826
46390
  exports.QUESTION_WIDGETS = QUESTION_WIDGETS;
@@ -45881,6 +46445,7 @@ exports.registerAllWidgetsForTesting = registerAllWidgetsForTesting;
45881
46445
  exports.shapes = shapes;
45882
46446
  exports.shouldHaveIndividualAnswer = shouldHaveIndividualAnswer;
45883
46447
  exports.traverse = traverse;
46448
+ exports.usePerseusI18n = usePerseusI18n;
45884
46449
  exports.violatingWidgets = violatingWidgets;
45885
46450
  exports.widgets = allWidgets;
45886
46451
  //# sourceMappingURL=index.js.map