diff --git a/plugin-designs/key-cities/cities-list.vue b/plugin-designs/key-cities/cities-list.vue index 47d20964f..7ec808445 100644 --- a/plugin-designs/key-cities/cities-list.vue +++ b/plugin-designs/key-cities/cities-list.vue @@ -26,7 +26,9 @@ * PERSECUTION-SAFETY: locations of PEOPLE GROUPS, which is public ethnographic * data. No church, believer or meeting-point information, and it must not gain any. */ -import { computed, inject, ref, onMounted } from 'vue' +import { computed, inject, ref, onMounted, h } from 'vue' +import { useI18n } from 'vue-i18n' +import { sentenceParts, keep, ltr } from '@map/composables/useSentence.js' import { useShadowStyles } from '@map/composables/useShadowStyles.js' import { resolveAssetsBase } from '@map/utils/embedBase.js' @@ -40,7 +42,7 @@ const assetsBase = computed(() => resolveAssetsBase(profileConfig.value)) // it and block first paint. Measured on the other two apps: static imports took // them to 2.0MB and 2.9MB before this fix. const table = ref({ rows: [] }) -const loadErr = ref(null) +const loadErr = ref(null) // the engine's or the server's own words: shown INSIDE a translated line onMounted(async () => { try { const r = await fetch(`${assetsBase.value}/tables/key-cities.json`) @@ -55,11 +57,8 @@ const named = computed(() => rows.value.filter((r) => r.named)) // Default to the NAMED population centres: a first screen of 1,369 unnamed // single-group clusters would read as broken rather than as honest. const filter = ref('named') -const FILTERS = [ - { id: 'named', label: 'Named population centres' }, - { id: 'multi', label: 'Multi-group clusters' }, - { id: 'all', label: 'All locations' }, -] +// Labels are keyCities.list.filters. (the template translates them). +const FILTERS = [{ id: 'named' }, { id: 'multi' }, { id: 'all' }] const shown = computed(() => { if (filter.value === 'named') return named.value @@ -87,6 +86,12 @@ const collapsed = ref({}) const isShut = (n) => !!collapsed.value[n] const toggle = (n) => { collapsed.value = { ...collapsed.value, [n]: !collapsed.value[n] } } const fmt = (n) => (Number(n) || 0).toLocaleString() +const { t } = useI18n() +// ONE key for the caveat's sentence: the count and the gazetteer's name go in as values, the name as +// its own left-to-right island (it is a proper name, never translated), so each language orders it. +const caveatRow = computed(() => h('span', sentenceParts( + t('keyCities.list.caveat.row', keep('n', 'source')), + { n: fmt(named.value.length), source: ltr('Nominatim/OpenStreetMap', true) }))) useShadowStyles( '.kc{position:absolute;inset:0;overflow-y:auto;padding:20px 18px 44px;background:#f6f7f9;color:#0f1419;' + @@ -139,7 +144,7 @@ const band = (a) => a === 'yes' ? 'ok' : a === 'partial' ? 'mid' : a === 'no' ? {{ $t('keyCities.list.intro', { named: fmt(named.length), clusters: fmt(rows.length) }) }}

-
{{ loadErr }}
+
{{ r.country }} - · {{ r.people_group_count }} group{{ r.people_group_count === 1 ? '' : 's' }} + · {{ $t('keyCities.list.peopleGroups', { n: fmt(r.people_group_count) }) }}
{{ fmt(r.upg_population) }}
- {{ r.country_internet_pct != null ? r.country_internet_pct + '%' : 'unknown' }} + {{ r.country_internet_pct != null ? r.country_internet_pct + '%' : $t('keyCities.map.unknown') }}
- What a row is. A cluster of people-group centroids within ~25km — not a municipal - boundary. Pins mark where a people group is centred, not a settlement, so clusters are never - named by inference: the {{ fmt(named.length) }} multi-group centres were resolved by - Nominatim/OpenStreetMap (ODbL, cited per row) and the rest stay unnamed rather than guessed. + {{ $t('keyCities.list.caveat.rowLead') }}

- Internet figures are NATIONAL, shown as context. Per-city connectivity is not publicly - available at this scale, and a national figure must not be read as a city measurement. + {{ $t('keyCities.map.national') }} {{ $t('keyCities.list.caveat.internet') }}

- This page carries people-group locations and connectivity only — no church, believer or - meeting-point data, by design. + {{ $t('keyCities.list.caveat.scope') }}
diff --git a/plugin-designs/key-cities/cities-map.vue b/plugin-designs/key-cities/cities-map.vue index 4eac00f21..d8221d40d 100644 --- a/plugin-designs/key-cities/cities-map.vue +++ b/plugin-designs/key-cities/cities-map.vue @@ -66,10 +66,11 @@ const selected = ref(null) const err = ref(null) // Bands, not a continuous ramp: a reader can hold four categories, not a gradient. +// Labels are keyCities.map.bands. (the legend translates them). const BANDS = [ - { id: 'named', label: 'named centre', color: '#0b7a5b' }, - { id: 'multi', label: 'multi-group', color: '#e07a2b' }, - { id: 'single', label: 'single group', color: '#9aa2ad' }, + { id: 'named', color: '#0b7a5b' }, + { id: 'multi', color: '#e07a2b' }, + { id: 'single', color: '#9aa2ad' }, ] const bandColor = (b) => (BANDS.find((x) => x.id === b) || {}).color || '#9aa2ad' diff --git a/plugin-designs/key-cities/city-poster-wall.vue b/plugin-designs/key-cities/city-poster-wall.vue index 4c028e002..e14c9c89f 100644 --- a/plugin-designs/key-cities/city-poster-wall.vue +++ b/plugin-designs/key-cities/city-poster-wall.vue @@ -19,6 +19,7 @@ * Partial and correct beats complete and wrong. The wall shows only what exists. */ import { computed, inject, ref, onMounted } from 'vue' +import { useI18n } from 'vue-i18n' import PosterWall from '@map/components/poster/PosterWall.vue' import { resolveAssetsBase } from '@map/utils/embedBase.js' @@ -46,12 +47,9 @@ const items = computed(() => rows.value bbox: null, }))) -const groupBy = (c) => c.region || 'Other' +const { t } = useI18n() +const groupBy = (c) => c.region || t('keyCities.posters.otherRegion') const collapsedBy = (sec) => sec.total < 20 - -const intro = - 'A printable sheet for each population centre where unengaged people groups live — ' + - 'named from a cited gazetteer, never inferred.'