Add i18n support
Make the "Running on.." string translatable and copy the extraction and conversion of JSX and PO files from Cockpit.
This commit is contained in:
parent
21950771af
commit
7ce7b2b40b
8 changed files with 220 additions and 3 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -10,3 +10,5 @@ Test*FAIL*
|
||||||
bots/
|
bots/
|
||||||
test/common/
|
test/common/
|
||||||
test/images/
|
test/images/
|
||||||
|
*.pot
|
||||||
|
POTFILES*
|
||||||
|
|
|
||||||
38
Makefile
38
Makefile
|
|
@ -7,7 +7,41 @@ VM_IMAGE=$(CURDIR)/test/images/$(TEST_OS)
|
||||||
|
|
||||||
all: dist/index.js
|
all: dist/index.js
|
||||||
|
|
||||||
dist/index.js: node_modules/react-lite $(wildcard src/*) package.json webpack.config.js
|
#
|
||||||
|
# i18n
|
||||||
|
#
|
||||||
|
|
||||||
|
LINGUAS=$(basename $(notdir $(wildcard po/*.po)))
|
||||||
|
|
||||||
|
po/POTFILES.js.in:
|
||||||
|
mkdir -p $(dir $@)
|
||||||
|
find src/ -name '*.js' -o -name '*.jsx' -o -name '*.es6' > $@
|
||||||
|
|
||||||
|
po/$(PACKAGE_NAME).pot: po/POTFILES.js.in
|
||||||
|
xgettext --default-domain=cockpit --output=$@ --language=C --keyword= \
|
||||||
|
--keyword=_:1,1t --keyword=_:1c,2,1t --keyword=C_:1c,2 \
|
||||||
|
--keyword=N_ --keyword=NC_:1c,2 \
|
||||||
|
--keyword=gettext:1,1t --keyword=gettext:1c,2,2t \
|
||||||
|
--keyword=ngettext:1,2,3t --keyword=ngettext:1c,2,3,4t \
|
||||||
|
--keyword=gettextCatalog.getString:1,3c --keyword=gettextCatalog.getPlural:2,3,4c \
|
||||||
|
--from-code=UTF-8 --files-from=$^
|
||||||
|
|
||||||
|
# Update translations against current PO template
|
||||||
|
update-po: po/$(PACKAGE_NAME).pot
|
||||||
|
for lang in $(LINGUAS); do \
|
||||||
|
msgmerge --output-file=po/$$lang.po po/$$lang.po $<; \
|
||||||
|
done
|
||||||
|
|
||||||
|
dist/po.%.js: po/%.po
|
||||||
|
mkdir -p $(dir $@)
|
||||||
|
po/po2json -m po/po.empty.js -o $@.js.tmp $<
|
||||||
|
mv $@.js.tmp $@
|
||||||
|
|
||||||
|
#
|
||||||
|
# Build/Install/dist
|
||||||
|
#
|
||||||
|
|
||||||
|
dist/index.js: node_modules/react-lite $(wildcard src/*) package.json webpack.config.js $(patsubst %,dist/po.%.js,$(LINGUAS))
|
||||||
NODE_ENV=$(NODE_ENV) npm run build
|
NODE_ENV=$(NODE_ENV) npm run build
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
@ -80,4 +114,4 @@ test/common:
|
||||||
node_modules/react-lite:
|
node_modules/react-lite:
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
.PHONY: all clean install devel-install dist-gzip srpm rpm check vm
|
.PHONY: all clean install devel-install dist-gzip srpm rpm check vm update-po
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,12 @@
|
||||||
"eslint": "^3.0.0",
|
"eslint": "^3.0.0",
|
||||||
"eslint-loader": "~1.6.1",
|
"eslint-loader": "~1.6.1",
|
||||||
"eslint-plugin-react": "~6.9.0",
|
"eslint-plugin-react": "~6.9.0",
|
||||||
|
"jed": "^1.1.1",
|
||||||
"jshint": "~2.9.1",
|
"jshint": "~2.9.1",
|
||||||
"jshint-loader": "~0.8.3",
|
"jshint-loader": "~0.8.3",
|
||||||
|
"po2json": "^0.4.5",
|
||||||
"sizzle": "^2.3.3",
|
"sizzle": "^2.3.3",
|
||||||
|
"stdio": "^0.2.7",
|
||||||
"webpack": "^2.6.1"
|
"webpack": "^2.6.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
||||||
18
po/de.po
Normal file
18
po/de.po
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# starter-kit German translations
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: starter-kit 1.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2018-06-19 10:54+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: src/app.jsx:40
|
||||||
|
msgid "Running on $0"
|
||||||
|
msgstr "Läuft auf $0"
|
||||||
14
po/po.empty.js
Normal file
14
po/po.empty.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
(function (root, data) {
|
||||||
|
var loaded, module;
|
||||||
|
|
||||||
|
/* Load into Cockpit locale */
|
||||||
|
if (typeof cockpit === 'object') {
|
||||||
|
cockpit.locale(data)
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loaded)
|
||||||
|
root.po = data;
|
||||||
|
|
||||||
|
/* The syntax of this line is important by po2json */
|
||||||
|
}(this, {"":{"language":"en"}}));
|
||||||
127
po/po2json
Executable file
127
po/po2json
Executable file
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,8 @@
|
||||||
import cockpit from 'cockpit';
|
import cockpit from 'cockpit';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
const _ = cockpit.gettext;
|
||||||
|
|
||||||
export class Application extends React.Component {
|
export class Application extends React.Component {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
@ -35,7 +37,7 @@ export class Application extends React.Component {
|
||||||
<div className="container-fluid">
|
<div className="container-fluid">
|
||||||
<h2>Starter Kit</h2>
|
<h2>Starter Kit</h2>
|
||||||
<div>
|
<div>
|
||||||
<span>Running on {this.state.hostname}</span>
|
<span>{ cockpit.format(_("Running on $0"), this.state.hostname) }</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
|
# coding: UTF-8
|
||||||
# Run this with --help to see available options for tracing and debugging
|
# Run this with --help to see available options for tracing and debugging
|
||||||
# See https://github.com/cockpit-project/cockpit/blob/master/test/common/testlib.py
|
# See https://github.com/cockpit-project/cockpit/blob/master/test/common/testlib.py
|
||||||
# "class Browser" and "class MachineCase" for the available API.
|
# "class Browser" and "class MachineCase" for the available API.
|
||||||
|
|
@ -28,6 +29,22 @@ class TestApplication(testlib.MachineCase):
|
||||||
b.wait_present(".container-fluid span")
|
b.wait_present(".container-fluid span")
|
||||||
b.wait_text(".container-fluid span", "Running on " + hostname)
|
b.wait_text(".container-fluid span", "Running on " + hostname)
|
||||||
|
|
||||||
|
# change language to German
|
||||||
|
b.switch_to_top()
|
||||||
|
b.click("#content-user-name")
|
||||||
|
b.click(".display-language-menu a")
|
||||||
|
b.wait_popup('display-language')
|
||||||
|
b.set_val("#display-language select", "de-de")
|
||||||
|
b.click("#display-language-select-button")
|
||||||
|
b.expect_load()
|
||||||
|
# HACK: work around language switching in Chrome not working in current session (Cockpit issue #8160)
|
||||||
|
b.reload(ignore_cache=True)
|
||||||
|
b.wait_present("#content")
|
||||||
|
|
||||||
|
b.go("/starter-kit")
|
||||||
|
b.enter_page("/starter-kit")
|
||||||
|
|
||||||
|
b.wait_in_text(".container-fluid span", "Läuft auf")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
testlib.test_main()
|
testlib.test_main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue