Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-06-20 - Unnecessary initial DOM updates for default language
**Learning:** The simple static i18n implementation runs `node.textContent = dict[node.dataset.i18n]` for every translatable node on the initial script load, even when the HTML is already written in the target language (Korean). This creates unnecessary layout/paint operations and blocking time on the main thread for elements that don't need text changes.
**Action:** Always check if the current value matches the desired value before updating the DOM (`node.textContent !== newText`), and add early exits when setting state to the same value to avoid redundant DOM traversal and writes.
66 changes: 55 additions & 11 deletions i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,26 +292,70 @@ function preferredLanguage() {
return messages[query] ? query : messages[saved] ? saved : browser;
}

// ⚡ Bolt: Cache DOM queries and current state to prevent redundant lookups and layout thrashing
let i18nNodes = null;
let langButtons = null;
let metaDesc = null;
let ogDesc = null;
let footerLogo = null;
let currentLang = null;

function setLanguage(lang) {
if (currentLang === lang) return; // Skip if already in the requested language

const dict = messages[lang] || messages.ko;
document.documentElement.lang = lang;
document.title = dict.metaTitle;
document.querySelector('meta[name="description"]')?.setAttribute("content", dict.metaDescription);
document.querySelector('meta[property="og:description"]')?.setAttribute("content", dict.metaDescription);
const footerLogo = document.querySelector("#footer-logo");

if (!i18nNodes) {
i18nNodes = document.querySelectorAll("[data-i18n]");
langButtons = document.querySelectorAll("[data-lang]");
metaDesc = document.querySelector('meta[name="description"]');
ogDesc = document.querySelector('meta[property="og:description"]');
footerLogo = document.querySelector("#footer-logo");
}

if (document.documentElement.lang !== lang) {
document.documentElement.lang = lang;
}
if (document.title !== dict.metaTitle) {
document.title = dict.metaTitle;
}

if (metaDesc && metaDesc.getAttribute("content") !== dict.metaDescription) {
metaDesc.setAttribute("content", dict.metaDescription);
}
if (ogDesc && ogDesc.getAttribute("content") !== dict.metaDescription) {
ogDesc.setAttribute("content", dict.metaDescription);
}

if (footerLogo) {
footerLogo.src = dict.logoSrc;
footerLogo.alt = dict.logoAlt;
if (footerLogo.getAttribute("src") !== dict.logoSrc) {
footerLogo.setAttribute("src", dict.logoSrc);
}
if (footerLogo.getAttribute("alt") !== dict.logoAlt) {
footerLogo.setAttribute("alt", dict.logoAlt);
}
}
document.querySelectorAll("[data-i18n]").forEach((node) => {
node.textContent = dict[node.dataset.i18n] || node.textContent;

// Only update textContent if it actually changed to avoid layout recalculations
i18nNodes.forEach((node) => {
const newText = dict[node.dataset.i18n];
if (newText && node.textContent !== newText) {
node.textContent = newText;
}
});
document.querySelectorAll("[data-lang]").forEach((button) => {
button.setAttribute("aria-pressed", String(button.dataset.lang === lang));

langButtons.forEach((button) => {
const pressed = String(button.dataset.lang === lang);
if (button.getAttribute("aria-pressed") !== pressed) {
button.setAttribute("aria-pressed", pressed);
}
});

localStorage.setItem("cwl-language", lang);
currentLang = lang;
}

// Event listeners can just use the initial querySelectorAll
document.querySelectorAll("[data-lang]").forEach((button) => {
button.addEventListener("click", () => setLanguage(button.dataset.lang));
});
Expand Down
Loading