localization.js 5.5 KB

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