A live language switch remounts the gallery's tab, instead of re-setting its attribute

The gallery re-drove the visible tab by re-setting its profile-config, which works only for a map
that re-reads its whole config live. Two production maps do not: Big Picture resolves its tStatic
strings once (2 of 352 changed on en to ar, measured) and Simple Research copies its config into its
panes once (0 of 43, 0 of 105). A fresh element reads the new locale at mount, so the visible tab is
now rebuilt in the new language whatever the map does inside, the way a provider change rebuilds.
View state is lost on a language switch; that is accepted.

Hidden tabs that are still mounted (a light tab stays mounted when hidden, and select() never
remounts a mounted tab) are reset too, so their next open mounts in the new language. The old comment
said they picked it up lazily; for light tabs they did not. Tabs are reset in place, keeping the slot
ref, because a light pane renders once and releaseTab's deleted ref would never come back.
This commit is contained in:
Doxa Maps 2026-09-29 18:50:13 +00:00
parent ec2146958e
commit f054b487cf

View file

@ -1055,21 +1055,29 @@ export default defineComponent({
},
watch: {
activeIndex() { this.helpOpen = false },
// When the parent page changes the inherited `language` prop at runtime, re-drive the CURRENTLY
// visible tab into the new language by re-setting its element's profile-config.locale (which is
// languages.js's contract). Other tabs pick it up lazily on their next open, since mountElement
// reads effectiveLocale fresh. ONE path now — every tab is a real element, so the old
// iframe-src-repoint branch is gone along with the iframes.
effectiveLocale(loc) {
const t = this.tabs[this.activeIndex]
// ★ A LANGUAGE SWITCH REMOUNTS, IT DOES NOT RE-SET THE ATTRIBUTE. Re-setting the visible tab's
// profile-config was the old path, and it relied on every nested map re-reading its whole config
// live. Two production maps do not: Big Picture's tStatic strings are resolved once (2/352 changed,
// measured), and Simple Research's composite copies its config into its panes once (0/43, 0/105).
// A fresh element reads effectiveLocale at mount, so the visible tab is rebuilt in the new language
// whatever each map does internally, the same way a provider change rebuilds. View state (filters,
// zoom) is lost on a language switch, which is acceptable.
// Hidden tabs that are still MOUNTED (a non-heavy tab stays mounted when hidden, and select() does
// not remount a mounted tab) are released, so their next open mounts fresh in the new language.
effectiveLocale() {
// Reset IN PLACE, not with releaseTab: releaseTab deletes the slot ref, and a light (v-show) pane
// is rendered once, so its slot ref would never be set again and the next mount would find none.
// Emptying the slot runs the old element's disconnected lifecycle, which frees its map.
const reset = (i, t) => {
try { const slot = this._slots && this._slots[i]; if (slot) slot.innerHTML = '' } catch { /* reset anyway */ }
t.mounted = false; t.state = 'idle'; t.error = ''
}
this.tabs.forEach((t, i) => { if (t.mounted && i !== this.activeIndex) reset(i, t) })
const i = this.activeIndex
const t = this.tabs[i]
if (!t || !t.mounted) return
try {
const slot = this._slots && this._slots[this.activeIndex]
const el = slot && slot.firstElementChild
// Rebuild the SAME config the mount used (childConfig), so a language switch cannot silently
// drop the profile/instance/token and re-render a different view in the new language.
if (el && el.setAttribute) el.setAttribute('profile-config', JSON.stringify(this.childConfig(t)))
} catch { /* non-fatal: the tab keeps its previous language */ }
reset(i, t)
this.mountElement(i)
},
// A host re-pointing the `tab` attribute after mount (e.g. from its own nav) switches the gallery.
tab(id) { if (id) this.onSelect(id) }