From 9d13bef3fba715f41555d2a945c87000cdf7365c Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Mon, 28 Sep 2026 13:17:03 +0000 Subject: [PATCH] Two labels resolved once and stayed in the boot language MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit upg100 measured both, and both were created by today's i18n fixes — mine and the batch's. A computed only re-runs when a REACTIVE dependency changes; tStatic reads a plain window global. So these resolved at first render and never moved again, while every label around them followed the switch. · big-picture.vue — heading depended on config.value.title, which does not change when the host changes language. It now reads hostLang, which is reactive and already drives setRuntimeLocale, and PASSES it to tStatic so the right catalogue is read. useI18n is not available here: this bundle installs no i18n, which is why the whole element reads through tStatic. · DashboardResearchFilters.vue — the live-search chip, same shape. research-dashboard DOES install i18n (index.js:83), so this takes vue-i18n's reactive locale ref, reads it to re-run and passes it for the lookup. Both land on check-frozen-labels' documented clean shape — an explicit locale argument — rather than hiding tStatic behind a helper that would satisfy the gate while leaving the label frozen. The heading also moved BELOW hostLang, so the dependency it now relies on is declared above it rather than four hundred lines later. Verified: check:sfc-parse PASS; check-frozen-labels (studio/landing58) reports exactly these two sites today, and the shape they move to is its own "pinned" self-test case. --- plugin-designs/big-picture/big-picture.vue | 13 ++++++++++++- .../dashboards/DashboardResearchFilters.vue | 9 ++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/plugin-designs/big-picture/big-picture.vue b/plugin-designs/big-picture/big-picture.vue index ef0c694a7..a5f68dfa7 100644 --- a/plugin-designs/big-picture/big-picture.vue +++ b/plugin-designs/big-picture/big-picture.vue @@ -70,7 +70,7 @@ const accent = computed(() => config.value.accent || '#8b5cf6') // `title` still wins — that is what the config key is for — but an absent one now resolves through // the catalogue rather than through a literal. The English wording is unchanged: this is the // shepherd's standing rule that translating EXISTING wording is not a product decision. -const heading = computed(() => config.value.title || tStatic('dashboard.bp_title') || 'Big Picture — Unified Research Dashboard') + // ★ THE LOCALE THE HOST ASKED FOR, AND THE ONE ELEMENT ON THE PAGE THAT CAN ACT ON IT. // MEASURED with check:host-locale-contract: this leaf drew 0 Arabic characters for // profile-config {"locale":"ar"} against 273 for the same page reached with ?lang=ar. Its siblings @@ -86,6 +86,17 @@ const hostLang = computed(() => { }) watchEffect(() => { if (hostLang.value) setRuntimeLocale(hostLang.value) }) +// ★ THE LOCALE IS PASSED, NOT ASSUMED, AND THAT IS WHAT KEEPS THIS LIVE. tStatic resolves through +// getRuntimeLocale() at CALL time, but a computed only calls again when a REACTIVE dependency changes — +// and config.value does not change when the host switches language. So this heading resolved once and +// stayed in the boot language while everything around it moved (upg100 measured it; check:frozen-labels +// catches the shape). hostLang IS reactive, so reading it both re-runs this computed and tells tStatic +// which catalogue to read. useI18n is not an option here: this bundle installs no i18n (index.js), which +// is why the whole element reads through tStatic in the first place. +const heading = computed(() => config.value.title + || tStatic('dashboard.bp_title', hostLang.value || undefined) + || 'Big Picture — Unified Research Dashboard') + // ★ RTL IS A DIRECTION, NOT A TRANSLATION — the half this element was still missing. // Its three sibling composites (dashboard-plus-grid, research-map-plus-grid) and the grid itself all // reflect `dir` onto their custom-element host; this one did not, so a host asking for Arabic got diff --git a/plugin-designs/research-dashboard/src/components/dashboards/DashboardResearchFilters.vue b/plugin-designs/research-dashboard/src/components/dashboards/DashboardResearchFilters.vue index dbb94b992..9a307e195 100644 --- a/plugin-designs/research-dashboard/src/components/dashboards/DashboardResearchFilters.vue +++ b/plugin-designs/research-dashboard/src/components/dashboards/DashboardResearchFilters.vue @@ -36,12 +36,14 @@ * mapped to this dashboard's facet keys). Nothing here is dashboard-specific beyond that map. */ import { inject, computed } from 'vue' +import { useI18n } from 'vue-i18n' import { tStatic } from '@map/i18n/index.js' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import SearchFilterHeader from '@map/components/search/SearchFilterHeader.vue' import { getPrayerLevel, LABELS as PRAYER_LABELS } from '@map/colors/prayer-progress.js' import { useTreeLegendLabels } from '../../composables/useTreeLegendLabels.js' +const { locale } = useI18n() const bus = inject('selectionBus', null) const dash = inject('dashboardData', null) const cfg = inject('dashboardConfig', {}) @@ -205,7 +207,12 @@ const activeChips = computed(() => { // in code would get both wrong even with a translated word. It was simply never wired, so ten // locales rendered an English label beside localized facet chips. tStatic takes no params, so // {term} is filled here rather than by the catalogue. - if (term) out.push({ name: '__search__', label: (tStatic('dashboard.search_live') || 'Search: {term}').replace('{term}', term) }) + // ★ locale.value is READ so this computed re-runs on a language switch, and PASSED so tStatic reads + // the catalogue the reader just chose. Without it the chip resolved once and stayed in the boot + // language beside chips that had moved — a frozen label, which check:frozen-labels now catches. + // research-dashboard installs i18n (index.js:83), so useI18n resolves here; big-picture does not, + // which is why its sibling fix pins the locale from hostLang instead. + if (term) out.push({ name: '__search__', label: (tStatic('dashboard.search_live', locale.value) || 'Search: {term}').replace('{term}', term) }) return out })