| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- /*
- * 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
- async setup(supportedLanguages, baseURL) {
- this.language = 'en'; // Default: US English
- this._dictionary = undefined;
- this._setupLanguage(supportedLanguages);
- await this._setupDictionary(baseURL);
- }
- _setupLanguage(supportedLanguages) {
- /*
- * 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("-");
- // 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: English fallback
- if (userLang[0] === 'en') {
- return;
- }
- // Third pass pass: other 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;
- }
- }
- }
- async _setupDictionary(baseURL) {
- if (baseURL) {
- if (!baseURL.endsWith("/")) {
- baseURL = baseURL + "/";
- }
- } else {
- baseURL = "";
- }
- if (this.language === "en") {
- return;
- }
- let response = await fetch(baseURL + this.language + ".json");
- if (!response.ok) {
- throw Error("" + response.status + " " + response.statusText);
- }
- this._dictionary = await response.json();
- }
- // 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 translateString(str) {
- // We assume surrounding whitespace, and whitespace around line
- // breaks is just for source formatting
- str = str.split("\n").map(s => s.trim()).join(" ").trim();
- return self.get(str);
- }
- function translateAttribute(elem, attr) {
- const str = translateString(elem.getAttribute(attr));
- elem.setAttribute(attr, str);
- }
- function translateTextNode(node) {
- const str = translateString(node.data);
- 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);
|