208 lines · 6.7 KB · JavaScript
RawDownload
import { localeCatalog, localeMessages } from "./site-locales.mjs"; const root = document.documentElement;const toggle = document.querySelector(".theme-toggle");const languageSelect = document.querySelector(".language-select");const metaTheme = document.querySelector('meta[name="theme-color"]');const metaDescription = document.querySelector('meta[name="description"]');const metaOgDescription = document.querySelector('meta[property="og:description"]');const media = window.matchMedia("(prefers-color-scheme: dark)");const supportedLocales = new Set(localeCatalog.map((locale) => locale.code));const rtlLocales = new Set(localeCatalog.filter((locale) => locale.direction === "rtl").map((locale) => locale.code));let activeMessages = localeMessages.en; function normalizeLocale(value) {  if (!value) return null;   const lower = value.toLowerCase();  const aliases = {    "zh-cn": "zh-CN",    "zh-hans": "zh-CN",    "zh-hant": "zh-TW",    "zh-hk": "zh-TW",    "zh-tw": "zh-TW",    ja: "ja-JP",    pt: "pt-BR",    "pt-br": "pt-BR",  };  if (aliases[lower]) return aliases[lower];   return localeCatalog.find((locale) =>    locale.code.toLowerCase() === lower || lower.startsWith(`${locale.code.toLowerCase()}-`))?.code ?? null;} function selectedLocale() {  const fromDocument = normalizeLocale(root.dataset.locale);  if (fromDocument) return fromDocument;   try {    const queryLocale = normalizeLocale(new URLSearchParams(location.search).get("lang"));    if (queryLocale) return queryLocale;    const storedLocale = normalizeLocale(localStorage.getItem("codexbar-language"));    if (storedLocale) return storedLocale;  } catch (_) {}   for (const language of navigator.languages || [navigator.language]) {    const locale = normalizeLocale(language);    if (locale) return locale;  }  return "en";} function message(key) {  return activeMessages[key] || localeMessages.en[key] || key;} function applyLocale(locale) {  const resolved = supportedLocales.has(locale) ? locale : "en";  activeMessages = { ...localeMessages.en, ...localeMessages[resolved] };   root.lang = resolved;  root.dir = rtlLocales.has(resolved) ? "rtl" : "ltr";  root.dataset.locale = resolved;  if (languageSelect) languageSelect.value = resolved;   document.title = message("meta.title");  metaDescription?.setAttribute("content", message("meta.description"));  metaOgDescription?.setAttribute("content", message("meta.ogDescription"));   document.querySelectorAll("[data-i18n]").forEach((element) => {    element.textContent = message(element.dataset.i18n);  });  document.querySelectorAll("[data-i18n-rich]").forEach((element) => {    renderRichMessage(element, message(element.dataset.i18nRich));  });  applyAttributeMessages("data-i18n-aria-label", "aria-label");  applyAttributeMessages("data-i18n-title", "title");  applyAttributeMessages("data-i18n-alt", "alt");  updateThemeUI();} function applyAttributeMessages(dataAttribute, targetAttribute) {  document.querySelectorAll(`[${dataAttribute}]`).forEach((element) => {    element.setAttribute(targetAttribute, message(element.getAttribute(dataAttribute)));  });} function renderRichMessage(element, value) {  const fragment = document.createDocumentFragment();  const tokenPattern = /\{([a-zA-Z][a-zA-Z0-9]*)\}/g;  let cursor = 0;  let match;   while ((match = tokenPattern.exec(value)) !== null) {    fragment.append(document.createTextNode(value.slice(cursor, match.index)));    fragment.append(richToken(match[1]));    cursor = match.index + match[0].length;  }  fragment.append(document.createTextNode(value.slice(cursor)));  element.replaceChildren(fragment);} function richToken(name) {  const codeTokens = {    cask: "brew install --cask steipete/tap/codexbar",    codexbar: "codexbar",    linuxCommand: "brew install steipete/tap/codexbar",    upgrade: "brew upgrade",  };  if (codeTokens[name]) {    const code = document.createElement("code");    code.textContent = codeTokens[name];    return code;  }  if (name === "releases") {    const link = document.createElement("a");    link.href = "https://github.com/steipete/CodexBar/releases/latest";    link.textContent = "GitHub Releases";    return link;  }  if (name === "issue") {    const link = document.createElement("a");    link.href = "https://github.com/steipete/CodexBar/issues/12";    link.textContent = "issue #12";    return link;  }   return document.createTextNode(`{${name}}`);} function queryTheme() {  try {    const value = new URLSearchParams(location.search).get("theme");    return value === "light" || value === "dark" ? value : null;  } catch (_) {    return null;  }} function storedTheme() {  try {    return localStorage.getItem("codexbar-theme");  } catch (_) {    return null;  }} function effectiveTheme() {  return root.dataset.theme || (media.matches ? "dark" : "light");} function updateThemeImages(theme) {  document.querySelectorAll("img[data-dark-src]").forEach((image) => {    if (!image.dataset.lightSrc) image.dataset.lightSrc = image.getAttribute("src");    image.setAttribute("src", theme === "dark" ? image.dataset.darkSrc : image.dataset.lightSrc);  });} function updateThemeUI() {  const theme = effectiveTheme();  updateThemeImages(theme);  if (toggle) {    const label = message(theme === "dark" ? "theme.toLight" : "theme.toDark");    toggle.setAttribute("aria-pressed", theme === "dark" ? "true" : "false");    toggle.setAttribute("aria-label", label);    toggle.title = label;  }  metaTheme?.setAttribute("content", theme === "dark" ? "#0a0a0c" : "#fbfbfc");} toggle?.addEventListener("click", () => {  const next = effectiveTheme() === "dark" ? "light" : "dark";  root.dataset.theme = next;  try {    localStorage.setItem("codexbar-theme", next);  } catch (_) {}  updateThemeUI();}); media.addEventListener?.("change", () => {  if (!queryTheme() && !storedTheme()) updateThemeUI();}); languageSelect?.addEventListener("change", () => {  const locale = normalizeLocale(languageSelect.value) || "en";  try {    localStorage.setItem("codexbar-language", locale);    const url = new URL(location.href);    url.searchParams.set("lang", locale);    history.replaceState(null, "", url);  } catch (_) {}  applyLocale(locale);}); const copyButton = document.querySelector(".brew-copy");copyButton?.addEventListener("click", () => {  const text = document.getElementById("brew-cmd")?.textContent;  if (!text || !navigator.clipboard) return;  navigator.clipboard.writeText(text).then(() => {    copyButton.classList.add("copied");    copyButton.setAttribute("aria-label", message("clipboard.copied"));    setTimeout(() => {      copyButton.classList.remove("copied");      copyButton.setAttribute("aria-label", message("clipboard.copy"));    }, 1500);  });}); applyLocale(selectedLocale());