a11y-i18n.src.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /**
  2. * Accessibility module - internationalization support
  3. *
  4. * (c) 2010-2019 Highsoft AS
  5. * Author: Øystein Moseng
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. import H from '../parts/Globals.js';
  11. import '../parts/Utilities.js';
  12. var pick = H.pick;
  13. /**
  14. * String trim that works for IE6-8 as well.
  15. *
  16. * @private
  17. * @function stringTrim
  18. *
  19. * @param {string} str
  20. * The input string
  21. *
  22. * @return {string}
  23. * The trimmed string
  24. */
  25. function stringTrim(str) {
  26. return str.trim && str.trim() || str.replace(/^\s+|\s+$/g, '');
  27. }
  28. /**
  29. * i18n utility function. Format a single array or plural statement in a format
  30. * string. If the statement is not an array or plural statement, returns the
  31. * statement within brackets. Invalid array statements return an empty string.
  32. *
  33. * @private
  34. * @function formatExtendedStatement
  35. *
  36. * @param {string} statement
  37. *
  38. * @param {Highcharts.Dictionary<*>} ctx
  39. * Context to apply to the format string.
  40. *
  41. * @return {string}
  42. */
  43. function formatExtendedStatement(statement, ctx) {
  44. var eachStart = statement.indexOf('#each('),
  45. pluralStart = statement.indexOf('#plural('),
  46. indexStart = statement.indexOf('['),
  47. indexEnd = statement.indexOf(']'),
  48. arr,
  49. result;
  50. // Dealing with an each-function?
  51. if (eachStart > -1) {
  52. var eachEnd = statement.slice(eachStart).indexOf(')') + eachStart,
  53. preEach = statement.substring(0, eachStart),
  54. postEach = statement.substring(eachEnd + 1),
  55. eachStatement = statement.substring(eachStart + 6, eachEnd),
  56. eachArguments = eachStatement.split(','),
  57. lenArg = Number(eachArguments[1]),
  58. len;
  59. result = '';
  60. arr = ctx[eachArguments[0]];
  61. if (arr) {
  62. lenArg = isNaN(lenArg) ? arr.length : lenArg;
  63. len = lenArg < 0 ?
  64. arr.length + lenArg :
  65. Math.min(lenArg, arr.length); // Overshoot
  66. // Run through the array for the specified length
  67. for (var i = 0; i < len; ++i) {
  68. result += preEach + arr[i] + postEach;
  69. }
  70. }
  71. return result.length ? result : '';
  72. }
  73. // Dealing with a plural-function?
  74. if (pluralStart > -1) {
  75. var pluralEnd = statement.slice(pluralStart).indexOf(')') + pluralStart,
  76. pluralStatement = statement.substring(pluralStart + 8, pluralEnd),
  77. pluralArguments = pluralStatement.split(','),
  78. num = Number(ctx[pluralArguments[0]]);
  79. switch (num) {
  80. case 0:
  81. result = pick(pluralArguments[4], pluralArguments[1]);
  82. break;
  83. case 1:
  84. result = pick(pluralArguments[2], pluralArguments[1]);
  85. break;
  86. case 2:
  87. result = pick(pluralArguments[3], pluralArguments[1]);
  88. break;
  89. default:
  90. result = pluralArguments[1];
  91. }
  92. return result ? stringTrim(result) : '';
  93. }
  94. // Array index
  95. if (indexStart > -1) {
  96. var arrayName = statement.substring(0, indexStart),
  97. ix = Number(statement.substring(indexStart + 1, indexEnd)),
  98. val;
  99. arr = ctx[arrayName];
  100. if (!isNaN(ix) && arr) {
  101. if (ix < 0) {
  102. val = arr[arr.length + ix];
  103. // Handle negative overshoot
  104. if (val === undefined) {
  105. val = arr[0];
  106. }
  107. } else {
  108. val = arr[ix];
  109. // Handle positive overshoot
  110. if (val === undefined) {
  111. val = arr[arr.length - 1];
  112. }
  113. }
  114. }
  115. return val !== undefined ? val : '';
  116. }
  117. // Standard substitution, delegate to H.format or similar
  118. return '{' + statement + '}';
  119. }
  120. /**
  121. * i18n formatting function. Extends Highcharts.format() functionality by also
  122. * handling arrays and plural conditionals. Arrays can be indexed as follows:
  123. *
  124. * - Format: 'This is the first index: {myArray[0]}. The last: {myArray[-1]}.'
  125. *
  126. * - Context: { myArray: [0, 1, 2, 3, 4, 5] }
  127. *
  128. * - Result: 'This is the first index: 0. The last: 5.'
  129. *
  130. *
  131. * They can also be iterated using the #each() function. This will repeat the
  132. * contents of the bracket expression for each element. Example:
  133. *
  134. * - Format: 'List contains: {#each(myArray)cm }'
  135. *
  136. * - Context: { myArray: [0, 1, 2] }
  137. *
  138. * - Result: 'List contains: 0cm 1cm 2cm '
  139. *
  140. *
  141. * The #each() function optionally takes a length parameter. If positive, this
  142. * parameter specifies the max number of elements to iterate through. If
  143. * negative, the function will subtract the number from the length of the array.
  144. * Use this to stop iterating before the array ends. Example:
  145. *
  146. * - Format: 'List contains: {#each(myArray, -1) }and {myArray[-1]}.'
  147. *
  148. * - Context: { myArray: [0, 1, 2, 3] }
  149. *
  150. * - Result: 'List contains: 0, 1, 2, and 3.'
  151. *
  152. *
  153. * Use the #plural() function to pick a string depending on whether or not a
  154. * context object is 1. Arguments are #plural(obj, plural, singular). Example:
  155. *
  156. * - Format: 'Has {numPoints} {#plural(numPoints, points, point}.'
  157. *
  158. * - Context: { numPoints: 5 }
  159. *
  160. * - Result: 'Has 5 points.'
  161. *
  162. *
  163. * Optionally there are additional parameters for dual and none: #plural(obj,
  164. * plural, singular, dual, none). Example:
  165. *
  166. * - Format: 'Has {#plural(numPoints, many points, one point, two points,
  167. * none}.'
  168. *
  169. * - Context: { numPoints: 2 }
  170. *
  171. * - Result: 'Has two points.'
  172. *
  173. *
  174. * The dual or none parameters will take precedence if they are supplied.
  175. *
  176. *
  177. * @function Highcharts.i18nFormat
  178. * @requires a11y-i18n
  179. *
  180. * @param {string} formatString
  181. * The string to format.
  182. *
  183. * @param {Highcharts.Dictionary<*>} context
  184. * Context to apply to the format string.
  185. *
  186. * @param {Highcharts.Time} time
  187. * A `Time` instance for date formatting, passed on to H.format().
  188. *
  189. * @return {string}
  190. * The formatted string.
  191. */
  192. H.i18nFormat = function (formatString, context, time) {
  193. var getFirstBracketStatement = function (sourceStr, offset) {
  194. var str = sourceStr.slice(offset || 0),
  195. startBracket = str.indexOf('{'),
  196. endBracket = str.indexOf('}');
  197. if (startBracket > -1 && endBracket > startBracket) {
  198. return {
  199. statement: str.substring(startBracket + 1, endBracket),
  200. begin: offset + startBracket + 1,
  201. end: offset + endBracket
  202. };
  203. }
  204. },
  205. tokens = [],
  206. bracketRes,
  207. constRes,
  208. cursor = 0;
  209. // Tokenize format string into bracket statements and constants
  210. do {
  211. bracketRes = getFirstBracketStatement(formatString, cursor);
  212. constRes = formatString.substring(
  213. cursor,
  214. bracketRes && bracketRes.begin - 1
  215. );
  216. // If we have constant content before this bracket statement, add it
  217. if (constRes.length) {
  218. tokens.push({
  219. value: constRes,
  220. type: 'constant'
  221. });
  222. }
  223. // Add the bracket statement
  224. if (bracketRes) {
  225. tokens.push({
  226. value: bracketRes.statement,
  227. type: 'statement'
  228. });
  229. }
  230. cursor = bracketRes && bracketRes.end + 1;
  231. } while (bracketRes);
  232. // Perform the formatting. The formatArrayStatement function returns the
  233. // statement in brackets if it is not an array statement, which means it
  234. // gets picked up by H.format below.
  235. tokens.forEach(function (token) {
  236. if (token.type === 'statement') {
  237. token.value = formatExtendedStatement(token.value, context);
  238. }
  239. });
  240. // Join string back together and pass to H.format to pick up non-array
  241. // statements.
  242. return H.format(tokens.reduce(function (acc, cur) {
  243. return acc + cur.value;
  244. }, ''), context, time);
  245. };
  246. /**
  247. * Apply context to a format string from lang options of the chart.
  248. *
  249. * @function Highcharts.Chart#langFormat
  250. * @requires a11y-i18n
  251. *
  252. * @param {string} langKey
  253. * Key (using dot notation) into lang option structure.
  254. *
  255. * @param {Highcharts.Dictionary<*>} context
  256. * Context to apply to the format string.
  257. *
  258. * @return {string}
  259. * The formatted string.
  260. */
  261. H.Chart.prototype.langFormat = function (langKey, context, time) {
  262. var keys = langKey.split('.'),
  263. formatString = this.options.lang,
  264. i = 0;
  265. for (; i < keys.length; ++i) {
  266. formatString = formatString && formatString[keys[i]];
  267. }
  268. return typeof formatString === 'string' && H.i18nFormat(
  269. formatString, context, time
  270. );
  271. };
  272. H.setOptions({
  273. lang: {
  274. /**
  275. * Configure the accessibility strings in the chart. Requires the
  276. * [accessibility module](//code.highcharts.com/modules/accessibility.js)
  277. * to be loaded. For a description of the module and information on its
  278. * features, see [Highcharts Accessibility](
  279. * http://www.highcharts.com/docs/chart-concepts/accessibility).
  280. *
  281. * For more dynamic control over the accessibility functionality, see
  282. * [accessibility.pointDescriptionFormatter](
  283. * accessibility.pointDescriptionFormatter),
  284. * [accessibility.seriesDescriptionFormatter](
  285. * accessibility.seriesDescriptionFormatter), and
  286. * [accessibility.screenReaderSectionFormatter](
  287. * accessibility.screenReaderSectionFormatter).
  288. *
  289. * @since 6.0.6
  290. * @optionparent lang.accessibility
  291. */
  292. accessibility: {
  293. /* eslint-disable max-len */
  294. screenReaderRegionLabel: 'Chart screen reader information.',
  295. navigationHint: 'Use regions/landmarks to skip ahead to chart {#plural(numSeries, and navigate between data series,)}',
  296. defaultChartTitle: 'Chart',
  297. longDescriptionHeading: 'Long description.',
  298. noDescription: 'No description available.',
  299. structureHeading: 'Structure.',
  300. viewAsDataTable: 'View as data table.',
  301. chartHeading: 'Chart graphic.',
  302. chartContainerLabel: 'Interactive chart. {title}. Use up and down arrows to navigate with most screen readers.',
  303. rangeSelectorMinInput: 'Select start date.',
  304. rangeSelectorMaxInput: 'Select end date.',
  305. tableSummary: 'Table representation of chart.',
  306. mapZoomIn: 'Zoom chart',
  307. mapZoomOut: 'Zoom out chart',
  308. rangeSelectorButton: 'Select range {buttonText}',
  309. legendItem: 'Toggle visibility of series {itemName}',
  310. /* eslint-enable max-len */
  311. /**
  312. * Title element text for the chart SVG element. Leave this
  313. * empty to disable adding the title element. Browsers will display
  314. * this content when hovering over elements in the chart. Assistive
  315. * technology may use this element to label the chart.
  316. *
  317. * @since 6.0.8
  318. */
  319. svgContainerTitle: '{chartTitle}',
  320. /**
  321. * Descriptions of lesser known series types. The relevant
  322. * description is added to the screen reader information region
  323. * when these series types are used.
  324. *
  325. * @since 6.0.6
  326. */
  327. seriesTypeDescriptions: {
  328. boxplot: 'Box plot charts are typically used to display ' +
  329. 'groups of statistical data. Each data point in the ' +
  330. 'chart can have up to 5 values: minimum, lower quartile, ' +
  331. 'median, upper quartile, and maximum.',
  332. arearange: 'Arearange charts are line charts displaying a ' +
  333. 'range between a lower and higher value for each point.',
  334. areasplinerange: 'These charts are line charts displaying a ' +
  335. 'range between a lower and higher value for each point.',
  336. bubble: 'Bubble charts are scatter charts where each data ' +
  337. 'point also has a size value.',
  338. columnrange: 'Columnrange charts are column charts ' +
  339. 'displaying a range between a lower and higher value for ' +
  340. 'each point.',
  341. errorbar: 'Errorbar series are used to display the ' +
  342. 'variability of the data.',
  343. funnel: 'Funnel charts are used to display reduction of data ' +
  344. 'in stages.',
  345. pyramid: 'Pyramid charts consist of a single pyramid with ' +
  346. 'item heights corresponding to each point value.',
  347. waterfall: 'A waterfall chart is a column chart where each ' +
  348. 'column contributes towards a total end value.'
  349. },
  350. /**
  351. * Chart type description strings. This is added to the chart
  352. * information region.
  353. *
  354. * If there is only a single series type used in the chart, we use
  355. * the format string for the series type, or default if missing.
  356. * There is one format string for cases where there is only a single
  357. * series in the chart, and one for multiple series of the same
  358. * type.
  359. *
  360. * @since 6.0.6
  361. */
  362. chartTypes: {
  363. /* eslint-disable max-len */
  364. emptyChart: 'Empty chart',
  365. mapTypeDescription: 'Map of {mapTitle} with {numSeries} data series.',
  366. unknownMap: 'Map of unspecified region with {numSeries} data series.',
  367. combinationChart: 'Combination chart with {numSeries} data series.',
  368. defaultSingle: 'Chart with {numPoints} data {#plural(numPoints, points, point)}.',
  369. defaultMultiple: 'Chart with {numSeries} data series.',
  370. splineSingle: 'Line chart with {numPoints} data {#plural(numPoints, points, point)}.',
  371. splineMultiple: 'Line chart with {numSeries} lines.',
  372. lineSingle: 'Line chart with {numPoints} data {#plural(numPoints, points, point)}.',
  373. lineMultiple: 'Line chart with {numSeries} lines.',
  374. columnSingle: 'Bar chart with {numPoints} {#plural(numPoints, bars, bar)}.',
  375. columnMultiple: 'Bar chart with {numSeries} data series.',
  376. barSingle: 'Bar chart with {numPoints} {#plural(numPoints, bars, bar)}.',
  377. barMultiple: 'Bar chart with {numSeries} data series.',
  378. pieSingle: 'Pie chart with {numPoints} {#plural(numPoints, slices, slice)}.',
  379. pieMultiple: 'Pie chart with {numSeries} pies.',
  380. scatterSingle: 'Scatter chart with {numPoints} {#plural(numPoints, points, point)}.',
  381. scatterMultiple: 'Scatter chart with {numSeries} data series.',
  382. boxplotSingle: 'Boxplot with {numPoints} {#plural(numPoints, boxes, box)}.',
  383. boxplotMultiple: 'Boxplot with {numSeries} data series.',
  384. bubbleSingle: 'Bubble chart with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
  385. bubbleMultiple: 'Bubble chart with {numSeries} data series.'
  386. }, /* eslint-enable max-len */
  387. /**
  388. * Axis description format strings.
  389. *
  390. * @since 6.0.6
  391. */
  392. axis: {
  393. /* eslint-disable max-len */
  394. xAxisDescriptionSingular: 'The chart has 1 X axis displaying {names[0]}.',
  395. xAxisDescriptionPlural: 'The chart has {numAxes} X axes displaying {#names.forEach(-1) }and {names[-1]}',
  396. yAxisDescriptionSingular: 'The chart has 1 Y axis displaying {names[0]}.',
  397. yAxisDescriptionPlural: 'The chart has {numAxes} Y axes displaying {#names.forEach(-1) }and {names[-1]}'
  398. }, /* eslint-enable max-len */
  399. /**
  400. * Exporting menu format strings for accessibility module.
  401. *
  402. * @since 6.0.6
  403. */
  404. exporting: {
  405. chartMenuLabel: 'Chart export',
  406. menuButtonLabel: 'View export menu',
  407. exportRegionLabel: 'Chart export menu'
  408. },
  409. /**
  410. * Lang configuration for different series types. For more dynamic
  411. * control over the series element descriptions, see
  412. * [accessibility.seriesDescriptionFormatter](
  413. * accessibility.seriesDescriptionFormatter).
  414. *
  415. * @since 6.0.6
  416. */
  417. series: {
  418. /**
  419. * Lang configuration for the series main summary. Each series
  420. * type has two modes:
  421. *
  422. * 1. This series type is the only series type used in the
  423. * chart
  424. *
  425. * 2. This is a combination chart with multiple series types
  426. *
  427. * If a definition does not exist for the specific series type
  428. * and mode, the 'default' lang definitions are used.
  429. *
  430. * @since 6.0.6
  431. */
  432. summary: {
  433. /* eslint-disable max-len */
  434. 'default': '{name}, series {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
  435. defaultCombination: '{name}, series {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
  436. line: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
  437. lineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
  438. spline: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
  439. splineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
  440. column: '{name}, bar series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bars, bar)}.',
  441. columnCombination: '{name}, series {ix} of {numSeries}. Bar series with {numPoints} {#plural(numPoints, bars, bar)}.',
  442. bar: '{name}, bar series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bars, bar)}.',
  443. barCombination: '{name}, series {ix} of {numSeries}. Bar series with {numPoints} {#plural(numPoints, bars, bar)}.',
  444. pie: '{name}, pie {ix} of {numSeries} with {numPoints} {#plural(numPoints, slices, slice)}.',
  445. pieCombination: '{name}, series {ix} of {numSeries}. Pie with {numPoints} {#plural(numPoints, slices, slice)}.',
  446. scatter: '{name}, scatter plot {ix} of {numSeries} with {numPoints} {#plural(numPoints, points, point)}.',
  447. scatterCombination: '{name}, series {ix} of {numSeries}, scatter plot with {numPoints} {#plural(numPoints, points, point)}.',
  448. boxplot: '{name}, boxplot {ix} of {numSeries} with {numPoints} {#plural(numPoints, boxes, box)}.',
  449. boxplotCombination: '{name}, series {ix} of {numSeries}. Boxplot with {numPoints} {#plural(numPoints, boxes, box)}.',
  450. bubble: '{name}, bubble series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
  451. bubbleCombination: '{name}, series {ix} of {numSeries}. Bubble series with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
  452. map: '{name}, map {ix} of {numSeries} with {numPoints} {#plural(numPoints, areas, area)}.',
  453. mapCombination: '{name}, series {ix} of {numSeries}. Map with {numPoints} {#plural(numPoints, areas, area)}.',
  454. mapline: '{name}, line {ix} of {numSeries} with {numPoints} data {#plural(numPoints, points, point)}.',
  455. maplineCombination: '{name}, series {ix} of {numSeries}. Line with {numPoints} data {#plural(numPoints, points, point)}.',
  456. mapbubble: '{name}, bubble series {ix} of {numSeries} with {numPoints} {#plural(numPoints, bubbles, bubble)}.',
  457. mapbubbleCombination: '{name}, series {ix} of {numSeries}. Bubble series with {numPoints} {#plural(numPoints, bubbles, bubble)}.'
  458. }, /* eslint-enable max-len */
  459. /**
  460. * User supplied description text. This is added after the main
  461. * summary if present.
  462. *
  463. * @since 6.0.6
  464. */
  465. description: '{description}',
  466. /**
  467. * xAxis description for series if there are multiple xAxes in
  468. * the chart.
  469. *
  470. * @since 6.0.6
  471. */
  472. xAxisDescription: 'X axis, {name}',
  473. /**
  474. * yAxis description for series if there are multiple yAxes in
  475. * the chart.
  476. *
  477. * @since 6.0.6
  478. */
  479. yAxisDescription: 'Y axis, {name}'
  480. }
  481. }
  482. }
  483. });