Translate the eight strings that had no catalogue entry, keeping their English exactly

The shepherd's call: translating wording that already exists is not a product
decision. So every string keeps the English it reads today and gains a key, in
all eleven locales.

Big Picture's four first, since it is the localization priority: Export CSV, the
detail drawer's title and its empty state, and the legend's "heartbeat zone"
bin. Then Top countries, Select all, the table dock's Expand/Collapse titles,
and the singular "group" whose plural was already translated — a half-translated
plural is its own small defect.

Full screen / Exit full screen now read buttons.toggleFullscreen, which already
shipped in every locale, so that one needed no new wording at all. The arrow
glyph still carries the state.

Three more went in that were not on the list, because leaving them would have
been worse than the defect being fixed: "Deselect all" is the other half of a
toggle whose first half we just translated, and the two row-selection aria
labels are the same control speaking to a screen reader. A control that changes
language halfway through is harder to use than one that never changed at all.

★ ONE STRING I AM NOT CONFIDENT IN, AND IT SHOULD BE CHECKED BY A SPEAKER.
"heartbeat zone" is the Driver's own coinage, not standard UI vocabulary, and a
metaphor is exactly where a translation I cannot read can go quietly wrong. I
translated it literally in all ten languages. The rest are ordinary interface
terms; that one is a judgement, and I would rather flag it than have it
discovered on the gift.

VERIFIED HEADLESS: i18n-parity passes with zero debt; three bundles build; the
keys AND their Arabic values are present in the built output (تصدير CSV,
منطقة نبض القلب, اختر شعبًا لعرض التفاصيل, and the header's placeholder).
Numerals are untouched, per the hold — this is text only.

(cherry picked from commit 67568c0509)
This commit is contained in:
Doxa Maps 2026-09-26 02:01:22 +00:00
parent 9844f72eaf
commit 8991181091
38 changed files with 218 additions and 46 deletions

View file

@ -3,5 +3,9 @@
"closeDetailView": "إغلاق عرض التفاصيل",
"collapseLegend": "طيّ وسيلة الإيضاح",
"expandLegend": "توسيع وسيلة الإيضاح",
"toggleLegend": "إظهار وسيلة الإيضاح أو إخفاؤها"
"toggleLegend": "إظهار وسيلة الإيضاح أو إخفاؤها",
"searchPeopleGroups": "ابحث عن الشعوب",
"resetAllFilters": "إعادة تعيين كل عوامل التصفية والبحث",
"selectAllRows": "تحديد كل الصفوف",
"deselectAllRows": "إلغاء تحديد كل الصفوف"
}

View file

@ -50,5 +50,15 @@
"groups": "مجموعة",
"expand": "توسيع",
"collapse": "طيّ",
"place": "المكان"
"place": "المكان",
"export_csv": "تصدير CSV",
"detail": "التفاصيل",
"detail_empty": "اختر شعبًا لعرض التفاصيل",
"heartbeat_zone": "منطقة نبض القلب",
"top_countries": "أبرز الدول",
"select_all": "تحديد الكل",
"expand_table": "توسيع الجدول",
"collapse_table": "طيّ الجدول — توسيع الخريطة",
"group": "مجموعة",
"deselect_all": "إلغاء تحديد الكل"
}

View file

@ -15,5 +15,6 @@
"loading": "جارٍ التحميل…",
"no_results": "لا توجد شعوب مطابقة لهذا التصفية.",
"type_to_search": "اكتب للبحث...",
"load_failed": "تعذّر تحميل بيانات الشعوب. هذه مشكلة اتصال، وليست مشكلة في التصفية."
"load_failed": "تعذّر تحميل بيانات الشعوب. هذه مشكلة اتصال، وليست مشكلة في التصفية.",
"search_people_or_countries": "ابحث عن الشعوب أو الدول…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "Detailansicht schließen",
"collapseLegend": "Legende einklappen",
"expandLegend": "Legende ausklappen",
"toggleLegend": "Legende ein- oder ausblenden"
"toggleLegend": "Legende ein- oder ausblenden",
"searchPeopleGroups": "Volksgruppen suchen",
"resetAllFilters": "Alle Filter und die Suche zurücksetzen",
"selectAllRows": "Alle Zeilen auswählen",
"deselectAllRows": "Auswahl aller Zeilen aufheben"
}

View file

@ -50,5 +50,15 @@
"groups": "Volksgruppen",
"expand": "Aufklappen",
"collapse": "Zuklappen",
"place": "Ort"
"place": "Ort",
"export_csv": "CSV exportieren",
"detail": "Details",
"detail_empty": "Wählen Sie eine Volksgruppe, um Details zu sehen",
"heartbeat_zone": "Herzschlag-Zone",
"top_countries": "Top-Länder",
"select_all": "Alle auswählen",
"expand_table": "Tabelle ausklappen",
"collapse_table": "Tabelle einklappen – Karte vergrößern",
"group": "Volksgruppe",
"deselect_all": "Auswahl aufheben"
}

View file

@ -15,5 +15,6 @@
"loading": "Wird geladen…",
"no_results": "Keine Volksgruppen entsprechen diesem Filter.",
"type_to_search": "Zum Suchen tippen...",
"load_failed": "Die Daten konnten nicht geladen werden. Das ist ein Verbindungsproblem, nicht Ihr Filter."
"load_failed": "Die Daten konnten nicht geladen werden. Das ist ein Verbindungsproblem, nicht Ihr Filter.",
"search_people_or_countries": "Volksgruppen oder Länder suchen…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "Close detail view",
"collapseLegend": "Collapse legend",
"expandLegend": "Expand legend",
"toggleLegend": "Show or hide the legend"
"toggleLegend": "Show or hide the legend",
"searchPeopleGroups": "Search people groups",
"resetAllFilters": "Reset all filters and search",
"selectAllRows": "Select all rows",
"deselectAllRows": "Deselect all rows"
}

View file

@ -50,5 +50,15 @@
"groups": "groups",
"expand": "Expand",
"collapse": "Collapse",
"place": "Place"
"place": "Place",
"export_csv": "Export CSV",
"detail": "Detail",
"detail_empty": "Select a people group to see detail",
"heartbeat_zone": "heartbeat zone",
"top_countries": "Top countries",
"select_all": "Select all",
"expand_table": "Expand table",
"collapse_table": "Collapse table — grow the map",
"group": "group",
"deselect_all": "Deselect all"
}

View file

@ -15,5 +15,6 @@
"loading": "Loading…",
"no_results": "No people groups match this filter.",
"type_to_search": "Type to search...",
"load_failed": "People-group data could not be loaded. This is a connection problem, not your filter."
"load_failed": "People-group data could not be loaded. This is a connection problem, not your filter.",
"search_people_or_countries": "Search people groups or countries…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "Cerrar vista de detalle",
"collapseLegend": "Contraer la leyenda",
"expandLegend": "Expandir la leyenda",
"toggleLegend": "Mostrar u ocultar la leyenda"
"toggleLegend": "Mostrar u ocultar la leyenda",
"searchPeopleGroups": "Buscar pueblos",
"resetAllFilters": "Restablecer todos los filtros y la búsqueda",
"selectAllRows": "Seleccionar todas las filas",
"deselectAllRows": "Deseleccionar todas las filas"
}

View file

@ -50,5 +50,15 @@
"groups": "pueblos",
"expand": "Expandir",
"collapse": "Contraer",
"place": "Lugar"
"place": "Lugar",
"export_csv": "Exportar CSV",
"detail": "Detalle",
"detail_empty": "Selecciona un pueblo para ver el detalle",
"heartbeat_zone": "zona del latido",
"top_countries": "Países principales",
"select_all": "Seleccionar todo",
"expand_table": "Expandir tabla",
"collapse_table": "Contraer tabla — ampliar el mapa",
"group": "pueblo",
"deselect_all": "Deseleccionar todo"
}

View file

@ -15,5 +15,6 @@
"loading": "Cargando…",
"no_results": "Ningún pueblo coincide con este filtro.",
"type_to_search": "Escribe para buscar...",
"load_failed": "No se pudieron cargar los datos de pueblos. Es un problema de conexión, no de tu filtro."
"load_failed": "No se pudieron cargar los datos de pueblos. Es un problema de conexión, no de tu filtro.",
"search_people_or_countries": "Buscar pueblos o países…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "Fermer la vue détaillée",
"collapseLegend": "Réduire la légende",
"expandLegend": "Développer la légende",
"toggleLegend": "Afficher ou masquer la légende"
"toggleLegend": "Afficher ou masquer la légende",
"searchPeopleGroups": "Rechercher des peuples",
"resetAllFilters": "Réinitialiser tous les filtres et la recherche",
"selectAllRows": "Sélectionner toutes les lignes",
"deselectAllRows": "Désélectionner toutes les lignes"
}

View file

@ -50,5 +50,15 @@
"groups": "peuples",
"expand": "Développer",
"collapse": "Replier",
"place": "Lieu"
"place": "Lieu",
"export_csv": "Exporter en CSV",
"detail": "Détail",
"detail_empty": "Sélectionnez un peuple pour voir le détail",
"heartbeat_zone": "zone du battement de cœur",
"top_countries": "Principaux pays",
"select_all": "Tout sélectionner",
"expand_table": "Développer le tableau",
"collapse_table": "Réduire le tableau — agrandir la carte",
"group": "peuple",
"deselect_all": "Tout désélectionner"
}

View file

@ -15,5 +15,6 @@
"loading": "Chargement…",
"no_results": "Aucun peuple ne correspond à ce filtre.",
"type_to_search": "Tapez pour rechercher...",
"load_failed": "Les données n’ont pas pu être chargées. C’est un problème de connexion, pas votre filtre."
"load_failed": "Les données n’ont pas pu être chargées. C’est un problème de connexion, pas votre filtre.",
"search_people_or_countries": "Rechercher des peuples ou des pays…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "विवरण दृश्य बंद करें",
"collapseLegend": "लेजेंड संक्षिप्त करें",
"expandLegend": "लेजेंड विस्तृत करें",
"toggleLegend": "लेजेंड दिखाएँ या छिपाएँ"
"toggleLegend": "लेजेंड दिखाएँ या छिपाएँ",
"searchPeopleGroups": "जनसमूह खोजें",
"resetAllFilters": "सभी फ़िल्टर और खोज रीसेट करें",
"selectAllRows": "सभी पंक्तियाँ चुनें",
"deselectAllRows": "सभी पंक्तियाँ अचयनित करें"
}

View file

@ -50,5 +50,15 @@
"groups": "जनसमूह",
"expand": "विस्तार करें",
"collapse": "संक्षिप्त करें",
"place": "स्थान"
"place": "स्थान",
"export_csv": "CSV निर्यात करें",
"detail": "विवरण",
"detail_empty": "विवरण देखने के लिए एक जनसमूह चुनें",
"heartbeat_zone": "हृदयगति क्षेत्र",
"top_countries": "शीर्ष देश",
"select_all": "सभी चुनें",
"expand_table": "तालिका विस्तृत करें",
"collapse_table": "तालिका संक्षिप्त करें — मानचित्र बड़ा करें",
"group": "जनसमूह",
"deselect_all": "सभी अचयनित करें"
}

View file

