From 29fad98754765e5560e59271fc65b687235f5f32 Mon Sep 17 00:00:00 2001 From: Adam Mills Date: Wed, 25 Oct 2017 21:12:23 -0400 Subject: [PATCH] Core POC support for polymer i18n (#227) * Core POC support for polymer i18n * Move translation from core.js to html * Replace fetch with XHR * Convert translation pipeline to gulp * Convert from polyglot to Polymer localize * Pass through missing keys for custom panels * Store promise to be reused * Use cacheFirst sw handler for translations * Write full filenames to translationFingerprints * Precache en translation * Convert home-assistant-main to ES6 class * Create a localization mixin * Cleanup * Add polymer tags to annotate for linter * Rename fingerprints to translationMetadata * Build translation native names into metadata * Add language selection UI to sidebar * Provide separate message namespace argument * Store language/resources on hass object * Store translationMetadata on hass * Move language selector to config panel * Temporarily hide language selector * Small cleanups * Use dynamic-align for more flexible layout * Migrate to fetch API * Only send change events for user selection events * Update for new linting rules * Migrate build_frontend changes --- bower.json | 1 + gulp/tasks/build.js | 2 +- gulp/tasks/gen-service-worker.js | 12 +- gulp/tasks/translations.js | 111 ++++++++++++ package.json | 6 + panels/config/core/ha-config-core.html | 16 ++ .../core/ha-config-section-translation.html | 79 +++++++++ script/build_frontend | 12 +- src/components/ha-sidebar.html | 14 +- src/home-assistant.html | 22 +++ src/util/ha-pref-storage.html | 1 + src/util/hass-mixins.html | 33 ++++ src/util/hass-translation.html | 84 +++++++++ translations/en.json | 12 ++ yarn.lock | 164 +++++++++++++++++- 15 files changed, 554 insertions(+), 15 deletions(-) create mode 100755 gulp/tasks/translations.js create mode 100644 panels/config/core/ha-config-section-translation.html create mode 100644 src/util/hass-translation.html create mode 100644 translations/en.json diff --git a/bower.json b/bower.json index ac76883bce..565796f114 100644 --- a/bower.json +++ b/bower.json @@ -9,6 +9,7 @@ "private": true, "dependencies": { "app-layout": "^2.0.0", + "app-localize-behavior": "PolymerElements/app-localize-behavior#~2.0.0", "app-route": "PolymerElements/app-route#^2.0.0", "app-storage": "^2.0.2", "fecha": "~2.3.0", diff --git a/gulp/tasks/build.js b/gulp/tasks/build.js index 09437feb3e..d39e622d23 100644 --- a/gulp/tasks/build.js +++ b/gulp/tasks/build.js @@ -30,7 +30,7 @@ function renamePanel(path) { } } -gulp.task('build', ['ru_all'], () => { +gulp.task('build', ['ru_all', 'build-translations'], () => { const strategy = composeStrategies([ generateShellMergeStrategy(polymerConfig.shell), stripImportsStrategy([ diff --git a/gulp/tasks/gen-service-worker.js b/gulp/tasks/gen-service-worker.js index a1851608ef..ca1a7a1e52 100755 --- a/gulp/tasks/gen-service-worker.js +++ b/gulp/tasks/gen-service-worker.js @@ -39,6 +39,7 @@ var staticFingerprinted = [ 'mdi.html', 'core.js', 'compatibility.js', + 'translations/en.json', ]; // These panels will always be registered inside HA and thus can @@ -62,9 +63,10 @@ gulp.task('gen-service-worker', () => { // Create fingerprinted versions of our dependencies. staticFingerprinted.forEach(fn => { var parts = path.parse(fn); - var hash = md5(rootDir + '/' + parts.name + parts.ext); - var url = '/static/' + parts.name + '-' + hash + parts.ext; - var fpath = rootDir + '/' + parts.name + parts.ext; + var base = parts.dir.length > 0 ? parts.dir + '/' + parts.name : parts.name; + var hash = md5(rootDir + '/' + base + parts.ext); + var url = '/static/' + base + '-' + hash + parts.ext; + var fpath = rootDir + '/' + base + parts.ext; dynamicUrlToDependencies[url] = [fpath]; }); @@ -89,6 +91,10 @@ gulp.task('gen-service-worker', () => { rootDir + '/fonts/roboto/Roboto-Bold.ttf', rootDir + '/images/card_media_player_bg.png', ], + runtimeCaching: [{ + urlPattern: /\/static\/translations\//, + handler: 'cacheFirst', + }], stripPrefix: 'hass_frontend', replacePrefix: 'static', verbose: true, diff --git a/gulp/tasks/translations.js b/gulp/tasks/translations.js new file mode 100755 index 0000000000..e6ea318ab7 --- /dev/null +++ b/gulp/tasks/translations.js @@ -0,0 +1,111 @@ +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 outDir = 'build/translations'; + +const tasks = []; + +function recursive_flatten (prefix, data) { + var output = {}; + Object.keys(data).forEach(function (key) { + if (typeof(data[key]) === 'object') { + output = Object.assign({}, output, recursive_flatten(key + '.', data[key])); + } else { + output[prefix + key] = data[key]; + } + }); + return output +} + +function flatten (data) { + return recursive_flatten('', data); +} + +var taskName = 'build-translation-native-names'; +gulp.task(taskName, function() { + return gulp.src(inDir + '/*.json') + .pipe(transform(function(data, file) { + // Look up the native name for each language and generate a json + // object with all available languages and native names + const lang = path.basename(file.relative, '.json'); + return {[lang]: {nativeName: data.language[lang]}}; + })) + .pipe(merge({ + fileName: 'translationNativeNames.json', + })) + .pipe(gulp.dest('build-temp')); +}); +tasks.push(taskName); + +var taskName = 'build-merged-translations'; +gulp.task(taskName, function () { + return gulp.src(inDir + '/*.json') + .pipe(foreach(function(stream, file) { + // For each language generate a merged json file. It begins with en.json as + // a failsafe for untranslated strings, and merges all parent tags into one + // file for each specific subtag + const tr = path.basename(file.history[0], '.json'); + const subtags = tr.split('-'); + const src = [inDir + '/en.json']; // Start with en as a fallback for missing translations + for (i = 1; i <= subtags.length; i++) { + const lang = subtags.slice(0, i).join('-'); + src.push(inDir + '/' + lang + '.json'); + } + return gulp.src(src) + .pipe(merge({ + fileName: tr + '.json', + })) + .pipe(transform(function(data, file) { + // Polymer.AppLocalizeBehavior requires flattened json + return flatten(data); + })) + .pipe(minify()) + .pipe(gulp.dest(outDir)); + })); +}); +tasks.push(taskName); + +var taskName = 'build-translation-fingerprints'; +gulp.task(taskName, ['build-merged-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, file) { + Object.keys(data).map(function(key, index) { + data[key] = {fingerprint: data[key]}; + }); + return data; + })) + .pipe(gulp.dest('build-temp')); +}); +tasks.push(taskName); + +var taskName = 'build-translations'; +gulp.task(taskName, ['build-translation-fingerprints', 'build-translation-native-names'], function() { + return gulp.src([ + 'build-temp/translationFingerprints.json', + 'build-temp/translationNativeNames.json', + ]) + .pipe(merge({})) + .pipe(insert.wrap('')) + .pipe(rename('translationMetadata.html')) + .pipe(gulp.dest('build-temp')); +}); +tasks.push(taskName); + +module.exports = tasks; diff --git a/package.json b/package.json index 16e6017adc..a41fa915f0 100644 --- a/package.json +++ b/package.json @@ -39,8 +39,14 @@ "gulp-babel": "^7.0.0", "gulp-file": "^0.3.0", "gulp-filter": "^5.0.1", + "gulp-foreach": "^0.1.0", + "gulp-hash": "^4.0.1", "gulp-html-minifier": "^0.1.8", "gulp-if": "^2.0.2", + "gulp-insert": "^0.5.0", + "gulp-json-transform": "^0.4.2", + "gulp-jsonminify": "^1.0.0", + "gulp-merge-json": "^1.0.0", "gulp-rename": "^1.2.2", "gulp-rollup-each": "^2.0.0", "gulp-uglify": "^3.0.0", diff --git a/panels/config/core/ha-config-core.html b/panels/config/core/ha-config-core.html index 627aac14c9..ed79e49075 100644 --- a/panels/config/core/ha-config-core.html +++ b/panels/config/core/ha-config-core.html @@ -7,6 +7,7 @@ + @@ -53,6 +54,14 @@ hass='[[hass]]' > + +