Also translate HTML elements

This commit is contained in:
Pierre Ossman 2016-11-14 22:02:12 +01:00
parent 3cdc603aa4
commit edffd9e2f8
7 changed files with 450 additions and 13 deletions

View File

@ -96,6 +96,9 @@ var UI;
UI.initSettings();
// Translate the DOM
Util.Localisation.translateDOM();
// Adapt the interface for touch screen devices
if (Util.isTouchDevice) {
document.documentElement.classList.add("noVNC_touch");

View File

@ -440,7 +440,81 @@ Util.Localisation = {
} else {
return id;
}
}
},
// Traverses the DOM and translates relevant fields
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
translateDOM: function () {
function process(elem, enabled) {
function isAnyOf(searchElement, items) {
return items.indexOf(searchElement) !== -1;
}
function translateAttribute(elem, attr) {
var str = elem.getAttribute(attr);
str = Util.Localisation.get(str);
elem.setAttribute(attr, str);
}
function translateTextNode(node) {
var str = node.data.trim();
str = Util.Localisation.get(str);
node.data = str;
}
if (elem.hasAttribute("translate")) {
if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
enabled = true;
} else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
enabled = false;
}
}
if (enabled) {
if (elem.hasAttribute("abbr") &&
elem.tagName === "TH") {
translateAttribute(elem, "abbr");
}
if (elem.hasAttribute("alt") &&
isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
translateAttribute(elem, "alt");
}
if (elem.hasAttribute("download") &&
isAnyOf(elem.tagName, ["A", "AREA"])) {
translateAttribute(elem, "download");
}
if (elem.hasAttribute("label") &&
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
"OPTION", "TRACK"])) {
translateAttribute(elem, "label");
}
// FIXME: Should update "lang"
if (elem.hasAttribute("placeholder") &&
isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) {
translateAttribute(elem, "placeholder");
}
if (elem.hasAttribute("title")) {
translateAttribute(elem, "title");
}
if (elem.hasAttribute("value") &&
elem.tagName === "INPUT" &&
isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
translateAttribute(elem, "value");
}
}
for (var i = 0;i < elem.childNodes.length;i++) {
node = elem.childNodes[i];
if (node.nodeType === node.ELEMENT_NODE) {
process(node, enabled);
} else if (node.nodeType === node.TEXT_NODE && enabled) {
translateTextNode(node);
}
}
}
process(document.body, true);
},
};
/* [module] export default Util; */

View File

@ -37,6 +37,7 @@
"chai": "^3.5.0",
"commander": "^2.9.0",
"fs-extra": "^1.0.0",
"jsdom": "*",
"karma": "^1.3.0",
"karma-chai": "^0.1.0",
"karma-mocha": "^1.3.0",
@ -46,6 +47,7 @@
"karma-sinon": "^1.0.5",
"karma-sinon-chai-latest": "^0.1.0",
"mocha": "^3.1.2",
"node-getopt": "*",
"open": "^0.0.5",
"phantomjs-prebuilt": "^2.1.13",
"po2json": "*",

View File

