Research dashboard: filter lists read as lists in the reader's language
Three places joined names with ", ", which is a list only in English. Arabic separates with a word, Chinese uses a different mark entirely and no comma at any position, and German and French put a word before the last item. The separator and the final connector are the grammar, so no catalogue key can fix them from outside. Two of the three carried a second, worse error. A multi-value filter holds ALTERNATIVES -- the bus matches a row if ANY of its values matches, in useSelectionBus with some() and in the data table with includes() -- so the chip means "Islam or Hinduism". Joined with a comma it reads as "and" in every language with an explicit connector, which states the opposite of what the filter does. Those two become a disjunction. The third is the screen-reader live region, which names the facets currently applied. Those hold together and narrow the set in turn, so it is a conjunction. It is also the one string here that some readers rely on entirely, having no chips to glance at. The composable is the single owner: every surface that turns a filter into words comes through it, so the chip bar, the research-filter chips and the CSV provenance line now say the same thing in the same language. Left alone deliberately: five joins in this plugin's gate scripts. Those are failure messages read in a terminal by whoever runs the gate, not text shown to a reader, and formatting them per locale would help nobody. Source only. This needs the bundle rebuilt before it ships.
This commit is contained in:
parent
9db9f519d4
commit
090b4eeb16
3 changed files with 17 additions and 5 deletions
|
|
@ -23,7 +23,7 @@
|
|||
* DashboardSearchHeader (DOXA-LIB-3) remains available in the kit but is not mounted here.
|
||||
*/
|
||||
import { computed, watch, inject, ref, unref, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { tStatic } from '@map/i18n/index.js'
|
||||
import { tStatic, formatList } from '@map/i18n/index.js'
|
||||
|
||||
// This component's chrome, read out of the catalogue. tStatic and not useI18n: these mount inside
|
||||
// custom elements that install no i18n plugin, and an unguarded useI18n() throws on the way in and
|
||||
|
|
@ -183,7 +183,10 @@ function announce() {
|
|||
// chips that carry that answer are exactly what a screen-reader user cannot glance at.
|
||||
const active = Object.keys(bus.filterSet)
|
||||
if (active.length) {
|
||||
const names = active.map((k) => mergedConfig.value.facets?.[k]?.label || k).join(', ')
|
||||
// CONJUNCTION: these are separate facets that all hold at once — Religion AND Affinity Bloc AND
|
||||
// Country — each one narrowing the set further. Read aloud, so the connector is the difference
|
||||
// between a list and a sentence in most locales.
|
||||
const names = formatList(active.map((k) => mergedConfig.value.facets?.[k]?.label || k), { type: 'conjunction' })
|
||||
msg += ' · ' + T('dashboard.filters_live', 'Filters: {list}').replace('{list}', names)
|
||||
}
|
||||
const q = (bus.searchTerm?.value || '').trim()
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
*/
|
||||
import { inject, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { tStatic } from '@map/i18n/index.js'
|
||||
import { tStatic, formatList } 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'
|
||||
|
|
@ -188,7 +188,11 @@ const activeChips = computed(() => {
|
|||
// a two-value pick renders as "A,B" with no spacing. LATENT, not live: nothing in this plugin
|
||||
// calls toggleFilterValue, so no caller produces a prayer array today. Closed here because the
|
||||
// next caller that does would not think to look in a chip label for the reason.
|
||||
const shown = k === PRAYER_KEY ? (Array.isArray(v) ? v.join(', ') : v) : labelFor(k, v)
|
||||
// DISJUNCTION, not a plain list: several values of ONE facet are ALTERNATIVES. The bus matches a
|
||||
// row if ANY of them matches (useSelectionBus some(), DashboardDataTable includes()), so a
|
||||
// comma-joined "Islam, Hinduism" reads as "and" in any language with an explicit connector —
|
||||
// stating the opposite of what the filter does.
|
||||
const shown = k === PRAYER_KEY ? (Array.isArray(v) ? formatList(v, { type: 'disjunction' }) : v) : labelFor(k, v)
|
||||
return { name: k, label: `${FACETS.value[k]?.label || k}: ${shown}` }
|
||||
})
|
||||
// ★ THE TYPED SEARCH IS A FILTER AND MUST LOOK LIKE ONE (reported 2026-09-23: "I searched Bhutan
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
import { computed } from 'vue'
|
||||
import { useAffinityBlockLegendData } from '@map/composables/legends/useAffinityBlockLegendData.js'
|
||||
import { formatList } from '@map/i18n/index.js'
|
||||
|
||||
/**
|
||||
* @param {object} cfg the injected dashboardConfig (read for facetRail)
|
||||
|
|
@ -83,7 +84,11 @@ export function useTreeLegendLabels(cfg, dataStore, searchSourceId) {
|
|||
// thing. Without it an array fell through to its default toString and a chip read
|
||||
// "Religion: Islam,Hinduism": the codes joined by a bare comma, with no spacing and no code
|
||||
// resolved to its human name.
|
||||
if (Array.isArray(value)) return value.map((v) => (map && map.get(v)) || v).join(', ')
|
||||
// DISJUNCTION: a multi-value filter holds ALTERNATIVES, and the bus matches a row if ANY of them
|
||||
// matches. Every surface that turns a filter into words comes through here, so the connector is
|
||||
// resolved once and the chip bar, the research-filter chips and the CSV provenance line all say
|
||||
// the same thing in the reader's language.
|
||||
if (Array.isArray(value)) return formatList(value.map((v) => (map && map.get(v)) || v), { type: 'disjunction' })
|
||||
if (!map) return value
|
||||
return map.get(value) || value
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue