Skip to content

Commit

Permalink
Create cockpit translation modules.
Browse files Browse the repository at this point in the history
For each translation file available, create a module for cockpit
containing the translations. We only need these modules to initialize
the locale data within cockpit and to provide translations for the
manifest label, but just include the entire .po file anyway so that we
don't need to do anything special.

po2json and po.empty.js are copied from the cockpit sources, and handle
converting .po files into modules in the format that cockpit requires.
Despite the name, the data format is very much not JSON, so beware if
attempting to modify the scripts. po.empty.js is modified from the
cockpit source to include an eslint-disable comment.

In main.js, check if we are running within cockpit, and if so use
cockpit's language information to select the messages to use with
react-intl.
  • Loading branch information
dashea committed Jul 26, 2018
1 parent 83e4c63 commit b8f4f0e
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -5,6 +5,7 @@ public/dist
public/css
public/fonts
public/img
public/po.*.js
db.json
core/build-version.json
npm-debug.log
Expand Down
10 changes: 9 additions & 1 deletion main.js
Expand Up @@ -12,6 +12,7 @@ import 'bootstrap';
import store from './core/store';
import router from './core/router';
import history from './core/history';
import utils from './core/utils';

// Intialize any necessary locale data, and load translated messages
import './build/localeLoader'; // eslint-disable-line import/no-unresolved
Expand All @@ -23,7 +24,14 @@ let routes = require('./routes.json'); // Loaded with utils/routes-loader.js
const container = document.getElementById('main');

// Check if we have translations for the user's language
let userLanguage = navigator.language.split('-')[0];
let userLanguage;
if (utils.inCockpit) {
var cockpit = require('cockpit'); // eslint-disable-line global-require, import/no-unresolved
userLanguage = cockpit.language;
} else {
userLanguage = navigator.language.split('-')[0];
}