@ -17,7 +17,7 @@ update-js: $(JSFILES)
./po2js $< $@
update-pot:
xgettext --output=noVNC.pot \
xgettext --output=noVNC.js.pot \
--copyright-holder="Various Authors" \
--package-name="noVNC" \
--package-version="$(VERSION)" \
@ -27,3 +27,8 @@ update-pot:
../app/*.js \
../core/*.js \
../core/input/*.js
./xgettext-html --output=noVNC.html.pot \
../vnc.html
msgcat --output-file=noVNC.pot \
--sort-by-file noVNC.js.pot noVNC.html.pot
rm -f noVNC.js.pot noVNC.html.pot

View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: noVNC 0.6.1\n"
"Report-Msgid-Bugs-To: novnc@googlegroups.com\n"
"POT-Creation-Date: 2016-11-15 08:11+0100\n"
"POT-Creation-Date: 2016-11-15 19:32+0100\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"
@ -17,35 +17,35 @@ msgstr ""
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: ../app/ui.js:402
#: ../app/ui.js:405
msgid "Connecting..."
msgstr ""
#: ../app/ui.js:409
#: ../app/ui.js:412
msgid "Connected (encrypted) to "
msgstr ""
#: ../app/ui.js:411
#: ../app/ui.js:414
msgid "Connected (unencrypted) to "
msgstr ""
#: ../app/ui.js:416
#: ../app/ui.js:419
msgid "Disconnecting..."
msgstr ""
#: ../app/ui.js:421
#: ../app/ui.js:424
msgid "Disconnected"
msgstr ""
#: ../app/ui.js:1006 ../core/rfb.js:278
#: ../app/ui.js:1009 ../core/rfb.js:278
msgid "Must set host and port"
msgstr ""
#: ../app/ui.js:1059
#: ../app/ui.js:1062
msgid "Password is required"
msgstr ""
#: ../app/ui.js:1272
#: ../app/ui.js:1275
msgid ""
"Forcing clipping mode since scrollbars aren't supported by IE in fullscreen"
msgstr ""
@ -53,3 +53,239 @@ msgstr ""
#: ../core/rfb.js:556
msgid "Disconnect timeout"
msgstr ""
#: ../vnc.html:70
msgid "noVNC encountered an error:"
msgstr ""
#: ../vnc.html:78
msgid "Hide/Show the control bar"
msgstr ""
#: ../vnc.html:85
msgid "Move/Drag Viewport"
msgstr ""
#: ../vnc.html:85
msgid "viewport drag"
msgstr ""
#: ../vnc.html:91 ../vnc.html:94 ../vnc.html:97 ../vnc.html:100
msgid "Active Mouse Button"
msgstr ""
#: ../vnc.html:91
msgid "No mousebutton"
msgstr ""
#: ../vnc.html:94
msgid "Left mousebutton"
msgstr ""
#: ../vnc.html:97
msgid "Middle mousebutton"
msgstr ""
#: ../vnc.html:100
msgid "Right mousebutton"
msgstr ""
#: ../vnc.html:103
msgid "Keyboard"
msgstr ""
#: ../vnc.html:103
msgid "Show Keyboard"
msgstr ""
#: ../vnc.html:110
msgid "Extra keys"
msgstr ""
#: ../vnc.html:110
msgid "Show Extra Keys"
msgstr ""
#: ../vnc.html:115
msgid "Ctrl"
msgstr ""
#: ../vnc.html:115
msgid "Toggle Ctrl"
msgstr ""
#: ../vnc.html:118
msgid "Alt"
msgstr ""
#: ../vnc.html:118
msgid "Toggle Alt"
msgstr ""
#: ../vnc.html:121
msgid "Send Tab"
msgstr ""
#: ../vnc.html:121
msgid "Tab"
msgstr ""
#: ../vnc.html:124
msgid "Esc"
msgstr ""
#: ../vnc.html:124
msgid "Send Escape"
msgstr ""
#: ../vnc.html:127
msgid "Ctrl+Alt+Del"
msgstr ""
#: ../vnc.html:127
msgid "Send Ctrl-Alt-Del"
msgstr ""
#: ../vnc.html:135
msgid "Shutdown/Reboot"
msgstr ""
#: ../vnc.html:135
msgid "Shutdown/Reboot..."
msgstr ""
#: ../vnc.html:141
msgid "Power"
msgstr ""
#: ../vnc.html:143
msgid "Shutdown"
msgstr ""
#: ../vnc.html:144
msgid "Reboot"
msgstr ""
#: ../vnc.html:145
msgid "Reset"
msgstr ""
#: ../vnc.html:150 ../vnc.html:156
msgid "Clipboard"
msgstr ""
#: ../vnc.html:160
msgid "Clear"
msgstr ""
#: ../vnc.html:166
msgid "Fullscreen"
msgstr ""
#: ../vnc.html:171 ../vnc.html:178
msgid "Settings"
msgstr ""
#: ../vnc.html:181
msgid "Encrypt"
msgstr ""
#: ../vnc.html:184
msgid "True Color"
msgstr ""
#: ../vnc.html:187
msgid "Local Cursor"
msgstr ""
#: ../vnc.html:190
msgid "Clip to Window"
msgstr ""
#: ../vnc.html:193
msgid "Shared Mode"
msgstr ""
#: ../vnc.html:196
msgid "View Only"
msgstr ""
#: ../vnc.html:200
msgid "Path:"
msgstr ""
#: ../vnc.html:204
msgid "Scaling Mode:"
msgstr ""
#: ../vnc.html:206
msgid "None"
msgstr ""
#: ../vnc.html:207
msgid "Local Scaling"
msgstr ""
#: ../vnc.html:208
msgid "Local Downscaling"
msgstr ""
#: ../vnc.html:209
msgid "Remote Resizing"
msgstr ""
#: ../vnc.html:213
msgid "Repeater ID:"
msgstr ""
#: ../vnc.html:219
msgid "Style:"
msgstr ""
#: ../vnc.html:221
msgid "default"
msgstr ""
#: ../vnc.html:227
msgid "Logging:"
msgstr ""
#: ../vnc.html:234
msgid "Apply"
msgstr ""
#: ../vnc.html:241 ../vnc.html:271
msgid "Connect"
msgstr ""
#: ../vnc.html:244
msgid "Disconnect"
msgstr ""
#: ../vnc.html:251
msgid "Connection"
msgstr ""
#: ../vnc.html:254
msgid "Host:"
msgstr ""
#: ../vnc.html:258
msgid "Port:"
msgstr ""
#: ../vnc.html:262 ../vnc.html:290
msgid "Password:"
msgstr ""
#: ../vnc.html:266
msgid "Token:"
msgstr ""
#: ../vnc.html:294
msgid "Send Password"
msgstr ""
#: ../vnc.html:319
msgid "Canvas not supported."
msgstr ""

117
po/xgettext-html Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env node
/*
* xgettext-html: HTML gettext parser
* Copyright (C) 2016 Pierre Ossman
* Licensed under MPL 2.0 (see LICENSE.txt)
*/
var getopt = require('node-getopt');
var jsdom = require("jsdom");
var fs = require("fs");
opt = getopt.create([
['o' , 'output=FILE' , 'write output to specified file'],
['h' , 'help' , 'display this help'],
]).bindHelp().parseSystem();
var strings = {};
function addString(str, location) {
if (str.length == 0) {
return;
}
if (strings[str] === undefined) {
strings[str] = {}
}
strings[str][location] = null;
}
// See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
function process(elem, locator, enabled) {
function isAnyOf(searchElement, items) {
return items.indexOf(searchElement) !== -1;
}
if (elem.hasAttribute("translate")) {
if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
enabled = true;
} else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
enabled = false;
}
}
if (enabled) {
if (elem.hasAttribute("abbr") &&
elem.tagName === "TH") {
addString(elem.getAttribute("abbr"), locator(elem));
}
if (elem.hasAttribute("alt") &&
isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
addString(elem.getAttribute("alt"), locator(elem));
}
if (elem.hasAttribute("download") &&
isAnyOf(elem.tagName, ["A", "AREA"])) {
addString(elem.getAttribute("download"), locator(elem));
}
if (elem.hasAttribute("label") &&
isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
"OPTION", "TRACK"])) {
addString(elem.getAttribute("label"), locator(elem));
}
if (elem.hasAttribute("placeholder") &&
isAnyOf(elem.tagName in ["INPUT", "TEXTAREA"])) {
addString(elem.getAttribute("placeholder"), locator(elem));
}
if (elem.hasAttribute("title")) {
addString(elem.getAttribute("title"), locator(elem));
}
if (elem.hasAttribute("value") &&
elem.tagName === "INPUT" &&
isAnyOf(elem.getAttribute("type"), ["reset", "button"])) {
addString(elem.getAttribute("value"), locator(elem));
}
}
for (var i = 0;i < elem.childNodes.length;i++) {
node = elem.childNodes[i];
if (node.nodeType === node.ELEMENT_NODE) {
process(node, locator, enabled);
} else if (node.nodeType === node.TEXT_NODE && enabled) {
addString(node.data.trim(), locator(node));
}
}
}
for (var i = 0;i < opt.argv.length;i++) {
var file;
fn = opt.argv[i];
file = fs.readFileSync(fn, "utf8");
doc = jsdom.jsdom(file);
locator = function (elem) {
offset = jsdom.nodeLocation(elem).start;
line = file.slice(0, offset).split("\n").length;
return fn + ":" + line;
};
process(doc.body, locator, true);
}
var output = "";
for (str in strings) {
output += "#:";
for (location in strings[str]) {
output += " " + location;
}
output += "\n";
output += "msgid " + JSON.stringify(str) + "\n";
output += "msgstr \"\"\n";
output += "\n";
}
fs.writeFileSync(opt.options.output, output);

View File

@ -79,7 +79,7 @@
<div class="noVNC_scroll">
<h1 class="noVNC_logo"><span>no</span><br />VNC</h1>
<h1 class="noVNC_logo" translate="no"><span>no</span><br />VNC</h1>
<!-- Drag/Pan the viewport -->
<input type="image" alt="viewport drag" src="app/images/drag.svg"
@ -304,7 +304,7 @@
</div>
<div id="noVNC_container">
<h1 id="noVNC_logo" class="noVNC_logo"><span>no</span><br />VNC</h1>
<h1 id="noVNC_logo" class="noVNC_logo" translate="no"><span>no</span><br />VNC</h1>
<!-- HTML5 Canvas -->
<div id="noVNC_screen">