diff --git a/README.md b/README.md index 6db04f1f25..2bbe914c96 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Axe can be built using your local language. To do so, a localization file must b This will create a new build for axe, called `axe..js` and `axe..min.js`. If you want to build localized versions, simply pass in `--all-lang` instead. -To create a new translation for axe, start by running `grunt translate --lang=`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. +To create a new translation for axe, start by running `grunt translate --lang=`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/docs/check-message-template.md). To update existing translation file, re-run `grunt translate --lang=`. This will add new messages used in English and remove messages which were not used in English. @@ -116,7 +116,7 @@ axe.configure({ 'aria-errormessage': { // Note: doT (https://github.com/olado/dot) templates are supported here. fail: - 'Der Wert der aria-errormessage {{~it.data:value}} `{{=value}}{{~}}` muss eine Technik verwenden, um die Message anzukündigen (z. B., aria-live, aria-describedby, role=alert, etc.).' + 'Der Wert der aria-errormessage ${data.values}` muss eine Technik verwenden, um die Message anzukündigen (z. B., aria-live, aria-describedby, role=alert, etc.).' } // ... } diff --git a/build/configure.js b/build/configure.js index a0e1fe09d5..bca1aef663 100644 --- a/build/configure.js +++ b/build/configure.js @@ -7,6 +7,7 @@ var dot = require('@deque/dot'); var templates = require('./templates'); var buildManual = require('./build-manual'); var entities = new (require('html-entities')).AllHtmlEntities(); +var dotRegex = /\{\{.+?\}\}/g; var descriptionHeaders = '| Rule ID | Description | Impact | Tags | Enabled by default | Failures | Needs Review |\n| :------- | :------- | :------- | :------- | :------- | :------- | :------- |\n'; @@ -59,7 +60,10 @@ function buildRules(grunt, options, commons, callback) { Object.keys(result.messages).forEach(function(key) { // only convert to templated function for strings // objects handled later in publish-metadata.js - if (typeof result.messages[key] !== 'object') { + if ( + typeof result.messages[key] !== 'object' && + dotRegex.test(result.messages[key]) + ) { result.messages[key] = dot .template(result.messages[key]) .toString(); @@ -67,7 +71,7 @@ function buildRules(grunt, options, commons, callback) { }); } //TODO this is actually failureSummaries, property name should better reflect that - if (result.failureMessage) { + if (result.failureMessage && dotRegex.test(result.failureMessage)) { result.failureMessage = dot.template(result.failureMessage).toString(); } return result; @@ -86,7 +90,10 @@ function buildRules(grunt, options, commons, callback) { function getIncompleteMsg(summaries) { var result = {}; summaries.forEach(function(summary) { - if (summary.incompleteFallbackMessage) { + if ( + summary.incompleteFallbackMessage && + dotRegex.test(summary.incompleteFallbackMessage) + ) { result = dot.template(summary.incompleteFallbackMessage).toString(); } }); diff --git a/build/tasks/validate.js b/build/tasks/validate.js index 802c279f93..b8eeb987e3 100644 --- a/build/tasks/validate.js +++ b/build/tasks/validate.js @@ -37,8 +37,6 @@ function hasMultipleOutcomes(messages) { switch (key) { case 'pass': case 'fail': - return typeof messages[key] === 'string'; - case 'incomplete': return ['string', 'object'].includes(typeof messages[key]); diff --git a/doc/check-message-template.md b/doc/check-message-template.md new file mode 100644 index 0000000000..b56408322d --- /dev/null +++ b/doc/check-message-template.md @@ -0,0 +1,124 @@ +# Check Message Template + +Axe-core uses a custom template to handle dynamic check messages (messages that use the `data` property to output values or to determine which message to display). The structure for the messages is as follows: + +## Simple Message + +A simple message is just a string that doesn't use the `data` property. Most checks uses this format. + +```json +{ + "messages": { + "pass": "Simple message for a passing check" + } +} +``` + +## Message with Data + +A message can also use the `data` property to output information from the check. If `data` is a String, Boolean, or Number, you can use the syntax `${data}` to have the message output the value of the `data` property. + +```js +// check.js +this.data(10); + +// check.json +{ + "messages": { + "pass": "Passed with a value of ${data}" + // => "Passed with a value of 10" + } +} +``` + +If `data` is an object, you can access properties of the object using the syntax `${data.propName}`. + +```js +// check.js +this.data({ + contrast: '3:1', + fontSize: '12px' +}); + +// check.json +{ + "messages": { + "fail": "Color-contrast failed with a contrast of ${data.contrast} and font size of ${data.fontSize}" + // => "Color-contrast failed with a contrast of 3:1 and font size of 12px" + } +} +``` + +## Singular and Plural Messages + +If the message needs to to know how many items are in the `data` property to determine the type of language to use (singular or plural), you can structure the message to use `singular` and `plural` properties. Use the syntax `${data.values}` to have the message output a comma-separated list of the items (`data.values` is provided by the template code for you). + +```js +// check.js +this.data(['item1', 'item2']); + +// check.json +{ + "messages": { + "fail": { + "singular": "Attribute ${data.values} is not allowed", + "plural": "Attributes: ${data.values} are not allowed" + } + // => Attributes: item1, item2 are not allowed + } +} +``` + +## Message Determined by Data + +Lastly, a message can use the `data` property to determine which message to display. Structure the message to use properties whose keys are the possible values of `data.messageKey`. You should also provide a `default` message that will be displayed if `messageKey` is not set. + +```js +// check.js +this.data({ + messageKey: 'imgNode' +}); + +// check.json +{ + "messages": { + "incomplete": { + "default": "Color-contrast could not be determined" + "bgImage": "Element's background color could not be determined due to a background image", + "imgNode": "Element's background color could not be determined because element contains an image node" + } + // => Element's background color could not be determined because element contains an image node + } +} +``` + +The messages can still use the syntax `${data.propName}` to access other properties on the `data` property. + +## Migrating From doT.js Template in Translations + +Axe-core use to use doT.js for it's temple library. To migrate from doT.js in a translation file, do the following: + +- If the message used `{{=it.data}}` or `{{=it.data.propName}}`, change the message to use the syntax `${data}` or `${data.propName}`. + +```diff +{ + "messages": { +- "incomplete": "Check that the