let messages = undefined;
if (userLanguage in translations) {
messages = translations[userLanguage];
Expand Down
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Expand Up @@ -64,7 +64,9 @@
"faker": "^4.1.0",
"file-loader": "^0.9.0",
"front-matter": "^2.1.0",
"glob": "^6.0.4",
"highlight.js": "^9.5.0",
"jed": "^1.1.1",
"jest": "^20.0.4",
"jsel": "^1.1.6",
"json-loader": "^0.5.4",
Expand All @@ -73,6 +75,7 @@
"path-to-regexp": "^1.5.3",
"pixrem": "^3.0.1",
"pleeease-filters": "^3.0.0",
"po2json": "^0.4.5",
"postcss": "^5.1.1",
"postcss-calc": "^5.3.0",
"postcss-color-function": "^2.0.1",
Expand All @@ -90,6 +93,7 @@
"react-intl-po": "^2.2.2",
"react-test-renderer": "^15.6.1",
"s3": "^4.4.0",
"stdio": "^0.2.7",
"style-loader": "^0.13.1",
"stylelint": "^9.3.0",
"stylelint-config-standard": "^18.2.0",
Expand Down
1 change: 1 addition & 0 deletions public/index.ejs
Expand Up @@ -15,6 +15,7 @@
<script src="dist/jquery.min.js"></script>
<script src="dist/jquery.matchHeight-min.js"></script>
<script type="text/javascript" src="../base1/cockpit.js"></script>
<script type="text/javascript" src="../*/po.js"></script>
<script src="dist/webcomponents-lite.js"></script>
<script src="dist/native-shim.js"></script>
<script src="./js/config.js"></script>
Expand Down
23 changes: 21 additions & 2 deletions run.js
Expand Up @@ -2,7 +2,9 @@
const fs = require('fs');
const del = require('del');
const ejs = require('ejs');
const glob = require('glob');
const mkdirp = require('mkdirp');
const path = require('path');
const webpack = require('webpack');
// TODO: Update configuration settings
const config = {
Expand All @@ -22,7 +24,7 @@ function run(task) {
//
// Clean up the output directory
// -----------------------------------------------------------------------------
tasks.set('clean', () => del(['public/dist/*', '!public/dist/.git', 'build/localeLoader.js'], { dot: true }));
tasks.set('clean', () => del(['public/dist/*', '!public/dist/.git', 'public/po.*.js', 'build/localeLoader.js'], { dot: true }));

//
// Copy ./index.html into the /public folder
Expand All @@ -40,7 +42,7 @@ tasks.set('html', () => {
// Create a module to handle loading the necessary locale data
// -----------------------------------------------------------------------------
tasks.set('locale-data', () => {
return new Promise(resolve => {
return new Promise((resolve, error) => {
// If the translations file does not exist, create an empty one
const translationsPath = './build/translations.json';
if (!fs.existsSync(translationsPath)) {
Expand All @@ -61,6 +63,23 @@ tasks.set('locale-data', () => {
})
`;
fs.writeFileSync(outputName, outputData);

// For cockpit, for each language, run po2json on the original .po file to output a module
// to public/po.<lang>.js containing the necessary initialization code and data.

const exec = require('child_process').exec;
var pofiles = glob.sync('./build/po/*.po');
pofiles.forEach((filename) => {
var language = path.basename(filename, '.po');
exec(`node ./utils/po2json -m ./utils/po.empty.js -o public/po.${language}.js ${filename}`,
(err) => {
if (err !== null) {
console.error(`Unable to create cockpit module for ${language}`);
error(err);
}
});
});

resolve();
});
});
Expand Down
62 changes: 62 additions & 0 deletions utils/po.empty.js
@@ -0,0 +1,62 @@
/* eslint-disable */
(function (root, data) {
var loaded, module;

/* Load into AMD if desired */
if (typeof define === 'function' && define.amd) {
define(data);
loaded = true;
}

/* Load into Cockpit locale */
if (typeof cockpit === 'object') {
cockpit.locale(data)
loaded = true;
}

function transformAngular(data, prev) {
var key, context, parts, value, result = { };
for (key in data) {
if (key === "")
continue;
parts = key.split("\u0004");
value = data[key];
if (parts[1]) {
context = parts[0];
key = parts[1];
} else {
context = "$$noContext";
key = parts[0];
}
if (value[0] === null)
value = value[1];
else
value = value.slice(1);
if (!(key in result))
result[key] = { };
result[key][context] = value;
}
return angular.extend(prev, result);
}

/* Load into angular here */
if (typeof angular === 'object') {
try {
module = angular.module(["gettext"]);
} catch(ex) { console.log(ex); /* Either no angular or angular-gettext */ };
if (module) {
loaded = true;
module.run(['gettextCatalog', function(gettextCatalog) {
var lang = data[""]["language"];
var prev = (gettextCatalog.getCurrentLanguage() == lang) ? gettextCatalog.strings : { };
gettextCatalog.setStrings(lang, transformAngular(data, prev));
gettextCatalog.setCurrentLanguage(lang);
}]);
}
}

if (!loaded)
root.po = data;

/* The syntax of this line is important by po2json */
}(this, {"":{"language":"en"}}));
127 changes: 127 additions & 0 deletions utils/po2json
@@ -0,0 +1,127 @@
#!/usr/bin/env node

function fatal(message, code) {
console.log((filename || "html2po") + ": " + message);
process.exit(code || 1);
}

function usage() {
console.log("usage: po2json [--module=template.js] input output");
process.exit(2);
}

var fs, po2json, Jed, stdio;

try {
fs = require('fs');
po2json = require('po2json');
Jed = require('jed');
stdio = require('stdio');
} catch(ex) {
fatal(ex.message, 127); /* missing looks for this */
}

var argi = 2;
var filename = null;

var opts = stdio.getopt({
module: { key: "m", args: 1, description: "Module template to include" },
output: { key: "o", args: 1, description: "Output file" },
});

if (opts.args.length != 1) {
usage();
}

parse();

function prepareHeader(header) {
var body, statement, plurals = header["plural-forms"], ret = null;
if (plurals) {
try {
/* Check that the plural forms isn't being sneaky since we build a function here */
Jed.PF.parse(plurals);
} catch(ex) {
fatal("bad plural forms: " + ex.message, 1);
}

/* A function for the front end */
statement = header["plural-forms"];
if (statement[statement.length - 1] != ';')
statement += ';';
ret = 'function(n) {\nvar nplurals, plural;\n' + statement + '\nreturn plural;\n}';

/* Added back in later */
delete header["plural-forms"];
}

/* We don't need to be transferring this */
delete header["project-id-version"];
delete header["report-msgid-bugs-to"];
delete header["pot-creation-date"];
delete header["po-revision-date"];
delete header["last-translator"];
delete header["language-team"];
delete header["mime-version"];
delete header["content-type"];
delete header["content-transfer-encoding"];

return ret;
}

/* Parse and process the po data */
function parse() {
filename = opts.args[0];
po2json.parseFile(opts.args[0], { "fuzzy": true }, function(err, jsonData) {
var plurals, pos;

if (err)
fatal(err.message);

var header = jsonData[""];
if (header)
plurals = prepareHeader(header);

var data = JSON.stringify(jsonData, null, 1);

/* We know the brace in is the location to insert our function */
if (plurals) {
pos = data.indexOf('{', 1);
data = data.substr(0, pos + 1) + "'plural-forms':" + String(plurals) + "," + data.substr(pos + 1);
}

if (data == JSON.stringify({}))
finish("");
else
wrap(data);
});
}

/* Wrap the data if desired */
function wrap(data) {
if (opts.module) {
filename = opts.module;
fs.readFile(opts.module, { encoding: "utf-8" }, function(err, template) {
if (err)
fatal(err.message);
data = template.replace('{"":{"language":"en"}}', data);
finish(data);
});
} else {
finish(data);
}
}

/* Write it out */
function finish(data) {
if (opts.output) {
fs.writeFile(opts.output, data, function(err) {
if (err)
fatal(err.message);
process.exit(0);
});
} else {
process.stdout.write(data);
process.exit(0);
}
}

0 comments on commit b8f4f0e

Please sign in to comment.