(function () {
function setLocaleCookie(locale) {
document.cookie =
"site_locale=" +
encodeURIComponent(locale) +
"; Max-Age=31536000; Path=/; SameSite=Lax; Secure";
}
function readLocaleCookie() {
var parts = document.cookie ? document.cookie.split(";") : [];
for (var i = 0; i < parts.length; i++) {
var part = parts[i].trim();
if (part.indexOf("site_locale=") === 0) {
try {
return decodeURIComponent(part.slice("site_locale=".length));
} catch (e) {
return part.slice("site_locale=".length);
}
}
}
return "";
}
/** Map a BCP 47 tag to a published locale id present in `available`. */
function mapBrowserLang(tag, available) {
if (!tag) return "";
var raw = String(tag).trim().replace(/_/g, "-");
if (!raw) return "";
var lower = raw.toLowerCase();
var primary = lower.split("-")[0];
if (
lower === "zh-tw" ||
lower === "zh-hk" ||
lower === "zh-mo" ||
lower === "zh-hant" ||
lower.indexOf("zh-hant") === 0 ||
lower === "zh-trad"
) {
return available["zh-Hant"] ? "zh-Hant" : "";
}
if (primary === "zh") {
return available.zh ? "zh" : available["zh-Hant"] ? "zh-Hant" : "";
}
if (primary === "pt") {
return available["pt-BR"] ? "pt-BR" : available.pt ? "pt" : "";
}
// Exact / case-insensitive match against published locale ids
if (available[raw]) return raw;
var keys = Object.keys(available);
for (var i = 0; i < keys.length; i++) {
if (keys[i].toLowerCase() === lower) return keys[i];
}
for (var j = 0; j < keys.length; j++) {
if (keys[j].toLowerCase().split("-")[0] === primary) return keys[j];
}
return "";
}
function preferredLocaleIds(available, currentLocale) {
var ranked = [];
var seen = {};
function push(id) {
if (!id || !available[id] || seen[id]) return;
seen[id] = true;
ranked.push(id);
}
// Cookie first — explicit prior choice
push(readLocaleCookie());
// Then browser language preferences
var navLangs =
(typeof navigator !== "undefined" &&
(navigator.languages ||
(navigator.language ? [navigator.language] : []))) ||
[];
for (var i = 0; i < navLangs.length; i++) {
push(mapBrowserLang(navLangs[i], available));
}
// Current page locale stays easy to find near the top of suggestions
push(currentLocale);
return ranked;
}
function prioritizeMenuItems(menu, currentLocale) {
var items = Array.prototype.slice.call(
menu.querySelectorAll('[role="menuitem"]')
);
if (items.length < 2) return items;
var available = {};
var byId = {};
items.forEach(function (item) {
var id = item.getAttribute("data-locale-target");
if (!id) return;
available[id] = true;
byId[id] = item;
item.removeAttribute("data-suggested");
});
var preferred = preferredLocaleIds(available, currentLocale);
preferred.forEach(function (id) {
if (byId[id] && id !== currentLocale) {
byId[id].setAttribute("data-suggested", "true");
}
});
var ordered = [];
preferred.forEach(function (id) {
if (byId[id]) ordered.push(byId[id]);
});
items.forEach(function (item) {
var id = item.getAttribute("data-locale-target");
if (id && preferred.indexOf(id) === -1) ordered.push(item);
});
// Re-append
wrappers in the new order (grid follows DOM order)
ordered.forEach(function (item) {
var li = item.parentElement;
if (li && li.parentElement === menu) menu.appendChild(li);
});
return ordered;
}
function columnCount(menu) {
var style = window.getComputedStyle(menu);
var cols = style.gridTemplateColumns;
if (!cols || cols === "none") return 1;
// Resolved value looks like "120px 120px 120px"
return Math.max(1, cols.split(/\s+/).filter(Boolean).length);
}
function initSwitcher(root) {
if (!root) return;
var toggle = root.querySelector("[data-lang-toggle]");
var menu = root.querySelector("[data-lang-menu]");
if (!toggle || !menu) return;
var pageId = root.getAttribute("data-page-id") || "";
var fromLocale = root.getAttribute("data-locale") || "";
var items = prioritizeMenuItems(menu, fromLocale);
if (!items.length) {
items = Array.prototype.slice.call(menu.querySelectorAll("a[href]"));
}
var rtl =
(document.documentElement.getAttribute("dir") || "").toLowerCase() ===
"rtl";
function refreshItems() {
items = Array.prototype.slice.call(
menu.querySelectorAll('[role="menuitem"]')
);
if (!items.length) {
items = Array.prototype.slice.call(menu.querySelectorAll("a[href]"));
}
}
function isOpen() {
return root.classList.contains("is-open");
}
function close(restoreFocus) {
if (!isOpen()) return;
root.classList.remove("is-open");
toggle.setAttribute("aria-expanded", "false");
menu.setAttribute("hidden", "");
var active = document.activeElement;
if (restoreFocus !== false) {
toggle.focus();
} else if (active && menu.contains(active)) {
// Outside-click / Tab close: don't leave focus on a hidden menuitem
// (can make the page feel stuck until focus moves elsewhere).
if (typeof active.blur === "function") active.blur();
}
}
function open(focusTarget) {
root.classList.add("is-open");
toggle.setAttribute("aria-expanded", "true");
menu.removeAttribute("hidden");
refreshItems();
var target = focusTarget;
if (!target) {
target =
menu.querySelector('[role="menuitem"][aria-current="true"]') ||
items[0];
}
if (target) target.focus();
}
function focusItem(index) {
refreshItems();
if (!items.length) return;
var i = index % items.length;
if (i < 0) i += items.length;
items[i].focus();
}
function itemIndex(el) {
refreshItems();
return items.indexOf(el);
}
toggle.addEventListener("click", function (e) {
e.stopPropagation();
if (isOpen()) close(false);
else open();
});
toggle.addEventListener("keydown", function (e) {
if (e.key === "Escape") {
close();
return;
}
// Enter/Space: let the button click handler toggle (avoids double-fire).
if (e.key === "ArrowDown") {
e.preventDefault();
if (!isOpen()) open(items[0]);
else focusItem(0);
} else if (e.key === "ArrowUp") {
e.preventDefault();
if (!isOpen()) open(items[items.length - 1]);
else focusItem(items.length - 1);
} else if (e.key === "Home") {
e.preventDefault();
if (!isOpen()) open(items[0]);
else focusItem(0);
} else if (e.key === "End") {
e.preventDefault();
if (!isOpen()) open(items[items.length - 1]);
else focusItem(items.length - 1);
}
});
menu.addEventListener("keydown", function (e) {
var current = document.activeElement;
var idx = itemIndex(current);
var cols = columnCount(menu);
if (e.key === "Escape") {
e.preventDefault();
close();
return;
}
if (e.key === "ArrowDown") {
e.preventDefault();
focusItem(idx < 0 ? 0 : idx + cols);
} else if (e.key === "ArrowUp") {
e.preventDefault();
focusItem(idx < 0 ? items.length - 1 : idx - cols);
} else if (e.key === "ArrowRight") {
e.preventDefault();
focusItem(idx < 0 ? 0 : idx + (rtl ? -1 : 1));
} else if (e.key === "ArrowLeft") {
e.preventDefault();
focusItem(idx < 0 ? 0 : idx + (rtl ? 1 : -1));
} else if (e.key === "Home") {
e.preventDefault();
focusItem(0);
} else if (e.key === "End") {
e.preventDefault();
focusItem(items.length - 1);
} else if (e.key === "Tab") {
close(false);
}
});
items.forEach(function (item) {
item.addEventListener("click", function (e) {
var toLocale = item.getAttribute("data-locale-target");
var href = item.getAttribute("href");
if (!toLocale || !href) return;
setLocaleCookie(toLocale);
if (typeof gtag === "function") {
gtag("event", "language_switch", {
from_locale: fromLocale,
to_locale: toLocale,
page_id: pageId,
});
}
if (toLocale === fromLocale) {
e.preventDefault();
close();
}
});
});
document.addEventListener("click", function (e) {
if (!root.contains(e.target)) close(false);
});
}
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll("[data-lang-switcher]").forEach(initSwitcher);
});
})();