Skip to content

Commit

Permalink
feat(translate): added validation for schemaTranslations.json
Browse files Browse the repository at this point in the history
  • Loading branch information
RomanKrasinskyi committed Feb 19, 2020
1 parent ba04cd6 commit fccafe2
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 0 deletions.
114 changes: 114 additions & 0 deletions lib/bundle-validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ function BundleValidator(themePath, themeConfig, isPrivate) {
// Array of tasks used in Async.series
this.validationTasks = [
validateThemeConfiguration.bind(this),
validateSchemaTranslations.bind(this),
validateJspmSettings.bind(this),
];

Expand Down Expand Up @@ -101,6 +102,119 @@ function validateThemeConfiguration(callback) {
callback(null, true);
}

/**
* Find translatable strings
* @param {object} schema
* @param {function} callback
*/
function getTranslatableStrings(schema, callback) {
var trackedKeys = ['name', 'content', 'label', 'settings', 'options'];

schema.forEach(element => {
Object.keys(element).forEach(key => {
const value = element[key];

if (trackedKeys.indexOf(key) === -1) {
return;
}

if (Array.isArray(value)) {
return getTranslatableStrings(value, callback);
}

callback(value);
});
});
}

/**
* Find i18n keys in schema
* @param {object} schema
* @return {array}
*/
function getI18nKeys(schema) {
var keys = [];

getTranslatableStrings(schema, (value) => {
if (
value
&& keys.indexOf(value) === -1
&& /^i18n\./.test(value)
) {
keys.push(value);
}
});

return keys;
}

/**
* Find missed i18n keys in schemaTranslations
* @param {array} keys
* @param {object} translations
* @return {array}
*/
function findMissedKeys(keys, translations) {
var translationsKeys = Object.keys(translations);
var missingKeys = [];

keys.forEach(key => {
if (translationsKeys.indexOf(key) === -1) {
missingKeys.push(key);
}
});

return missingKeys;
}

/**
* Ensure that schema translations exists and there are no missing keys.
* @param {function} callback
* @return {function} callback
*/
function validateSchemaTranslations(callback) {
var v = new Validator();
var validatorTranslations = './schemaTranslations.schema.json';
var translations = {};
var keys = [];
var missedKeys;
var validation;
var errorMessage;

if (this.themeConfig.schemaExists()) {
keys = getI18nKeys(this.themeConfig.getRawSchema());
}

if (this.themeConfig.schemaTranslationsExists()) {
translations = this.themeConfig.getRawSchemaTranslations();
}

if (keys.length && _.isEmpty(translations)) {
errorMessage = 'Missed or corrupted schemaTranslations.json file'.red;

return callback(new Error(errorMessage));
}

missedKeys = findMissedKeys(keys, translations);
validation = v.validate(translations, require(validatorTranslations));

if ((validation.errors && validation.errors.length > 0) || (missedKeys && missedKeys.length > 0)) {
errorMessage = 'Your theme\'s schemaTranslations.json has errors:'.red;

missedKeys.forEach(key => {
errorMessage += '\r\nmissing translation key "'.red + key.red + '"'.red;
});

validation.errors.forEach(error => {
errorMessage += '\r\nschemaTranslations'.red + error.stack.substring(8).red;
});

return callback(new Error(errorMessage));
}

callback(null, true);
}

/**
* If a theme is using JSPM, make sure they
* @param callback
Expand Down
22 changes: 22 additions & 0 deletions lib/schemaTranslations.schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "http://json-schema.org/draft-07/schema#",
"type": "object",
"patternProperties": {
"^i18n.": {
"type": "object",
"properties": {
"default": {
"type": "string"
}
},
"additionalProperties": {
"type": "string"
},
"required": [
"default"
]
}
},
"additionalProperties": false
}
20 changes: 20 additions & 0 deletions lib/stencil-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function Bundle(themePath, themeConfig, rawConfig, options) {
tasks.templates = this.assembleTemplatesTask.bind(this);
tasks.lang = this.assembleLangTask.bind(this);
tasks.schema = this.assembleSchema.bind(this);
tasks.schemaTranslations = this.assembleSchemaTranslations.bind(this);

if (typeof buildConfig.production === 'function') {
tasks.theme = callback => {
Expand Down Expand Up @@ -184,6 +185,20 @@ Bundle.prototype.assembleSchema = function (callback) {
});
};

Bundle.prototype.assembleSchemaTranslations = function (callback) {
console.log('Schema Translations Parsing Started...');

this.themeConfig.getSchemaTranslations((err, schema) => {
if (err) {
callback(err);
}

console.log('ok'.green + ' -- Schema Translations Parsing Finished');

callback(null, schema);
});
};

Bundle.prototype.assembleLangTask = function (callback) {
console.log('Language Files Parsing Started...');
LangAssembler.assemble((err, results) => {
Expand Down Expand Up @@ -398,6 +413,11 @@ function bundleParsedFiles(archive, taskResults) {
archiveJsonFile(data, 'schema.json');
break;

case 'schemaTranslations':
// append the parsed schemaTranslations.json file
archiveJsonFile(data, 'schemaTranslations.json');
break;

case 'manifest':
// append the generated manifest.json file
archiveJsonFile(data, 'manifest.json');
Expand Down
26 changes: 26 additions & 0 deletions lib/theme-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,14 @@ ThemeConfig.prototype.schemaExists = function () {
return fileExists(this.schemaPath);
};

/**
* Check if the schemaTranslations.json file exists
* @return {Boolean}
*/
ThemeConfig.prototype.schemaTranslationsExists = function () {
return fileExists(this.schemaTranslationsPath);
};

/**
* Scans the theme template directory for theme settings that need force reload
*
Expand Down Expand Up @@ -410,6 +418,24 @@ ThemeConfig.prototype.getRawConfig = function() {
return jsonLint.parse(Fs.readFileSync(this.configPath, {encoding: 'utf-8'}), this.configPath);
};

/**
* Return the raw schema.json data
*
* @return {object}
*/
ThemeConfig.prototype.getRawSchema = function() {
return jsonLint.parse(Fs.readFileSync(this.schemaPath, {encoding: 'utf-8'}), this.schemaPath);
};

/**
* Return the raw config.json data
*
* @return {object}
*/
ThemeConfig.prototype.getRawSchemaTranslations = function() {
return jsonLint.parse(Fs.readFileSync(this.schemaTranslationsPath, {encoding: 'utf-8'}), this.schemaTranslationsPath);
};

/**
* Grabs out a variation based on a name. Or if the name is not passed in, the very first one in the list.
*
Expand Down

0 comments on commit fccafe2

Please sign in to comment.