-
Notifications
You must be signed in to change notification settings - Fork 783
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
Conversation
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.
There was a problem hiding this 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`. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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`. |
There was a problem hiding this comment.
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: {}, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
lib/core/base/audit.js
Outdated
fail = axe.imports.doT.compile(fail); | ||
} | ||
return { | ||
impact: a.impact, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
lib/core/base/audit.js
Outdated
* to the default ("first") configuration. | ||
*/ | ||
|
||
Audit.prototype._setPreviousLocale = function() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)
lib/core/base/audit.js
Outdated
fail = axe.imports.doT.compile(fail); | ||
} | ||
return { | ||
impact: a.impact, |
There was a problem hiding this comment.
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.
lib/core/base/audit.js
Outdated
} | ||
return { | ||
help: help || a.help, | ||
description: description || a.description |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, will update!
lib/core/base/audit.js
Outdated
*/ | ||
|
||
Audit.prototype._setPreviousLocale = function() { | ||
if (this._previousLocale) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 or fallbackLocale?
There was a problem hiding this comment.
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: {}, |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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:
- 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?)
- Test that if you pass an object into the message function, the template ran correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tests have been added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lgtm.
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
) toaxe.configure()
.For example:
These locale strings are run thru
doT
, enabling template support. For example: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)