@khanacademy/perseus 21.6.0 → 22.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) 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 +863 -485
  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 +1083 -523
  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/interactive-graph-question-builder.d.ts +21 -0
  39. package/dist/widgets/interactive-graphs/locked-point.d.ts +1 -1
  40. package/dist/widgets/interactive-graphs/mafs-graph.d.ts +1 -0
  41. package/dist/widgets/interactive-graphs/reducer/use-graph-config.d.ts +1 -0
  42. package/dist/widgets/label-image/marker.d.ts +6 -0
  43. package/dist/widgets/label-image.d.ts +6 -0
  44. package/dist/widgets/matcher.d.ts +6 -0
  45. package/dist/widgets/matrix.d.ts +9 -1
  46. package/dist/widgets/molecule.d.ts +6 -0
  47. package/dist/widgets/number-line.d.ts +6 -0
  48. package/dist/widgets/numeric-input.d.ts +8 -1
  49. package/dist/widgets/passage-ref-target.d.ts +6 -0
  50. package/dist/widgets/passage-ref.d.ts +6 -0
  51. package/dist/widgets/passage.d.ts +6 -0
  52. package/dist/widgets/plotter.d.ts +6 -0
  53. package/dist/widgets/radio/choice-a11y-text.d.ts +2 -1
  54. package/dist/widgets/radio/radio.d.ts +8 -1
  55. package/dist/widgets/radio/util.d.ts +2 -1
  56. package/dist/widgets/table.d.ts +7 -0
  57. package/dist/widgets/unit.d.ts +8 -1
  58. package/dist/widgets/video.d.ts +6 -0
  59. package/dist/widgets.d.ts +2 -1
  60. package/package.json +18 -6
  61. package/dist/widgets/label-image/strings.d.ts +0 -3
package/dist/es/index.js CHANGED
@@ -1,13 +1,12 @@
1
1
  import * as KAS from '@khanacademy/kas';
2
- import { keyTranslator, CursorContext, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
2
+ import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
3
  import * as PerseusLinter from '@khanacademy/perseus-linter';
4
4
  import { linterContextDefault, Rule, linterContextProps } from '@khanacademy/perseus-linter';
5
5
  import { View, RenderStateRoot, useUniqueIdWithMock, UniqueIDProvider, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
6
- import * as i18n from '@khanacademy/wonder-blocks-i18n';
7
6
  import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
8
7
  import classNames from 'classnames';
9
8
  import * as React from 'react';
10
- import React__default, { useState, useEffect, useRef, createContext, useImperativeHandle } from 'react';
9
+ import React__default, { useContext, useState, useEffect, useRef, createContext, useImperativeHandle } from 'react';
11
10
  import _, { debounce as debounce$1 } from 'underscore';
12
11
  import Clickable from '@khanacademy/wonder-blocks-clickable';
13
12
  import { Popover, PopoverContentCore, PopoverContent } from '@khanacademy/wonder-blocks-popover';
@@ -69,6 +68,211 @@ function _objectWithoutPropertiesLoose(source, excluded) {
69
68
  return target;
70
69
  }
71
70
 
71
+ /**
72
+ * The translated strings that are used to render Perseus.
73
+ */
74
+
75
+ /**
76
+ * Mock strings for the Perseus package, to be used for tests and Storybook.
77
+ */
78
+ const mockStrings = {
79
+ closeKeypad: "close math keypad",
80
+ openKeypad: "open math keypad",
81
+ removeHighlight: "Remove highlight",
82
+ addHighlight: "Add highlight",
83
+ hintPos: ({
84
+ pos
85
+ }) => `Hint #${pos}`,
86
+ errorRendering: ({
87
+ error
88
+ }) => `Error rendering: ${error}`,
89
+ 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>",
90
+ EXTRA_SYMBOLS_ERROR: "We could not understand your " + "answer. Please check your answer for extra " + "text or symbols.",
91
+ NEEDS_TO_BE_SIMPLFIED_ERROR: "Your answer is almost correct, " + "but it needs to be simplified.",
92
+ MISSING_PERCENT_ERROR: "Your answer is almost correct, " + "but it is missing a " + "<code>\\%</code> at the end.",
93
+ MULTIPLICATION_SIGN_ERROR: "I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign.",
94
+ WRONG_CASE_ERROR: "Your answer includes use of a variable with the wrong case.",
95
+ WRONG_LETTER_ERROR: "Your answer includes a wrong variable letter.",
96
+ invalidSelection: "Make sure you select something for every row.",
97
+ ERROR_TITLE: "Oops!",
98
+ ERROR_MESSAGE: "Sorry, I don't understand that!",
99
+ hints: "Hints",
100
+ getAnotherHint: "Get another hint",
101
+ deprecatedStandin: "Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",
102
+ keepTrying: "Keep trying",
103
+ tryAgain: "Try again",
104
+ check: "Check",
105
+ correctExcited: "Correct!",
106
+ nextQuestion: "Next question",
107
+ skipToTitle: ({
108
+ title
109
+ }) => `Skip to ${title}`,
110
+ current: "Current",
111
+ correct: "Correct",
112
+ correctSelected: "Correct (selected)",
113
+ correctCrossedOut: "Correct (but you crossed it out)",
114
+ incorrect: "Incorrect",
115
+ incorrectSelected: "Incorrect (selected)",
116
+ hideExplanation: "Hide explanation",
117
+ explain: "Explain",
118
+ INVALID_MESSAGE_PREFIX: "We couldn't grade your answer.",
119
+ DEFAULT_INVALID_MESSAGE_1: "It looks like you left something blank or ",
120
+ DEFAULT_INVALID_MESSAGE_2: "entered in an invalid answer.",
121
+ integerExample: "an integer, like $6$",
122
+ properExample: "a *proper* fraction, like $1/2$ or $6/10$",
123
+ simplifiedProperExample: "a *simplified proper* fraction, like $3/5$",
124
+ improperExample: "an *improper* fraction, like $10/7$ or $14/8$",
125
+ simplifiedImproperExample: "a *simplified improper* fraction, like $7/4$",
126
+ mixedExample: "a mixed number, like $1\\ 3/4$",
127
+ decimalExample: "an *exact* decimal, like $0.75$",
128
+ percentExample: "a percent, like $12.34\\%$",
129
+ piExample: "a multiple of pi, like $12\\ \\text{pi}$ or $2/3\\ \\text{pi}$",
130
+ yourAnswer: "**Your answer should be** ",
131
+ yourAnswerLabel: "Your answer:",
132
+ addPoints: "Click to add points",
133
+ addVertices: "Click to add vertices",
134
+ tapMultiple: "Tap each dot on the image to select all answers that apply.",
135
+ tapSingle: "Tap each dot on the image to select an answer.",
136
+ clickMultiple: "Click each dot on the image to select all answers that apply.",
137
+ clickSingle: "Click each dot on the image to select an answer.",
138
+ choices: "Choices:",
139
+ answers: ({
140
+ num
141
+ }) => num === 1 ? `${num} answer` : `${num} answers`,
142
+ hideAnswersToggleLabel: "Hide answer choices",
143
+ moves: ({
144
+ num
145
+ }) => num === 1 ? `Moves: ${num}` : `Moves: ${num}`,
146
+ clickTiles: "Click on the tiles to change the lights.",
147
+ turnOffLights: "You must turn on all of the lights to continue.",
148
+ fillAllCells: "Make sure you fill in all cells in the matrix.",
149
+ molecularDrawing: ({
150
+ content
151
+ }) => `A molecular structure drawing. SMILES notation: ${content}`,
152
+ switchDirection: "Switch direction",
153
+ circleOpen: "Make circle open",
154
+ circleFilled: "Make circle filled",
155
+ numDivisions: "Number of divisions:",
156
+ divisions: ({
157
+ divRangeString
158
+ }) => `Please make sure the number of divisions is in the range ${divRangeString}.`,
159
+ lineRange: ({
160
+ lineRange
161
+ }) => `lines ${lineRange}`,
162
+ lineNumber: ({
163
+ lineNumber
164
+ }) => `line ${lineNumber}`,
165
+ symbolPassage: ({
166
+ questionSymbol,
167
+ questionNumber
168
+ }) => `The symbol ${questionSymbol} indicates that question ${questionNumber} references this portion of the passage.`,
169
+ symbolQuestion: ({
170
+ sentenceSymbol
171
+ }) => ` The symbol ${sentenceSymbol} indicates that the following sentence is referenced in a question.`,
172
+ lineLabel: "Line",
173
+ beginningPassage: "Beginning of reading passage.",
174
+ beginningFootnotes: "Beginning of reading passage footnotes.",
175
+ endPassage: "End of reading passage.",
176
+ questionMarker: ({
177
+ number
178
+ }) => `[Marker for question ${number}]`,
179
+ circleMarker: ({
180
+ number
181
+ }) => `[Circle marker ${number}]`,
182
+ sentenceMarker: ({
183
+ number
184
+ }) => `[Sentence ${number}]`,
185
+ dragHandles: "Drag handles to make graph",
186
+ tapAddPoints: "Tap to add points",
187
+ false: "False",
188
+ true: "True",
189
+ no: "No",
190
+ yes: "Yes",
191
+ chooseCorrectNum: "Please choose the correct number of answers.",
192
+ notNoneOfTheAbove: "'None of the above' may not be selected when other answers are selected.",
193
+ noneOfTheAbove: "None of the above",
194
+ chooseNumAnswers: ({
195
+ numCorrect
196
+ }) => `Choose ${numCorrect} answers:`,
197
+ chooseAllAnswers: "Choose all answers that apply:",
198
+ chooseOneAnswer: "Choose 1 answer:",
199
+ choiceCheckedCorrect: ({
200
+ letter
201
+ }) => `(Choice ${letter}, Checked, Correct)`,
202
+ choiceCrossedOutCorrect: ({
203
+ letter
204
+ }) => `(Choice ${letter}, Crossed out, Correct)`,
205
+ choiceCorrect: ({
206
+ letter
207
+ }) => `(Choice ${letter}, Correct)`,
208
+ choiceCheckedIncorrect: ({
209
+ letter
210
+ }) => `(Choice ${letter}, Checked, Incorrect)`,
211
+ choiceCrossedOutIncorrect: ({
212
+ letter
213
+ }) => `(Choice ${letter}, Crossed out, Incorrect)`,
214
+ choiceIncorrect: ({
215
+ letter
216
+ }) => `(Choice ${letter}, Incorrect)`,
217
+ choiceChecked: ({
218
+ letter
219
+ }) => `(Choice ${letter}, Checked)`,
220
+ choiceCrossedOut: ({
221
+ letter
222
+ }) => `(Choice ${letter}, Crossed out)`,
223
+ choice: ({
224
+ letter
225
+ }) => `(Choice ${letter})`,
226
+ crossOut: "Cross out",
227
+ crossOutOption: "Cross out option",
228
+ crossOutChoice: ({
229
+ letter
230
+ }) => `Cross out Choice ${letter}`,
231
+ bringBack: "Bring back",
232
+ openMenuForChoice: ({
233
+ letter
234
+ }) => `Open menu for Choice ${letter}`,
235
+ 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",
236
+ rightArrow: "Reaction arrow pointing to the right.",
237
+ dontUnderstandUnits: "I couldn't understand those units.",
238
+ checkSigFigs: "Check your significant figures.",
239
+ answerNumericallyIncorrect: "That answer is numerically incorrect.",
240
+ checkUnits: "Check your units.",
241
+ dontUnderstand: "I don't understand that",
242
+ loading: "Loading...",
243
+ videoTranscript: "See video transcript",
244
+ somethingWrong: "Something went wrong.",
245
+ videoWrapper: "Khan Academy video wrapper"
246
+ };
247
+
248
+ /**
249
+ * PerseusI18nContext provides a way to set the strings and locale that are
250
+ * used inside the Perseus package.
251
+ *
252
+ */
253
+ // @ts-expect-error - TS2322 - Type 'Context<{ strings: {}; locale: string; }>' is not assignable to type 'Context<I18nContextType>'.
254
+ const PerseusI18nContext = /*#__PURE__*/React.createContext(process.env.NODE_ENV === "test" || process.env.STORYBOOK ? {
255
+ strings: mockStrings,
256
+ locale: "en"
257
+ } :
258
+ // We want to return null here, not an empty object, so that we
259
+ // are will throw an error when attempting to access the
260
+ // undefined locale or strings, making it easier to debug.
261
+ null);
262
+ function PerseusI18nContextProvider({
263
+ children,
264
+ strings,
265
+ locale
266
+ }) {
267
+ return /*#__PURE__*/React.createElement(PerseusI18nContext.Provider, {
268
+ value: {
269
+ strings,
270
+ locale
271
+ }
272
+ }, children);
273
+ }
274
+ const usePerseusI18n = () => useContext(PerseusI18nContext);
275
+
72
276
  const debounce = (func, delay) => {
73
277
  let timer = null;
74
278
  return (...args) => {
@@ -81,21 +285,8 @@ const debounce = (func, delay) => {
81
285
  };
82
286
  };
83
287
 
84
- const customKeyTranslator = _extends({}, keyTranslator, {
85
- // If there's something in the input that can become part of a
86
- // fraction, typing "/" puts it in the numerator. If not, typing
87
- // "/" does nothing. In that case, enter a \frac.
88
- FRAC: mathQuill => {
89
- const contents = mathQuill.latex();
90
- mathQuill.typedText("/");
91
- if (mathQuill.latex() === contents) {
92
- mathQuill.cmd("\\frac");
93
- }
94
- }
95
- });
96
-
97
288
  // A WYSIWYG math input that calls `onChange(LaTeX-string)`
98
- class MathInput extends React.Component {
289
+ class InnerMathInput extends React.Component {
99
290
  constructor(...args) {
100
291
  super(...args);
101
292
  // @ts-expect-error - TS2564 - Property 'mouseDown' has no initializer and is not definitely assigned in the constructor.
@@ -109,6 +300,21 @@ class MathInput extends React.Component {
109
300
  };
110
301
  this.insert = value => {
111
302
  const input = this.mathField();
303
+ const {
304
+ locale
305
+ } = this.context;
306
+ const customKeyTranslator = _extends({}, getKeyTranslator(locale), {
307
+ // If there's something in the input that can become part of a
308
+ // fraction, typing "/" puts it in the numerator. If not, typing
309
+ // "/" does nothing. In that case, enter a \frac.
310
+ FRAC: mathQuill => {
311
+ const contents = mathQuill.latex();
312
+ mathQuill.typedText("/");
313
+ if (mathQuill.latex() === contents) {
314
+ mathQuill.cmd("\\frac");
315
+ }
316
+ }
317
+ });
112
318
  const inputModifier = customKeyTranslator[value];
113
319
  if (inputModifier) {
114
320
  inputModifier(input, value);
@@ -131,8 +337,11 @@ class MathInput extends React.Component {
131
337
  };
132
338
  this.mathField = () => {
133
339
  if (!this.__mathField && this.__mathFieldWrapperRef) {
340
+ const {
341
+ locale
342
+ } = this.context;
134
343
  // Initialize MathQuill.MathField instance
135
- this.__mathField = createMathField(this.__mathFieldWrapperRef, baseConfig => _extends({}, baseConfig, {
344
+ this.__mathField = createMathField(this.__mathFieldWrapperRef, this.props.mathInputStrings, baseConfig => _extends({}, baseConfig, {
136
345
  handlers: {
137
346
  edit: debounce(mathField => {
138
347
  // This handler is guaranteed to be called on change, but
@@ -148,7 +357,7 @@ class MathInput extends React.Component {
148
357
  // Use the specified symbol to represent multiplication
149
358
  // TODO(alex): Add an option to disallow variables, in
150
359
  // which case 'x' should get converted to '\\times'
151
- if (convertDotToTimesByLocale(this.props.convertDotToTimes)) {
360
+ if (convertDotToTimesByLocale(locale, this.props.convertDotToTimes)) {
152
361
  value = value.replace(/\\cdot/g, "\\times");
153
362
 
154
363
  // Preserve cursor position in the common case:
@@ -208,7 +417,10 @@ class MathInput extends React.Component {
208
417
  focused: false
209
418
  });
210
419
  this.handleKeypadPress = (key, e) => {
211
- const translator = keyTranslator[key];
420
+ const {
421
+ locale
422
+ } = this.context;
423
+ const translator = getKeyTranslator(locale)[key];
212
424
  const mathField = this.mathField();
213
425
  if (mathField) {
214
426
  if (translator) {
@@ -307,7 +519,7 @@ class MathInput extends React.Component {
307
519
  focused: false,
308
520
  active: false
309
521
  }) : /*#__PURE__*/React.createElement(Clickable, {
310
- "aria-label": this.state.keypadOpen ? i18n._("close math keypad") : i18n._("open math keypad"),
522
+ "aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
311
523
  "aria-checked": this.state.keypadOpen,
312
524
  role: "switch",
313
525
  onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
@@ -316,10 +528,36 @@ class MathInput extends React.Component {
316
528
  }, props))))));
317
529
  }
318
530
  }
319
- MathInput.defaultProps = {
531
+
532
+ // We need to have two contexts (one for Perseus and one for MathInput), so we
533
+ // add a wrapper around the MathInput component to provide the MathInput context
534
+ // to it.
535
+ InnerMathInput.contextType = PerseusI18nContext;
536
+ InnerMathInput.defaultProps = {
320
537
  value: "",
321
538
  convertDotToTimes: false
322
539
  };
540
+ class MathInput extends React.Component {
541
+ constructor(...args) {
542
+ super(...args);
543
+ this.inputRef = /*#__PURE__*/React.createRef();
544
+ }
545
+ focus() {
546
+ var _this$inputRef$curren;
547
+ (_this$inputRef$curren = this.inputRef.current) == null || _this$inputRef$curren.focus();
548
+ }
549
+ insert(value) {
550
+ var _this$inputRef$curren2;
551
+ (_this$inputRef$curren2 = this.inputRef.current) == null || _this$inputRef$curren2.insert(value);
552
+ }
553
+ render() {
554
+ return /*#__PURE__*/React.createElement(InnerMathInput, _extends({}, this.props, {
555
+ ref: this.inputRef,
556
+ mathInputStrings: this.context.strings
557
+ }));
558
+ }
559
+ }
560
+ MathInput.contextType = MathInputI18nContext;
323
561
  const MathInputIcon = ({
324
562
  hovered,
325
563
  focused,
@@ -1235,16 +1473,6 @@ const MAXERROR_EPSILON = Math.pow(2, -42);
1235
1473
  // TOOD(kevinb): Figure out how this relates to KEScore in
1236
1474
  // perseus-all-package/types.js and see if there's a way to
1237
1475
  // unify these types.
1238
- const errors = {
1239
- APPROXIMATED_PI_ERROR: i18n._("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>"),
1240
- EXTRA_SYMBOLS_ERROR: i18n._("We could not understand your " + "answer. Please check your answer for extra " + "text or symbols."),
1241
- NEEDS_TO_BE_SIMPLFIED_ERROR: i18n._("Your answer is almost correct, " + "but it needs to be simplified."),
1242
- MISSING_PERCENT_ERROR: i18n._("Your answer is almost correct, " + "but it is missing a " + "<code>\\%</code> at the end."),
1243
- MULTIPLICATION_SIGN_ERROR: i18n._("I'm a computer. I only understand " + "multiplication if you use an asterisk " + "(*) as the multiplication sign."),
1244
- WRONG_CASE_ERROR: i18n._("Your answer includes use of a variable with the wrong case."),
1245
- WRONG_LETTER_ERROR: i18n._("Your answer includes a wrong variable letter.")
1246
- };
1247
-
1248
1476
  /*
1249
1477
  * Answer types
1250
1478
  *
@@ -1317,7 +1545,7 @@ const KhanAnswerTypes = {
1317
1545
  */
1318
1546
  predicate: {
1319
1547
  defaultForms: "integer, proper, improper, mixed, decimal",
1320
- createValidatorFunctional: function (predicate, options) {
1548
+ createValidatorFunctional: function (predicate, options, strings) {
1321
1549
  // Extract the options from the given solution object
1322
1550
  options = _.extend({
1323
1551
  simplify: "required",
@@ -1705,12 +1933,12 @@ const KhanAnswerTypes = {
1705
1933
  } else if (form === "percent") {
1706
1934
  // Otherwise, an error was returned
1707
1935
  score.empty = true;
1708
- score.message = errors.MISSING_PERCENT_ERROR;
1936
+ score.message = strings.MISSING_PERCENT_ERROR;
1709
1937
  } else {
1710
1938
  if (options.simplify !== "enforced") {
1711
1939
  score.empty = true;
1712
1940
  }
1713
- score.message = errors.NEEDS_TO_BE_SIMPLFIED_ERROR;
1941
+ score.message = strings.NEEDS_TO_BE_SIMPLFIED_ERROR;
1714
1942
  }
1715
1943
  // The return false below stops the looping of the
1716
1944
  // callback since predicate check succeeded.
@@ -1719,7 +1947,7 @@ const KhanAnswerTypes = {
1719
1947
  }
1720
1948
  if (piApprox && predicate(val, Math.abs(val * 0.001))) {
1721
1949
  score.empty = true;
1722
- score.message = errors.APPROXIMATED_PI_ERROR;
1950
+ score.message = strings.APPROXIMATED_PI_ERROR;
1723
1951
  }
1724
1952
  }
1725
1953
  });
@@ -1735,7 +1963,7 @@ const KhanAnswerTypes = {
1735
1963
  });
1736
1964
  if (!interpretedGuess) {
1737
1965
  score.empty = true;
1738
- score.message = errors.EXTRA_SYMBOLS_ERROR;
1966
+ score.message = strings.EXTRA_SYMBOLS_ERROR;
1739
1967
  return score;
1740
1968
  }
1741
1969
  }
@@ -1758,10 +1986,10 @@ const KhanAnswerTypes = {
1758
1986
  type: "predicate"
1759
1987
  })];
1760
1988
  },
1761
- createValidatorFunctional: function (correctAnswer, options) {
1989
+ createValidatorFunctional: function (correctAnswer, options, strings) {
1762
1990
  return KhanAnswerTypes.predicate.createValidatorFunctional(
1763
1991
  // @ts-expect-error - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
1764
- ...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options));
1992
+ ...KhanAnswerTypes.number.convertToPredicate(correctAnswer, options), strings);
1765
1993
  }
1766
1994
  },
1767
1995
  /*
@@ -1826,7 +2054,7 @@ const KhanAnswerTypes = {
1826
2054
  }
1827
2055
  return solution;
1828
2056
  },
1829
- createValidatorFunctional: function (solution, options) {
2057
+ createValidatorFunctional: function (solution, options, strings) {
1830
2058
  return function (guess) {
1831
2059
  const score = {
1832
2060
  empty: false,
@@ -1877,7 +2105,7 @@ const KhanAnswerTypes = {
1877
2105
  // @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
1878
2106
  score.ungraded = true;
1879
2107
  // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
1880
- score.message = result.wrongVariableCase ? errors.WRONG_CASE_ERROR : errors.WRONG_LETTER_ERROR;
2108
+ score.message = result.wrongVariableCase ? strings.WRONG_CASE_ERROR : strings.WRONG_LETTER_ERROR;
1881
2109
  // Don't tell the use they're "almost there" in this case, that may not be true and isn't helpful.
1882
2110
  // @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; }'.
1883
2111
  score.suppressAlmostThere = true;
@@ -1903,7 +2131,7 @@ const KhanAnswerTypes = {
1903
2131
  // @ts-expect-error - TS2540 - Cannot assign to 'ungraded' because it is a read-only property.
1904
2132
  score.ungraded = true;
1905
2133
  // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
1906
- score.message = errors.MULTIPLICATION_SIGN_ERROR;
2134
+ score.message = strings.MULTIPLICATION_SIGN_ERROR;
1907
2135
  } else if (resultX.message) {
1908
2136
  // TODO(aasmund): I18nize `score.message`
1909
2137
  // @ts-expect-error - TS2540 - Cannot assign to 'message' because it is a read-only property.
@@ -1918,9 +2146,6 @@ const KhanAnswerTypes = {
1918
2146
  };
1919
2147
 
1920
2148
  const _excluded$5 = ["scoring"];
1921
- const ERROR_TITLE = i18n._("Oops!");
1922
- const ERROR_MESSAGE = i18n._("Sorry, I don't understand that!");
1923
-
1924
2149
  // Map of international operator names to their English equivalents
1925
2150
  const englishOperators = {
1926
2151
  arctg: "arctan",
@@ -1950,6 +2175,32 @@ const deriveKeypadVersion = apiOptions => {
1950
2175
  // can remove this `virtualKeypadVersion` field entirely.
1951
2176
  return apiOptions.nativeKeypadProxy != null ? "REACT_NATIVE_KEYPAD" : "MATH_INPUT_KEYPAD_V2";
1952
2177
  };
2178
+ /**
2179
+ * Get the character used for separating decimals.
2180
+ */
2181
+ const getDecimalSeparator = locale => {
2182
+ var _match$;
2183
+ switch (locale) {
2184
+ // TODO(somewhatabstract): Remove this when Chrome supports the `ka`
2185
+ // locale properly.
2186
+ // https://github.com/formatjs/formatjs/issues/1526#issuecomment-559891201
2187
+ //
2188
+ // Supported locales in Chrome:
2189
+ // https://source.chromium.org/chromium/chromium/src/+/master:third_party/icu/scripts/chrome_ui_languages.list
2190
+ case "ka":
2191
+ return ",";
2192
+ default:
2193
+ const numberWithDecimalSeparator = 1.1;
2194
+ // TODO(FEI-3647): Update to use .formatToParts() once we no longer have to
2195
+ // support Safari 12.
2196
+ const match = new Intl.NumberFormat(locale).format(numberWithDecimalSeparator)
2197
+ // 0x661 is ARABIC-INDIC DIGIT ONE
2198
+ // 0x6F1 is EXTENDED ARABIC-INDIC DIGIT ONE
2199
+ .match(/[^\d\u0661\u06F1]/);
2200
+ return (_match$ = match == null ? void 0 : match[0]) != null ? _match$ : ".";
2201
+ }
2202
+ };
2203
+
1953
2204
  // The new, MathQuill input expression widget
1954
2205
  class Expression extends React.Component {
1955
2206
  constructor(..._args) {
@@ -1982,7 +2233,7 @@ class Expression extends React.Component {
1982
2233
  if (!this.parse(this.props.value, this.props).parsed) {
1983
2234
  const apiResult = this.props.apiOptions.onInputError(null,
1984
2235
  // reserved for some widget identifier
1985
- this.props.value, ERROR_MESSAGE);
2236
+ this.props.value, this.context.strings.ERROR_TITLE);
1986
2237
  if (apiResult !== false) {
1987
2238
  this.setState({
1988
2239
  invalid: true
@@ -2006,7 +2257,7 @@ class Expression extends React.Component {
2006
2257
  scoring
2007
2258
  } = _ref,
2008
2259
  rubric = _objectWithoutPropertiesLoose(_ref, _excluded$5);
2009
- const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {});
2260
+ const score = Expression.validate(this.getUserInput(), rubric, onInputError || function () {}, this.context.strings, this.context.locale);
2010
2261
 
2011
2262
  // "scoring" is a flag that indicates when we are checking answers.
2012
2263
  // otherwise, we may just be checking validity after changes.
@@ -2033,7 +2284,7 @@ class Expression extends React.Component {
2033
2284
  // make it so that solution answers with ','s or '.'s work
2034
2285
  const options = _.pick(props || this.props, "functions");
2035
2286
  _.extend(options, {
2036
- decimal_separator: i18n.getDecimalSeparator()
2287
+ decimal_separator: getDecimalSeparator(this.context.locale)
2037
2288
  });
2038
2289
  return KAS.parse(normalizeTex(value), options);
2039
2290
  };
@@ -2111,10 +2362,10 @@ class Expression extends React.Component {
2111
2362
  */
2112
2363
  static validate(userInput, rubric,
2113
2364
  // @ts-expect-error - TS2322 - Type '() => void' is not assignable to type 'OnInputErrorFunctionType'.
2114
- onInputError = function () {}) {
2365
+ onInputError = function () {}, strings, locale) {
2115
2366
  const options = _.clone(rubric);
2116
2367
  _.extend(options, {
2117
- decimal_separator: i18n.getDecimalSeparator()
2368
+ decimal_separator: getDecimalSeparator(locale)
2118
2369
  });
2119
2370
  const createValidator = answer => {
2120
2371
  // We give options to KAS.parse here because it is parsing the
@@ -2137,7 +2388,7 @@ class Expression extends React.Component {
2137
2388
  return KhanAnswerTypes.expression.createValidatorFunctional(expression.expr, _({}).extend(options, {
2138
2389
  simplify: answer.simplify,
2139
2390
  form: answer.form
2140
- }));
2391
+ }), strings);
2141
2392
  };
2142
2393
 
2143
2394
  // Find the first answer form that matches the user's input and that
@@ -2280,6 +2531,10 @@ class Expression extends React.Component {
2280
2531
  "perseus-widget-expression": true,
2281
2532
  "show-error-tooltip": this.state.showErrorTooltip
2282
2533
  });
2534
+ const {
2535
+ ERROR_MESSAGE,
2536
+ ERROR_TITLE
2537
+ } = this.context.strings;
2283
2538
  return /*#__PURE__*/React.createElement("div", {
2284
2539
  className: className,
2285
2540
  onBlur: () => this.state.invalid && this.setState({
@@ -2328,6 +2583,7 @@ class Expression extends React.Component {
2328
2583
  * to be included as keys on the keypad. These are scraped from the answer
2329
2584
  * forms.
2330
2585
  */
2586
+ Expression.contextType = PerseusI18nContext;
2331
2587
  Expression.defaultProps = {
2332
2588
  value: "",
2333
2589
  times: false,
@@ -2715,7 +2971,7 @@ function keScoreFromPerseusScore(score, guess, state) {
2715
2971
  * Return the first valid interpretation of 'text' as a number, in the form
2716
2972
  * {value: 2.3, exact: true}.
2717
2973
  */
2718
- function firstNumericalParse$1(text) {
2974
+ function firstNumericalParse$1(text, strings) {
2719
2975
  // TODO(alpert): This is sort of hacky...
2720
2976
  let first;
2721
2977
  const val = KhanAnswerTypes.predicate.createValidatorFunctional(function (ans) {
@@ -2725,7 +2981,7 @@ function firstNumericalParse$1(text) {
2725
2981
  simplify: "optional",
2726
2982
  inexact: true,
2727
2983
  forms: "integer, proper, improper, pi, log, mixed, decimal"
2728
- });
2984
+ }, strings);
2729
2985
  val(text);
2730
2986
  return first;
2731
2987
  }
@@ -10277,8 +10533,6 @@ const getClipPoint = function getClipPoint(graph, coord, angle) {
10277
10533
  class WrappedDrawing {
10278
10534
  constructor(graphie, initialPoint) {
10279
10535
  this.graphie = void 0;
10280
- this.wrapper = void 0;
10281
- this.visibleShape = void 0;
10282
10536
  this.initialPoint = void 0;
10283
10537
  this.graphie = graphie;
10284
10538
  this.initialPoint = initialPoint;
@@ -18312,7 +18566,7 @@ const rules$1 = _extends({}, pureMarkdownRules, {
18312
18566
  // just a stub for testing.
18313
18567
  return /*#__PURE__*/React.createElement("em", {
18314
18568
  key: state.key
18315
- }, i18n.doNotTranslate("[Widget: ", node.id, "]"));
18569
+ }, `[Widget: ${node.id}]`);
18316
18570
  }
18317
18571
  }),
18318
18572
  blockMath: _extends({}, pureMarkdownRules.blockMath, {
@@ -18785,6 +19039,7 @@ const replaceDeprecatedEditors = () => {
18785
19039
  replaceEditor("simulator", "deprecated-standin");
18786
19040
  };
18787
19041
  const getWidget = name => {
19042
+ var _widgets$name;
18788
19043
  // TODO(alex): Consider referring to these as renderers to avoid
18789
19044
  // overloading "widget"
18790
19045
  if (!_.has(widgets, name)) {
@@ -18792,9 +19047,9 @@ const getWidget = name => {
18792
19047
  }
18793
19048
 
18794
19049
  // Allow widgets to specify a widget directly or via a function
18795
- if (widgets[name].getWidget) {
18796
- // @ts-expect-error - TS2722 - Cannot invoke an object which is possibly 'undefined'.
18797
- return widgets[name].getWidget();
19050
+ if ((_widgets$name = widgets[name]) != null && _widgets$name.getWidget) {
19051
+ var _widgets$name$getWidg, _widgets$name2;
19052
+ return (_widgets$name$getWidg = (_widgets$name2 = widgets[name]).getWidget) == null ? void 0 : _widgets$name$getWidg.call(_widgets$name2);
18798
19053
  }
18799
19054
  return widgets[name].widget;
18800
19055
  };
@@ -18943,7 +19198,7 @@ const upgradeWidgetInfoToLatestVersion = oldWidgetInfo => {
18943
19198
  options: newEditorProps
18944
19199
  });
18945
19200
  };
18946
- const getRendererPropsForWidgetInfo = (widgetInfo, problemNum) => {
19201
+ const getRendererPropsForWidgetInfo = (widgetInfo, strings, problemNum) => {
18947
19202
  const type = widgetInfo.type;
18948
19203
  const widgetExports = widgets[type];
18949
19204
  if (widgetExports == null) {
@@ -18964,7 +19219,7 @@ const getRendererPropsForWidgetInfo = (widgetInfo, problemNum) => {
18964
19219
  transform = widgetExports.transform || _.identity;
18965
19220
  }
18966
19221
  // widgetInfo.options are the widgetEditor's props:
18967
- return transform(widgetInfo.options, problemNum);
19222
+ return transform(widgetInfo.options, strings, problemNum);
18968
19223
  };
18969
19224
  const traverseChildWidgets$3 = (widgetInfo, traverseRenderer) => {
18970
19225
  if (!traverseRenderer) {
@@ -19363,8 +19618,11 @@ class Renderer extends React.Component {
19363
19618
  problemNum
19364
19619
  } = props;
19365
19620
  const widgetsStartProps = {};
19621
+ const {
19622
+ strings
19623
+ } = this.props;
19366
19624
  entries(allWidgetInfo).forEach(([key, widgetInfo]) => {
19367
- widgetsStartProps[key] = getRendererPropsForWidgetInfo(widgetInfo, problemNum);
19625
+ widgetsStartProps[key] = getRendererPropsForWidgetInfo(widgetInfo, strings, problemNum);
19368
19626
  });
19369
19627
 
19370
19628
  // Call the onWidgetStartProps callback if it exists
@@ -21424,10 +21682,12 @@ class InputWithExamples extends React.Component {
21424
21682
  id: this._getUniqueId()
21425
21683
  }, /*#__PURE__*/React.createElement(Renderer, {
21426
21684
  content: examplesContent,
21427
- linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "input-with-examples")
21685
+ linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "input-with-examples"),
21686
+ strings: this.context.strings
21428
21687
  })));
21429
21688
  }
21430
21689
  }
21690
+ InputWithExamples.contextType = PerseusI18nContext;
21431
21691
  InputWithExamples.defaultProps = {
21432
21692
  type: "text",
21433
21693
  shouldShowExamples: true,
@@ -21549,32 +21809,32 @@ const answerTypes = {
21549
21809
  }
21550
21810
  };
21551
21811
  const formExamples$1 = {
21552
- integer: function (options) {
21553
- return i18n._("an integer, like $6$");
21812
+ integer: function (options, strings) {
21813
+ return strings.integerExample;
21554
21814
  },
21555
- proper: function (options) {
21815
+ proper: function (options, strings) {
21556
21816
  if (options.simplify === "optional") {
21557
- return i18n._("a *proper* fraction, like $1/2$ or $6/10$");
21817
+ return strings.properExample;
21558
21818
  }
21559
- return i18n._("a *simplified proper* fraction, like $3/5$");
21819
+ return strings.simplifiedProperExample;
21560
21820
  },
21561
- improper: function (options) {
21821
+ improper: function (options, strings) {
21562
21822
  if (options.simplify === "optional") {
21563
- return i18n._("an *improper* fraction, like $10/7$ or $14/8$");
21823
+ return strings.improperExample;
21564
21824
  }
21565
- return i18n._("a *simplified improper* fraction, like $7/4$");
21825
+ return strings.simplifiedImproperExample;
21566
21826
  },
21567
- mixed: function (options) {
21568
- return i18n._("a mixed number, like $1\\ 3/4$");
21827
+ mixed: function (options, strings) {
21828
+ return strings.mixedExample;
21569
21829
  },
21570
- decimal: function (options) {
21571
- return i18n._("an *exact* decimal, like $0.75$");
21830
+ decimal: function (options, strings) {
21831
+ return strings.decimalExample;
21572
21832
  },
21573
- percent: function (options) {
21574
- return i18n._("a percent, like $12.34\\%$");
21833
+ percent: function (options, strings) {
21834
+ return strings.percentExample;
21575
21835
  },
21576
- pi: function (options) {
21577
- return i18n._("a multiple of pi, like $12\\ \\text{pi}$ or " + "$2/3\\ \\text{pi}$");
21836
+ pi: function (options, strings) {
21837
+ return strings.piExample;
21578
21838
  }
21579
21839
  };
21580
21840
  class InputNumber extends React.Component {
@@ -21632,18 +21892,16 @@ class InputNumber extends React.Component {
21632
21892
  };
21633
21893
  this.simpleValidate = (rubric, onInputError) => {
21634
21894
  onInputError = onInputError || function () {};
21635
- return InputNumber.validate(this.getUserInput(), rubric, onInputError);
21895
+ return InputNumber.validate(this.getUserInput(), rubric, this.context.strings, onInputError);
21636
21896
  };
21637
21897
  this.examples = () => {
21898
+ const {
21899
+ strings
21900
+ } = this.context;
21638
21901
  const type = this.props.answerType;
21639
21902
  const forms = answerTypes[type].forms.split(/\s*,\s*/);
21640
- const examples = _.map(forms, function (form) {
21641
- // eslint-disable-next-line @babel/no-invalid-this
21642
- // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
21643
- // eslint-disable-next-line @babel/no-invalid-this
21644
- return formExamples$1[form](this.props);
21645
- }, this);
21646
- return [i18n._("**Your answer should be** ")].concat(examples);
21903
+ const examples = _.map(forms, form => formExamples$1[form](this.props, strings));
21904
+ return [strings.yourAnswer].concat(examples);
21647
21905
  };
21648
21906
  }
21649
21907
  render() {
@@ -21696,7 +21954,7 @@ class InputNumber extends React.Component {
21696
21954
  linterContext: this.props.linterContext
21697
21955
  });
21698
21956
  }
21699
- static validate(state, rubric, onInputError = () => {}) {
21957
+ static validate(state, rubric, strings, onInputError = () => {}) {
21700
21958
  if (rubric.answerType == null) {
21701
21959
  rubric.answerType = "number";
21702
21960
  }
@@ -21710,7 +21968,7 @@ class InputNumber extends React.Component {
21710
21968
  inexact: rubric.inexact || undefined,
21711
21969
  maxError: rubric.maxError,
21712
21970
  forms: answerTypes[rubric.answerType].forms
21713
- });
21971
+ }, strings);
21714
21972
 
21715
21973
  // We may have received TeX; try to parse it before grading.
21716
21974
  // If `currentValue` is not TeX, this should be a no-op.
@@ -21752,6 +22010,7 @@ class InputNumber extends React.Component {
21752
22010
  return answerString;
21753
22011
  }
21754
22012
  }
22013
+ InputNumber.contextType = PerseusI18nContext;
21755
22014
  InputNumber.defaultProps = {
21756
22015
  currentValue: "",
21757
22016
  size: "normal",
@@ -21835,12 +22094,12 @@ const answerFormButtons = [{
21835
22094
  content: "\u03C0"
21836
22095
  }];
21837
22096
  const formExamples = {
21838
- integer: () => i18n._("an integer, like $6$"),
21839
- proper: form => form.simplify === "optional" ? i18n._("a *proper* fraction, like $1/2$ or $6/10$") : i18n._("a *simplified proper* fraction, like $3/5$"),
21840
- improper: form => form.simplify === "optional" ? i18n._("an *improper* fraction, like $10/7$ or $14/8$") : i18n._("a *simplified improper* fraction, like $7/4$"),
21841
- mixed: () => i18n._("a mixed number, like $1\\ 3/4$"),
21842
- decimal: () => i18n._("an *exact* decimal, like $0.75$"),
21843
- pi: () => i18n._("a multiple of pi, like $12\\ \\text{pi}$ or " + "$2/3\\ \\text{pi}$")
22097
+ integer: (form, strings) => strings.integerExample,
22098
+ proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
22099
+ improper: (form, strings) => form.simplify === "optional" ? strings.improperExample : strings.simplifiedImproperExample,
22100
+ mixed: (form, strings) => strings.mixedExample,
22101
+ decimal: (form, strings) => strings.decimalExample,
22102
+ pi: (form, strings) => strings.piExample
21844
22103
  };
21845
22104
  class NumericInput extends React.Component {
21846
22105
  constructor(...args) {
@@ -21862,13 +22121,12 @@ class NumericInput extends React.Component {
21862
22121
  };
21863
22122
  });
21864
22123
  let examples = _.map(forms, form => {
21865
- // @ts-expect-error - TS2345 - Argument of type 'PerseusNumericInputAnswerForm | { name: string; simplify: string; }' is not assignable to parameter of type 'PerseusNumericInputAnswerForm'.
21866
- return formExamples[form.name](form);
22124
+ return formExamples[form.name](form, this.context.strings);
21867
22125
  });
21868
22126
  // Ensure no duplicate tooltip text from simplified and unsimplified
21869
22127
  // versions of the same format
21870
22128
  examples = _.uniq(examples);
21871
- return [i18n._("**Your answer should be** ")].concat(examples);
22129
+ return [this.context.strings.yourAnswer].concat(examples);
21872
22130
  };
21873
22131
  this.shouldShowExamples = () => {
21874
22132
  var _this$props$answerFor2, _this$props$answerFor3;
@@ -21881,7 +22139,7 @@ class NumericInput extends React.Component {
21881
22139
  return !noFormsAccepted && !allFormsAccepted;
21882
22140
  };
21883
22141
  this.simpleValidate = rubric => {
21884
- return NumericInput.validate(this.getUserInput(), rubric);
22142
+ return NumericInput.validate(this.getUserInput(), rubric, this.context.strings);
21885
22143
  };
21886
22144
  this.focus = () => {
21887
22145
  var _this$inputRef;
@@ -21961,7 +22219,7 @@ class NumericInput extends React.Component {
21961
22219
  }
21962
22220
  return answerStrings[0];
21963
22221
  }
21964
- static validate(userInput, rubric) {
22222
+ static validate(userInput, rubric, strings) {
21965
22223
  const defaultAnswerForms = answerFormButtons.map(e => e["value"])
21966
22224
  // Don't default to validating the answer as a pi answer
21967
22225
  // if answerForm isn't set on the answer
@@ -21988,7 +22246,7 @@ class NumericInput extends React.Component {
21988
22246
  // TODO(merlob) backfill / delete
21989
22247
  maxError: answer.maxError,
21990
22248
  forms: validatorForms
21991
- });
22249
+ }, strings);
21992
22250
  };
21993
22251
 
21994
22252
  // We may have received TeX; try to parse it before grading.
@@ -22058,7 +22316,7 @@ class NumericInput extends React.Component {
22058
22316
  render() {
22059
22317
  let labelText = this.props.labelText;
22060
22318
  if (labelText == null || labelText === "") {
22061
- labelText = i18n._("Your answer:");
22319
+ labelText = this.context.strings.yourAnswerLabel;
22062
22320
  }
22063
22321
 
22064
22322
  // To right align a custom keypad we need to wrap it.
@@ -22113,6 +22371,7 @@ class NumericInput extends React.Component {
22113
22371
  // a given *problem* rather than for each possible [correct/wrong] *answer*.
22114
22372
  // When should two answers to a problem take different answer types?
22115
22373
  // See D27790 for more discussion.
22374
+ NumericInput.contextType = PerseusI18nContext;
22116
22375
  NumericInput.defaultProps = {
22117
22376
  currentValue: "",
22118
22377
  size: "normal",
@@ -22268,18 +22527,21 @@ class PassageRef extends React.Component {
22268
22527
  return !_.isEqual(this.props, nextProps) || !_.isEqual(this.state, nextState);
22269
22528
  }
22270
22529
  render() {
22530
+ const {
22531
+ strings
22532
+ } = this.context;
22271
22533
  const lineRange = this.state.lineRange;
22272
22534
  let lineRangeOutput;
22273
22535
  if (!lineRange) {
22274
- lineRangeOutput = i18n.$_("lines %(lineRange)s", {
22536
+ lineRangeOutput = strings.lineRange({
22275
22537
  lineRange: `?${EN_DASH$1}?`
22276
22538
  });
22277
22539
  } else if (lineRange[0] === lineRange[1]) {
22278
- lineRangeOutput = i18n.$_("line %(lineNumber)s", {
22279
- lineNumber: lineRange[0]
22540
+ lineRangeOutput = strings.lineNumber({
22541
+ lineNumber: String(lineRange[0])
22280
22542
  });
22281
22543
  } else {
22282
- lineRangeOutput = i18n.$_("lines %(lineRange)s", {
22544
+ lineRangeOutput = strings.lineRange({
22283
22545
  lineRange: lineRange[0] + EN_DASH$1 + lineRange[1]
22284
22546
  });
22285
22547
  }
@@ -22321,6 +22583,7 @@ class PassageRef extends React.Component {
22321
22583
  };
22322
22584
  }
22323
22585
  }
22586
+ PassageRef.contextType = PerseusI18nContext;
22324
22587
  PassageRef.defaultProps = {
22325
22588
  passageNumber: 1,
22326
22589
  referenceNumber: 1,
@@ -22684,7 +22947,7 @@ Icon.defaultProps = {
22684
22947
  color: "currentColor"
22685
22948
  };
22686
22949
 
22687
- function getA11yText(letter, checked, correct, crossedOut, showCorrectness) {
22950
+ function getA11yText(letter, checked, correct, crossedOut, showCorrectness, strings) {
22688
22951
  // There are two pieces of metadata we want to add to each a11yText:
22689
22952
  // whether the answer was checked/crossed-out/neither, and whether the
22690
22953
  // answer is correct/incorrect/not-yet-revealed.
@@ -22693,45 +22956,45 @@ function getA11yText(letter, checked, correct, crossedOut, showCorrectness) {
22693
22956
  // we've just enumerated all 9 possibilities as separate strings.
22694
22957
  if (showCorrectness && correct) {
22695
22958
  if (checked) {
22696
- return i18n._("(Choice %(letter)s, Checked, Correct)", {
22959
+ return strings.choiceCheckedCorrect({
22697
22960
  letter
22698
22961
  });
22699
22962
  }
22700
22963
  if (crossedOut) {
22701
- return i18n._("(Choice %(letter)s, Crossed out, Correct)", {
22964
+ return strings.choiceCrossedOutCorrect({
22702
22965
  letter
22703
22966
  });
22704
22967
  }
22705
- return i18n._("(Choice %(letter)s, Correct)", {
22968
+ return strings.choiceCorrect({
22706
22969
  letter
22707
22970
  });
22708
22971
  }
22709
22972
  if (showCorrectness && !correct) {
22710
22973
  if (checked) {
22711
- return i18n._("(Choice %(letter)s, Checked, Incorrect)", {
22974
+ return strings.choiceCheckedIncorrect({
22712
22975
  letter
22713
22976
  });
22714
22977
  }
22715
22978
  if (crossedOut) {
22716
- return i18n._("(Choice %(letter)s, Crossed out, Incorrect)", {
22979
+ return strings.choiceCrossedOutIncorrect({
22717
22980
  letter
22718
22981
  });
22719
22982
  }
22720
- return i18n._("(Choice %(letter)s, Incorrect)", {
22983
+ return strings.choiceIncorrect({
22721
22984
  letter
22722
22985
  });
22723
22986
  }
22724
22987
  if (checked) {
22725
- return i18n._("(Choice %(letter)s, Checked)", {
22988
+ return strings.choiceChecked({
22726
22989
  letter
22727
22990
  });
22728
22991
  }
22729
22992
  if (crossedOut) {
22730
- return i18n._("(Choice %(letter)s, Crossed out)", {
22993
+ return strings.choiceCrossedOut({
22731
22994
  letter
22732
22995
  });
22733
22996
  }
22734
- return i18n._("(Choice %(letter)s)", {
22997
+ return strings.choice({
22735
22998
  letter
22736
22999
  });
22737
23000
  }
@@ -22778,18 +23041,8 @@ const styles$z = StyleSheet.create({
22778
23041
  * "choice letter". (For example, in English, `getChoiceLetter(0)` is "A",
22779
23042
  * `getChoiceLetter(1)` is "B", etc.)
22780
23043
  */
22781
- function getChoiceLetter(pos) {
22782
- /* I18N: This is a list of single-character labels that will appear in
22783
- * front of multiple-choice options. For instance, a multiple-choice
22784
- * question with three options would display
22785
- * (A) first option
22786
- * (B) second option
22787
- * (C) third option
22788
- * There must be spaces between each of the different characters. The
22789
- * characters will show up next to options in the order that they are
22790
- * listed here. Most multiple choice questions have 5 or fewer options.
22791
- */
22792
- const lettersString = i18n._("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");
23044
+ function getChoiceLetter(pos, strings) {
23045
+ const lettersString = strings.letters;
22793
23046
  const letters = lettersString.split(" ");
22794
23047
  if (pos < letters.length) {
22795
23048
  // If the position we need is listed in the localized string, use that.
@@ -22853,7 +23106,10 @@ function ChoiceInner(props) {
22853
23106
  showCorrectness,
22854
23107
  correct
22855
23108
  } = props;
22856
- const letter = getChoiceLetter(pos);
23109
+ const {
23110
+ strings
23111
+ } = usePerseusI18n();
23112
+ const letter = getChoiceLetter(pos, strings);
22857
23113
  if (!showCorrectness) {
22858
23114
  return letter;
22859
23115
  }
@@ -22999,24 +23255,24 @@ const styles$w = StyleSheet.create({
22999
23255
  * ChoiceIcon visualizations but is meant to make the distinctions
23000
23256
  * between the states more immediately clear to users.
23001
23257
  */
23002
- function renderText(checked, correct, crossedOut) {
23258
+ function renderText(checked, correct, crossedOut, strings) {
23003
23259
  if (correct) {
23004
23260
  // For correct answers, we surface checked _or_ crossedOut state,
23005
23261
  // because any interaction with the correct answer is noteworthy!
23006
23262
  if (checked) {
23007
- return i18n._("Correct (selected)");
23263
+ return strings.correctSelected;
23008
23264
  }
23009
23265
  if (crossedOut) {
23010
- return i18n._("Correct (but you crossed it out)");
23266
+ return strings.correctCrossedOut;
23011
23267
  }
23012
- return i18n._("Correct");
23268
+ return strings.correct;
23013
23269
  }
23014
23270
  // But, for incorrect answers, we only surface checked state,
23015
23271
  // because crossing out an incorrect answer is not noteworthy.
23016
23272
  if (checked) {
23017
- return i18n._("Incorrect (selected)");
23273
+ return strings.incorrectSelected;
23018
23274
  }
23019
- return i18n._("Incorrect");
23275
+ return strings.incorrect;
23020
23276
  }
23021
23277
  const OptionStatus = function OptionStatus(props) {
23022
23278
  const {
@@ -23026,6 +23282,9 @@ const OptionStatus = function OptionStatus(props) {
23026
23282
  previouslyAnswered,
23027
23283
  reviewMode
23028
23284
  } = props;
23285
+ const {
23286
+ strings
23287
+ } = usePerseusI18n();
23029
23288
 
23030
23289
  // Option status is shown only in review mode, or for incorrectly
23031
23290
  // answered items.
@@ -23045,7 +23304,7 @@ const OptionStatus = function OptionStatus(props) {
23045
23304
  }
23046
23305
  return /*#__PURE__*/React.createElement("div", {
23047
23306
  className: css(styles$v.text, textStyle)
23048
- }, renderText(checked, correct, crossedOut));
23307
+ }, renderText(checked, correct, crossedOut, strings));
23049
23308
  };
23050
23309
  const styles$v = StyleSheet.create({
23051
23310
  text: {
@@ -23092,6 +23351,9 @@ const Choice = function Choice(props) {
23092
23351
  forwardedRef
23093
23352
  } = props;
23094
23353
  const [isInputFocused, setIsInputFocused] = useState(false);
23354
+ const {
23355
+ strings
23356
+ } = usePerseusI18n();
23095
23357
  useEffect(() => {
23096
23358
  if (isInputFocused && disabled) {
23097
23359
  setIsInputFocused(false);
@@ -23121,8 +23383,8 @@ const Choice = function Choice(props) {
23121
23383
  // However, we don't want to do this when we're in review mode in the
23122
23384
  // content library.
23123
23385
  const showDimmed = !reviewMode && apiOptions.readOnly || crossedOut;
23124
- const letter = getChoiceLetter(pos);
23125
- const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness);
23386
+ const letter = getChoiceLetter(pos, strings);
23387
+ const a11yText = getA11yText(letter, checked, correct, crossedOut, showCorrectness, strings);
23126
23388
  const idFactory = useUniqueIdWithMock(`choice`);
23127
23389
  const choiceId = idFactory.get(letter);
23128
23390
  return /*#__PURE__*/React.createElement("div", {
@@ -23222,15 +23484,15 @@ const Choice = function Choice(props) {
23222
23484
  content: ({
23223
23485
  close
23224
23486
  }) => /*#__PURE__*/React.createElement(PopoverContent, {
23225
- title: i18n._("Cross out"),
23226
- content: i18n._("Cross out option"),
23487
+ title: strings.crossOut,
23488
+ content: strings.crossOutOption,
23227
23489
  closeButtonVisible: true,
23228
23490
  actions: /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(Strut, {
23229
23491
  size: spacing.medium_16
23230
23492
  }), /*#__PURE__*/React.createElement(Button, {
23231
23493
  kind: "primary",
23232
- "aria-label": i18n._("Cross out Choice %(letter)s", {
23233
- letter: getChoiceLetter(pos)
23494
+ "aria-label": strings.crossOutChoice({
23495
+ letter: getChoiceLetter(pos, strings)
23234
23496
  }),
23235
23497
  disabled: apiOptions.readOnly || reviewMode,
23236
23498
  onClick: () => {
@@ -23250,14 +23512,14 @@ const Choice = function Choice(props) {
23250
23512
  }
23251
23513
  close();
23252
23514
  }
23253
- }, crossedOut ? i18n._("Bring back") : i18n._("Cross out")))
23515
+ }, crossedOut ? strings.bringBack : strings.crossOut))
23254
23516
  })
23255
23517
  }, ({
23256
23518
  open
23257
23519
  }) => /*#__PURE__*/React.createElement(Clickable, {
23258
23520
  onClick: open,
23259
- "aria-label": i18n._(`Open menu for Choice %(letter)s`, {
23260
- letter: getChoiceLetter(pos)
23521
+ "aria-label": strings.openMenuForChoice({
23522
+ letter: getChoiceLetter(pos, strings)
23261
23523
  }),
23262
23524
  style: {
23263
23525
  alignSelf: "center",
@@ -23309,6 +23571,9 @@ const ChoiceNoneAbove = function ChoiceNoneAbove(props) {
23309
23571
  forwardedRef
23310
23572
  } = props,
23311
23573
  rest = _objectWithoutPropertiesLoose(props, _excluded$3);
23574
+ const {
23575
+ strings
23576
+ } = usePerseusI18n();
23312
23577
  const choiceProps = _extends({}, rest, {
23313
23578
  content: showContent ? content :
23314
23579
  /*#__PURE__*/
@@ -23324,7 +23589,8 @@ const ChoiceNoneAbove = function ChoiceNoneAbove(props) {
23324
23589
  // just to render a string
23325
23590
  React.createElement(Renderer, {
23326
23591
  key: "noneOfTheAboveRenderer",
23327
- content: i18n._("None of the above")
23592
+ content: strings.noneOfTheAbove,
23593
+ strings: strings
23328
23594
  })
23329
23595
  });
23330
23596
  return /*#__PURE__*/React.createElement(Choice$1, _extends({}, choiceProps, {
@@ -23344,16 +23610,16 @@ const {
23344
23610
 
23345
23611
  // exported for tests
23346
23612
 
23347
- function getInstructionsText(multipleSelect, countChoices, numCorrect) {
23613
+ function getInstructionsText(multipleSelect, countChoices, numCorrect, strings) {
23348
23614
  if (multipleSelect) {
23349
23615
  if (countChoices) {
23350
- return i18n._("Choose %(numCorrect)s answers:", {
23351
- numCorrect: numCorrect
23616
+ return strings.chooseNumAnswers({
23617
+ numCorrect: String(numCorrect)
23352
23618
  });
23353
23619
  }
23354
- return i18n._("Choose all answers that apply:");
23620
+ return strings.chooseAllAnswers;
23355
23621
  }
23356
- return i18n._("Choose 1 answer:");
23622
+ return strings.chooseOneAnswer;
23357
23623
  }
23358
23624
  const BaseRadio = function BaseRadio(props) {
23359
23625
  const {
@@ -23368,6 +23634,9 @@ const BaseRadio = function BaseRadio(props) {
23368
23634
  isLastUsedWidget,
23369
23635
  registerFocusFunction
23370
23636
  } = props;
23637
+ const {
23638
+ strings
23639
+ } = usePerseusI18n();
23371
23640
 
23372
23641
  // useEffect doesn't have previous props
23373
23642
  const prevReviewModeRubric = useRef();
@@ -23474,7 +23743,7 @@ const BaseRadio = function BaseRadio(props) {
23474
23743
  const lastChoiceHighlighted = choices[choices.length - 1].highlighted;
23475
23744
  const className = classNames("perseus-widget-radio", !editMode && "perseus-rendered-radio", css(styles$t.radio, styles$t.responsiveRadioContainer, firstChoiceHighlighted && isMobile && styles$t.radioContainerFirstHighlighted, lastChoiceHighlighted && isMobile && styles$t.radioContainerLastHighlighted));
23476
23745
  const instructionsClassName = classNames("instructions", css(styles$t.instructions, isMobile && styles$t.instructionsMobile));
23477
- const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect);
23746
+ const instructions = getInstructionsText(multipleSelect, countChoices, numCorrect, strings);
23478
23747
  const responsiveClassName = css(styles$t.responsiveFieldset);
23479
23748
  const fieldset = /*#__PURE__*/React.createElement("fieldset", {
23480
23749
  className: `perseus-widget-radio-fieldset ${responsiveClassName}`
@@ -23739,7 +24008,8 @@ class Radio$1 extends React.Component {
23739
24008
  linterContext: _extends({}, this.props.linterContext, {
23740
24009
  // @ts-expect-error - TS2322 - Type '{ blockHighlight: true; contentType: string; highlightLint: boolean; paths: readonly string[]; stack: readonly string[]; }' is not assignable to type 'LinterContextProps'.
23741
24010
  blockHighlight: true
23742
- })
24011
+ }),
24012
+ strings: this.context.strings
23743
24013
  });
23744
24014
  };
23745
24015
  // When `BaseRadio`'s `onChange` handler is called, indicating a change in
@@ -23791,7 +24061,7 @@ class Radio$1 extends React.Component {
23791
24061
  return Radio$1.getUserInputFromProps(this.props);
23792
24062
  };
23793
24063
  this.simpleValidate = rubric => {
23794
- return Radio$1.validate(this.getUserInput(), rubric);
24064
+ return Radio$1.validate(this.getUserInput(), rubric, this.context.strings);
23795
24065
  };
23796
24066
  /**
23797
24067
  * Turn on rationale display for the currently selected choices. Note that
@@ -23852,7 +24122,7 @@ class Radio$1 extends React.Component {
23852
24122
  }
23853
24123
  };
23854
24124
  }
23855
- static validate(userInput, rubric) {
24125
+ static validate(userInput, rubric, strings) {
23856
24126
  const numSelected = userInput.choicesSelected.reduce((sum, selected) => {
23857
24127
  return sum + (selected ? 1 : 0);
23858
24128
  }, 0);
@@ -23865,7 +24135,7 @@ class Radio$1 extends React.Component {
23865
24135
  if (userInput.numCorrect && userInput.numCorrect > 1 && numSelected !== userInput.numCorrect) {
23866
24136
  return {
23867
24137
  type: "invalid",
23868
- message: i18n._("Please choose the correct number of answers.")
24138
+ message: strings.chooseCorrectNum
23869
24139
  };
23870
24140
  // If NOTA and some other answer are checked, ...
23871
24141
  }
@@ -23873,7 +24143,7 @@ class Radio$1 extends React.Component {
23873
24143
  if (userInput.noneOfTheAboveSelected && numSelected > 1) {
23874
24144
  return {
23875
24145
  type: "invalid",
23876
- message: i18n._("'None of the above' may not be selected " + "when other answers are selected.")
24146
+ message: strings.notNoneOfTheAbove
23877
24147
  };
23878
24148
  }
23879
24149
  const correct = userInput.choicesSelected.every((selected, i) => {
@@ -23985,6 +24255,9 @@ class Radio$1 extends React.Component {
23985
24255
  const {
23986
24256
  choices
23987
24257
  } = this.props;
24258
+ const {
24259
+ strings
24260
+ } = this.context;
23988
24261
  let choiceStates;
23989
24262
  if (this.props.static) {
23990
24263
  choiceStates = choices.map(choice => ({
@@ -24036,11 +24309,7 @@ class Radio$1 extends React.Component {
24036
24309
  }));
24037
24310
  }
24038
24311
  const choicesProp = choices.map((choice, i) => {
24039
- const content = choice.isNoneOfTheAbove && !choice.content ?
24040
- // we use i18n._ instead of $_ here because the content
24041
- // sent to a renderer needs to be a string, not a react
24042
- // node (/renderable/fragment).
24043
- i18n._("None of the above") : choice.content;
24312
+ const content = choice.isNoneOfTheAbove && !choice.content ? strings.noneOfTheAbove : choice.content;
24044
24313
  const {
24045
24314
  selected,
24046
24315
  crossedOut,
@@ -24092,6 +24361,7 @@ class Radio$1 extends React.Component {
24092
24361
  });
24093
24362
  }
24094
24363
  }
24364
+ Radio$1.contextType = PerseusI18nContext;
24095
24365
  Radio$1.defaultProps = {
24096
24366
  choices: [],
24097
24367
  multipleSelect: false,
@@ -24106,14 +24376,8 @@ const {
24106
24376
  random
24107
24377
  } = Util;
24108
24378
 
24109
- // Represents choices that we automatically re-order if encountered.
24110
- // Note: these are in the reversed (incorrect) order that we will swap, if
24111
- // found.
24112
- // Note 2: these are internationalized when compared later on.
24113
- const ReversedChoices = [[i18n._("False"), i18n._("True")], [i18n._("No"), i18n._("Yes")]];
24114
-
24115
24379
  // Transforms the choices for display.
24116
- const _choiceTransform = (widgetOptions, problemNum) => {
24380
+ const _choiceTransform = (widgetOptions, strings, problemNum) => {
24117
24381
  const _maybeRandomize = function _maybeRandomize(array) {
24118
24382
  const randomSeed = problemNum === undefined ? random : problemNum;
24119
24383
  // NOTE: `problemNum` will only be set when the radio widget is
@@ -24142,6 +24406,11 @@ const _choiceTransform = (widgetOptions, problemNum) => {
24142
24406
  return newChoices;
24143
24407
  };
24144
24408
  const enforceOrdering = choices => {
24409
+ // Represents choices that we automatically re-order if encountered.
24410
+ // Note: these are in the reversed (incorrect) order that we will swap, if
24411
+ // found.
24412
+ // Note 2: these are internationalized when compared later on.
24413
+ const ReversedChoices = [[strings.false, strings.true], [strings.no, strings.yes]];
24145
24414
  const content = choices.map(c => c.content);
24146
24415
  if (ReversedChoices.some(reversed => _.isEqual(content, reversed))) {
24147
24416
  return [choices[1], choices[0]];
@@ -24167,8 +24436,8 @@ const _choiceTransform = (widgetOptions, problemNum) => {
24167
24436
  // 1) we randomize the order first
24168
24437
  _maybeRandomize(choices)));
24169
24438
  };
24170
- const transform = (widgetOptions, problemNum) => {
24171
- const choices = _choiceTransform(widgetOptions, problemNum);
24439
+ const transform = (widgetOptions, strings, problemNum) => {
24440
+ const choices = _choiceTransform(widgetOptions, strings, problemNum);
24172
24441
  const numCorrect = _.reduce(widgetOptions.choices, function (memo, choice) {
24173
24442
  return choice.correct ? memo + 1 : memo;
24174
24443
  }, 0);
@@ -24254,7 +24523,7 @@ class Categorizer extends React.Component {
24254
24523
  return Categorizer.getUserInputFromProps(this.props);
24255
24524
  };
24256
24525
  this.simpleValidate = rubric => {
24257
- return Categorizer.validate(this.getUserInput(), rubric);
24526
+ return Categorizer.validate(this.getUserInput(), rubric, this.context.strings);
24258
24527
  };
24259
24528
  }
24260
24529
  render() {
@@ -24283,7 +24552,8 @@ class Categorizer extends React.Component {
24283
24552
  key: i
24284
24553
  }, /*#__PURE__*/React.createElement(Renderer, {
24285
24554
  content: category,
24286
- linterContext: this.props.linterContext
24555
+ linterContext: this.props.linterContext,
24556
+ strings: this.context.strings
24287
24557
  }));
24288
24558
  }))), /*#__PURE__*/React.createElement("tbody", null, indexedItems.map(indexedItem => {
24289
24559
  const item = indexedItem[0];
@@ -24295,7 +24565,8 @@ class Categorizer extends React.Component {
24295
24565
  // @ts-expect-error - TS2322 - Type 'string | number' is not assignable to type 'string | undefined'.
24296
24566
  , {
24297
24567
  content: item,
24298
- linterContext: this.props.linterContext
24568
+ linterContext: this.props.linterContext,
24569
+ strings: this.context.strings
24299
24570
  })), self.props.categories.map((catName, catNum) => {
24300
24571
  const selected = self.props.values[itemNum] === catNum;
24301
24572
  return /*#__PURE__*/React.createElement("td", {
@@ -24338,7 +24609,7 @@ class Categorizer extends React.Component {
24338
24609
  this.change("values", values);
24339
24610
  this.props.trackInteraction();
24340
24611
  }
24341
- static validate(userInput, rubric) {
24612
+ static validate(userInput, rubric, strings) {
24342
24613
  let completed = true;
24343
24614
  let allCorrect = true;
24344
24615
  rubric.values.forEach((value, i) => {
@@ -24352,7 +24623,7 @@ class Categorizer extends React.Component {
24352
24623
  if (!completed) {
24353
24624
  return {
24354
24625
  type: "invalid",
24355
- message: i18n._("Make sure you select something for every row.")
24626
+ message: strings.invalidSelection
24356
24627
  };
24357
24628
  }
24358
24629
  return {
@@ -24368,6 +24639,7 @@ class Categorizer extends React.Component {
24368
24639
  }
24369
24640
 
24370
24641
  // TODO(benkomalo): inject page-margin into Perseus instead of hardcoding.
24642
+ Categorizer.contextType = PerseusI18nContext;
24371
24643
  Categorizer.defaultProps = {
24372
24644
  items: [],
24373
24645
  categories: [],
@@ -24693,7 +24965,8 @@ class Definition extends React.Component {
24693
24965
  }, /*#__PURE__*/React.createElement(Renderer, {
24694
24966
  apiOptions: this.props.apiOptions,
24695
24967
  content: this.props.definition,
24696
- widgets: this.props.widgets
24968
+ widgets: this.props.widgets,
24969
+ strings: this.context.strings
24697
24970
  })),
24698
24971
  opened: activeDefinitionId === this.props.widgetId,
24699
24972
  onClose: () => setActiveDefinitionId(null),
@@ -24715,6 +24988,7 @@ class Definition extends React.Component {
24715
24988
  }, this.props.togglePrompt))));
24716
24989
  }
24717
24990
  }
24991
+ Definition.contextType = PerseusI18nContext;
24718
24992
  Definition.defaultProps = {
24719
24993
  togglePrompt: "define me",
24720
24994
  definition: "definition goes here"
@@ -24765,12 +25039,13 @@ class DeprecatedStandin extends React__default.Component {
24765
25039
  paddingBottom: 8
24766
25040
  }
24767
25041
  }, /*#__PURE__*/React__default.createElement(Banner, {
24768
- text: i18n._("Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!"),
25042
+ text: this.context.strings.deprecatedStandin,
24769
25043
  kind: "info",
24770
25044
  layout: "full-width"
24771
25045
  }));
24772
25046
  }
24773
25047
  }
25048
+ DeprecatedStandin.contextType = PerseusI18nContext;
24774
25049
  var DeprecatedStandin$1 = {
24775
25050
  name: "deprecated-standin",
24776
25051
  displayName: "Deprecated Standin",
@@ -24965,10 +25240,12 @@ class Explanation extends React.Component {
24965
25240
  apiOptions: this.props.apiOptions,
24966
25241
  content: this.props.explanation,
24967
25242
  widgets: this.props.widgets,
24968
- linterContext: this.props.linterContext
25243
+ linterContext: this.props.linterContext,
25244
+ strings: this.context.strings
24969
25245
  })))));
24970
25246
  }
24971
25247
  }
25248
+ Explanation.contextType = PerseusI18nContext;
24972
25249
  Explanation.defaultProps = {
24973
25250
  showPrompt: "Explain",
24974
25251
  hidePrompt: "Hide explanation",
@@ -25036,6 +25313,13 @@ class GradedGroupAnswerBar extends React.Component {
25036
25313
  onCheckAnswer,
25037
25314
  onNextQuestion
25038
25315
  } = this.props;
25316
+ const {
25317
+ keepTrying,
25318
+ tryAgain,
25319
+ check,
25320
+ correctExcited,
25321
+ nextQuestion
25322
+ } = this.context.strings;
25039
25323
  const answerBarStyle = _extends({}, styles$o.answerBar, {
25040
25324
  backgroundColor: answerBarState === "CORRECT" ? color.offWhite : "white",
25041
25325
  // Center the "Correct!" message only when there's no next question
@@ -25049,10 +25333,10 @@ class GradedGroupAnswerBar extends React.Component {
25049
25333
  style: {
25050
25334
  marginLeft: 8
25051
25335
  }
25052
- }, i18n._("Keep trying"))) : /*#__PURE__*/React.createElement("span", null); // empty span keeps the button on the right side
25336
+ }, keepTrying)) : /*#__PURE__*/React.createElement("span", null); // empty span keeps the button on the right side
25053
25337
 
25054
25338
  if (answerBarState !== "CORRECT") {
25055
- const buttonLabel = answerBarState === "INCORRECT" ? i18n._("Try again") : i18n._("Check");
25339
+ const buttonLabel = answerBarState === "INCORRECT" ? tryAgain : check;
25056
25340
  return /*#__PURE__*/React.createElement("div", {
25057
25341
  style: answerBarStyle
25058
25342
  }, message, /*#__PURE__*/React.createElement(Button, {
@@ -25075,15 +25359,16 @@ class GradedGroupAnswerBar extends React.Component {
25075
25359
  }
25076
25360
  }))), /*#__PURE__*/React.createElement("span", {
25077
25361
  role: "alert",
25078
- "aria-label": i18n._("Correct!"),
25362
+ "aria-label": correctExcited,
25079
25363
  style: {
25080
25364
  marginLeft: 8
25081
25365
  }
25082
- }, i18n._("Correct!"))), onNextQuestion && /*#__PURE__*/React.createElement(Button, {
25366
+ }, correctExcited)), onNextQuestion && /*#__PURE__*/React.createElement(Button, {
25083
25367
  onClick: onNextQuestion
25084
- }, i18n._("Next question")));
25368
+ }, nextQuestion));
25085
25369
  }
25086
25370
  }
25371
+ GradedGroupAnswerBar.contextType = PerseusI18nContext;
25087
25372
  const fontSize = 17;
25088
25373
  const styles$o = {
25089
25374
  answerBar: {
@@ -25137,10 +25422,6 @@ const getNextState = (currentState, answerable) => {
25137
25422
  }
25138
25423
  };
25139
25424
 
25140
- // Prepended to all invalid messages to make the widget messages a bit clearer
25141
- const INVALID_MESSAGE_PREFIX = i18n._("We couldn't grade your answer.");
25142
- const DEFAULT_INVALID_MESSAGE = i18n._("It looks like you left something blank or ") + i18n._("entered in an invalid answer.");
25143
-
25144
25425
  // exports has no 'transform'
25145
25426
 
25146
25427
  // A Graded Group is more or less a Group widget that displays a check
@@ -25192,8 +25473,13 @@ class GradedGroup extends React.Component {
25192
25473
  // eslint-disable-next-line react/no-string-refs
25193
25474
  // @ts-expect-error - TS2339 - Property 'score' does not exist on type 'ReactInstance'.
25194
25475
  const score = this.refs.renderer.score();
25476
+ const {
25477
+ INVALID_MESSAGE_PREFIX,
25478
+ DEFAULT_INVALID_MESSAGE_1,
25479
+ DEFAULT_INVALID_MESSAGE_2
25480
+ } = this.context.strings;
25195
25481
  const status = score.type === "points" ? score.total === score.earned ? GRADING_STATUSES.correct : GRADING_STATUSES.incorrect : GRADING_STATUSES.invalid;
25196
- const message = score.type === "points" ? score.message || "" : score.message ? `${INVALID_MESSAGE_PREFIX} ${score.message}` : `${INVALID_MESSAGE_PREFIX} ${DEFAULT_INVALID_MESSAGE}`;
25482
+ 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}`;
25197
25483
  this.setState({
25198
25484
  status: status,
25199
25485
  message: message,
@@ -25258,8 +25544,7 @@ class GradedGroup extends React.Component {
25258
25544
  color: "#526f03"
25259
25545
  }
25260
25546
  }));
25261
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
25262
- gradeStatus = i18n._("Correct");
25547
+ gradeStatus = this.context.strings.correct;
25263
25548
  } else if (this.state.status === GRADING_STATUSES.incorrect) {
25264
25549
  // TODO(jeremy): update to a WB colour
25265
25550
  // @ts-expect-error - TS2322 - Type 'Element' is not assignable to type 'null'.
@@ -25268,8 +25553,7 @@ class GradedGroup extends React.Component {
25268
25553
  color: "#ff5454"
25269
25554
  }
25270
25555
  }));
25271
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
25272
- gradeStatus = i18n._("Incorrect");
25556
+ gradeStatus = this.context.strings.incorrect;
25273
25557
  }
25274
25558
  const mobileClass = this.props.inGradedGroupSet ? css(styles$n.gradedGroupInSet) : css(styles$n.gradedGroup);
25275
25559
  const classes = classNames({
@@ -25298,7 +25582,8 @@ class GradedGroup extends React.Component {
25298
25582
  readOnly
25299
25583
  }),
25300
25584
  onInteractWithWidget: this._onInteractWithWidget,
25301
- linterContext: this.props.linterContext
25585
+ linterContext: this.props.linterContext,
25586
+ strings: this.context.strings
25302
25587
  })), !apiOptions.isMobile && icon && /*#__PURE__*/React.createElement("div", {
25303
25588
  className: "group-icon"
25304
25589
  }, icon), !apiOptions.isMobile && gradeStatus && /*#__PURE__*/React.createElement("div", {
@@ -25312,14 +25597,14 @@ class GradedGroup extends React.Component {
25312
25597
  kind: "secondary",
25313
25598
  disabled: this.props.apiOptions.readOnly,
25314
25599
  onClick: this._checkAnswer
25315
- }, i18n._("Check")), !apiOptions.isMobile && isCorrect && this.props.onNextQuestion && /*#__PURE__*/React.createElement(Button, {
25600
+ }, this.context.strings.check), !apiOptions.isMobile && isCorrect && this.props.onNextQuestion && /*#__PURE__*/React.createElement(Button, {
25316
25601
  kind: "secondary",
25317
25602
  disabled: this.props.apiOptions.readOnly,
25318
25603
  onClick: this.props.onNextQuestion,
25319
25604
  style: {
25320
25605
  marginLeft: 5
25321
25606
  }
25322
- }, i18n._("Next question")), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
25607
+ }, this.context.strings.nextQuestion), this.props.hint && this.props.hint.content && (this.state.showHint ? /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("button", {
25323
25608
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
25324
25609
  tabIndex: "0",
25325
25610
  className: css(styles$n.explanationTitle),
@@ -25333,11 +25618,12 @@ class GradedGroup extends React.Component {
25333
25618
  showHint: false
25334
25619
  });
25335
25620
  }
25336
- }, i18n._("Hide explanation")), /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props.hint, {
25621
+ }, this.context.strings.hideExplanation), /*#__PURE__*/React.createElement(Renderer, _extends({}, this.props.hint, {
25337
25622
  // eslint-disable-next-line react/no-string-refs
25338
25623
  ref: "hints-renderer",
25339
25624
  apiOptions: apiOptions,
25340
- linterContext: this.props.linterContext
25625
+ linterContext: this.props.linterContext,
25626
+ strings: this.context.strings
25341
25627
  }))) :
25342
25628
  /*#__PURE__*/
25343
25629
  // Not using Button here bc the styles won't work.
@@ -25355,7 +25641,7 @@ class GradedGroup extends React.Component {
25355
25641
  });
25356
25642
  },
25357
25643
  className: css(styles$n.showHintLink)
25358
- }, i18n._("Explain"))), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React.createElement(GradedGroupAnswerBar, {
25644
+ }, this.context.strings.explain)), apiOptions.isMobile && answerBarState !== "HIDDEN" && /*#__PURE__*/React.createElement(GradedGroupAnswerBar, {
25359
25645
  apiOptions: apiOptions,
25360
25646
  answerBarState: answerBarState,
25361
25647
  onCheckAnswer: this._checkAnswer,
@@ -25363,6 +25649,7 @@ class GradedGroup extends React.Component {
25363
25649
  }));
25364
25650
  }
25365
25651
  }
25652
+ GradedGroup.contextType = PerseusI18nContext;
25366
25653
  GradedGroup.defaultProps = {
25367
25654
  title: "",
25368
25655
  content: "",
@@ -25456,7 +25743,7 @@ class Indicators extends React.Component {
25456
25743
  key: title
25457
25744
  }, /*#__PURE__*/React.createElement(Clickable, {
25458
25745
  role: "button",
25459
- "aria-label": i18n._("Skip to %(title)s", {
25746
+ "aria-label": this.context.strings.skipToTitle({
25460
25747
  title
25461
25748
  }),
25462
25749
  style: styles$m.indicatorButton,
@@ -25472,12 +25759,10 @@ class Indicators extends React.Component {
25472
25759
  style: styles$m.indicatorDotActive
25473
25760
  }, /*#__PURE__*/React.createElement("span", {
25474
25761
  className: css(a11y.srOnly)
25475
- }, i18n._("Current"))))))));
25762
+ }, this.context.strings.current)))))));
25476
25763
  }
25477
25764
  }
25478
-
25479
- // no transform
25480
-
25765
+ Indicators.contextType = PerseusI18nContext; // no transform
25481
25766
  // TODO(jared): find a better name for this :) and for GradedGroup; the names
25482
25767
  // are currently a little confusing.
25483
25768
  class GradedGroupSet extends React.Component {
@@ -25553,7 +25838,7 @@ class GradedGroupSet extends React.Component {
25553
25838
  }
25554
25839
  const currentGroup = this.props.gradedGroups[this.state.currentGroup];
25555
25840
  if (!currentGroup) {
25556
- return /*#__PURE__*/React.createElement("span", null, i18n.doNotTranslate("No current group..."));
25841
+ return /*#__PURE__*/React.createElement("span", null, "No current group...");
25557
25842
  }
25558
25843
  const numGroups = this.props.gradedGroups.length;
25559
25844
  const handleNextQuestion = this.state.currentGroup < numGroups - 1 ? this.handleNextQuestion : null;
@@ -26848,7 +27133,8 @@ class Group extends React.Component {
26848
27133
  findExternalWidgets: this.props.findWidgets,
26849
27134
  reviewMode: !!this.props.reviewModeRubric,
26850
27135
  onInteractWithWidget: onInteractWithWidget,
26851
- linterContext: this.props.linterContext
27136
+ linterContext: this.props.linterContext,
27137
+ strings: this.context.strings
26852
27138
  }), this.props.icon &&
26853
27139
  /*#__PURE__*/
26854
27140
  // @ts-expect-error - TS2339 - Property 'icon' does not exist on type 'Readonly<Props> & Readonly<{ children?: ReactNode; }>'.
@@ -26857,6 +27143,7 @@ class Group extends React.Component {
26857
27143
  }, this.props.icon));
26858
27144
  }
26859
27145
  }
27146
+ Group.contextType = PerseusI18nContext;
26860
27147
  Group.defaultProps = {
26861
27148
  content: "",
26862
27149
  widgets: {},
@@ -27122,7 +27409,8 @@ class ImageWidget extends React.Component {
27122
27409
  }, /*#__PURE__*/React.createElement(Renderer, {
27123
27410
  content: this.props.alt,
27124
27411
  apiOptions: apiOptions,
27125
- linterContext: this.props.linterContext
27412
+ linterContext: this.props.linterContext,
27413
+ strings: this.context.strings
27126
27414
  }));
27127
27415
  }
27128
27416
 
@@ -27182,7 +27470,8 @@ class ImageWidget extends React.Component {
27182
27470
  }, /*#__PURE__*/React.createElement(Renderer, {
27183
27471
  content: _title + this.props.caption,
27184
27472
  apiOptions: apiOptions,
27185
- linterContext: this.props.linterContext
27473
+ linterContext: this.props.linterContext,
27474
+ strings: this.context.strings
27186
27475
  })));
27187
27476
  }
27188
27477
  return /*#__PURE__*/React.createElement("figure", {
@@ -27200,7 +27489,8 @@ class ImageWidget extends React.Component {
27200
27489
  }, /*#__PURE__*/React.createElement(Renderer, {
27201
27490
  content: this.props.title,
27202
27491
  apiOptions: apiOptions,
27203
- linterContext: this.props.linterContext
27492
+ linterContext: this.props.linterContext,
27493
+ strings: this.context.strings
27204
27494
  }));
27205
27495
  }
27206
27496
  if (this.props.caption) {
@@ -27212,7 +27502,8 @@ class ImageWidget extends React.Component {
27212
27502
  }, /*#__PURE__*/React.createElement(Renderer, {
27213
27503
  content: this.props.caption,
27214
27504
  apiOptions: apiOptions,
27215
- linterContext: this.props.linterContext
27505
+ linterContext: this.props.linterContext,
27506
+ strings: this.context.strings
27216
27507
  }));
27217
27508
  }
27218
27509
  return /*#__PURE__*/React.createElement("figure", {
@@ -27231,6 +27522,7 @@ class ImageWidget extends React.Component {
27231
27522
  };
27232
27523
  }
27233
27524
  }
27525
+ ImageWidget.contextType = PerseusI18nContext;
27234
27526
  ImageWidget.defaultProps = {
27235
27527
  alignment: DEFAULT_ALIGNMENT,
27236
27528
  title: "",
@@ -28163,6 +28455,123 @@ Graph.defaultProps = {
28163
28455
  isMobile: false
28164
28456
  };
28165
28457
 
28458
+ // TODO(FEI-4010): Remove `Perseus` prefix for all types here
28459
+ // TODO(FEI-4011): Use types generated by https://github.com/jaredly/generate-perseus-flowtypes
28460
+
28461
+ // Range is replaced within this file with Interval, but it is used elsewhere
28462
+ // and exported from the package, so we need to keep it around.
28463
+ // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
28464
+ const ItemExtras = [
28465
+ // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
28466
+ "calculator",
28467
+ // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
28468
+ "chi2Table",
28469
+ // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
28470
+ "financialCalculatorMonthlyPayment",
28471
+ // The user might benefit from a total amount calculator. Provided on Khan Academy when true
28472
+ "financialCalculatorTotalAmount",
28473
+ // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
28474
+ "financialCalculatorTimeToPayOff",
28475
+ // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
28476
+ "periodicTable",
28477
+ // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
28478
+ "periodicTableWithKey",
28479
+ // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
28480
+ "tTable",
28481
+ // The user might benefit from using a statistics Z Table like https://www.ztable.net/
28482
+ "zTable"];
28483
+
28484
+ // prettier-ignore
28485
+
28486
+ // prettier-ignore
28487
+
28488
+ // prettier-ignore
28489
+
28490
+ // prettier-ignore
28491
+
28492
+ // prettier-ignore
28493
+
28494
+ // prettier-ignore
28495
+
28496
+ // prettier-ignore
28497
+
28498
+ // prettier-ignore
28499
+
28500
+ // prettier-ignore
28501
+
28502
+ // prettier-ignore
28503
+
28504
+ // prettier-ignore
28505
+
28506
+ // prettier-ignore
28507
+
28508
+ // prettier-ignore
28509
+
28510
+ // prettier-ignore
28511
+
28512
+ // prettier-ignore
28513
+
28514
+ // prettier-ignore
28515
+
28516
+ // prettier-ignore
28517
+
28518
+ // prettier-ignore
28519
+
28520
+ // prettier-ignore
28521
+
28522
+ // prettier-ignore
28523
+
28524
+ // prettier-ignore
28525
+
28526
+ // prettier-ignore
28527
+
28528
+ // prettier-ignore
28529
+
28530
+ // prettier-ignore
28531
+
28532
+ // prettier-ignore
28533
+
28534
+ // prettier-ignore
28535
+
28536
+ // prettier-ignore
28537
+
28538
+ // prettier-ignore
28539
+
28540
+ // prettier-ignore
28541
+
28542
+ // prettier-ignore
28543
+
28544
+ // prettier-ignore
28545
+
28546
+ // prettier-ignore
28547
+
28548
+ // prettier-ignore
28549
+
28550
+ // prettier-ignore
28551
+
28552
+ // prettier-ignore
28553
+
28554
+ // prettier-ignore
28555
+
28556
+ //prettier-ignore
28557
+
28558
+ // A background image applied to various widgets.
28559
+
28560
+ const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
28561
+ const lockedFigureColors = {
28562
+ blue: "#3D7586",
28563
+ green: "#447A53",
28564
+ gray: "#5D5F66",
28565
+ grayH: "#3B3D45",
28566
+ grayI: "#21242C",
28567
+ purple: "#594094",
28568
+ purpleD: "#8351E8",
28569
+ pink: "#B25071",
28570
+ orange: "#946700",
28571
+ red: "#D92916"
28572
+ };
28573
+ const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
28574
+
28166
28575
  const LockedLine = props => {
28167
28576
  const {
28168
28577
  color: color$1,
@@ -28179,7 +28588,7 @@ const LockedLine = props => {
28179
28588
  line = /*#__PURE__*/React.createElement(LineType, {
28180
28589
  point1: point1.coord,
28181
28590
  point2: point2.coord,
28182
- color: color[color$1],
28591
+ color: lockedFigureColors[color$1],
28183
28592
  style: lineStyle
28184
28593
  });
28185
28594
  } else {
@@ -28193,8 +28602,8 @@ const LockedLine = props => {
28193
28602
  y: point1.coord[1],
28194
28603
  svgCircleProps: {
28195
28604
  style: {
28196
- fill: point1.filled ? color[point1.color] : color.white,
28197
- stroke: color[point1.color],
28605
+ fill: point1.filled ? lockedFigureColors[point1.color] : color.white,
28606
+ stroke: lockedFigureColors[point1.color],
28198
28607
  strokeWidth: spacing.xxxxSmall_2
28199
28608
  }
28200
28609
  }
@@ -28203,8 +28612,8 @@ const LockedLine = props => {
28203
28612
  y: point2.coord[1],
28204
28613
  svgCircleProps: {
28205
28614
  style: {
28206
- fill: point2.filled ? color[point2.color] : color.white,
28207
- stroke: color[point2.color],
28615
+ fill: point2.filled ? lockedFigureColors[point2.color] : color.white,
28616
+ stroke: lockedFigureColors[point2.color],
28208
28617
  strokeWidth: spacing.xxxxSmall_2
28209
28618
  }
28210
28619
  }
@@ -28223,8 +28632,8 @@ const LockedPoint = props => {
28223
28632
  y: y,
28224
28633
  svgCircleProps: {
28225
28634
  style: {
28226
- fill: filled ? color[color$1] : color.white,
28227
- stroke: color[color$1],
28635
+ fill: filled ? lockedFigureColors[color$1] : color.white,
28636
+ stroke: lockedFigureColors[color$1],
28228
28637
  strokeWidth: spacing.xxxxSmall_2
28229
28638
  }
28230
28639
  }
@@ -28577,7 +28986,8 @@ const MovableLine = props => {
28577
28986
  const defaultGraphConfig = {
28578
28987
  range: [[0, 1], [0, 1]],
28579
28988
  snapStep: [1, 1],
28580
- markings: "none"
28989
+ markings: "none",
28990
+ showTooltips: false
28581
28991
  };
28582
28992
  const GraphConfigContext = /*#__PURE__*/createContext(defaultGraphConfig);
28583
28993
  function useGraphConfig() {
@@ -28588,10 +28998,12 @@ function useGraphConfig() {
28588
28998
  // graph.
28589
28999
  const hitboxSizePx = 48;
28590
29000
  const StyledMovablePoint = props => {
29001
+ var _Object$entries$find$, _Object$entries$find;
28591
29002
  const {
28592
29003
  range,
28593
29004
  snapStep,
28594
- markings
29005
+ markings,
29006
+ showTooltips
28595
29007
  } = useGraphConfig();
28596
29008
  const hitboxRef = useRef(null);
28597
29009
  const {
@@ -28599,6 +29011,10 @@ const StyledMovablePoint = props => {
28599
29011
  onMove,
28600
29012
  color: color$1 = color.blue
28601
29013
  } = props;
29014
+
29015
+ // WB Tooltip requires a color name for the background color.
29016
+ // Since the color in props is a hex value, a reverse lookup is needed.
29017
+ const wbColorName = (_Object$entries$find$ = (_Object$entries$find = Object.entries(color).find(([_, value]) => value === color$1)) == null ? void 0 : _Object$entries$find[0]) != null ? _Object$entries$find$ : "blue";
28602
29018
  const {
28603
29019
  dragging
28604
29020
  } = useMovable({
@@ -28616,19 +29032,20 @@ const StyledMovablePoint = props => {
28616
29032
  const [[_, horizontalStartY]] = useTransform([0, yMin]);
28617
29033
  const [[__, horizontalEndY]] = useTransform([0, yMax]);
28618
29034
  const showHairlines = dragging && markings !== "none";
28619
- return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
29035
+ const hairlines = /*#__PURE__*/React.createElement("g", null, /*#__PURE__*/React.createElement("line", {
28620
29036
  x1: verticalStartX,
28621
29037
  y1: y,
28622
29038
  x2: verticalEndX,
28623
29039
  y2: y,
28624
- stroke: color.green
29040
+ stroke: color$1
28625
29041
  }), /*#__PURE__*/React.createElement("line", {
28626
29042
  x1: x,
28627
29043
  y1: horizontalStartY,
28628
29044
  x2: x,
28629
29045
  y2: horizontalEndY,
28630
- stroke: color.green
28631
- })), /*#__PURE__*/React.createElement("g", {
29046
+ stroke: color$1
29047
+ }));
29048
+ const svgForPoint = /*#__PURE__*/React.createElement("g", {
28632
29049
  ref: hitboxRef,
28633
29050
  className: pointClasses,
28634
29051
  tabIndex: 0,
@@ -28661,7 +29078,15 @@ const StyledMovablePoint = props => {
28661
29078
  fill: color$1
28662
29079
  },
28663
29080
  "data-testid": "movable-point__center"
28664
- })));
29081
+ }));
29082
+ return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && hairlines, showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
29083
+ autoUpdate: true,
29084
+ backgroundColor: wbColorName,
29085
+ content: `(${point[0]}, ${point[1]})`,
29086
+ contentStyle: {
29087
+ color: "white"
29088
+ }
29089
+ }, svgForPoint) : svgForPoint);
28665
29090
  };
28666
29091
 
28667
29092
  const SegmentGraph = props => {
@@ -29587,7 +30012,7 @@ class NumberInput extends React.Component {
29587
30012
  const placeholder = this.props.placeholder;
29588
30013
  return _.isFinite(placeholder) ? +placeholder : null;
29589
30014
  }
29590
- const result = firstNumericalParse(value);
30015
+ const result = firstNumericalParse(value, this.context.strings);
29591
30016
  return _.isFinite(result) ? result : this.props.value;
29592
30017
  };
29593
30018
  /* Set text input focus to this input */
@@ -29617,7 +30042,7 @@ class NumberInput extends React.Component {
29617
30042
  if (value == null) {
29618
30043
  return true;
29619
30044
  }
29620
- const val = firstNumericalParse(value);
30045
+ const val = firstNumericalParse(value, this.context.strings);
29621
30046
  const checkValidity = this.props.checkValidity;
29622
30047
  return _.isFinite(val) && checkValidity(val);
29623
30048
  };
@@ -29713,6 +30138,7 @@ class NumberInput extends React.Component {
29713
30138
  }
29714
30139
  }
29715
30140
  }
30141
+ NumberInput.contextType = PerseusI18nContext;
29716
30142
  NumberInput.propTypes = {
29717
30143
  value: PropTypes.number,
29718
30144
  format: PropTypes.string,
@@ -30448,7 +30874,8 @@ const MafsGraph = props => {
30448
30874
  value: {
30449
30875
  range: state.range,
30450
30876
  snapStep: state.snapStep,
30451
- markings: props.markings
30877
+ markings: props.markings,
30878
+ showTooltips: !!props.showTooltips
30452
30879
  }
30453
30880
  }, /*#__PURE__*/React.createElement(View, {
30454
30881
  style: {
@@ -31710,9 +32137,9 @@ class LegacyInteractiveGraph extends React.Component {
31710
32137
  // isClickToAddPoints() only applies to points and polygons
31711
32138
  if (this.isClickToAddPoints() && this.state.shouldShowInstructions) {
31712
32139
  if (this.props.graph.type === "point") {
31713
- instructions = i18n._("Click to add points");
32140
+ instructions = this.context.strings.addPoints;
31714
32141
  } else if (this.props.graph.type === "polygon") {
31715
- instructions = i18n._("Click to add vertices");
32142
+ instructions = this.context.strings.addVertices;
31716
32143
  }
31717
32144
  } else {
31718
32145
  instructions = undefined;
@@ -31752,6 +32179,7 @@ class LegacyInteractiveGraph extends React.Component {
31752
32179
  }));
31753
32180
  }
31754
32181
  }
32182
+ LegacyInteractiveGraph.contextType = PerseusI18nContext;
31755
32183
  LegacyInteractiveGraph.defaultProps = {
31756
32184
  labels: ["x", "y"],
31757
32185
  range: [[-10, 10], [-10, 10]],
@@ -31798,7 +32226,8 @@ class InteractiveGraph extends React.Component {
31798
32226
  ref: this.mafsRef,
31799
32227
  gridStep: gridStep,
31800
32228
  snapStep: snapStep,
31801
- box: box
32229
+ box: box,
32230
+ showTooltips: !!this.props.showTooltips
31802
32231
  }));
31803
32232
  }
31804
32233
  return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
@@ -33340,6 +33769,9 @@ var globalStyles = /*#__PURE__*/Object.freeze({
33340
33769
  });
33341
33770
 
33342
33771
  const AnswerChoices = props => {
33772
+ const {
33773
+ strings
33774
+ } = usePerseusI18n();
33343
33775
  const onAnswerChange = selected => {
33344
33776
  const {
33345
33777
  choices,
@@ -33357,6 +33789,7 @@ const AnswerChoices = props => {
33357
33789
  value: content,
33358
33790
  label: /*#__PURE__*/React.createElement(Renderer, {
33359
33791
  content: content,
33792
+ strings: strings,
33360
33793
  inline: true
33361
33794
  })
33362
33795
  }));
@@ -33386,14 +33819,13 @@ const AnswerChoices = props => {
33386
33819
  }));
33387
33820
  };
33388
33821
 
33389
- const strings = {
33390
- hideAnswersToggleLabel: i18n._("Hide answer choices")
33391
- };
33392
-
33393
33822
  const HideAnswersToggle = props => {
33394
33823
  const idFactory = useUniqueIdWithMock();
33395
33824
  const switchId = idFactory.get("hide-answers-toggle");
33396
33825
  const labelId = idFactory.get("hide-answers-label");
33826
+ const {
33827
+ strings
33828
+ } = usePerseusI18n();
33397
33829
  return /*#__PURE__*/React.createElement(View, {
33398
33830
  style: styles$g.switchWrapper
33399
33831
  }, /*#__PURE__*/React.createElement(Switch, {
@@ -33434,9 +33866,12 @@ const AnswerPill = props => {
33434
33866
  hovered
33435
33867
  } = props;
33436
33868
  const idFactory = useUniqueIdWithMock();
33437
- const answerString = selectedAnswers.length > 1 ?
33438
- // always need `ngettext` for variable numbers even if we don't use the singular, see https://khanacademy.slack.com/archives/C0918TZ5G/p1700163024293079
33439
- i18n.ngettext("%(num)s answer", "%(num)s answers", selectedAnswers.length) : selectedAnswers[0];
33869
+ const {
33870
+ strings
33871
+ } = usePerseusI18n();
33872
+ const answerString = selectedAnswers.length > 1 ? strings.answers({
33873
+ num: selectedAnswers.length
33874
+ }) : selectedAnswers[0];
33440
33875
 
33441
33876
  // It should no longer be possible to interact with an answer after it
33442
33877
  // has been labeled (answered) correctly.
@@ -33463,6 +33898,7 @@ const AnswerPill = props => {
33463
33898
  style: [style, popperStyle, styles$f.pill, correct && styles$f.correct, incorrect && styles$f.incorrect, (focused || hovered) && BringToFront]
33464
33899
  }, /*#__PURE__*/React.createElement(Renderer, {
33465
33900
  content: answerString,
33901
+ strings: strings,
33466
33902
  inline: true
33467
33903
  })));
33468
33904
  };
@@ -33557,10 +33993,8 @@ class Marker extends React.Component {
33557
33993
  const markerDisabled = showCorrectness === "correct";
33558
33994
  const active = hovered || focused;
33559
33995
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(View, {
33560
- style: [styles$e.marker, active && !markerDisabled && styles$e.markerActive]
33561
- // already translated in: /widgets/graded-group.tsx
33562
- ,
33563
- "aria-label": markerDisabled ? i18n._("Correct!") : label
33996
+ style: [styles$e.marker, active && !markerDisabled && styles$e.markerActive],
33997
+ "aria-label": markerDisabled ? this.context.strings.correctExcited : label
33564
33998
  }, this.renderIcon()), !!selected && showAnswer && /*#__PURE__*/React.createElement(AnswerPill, {
33565
33999
  selectedAnswers: selected,
33566
34000
  showCorrectness: showCorrectness,
@@ -33572,6 +34006,7 @@ class Marker extends React.Component {
33572
34006
  }));
33573
34007
  }
33574
34008
  }
34009
+ Marker.contextType = PerseusI18nContext;
33575
34010
  Marker.defaultProps = {
33576
34011
  selected: []
33577
34012
  };
@@ -34143,8 +34578,11 @@ class LabelImage extends React.Component {
34143
34578
  multipleAnswers,
34144
34579
  hideChoicesFromInstructions: hideChoices
34145
34580
  } = this.props;
34146
- const promptString = isMobile ? multipleAnswers ? i18n._("Tap each dot on the image to select all answers that apply.") : i18n._("Tap each dot on the image to select an answer.") : multipleAnswers ? i18n._("Click each dot on the image to select all answers that apply.") : i18n._("Click each dot on the image to select an answer.");
34147
- const choicesString = i18n._("Choices:");
34581
+ const {
34582
+ strings
34583
+ } = this.context;
34584
+ const promptString = isMobile ? multipleAnswers ? strings.tapMultiple : strings.tapSingle : multipleAnswers ? strings.clickMultiple : strings.clickSingle;
34585
+ const choicesString = strings.choices;
34148
34586
  return /*#__PURE__*/React.createElement("div", {
34149
34587
  className: classNames("perseus-label-image-widget-instructions", css(styles$d.instructions))
34150
34588
  }, /*#__PURE__*/React.createElement("div", {
@@ -34155,7 +34593,8 @@ class LabelImage extends React.Component {
34155
34593
  className: css(styles$d.instructionsChoice),
34156
34594
  key: index
34157
34595
  }, /*#__PURE__*/React.createElement(Renderer, {
34158
- content: choice
34596
+ content: choice,
34597
+ strings: strings
34159
34598
  })))));
34160
34599
  }
34161
34600
  render() {
@@ -34206,6 +34645,7 @@ class LabelImage extends React.Component {
34206
34645
  }));
34207
34646
  }
34208
34647
  }
34648
+ LabelImage.contextType = PerseusI18nContext;
34209
34649
  const styles$d = StyleSheet.create({
34210
34650
  instructions: {
34211
34651
  paddingBottom: 16
@@ -34519,7 +34959,8 @@ class Draggable extends React.Component {
34519
34959
  }, /*#__PURE__*/React.createElement(Renderer, {
34520
34960
  content: this.props.content,
34521
34961
  linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "draggable"),
34522
- onRender: this.props.onRender
34962
+ onRender: this.props.onRender,
34963
+ strings: this.context.strings
34523
34964
  }));
34524
34965
  }
34525
34966
  componentDidUpdate(prevProps) {
@@ -34544,6 +34985,7 @@ class Draggable extends React.Component {
34544
34985
  }
34545
34986
  }
34546
34987
  }
34988
+ Draggable.contextType = PerseusI18nContext;
34547
34989
  Draggable.defaultProps = {
34548
34990
  includePadding: true,
34549
34991
  type: ItemState.STATIC,
@@ -35104,12 +35546,14 @@ class Matcher extends React.Component {
35104
35546
  className: css(styles$b.column, styles$b.columnLabel)
35105
35547
  }, /*#__PURE__*/React.createElement(Renderer, {
35106
35548
  content: this.props.labels[0] || "...",
35107
- linterContext: this.props.linterContext
35549
+ linterContext: this.props.linterContext,
35550
+ strings: this.context.strings
35108
35551
  })), /*#__PURE__*/React.createElement("th", {
35109
35552
  className: css(styles$b.column, styles$b.columnRight, styles$b.columnLabel)
35110
35553
  }, /*#__PURE__*/React.createElement(Renderer, {
35111
35554
  content: this.props.labels[1] || "...",
35112
- linterContext: this.props.linterContext
35555
+ linterContext: this.props.linterContext,
35556
+ strings: this.context.strings
35113
35557
  }))), /*#__PURE__*/React.createElement("tr", {
35114
35558
  className: css(styles$b.row)
35115
35559
  }, /*#__PURE__*/React.createElement("td", {
@@ -35146,6 +35590,7 @@ class Matcher extends React.Component {
35146
35590
  })))));
35147
35591
  }
35148
35592
  }
35593
+ Matcher.contextType = PerseusI18nContext;
35149
35594
  Matcher.propTypes = {
35150
35595
  apiOptions: ApiOptions.propTypes,
35151
35596
  labels: PropTypes.array,
@@ -35431,8 +35876,58 @@ class Matrix extends React.Component {
35431
35876
  };
35432
35877
  };
35433
35878
  this.simpleValidate = rubric => {
35434
- // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Matrix'.
35435
- return Matrix.validate(this.getUserInput(), rubric);
35879
+ return Matrix.validate(this.getUserInput(), rubric, this.context.strings);
35880
+ };
35881
+ }
35882
+ static validate(state, rubric, strings) {
35883
+ const solution = rubric.answers;
35884
+ const supplied = state.answers;
35885
+ const solutionSize = getMatrixSize(solution);
35886
+ const suppliedSize = getMatrixSize(supplied);
35887
+ const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
35888
+ const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
35889
+ let message = null;
35890
+ let hasEmptyCell = false;
35891
+ let incorrect = false;
35892
+ _(suppliedSize[0]).times(row => {
35893
+ _(suppliedSize[1]).times(col => {
35894
+ if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
35895
+ hasEmptyCell = true;
35896
+ }
35897
+ if (!incorrectSize) {
35898
+ const validator = createValidator(solution[row][col], {
35899
+ simplify: true
35900
+ }, strings);
35901
+ const result = validator(supplied[row][col]);
35902
+ if (result.message) {
35903
+ // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
35904
+ message = result.message;
35905
+ }
35906
+ if (!result.correct) {
35907
+ incorrect = true;
35908
+ }
35909
+ }
35910
+ });
35911
+ });
35912
+ if (hasEmptyCell) {
35913
+ return {
35914
+ type: "invalid",
35915
+ message: strings.fillAllCells
35916
+ };
35917
+ }
35918
+ if (incorrectSize) {
35919
+ return {
35920
+ type: "points",
35921
+ earned: 0,
35922
+ total: 1,
35923
+ message: null
35924
+ };
35925
+ }
35926
+ return {
35927
+ type: "points",
35928
+ earned: incorrect ? 0 : 1,
35929
+ total: 1,
35930
+ message: message
35436
35931
  };
35437
35932
  }
35438
35933
  componentDidMount() {
@@ -35474,7 +35969,8 @@ class Matrix extends React.Component {
35474
35969
  className: "matrix-prefix"
35475
35970
  }, /*#__PURE__*/React.createElement(Renderer, {
35476
35971
  content: this.props.prefix,
35477
- linterContext: this.props.linterContext
35972
+ linterContext: this.props.linterContext,
35973
+ strings: this.context.strings
35478
35974
  })), /*#__PURE__*/React.createElement("div", {
35479
35975
  className: "matrix-input"
35480
35976
  }, /*#__PURE__*/React.createElement("div", {
@@ -35587,10 +36083,12 @@ class Matrix extends React.Component {
35587
36083
  className: "matrix-suffix"
35588
36084
  }, /*#__PURE__*/React.createElement(Renderer, {
35589
36085
  content: this.props.suffix,
35590
- linterContext: this.props.linterContext
36086
+ linterContext: this.props.linterContext,
36087
+ strings: this.context.strings
35591
36088
  })));
35592
36089
  }
35593
36090
  }
36091
+ Matrix.contextType = PerseusI18nContext;
35594
36092
  Matrix.defaultProps = {
35595
36093
  matrixBoardSize: [3, 3],
35596
36094
  answers: [[]],
@@ -35600,59 +36098,6 @@ Matrix.defaultProps = {
35600
36098
  apiOptions: ApiOptions.defaults,
35601
36099
  linterContext: linterContextDefault
35602
36100
  };
35603
- _.extend(Matrix, {
35604
- validate: function (state, rubric) {
35605
- const solution = rubric.answers;
35606
- const supplied = state.answers;
35607
- const solutionSize = getMatrixSize(solution);
35608
- const suppliedSize = getMatrixSize(supplied);
35609
- const incorrectSize = solutionSize[0] !== suppliedSize[0] || solutionSize[1] !== suppliedSize[1];
35610
- const createValidator = KhanAnswerTypes.number.createValidatorFunctional;
35611
- let message = null;
35612
- let hasEmptyCell = false;
35613
- let incorrect = false;
35614
- _(suppliedSize[0]).times(row => {
35615
- _(suppliedSize[1]).times(col => {
35616
- if (supplied[row][col] == null || supplied[row][col].toString().length === 0) {
35617
- hasEmptyCell = true;
35618
- }
35619
- if (!incorrectSize) {
35620
- const validator = createValidator(solution[row][col], {
35621
- simplify: true
35622
- });
35623
- const result = validator(supplied[row][col]);
35624
- if (result.message) {
35625
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
35626
- message = result.message;
35627
- }
35628
- if (!result.correct) {
35629
- incorrect = true;
35630
- }
35631
- }
35632
- });
35633
- });
35634
- if (hasEmptyCell) {
35635
- return {
35636
- type: "invalid",
35637
- message: i18n._("Make sure you fill in all cells in the matrix.")
35638
- };
35639
- }
35640
- if (incorrectSize) {
35641
- return {
35642
- type: "points",
35643
- earned: 0,
35644
- total: 1,
35645
- message: null
35646
- };
35647
- }
35648
- return {
35649
- type: "points",
35650
- earned: incorrect ? 0 : 1,
35651
- total: 1,
35652
- message: message
35653
- };
35654
- }
35655
- });
35656
36101
  const propTransform$1 = editorProps => {
35657
36102
  // Remove answers before passing to widget
35658
36103
  const blankAnswers = _(editorProps.matrixBoardSize[0]).times(function () {
@@ -36690,8 +37135,8 @@ class Molecule extends React.Component {
36690
37135
  // eslint-disable-next-line react/no-string-refs
36691
37136
  ,
36692
37137
  ref: "canvas"
36693
- }, i18n.$_("A molecular structure drawing. SMILES notation: %(content)s", {
36694
- content: this.props.smiles
37138
+ }, this.context.strings.molecularDrawing({
37139
+ content: this.props.smiles || ""
36695
37140
  }));
36696
37141
  if (this.state.error) {
36697
37142
  content = /*#__PURE__*/React.createElement("div", {
@@ -36703,6 +37148,7 @@ class Molecule extends React.Component {
36703
37148
  }, content);
36704
37149
  }
36705
37150
  }
37151
+ Molecule.contextType = PerseusI18nContext;
36706
37152
  class MoleculeWidget extends React.Component {
36707
37153
  constructor(...args) {
36708
37154
  super(...args);
@@ -37165,18 +37611,21 @@ class NumberLine extends React.Component {
37165
37611
  };
37166
37612
  }
37167
37613
  render() {
37614
+ const {
37615
+ strings
37616
+ } = this.context;
37168
37617
  const divisionRange = this.props.divisionRange;
37169
37618
  const divRangeString = divisionRange[0] + EN_DASH + divisionRange[1];
37170
37619
  const invalidNumDivisions = this.props.numDivisions < divisionRange[0] || this.props.numDivisions > divisionRange[1];
37171
37620
  const inequalityControls = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("input", {
37172
37621
  type: "button",
37173
37622
  className: "simple-button",
37174
- value: i18n._("Switch direction"),
37623
+ value: strings.switchDirection,
37175
37624
  onClick: this.handleReverse
37176
37625
  }), /*#__PURE__*/React.createElement("input", {
37177
37626
  type: "button",
37178
37627
  className: "simple-button",
37179
- value: _(["le", "ge"]).contains(this.props.rel) ? i18n._("Make circle open") : i18n._("Make circle filled"),
37628
+ value: _(["le", "ge"]).contains(this.props.rel) ? strings.circleOpen : strings.circleFilled,
37180
37629
  onClick: this.handleToggleStrict
37181
37630
  }));
37182
37631
  let tickCtrl;
@@ -37187,7 +37636,7 @@ class NumberLine extends React.Component {
37187
37636
  } else {
37188
37637
  Input = NumberInput;
37189
37638
  }
37190
- tickCtrl = /*#__PURE__*/React.createElement("label", null, i18n._("Number of divisions:"), " ", /*#__PURE__*/React.createElement(Input
37639
+ tickCtrl = /*#__PURE__*/React.createElement("label", null, strings.numDivisions, " ", /*#__PURE__*/React.createElement(Input
37191
37640
  // eslint-disable-next-line react/no-string-refs
37192
37641
  , {
37193
37642
  ref: "tick-ctrl",
@@ -37204,13 +37653,14 @@ class NumberLine extends React.Component {
37204
37653
  className: "perseus-widget " + "perseus-widget-interactive-number-line"
37205
37654
  }, tickCtrl, !this.isValid() ? /*#__PURE__*/React.createElement("div", {
37206
37655
  className: "perseus-error"
37207
- }, i18n.doNotTranslate("Invalid number line configuration.")) : this.props.isTickCtrl && invalidNumDivisions ? /*#__PURE__*/React.createElement("div", {
37656
+ }, "Invalid number line configuration.") : this.props.isTickCtrl && invalidNumDivisions ? /*#__PURE__*/React.createElement("div", {
37208
37657
  className: "perseus-error"
37209
- }, i18n.$_("Please make sure the number of divisions is " + "in the range %(divRangeString)s.", {
37658
+ }, strings.divisions({
37210
37659
  divRangeString: divRangeString
37211
37660
  })) : this._renderGraphie(), !this.props.static && this.props.isInequality && inequalityControls);
37212
37661
  }
37213
37662
  }
37663
+ NumberLine.contextType = PerseusI18nContext;
37214
37664
  NumberLine.defaultProps = {
37215
37665
  range: [0, 10],
37216
37666
  labelStyle: "decimal",
@@ -37437,7 +37887,8 @@ class Card extends React.Component {
37437
37887
  }, /*#__PURE__*/React.createElement("div", {
37438
37888
  className: className.join(" ")
37439
37889
  }, /*#__PURE__*/React.createElement(Renderer, _extends({}, rendererProps, {
37440
- linterContext: this.props.linterContext
37890
+ linterContext: this.props.linterContext,
37891
+ strings: this.context.strings
37441
37892
  }))));
37442
37893
  }
37443
37894
  shouldComponentUpdate(nextProps, nextState) {
@@ -37490,6 +37941,7 @@ class Card extends React.Component {
37490
37941
  document.removeEventListener("touchmove", this.onMouseMove);
37491
37942
  }
37492
37943
  }
37944
+ Card.contextType = PerseusI18nContext;
37493
37945
  Card.propTypes = {
37494
37946
  floating: PropTypes.bool.isRequired,
37495
37947
  animating: PropTypes.bool,
@@ -38779,7 +39231,7 @@ class HighlightSetRenderer extends React.PureComponent {
38779
39231
  }
38780
39232
  const hoveredHighlight = this.props.highlights[hoveredHighlightKey];
38781
39233
  return /*#__PURE__*/React.createElement(HighlightTooltip, {
38782
- label: i18n._("Remove highlight"),
39234
+ label: this.context.strings.removeHighlight,
38783
39235
  focusNode: hoveredHighlight.domRange.endContainer,
38784
39236
  focusOffset: hoveredHighlight.domRange.endOffset,
38785
39237
  offsetParent: this.props.offsetParent,
@@ -38809,6 +39261,7 @@ class HighlightSetRenderer extends React.PureComponent {
38809
39261
  })), this.props.editable && this._renderTooltip());
38810
39262
  }
38811
39263
  }
39264
+ HighlightSetRenderer.contextType = PerseusI18nContext;
38812
39265
 
38813
39266
  /* eslint-disable react/no-unsafe */
38814
39267
  class SelectionTracker extends React.PureComponent {
@@ -39009,7 +39462,7 @@ class HighlightingUI extends React.PureComponent {
39009
39462
  onRemoveHighlight: this.props.onRemoveHighlight,
39010
39463
  zIndexes: this.props.zIndexes
39011
39464
  }), this._selectionIsValid(trackedSelection) && !userIsMouseSelecting && /*#__PURE__*/React.createElement(HighlightTooltip, {
39012
- label: i18n._("Add highlight"),
39465
+ label: this.context.strings.addHighlight,
39013
39466
  onClick: () => this._handleAddHighlight(trackedSelection.proposedHighlight),
39014
39467
  focusNode: trackedSelection.focusNode,
39015
39468
  focusOffset: trackedSelection.focusOffset,
@@ -39017,6 +39470,7 @@ class HighlightingUI extends React.PureComponent {
39017
39470
  })));
39018
39471
  }
39019
39472
  }
39473
+ HighlightingUI.contextType = PerseusI18nContext;
39020
39474
 
39021
39475
  /**
39022
39476
  * This component, whenever its content updates, traverses the DOM to find
@@ -39307,16 +39761,30 @@ class RefStart extends React.Component {
39307
39761
  render() {
39308
39762
  return /*#__PURE__*/React.createElement("span", {
39309
39763
  style: REF_STYLE
39310
- }, i18n.doNotTranslate("_"));
39764
+ }, "_");
39311
39765
  }
39312
39766
  }
39313
39767
  class RefEnd extends React.Component {
39314
39768
  render() {
39315
39769
  return /*#__PURE__*/React.createElement("span", {
39316
39770
  style: REF_STYLE
39317
- }, i18n.doNotTranslate("_"));
39771
+ }, "_");
39318
39772
  }
39319
39773
  }
39774
+ const AltText = ({
39775
+ id,
39776
+ number
39777
+ }) => {
39778
+ const {
39779
+ strings
39780
+ } = usePerseusI18n();
39781
+ return /*#__PURE__*/React.createElement("span", {
39782
+ key: "alt-text",
39783
+ className: "perseus-sr-only"
39784
+ }, strings[id]({
39785
+ number
39786
+ }));
39787
+ };
39320
39788
  const rules = {
39321
39789
  newline: SimpleMarkdown.defaultRules.newline,
39322
39790
  paragraph: SimpleMarkdown.defaultRules.paragraph,
@@ -39485,12 +39953,10 @@ const rules = {
39485
39953
  "aria-hidden": "true"
39486
39954
  }, /*#__PURE__*/React.createElement("span", {
39487
39955
  style: SQUARE_LABEL_STYLE
39488
- }, node.content)), /*#__PURE__*/React.createElement("span", {
39489
- key: "alt-text",
39490
- className: "perseus-sr-only"
39491
- }, i18n.$_("[Marker for question %(number)s]", {
39956
+ }, node.content)), /*#__PURE__*/React.createElement(AltText, {
39957
+ id: "questionMarker",
39492
39958
  number: node.content
39493
- })), node.space ? "\u00A0" : null];
39959
+ }), node.space ? "\u00A0" : null];
39494
39960
  }
39495
39961
  },
39496
39962
  circleLabel: {
@@ -39511,12 +39977,10 @@ const rules = {
39511
39977
  "aria-hidden": true
39512
39978
  }, /*#__PURE__*/React.createElement("span", {
39513
39979
  style: CIRCLE_LABEL_STYLE
39514
- }, node.content)), /*#__PURE__*/React.createElement("span", {
39515
- key: "alt-text",
39516
- className: "perseus-sr-only"
39517
- }, i18n.$_("[Circle marker %(number)s]", {
39980
+ }, node.content)), /*#__PURE__*/React.createElement(AltText, {
39981
+ id: "circleMarker",
39518
39982
  number: node.content
39519
- })), node.space ? "\u00A0" : null];
39983
+ }), node.space ? "\u00A0" : null];
39520
39984
  }
39521
39985
  },
39522
39986
  squareBracketRef: {
@@ -39536,12 +40000,10 @@ const rules = {
39536
40000
  key: "visual-brackets",
39537
40001
  className: "perseus-passage-bracket-label",
39538
40002
  "aria-hidden": "true"
39539
- }, "[", node.content, "]"), /*#__PURE__*/React.createElement("span", {
39540
- key: "alt-text",
39541
- className: "perseus-sr-only"
39542
- }, i18n.$_("[Sentence %(number)s]", {
40003
+ }, "[", node.content, "]"), /*#__PURE__*/React.createElement(AltText, {
40004
+ id: "sentenceMarker",
39543
40005
  number: node.content
39544
- })), node.space ? "\u00A0" : null];
40006
+ }), node.space ? "\u00A0" : null];
39545
40007
  }
39546
40008
  },
39547
40009
  highlight: {
@@ -39932,15 +40394,18 @@ class Passage extends React.Component {
39932
40394
  _renderInstructions(parseState) {
39933
40395
  const firstQuestionNumber = parseState.firstQuestionRef;
39934
40396
  const firstSentenceRef = parseState.firstSentenceRef;
40397
+ const {
40398
+ strings
40399
+ } = this.context;
39935
40400
  let instructions = "";
39936
40401
  if (firstQuestionNumber) {
39937
- instructions += i18n._("The symbol %(questionSymbol)s indicates that question " + "%(questionNumber)s references this portion of the " + "passage.", {
40402
+ instructions += strings.symbolPassage({
39938
40403
  questionSymbol: "[[" + firstQuestionNumber + "]]",
39939
40404
  questionNumber: firstQuestionNumber
39940
40405
  });
39941
40406
  }
39942
40407
  if (firstSentenceRef) {
39943
- instructions += i18n._(" The symbol %(sentenceSymbol)s indicates that the " + "following sentence is referenced in a question.", {
40408
+ instructions += strings.symbolQuestion({
39944
40409
  sentenceSymbol: "[" + firstSentenceRef + "]"
39945
40410
  });
39946
40411
  }
@@ -39986,6 +40451,9 @@ class Passage extends React.Component {
39986
40451
  return PassageMarkdown.output(parsed);
39987
40452
  }
39988
40453
  render() {
40454
+ const {
40455
+ strings
40456
+ } = this.context;
39989
40457
  let lineNumbers;
39990
40458
  const nLines = this.state.nLines;
39991
40459
  if (this.props.showLineNumbers && nLines) {
@@ -39994,9 +40462,7 @@ class Passage extends React.Component {
39994
40462
  lineNumbers = _.range(1, nLines + 1).map(lineN => {
39995
40463
  const lineAt = lineN + this.state.startLineNumbersAfter;
39996
40464
  if (lineAt === 4) {
39997
- // we leave a space in i18n to disambiguate translations, see TP-5392
39998
- // I18N: a label next to a reading passage to denote the line number
39999
- const translatedLine = i18n._("Line ");
40465
+ const translatedLine = strings.lineLabel;
40000
40466
  return /*#__PURE__*/React.createElement("span", {
40001
40467
  key: "line-marker",
40002
40468
  className: "line-marker"
@@ -40026,13 +40492,14 @@ class Passage extends React.Component {
40026
40492
  className: "passage-title"
40027
40493
  }, /*#__PURE__*/React.createElement(Renderer, {
40028
40494
  content: this.props.passageTitle,
40029
- linterContext: this.props.linterContext
40495
+ linterContext: this.props.linterContext,
40496
+ strings: strings
40030
40497
  })), lineNumbers && /*#__PURE__*/React.createElement("div", {
40031
40498
  className: "line-numbers",
40032
40499
  "aria-hidden": true
40033
40500
  }, lineNumbers), !hasTitle && /*#__PURE__*/React.createElement("h3", {
40034
40501
  className: "perseus-sr-only"
40035
- }, i18n._("Beginning of reading passage.")), /*#__PURE__*/React.createElement("div", {
40502
+ }, strings.beginningPassage), /*#__PURE__*/React.createElement("div", {
40036
40503
  className: "passage-text"
40037
40504
  }, this.shouldRenderJipt() ?
40038
40505
  /*#__PURE__*/
@@ -40040,18 +40507,20 @@ class Passage extends React.Component {
40040
40507
  // content to a <Renderer /> which knows how
40041
40508
  // to handle rendering JIPT text.
40042
40509
  React.createElement(Renderer, {
40043
- content: this.props.passageText
40510
+ content: this.props.passageText,
40511
+ strings: strings
40044
40512
  }) : this._renderContent(parsedContent)), this._hasFootnotes() && [/*#__PURE__*/React.createElement("h4", {
40045
40513
  key: "footnote-start",
40046
40514
  className: "perseus-sr-only"
40047
- }, i18n._("Beginning of reading passage footnotes.")), /*#__PURE__*/React.createElement("div", {
40515
+ }, strings.beginningFootnotes), /*#__PURE__*/React.createElement("div", {
40048
40516
  key: "footnotes",
40049
40517
  className: "footnotes"
40050
40518
  }, this._renderFootnotes())], /*#__PURE__*/React.createElement("div", {
40051
40519
  className: "perseus-sr-only"
40052
- }, i18n._("End of reading passage.")))));
40520
+ }, strings.endPassage))));
40053
40521
  }
40054
40522
  }
40523
+ Passage.contextType = PerseusI18nContext;
40055
40524
  Passage.defaultProps = {
40056
40525
  passageTitle: "",
40057
40526
  passageText: "",
@@ -40102,10 +40571,12 @@ class PassageRefTarget extends React.Component {
40102
40571
  // inline={true}
40103
40572
  ,
40104
40573
  apiOptions: this.props.apiOptions,
40105
- linterContext: this.props.linterContext
40574
+ linterContext: this.props.linterContext,
40575
+ strings: this.context.strings
40106
40576
  });
40107
40577
  }
40108
40578
  }
40579
+ PassageRefTarget.contextType = PerseusI18nContext;
40109
40580
  PassageRefTarget.defaultProps = {
40110
40581
  content: "",
40111
40582
  linterContext: linterContextDefault
@@ -40289,11 +40760,11 @@ class Plotter extends React.Component {
40289
40760
  }
40290
40761
  }
40291
40762
  if ((isBar || isLine) && isMobile && !this.props.static) {
40292
- self.graphie.dragPrompt = graphie.label([c.dimX / 2, c.dimY / 2], i18n._("Drag handles to make graph"), "center", false).css("font-weight", "bold").css("color", KhanColors.KA_GREEN).css("display", "none");
40763
+ 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");
40293
40764
  }
40294
40765
  self.setupCategories(config);
40295
40766
  if (isTiledPlot && isMobile) {
40296
- self.graphie.dotPrompt = graphie.label([c.dimX / 2, c.dimY / 2], i18n._("Tap to add points"), "center", false).css("font-weight", "bold").css("color", KhanColors.KA_GREEN).css("display", "none");
40767
+ 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");
40297
40768
  }
40298
40769
  if (isTiledPlot) {
40299
40770
  self.drawPicHeights(self.state.values, prevState.values);
@@ -40908,6 +41379,7 @@ class Plotter extends React.Component {
40908
41379
  });
40909
41380
  }
40910
41381
  }
41382
+ Plotter.contextType = PerseusI18nContext;
40911
41383
  Plotter.defaultProps = {
40912
41384
  type: "bar",
40913
41385
  labels: ["", ""],
@@ -41179,7 +41651,7 @@ class Table extends React.Component {
41179
41651
  };
41180
41652
  this.simpleValidate = rubric => {
41181
41653
  // @ts-expect-error - TS2339 - Property 'validate' does not exist on type 'typeof Table'.
41182
- return Table.validate(this.getUserInput(), rubric);
41654
+ return Table.validate(this.getUserInput(), rubric, this.context.strings);
41183
41655
  };
41184
41656
  this._handleFocus = inputPath => {
41185
41657
  this.props.onFocus(inputPath);
@@ -41284,7 +41756,8 @@ class Table extends React.Component {
41284
41756
  key: i
41285
41757
  }, /*#__PURE__*/React.createElement(Renderer, {
41286
41758
  content: header,
41287
- linterContext: this.props.linterContext
41759
+ linterContext: this.props.linterContext,
41760
+ strings: this.context.strings
41288
41761
  }));
41289
41762
  }))), /*#__PURE__*/React.createElement("tbody", null, _(rows).times(r => {
41290
41763
  return /*#__PURE__*/React.createElement("tr", {
@@ -41306,6 +41779,7 @@ class Table extends React.Component {
41306
41779
  })));
41307
41780
  }
41308
41781
  }
41782
+ Table.contextType = PerseusI18nContext;
41309
41783
  Table.propTypes = {
41310
41784
  answers: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),
41311
41785
  editableHeaders: PropTypes.bool,
@@ -41333,7 +41807,7 @@ Table.defaultProps = function () {
41333
41807
  };
41334
41808
  }();
41335
41809
  _.extend(Table, {
41336
- validate: function (state, rubric) {
41810
+ validate: function (state, rubric, strings) {
41337
41811
  const filterNonEmpty = function filterNonEmpty(table) {
41338
41812
  return _.filter(table, function (row) {
41339
41813
  // Check if row has a cell that is nonempty
@@ -41371,7 +41845,7 @@ _.extend(Table, {
41371
41845
  const cellSolution = rowSolution[i];
41372
41846
  const validator = createValidator(cellSolution, {
41373
41847
  simplify: true
41374
- });
41848
+ }, strings);
41375
41849
  const result = validator(cellSupplied);
41376
41850
  if (result.message) {
41377
41851
  // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
@@ -42134,7 +42608,7 @@ class OldUnitInput extends React.Component {
42134
42608
  });
42135
42609
  };
42136
42610
  this.simpleValidate = (rubric, onInputError) => {
42137
- return OldUnitInput.validate(this.getUserInput(), rubric);
42611
+ return OldUnitInput.validate(this.getUserInput(), rubric, this.context.strings);
42138
42612
  };
42139
42613
  this.getUserInput = () => {
42140
42614
  return this.props.value;
@@ -42187,13 +42661,13 @@ class OldUnitInput extends React.Component {
42187
42661
  return "unit";
42188
42662
  };
42189
42663
  }
42190
- static validate(userInput, rubric) {
42664
+ static validate(userInput, rubric, strings) {
42191
42665
  const answer = KAS.unitParse(rubric.value).expr;
42192
42666
  const guess = KAS.unitParse(userInput);
42193
42667
  if (!guess.parsed) {
42194
42668
  return {
42195
42669
  type: "invalid",
42196
- message: i18n._("I couldn't understand those units.")
42670
+ message: strings.dontUnderstandUnits
42197
42671
  };
42198
42672
  }
42199
42673
 
@@ -42208,8 +42682,7 @@ class OldUnitInput extends React.Component {
42208
42682
  const sigfigs = rubric.sigfigs;
42209
42683
  const sigfigsCorrect = countSigfigs(guess.coefficient) === sigfigs;
42210
42684
  if (!sigfigsCorrect) {
42211
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42212
- message = i18n._("Check your significant figures.");
42685
+ message = strings.checkSigFigs;
42213
42686
  }
42214
42687
 
42215
42688
  // now we need to check that the answer is correct to the precision we
@@ -42228,8 +42701,7 @@ class OldUnitInput extends React.Component {
42228
42701
  numericallyCorrect = false;
42229
42702
  }
42230
42703
  if (!numericallyCorrect) {
42231
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42232
- message = i18n._("That answer is numerically incorrect.");
42704
+ message = strings.answerNumericallyIncorrect;
42233
42705
  }
42234
42706
  let kasCorrect;
42235
42707
  const guessUnit = primUnits(guess.expr.simplify());
@@ -42249,8 +42721,7 @@ class OldUnitInput extends React.Component {
42249
42721
  });
42250
42722
  }
42251
42723
  if (!kasCorrect) {
42252
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'null'.
42253
- message = i18n._("Check your units.");
42724
+ message = strings.checkUnits;
42254
42725
  }
42255
42726
  const correct = kasCorrect && numericallyCorrect && sigfigsCorrect;
42256
42727
  return {
@@ -42299,12 +42770,13 @@ class OldUnitInput extends React.Component {
42299
42770
  style: {
42300
42771
  display: "none"
42301
42772
  }
42302
- }, i18n._("I don't understand that")));
42773
+ }, this.context.strings.dontUnderstand));
42303
42774
  }
42304
42775
  }
42305
42776
 
42306
42777
  // Extract the primitive units from a unit expression. This first simplifies
42307
42778
  // `expr` to a `Mul` like "5 kg m / s^2" then removes the first term.
42779
+ OldUnitInput.contextType = PerseusI18nContext;
42308
42780
  OldUnitInput.defaultProps = {
42309
42781
  apiOptions: ApiOptions.defaults,
42310
42782
  value: ""
@@ -42355,9 +42827,12 @@ const VideoTranscriptLink = props => {
42355
42827
  // which is used by our GraphQL framework.
42356
42828
  // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'VideoKind'.
42357
42829
  const result = useVideo(id, kind);
42830
+ const {
42831
+ strings
42832
+ } = usePerseusI18n();
42358
42833
  switch (result.status) {
42359
42834
  case "loading":
42360
- return /*#__PURE__*/React.createElement(View, null, i18n._("Loading..."));
42835
+ return /*#__PURE__*/React.createElement(View, null, strings.loading);
42361
42836
  case "success":
42362
42837
  {
42363
42838
  var _result$data;
@@ -42370,14 +42845,14 @@ const VideoTranscriptLink = props => {
42370
42845
  href: "/transcript/" + ((video == null ? void 0 : video.contentId) || "videoNotFound"),
42371
42846
  target: "_blank",
42372
42847
  className: "visited-no-recolor"
42373
- }, i18n._("See video transcript")));
42848
+ }, strings.videoTranscript));
42374
42849
  }
42375
42850
  case "error":
42376
- return /*#__PURE__*/React.createElement(View, null, i18n._("Something went wrong."));
42851
+ return /*#__PURE__*/React.createElement(View, null, strings.somethingWrong);
42377
42852
  case "aborted":
42378
- return /*#__PURE__*/React.createElement(View, null, i18n._("Something went wrong."));
42853
+ return /*#__PURE__*/React.createElement(View, null, strings.somethingWrong);
42379
42854
  default:
42380
- return /*#__PURE__*/React.createElement(View, null, i18n._("Something went wrong."));
42855
+ return /*#__PURE__*/React.createElement(View, null, strings.somethingWrong);
42381
42856
  }
42382
42857
  };
42383
42858
  const styles$4 = StyleSheet.create({
@@ -42469,7 +42944,7 @@ class Video extends React.Component {
42469
42944
  key: location + this.props.alignment
42470
42945
  }, /*#__PURE__*/React.createElement(View, {
42471
42946
  style: a11y.srOnly
42472
- }, i18n._("Khan Academy video wrapper")), /*#__PURE__*/React.createElement("iframe", {
42947
+ }, this.context.strings.videoWrapper), /*#__PURE__*/React.createElement("iframe", {
42473
42948
  // TODO(joshuan): Consider not using iframes when we're
42474
42949
  // loading this from webapp. This iframe is problematic
42475
42950
  // for screenreaders.
@@ -42484,6 +42959,7 @@ class Video extends React.Component {
42484
42959
  }));
42485
42960
  }
42486
42961
  }
42962
+ Video.contextType = PerseusI18nContext;
42487
42963
  var Video$1 = {
42488
42964
  name: "video",
42489
42965
  displayName: "Video",
@@ -42533,7 +43009,7 @@ const init = function init(options) {
42533
43009
 
42534
43010
  // This file is processed by a Rollup plugin (replace) to inject the production
42535
43011
  const libName = "@khanacademy/perseus";
42536
- const libVersion = "21.6.0";
43012
+ const libVersion = "22.0.0";
42537
43013
  addLibraryVersionToPerseusDebug(libName, libVersion);
42538
43014
 
42539
43015
  const apiVersion = {
@@ -42699,8 +43175,9 @@ class ArticleRenderer extends React.Component {
42699
43175
  }
42700
43176
  }),
42701
43177
  linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "article"),
42702
- legacyPerseusLint: this.props.legacyPerseusLint
42703
- }, this.props.dependencies)));
43178
+ legacyPerseusLint: this.props.legacyPerseusLint,
43179
+ strings: this.context.strings
43180
+ })));
42704
43181
  });
42705
43182
  return /*#__PURE__*/React.createElement("div", {
42706
43183
  className: classes
@@ -42709,6 +43186,7 @@ class ArticleRenderer extends React.Component {
42709
43186
  }, sections));
42710
43187
  }
42711
43188
  }
43189
+ ArticleRenderer.contextType = PerseusI18nContext;
42712
43190
  ArticleRenderer.defaultProps = {
42713
43191
  apiOptions: ApiOptions.defaults,
42714
43192
  useNewStyles: false,
@@ -42761,7 +43239,7 @@ class HintRenderer extends React.Component {
42761
43239
  tabIndex: "-1"
42762
43240
  }, !apiOptions.isMobile && /*#__PURE__*/React.createElement("span", {
42763
43241
  className: "perseus-sr-only"
42764
- }, i18n._("Hint #%(pos)s", {
43242
+ }, this.context.strings.hintPos({
42765
43243
  pos: pos + 1
42766
43244
  })), !apiOptions.isMobile && totalHints != null && pos != null && /*#__PURE__*/React.createElement("span", {
42767
43245
  className: "perseus-hint-label",
@@ -42778,11 +43256,13 @@ class HintRenderer extends React.Component {
42778
43256
  images: hint.images,
42779
43257
  apiOptions: rendererApiOptions,
42780
43258
  findExternalWidgets: this.props.findExternalWidgets,
42781
- linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "hint")
43259
+ linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "hint"),
43260
+ strings: this.context.strings
42782
43261
  }))
42783
43262
  );
42784
43263
  }
42785
43264
  }
43265
+ HintRenderer.contextType = PerseusI18nContext;
42786
43266
  HintRenderer.defaultProps = {
42787
43267
  linterContext: PerseusLinter.linterContextDefault
42788
43268
  };
@@ -42964,7 +43444,7 @@ class HintsRenderer extends React.Component {
42964
43444
  className: classNames$1
42965
43445
  }, apiOptions.isMobile && hintsVisible > 0 && /*#__PURE__*/React.createElement("div", {
42966
43446
  className: css(styles$2.mobileHintStylesHintTitle, sharedStyles.responsiveLabel)
42967
- }, i18n._("Hints")), hints, showGetAnotherHint && /*#__PURE__*/React.createElement("button", {
43447
+ }, this.context.strings.hints), hints, showGetAnotherHint && /*#__PURE__*/React.createElement("button", {
42968
43448
  // @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>'.
42969
43449
  rel: "button",
42970
43450
  className: css(styles$2.linkButton, styles$2.getAnotherHintButton, apiOptions.isMobile && styles$2.mobileHintStylesGetAnotherHintButton),
@@ -42979,9 +43459,10 @@ class HintsRenderer extends React.Component {
42979
43459
  className: css(styles$2.plusText, apiOptions.isMobile && styles$2.mobileHintStylesPlusText)
42980
43460
  }, "+"), /*#__PURE__*/React.createElement("span", {
42981
43461
  className: css(styles$2.getAnotherHintText)
42982
- }, i18n._("Get another hint"), " ", hintRatioCopy)));
43462
+ }, this.context.strings.getAnotherHint, " ", hintRatioCopy)));
42983
43463
  }
42984
43464
  }
43465
+ HintsRenderer.contextType = PerseusI18nContext;
42985
43466
  HintsRenderer.defaultProps = {
42986
43467
  linterContext: PerseusLinter.linterContextDefault
42987
43468
  };
@@ -43372,14 +43853,16 @@ class ServerItemRenderer extends React.Component {
43372
43853
  content: this.props.item.question.content,
43373
43854
  widgets: this.props.item.question.widgets,
43374
43855
  images: this.props.item.question.images,
43375
- linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "question")
43856
+ linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "question"),
43857
+ strings: this.context.strings
43376
43858
  }, this.props.dependencies)));
43377
43859
  const hintsRenderer = /*#__PURE__*/React.createElement(HintsRenderer, {
43378
43860
  hints: this.props.item.hints,
43379
43861
  hintsVisible: this.props.hintsVisible,
43380
43862
  apiOptions: apiOptions,
43381
43863
  ref: elem => this.hintsRenderer = elem,
43382
- linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "hints")
43864
+ linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "hints"),
43865
+ strings: this.context.strings
43383
43866
  });
43384
43867
  return /*#__PURE__*/React.createElement(DependenciesContext.Provider, {
43385
43868
  value: this.props.dependencies
@@ -43393,6 +43876,7 @@ class ServerItemRenderer extends React.Component {
43393
43876
  }, hintsRenderer)));
43394
43877
  }
43395
43878
  }
43879
+ ServerItemRenderer.contextType = PerseusI18nContext;
43396
43880
  ServerItemRenderer.defaultProps = {
43397
43881
  apiOptions: {},
43398
43882
  // a deep default is done in `this.update()`
@@ -43555,115 +44039,6 @@ var constants = /*#__PURE__*/Object.freeze({
43555
44039
  perseusFrameBorderWidth: perseusFrameBorderWidth
43556
44040
  });
43557
44041
 
43558
- // TODO(FEI-4010): Remove `Perseus` prefix for all types here
43559
- // TODO(FEI-4011): Use types generated by https://github.com/jaredly/generate-perseus-flowtypes
43560
-
43561
- // Range is replaced within this file with Interval, but it is used elsewhere
43562
- // and exported from the package, so we need to keep it around.
43563
- // TODO(FEI-5054): Figure out how to get global .d.ts files working with monorepos
43564
- const ItemExtras = [
43565
- // The user might benefit from using a Scientific Calculator. Provided on Khan Academy when true
43566
- "calculator",
43567
- // The user might benefit from using a statistics Chi Squared Table like https://people.richland.edu/james/lecture/m170/tbl-chi.html
43568
- "chi2Table",
43569
- // The user might benefit from a monthly payments calculator. Provided on Khan Academy when true
43570
- "financialCalculatorMonthlyPayment",
43571
- // The user might benefit from a total amount calculator. Provided on Khan Academy when true
43572
- "financialCalculatorTotalAmount",
43573
- // The user might benefit from a time to pay off calculator. Provided on Khan Academy when true
43574
- "financialCalculatorTimeToPayOff",
43575
- // The user might benefit from using a Periodic Table of Elements. Provided on Khan Academy when true
43576
- "periodicTable",
43577
- // The user might benefit from using a Periodic Table of Elements with key. Provided on Khan Academy when true
43578
- "periodicTableWithKey",
43579
- // The user might benefit from using a statistics T Table like https://www.statisticshowto.com/tables/t-distribution-table/
43580
- "tTable",
43581
- // The user might benefit from using a statistics Z Table like https://www.ztable.net/
43582
- "zTable"];
43583
-
43584
- // prettier-ignore
43585
-
43586
- // prettier-ignore
43587
-
43588
- // prettier-ignore
43589
-
43590
- // prettier-ignore
43591
-
43592
- // prettier-ignore
43593
-
43594
- // prettier-ignore
43595
-
43596
- // prettier-ignore
43597
-
43598
- // prettier-ignore
43599
-
43600
- // prettier-ignore
43601
-
43602
- // prettier-ignore
43603
-
43604
- // prettier-ignore
43605
-
43606
- // prettier-ignore
43607
-
43608
- // prettier-ignore
43609
-
43610
- // prettier-ignore
43611
-
43612
- // prettier-ignore
43613
-
43614
- // prettier-ignore
43615
-
43616
- // prettier-ignore
43617
-
43618
- // prettier-ignore
43619
-
43620
- // prettier-ignore
43621
-
43622
- // prettier-ignore
43623
-
43624
- // prettier-ignore
43625
-
43626
- // prettier-ignore
43627
-
43628
- // prettier-ignore
43629
-
43630
- // prettier-ignore
43631
-
43632
- // prettier-ignore
43633
-
43634
- // prettier-ignore
43635
-
43636
- // prettier-ignore
43637
-
43638
- // prettier-ignore
43639
-
43640
- // prettier-ignore
43641
-
43642
- // prettier-ignore
43643
-
43644
- // prettier-ignore
43645
-
43646
- // prettier-ignore
43647
-
43648
- // prettier-ignore
43649
-
43650
- // prettier-ignore
43651
-
43652
- // prettier-ignore
43653
-
43654
- // prettier-ignore
43655
-
43656
- //prettier-ignore
43657
-
43658
- // A background image applied to various widgets.
43659
-
43660
- const PerseusExpressionAnswerFormConsidered = ["correct", "wrong", "ungraded"];
43661
-
43662
- // 2D range: xMin, xMax, yMin, yMax
43663
-
43664
- const lockedFigureColors = ["purple", "blue", "teal", "green", "gold", "red", "pink"];
43665
- const plotterPlotTypes = ["bar", "line", "pic", "histogram", "dotplot"];
43666
-
43667
44042
  /**
43668
44043
  * Traverses a {content, widgets, images} renderer props object,
43669
44044
  * such as `itemData.question`
@@ -44272,7 +44647,9 @@ class MultiRenderer extends React.Component {
44272
44647
  // `<Renderer>`. I'm not sure how else to do this.
44273
44648
  const _this$props = this.props,
44274
44649
  otherProps = _objectWithoutPropertiesLoose(_this$props, _excluded);
44275
- return otherProps;
44650
+ return _extends({}, otherProps, {
44651
+ strings: this.context.strings
44652
+ });
44276
44653
  }
44277
44654
 
44278
44655
  /**
@@ -44523,7 +44900,7 @@ class MultiRenderer extends React.Component {
44523
44900
  if (this.state.renderError) {
44524
44901
  return /*#__PURE__*/React.createElement("div", {
44525
44902
  className: css(styles.error)
44526
- }, i18n.$_("Error rendering: %(error)s", {
44903
+ }, this.context.strings.errorRendering({
44527
44904
  error: String(this.state.renderError)
44528
44905
  }));
44529
44906
  }
@@ -44537,6 +44914,7 @@ class MultiRenderer extends React.Component {
44537
44914
  }));
44538
44915
  }
44539
44916
  }
44917
+ MultiRenderer.contextType = PerseusI18nContext;
44540
44918
  const styles = StyleSheet.create({
44541
44919
  error: {
44542
44920
  color: "red"
@@ -45632,5 +46010,5 @@ const EditorJsonify = {
45632
46010
  }
45633
46011
  };
45634
46012
 
45635
- export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, violatingWidgets, allWidgets as widgets };
46013
+ export { ApiOptions, ArrowPicker, ArticleRenderer, BaseRadio, Categorizer$1 as Categorizer, changeable as Changeable, ClassNames, ColorPicker, DashPicker, dependencies as Dependencies, EditorJsonify, ElementContainer, Errors, Expression, util as GrapherUtil, Grapher$1 as GrapherWidget, HintRenderer, HintsRenderer, InputNumber$1 as InputNumber, InteractiveGraph$1 as InteractiveGraphWidget, ItemExtras, JiptParagraphs, KhanColors, KhanMath, context as LoadingContext, Log, Matrix$1 as MatrixWidget, MultiItems, NumericInput$1 as NumericInput, PerseusError, PerseusExpressionAnswerFormConsidered, PerseusI18nContext, PerseusI18nContextProvider, PerseusMarkdown, Plotter$1 as PlotterWidget, QUESTION_WIDGETS, Radio, Renderer, ref as ServerItemRenderer, sizingUtils as SizingUtils, Table$1 as TableWidget, Util, denylist as WIDGET_PROP_DENYLIST, WidgetJsonifyDeprecated, widgets$1 as Widgets, addWidget, apiVersion, buildEmptyItemTreeForShape, buildMapper, components, constants, contentHasWidgetType, convertWidgetNameToEnum, displaySigFigs, generateTestCategorizerWidget, generateTestExpressionWidget, generateTestInteractiveGraphWidget, generateTestNumericInputWidget, generateTestPerseusItem, generateTestRadioWidget, genericPerseusItemData, getAnswerFromUserInput, getAnswersFromWidgets, getCorrectAnswerForWidgetId, getValidWidgetIds, getWidgetFromWidgetMap, getWidgetIdsFromContent, getWidgetIdsFromContentByType, getWidgetTypeByWidgetId, getWidgetsFromWidgetMap, getWidgetsMapFromItemData, globalConstants, globalStyles, iconPaths as icons, init, injectWidgets, interactiveSizes$1 as interactiveSizes, isItemRenderableByVersion, isWidgetIdInContent, isWrongAnswerSupported, itemDataVersion, itemToTree, ItemVersion as itemVersion, libVersion, lockedFigureColors, mediaQueries, parsePerseusItem, plotterPlotTypes, preprocessTex, registerAllWidgetsForTesting, shapes, shouldHaveIndividualAnswer, traverse, usePerseusI18n, violatingWidgets, allWidgets as widgets };
45636
46014
  //# sourceMappingURL=index.js.map