]> git.proxmox.com Git - mirror_novnc.git/blame - app/localization.js
feat: add French localization strings
[mirror_novnc.git] / app / localization.js
CommitLineData
6d6f0db0
SR
1/*
2 * noVNC: HTML5 VNC client
84586c0f 3 * Copyright (C) 2018 The noVNC Authors
6d6f0db0
SR
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
0e4808bf
JD
13export class Localizer {
14 constructor() {
15 // Currently configured language
16 this.language = 'en';
6d6f0db0 17
0e4808bf
JD
18 // Current dictionary of translations
19 this.dictionary = undefined;
20 }
6d6f0db0 21
6d6f0db0 22 // Configure suitable language based on user preferences
0e4808bf 23 setup(supportedLanguages) {
6d6f0db0
SR
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 */
2b5f94fa 30 let userLanguages;
6d6f0db0
SR
31 if (typeof window.navigator.languages == 'object') {
32 userLanguages = window.navigator.languages;
33 } else {
34 userLanguages = [navigator.language || navigator.userLanguage];
35 }
36
2b5f94fa
JD
37 for (let i = 0;i < userLanguages.length;i++) {
38 const userLang = userLanguages[i]
39 .toLowerCase()
40 .replace("_", "-")
41 .split("-");
6d6f0db0
SR
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
2b5f94fa
JD
50 for (let j = 0; j < supportedLanguages.length; j++) {
51 const supLang = supportedLanguages[j]
52 .toLowerCase()
53 .replace("_", "-")
54 .split("-");
6d6f0db0 55
426a8c92 56 if (userLang[0] !== supLang[0]) {
6d6f0db0 57 continue;
426a8c92
PO
58 }
59 if (userLang[1] !== supLang[1]) {
6d6f0db0 60 continue;
426a8c92 61 }
6d6f0db0
SR
62
63 this.language = supportedLanguages[j];
64 return;
65 }
66
67 // Second pass: fallback
2b5f94fa
JD
68 for (let j = 0;j < supportedLanguages.length;j++) {
69 const supLang = supportedLanguages[j]
70 .toLowerCase()
71 .replace("_", "-")
72 .split("-");
6d6f0db0 73
426a8c92 74 if (userLang[0] !== supLang[0]) {
6d6f0db0 75 continue;
426a8c92
PO
76 }
77 if (supLang[1] !== undefined) {
6d6f0db0 78 continue;
426a8c92 79 }
6d6f0db0
SR
80
81 this.language = supportedLanguages[j];
82 return;
83 }
84 }
0e4808bf 85 }
6d6f0db0
SR
86
87 // Retrieve localised text
0e4808bf 88 get(id) {
6d6f0db0
SR
89 if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) {
90 return this.dictionary[id];
91 } else {
92 return id;
93 }
0e4808bf 94 }
6d6f0db0
SR
95
96 // Traverses the DOM and translates relevant fields
97 // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate
0e4808bf 98 translateDOM() {
2b5f94fa 99 const self = this;
0e4808bf 100
6d6f0db0
SR
101 function process(elem, enabled) {
102 function isAnyOf(searchElement, items) {
103 return items.indexOf(searchElement) !== -1;
104 }
105
106 function translateAttribute(elem, attr) {
2b5f94fa 107 const str = self.get(elem.getAttribute(attr));
6d6f0db0
SR
108 elem.setAttribute(attr, str);
109 }
110
111 function translateTextNode(node) {
2b5f94fa 112 const str = self.get(node.data.trim());
6d6f0db0
SR
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",
7b536961 139 "OPTION", "TRACK"])) {
6d6f0db0
SR
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" &&
86b9c473 152 isAnyOf(elem.getAttribute("type"), ["reset", "button", "submit"])) {
6d6f0db0
SR
153 translateAttribute(elem, "value");
154 }
155 }
156
2b5f94fa
JD
157 for (let i = 0; i < elem.childNodes.length; i++) {
158 const node = elem.childNodes[i];
6d6f0db0
SR
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);
0e4808bf
JD
168 }
169}
6d6f0db0 170
2b5f94fa 171export const l10n = new Localizer();
6d6f0db0 172export default l10n.get.bind(l10n);