From d6df237202682fe117cf20f9c461d6dc313e6585 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 20 Jun 2026 14:23:22 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20Prevent=20redundant=20DOM?= =?UTF-8?q?=20updates=20on=20initial=20i18n=20load?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Cache DOM queries so they only run once - Add early return if active language matches requested language - Only update textContent if it has actually changed --- .jules/bolt.md | 3 +++ i18n.js | 66 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index e69de29..f74ef2f 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -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. diff --git a/i18n.js b/i18n.js index 75bc0f3..8d4266b 100644 --- a/i18n.js +++ b/i18n.js @@ -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)); });