frontend/gulp/tasks/translations.js
Paulus Schoutsen a4afc2e37a
Polymer 3 modulize (#1154)
* Version bump to 20180510.1

* Fix hass util

* Fix translations

* Bye paper-time-input

* Add webpack config

* Add webpack to package.json

* Fix translation import

* Disable web animations polyfill bad import

* Disable importHref import

* Update webpack config to build authorize.js

* Build translations json

* Build frontend correctly

* Run eslint --fix

* Load markdown JS on demand (#1155)

* Add HTML imports (#1160)

* Fix localize (#1161)

* Fix Roboto in build (#1162)

* Load web animations polyfill (#1163)

* P3: Fix chart js (#1164)

* P3: Fix Chart JS

* Update timeline package

* P3: panel resolver (#1165)

* WIP

* Initial importing of panels

* Fix panel resolver

* Fix automation and script editor (#1166)

* Expose Polymer and Polymer.Element on window (#1167)

* Remove unused import

* eslint --fix

* Es5 build (#1168)

* Build for ES5

* Fix build_frontend

* Remove stale comment

* Migrate to use paper-material-styles (#1170)

* Send parsed date to history/logbook (#1171)

* Fork app storage behavior (#1172)

* Add paper input with type time (#1173)

* Fix authorize

* Lint

* Sort imports

* Lint

* Remove eslint-html

* Do not lint authorize.html

* Fix polymer lint

* Try chrome 62 for wct

* P3: Add patched iconset (#1175)

* Add patched iconset

* Lint

* Test with latest Chrome again

* Use less window.hassUtil

* Teporarily use my fecha fork

* Import correct intl.messageFormat

* Update wct-browser-legacy to 1.0.0

* Include polyfill in right place

* Fix IntlMessageFormat

* Fix test not having a global scope

* Rollup <_<

* Fork app-localize-behavior

* Disable wct tests

* Lint
2018-05-15 13:31:47 -04:00

257 lines
8.0 KiB
JavaScript
Executable File

const path = require('path');
const gulp = require('gulp');
const foreach = require('gulp-foreach');
const hash = require('gulp-hash');
const insert = require('gulp-insert');
const merge = require('gulp-merge-json');
const minify = require('gulp-jsonminify');
const rename = require('gulp-rename');
const transform = require('gulp-json-transform');
const inDir = 'translations';
const workDir = 'build-translations';
const fullDir = workDir + '/full';
const coreDir = workDir + '/core';
const outDir = workDir + '/output';
// Panel translations which should be split from the core translations. These
// should mirror the fragment definitions in polymer.json, so that we load
// additional resources at equivalent points.
const TRANSLATION_FRAGMENTS = [
'config',
'history',
'logbook',
'mailbox',
'shopping-list',
];
const tasks = [];
function recursiveFlatten(prefix, data) {
let output = {};
Object.keys(data).forEach(function (key) {
if (typeof (data[key]) === 'object') {
output = Object.assign({}, output, recursiveFlatten(prefix + key + '.', data[key]));
} else {
output[prefix + key] = data[key];
}
});
return output;
}
function flatten(data) {
return recursiveFlatten('', data);
}
function emptyFilter(data) {
const newData = {};
Object.keys(data).forEach((key) => {
if (data[key]) {
if (typeof (data[key]) === 'object') {
newData[key] = emptyFilter(data[key]);
} else {
newData[key] = data[key];
}
}
});
return newData;
}
/**
* Replace Lokalise key placeholders with their actual values.
*
* We duplicate the behavior of Lokalise here so that placeholders can
* be included in src/translations/en.json, but still be usable while
* developing locally.
*
* @link https://docs.lokalise.co/article/KO5SZWLLsy-key-referencing
*/
const re_key_reference = /\[%key:([^%]+)%\]/;
function lokalise_transform (data, original) {
const output = {};
Object.entries(data).forEach(([key, value]) => {
if (value instanceof Object) {
output[key] = lokalise_transform(value, original);
} else {
output[key] = value.replace(re_key_reference, (match, key) => {
const replace = key.split('::').reduce((tr, k) => tr[k], original);
if (typeof replace !== 'string') {
throw Error(`Invalid key placeholder ${key} in src/translations/en.json`);
}
return replace;
});
}
});
return output;
}
/**
* This task will build a master translation file, to be used as the base for
* all languages. This starts with src/translations/en.json, and replaces all
* Lokalise key placeholders with their target values. Under normal circumstances,
* this will be the same as translations/en.json However, we build it here to
* facilitate both making changes in development mode, and to ensure that the
* project is buildable immediately after merging new translation keys, since
* the Lokalise update to translations/en.json will not happen immediately.
*/
let taskName = 'build-master-translation';
gulp.task(taskName, function () {
return gulp.src('src/translations/en.json')
.pipe(transform(function(data, file) {
return lokalise_transform(data, data);
}))
.pipe(rename('translationMaster.json'))
.pipe(gulp.dest(workDir));
});
tasks.push(taskName);
taskName = 'build-merged-translations';
gulp.task(taskName, ['build-master-translation'], function () {
return gulp.src(inDir + '/*.json')
.pipe(foreach(function(stream, file) {
// For each language generate a merged json file. It begins with the master
// translation as a failsafe for untranslated strings, and merges all parent
// tags into one file for each specific subtag
//
// TODO: This is a naive interpretation of BCP47 that should be improved.
// Will be OK for now as long as we don't have anything more complicated
// than a base translation + region.
const tr = path.basename(file.history[0], '.json');
const subtags = tr.split('-');
const src = [workDir + '/translationMaster.json'];
for (let i = 1; i <= subtags.length; i++) {
const lang = subtags.slice(0, i).join('-');
src.push(inDir + '/' + lang + '.json');
}
return gulp.src(src)
.pipe(transform(data => emptyFilter(data)))
.pipe(merge({
fileName: tr + '.json',
}))
.pipe(gulp.dest(fullDir));
}));
});
tasks.push(taskName);
const splitTasks = [];
TRANSLATION_FRAGMENTS.forEach((fragment) => {
taskName = 'build-translation-fragment-' + fragment;
gulp.task(taskName, ['build-merged-translations'], function () {
// Return only the translations for this fragment.
return gulp.src(fullDir + '/*.json')
.pipe(transform(data => ({
ui: {
panel: {
[fragment]: data.ui.panel[fragment],
},
},
})))
.pipe(gulp.dest(workDir + '/' + fragment));
});
tasks.push(taskName);
splitTasks.push(taskName);
});
taskName = 'build-translation-core';
gulp.task(taskName, ['build-merged-translations'], function () {
// Remove the fragment translations from the core translation.
return gulp.src(fullDir + '/*.json')
.pipe(transform((data) => {
TRANSLATION_FRAGMENTS.forEach((fragment) => {
delete data.ui.panel[fragment];
});
return data;
}))
.pipe(gulp.dest(coreDir));
});
tasks.push(taskName);
splitTasks.push(taskName);
taskName = 'build-flattened-translations';
gulp.task(taskName, splitTasks, function () {
// Flatten the split versions of our translations, and move them into outDir
return gulp.src(
TRANSLATION_FRAGMENTS.map(fragment => workDir + '/' + fragment + '/*.json')
.concat(coreDir + '/*.json'),
{ base: workDir },
)
.pipe(transform(function (data) {
// Polymer.AppLocalizeBehavior requires flattened json
return flatten(data);
}))
.pipe(minify())
.pipe(rename((filePath) => {
if (filePath.dirname === 'core') {
filePath.dirname = '';
}
}))
.pipe(gulp.dest(outDir));
});
tasks.push(taskName);
taskName = 'build-translation-fingerprints';
gulp.task(taskName, ['build-flattened-translations'], function () {
return gulp.src(outDir + '/**/*.json')
.pipe(rename({
extname: '',
}))
.pipe(hash({
algorithm: 'md5',
hashLength: 32,
template: '<%= name %>-<%= hash %>.json',
}))
.pipe(hash.manifest('translationFingerprints.json'))
.pipe(transform(function (data) {
// After generating fingerprints of our translation files, consolidate
// all translation fragment fingerprints under the translation name key
const newData = {};
Object.entries(data).forEach(([key, value]) => {
const parts = key.split('/');
let translation = key;
if (parts.length === 2) {
translation = parts[1];
}
if (!(translation in newData)) {
newData[translation] = {
fingerprints: {},
};
}
newData[translation].fingerprints[key] = value;
});
return newData;
}))
.pipe(gulp.dest(workDir));
});
tasks.push(taskName);
taskName = 'build-translations';
gulp.task(taskName, ['build-translation-fingerprints'], function () {
return gulp.src([
'src/translations/translationMetadata.json',
workDir + '/translationFingerprints.json',
])
.pipe(merge({}))
.pipe(transform(function (data) {
const newData = {};
Object.entries(data).forEach(([key, value]) => {
// Filter out translations without native name.
if (data[key].nativeName) {
newData[key] = data[key];
} else {
console.warn(`Skipping language ${key}. Native name was not translated.`);
}
if (data[key]) newData[key] = value;
});
return newData;
}))
.pipe(transform(data => ({
fragments: TRANSLATION_FRAGMENTS,
translations: data,
})))
.pipe(rename('translationMetadata.json'))
.pipe(gulp.dest(workDir));
});
tasks.push(taskName);
module.exports = tasks;