The grid's DATA follows the URL's language, and the grid turns round in Arabic

Two defects rega found at ?lang=ar and ?lang=es (row 30).

★ THE CHROME TRANSLATED AND THE DATA DID NOT. The `&lang=` on the people-groups
fetch was built from props and profile-config only, never from the URL — so a
reader who asked for Arabic got Arabic buttons around English country, affinity
and religion names. That reads as a half-finished translation rather than the
wiring bug it is. It now uses the library's own localeFromUrl() — imported, not
re-implemented — so the fetch and the chrome cannot disagree about what the URL
asked for. Same ladder regions uses, and a host passing languageCode still wins.

★ AND THERE WAS NO RTL AT ALL. In ar the cards, the total and the filter bar laid
out left-to-right around Arabic words. createAppI18n reflects an RTL locale onto
document.documentElement, which is right for a page it owns and useless for an
element dropped into somebody else's — which is this grid's whole job. `dir` is
set on the HOST now, like both composites: it takes effect through the inherited
CSS `direction`, and inheritance crosses shadow boundaries, so one attribute
turns the element and everything it renders. Reflected on mount as well as on
change, because the common case is a page that loads in Arabic and never changes.

⚠ ONE CONSEQUENCE I AM NOT HIDING. Localizing the DATA introduces a coupling the
linking did not have: the composite's language facet travels as a DISPLAY NAME,
so the map and the grid must now fetch in the SAME locale for that link to match.
Region links on a slug and religion on a family letter, so both are unaffected;
language is the one that depends on it. check:linked-vocabulary compares the two
halves' vocabularies and would catch a mismatch, but it runs in English today —
extending it to a non-English locale is the honest next step, and it is not done.
This commit is contained in:
Doxa Maps 2026-09-26 07:56:38 +00:00
parent 688ba6cfde
commit ae5e27c389

View file

@ -46,7 +46,7 @@ import SearchBar from '@map/components/search/SearchBar.vue'
import FilterDropdown from '@map/components/search/FilterDropdown.vue'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
import { applyPeopleGroupList } from '@map/lists/peopleGroupList.js'
import { tStatic } from '@map/i18n/index.js'
import { tStatic, localeFromUrl, RTL_LOCALES, getRuntimeLocale, detectLocale } from '@map/i18n/index.js'
import { countrySlug } from './src/countries.js'
// ── DERIVED family axes (BUG-SEMANTIC-TREE-SIDEBAR-NOT-INTEROPERABLE-WITH-GRID) ──
// The Religion/Language trees' TOP TIERS emit DERIVED family names ("Hinduism",
@ -251,7 +251,36 @@ const filtersBackdrop = computed(() => {
}
})
const prayBaseUrl = computed(() => props.prayBaseUrl || cfg.value.prayBaseUrl || 'https://pray.doxa.life')
const languageCode = computed(() => props.languageCode || cfg.value.languageCode || '')
// ★ THE URL IS A SOURCE OF TRUTH FOR THE DATA TOO, not just for the chrome.
// MEASURED by rega at ?lang=ar and ?lang=es: this grid's CHROME translated and its DATA stayed
// English — country names, affinity names, religion labels — because the `&lang=` on the fetch was
// built from props and profile-config ONLY. A reader who asked for Arabic got Arabic buttons around
// English content, which reads as a half-finished translation rather than the wiring bug it is.
// localeFromUrl() is the library's own reader, imported rather than re-implemented, so the fetch and
// the chrome can never disagree about what the URL asked for. Same ladder regions uses
// (region-dashboards.vue:95), and a host that passes languageCode still wins.
const languageCode = computed(() =>
props.languageCode || cfg.value.languageCode || cfg.value.locale || cfg.value.lang || localeFromUrl() || '')
// ★ RTL IS A DIRECTION, NOT A TRANSLATION — and this grid had none.
// MEASURED in ar: the cards, the total and the filter bar all laid out left-to-right around Arabic
// words. createAppI18n reflects an RTL locale onto document.documentElement, which is right for a
// page it owns and useless for an element dropped into someone else's, which is this grid's whole
// job. Set on the HOST, like both composites: `dir` takes effect through the inherited CSS
// `direction`, and inheritance crosses shadow boundaries, so one attribute turns the element and
// everything it renders. Only ever SET, never cleared — a host page that declares its own dir owns
// that decision.
const textDir = computed(() => {
const l = languageCode.value || getRuntimeLocale() || detectLocale()
return RTL_LOCALES.has(String(l).split('-')[0].toLowerCase()) ? 'rtl' : 'ltr'
})
function reflectDir() {
try {
const host = hostEl()
if (host && host.setAttribute && textDir.value === 'rtl') host.setAttribute('dir', 'rtl')
} catch (_) { /* no host (SSR/tests) — nothing to reflect */ }
}
watch(textDir, reflectDir)
// ★ ABSOLUTE CANONICAL DEFAULT (Driver 2026-09-23, pasted from his own browser:
// http://localhost:5174/research/pitaguari/ -- "when I click view more information it does not
// send me to the proper doxa.life page"). The old default '/research/' was ROOT-RELATIVE, so it
@ -842,6 +871,9 @@ onMounted(() => {
const el = hostEl()
if (el) el.__vocabulary = vocabulary
if (typeof window !== 'undefined') window.__peopleGroupsGridVocabulary = vocabulary
// The watcher above only fires on a CHANGE, and the common case is a page that loads in Arabic
// and never changes — so reflect it once on mount as well.
reflectDir()
getUUPGs()
})
</script>