Cities with Internet: its load and engine errors speak the reader's language

The engine-missing message read the shared messages.engineMissing through this file's
connectivity-prefixed t(), so it always missed and showed English in every locale.
Shared keys are now read qualified with tStatic: engineMissing, engineNotLoaded, and
couldNotLoadNamed for a data table that fails to load (naming the tab in the reader's
language, not 'table not found (404)'). Four unused English tab labels are removed.
This commit is contained in:
Doxa Maps 2026-09-28 11:56:46 +00:00
parent c9905dd9db
commit 5dc161daa9
2 changed files with 21 additions and 13 deletions

View file

@ -33,7 +33,8 @@
* carrier flag is a NAME-PATTERN HEURISTIC, not a verified classification.
*/
import { computed, inject, ref, shallowRef, markRaw, onMounted, onBeforeUnmount } from 'vue'
import { useOptionalT } from '@map/composables/useOptionalT.js'
import { useOptionalT, fillParams } from '@map/composables/useOptionalT.js'
import { tStatic } from '@map/i18n/index.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { addMapAttribution } from '@map/composables/useMapAttribution'
@ -374,12 +375,13 @@ onMounted(async () => {
if (!gl) {
const engine = ciProvider === 'maplibre' ? 'MapLibre' : 'Mapbox'
console.warn(`[connectivity-map] the ${engine} engine did not load; refusing to substitute another engine.`)
// messages.engineMissing is the shared key (studio's landing 3); English only if a host has no catalogue.
const msg = t('messages.engineMissing', { engine })
err.value = msg && msg !== 'messages.engineMissing' ? msg : `The ${engine} map engine did not load.`
// messages.engineMissing is the SHARED key, so read it qualified with tStatic: this file's t() prefixes
// every key with connectivity., which made it connectivity.messages.engineMissing, a miss, so the
// message was English in every locale. tStatic follows the page locale and falls back to English itself.
err.value = fillParams(tStatic('messages.engineMissing'), { engine })
return
}
if (!mapEl.value) { err.value = 'map engine not loaded'; return }
if (!mapEl.value) { err.value = tStatic('messages.engineNotLoaded'); return }
// RTL SHAPING, before the first map exists. This raw map does not go through useMapInstance
// (which installs it), and the basemap draws each place in its OWN script: over the Middle East
// most labels are Arabic, Persian or Hebrew for EVERY viewer. An entry-time call cannot do this,

View file

@ -25,7 +25,8 @@ import { computed, inject, ref, watch } from 'vue'
import DataTable from '@map/components/tables/DataTable.vue'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
import { useOptionalT } from '@map/composables/useOptionalT.js'
import { useOptionalT, fillParams } from '@map/composables/useOptionalT.js'
import { tStatic } from '@map/i18n/index.js'
const profileConfig = inject('profileConfig', ref(null))
@ -49,13 +50,13 @@ const t = useOptionalT('connectivity')
const num = (n) => (Number(n) || 0).toLocaleString()
const TABS = [
{ id: 'cities', label: 'Key Cities', file: 'key-cities' },
{ id: 'internet', label: 'Connectivity', file: 'country-connectivity' },
{ id: 'operators', label: 'ISPs & Carriers', file: 'network-operators' },
{ id: 'social', label: 'Social Platforms', file: 'social-platforms' },
{ id: 'cities', file: 'key-cities' },
{ id: 'internet', file: 'country-connectivity' },
{ id: 'operators', file: 'network-operators' },
{ id: 'social', file: 'social-platforms' },
// Messaging apps are the channel referral stats CANNOT see — WhatsApp tops 54
// countries, Telegram 38, and neither appears in StatCounter at all.
{ id: 'messaging', label: 'Messaging Apps', file: 'messaging-channels' },
{ id: 'messaging', file: 'messaging-channels' },
]
const active = ref('cities')
const current = computed(() => TABS.find((t) => t.id === active.value) || TABS[0])
@ -73,9 +74,14 @@ async function ensure(file) {
loading.value = true; loadErr.value = null
try {
const r = await fetch(`${assetsBase.value}/tables/${file}.json`)
if (!r.ok) throw new Error(`table not found (${r.status})`)
if (!r.ok) throw new Error(String(r.status))
cache.value = { ...cache.value, [file]: await r.json() }
} catch (e) { loadErr.value = String(e.message || e) }
} catch (_) {
// The reader sees the shared, translated "Could not load {name}." (qualified via tStatic: this file's
// t() would prefix connectivity.), naming the tab in their language; the status went to the console.
const tab = TABS.find((x) => x.file === file)
loadErr.value = fillParams(tStatic('messages.couldNotLoadNamed'), { name: tab ? t('tables.tabs.' + tab.id) : file })
}
finally { loading.value = false }
}
watch(current, (t) => t && ensure(t.file), { immediate: true })