PageSourceSearch

https://rooms.xyz/js-intl/intl.js

js rooms.xyz collected 2026-10-02 04:29:25 UTC 5,043 bytes, 148 lines download raw bytes

1// Import English strings statically (they are used as fallback regardless of
2// the language).
3import * as stringsEN from "./strings-en.js";
4
5// This must be kept in sync with the list of supported languages
6// in several places in different codebases:
7//
8// * roomsxyz/auto-translate/supported-languages.txt
9// * IntlSystem.cs
10// * roomsxyz-cloudfun/functions/translator.js
11const SUPPORTED_LANGUAGES = [
12  "en", "fr", "it", "de", "es", "pt", "zh-Hant", "zh-Hans", "ko", "ja",
13  "ru", "vi",
14  "uk", "tr", "pl", "nl"
15];
16
17// Promise that resolves to the internationalized strings table
18// when it loads.
19let intlLoadPromiseResolve = null;
20const intlLoadPromise = new Promise(resolve => {
21  intlLoadPromiseResolve = resolve;
22});
23let stringTable = null;  // Loaded asynchronously
24
25// Must be called when the page loads.
26export function init() {
27  console.log('INTL: init...');
28  asyncLoadStrings();
29}
30
31// Returns a Promise that resolves when internationalization is loaded.
32// Usage:
33//     whenIntlLoaded().then(....)
34// OR:
35//     await whenIntlLoaded()
36//
37// After loaded, you can call getString synchronously to get a string.
38export function whenIntlLoaded() { return intlLoadPromise; }
39
40function getFallbackStringTemplate(stringId) {
41  const entry = stringsEN.STRINGS[stringId];
42  if (typeof(entry) === "string") return entry;
43  if (entry && typeof(entry) === "object" && typeof(entry.text) === "string") return entry.text;
44  return "?" + stringId;
45}
46
47// ATTENTION: this is a synchronous function that can only be called after
48// internationalization is done loading (await on whenIntlLoaded() if needed).
49export function getString(stringId, ...args) {
50  let string = "?" + stringId;
51
52  if (stringTable) {
53    const entry = stringTable[stringId];
54    if (typeof(entry) === "string") {
55      string = entry;
56    } else if (entry && typeof(entry) === "object" && typeof(entry.text) === "string") {
57      string = entry.text;
58    } else {
59      console.warn(`INTL: string not found ${stringId}`);
60      // Fall back to English
61      string = getFallbackStringTemplate(stringId);
62    }
63  } else {
64    console.error("INTL: Attempt to call getString before strings are done loading!");
65    // Fall back to English
66    string = getFallbackStringTemplate(stringId);
67  }
68
69  // Replace special {R} placeholder (app name).
70  // We expand {R} to "Rooms\u00AE" (Rooms followed by the ® registered
71  // trademark symbol) so that every localized string referring to the brand
72  // gets the trademark mark automatically.
73  string = string.replaceAll('{R}', "Rooms\u00AE");
74  string = string.replaceAll('{T}', "Things, Inc");
75
76  // Replace placeholders like {0}, {1}, {2}, etc.
77  return string.replace(/{(\d+)}/g, (match, index) => {
78    return typeof(args[index]) !== 'undefined' ? args[index] : match;
79  });
80}
81
82export function getLangCode() {
83  let lang = navigator.language || "en";
84
85  // Check for stored preference first
86  const storedLang = localStorage.getItem('preferredLanguage');
87  if (storedLang && SUPPORTED_LANGUAGES.includes(storedLang)) {
88    return storedLang;
89  }
90
91  // If the URL contains an 'hl' parameter, honor it
92  if (window.location && window.location.search) {
93    const urlParams = new URLSearchParams(window.location.search);
94    const urlLang = urlParams.get('hl');
95    if (urlLang) {
96      lang = urlLang;
97    }
98  }
99
100  // Supported directly?
101  if (SUPPORTED_LANGUAGES.includes(lang)) return lang;
102
103  // Okay, let's try to be creative.
104  // Special case for China
105  if (lang.startsWith("zh")) {
106    return (lang === "zh-CN" || lang === "zh-SG" || lang === "zh-hans") ?
107      "zh-Hans" : "zh-Hant";
108  }
109
110  // Otherwise, try looking only at the part before the hyphen.
111  if (lang.includes('-')) {
112    lang = lang.split('-')[0];
113  }
114  if (SUPPORTED_LANGUAGES.includes(lang)) return lang;
115
116  lang = lang.toLowerCase();
117  return SUPPORTED_LANGUAGES.includes(lang) ? lang : "en";
118}
119
120async function asyncLoadStrings() {
121  const langCode = getLangCode();
122  console.log(`INTL: User language: ${navigator.language}, ` + 
123    `our supported langCode is: ${langCode}, loading...`);
124  try {
125    stringTable = (await import("/js-intl/strings-" + langCode + ".js")).STRINGS;
126    if (!stringTable) throw new Error(`Loaded module for ${langCode} but it had no STRINGS`);
127    console.log(`INTL: Successfully loaded string table for langCode ${langCode}, ${Object.keys(stringTable).length} strings.`);
128  } catch (e) {
129    console.error("INTL: Failed to load internationalization for langCode " + langCode);
130    console.error(e);
131    return;
132  }
133
134  // Populate translated text on static HTML elements.
135  let numEls = 0;
136  for (const el of document.querySelectorAll('[data-string-id]')) {
137    const stringId = el.getAttribute('data-string-id');
138    if (!stringId) continue;
139    el.innerHTML = getString(stringId);
140    ++numEls;
141  }
142  console.log(`INTL: auto-translated ${numEls} elements`);
143
144  // Add a class to the body to indicate what language we're translated to.
145  document.body.classList.add("intl-" + langCode);
146
147  intlLoadPromiseResolve();
148}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.