PageSourceSearch

https://xagent.id/assets/i18n.js?v=audit-20260818

js xagent.id collected 2026-10-05 06:32:37 UTC 6,313 bytes, 163 lines download raw bytes

1(function initializeXAgentI18n(root) {
2  "use strict";
3
4  const catalog = root && root.XAgentI18nCatalog;
5  if (!root || !root.document || !catalog) return;
6  const doc = root.document;
7  const supported = new Set(catalog.locales);
8  const textSources = new WeakMap();
9  const attributeSources = new WeakMap();
10  let currentLocale = "en";
11
12  function normalizeLocale(raw) {
13    const value = String(raw || "").trim().toLowerCase().replaceAll("_", "-");
14    if (/^zh-(tw|hk|mo|hant)/.test(value) || value === "zh-hant") return "zh-Hant";
15    if (value.startsWith("zh")) return "zh-Hans";
16    return catalog.locales.find((locale) => locale !== "zh-Hant" && locale !== "zh-Hans" && value.startsWith(locale.toLowerCase())) || "en";
17  }
18
19  function preferredLocale() {
20    const requested = new URLSearchParams(root.location.search).get("lang");
21    let stored = "";
22    try { stored = root.localStorage.getItem("agent-id-lang") || ""; } catch (_error) {}
23    const candidates = [requested, stored, ...(root.navigator.languages || [root.navigator.language])];
24    for (const candidate of candidates) {
25      if (!candidate) continue;
26      const locale = normalizeLocale(candidate);
27      if (supported.has(locale)) return locale;
28    }
29    return "en";
30  }
31
32  function messageFor(source, locale = currentLocale) {
33    const canonical = catalog.aliases[source] || source;
34    return catalog.messages[canonical]?.[locale] || source;
35  }
36
37  function shouldSkip(node) {
38    const parent = node.parentElement;
39    if (!parent) return true;
40    if (parent.closest("script,style,code,pre,textarea,[data-i18n-skip]")) return true;
41    return parent.classList.contains("lang-en") || parent.classList.contains("lang-zh");
42  }
43
44  function translateTextNode(node) {
45    if (shouldSkip(node)) return;
46    const visible = node.nodeValue || "";
47    const trimmed = visible.trim();
48    if (!trimmed) return;
49    if (!textSources.has(node)) textSources.set(node, trimmed);
50    const source = textSources.get(node);
51    const translated = messageFor(source);
52    if (translated === source && !catalog.messages[catalog.aliases[source] || source]) return;
53    const leading = visible.match(/^\s*/)?.[0] || "";
54    const trailing = visible.match(/\s*$/)?.[0] || "";
55    node.nodeValue = `${leading}${translated}${trailing}`;
56  }
57
58  function translateAttributes(element) {
59    const names = ["aria-label", "title", "placeholder"];
60    let sources = attributeSources.get(element);
61    if (!sources) {
62      sources = {};
63      names.forEach((name) => { if (element.hasAttribute(name)) sources[name] = element.getAttribute(name); });
64      attributeSources.set(element, sources);
65    }
66    Object.entries(sources).forEach(([name, source]) => element.setAttribute(name, messageFor(source)));
67  }
68
69  function translateTree(rootNode = doc.body) {
70    if (!rootNode) return;
71    if (rootNode.nodeType === Node.TEXT_NODE) {
72      translateTextNode(rootNode);
73      return;
74    }
75    if (rootNode.nodeType !== Node.ELEMENT_NODE) return;
76    translateAttributes(rootNode);
77    const walker = doc.createTreeWalker(rootNode, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
78    let node = walker.nextNode();
79    while (node) {
80      if (node.nodeType === Node.TEXT_NODE) translateTextNode(node);
81      else translateAttributes(node);
82      node = walker.nextNode();
83    }
84  }
85
86  function ensureSelector() {
87    let select = doc.querySelector("[data-language-select]");
88    if (select) return select;
89    const control = doc.createElement("label");
90    control.className = "site-language-control";
91    control.setAttribute("aria-label", "Language");
92    control.innerHTML = '<span aria-hidden="true">Aa</span><select data-language-select aria-label="Language"></select>';
93    select = control.querySelector("select");
94    const header = doc.querySelector(".site-header .nav, .topbar .nav, .gtm-nav");
95    if (header) {
96      const mobile = header.querySelector?.(".mobile-nav");
97      header.insertBefore(control, mobile || null);
98    } else {
99      control.classList.add("is-floating");
100      doc.body.prepend(control);
101    }
102    return select;
103  }
104
105  function fillSelector(select) {
106    if (!select || select.options.length === catalog.locales.length) return;
107    select.replaceChildren(...catalog.locales.map((locale) => {
108      const option = doc.createElement("option");
109      option.value = locale;
110      option.textContent = catalog.labels[locale];
111      return option;
112    }));
113  }
114
115  function setLocale(raw, persist = true) {
116    const locale = supported.has(raw) ? raw : normalizeLocale(raw);
117    currentLocale = locale;
118    doc.documentElement.lang = locale;
119    doc.body.dataset.lang = locale;
120    const select = ensureSelector();
121    fillSelector(select);
122    select.value = locale;
123    translateTree(doc.body);
124    const originalTitle = doc.documentElement.dataset.i18nTitle || doc.title;
125    doc.documentElement.dataset.i18nTitle = originalTitle;
126    doc.title = messageFor(originalTitle, locale);
127    const description = doc.querySelector('meta[name="description"]');
128    if (description) {
129      const original = description.dataset.i18nSource || description.content;
130      description.dataset.i18nSource = original;
131      description.content = messageFor(original, locale);
132    }
133    if (persist) {
134      try { root.localStorage.setItem("agent-id-lang", locale); } catch (_error) {}
135      try {
136        const url = new URL(root.location.href);
137        url.searchParams.set("lang", locale);
138        root.history.replaceState(root.history.state, "", url);
139      } catch (_error) {}
140    }
141    root.dispatchEvent(new CustomEvent("xagent:languagechange", { detail: { locale } }));
142  }
143
144  const select = ensureSelector();
145  fillSelector(select);
146  select.addEventListener("change", (event) => setLocale(event.target.value));
147  setLocale(preferredLocale(), false);
148
149  const observer = new MutationObserver((records) => {
150    for (const record of records) {
151      for (const node of record.addedNodes) translateTree(node);
152    }
153  });
154  observer.observe(doc.body, { childList: true, subtree: true });
155
156  root.XAgentI18n = {
157    locales: [...catalog.locales],
158    getLocale: () => currentLocale,
159    setLocale,
160    t: (source) => messageFor(source),
161    translate: translateTree,
162  };
163})(typeof window !== "undefined" ? window : globalThis);

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.