The research dashboard asked the API for English, then translated half of it
useDashboardData read `lang` straight out of sources.json, where it is frozen at "en", and nothing ever overrode it. So every row the dashboard fetched came back English regardless of the reader's locale. The result was not a uniformly English page, which would at least be honest. It was worse. DashboardChartsRail localizes its country labels through countryName(), so at ?lang=ar the top-countries chart read الصين while the table row directly beneath it read "People's Republic of China" — and a page that is half translated looks like a bug in the half that works. Three surfaces rendered the raw English label: the data table's country column (dashboard.config.js:299), the detail drawer (DashboardDetailDrawer.vue:171) and the search-result subtitle (DashboardTopBar.vue:327). Fixing it at the SOURCE rather than at the three render sites is what makes them agree: ask the API in the reader's language and the labels arrive correct, with no countryName() call and no third spelling of a country's name to reconcile. DataSourceManager.js:261 has done exactly this for its own callers all along — `if (this.locale) extraParams.lang = this.locale` — and doxa-research-map, doxa-simple-map and country-posters all pass a locale in. This composable simply never got the same treatment. BOTH LOCALE CHANNELS, because it genuinely arrives by either: `lang` is provided by ProfileLoader for an element configured through profile-config, and window[PAGE_LOCALE_KEY] is what a generated leaf sets from ?lang= — the URL a person actually shares. Reading only one left the regions dashboard's data labels English on the shared-link path while its chrome translated, which is the measurement this copies rather than rediscovers. Resolved at REQUEST time, not captured at setup, so a locale switch cannot pin the dashboard to whichever language happened to load first. AND A LOCALE CHANGE NOW REFETCHES. load() ran only onMounted, so switching language mid-session would have re-rendered chrome over data still in the old language — the same defect one layer up. Same shape as region-dashboards.vue:680, guarded on a real change so the mount fetch is not doubled. Caught while writing it: my first version of that watch called inject() inside the callback, where it does not work — it warns on every tick and returns null. The ref is captured in setup and only read later, which is what this file already does for profileConfig, for the same reason. Proved headless: locale resolution across all four channels (leaf ?lang=, injected profile-config, neither, and injected-beats-runtime), then that the chart, the table column, the drawer and the search subtitle produce the SAME string for CHN, LAO and COD at ar — the disagreement that started this. The API's ?lang= behaviour itself is not my assumption: it was measured live at ar and zh on region-dashboards (its comment at :366).
This commit is contained in:
parent
34010ce6d8
commit
b1dbd9bc87
2 changed files with 44 additions and 3 deletions
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
import { ref, shallowRef, inject } from 'vue'
|
||||
import { getApiBaseUrl } from '@map/api/apiBaseUrl.js'
|
||||
import { getRuntimeLocale } from '@map/i18n/index.js'
|
||||
import sourcesConfig from '@map/api/sources.json'
|
||||
import { applyPeopleGroupList, resolvePeopleGroupList, LIST_DEFAULT } from '@map/lists/peopleGroupList.js'
|
||||
|
||||
|
|
@ -38,6 +39,25 @@ export function useDashboardData(dataOpts = {}) {
|
|||
let profileConfig = null
|
||||
try { profileConfig = inject('profileConfig', null) } catch (_) { /* called outside setup */ }
|
||||
|
||||
// ★ THE READER'S LOCALE, READ IN SETUP WHERE inject WORKS (the same reason profileConfig is).
|
||||
// This dashboard asked the API for lang=en on every request, because sources.json freezes that
|
||||
// value and nothing here ever overrode it. The effect was not a uniformly English page — it was
|
||||
// WORSE: DashboardChartsRail localizes its country labels through countryName(), so at ?lang=ar
|
||||
// the top-countries chart read الصين while the table row beneath it read "People's Republic of
|
||||
// China". A page that is half-translated looks like a translation bug in the half that works.
|
||||
//
|
||||
// DataSourceManager.js:261 has done exactly this for its own callers all along —
|
||||
// `if (this.locale) extraParams.lang = this.locale` — and doxa-research-map, doxa-simple-map and
|
||||
// country-posters all pass a locale in. This composable simply never got the same treatment.
|
||||
//
|
||||
// BOTH CHANNELS, because the locale genuinely arrives by either: `lang` is provided by
|
||||
// ProfileLoader for an element configured through profile-config, and window[PAGE_LOCALE_KEY] is
|
||||
// what a generated leaf sets from ?lang= — which is the URL a person actually shares. Measured on
|
||||
// the regions dashboard, where reading only one of the two left every data label English on the
|
||||
// shared-link path while the chrome translated.
|
||||
let injectedLang = null
|
||||
try { injectedLang = inject('lang', null) } catch (_) { /* called outside setup */ }
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
|
@ -46,7 +66,11 @@ export function useDashboardData(dataOpts = {}) {
|
|||
const base = getApiBaseUrl()
|
||||
const bulk = src.endpoints?.bulk || '/api/people-groups/list'
|
||||
const fields = (src.defaultFields || []).join(',')
|
||||
const lang = src.queryParams?.lang || 'en'
|
||||
// The runtime locale WINS over the source's frozen value; 'en' remains the floor. Resolved at
|
||||
// REQUEST time, not at setup: a locale switch refetches through this same function, and a
|
||||
// value captured once would pin the dashboard to whichever locale happened to load first.
|
||||
const runtime = (injectedLang && injectedLang.value) || getRuntimeLocale() || ''
|
||||
const lang = runtime || src.queryParams?.lang || 'en'
|
||||
|
||||
// ── 1) bulk list (shared config: endpoint + defaultFields + lang + cache-bust) ──
|
||||
const listUrl = `${base}${bulk}?${fields ? `fields=${fields}&` : ''}lang=${lang}&_=${Date.now()}`
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import { useTreeLegendLabels } from './useTreeLegendLabels.js'
|
|||
import { useTreeLegendColors } from './useTreeLegendColors.js'
|
||||
import { DataSourceManager } from '@map/api/DataSourceManager.js'
|
||||
import sourcesConfig from '@map/api/sources.json'
|
||||
import { tStatic } from '@map/i18n/index.js'
|
||||
import { tStatic, getRuntimeLocale } from '@map/i18n/index.js'
|
||||
|
||||
// tiny lodash-free mapValues: { k: v } -> { k: fn(v, k) }
|
||||
const mapValues = (obj, fn) =>
|
||||
|
|
@ -265,7 +265,24 @@ export function useDashboardShell(config) {
|
|||
provide('dataStore', dataStore)
|
||||
provide('searchSourceId', searchSourceId)
|
||||
|
||||
onMounted(() => data.load())
|
||||
// ★ A LOCALE CHANGE MUST REFETCH, not just re-render — the country, religion and region labels
|
||||
// come from the API's ?lang=, so without this the chrome switches language and every DATA label
|
||||
// keeps the language of first paint. Same rule and same shape as region-dashboards.vue:680,
|
||||
// which learned it the same way. Guarded on a real change so the mount fetch is not doubled.
|
||||
let _lastLang = null
|
||||
// inject() ONLY works during setup — calling it inside the watch callback would warn on every
|
||||
// tick and return null, so the ref is captured HERE and only READ later. This file already does
|
||||
// the same for profileConfig, for the same reason.
|
||||
let _langRef = null
|
||||
try { _langRef = inject('lang', null) } catch (_) { /* called outside setup */ }
|
||||
const _langNow = () => (_langRef && _langRef.value) || getRuntimeLocale() || ''
|
||||
watch(_langNow, (next) => {
|
||||
if (next === _lastLang) return
|
||||
_lastLang = next
|
||||
data.load()
|
||||
})
|
||||
|
||||
onMounted(() => { _lastLang = _langNow(); data.load() })
|
||||
|
||||
return { bus, data, FACET_ACC, filteredRows, kpis, exporter, responsive }
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue