Re-render the UPG-100 legend and wall names on a runtime language switch
ProfileLoader switches the i18n locale mid-session, and two things the i18n
batch localized were resolved once and then kept:
- the legend tree, built through legendOpts() and stored, so every region
and zone name stayed in the boot language (as did the flat zone list and
the search feed built from its labels);
- poster-wall's card names, a computed that read only getRuntimeLocale(),
a plain getter, so it never re-ran.
check:frozen-labels cannot see either: t() runs inside a function, and the
computed has no t() in it at all.
One buildLegend() now serves all three build sites and remembers the last
records; a watch on the locale rebuilds the tree, relabels the flat zone
list in place (colours and selectors unchanged) and rebuilds the search
feed. The wall's computed reads the i18n locale ref, so it re-runs.
The first-status watch (ac5a14ffa) now watches packetLang() rather than packetLocale:
the host may install i18n with a plain-string locale, and watch() on a non-ref never fires.
This commit is contained in:
parent
538e2fb782
commit
2067233553
2 changed files with 32 additions and 6 deletions
|
|
@ -39,7 +39,7 @@
|
|||
* IDEA-POSTER-EXPORT-404-ON-DROPPED-FOLDER-TRADEOFF.
|
||||
*/
|
||||
import { computed, inject, ref } from 'vue'
|
||||
import { useOptionalT } from '@map/composables/useOptionalT.js'
|
||||
import { useOptionalT, useOptionalI18n } from '@map/composables/useOptionalT.js'
|
||||
// The page's people-group LIST (library/lists/): every people-group fetch below keeps only its groups.
|
||||
import { resolvePeopleGroupList } from '@map/lists/peopleGroupList.js'
|
||||
import PosterWall from '@map/components/poster/PosterWall.vue'
|
||||
|
|
@ -151,9 +151,13 @@ const regionFor = (id) => {
|
|||
// buildLegendNodes collapses the two deaf_lists into one unified row. This spread still yields 27 because
|
||||
// it reads the three geographic buckets and the deaf row is synthesised — so the number is right for what
|
||||
// this list IS, and the old comment was describing the wrong thing.
|
||||
const { locale: wallLocale } = useOptionalI18n('')
|
||||
const items = computed(() => {
|
||||
const z = zonesForList(resolvePeopleGroupList(profileConfig))
|
||||
const locale = getRuntimeLocale() || detectLocale()
|
||||
// The i18n locale REF, read here so this computed re-runs on a runtime language switch: getRuntimeLocale()
|
||||
// is a plain getter, and a computed that reads only it is resolved once and never again (a frozen label).
|
||||
const l = wallLocale && (wallLocale.value ?? wallLocale)
|
||||
const locale = (typeof l === 'string' && l) || getRuntimeLocale() || detectLocale()
|
||||
return [
|
||||
...(z.lists || []),
|
||||
...(z.asia_lists || []),
|
||||
|
|
|
|||
|
|
@ -417,7 +417,9 @@ const { locale: packetLocale, t } = useI18n()
|
|||
// The first status a reader sees. Set in a watcher, not once at setup, so a language switch while the
|
||||
// map is still loading re-resolves it; the first real status replaces it and the watcher then leaves it.
|
||||
let loadingText = ''
|
||||
watch(packetLocale, () => {
|
||||
// Watched through packetLang(), not packetLocale itself: the host may install i18n with a plain-string
|
||||
// locale (see packetLang), and watch() on a non-ref is an invalid source that never fires.
|
||||
watch(() => packetLang(), () => {
|
||||
if (status.value === '' || status.value === loadingText) status.value = loadingText = t('legend.loadingData')
|
||||
}, { immediate: true })
|
||||
|
||||
|
|
@ -438,6 +440,24 @@ function legendOpts(records) {
|
|||
regionTitle: (english) => (REGION_KEY[english] ? t(`poster.${REGION_KEY[english]}`) : english),
|
||||
}
|
||||
}
|
||||
// ★ AND RE-BUILT WHEN THE LANGUAGE CHANGES. The tree above is built once and STORED, so a runtime switch
|
||||
// (ProfileLoader drives the i18n locale mid-session) left every region and zone name in the boot language
|
||||
// while the chrome around it changed — a frozen label that check:frozen-labels cannot see, because t()
|
||||
// runs inside a function. One builder for all three build sites remembers the last records, and a watch
|
||||
// on the locale rebuilds the tree, relabels the flat zone list IN PLACE (colours and selectors stay), and
|
||||
// refreshes the search feed built from those labels.
|
||||
let legendRecords = null
|
||||
function buildLegend(records) {
|
||||
if (records !== undefined) legendRecords = records
|
||||
return buildLegendNodes(listZones(), legendOpts(legendRecords))
|
||||
}
|
||||
watch(() => packetLang(), () => {
|
||||
if (!legendNodes.value.length) return
|
||||
legendNodes.value = buildLegend()
|
||||
const fresh = new Map(leafZones(legendNodes.value).map((l) => [l.id, l.label]))
|
||||
for (const z of leafZoneList) if (fresh.has(z.id)) z.label = fresh.get(z.id)
|
||||
if (searchRecords) buildSearch(searchRecords)
|
||||
})
|
||||
// ⚠ IT WAS A HARDCODED ENGLISH LITERAL, and SemanticTreeLegend passes `title` through RAW —
|
||||
// the caller owns localizing it. MEASURED 2026-09-25 on the live Chinese render: the legend
|
||||
// header read "My UPG 100 List — Legend" in full English inside an otherwise localized panel,
|
||||
|
|
@ -952,7 +972,9 @@ function clearSelection() {
|
|||
// useGeoSearch consumes; zone rows from the legend leafZones; territory rows
|
||||
// from admin-counts (ADM1) + ISO3 names (ADM0 countries). countryNameMap (built
|
||||
// from the live records) gives the exact country label the legend shows.
|
||||
let searchRecords = null // the records the feed was last built from (see the locale watch)
|
||||
function buildSearch(records) {
|
||||
searchRecords = records
|
||||
// (i) Feed the SHARED GeocoderComponent: unify PG + zone + territory into ONE
|
||||
// record list and cache it under our source so useGeoSearch (inside the
|
||||
// component) indexes it. Zones/territories ride as synthetic records with a
|
||||
|
|
@ -2162,7 +2184,7 @@ function bootstrapLegend() {
|
|||
// the two would disagree with nothing reporting it. Found by grepping for `clusters.` rather than by
|
||||
// trusting the convention comment.
|
||||
const z = listZones()
|
||||
const nodes = buildLegendNodes(z, legendOpts(null))
|
||||
const nodes = buildLegend(null)
|
||||
legendNodes.value = nodes
|
||||
leafZoneList = leafZones(nodes)
|
||||
const byId = {}
|
||||
|
|
@ -2193,7 +2215,7 @@ async function enrichLegendFromRecords() {
|
|||
if (!resp.ok) throw new Error(`list ${resp.status}`)
|
||||
const records = normalizeRecords(await applyPeopleGroupList(extractRecords(await resp.json()), profileConfig))
|
||||
countryNameMap = buildCountryNameMap(records)
|
||||
legendNodes.value = buildLegendNodes(listZones(), legendOpts(records))
|
||||
legendNodes.value = buildLegend(records)
|
||||
} catch (_) { /* counts-only legend is the honest fallback; keep it rendered */ }
|
||||
}
|
||||
|
||||
|
|
@ -2556,7 +2578,7 @@ onMounted(async () => {
|
|||
|
||||
// Re-shape the legend tree WITH records so every node carries `pop` (slice
|
||||
// 04u §1b). Selectors/colors/counts are unchanged; only `pop` is added.
|
||||
legendNodes.value = buildLegendNodes(listZones(), legendOpts(records))
|
||||
legendNodes.value = buildLegend(records)
|
||||
|
||||
|
||||
// ── pins layer (kept from 04b, now stamped with zoneKey for isolate dim) ──
|
||||
|
|
|
|||
Loading…
Reference in a new issue