]> git.proxmox.com Git - mirror_novnc.git/blob - app/localization.js
Update generated JS files for translations
[mirror_novnc.git] / app / localization.js
1 /*
2 * noVNC: HTML5 VNC client
3 * Copyright (C) 2018 The noVNC Authors
4 * Licensed under MPL 2.0 (see LICENSE.txt)
5 *
6 * See README.md for usage and integration instructions.
7 */
8
9 /*
10 * Localization Utilities
11 */
12
13 export class Localizer {
14 constructor() {
15 // Currently configured language
16 this.language = 'en';
17
18 // Current dictionary of translations
19 this.dictionary = undefined;
20 }
21
22 // Configure suitable language based on user preferences
23 setup(supportedLanguages) {
24 this.language = 'en'; // Default: US English
25
26 /*
27 * Navigator.languages only available in Chrome (32+) and FireFox (32+)
28 * Fall back to navigator.language for other browsers
29 */
30 let userLanguages;
31 if (typeof window.navigator.languages == 'object') {
32 userLanguages = window.navigator.languages;
33 } else {
34 userLanguages = [navigator.language || navigator.userLanguage];
35 }
36
37 for (let i = 0;i < userLanguages.length;i++) {
38 const userLang = userLanguages[i]
39 .toLowerCase()
40 .replace("_", "-")
41 .split("-");
42
43 // Built-in default?
44 if ((userLang[0] === 'en') &&
45 ((userLang[1] === undefined) || (userLang[1] === 'us'))) {
46 return;
47 }
48
49 // First pass: perfect match
50 for (let j = 0; j < supportedLanguages.length; j++) {
51 const supLang = supportedLanguages[j]
52 .toLowerCase()
53 .replace("_", "-")
54 .split("-");
55
56 if (userLang[0] !== supLang[0]) {
57 continue;
58 }
59 if (userLang[1] !== supLang[1]) {
60 continue;
61 }
62
63 this.language = supportedLanguages[j];
64 return;
65 }
66
67 // Second pass: fallback
68 for (let j = 0;j < supportedLanguages.length;j++) {
69 const supLang = supportedLanguages[j]
70 .toLowerCase()
71 .replace("_", "-")
72 .split("-");
73
74 if (userLang[0] !== supLang[0]) {
75 continue;
76 }
77 if (supLang[1] !== undefined) {
78 continue;
79 }
80
81 this.language = supportedLanguages[j];
82 return;
83 }
84 }
85 }
86
87 // Retrieve localised text
88 get(id) {
89 if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
90 return this.dictionary[id];
91 } else {
92 return id;
93 }
94 }
95
96 // Traverses the DOM and translates relevant fields
97 // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
98 translateDOM() {
99 const self = this;
100
101 function process(elem, enabled) {
102 function isAnyOf(searchElement, items) {
103 return items.indexOf(searchElement) !== -1;
104 }
105
106 function translateAttribute(elem, attr) {
107 const str = self.get(elem.getAttribute(attr));
108 elem.setAttribute(attr, str);
109 }
110
111 function translateTextNode(node) {
112 const str = self.get(node.data.trim());
113 node.data = str;
114 }
115
116 if (elem.hasAttribute("translate")) {
117 if (isAnyOf(elem.getAttribute("translate"), ["", "yes"])) {
118 enabled = true;
119 } else if (isAnyOf(elem.getAttribute("translate"), ["no"])) {
120 enabled = false;
121 }
122 }
123
124 if (enabled) {
125 if (elem.hasAttribute("abbr") &&
126 elem.tagName === "TH") {
127 translateAttribute(elem, "abbr");
128 }
129 if (elem.hasAttribute("alt") &&
130 isAnyOf(elem.tagName, ["AREA", "IMG", "INPUT"])) {
131 translateAttribute(elem, "alt");
132 }
133 if (elem.hasAttribute("download") &&
134 isAnyOf(elem.tagName, ["A", "AREA"])) {
135 translateAttribute(elem, "download");
136 }
137 if (elem.hasAttribute("label") &&
138 isAnyOf(elem.tagName, ["MENUITEM", "MENU", "OPTGROUP",
139 "OPTION", "TRACK"])) {
140 translateAttribute(elem, "label");
141 }
142 // FIXME: Should update "lang"
143 if (elem.hasAttribute("placeholder") &&
144 isAnyOf(elem.tagName, ["INPUT", "TEXTAREA"])) {
145 translateAttribute(elem, "placeholder");
146 }
147 if (elem.hasAttribute("title")) {
148 translateAttribute(elem, "title");
149 }
150 if (elem.hasAttribute("value") &&
151 elem.tagName === "INPUT" &&
152 isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) {
153 translateAttribute(elem, "value");
154 }
155 }
156
157 for (let i = 0; i < elem.childNodes.length; i++) {
158 const node = elem.childNodes[i];
159 if (node.nodeType === node.ELEMENT_NODE) {
160 process(node, enabled);
161 } else if (node.nodeType === node.TEXT_NODE && enabled) {
162 translateTextNode(node);
163 }
164 }
165 }
166
167 process(document.body, true);
168 }
169 }
170
171 export const l10n = new Localizer();
172 export default l10n.get.bind(l10n);