Skip to content

Commit bf8b374

Browse files
authored
feat: Localization, English + French (#223)
1 parent 5d78397 commit bf8b374

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+963
-228
lines changed

packages/ace-cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@daisy/ace-cli",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"description": "Ace by DAISY, an Accessibility Checker for EPUB",
55
"author": {
66
"name": "DAISY developers",

packages/ace-cli/src/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const cli = meow({
3232
-V, --verbose display verbose output
3333
-s, --silent do not display any output
3434
35+
-l, --lang <language> language code for localized messages (e.g. "fr"), default is "en"
3536
Examples
3637
$ ace -o out ~/Documents/book.epub
3738
`,
@@ -46,9 +47,10 @@ version: pkg.version
4647
t: 'tempdir',
4748
v: 'version',
4849
V: 'verbose',
50+
l: 'lang',
4951
},
5052
boolean: ['force', 'verbose', 'silent', 'subdir'],
51-
string: ['outdir', 'tempdir'],
53+
string: ['outdir', 'tempdir', 'lang'],
5254
});
5355

5456
function sleep(ms) {
@@ -102,6 +104,7 @@ ${overrides.map(file => ` - ${file}`).join('\n')}
102104
verbose: cli.flags.verbose,
103105
silent: cli.flags.silent,
104106
jobId: '',
107+
lang: cli.flags.lang,
105108
})
106109
.then((jobData) => {
107110
var reportJson = jobData[1];

packages/ace-core/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@daisy/ace-core",
3-
"version": "1.0.3",
3+
"version": "1.0.4",
44
"description": "Core library for Ace",
55
"author": {
66
"name": "DAISY developers",
@@ -18,6 +18,8 @@
1818
"license": "MIT",
1919
"main": "lib/index.js",
2020
"dependencies": {
21+
"@daisy/ace-localize": "^1.0.0",
22+
"@daisy/ace-logger": "^1.0.1",
2123
"@daisy/ace-meta": "^1.0.3",
2224
"@daisy/ace-report": "^1.0.1",
2325
"@daisy/ace-report-axe": "^1.0.1",

packages/ace-core/src/checker/checker-chromium.js

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ const winston = require('winston');
1212
const axe2ace = require('@daisy/ace-report-axe');
1313
const utils = require('@daisy/puppeteer-utils');
1414

15+
const { getRawResourcesForCurrentLanguage } = require('../l10n/localize').localizer;
16+
1517
tmp.setGracefulCleanup();
1618

1719
const scripts = [
@@ -23,7 +25,7 @@ const scripts = [
2325
require.resolve('../scripts/ace-extraction.js'),
2426
];
2527

26-
async function checkSingle(spineItem, epub, browser) {
28+
async function checkSingle(spineItem, epub, browser, lang) {
2729
winston.verbose(`- Processing ${spineItem.relpath}`);
2830
try {
2931
let url = spineItem.url;
@@ -42,6 +44,46 @@ async function checkSingle(spineItem, epub, browser) {
4244

4345
const page = await browser.newPage();
4446
await page.goto(url);
47+
48+
let localePath = "";
49+
try {
50+
winston.info(`- Axe locale: [${lang}]`);
51+
52+
// https://github.com/dequelabs/axe-core#localization
53+
// https://github.com/dequelabs/axe-core/tree/develop/locales
54+
55+
if (lang && lang !== "en" && lang.indexOf("en-") !== 0) { // default English built into Axe source code
56+
localePath = path.resolve(require.resolve('axe-core'), `../locales/${lang}.json`);
57+
if (fs.existsSync(localePath)) {
58+
const localeStr = fs.readFileSync(localePath, { encoding: "utf8" });
59+
const localeScript = `window.__axeLocale__=${localeStr};`;
60+
await utils.addScriptContents([localeScript], page);
61+
} else {
62+
winston.info(`- Axe locale missing? [${lang}] => ${localePath}`);
63+
}
64+
}
65+
66+
let localizedScript = "";
67+
const rawJson = getRawResourcesForCurrentLanguage();
68+
69+
["axecheck", "axerule"].forEach((checkOrRule) => {
70+
const checkOrRuleKeys = Object.keys(rawJson[checkOrRule]);
71+
for (const checkOrRuleKey of checkOrRuleKeys) {
72+
const msgs = Object.keys(rawJson[checkOrRule][checkOrRuleKey]);
73+
for (const msg of msgs) {
74+
const k = `__aceLocalize__${checkOrRule}_${checkOrRuleKey}_${msg}`;
75+
localizedScript += `window['${k}']="${rawJson[checkOrRule][checkOrRuleKey][msg]}";\n`;
76+
}
77+
}
78+
});
79+
await utils.addScriptContents([localizedScript], page);
80+
81+
} catch (err) {
82+
console.log(err);
83+
winston.verbose(err);
84+
winston.info(`- Axe locale problem? [${lang}] => ${localePath}`);
85+
}
86+
4587
await utils.addScripts(scripts, page);
4688

4789
const results = await page.evaluate(() => new Promise((resolve, reject) => {
@@ -57,7 +99,7 @@ async function checkSingle(spineItem, epub, browser) {
5799
await page.close();
58100

59101
// Post-process results
60-
results.assertions = (results.axe != null) ? axe2ace.axe2ace(spineItem, results.axe) : [];
102+
results.assertions = (results.axe != null) ? axe2ace.axe2ace(spineItem, results.axe, lang) : [];
61103
delete results.axe;
62104
winston.info(`- ${spineItem.relpath}: ${
63105
(results.assertions && results.assertions.assertions.length > 0)
@@ -97,14 +139,14 @@ async function checkSingle(spineItem, epub, browser) {
97139
}
98140
}
99141

100-
module.exports.check = async (epub) => {
142+
module.exports.check = async (epub, lang) => {
101143
const args = [];
102144
if (os.platform() !== 'win32' && os.platform() !== 'darwin') {
103145
args.push('--no-sandbox')
104146
}
105147
const browser = await puppeteer.launch({ args });
106148
winston.info('Checking documents...');
107-
return pMap(epub.contentDocs, doc => checkSingle(doc, epub, browser), { concurrency: 4 })
149+
return pMap(epub.contentDocs, doc => checkSingle(doc, epub, browser, lang), { concurrency: 4 })
108150
.then(async (results) => {
109151
await browser.close();
110152
return results;

packages/ace-core/src/checker/checker-epub.js

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const builders = require('@daisy/ace-report').builders;
44
const winston = require('winston');
55

6+
const { localize } = require('../l10n/localize').localizer;
7+
68
const ASSERTED_BY = 'Ace';
79
const MODE = 'automatic';
810
const KB_BASE = 'http://kb.daisy.org/publishing/';
@@ -42,11 +44,11 @@ function newMetadataAssertion(name, impact = 'serious') {
4244
return newViolation({
4345
impact,
4446
title: `metadata-${name.toLowerCase().replace(':', '-')}`,
45-
testDesc: `Ensures a '${name}' metadata is present`,
46-
resDesc: `Add a '${name}' metadata property to the Package Document`,
47+
testDesc: localize("checkepub.metadataviolation.testdesc", { name, interpolation: { escapeValue: false } }),
48+
resDesc: localize("checkepub.metadataviolation.resdesc", { name, interpolation: { escapeValue: false } }),
4749
kbPath: 'docs/metadata/schema-org.html',
48-
kbTitle: 'Schema.org Accessibility Metadata',
49-
ruleDesc: `Publications must declare the '${name}' metadata`
50+
kbTitle: localize("checkepub.metadataviolation.kbtitle"),
51+
ruleDesc: localize("checkepub.metadataviolation.ruledesc", { name, interpolation: { escapeValue: false } })
5052
});
5153
}
5254

@@ -70,11 +72,11 @@ function checkTitle(assertions, epub) {
7072
if (title === '') {
7173
assertions.withAssertions(newViolation({
7274
title: 'epub-title',
73-
testDesc: 'Ensures the EPUB has a title',
74-
resDesc: 'Add a \'dc:title\' metadata property to the Package Document',
75+
testDesc: localize("checkepub.titleviolation.testdesc"),
76+
resDesc: localize("checkepub.titleviolation.resdesc"),
7577
kbPath: '',
76-
kbTitle: 'EPUB Title',
77-
ruleDesc: 'Publications must have a title',
78+
kbTitle: localize("checkepub.titleviolation.kbtitle"),
79+
ruleDesc: localize("checkepub.titleviolation.ruledesc")
7880
}));
7981
}
8082
}
@@ -85,11 +87,11 @@ function checkPageSource(assertion, epub) {
8587
|| epub.metadata['dc:source'].toString() === '')) {
8688
assertion.withAssertions(newViolation({
8789
title: 'epub-pagesource',
88-
testDesc: 'Ensures the source of page breaks is identified',
89-
resDesc: 'Add a \'dc:source\' metadata property to the Package Document',
90+
testDesc: localize("checkepub.pagesourceviolation.testdesc"),
91+
resDesc: localize("checkepub.pagesourceviolation.resdesc"),
9092
kbPath: 'docs/navigation/pagelist.html',
91-
kbTitle: 'Page Navigation',
92-
ruleDesc: 'Publications with page breaks must declare the \'dc:source\' metadata',
93+
kbTitle: localize("checkepub.pagesourceviolation.kbtitle"),
94+
ruleDesc: localize("checkepub.pagesourceviolation.ruledesc")
9395
}));
9496
}
9597
}
@@ -121,7 +123,7 @@ function check(epub, report) {
121123
hasBindings: epub.hasBindings,
122124
hasSVGContentDocuments: epub.hasSVGContentDocuments,
123125
});
124-
126+
125127
winston.info(`- ${epub.packageDoc.src}: ${
126128
(builtAssertions.assertions && builtAssertions.assertions.length > 0)
127129
? builtAssertions.assertions.length

packages/ace-core/src/checker/checker.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ function consolidate(results, report) {
2323
return report;
2424
}
2525

26-
module.exports.check = function check(epub, report) {
26+
module.exports.check = function check(epub, report,lang) {
2727
return epubChecker.check(epub, report)
28-
.then(() => htmlChecker.check(epub))
28+
.then(() => htmlChecker.check(epub, lang))
2929
.then(results => consolidate(results, report));
3030
};

packages/ace-core/src/core/ace.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,21 @@ const pkg = require('@daisy/ace-meta/package');
1010
const EPUB = require('@daisy/epub-utils').EPUB;
1111
const Report = require('@daisy/ace-report').Report;
1212
const checker = require('../checker/checker.js');
13+
const { setCurrentLanguage } = require('../l10n/localize').localizer;
14+
15+
const logger = require('@daisy/ace-logger');
1316

1417
tmp.setGracefulCleanup();
1518

1619
module.exports = function ace(epubPath, options) {
20+
if (options.lang) {
21+
setCurrentLanguage(options.lang);
22+
}
23+
24+
if (options.initLogger) {
25+
logger.initLogger({ verbose: options.verbose, silent: options.silent });
26+
}
27+
1728
return new Promise((resolve, reject) => {
1829
// the jobid option just gets returned in the resolve/reject
1930
// so the calling function can track which job finished
@@ -56,9 +67,9 @@ module.exports = function ace(epubPath, options) {
5667
epub.extract()
5768
.then(() => epub.parse())
5869
// initialize the report
59-
.then(() => new Report(epub, options.outdir))
70+
.then(() => new Report(epub, options.outdir, options.lang))
6071
// Check each Content Doc
61-
.then(report => checker.check(epub, report))
72+
.then(report => checker.check(epub, report, options.lang))
6273
// Process the Results
6374
.then((report) => {
6475
if (options.outdir === undefined) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"checkepub": {
3+
"metadataviolation": {
4+
"testdesc": "Ensures a '{{name}}' metadata is present",
5+
"resdesc": "Add a '{{name}}' metadata property to the Package Document",
6+
"ruledesc": "Publications must declare the '{{name}}' metadata",
7+
"kbtitle": "Schema.org Accessibility Metadata"
8+
},
9+
"titleviolation": {
10+
"testdesc": "Ensures the EPUB has a title",
11+
"resdesc": "Add a 'dc:title' metadata property to the Package Document",
12+
"ruledesc": "Publications must have a title",
13+
"kbtitle": "EPUB Title"
14+
},
15+
"pagesourceviolation": {
16+
"testdesc": "Ensures the source of page breaks is identified",
17+
"resdesc": "Add a 'dc:source' metadata property to the Package Document",
18+
"ruledesc": "Publications with page breaks must declare the 'dc:source' metadata",
19+
"kbtitle": "Page Navigation"
20+
}
21+
},
22+
"axecheck": {
23+
"matching-aria-role": {
24+
"pass": "Element has an ARIA role matching its epub:type",
25+
"fail": "Element has no ARIA role matching its epub:type"
26+
}
27+
},
28+
"axerule": {
29+
"pagebreak-label": {
30+
"desc": "Ensure page markers have an accessible label"
31+
},
32+
"epub-type-has-matching-role": {
33+
"help": "ARIA role should be used in addition to epub:type",
34+
"desc": "Ensure the element has an ARIA role matching its epub:type"
35+
}
36+
}
37+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"checkepub": {
3+
"metadataviolation": {
4+
"testdesc": "Vérifie que la métadonnée '{{name}}' est présente",
5+
"resdesc": "Ajouter la métadonnée '{{name}}' au Document de Package",
6+
"ruledesc": "Toute publication doit déclarer la métadonnée '{{name}}'",
7+
"kbtitle": "Métadonnées d'accessibilité Schema.org"
8+
},
9+
"titleviolation": {
10+
"testdesc": "Vérifie que le EPUB a un titre",
11+
"resdesc": "Ajouter la métadonnée 'dc:title' au Document de Package",
12+
"ruledesc": "Une publication doit avoir un titre",
13+
"kbtitle": "Titre de l’EPUB"
14+
},
15+
"pagesourceviolation": {
16+
"testdesc": "Vérifie que la source des sauts de page est identifiée",
17+
"resdesc": "Ajouter la métadonnée 'dc:source' au Document de Package",
18+
"ruledesc": "Une publication avec des sauts de page doit déclarer la métadonnée 'dc:source'",
19+
"kbtitle": "Navigation par page"
20+
}
21+
},
22+
"axecheck": {
23+
"matching-aria-role": {
24+
"pass": "L’élément a un rôle ARIA correspondant à son epub:type",
25+
"fail": "L’élément n’a pas de rôle ARIA correspondant à son epub:type"
26+
}
27+
},
28+
"axerule": {
29+
"pagebreak-label": {
30+
"desc": "Vérifie que les sauts de page ont un label accessible"
31+
},
32+
"epub-type-has-matching-role": {
33+
"help": "Un rôle ARIA devrait être spécifié en plus de l’epub:type",
34+
"desc": "Vérifie qu’un élément a un rôle ARIA correspondant à son epub:type"
35+
}
36+
}
37+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const { newLocalizer } = require('@daisy/ace-localize');
2+
3+
const enJson = require("./locales/en.json");
4+
const frJson = require("./locales/fr.json");
5+
6+
export const localizer = newLocalizer({
7+
en: {
8+
name: "English",
9+
default: true,
10+
translation: enJson,
11+
},
12+
fr: {
13+
name: "Français",
14+
translation: frJson,
15+
},
16+
});

0 commit comments

Comments
 (0)