From 52b3e6d37843ccc2fadc52c55caa4e873147f7be Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Sat, 15 Aug 2026 12:26:01 +0530 Subject: [PATCH] fix(csr): preserve translations across navigation --- bun.lock | 2 +- packages/csr/package.json | 2 +- packages/csr/src/nav-runtime.ts | 21 +++++++++++++++++++++ packages/csr/test/nav.test.ts | 30 ++++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/bun.lock b/bun.lock index e2dbb42f..0ef1257f 100644 --- a/bun.lock +++ b/bun.lock @@ -317,7 +317,7 @@ }, "packages/csr": { "name": "@wrnexus/csr", - "version": "0.8.20", + "version": "0.8.21", "dependencies": { "@wrnexus/core": "workspace:*", }, diff --git a/packages/csr/package.json b/packages/csr/package.json index f1b36e46..0ff1d41a 100644 --- a/packages/csr/package.json +++ b/packages/csr/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/csr", - "version": "0.8.20", + "version": "0.8.21", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/csr/src/nav-runtime.ts b/packages/csr/src/nav-runtime.ts index 753f0685..dbcc912c 100644 --- a/packages/csr/src/nav-runtime.ts +++ b/packages/csr/src/nav-runtime.ts @@ -380,6 +380,27 @@ export const NAV_RUNTIME = String.raw` var current = window.__wrnI18n || {}; var translator = current.t; var setter = current.set; + function mergeCatalog(base, update) { + var output = {}; + Object.keys(base && typeof base === "object" ? base : {}).forEach(function (key) { + var value = base[key]; + output[key] = value && typeof value === "object" && !Array.isArray(value) + ? mergeCatalog(value, {}) + : value; + }); + Object.keys(update && typeof update === "object" ? update : {}).forEach(function (key) { + var left = output[key]; + var right = update[key]; + output[key] = left && right && typeof left === "object" && typeof right === "object" && !Array.isArray(left) && !Array.isArray(right) + ? mergeCatalog(left, right) + : right; + }); + return output; + } + if (current.lang && current.lang === incoming.lang) { + incoming.messages = mergeCatalog(current.messages, incoming.messages); + incoming.fallbackMessages = mergeCatalog(current.fallbackMessages, incoming.fallbackMessages); + } window.__wrnI18n = incoming; if (translator) window.__wrnI18n.t = translator; if (setter) window.__wrnI18n.set = setter; diff --git a/packages/csr/test/nav.test.ts b/packages/csr/test/nav.test.ts index 7d1bb54e..b733618a 100644 --- a/packages/csr/test/nav.test.ts +++ b/packages/csr/test/nav.test.ts @@ -161,6 +161,36 @@ test("synchronizes and rebinds i18n data during client navigation", async () => expect(boundRoot).toBe(win.document.getElementById("app")); }); +test("preserves same-language translations when an incoming navigation catalog is partial", async () => { + install(`
About
`); + win.__wrnI18n = { + lang: "en", + messages: { navigation: { home: "Home" }, footer: { contact: "Contact" } }, + fallbackMessages: {}, + }; + win.__wrnLang = { + bind: (root: ParentNode) => { + root.querySelectorAll("[data-t]").forEach((node) => { + const parts = String(node.getAttribute("data-t") || "").split("."); + let value: any = win.__wrnI18n.messages; + for (const part of parts) value = value?.[part]; + node.textContent = typeof value === "string" ? value : node.getAttribute("data-t"); + }); + }, + }; + nextHtml = + `

navigation.home

` + + `` + + ``; + + win.document.getElementById("lnk").click(); + await flush(); + + expect(win.__wrnI18n.messages.navigation.home).toBe("Home"); + expect(win.__wrnI18n.messages.footer.contact).toBe("Contact"); + expect(win.document.querySelector("[data-t]")?.textContent).toBe("Home"); +}); + test("unmounts and remounts package runtimes during client navigation", async () => { install( `
Old
Next
`,