@ -15,5 +15,6 @@
"loading": "लोड हो रहा है…",
"no_results": "इस फ़िल्टर से कोई जनसमूह मेल नहीं खाता।",
"type_to_search": "खोजने के लिए लिखें...",
"load_failed": "जनसमूह डेटा लोड नहीं हो सका। यह कनेक्शन की समस्या है, आपके फ़िल्टर की नहीं।"
"load_failed": "जनसमूह डेटा लोड नहीं हो सका। यह कनेक्शन की समस्या है, आपके फ़िल्टर की नहीं।",
"search_people_or_countries": "जनसमूह या देश खोजें…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "Chiudi vista dettaglio",
"collapseLegend": "Comprimi la legenda",
"expandLegend": "Espandi la legenda",
"toggleLegend": "Mostra o nascondi la legenda"
"toggleLegend": "Mostra o nascondi la legenda",
"searchPeopleGroups": "Cerca popoli",
"resetAllFilters": "Reimposta tutti i filtri e la ricerca",
"selectAllRows": "Seleziona tutte le righe",
"deselectAllRows": "Deseleziona tutte le righe"
}

View file

@ -50,5 +50,15 @@
"groups": "popoli",
"expand": "Espandi",
"collapse": "Comprimi",
"place": "Luogo"
"place": "Luogo",
"export_csv": "Esporta CSV",
"detail": "Dettaglio",
"detail_empty": "Seleziona un popolo per vedere il dettaglio",
"heartbeat_zone": "zona del battito",
"top_countries": "Paesi principali",
"select_all": "Seleziona tutto",
"expand_table": "Espandi tabella",
"collapse_table": "Comprimi tabella — ingrandisci la mappa",
"group": "popolo",
"deselect_all": "Deseleziona tutto"
}

View file

@ -15,5 +15,6 @@
"loading": "Caricamento…",
"no_results": "Nessun popolo corrisponde a questo filtro.",
"type_to_search": "Digita per cercare...",
"load_failed": "Impossibile caricare i dati dei popoli. È un problema di connessione, non del filtro."
"load_failed": "Impossibile caricare i dati dei popoli. È un problema di connessione, non del filtro.",
"search_people_or_countries": "Cerca popoli o paesi…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "Fechar vista de detalhe",
"collapseLegend": "Recolher a legenda",
"expandLegend": "Expandir a legenda",
"toggleLegend": "Mostrar ou ocultar a legenda"
"toggleLegend": "Mostrar ou ocultar a legenda",
"searchPeopleGroups": "Pesquisar povos",
"resetAllFilters": "Redefinir todos os filtros e a pesquisa",
"selectAllRows": "Selecionar todas as linhas",
"deselectAllRows": "Desmarcar todas as linhas"
}

View file

@ -50,5 +50,15 @@
"groups": "povos",
"expand": "Expandir",
"collapse": "Recolher",
"place": "Lugar"
"place": "Lugar",
"export_csv": "Exportar CSV",
"detail": "Detalhe",
"detail_empty": "Selecione um povo para ver o detalhe",
"heartbeat_zone": "zona do batimento",
"top_countries": "Principais países",
"select_all": "Selecionar tudo",
"expand_table": "Expandir tabela",
"collapse_table": "Recolher tabela — ampliar o mapa",
"group": "povo",
"deselect_all": "Desmarcar tudo"
}

View file

@ -15,5 +15,6 @@
"loading": "A carregar…",
"no_results": "Nenhum povo corresponde a este filtro.",
"type_to_search": "Escreva para pesquisar...",
"load_failed": "Não foi possível carregar os dados dos povos. É um problema de ligação, não do seu filtro."
"load_failed": "Não foi possível carregar os dados dos povos. É um problema de ligação, não do seu filtro.",
"search_people_or_countries": "Pesquisar povos ou países…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "Închide vizualizarea detaliată",
"collapseLegend": "Restrânge legenda",
"expandLegend": "Extinde legenda",
"toggleLegend": "Afișează sau ascunde legenda"
"toggleLegend": "Afișează sau ascunde legenda",
"searchPeopleGroups": "Caută popoare",
"resetAllFilters": "Resetează toate filtrele și căutarea",
"selectAllRows": "Selectează toate rândurile",
"deselectAllRows": "Deselectează toate rândurile"
}

View file

@ -50,5 +50,15 @@
"groups": "popoare",
"expand": "Extinde",
"collapse": "Restrânge",
"place": "Loc"
"place": "Loc",
"export_csv": "Exportă CSV",
"detail": "Detaliu",
"detail_empty": "Selectează un popor pentru a vedea detaliile",
"heartbeat_zone": "zona bătăilor inimii",
"top_countries": "Țări de top",
"select_all": "Selectează tot",
"expand_table": "Extinde tabelul",
"collapse_table": "Restrânge tabelul — mărește harta",
"group": "popor",
"deselect_all": "Deselectează tot"
}

View file

@ -15,5 +15,6 @@
"loading": "Se încarcă…",
"no_results": "Niciun popor nu corespunde acestui filtru.",
"type_to_search": "Scrie pentru a căuta...",
"load_failed": "Datele despre popoare nu au putut fi încărcate. Este o problemă de conexiune, nu a filtrului."
"load_failed": "Datele despre popoare nu au putut fi încărcate. Este o problemă de conexiune, nu a filtrului.",
"search_people_or_countries": "Caută popoare sau țări…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "Закрыть подробный вид",
"collapseLegend": "Свернуть легенду",
"expandLegend": "Развернуть легенду",
"toggleLegend": "Показать или скрыть легенду"
"toggleLegend": "Показать или скрыть легенду",
"searchPeopleGroups": "Поиск народов",
"resetAllFilters": "Сбросить все фильтры и поиск",
"selectAllRows": "Выбрать все строки",
"deselectAllRows": "Снять выделение со всех строк"
}

View file

@ -50,5 +50,15 @@
"groups": "народов",
"expand": "Развернуть",
"collapse": "Свернуть",
"place": "Место"
"place": "Место",
"export_csv": "Экспорт CSV",
"detail": "Подробности",
"detail_empty": "Выберите народ, чтобы увидеть подробности",
"heartbeat_zone": "зона сердцебиения",
"top_countries": "Основные страны",
"select_all": "Выбрать все",
"expand_table": "Развернуть таблицу",
"collapse_table": "Свернуть таблицу — увеличить карту",
"group": "народ",
"deselect_all": "Снять выделение"
}

View file

@ -15,5 +15,6 @@
"loading": "Загрузка…",
"no_results": "Нет народов, соответствующих этому фильтру.",
"type_to_search": "Введите для поиска...",
"load_failed": "Не удалось загрузить данные о народах. Это проблема соединения, а не фильтра."
"load_failed": "Не удалось загрузить данные о народах. Это проблема соединения, а не фильтра.",
"search_people_or_countries": "Поиск народов или стран…"
}

View file

@ -3,5 +3,9 @@
"closeDetailView": "关闭详情视图",
"collapseLegend": "收起图例",
"expandLegend": "展开图例",
"toggleLegend": "显示或隐藏图例"
"toggleLegend": "显示或隐藏图例",
"searchPeopleGroups": "搜索民族",
"resetAllFilters": "重置所有筛选和搜索",
"selectAllRows": "选择所有行",
"deselectAllRows": "取消选择所有行"
}

View file

@ -50,5 +50,15 @@
"groups": "个民族",
"expand": "展开",
"collapse": "收起",
"place": "地点"
"place": "地点",
"export_csv": "导出 CSV",
"detail": "详情",
"detail_empty": "选择一个民族以查看详情",
"heartbeat_zone": "心跳区",
"top_countries": "主要国家",
"select_all": "全选",
"expand_table": "展开表格",
"collapse_table": "收起表格 — 放大地图",
"group": "个民族",
"deselect_all": "取消全选"
}

View file

@ -15,5 +15,6 @@
"loading": "加载中…",
"no_results": "没有符合此筛选条件的民族。",
"type_to_search": "输入以搜索...",
"load_failed": "无法加载民族数据。这是连接问题,而不是筛选问题。"
"load_failed": "无法加载民族数据。这是连接问题,而不是筛选问题。",
"search_people_or_countries": "搜索民族或国家…"
}

View file

@ -164,8 +164,8 @@ onMounted(loadOptions)
<div class="rh-search">
<SearchBar
:model-value="query"
placeholder="Search people groups or countries…"
aria-label="Search people groups"
:placeholder="L('search_people_or_countries', 'Search people groups or countries…')"
:aria-label="tStatic('aria.searchPeopleGroups') || 'Search people groups'"
@search="onSearch"
/>
</div>
@ -196,7 +196,7 @@ onMounted(loadOptions)
v-if="hasAny"
class="rh-reset"
type="button"
aria-label="Reset all filters and search"
:aria-label="tStatic('aria.resetAllFilters') || 'Reset all filters and search'"
@click="reset"
>{{ L('reset_all', '') || tStatic('controls.reset_all') || 'Reset' }}</button>
</div>

View file

@ -417,7 +417,7 @@ export default {
// rail's Religion tree for anyone who needs Sunni vs Folk.
{ id: 'religion', type: 'donut', facet: 'religion_family', get title() { return tStatic('dashboard.religion') || 'Religion' }, topN: 8 },
{ id: 'region', type: 'donut', facet: 'wagf_region', get title() { return tStatic('dashboard.region') || 'Region' }, topN: 9 },
{ id: 'country', type: 'bar', facet: 'country_code', title: 'Top countries', topN: 12 },
{ id: 'country', type: 'bar', facet: 'country_code', get title() { return tStatic('dashboard.top_countries') || 'Top countries' }, topN: 12 },
],
// ── detail drawer ── fields surfaced for a focused record

View file

@ -783,10 +783,10 @@ useShadowStyles(`
<!-- Header bar — ALWAYS rendered, collapsed or not. The count + Export CSV are the two
controls the Driver asked to keep docked under the map when the grid is hidden. -->
<div class="rd-table-bar" :data-collapsed="collapsed ? '1' : '0'">
<button class="rd-dock" :title="collapsed ? 'Expand table' : 'Collapse table — grow the map'"
<button class="rd-dock" :title="collapsed ? T('dashboard.expand_table', 'Expand table') : T('dashboard.collapse_table', 'Collapse table — grow the map')"
data-table-dock :aria-expanded="collapsed ? 'false' : 'true'" @click="toggleDock">{{ collapsed ? '▴' : '▾' }}</button>
<span class="ttl">{{ T('legend.titleBar.peopleGroups', 'People Groups') }}</span>
<span class="ct" data-table-count>{{ fmt(rowCount) }} {{ rowCount === 1 ? 'group' : T('dashboard.groups', 'groups') }}</span>
<span class="ct" data-table-count>{{ fmt(rowCount) }} {{ rowCount === 1 ? T('dashboard.group', 'group') : T('dashboard.groups', 'groups') }}</span>
<!-- role=status so assistive tech announces the count as the selection changes. -->
<!-- ★ NO CLEAR BUTTON IN THIS BAR (Driver 2026-09-23, on the selection flood: "it does not
need to give me a Clear Selected button in the header because the FOOTER of the page
@ -842,14 +842,14 @@ useShadowStyles(`
:title="isFull ? 'Exit full screen (Esc) — back to seven rows'
: 'Open this table full screen'"
:aria-label="isFull ? 'Exit full screen' : 'Open table full screen'"
@click="toggleFull">{{ isFull ? '⤡ Exit full screen' : '⤢ Full screen' }}</button>
@click="toggleFull">{{ (isFull ? '⤡ ' : '⤢ ') + T('buttons.toggleFullscreen', 'Full screen') }}</button>
<span class="rd-csv" :class="{ busy: exporting }" role="button" tabindex="0" data-csv-btn
:aria-busy="exporting ? 'true' : 'false'"
:title="selCount ? `Export the ${selCount} selected people groups — all fields`
: `Export these ${fmt(rowCount)} people groups — all fields`"
@click="onExport" @mouseenter="onExportHover" @focus="onExportHover"
@keydown.enter.prevent="onExport" @keydown.space.prevent="onExport">
{{ exporting ? '…' : '↓' }} Export CSV<span v-if="selCount" class="rd-csv-n">{{ selCount }}</span>
{{ exporting ? '…' : '↓' }} {{ T('dashboard.export_csv', 'Export CSV') }}<span v-if="selCount" class="rd-csv-n">{{ selCount }}</span>
</span>
</div>
@ -865,11 +865,11 @@ useShadowStyles(`
SAME floor for the same reason they all carry the same flex weight: three different
widths would mean three different column grids. -->
<div class="rd-thead" role="row" :style="rowMinStyle">
<div class="rd-th chk" style="cursor:pointer" :title="allVisibleSelected ? 'Deselect all ' + fmt(rowCount) : 'Select all ' + fmt(rowCount)" @click="toggleAll">
<div class="rd-th chk" style="cursor:pointer" :title="(allVisibleSelected ? T('dashboard.deselect_all', 'Deselect all') : T('dashboard.select_all', 'Select all')) + ' ' + fmt(rowCount)" @click="toggleAll">
<!-- tri-state: `indeterminate` is a JS-only property (no HTML attribute), so it is set
via a ref callback; the browser then reports aria-checked="mixed" for us. -->
<input type="checkbox" :checked="allVisibleSelected" :ref="setAllChkEl"
:aria-label="allVisibleSelected ? 'Deselect all rows' : 'Select all rows'" tabindex="-1" style="pointer-events:none" />
:aria-label="allVisibleSelected ? T('aria.deselectAllRows', 'Deselect all rows') : T('aria.selectAllRows', 'Select all rows')" tabindex="-1" style="pointer-events:none" />
</div>
<!-- :style flex weight — see dashboard.config.js `columns[].w`. Header, body and FOOTER must
all carry the same weight or the summary row drifts out of alignment with its columns. -->

View file

@ -13,6 +13,13 @@
* behavior are unchanged from the source panel.
*/
import { inject, ref, computed, onMounted, onBeforeUnmount } from 'vue'
import { tStatic } from '@map/i18n/index.js'
// This drawer's chrome, read out of the catalogue. tStatic and not useI18n: these components mount
// inside custom elements that install no i18n plugin, and an unguarded useI18n() throws on the way
// in and takes the panel with it. The English stays as the fallback so a missing key degrades to
// what shipped rather than to a key name.
const T = (key, en) => tStatic(key) || en
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { useCsvExport } from '../../composables/useCsvExport.js'
import { useTreeLegendLabels } from '../../composables/useTreeLegendLabels.js'
@ -149,10 +156,10 @@ useShadowStyles(`
<template>
<section class="rd-detail" data-region="detail-drawer">
<div class="rd-dh">
<span>Detail</span>
<span>{{ T('dashboard.detail', 'Detail') }}</span>
<button v-if="record" class="rd-detail-close" type="button" aria-label="Close detail" data-detail-close @click="clearDetail">✕</button>
</div>
<div v-if="!record" class="rd-empty" data-detail-empty>Select a people group to see detail</div>
<div v-if="!record" class="rd-empty" data-detail-empty>{{ T('dashboard.detail_empty', 'Select a people group to see detail') }}</div>
<template v-else>
<div class="rd-top">
<img v-if="showPhoto" class="rd-thumb" :src="record.image_url" :alt="record.name" data-detail-thumb />

View file

@ -542,7 +542,7 @@ const HEARTBEAT_DEFAULTS = { heartbeatThreshold: 150, binThresholds: [5, 20, 60]
// is produced, so a host that moves heartbeatThreshold relabels correctly and CANNOT end up with
// the name on a bin that is no longer the top one.
function binLabel(lo, hi) {
if (!Number.isFinite(hi)) return `${lo}+ · heartbeat zone`
if (!Number.isFinite(hi)) return `${lo}+ · ${T('dashboard.heartbeat_zone', 'heartbeat zone')}`
return hi - 1 <= lo ? `${lo}` : `${lo}–${hi - 1}`
}