/* * noVNC: HTML5 VNC client * Copyright (C) 2018 The noVNC Authors * Licensed under MPL 2.0 (see LICENSE.txt) * * See README.md for usage and integration instructions. */ /* * Localization Utilities */ export class Localizer { constructor() { // Currently configured language this.language = 'en'; // Current dictionary of translations this.dictionary = undefined; } // Configure suitable language based on user preferences setup(supportedLanguages) { this.language = 'en'; // Default: US English /* * Navigator.languages only available in Chrome (32+) and FireFox (32+) * Fall back to navigator.language for other browsers */ let userLanguages; if (typeof window.navigator.languages == 'object') { userLanguages = window.navigator.languages; } else { userLanguages = [navigator.language || navigator.userLanguage]; } for (let i = 0;i < userLanguages.length;i++) { const userLang = userLanguages[i] .toLowerCase() .replace("_", "-") .split("-"); // Built-in default? if ((userLang[0] === 'en') && ((userLang[1] === undefined) || (userLang[1] === 'us'))) { return; } // First pass: perfect match for (let j = 0; j < supportedLanguages.length; j++) { const supLang = supportedLanguages[j] .toLowerCase() .replace("_", "-") .split("-"); if (userLang[0] !== supLang[0]) { continue; } if (userLang[1] !== supLang[1]) { continue; } this.language = supportedLanguages[j]; return; } // Second pass: fallback for (let j = 0;j < supportedLanguages.length;j++) { const supLang = supportedLanguages[j] .toLowerCase() .replace("_", "-") .split("-"); if (userLang[0] !== supLang[0]) { continue; } if (supLang[1] !== undefined) { continue; } this.language = supportedLanguages[j]; return; } } } // Retrieve localised text get(id) { if (typeof this.dictionary !== 'undefined' && this.dictionary[id]) { return this.dictionary[id]; } else { return id; } } // Traverses the DOM and translates relevant fields // See https://html.spec.whatwg.org/multipage/dom.html#attr-translate translateDOM() { const self = this; function process(elem, enabled) { function isAnyOf(searchElement, items) { return items.indexOf(searchElement) !== -1; } function translateAttribute(elem, attr) { const str = self.get(elem.getAttribute(attr)); elem.setAttribute(attr, str); } function translateTextNode(node) { const str = self.get(node.data.trim()); 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, ["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", "submit"])) { translateAttribute(elem, "value"); } } for (let i = 0; i < elem.childNodes.length; i++) { const 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); } } export const l10n = new Localizer(); export default l10n.get.bind(l10n);