Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add runtime localization support #1036

Merged
merged 6 commits into from
Aug 8, 2018
Merged

Conversation

stephenmathieson
Copy link
Member

@stephenmathieson stephenmathieson commented Jul 31, 2018

This patch adds support for runtime localization. A user may now provide a locale object (in the same shape as the locales in /locale/*.json) to axe.configure().

For example:

axe.configure({
  locale: {
    rules: {
      'some-rule': {
        help: 'the help message',
        description: 'the description'
      }
    },
    checks: {
      [...]
    }
  }
})

These locale strings are run thru doT, enabling template support. For example:

axe.configure({
  locale: {
    rules: {
      foo: {
        help: 'some help',
        description: 'foo: {{~it.data:value}} {{=value}}{{~}}.'
      }
    },
    checks: {
      bar: {
        pass: 'the pass message',
        fail: 'something useful: {{~it.data:value}} {{=value}}{{~}}.'
      }
    }
  }
})

Upon calling axe.reset(), any custom locale will be reset to the "default" axe-core shipped with.

Reviewer checks

Required fields, to be filled out by PR reviewer(s)

  • Follows the commit message policy, appropriate for next version
  • Has documentation updated, a DU ticket, or requires no documentation change
  • Includes new tests, or was unnecessary
  • Code is reviewed for security by: @isner, @JKODU

This patch adds support for runtime localization. A user may now provide a `locale` object (in the same shape as the locales in `/locale/*.json`) to `axe.configure()`.

For example:

```js
axe.configure({
  locale: {
    rules: {
      'some-rule': {
        help: 'the help message',
        description: 'the description'
      }
    },
    checks: {
      [...]
    }
  }
})
```

These locale strings are run thru `doT`, enabling template support. For example:

```js
axe.configure({
  locale: {
    rules: {
      foo: {
        help: 'some help',
        description: 'foo: {{~it.data:value}} {{=value}}{{~}}.'
      }
    },
    checks: {
      bar: {
        pass: 'the pass message',
        fail: 'something useful: {{~it.data:value}} {{=value}}{{~}}.'
      }
    }
  }
})
```

Upon calling `axe.reset()`, any custom locale will be reset to the "default" axe-core shipped with.
isner
isner previously approved these changes Aug 1, 2018
Copy link
Contributor

@isner isner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works like a charm!

@@ -183,6 +185,7 @@ axe.configure({
* `tags` - array(optional, default `[]`). A list if the tags that "classify" the rule. In practice, you must supply some valid tags or the default evaluation will not invoke the rule. The convention is to include the standard (WCAG 2 and/or section 508), the WCAG 2 level, Section 508 paragraph, and the WCAG 2 success criteria. Tags are constructed by converting all letters to lower case, removing spaces and periods and concatinating the result. E.g. WCAG 2 A success criteria 1.1.1 would become ["wcag2a", "wcag111"]
* `matches` - string(optional, default `*`). A filtering CSS selector that will exclude elements that do not match the CSS selector.
* `disableOtherRules` - Disables all rules not included in the `rules` property.
* `locale` - A locale object to apply (at runtime) to all rules and checks, in the same shape as `/locales/*.json`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this enough for users to understand how to utilize the templating feature?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not. I'm hoping to get some feedback here that helps me create the missing portion(s) of documentation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add a note to the README: https://github.com/dequelabs/axe-core#localization

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@WilcoFiers and I have plans to work on documentation for this today.

@@ -183,6 +185,7 @@ axe.configure({
* `tags` - array(optional, default `[]`). A list if the tags that "classify" the rule. In practice, you must supply some valid tags or the default evaluation will not invoke the rule. The convention is to include the standard (WCAG 2 and/or section 508), the WCAG 2 level, Section 508 paragraph, and the WCAG 2 success criteria. Tags are constructed by converting all letters to lower case, removing spaces and periods and concatinating the result. E.g. WCAG 2 A success criteria 1.1.1 would become ["wcag2a", "wcag111"]
* `matches` - string(optional, default `*`). A filtering CSS selector that will exclude elements that do not match the CSS selector.
* `disableOtherRules` - Disables all rules not included in the `rules` property.
* `locale` - A locale object to apply (at runtime) to all rules and checks, in the same shape as `/locales/*.json`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add a note to the README: https://github.com/dequelabs/axe-core#localization

}

const locale = {
checks: {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we keep track of a lang key here for any reason? The existing locale files include that alongside rules and checks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not used by anything, so saving it didn't seem super important. What do you think we need it for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point. I think we should include a lang key on the results object. I think we should do that separately from this PR though.

fail = axe.imports.doT.compile(fail);
}
return {
impact: a.impact,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if they want to override the impact?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not currently supported with the existing localization setup. Adding it now falls out of scope of the task I was given.

If it's something we know we want, we can add support after this PR lands.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to Stephen's comment. You can change impact by doing this:

axe.configure({
  checks: [{
    id:'foo',
    metadata: { impact: 'minor' }
  }]
});

I think that instead of copying over impact, we should just spread the rest of the props in (...a). That way we can't accidentally forget to copy stuff over if we add stuff to metadata in the future.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 I'll spread a here.

* pass: string | function,
* fail: string | function,
* incomplete: string | {
* [key: string]: string | function
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the value can be a function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can, and will be. The result of calling dot.compile() on the string is a function.

* to the default ("first") configuration.
*/

Audit.prototype._setPreviousLocale = function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a huge deal, but I'd prefer we use named functions where we can in axe-core. It tends to make debugging a lot easier.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're mistaking this method for a function expression. Because this is a method, we'll see its implicit name in stack traces.

Given the code:

function F () {}

F.prototype.foo = function () {
  throw new Error('boom')
}

const f = new F
f.foo()

We'll clearly see "foo" in the stack trace:

node foo.js
/Users/stephen/dev/src/github.com/dequelabs/axe-core/foo.js:4
  throw new Error('boom')
  ^

Error: boom
    at F.foo (/Users/stephen/dev/src/github.com/dequelabs/axe-core/foo.js:4:9)
    at Object.<anonymous> (/Users/stephen/dev/src/github.com/dequelabs/axe-core/foo.js:8:3)
    at Module._compile (internal/modules/cjs/loader.js:702:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:713:10)
    at Module.load (internal/modules/cjs/loader.js:612:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:551:12)
    at Function.Module._load (internal/modules/cjs/loader.js:543:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:744:10)
    at startup (internal/bootstrap/node.js:238:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:572:3)

fail = axe.imports.doT.compile(fail);
}
return {
impact: a.impact,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to Stephen's comment. You can change impact by doing this:

axe.configure({
  checks: [{
    id:'foo',
    metadata: { impact: 'minor' }
  }]
});

I think that instead of copying over impact, we should just spread the rest of the props in (...a). That way we can't accidentally forget to copy stuff over if we add stuff to metadata in the future.

}
return {
help: help || a.help,
description: description || a.description
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing the helpUrl property. I think we should spread a (...a) so we just copy over the remainder.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, will update!

*/

Audit.prototype._setPreviousLocale = function() {
if (this._previousLocale) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think instead of calling this previousLocale, we should call this defaultLocale or originalLocale. Same for the function names. We're not going back to the previous one. This is going back to the original locale (which is what reset should do). You may want to put a test in place for that as well.

Copy link
Contributor

@jeeyyy jeeyyy Aug 8, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 or fallbackLocale?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a fallback, but instead, it's the default. I agree with Wilco that it should be called defaultLocale.

}

const locale = {
checks: {},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good point. I think we should include a lang key on the results object. I think we should do that separately from this PR though.

@@ -239,4 +239,230 @@ describe('axe.configure', function() {
assert.equal(axe._audit.rules[3].id, 'black-panther');
assert.equal(axe._audit.rules[3].enabled, true);
});

describe('given a locale object', function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need tests for:

  1. An axe.configure call with both metadata and a locale, make sure that locale is always used over metadata (should it throw if we're trying to set both?)
  2. Test that if you pass an object into the message function, the template ran correctly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests have been added.

Copy link
Contributor

@jeeyyy jeeyyy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm.

@stephenmathieson stephenmathieson merged commit 7d4b70f into develop Aug 8, 2018
@stephenmathieson stephenmathieson deleted the add-runtime-locale branch August 8, 2018 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants