diff --git a/internal/check-hardcoded-english.mjs b/internal/check-hardcoded-english.mjs index de39895df..6ba093f61 100644 --- a/internal/check-hardcoded-english.mjs +++ b/internal/check-hardcoded-english.mjs @@ -269,7 +269,12 @@ function resolvedFallbackOf(node, parent, stack, names, root = '.') { // form 2: the literal is the RIGHT side of `t(…) || 'English'` or `t(…) ?? 'English'` const lg = stack[stack.length - 1] if (lg && lg.type === 'LogicalExpression' && (lg.operator === '||' || lg.operator === '??') && lg.right === node) { + // `||` is left-associative, so `config || t('key') || 'English'` parses with the whole + // `config || t('key')` on the left. The translator is the LAST alternative before the English, + // which is the rightmost call down that spine — a real shape here: an instance may override the + // wording, otherwise the catalogue, and the English only if neither exists. let call = lg.left + while (call?.type === 'LogicalExpression' && (call.operator === '||' || call.operator === '??')) call = call.right if (call?.type === 'CallExpression' && names.has(calleeName(call))) { const k = call.arguments[0] if (k?.type === 'StringLiteral') return k.value @@ -771,6 +776,20 @@ const b = tStatic('dashboard.no_such_key_at_all') || 'This one has no key and mu 'QUIET on a tStatic(key) || English fallback whose key resolves') ok(ts.some((f) => /must still count/.test(f.text)), 'COUNTS a tStatic fallback whose key does not resolve') + // `config || t('key') || 'English'` — an instance may override the wording, else the catalogue, + // else the English. The translator is not the immediate left operand, so the spine is walked. + const CH = ` +` + const ch = scanSfc(CH, 'chain.vue') + ok(!ch.some((f) => /Research map loading/.test(f.text)), + 'QUIET on config || t(key) || English when the key resolves') + ok(ch.some((f) => /no resolving key/.test(f.text)) && ch.some((f) => /no translator at all/.test(f.text)), + 'COUNTS the same chain when the key is missing, and when there is no translator in it') // the catalogue-side rule, tested directly: the two facts that make a fallback safe ok(fallbackFullyResolved('dashboard.map_loading'), 'a key present and really translated in all 11 locales counts as resolved') diff --git a/library/i18n/locales/ar/dashboard.json b/library/i18n/locales/ar/dashboard.json index dbed586a2..9cd50d3a6 100644 --- a/library/i18n/locales/ar/dashboard.json +++ b/library/i18n/locales/ar/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "الخروج من ملء الشاشة", "fs_open": "فتح الجدول بملء الشاشة", "export_selected_hint": "تصدير جميع الحقول — الشعوب المحددة: {n}", - "export_all_hint": "تصدير جميع الحقول — الشعوب: {n}" + "export_all_hint": "تصدير جميع الحقول — الشعوب: {n}", + "map_init_failed": "تعذّر تشغيل الخريطة. تحقّق من رمز الوصول ومن حاوية الخريطة.", + "show_filters": "إظهار المرشّحات", + "hide_filters": "إخفاء المرشّحات — لتوسيع الخريطة", + "show_charts": "إظهار الرسوم", + "hide_charts": "إخفاء الرسوم — لتوسيع الخريطة", + "export_copied": "نُسخ إلى الحافظة", + "export_done": "صُدّر", + "export_ready": "جاهز", + "export_toast": "✓ {rows} صفًّا × {fields} حقلًا {how}", + "export_failed": "✕ فشل التصدير", + "pray_for_group": "صلِّ لأجل هذا الشعب", + "more_information": "مزيد من المعلومات", + "title": "لوحة البحث", + "gospel_pending": "غير جاهز بعد: ما زال رقم غير الموصول إليهم ومفتاح مصدر البيانات وشكل الواجهة قيد التقرير." } diff --git a/library/i18n/locales/ar/search.json b/library/i18n/locales/ar/search.json index 44526b8ac..6df068929 100644 --- a/library/i18n/locales/ar/search.json +++ b/library/i18n/locales/ar/search.json @@ -24,5 +24,11 @@ "allData": "كل بيانات DOXA" }, "upg100Placeholder": "ابحث عن شعب أو قائمة UPG-100 أو منطقة…", + "tabPlaceholder": { + "languageFamilies": "ابحث في عائلة اللغة أو اللغة أو اللهجة/التنوع", + "affinityBlocks": "ابحث في كتلة التقارب أو المجموعة أو الشعب", + "regions": "ابحث في المنطقة أو الدولة أو الشعب", + "religion": "ابحث في الديانة أو الشعب" + }, "placeholderRegions": "ابحث عن منطقة أو دولة أو شعب" } diff --git a/library/i18n/locales/de/dashboard.json b/library/i18n/locales/de/dashboard.json index 217316692..ea804a50e 100644 --- a/library/i18n/locales/de/dashboard.json +++ b/library/i18n/locales/de/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "Vollbild beenden", "fs_open": "Tabelle im Vollbild öffnen", "export_selected_hint": "Alle Felder exportieren — ausgewählte Volksgruppen: {n}", - "export_all_hint": "Alle Felder exportieren — Volksgruppen: {n}" + "export_all_hint": "Alle Felder exportieren — Volksgruppen: {n}", + "map_init_failed": "Die Karte konnte nicht starten. Prüfen Sie das Zugriffstoken und den Kartencontainer.", + "show_filters": "Filter einblenden", + "hide_filters": "Filter ausblenden — Karte vergrößern", + "show_charts": "Diagramme einblenden", + "hide_charts": "Diagramme ausblenden — Karte vergrößern", + "export_copied": "in die Zwischenablage kopiert", + "export_done": "exportiert", + "export_ready": "bereit", + "export_toast": "✓ {rows} Zeilen × {fields} Felder {how}", + "export_failed": "✕ Export fehlgeschlagen", + "pray_for_group": "Für diese Volksgruppe beten", + "more_information": "Weitere Informationen", + "title": "Forschungs-Dashboard", + "gospel_pending": "Noch nicht so weit: die Zahl der Unerreichten, der Schlüssel zur Datenquelle und die Gestaltung werden noch entschieden." } diff --git a/library/i18n/locales/de/search.json b/library/i18n/locales/de/search.json index d609786dc..a6af3edf9 100644 --- a/library/i18n/locales/de/search.json +++ b/library/i18n/locales/de/search.json @@ -24,5 +24,11 @@ "religionKind": "Religion" }, "upg100Placeholder": "Volksgruppe, UPG-100-Liste oder Region suchen…", + "tabPlaceholder": { + "languageFamilies": "Sprachfamilie, Sprache, Dialekt/Varietät suchen", + "affinityBlocks": "Affinitätsblock, Cluster, Volksgruppe suchen", + "regions": "Region, Land, Volksgruppe suchen", + "religion": "Religion, Volksgruppe suchen" + }, "placeholderRegions": "Region, Land oder Volksgruppe suchen" } diff --git a/library/i18n/locales/en/dashboard.json b/library/i18n/locales/en/dashboard.json index 03231a952..bdb577afe 100644 --- a/library/i18n/locales/en/dashboard.json +++ b/library/i18n/locales/en/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "Exit full screen", "fs_open": "Open table full screen", "export_selected_hint": "Export all fields — selected people groups: {n}", - "export_all_hint": "Export all fields — people groups: {n}" + "export_all_hint": "Export all fields — people groups: {n}", + "map_init_failed": "The map could not start. Check the access token and the map container.", + "show_filters": "Show filters", + "hide_filters": "Hide filters — grow the map", + "show_charts": "Show charts", + "hide_charts": "Hide charts — grow the map", + "export_copied": "copied to clipboard", + "export_done": "exported", + "export_ready": "ready", + "export_toast": "✓ {rows} rows × {fields} fields {how}", + "export_failed": "✕ Export failed", + "pray_for_group": "Pray for this people group", + "more_information": "More information", + "title": "Research Dashboard", + "gospel_pending": "Not ready yet: the lostness figure, the data-source key and the layout are still being decided." } diff --git a/library/i18n/locales/en/search.json b/library/i18n/locales/en/search.json index 23ca1427c..1b79016bb 100644 --- a/library/i18n/locales/en/search.json +++ b/library/i18n/locales/en/search.json @@ -24,5 +24,11 @@ "allData": "All DOXA Data" }, "upg100Placeholder": "Search people group, UPG-100 list, or region…", + "tabPlaceholder": { + "languageFamilies": "Search language family, language, dialect/variety", + "affinityBlocks": "Search affinity bloc, cluster, people group", + "regions": "Search region, country, people group", + "religion": "Search religion, people group" + }, "placeholderRegions": "Search region, country, people group" } diff --git a/library/i18n/locales/es/dashboard.json b/library/i18n/locales/es/dashboard.json index 847f01560..d5a970f29 100644 --- a/library/i18n/locales/es/dashboard.json +++ b/library/i18n/locales/es/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "Salir de pantalla completa", "fs_open": "Abrir la tabla a pantalla completa", "export_selected_hint": "Exportar todos los campos — pueblos seleccionados: {n}", - "export_all_hint": "Exportar todos los campos — pueblos: {n}" + "export_all_hint": "Exportar todos los campos — pueblos: {n}", + "map_init_failed": "El mapa no pudo iniciarse. Compruebe el token de acceso y el contenedor del mapa.", + "show_filters": "Mostrar filtros", + "hide_filters": "Ocultar filtros — ampliar el mapa", + "show_charts": "Mostrar gráficos", + "hide_charts": "Ocultar gráficos — ampliar el mapa", + "export_copied": "copiado al portapapeles", + "export_done": "exportado", + "export_ready": "listo", + "export_toast": "✓ {rows} filas × {fields} campos {how}", + "export_failed": "✕ Error al exportar", + "pray_for_group": "Ore por este pueblo", + "more_information": "Más información", + "title": "Panel de investigación", + "gospel_pending": "Aún no está listo: la cifra de no alcanzados, la clave de la fuente de datos y la forma de la interfaz siguen por decidir." } diff --git a/library/i18n/locales/es/search.json b/library/i18n/locales/es/search.json index 6ae264dd8..ac8da59ee 100644 --- a/library/i18n/locales/es/search.json +++ b/library/i18n/locales/es/search.json @@ -24,5 +24,11 @@ "allData": "Todos los datos DOXA" }, "upg100Placeholder": "Buscar pueblo, lista UPG-100 o región…", + "tabPlaceholder": { + "languageFamilies": "Buscar familia lingüística, idioma, dialecto/variedad", + "affinityBlocks": "Buscar bloque de afinidad, clúster, pueblo", + "regions": "Buscar región, país, pueblo", + "religion": "Buscar religión, pueblo" + }, "placeholderRegions": "Buscar región, país o pueblo" } diff --git a/library/i18n/locales/fr/dashboard.json b/library/i18n/locales/fr/dashboard.json index 3bbf5b026..f452bce64 100644 --- a/library/i18n/locales/fr/dashboard.json +++ b/library/i18n/locales/fr/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "Quitter le plein écran", "fs_open": "Ouvrir le tableau en plein écran", "export_selected_hint": "Exporter tous les champs — peuples sélectionnés : {n}", - "export_all_hint": "Exporter tous les champs — peuples : {n}" + "export_all_hint": "Exporter tous les champs — peuples : {n}", + "map_init_failed": "La carte n’a pas pu démarrer. Vérifiez le jeton d’accès et le conteneur de la carte.", + "show_filters": "Afficher les filtres", + "hide_filters": "Masquer les filtres — agrandir la carte", + "show_charts": "Afficher les graphiques", + "hide_charts": "Masquer les graphiques — agrandir la carte", + "export_copied": "copié dans le presse-papiers", + "export_done": "exporté", + "export_ready": "prêt", + "export_toast": "✓ {rows} lignes × {fields} champs {how}", + "export_failed": "✕ Échec de l’export", + "pray_for_group": "Prier pour ce peuple", + "more_information": "Plus d’informations", + "title": "Tableau de bord de recherche", + "gospel_pending": "Pas encore prêt : le chiffre des non atteints, la clé de la source de données et la forme de l’interface restent à décider." } diff --git a/library/i18n/locales/fr/search.json b/library/i18n/locales/fr/search.json index 6fb5283eb..af26ea1d5 100644 --- a/library/i18n/locales/fr/search.json +++ b/library/i18n/locales/fr/search.json @@ -24,5 +24,11 @@ "allData": "Toutes les données DOXA" }, "upg100Placeholder": "Rechercher un peuple, une liste UPG-100 ou une région…", + "tabPlaceholder": { + "languageFamilies": "Rechercher une famille linguistique, une langue, un dialecte/une variété", + "affinityBlocks": "Rechercher un bloc d’affinité, une grappe, un peuple", + "regions": "Rechercher une région, un pays, un peuple", + "religion": "Rechercher une religion, un peuple" + }, "placeholderRegions": "Rechercher une région, un pays, un peuple" } diff --git a/library/i18n/locales/hi/dashboard.json b/library/i18n/locales/hi/dashboard.json index 95ddeb0b2..c52d4e144 100644 --- a/library/i18n/locales/hi/dashboard.json +++ b/library/i18n/locales/hi/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "पूर्ण स्क्रीन से बाहर निकलें", "fs_open": "तालिका को पूर्ण स्क्रीन में खोलें", "export_selected_hint": "सभी फ़ील्ड निर्यात करें — चयनित जनसमूह: {n}", - "export_all_hint": "सभी फ़ील्ड निर्यात करें — जनसमूह: {n}" + "export_all_hint": "सभी फ़ील्ड निर्यात करें — जनसमूह: {n}", + "map_init_failed": "मानचित्र आरंभ नहीं हो सका। एक्सेस टोकन और मानचित्र कंटेनर जाँचें।", + "show_filters": "फ़िल्टर दिखाएँ", + "hide_filters": "फ़िल्टर छिपाएँ — मानचित्र बड़ा करें", + "show_charts": "चार्ट दिखाएँ", + "hide_charts": "चार्ट छिपाएँ — मानचित्र बड़ा करें", + "export_copied": "क्लिपबोर्ड पर कॉपी हुआ", + "export_done": "निर्यात हुआ", + "export_ready": "तैयार", + "export_toast": "✓ {rows} पंक्तियाँ × {fields} क्षेत्र {how}", + "export_failed": "✕ निर्यात विफल", + "pray_for_group": "इस जनसमूह के लिए प्रार्थना करें", + "more_information": "अधिक जानकारी", + "title": "शोध डैशबोर्ड", + "gospel_pending": "अभी तैयार नहीं: अनपहुँचे लोगों का आँकड़ा, डेटा स्रोत की कुंजी और अंतरफलक का रूप अभी तय हो रहे हैं।" } diff --git a/library/i18n/locales/hi/search.json b/library/i18n/locales/hi/search.json index 8161c951f..5af971919 100644 --- a/library/i18n/locales/hi/search.json +++ b/library/i18n/locales/hi/search.json @@ -24,5 +24,11 @@ "religionKind": "धर्म" }, "upg100Placeholder": "जनसमूह, UPG-100 सूची या क्षेत्र खोजें…", + "tabPlaceholder": { + "languageFamilies": "भाषा परिवार, भाषा, बोली/रूप खोजें", + "affinityBlocks": "समानता समूह, क्लस्टर, जनसमूह खोजें", + "regions": "क्षेत्र, देश, जनसमूह खोजें", + "religion": "धर्म, जनसमूह खोजें" + }, "placeholderRegions": "क्षेत्र, देश या जनसमूह खोजें" } diff --git a/library/i18n/locales/it/dashboard.json b/library/i18n/locales/it/dashboard.json index 04ded77f2..1abb8929d 100644 --- a/library/i18n/locales/it/dashboard.json +++ b/library/i18n/locales/it/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "Esci da schermo intero", "fs_open": "Apri la tabella a schermo intero", "export_selected_hint": "Esporta tutti i campi — popoli selezionati: {n}", - "export_all_hint": "Esporta tutti i campi — popoli: {n}" + "export_all_hint": "Esporta tutti i campi — popoli: {n}", + "map_init_failed": "La mappa non è riuscita ad avviarsi. Controlla il token di accesso e il contenitore della mappa.", + "show_filters": "Mostra i filtri", + "hide_filters": "Nascondi i filtri — ingrandisci la mappa", + "show_charts": "Mostra i grafici", + "hide_charts": "Nascondi i grafici — ingrandisci la mappa", + "export_copied": "copiato negli appunti", + "export_done": "esportato", + "export_ready": "pronto", + "export_toast": "✓ {rows} righe × {fields} campi {how}", + "export_failed": "✕ Esportazione non riuscita", + "pray_for_group": "Prega per questo popolo", + "more_information": "Maggiori informazioni", + "title": "Cruscotto di ricerca", + "gospel_pending": "Non ancora pronto: il numero dei non raggiunti, la chiave della fonte dati e la forma dell’interfaccia sono ancora da decidere." } diff --git a/library/i18n/locales/it/search.json b/library/i18n/locales/it/search.json index e45e4c159..165fdfee0 100644 --- a/library/i18n/locales/it/search.json +++ b/library/i18n/locales/it/search.json @@ -24,5 +24,11 @@ "religionKind": "Religione" }, "upg100Placeholder": "Cerca popolo, elenco UPG-100 o regione…", + "tabPlaceholder": { + "languageFamilies": "Cerca famiglia linguistica, lingua, dialetto/variante", + "affinityBlocks": "Cerca blocco di affinità, cluster, popolo", + "regions": "Cerca regione, paese, popolo", + "religion": "Cerca religione, popolo" + }, "placeholderRegions": "Cerca regione, paese o popolo" } diff --git a/library/i18n/locales/pt/dashboard.json b/library/i18n/locales/pt/dashboard.json index 7d3e099a9..e09f6169c 100644 --- a/library/i18n/locales/pt/dashboard.json +++ b/library/i18n/locales/pt/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "Sair da tela cheia", "fs_open": "Abrir a tabela em tela cheia", "export_selected_hint": "Exportar todos os campos — povos selecionados: {n}", - "export_all_hint": "Exportar todos os campos — povos: {n}" + "export_all_hint": "Exportar todos os campos — povos: {n}", + "map_init_failed": "O mapa não conseguiu iniciar. Verifique o token de acesso e o contentor do mapa.", + "show_filters": "Mostrar filtros", + "hide_filters": "Ocultar filtros — ampliar o mapa", + "show_charts": "Mostrar gráficos", + "hide_charts": "Ocultar gráficos — ampliar o mapa", + "export_copied": "copiado para a área de transferência", + "export_done": "exportado", + "export_ready": "pronto", + "export_toast": "✓ {rows} linhas × {fields} campos {how}", + "export_failed": "✕ Falha ao exportar", + "pray_for_group": "Ore por este povo", + "more_information": "Mais informações", + "title": "Painel de pesquisa", + "gospel_pending": "Ainda não está pronto: o número de não alcançados, a chave da fonte de dados e a forma da interface estão por decidir." } diff --git a/library/i18n/locales/pt/search.json b/library/i18n/locales/pt/search.json index 8accd6dbb..b8dcbecb8 100644 --- a/library/i18n/locales/pt/search.json +++ b/library/i18n/locales/pt/search.json @@ -24,5 +24,11 @@ "allData": "Todos os dados DOXA" }, "upg100Placeholder": "Procurar povo, lista UPG-100 ou região…", + "tabPlaceholder": { + "languageFamilies": "Pesquisar família linguística, idioma, dialeto/variedade", + "affinityBlocks": "Pesquisar bloco de afinidade, agrupamento, povo", + "regions": "Pesquisar região, país, povo", + "religion": "Pesquisar religião, povo" + }, "placeholderRegions": "Procurar região, país ou povo" } diff --git a/library/i18n/locales/ro/dashboard.json b/library/i18n/locales/ro/dashboard.json index 4e965da62..b768298bd 100644 --- a/library/i18n/locales/ro/dashboard.json +++ b/library/i18n/locales/ro/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "Ieși din ecran complet", "fs_open": "Deschide tabelul pe ecran complet", "export_selected_hint": "Exportă toate câmpurile — popoare selectate: {n}", - "export_all_hint": "Exportă toate câmpurile — popoare: {n}" + "export_all_hint": "Exportă toate câmpurile — popoare: {n}", + "map_init_failed": "Harta nu a putut porni. Verificați jetonul de acces și containerul hărții.", + "show_filters": "Arată filtrele", + "hide_filters": "Ascunde filtrele — mărește harta", + "show_charts": "Arată graficele", + "hide_charts": "Ascunde graficele — mărește harta", + "export_copied": "copiat în clipboard", + "export_done": "exportat", + "export_ready": "gata", + "export_toast": "✓ {rows} rânduri × {fields} câmpuri {how}", + "export_failed": "✕ Exportul a eșuat", + "pray_for_group": "Roagă-te pentru acest popor", + "more_information": "Mai multe informații", + "title": "Tablou de bord de cercetare", + "gospel_pending": "Încă nu este gata: cifra celor neatinși, cheia sursei de date și forma interfeței sunt încă de hotărât." } diff --git a/library/i18n/locales/ro/search.json b/library/i18n/locales/ro/search.json index 380c4f024..33b41cc89 100644 --- a/library/i18n/locales/ro/search.json +++ b/library/i18n/locales/ro/search.json @@ -24,5 +24,11 @@ "religionKind": "Religie" }, "upg100Placeholder": "Caută popor, listă UPG-100 sau regiune…", + "tabPlaceholder": { + "languageFamilies": "Caută familie de limbi, limbă, dialect/variantă", + "affinityBlocks": "Caută bloc de afinitate, grup, popor", + "regions": "Caută regiune, țară, popor", + "religion": "Caută religie, popor" + }, "placeholderRegions": "Caută regiune, țară sau popor" } diff --git a/library/i18n/locales/ru/dashboard.json b/library/i18n/locales/ru/dashboard.json index be0d8e332..475c541ee 100644 --- a/library/i18n/locales/ru/dashboard.json +++ b/library/i18n/locales/ru/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "Выйти из полноэкранного режима", "fs_open": "Открыть таблицу на весь экран", "export_selected_hint": "Экспорт всех полей — выбрано народов: {n}", - "export_all_hint": "Экспорт всех полей — народов: {n}" + "export_all_hint": "Экспорт всех полей — народов: {n}", + "map_init_failed": "Карта не запустилась. Проверьте токен доступа и контейнер карты.", + "show_filters": "Показать фильтры", + "hide_filters": "Скрыть фильтры — расширить карту", + "show_charts": "Показать диаграммы", + "hide_charts": "Скрыть диаграммы — расширить карту", + "export_copied": "скопировано в буфер обмена", + "export_done": "экспортировано", + "export_ready": "готово", + "export_toast": "✓ {rows} строк × {fields} полей {how}", + "export_failed": "✕ Не удалось экспортировать", + "pray_for_group": "Молиться об этом народе", + "more_information": "Подробнее", + "title": "Исследовательская панель", + "gospel_pending": "Пока не готово: число непросвещённых, ключ к источнику данных и вид интерфейса ещё определяются." } diff --git a/library/i18n/locales/ru/search.json b/library/i18n/locales/ru/search.json index aaf424e70..c1475c794 100644 --- a/library/i18n/locales/ru/search.json +++ b/library/i18n/locales/ru/search.json @@ -24,5 +24,11 @@ "allData": "Все данные DOXA" }, "upg100Placeholder": "Поиск народа, списка UPG-100 или региона…", + "tabPlaceholder": { + "languageFamilies": "Поиск по языковой семье, языку, диалекту/варианту", + "affinityBlocks": "Поиск по блоку родства, кластеру, народу", + "regions": "Поиск по региону, стране, народу", + "religion": "Поиск по религии, народу" + }, "placeholderRegions": "Поиск региона, страны или народа" } diff --git a/library/i18n/locales/zh/dashboard.json b/library/i18n/locales/zh/dashboard.json index 6b80317a4..6b62debde 100644 --- a/library/i18n/locales/zh/dashboard.json +++ b/library/i18n/locales/zh/dashboard.json @@ -94,5 +94,19 @@ "fs_exit": "退出全屏", "fs_open": "全屏打开表格", "export_selected_hint": "导出全部字段 — 已选民族:{n}", - "export_all_hint": "导出全部字段 — 民族:{n}" + "export_all_hint": "导出全部字段 — 民族:{n}", + "map_init_failed": "地图无法启动。请检查访问令牌与地图容器。", + "show_filters": "显示筛选", + "hide_filters": "隐藏筛选 —— 放大地图", + "show_charts": "显示图表", + "hide_charts": "隐藏图表 —— 放大地图", + "export_copied": "已复制到剪贴板", + "export_done": "已导出", + "export_ready": "已就绪", + "export_toast": "✓ {rows} 行 × {fields} 字段 {how}", + "export_failed": "✕ 导出失败", + "pray_for_group": "为这个民族祷告", + "more_information": "更多信息", + "title": "研究面板", + "gospel_pending": "尚未就绪:未得之民的数字、数据来源密钥与界面形式仍在确定中。" } diff --git a/library/i18n/locales/zh/search.json b/library/i18n/locales/zh/search.json index 870923afa..83a48efa9 100644 --- a/library/i18n/locales/zh/search.json +++ b/library/i18n/locales/zh/search.json @@ -24,5 +24,11 @@ "religionKind": "宗教" }, "upg100Placeholder": "搜索民族、UPG-100 名单或区域…", + "tabPlaceholder": { + "languageFamilies": "搜索语系、语言、方言/变体", + "affinityBlocks": "搜索亲缘板块、民族群、民族", + "regions": "搜索区域、国家、民族", + "religion": "搜索宗教、民族" + }, "placeholderRegions": "搜索区域、国家或族群" } diff --git a/plugin-designs/doxa-research-map/research-map.vue b/plugin-designs/doxa-research-map/research-map.vue index dc4d9cbdf..931566b56 100644 --- a/plugin-designs/doxa-research-map/research-map.vue +++ b/plugin-designs/doxa-research-map/research-map.vue @@ -606,23 +606,19 @@ watch(() => activeTab.value?.colorStrategy, (strategy) => mapTheme.setLook(mapTh const activeLegendType = computed(() => activeTab.value?.legend ?? 'default') const activePopupAction = computed(() => activeTab.value?.popup ?? 'pray') -// Per-tab geocoder placeholder. The Language Families tab makes the search -// scope explicit ("Search language family, language, dialect/variety") so users -// know the search is wired to the legend's hierarchy. Other tabs use the -// default i18n placeholder ("Search people, place, language, religion"). +// Per-tab geocoder placeholder. The four tiered tabs name their own tiers, so the reader can see +// the search is wired to the legend's hierarchy rather than to the map at large; the flat tabs fall +// through to the default placeholder. Empty string means "no opinion" — GeocoderComponent then uses +// its own i18n default, which is why an unresolved key returns '' rather than English. +const GEOCODER_PLACEHOLDER_KEY = { + 'language-families': 'search.tabPlaceholder.languageFamilies', + 'affinity-blocks': 'search.tabPlaceholder.affinityBlocks', + 'doxa-regions': 'search.tabPlaceholder.regions', + 'religion': 'search.tabPlaceholder.religion', +} const geocoderPlaceholder = computed(() => { - if (activeTab.value?.id === 'language-families') { - return 'Search language family, language, dialect/variety' - } - if (activeTab.value?.id === 'affinity-blocks') { - return 'Search affinity bloc, cluster, people group' - } - if (activeTab.value?.id === 'doxa-regions') { - return 'Search region, country, people group' - } - if (activeTab.value?.id === 'religion') { - return 'Search religion, people group' - } + const key = GEOCODER_PLACEHOLDER_KEY[activeTab.value?.id] + if (key && te(key)) return t(key) return '' // empty → GeocoderComponent falls back to i18n default }) @@ -3103,8 +3099,10 @@ onMounted(async () => { if (!map.value) { // The code has already concluded the map is dead — say so instead of leaving // the loading line up, which is indistinguishable from "still loading". + // The console line stays English: it is for whoever is debugging. What the READER sees comes + // from the catalogue, and says what to do rather than naming the internals that failed. console.error('[research-map] Map failed to initialize — check token and container') - mapError.value = 'Map failed to initialize — check token and container' + mapError.value = t('dashboard.map_init_failed') return } map.value.once('style.load', async () => { diff --git a/plugin-designs/research-dashboard/research-dashboard.vue b/plugin-designs/research-dashboard/research-dashboard.vue index 073370bcb..c4c18b3bb 100644 --- a/plugin-designs/research-dashboard/research-dashboard.vue +++ b/plugin-designs/research-dashboard/research-dashboard.vue @@ -817,10 +817,10 @@ onMounted(() => { diff --git a/plugin-designs/research-dashboard/src/components/dashboards/DashboardChartsRail.vue b/plugin-designs/research-dashboard/src/components/dashboards/DashboardChartsRail.vue index 831e16ad1..49c8f414d 100644 --- a/plugin-designs/research-dashboard/src/components/dashboards/DashboardChartsRail.vue +++ b/plugin-designs/research-dashboard/src/components/dashboards/DashboardChartsRail.vue @@ -40,7 +40,7 @@ const tabs = [ id: 'gospel', get label() { return tStatic('dashboard.gospel_resources') || 'Gospel Resources' }, disabled: true, - why: 'Waiting on three decisions: the official lostness number, key-less vs a registered Joshua Project key, and the UI shape. See IDEA-GOSPEL-RESOURCES-INTEGRATION-STRATEGY.', + get why() { return tStatic('dashboard.gospel_pending') || 'Not ready yet: the lostness figure, the data-source key and the layout are still being decided.' }, }, ] const activeTab = ref('charts') diff --git a/plugin-designs/research-dashboard/src/components/dashboards/DashboardDataTable.vue b/plugin-designs/research-dashboard/src/components/dashboards/DashboardDataTable.vue index 74fdb83f8..db65406e6 100644 --- a/plugin-designs/research-dashboard/src/components/dashboards/DashboardDataTable.vue +++ b/plugin-designs/research-dashboard/src/components/dashboards/DashboardDataTable.vue @@ -18,6 +18,7 @@ import { useCsvExport } from '@map/composables/useCsvExport.js' import { formatCount } from '@map/utils/formatCount.js' import { useTreeLegendLabels } from '../../composables/useTreeLegendLabels.js' import { tStatic } from '@map/i18n/index.js' +import { fillParams } from '@map/composables/useOptionalT.js' // This table's chrome, read out of the catalogue. tStatic and not useI18n, for the reason the rest // of this tree gives: these components mount inside custom elements that install no i18n plugin, @@ -561,10 +562,16 @@ async function onExport() { exporting.value = true try { const r = await csv.exportCsvAsync() - const how = r.downloaded ? 'exported' : r.clipboard ? 'copied to clipboard' : 'ready' - exportToast.value = `✓ ${fmt(r.rows)} rows × ${r.fields} fields ${how}` + // The counts keep going through fmt(), which groups digits the way the locale does; only the + // words around them come from the catalogue. Interpolated rather than concatenated, because the + // order of "rows", "fields" and the outcome is not the same in every language. + const how = r.downloaded ? T('dashboard.export_done', 'exported') + : r.clipboard ? T('dashboard.export_copied', 'copied to clipboard') + : T('dashboard.export_ready', 'ready') + exportToast.value = fillParams(T('dashboard.export_toast', '✓ {rows} rows × {fields} fields {how}'), + { rows: fmt(r.rows), fields: r.fields, how }) } catch (e) { - exportToast.value = '✕ Export failed' + exportToast.value = T('dashboard.export_failed', '✕ Export failed') } finally { exporting.value = false clearTimeout(_toastT); _toastT = setTimeout(() => { exportToast.value = '' }, 3200) @@ -805,7 +812,7 @@ useShadowStyles(` bus and any future caller. This also deletes the one control in the cluster that read as "a button that doesn't make sense" -- a bare x with no label. --> - {{ fmt(selCount) }} selected + {{ fmt(selCount) }} {{ T('dashboard.selected', 'Selected') }} diff --git a/plugin-designs/research-dashboard/src/components/dashboards/DashboardDetailDrawer.vue b/plugin-designs/research-dashboard/src/components/dashboards/DashboardDetailDrawer.vue index 69eb98d3f..4703f5f58 100644 --- a/plugin-designs/research-dashboard/src/components/dashboards/DashboardDetailDrawer.vue +++ b/plugin-designs/research-dashboard/src/components/dashboards/DashboardDetailDrawer.vue @@ -96,8 +96,8 @@ const moreInfoHref = computed(() => { const def = `${origin}/research/${encodeURIComponent(r.slug || '')}/?utm_source=${utm}` return resolveUrl(detailCfg.value.moreInfoUrl, r, def) }) -const prayLabel = computed(() => detailCfg.value.prayLabel || 'Pray for this people group') -const moreInfoLabel = computed(() => detailCfg.value.moreInfoLabel || 'More information') +const prayLabel = computed(() => detailCfg.value.prayLabel || T('dashboard.pray_for_group', 'Pray for this people group')) +const moreInfoLabel = computed(() => detailCfg.value.moreInfoLabel || T('dashboard.more_information', 'More information')) function exportThis() { if (!exporter || !bus) return diff --git a/plugin-designs/research-dashboard/src/components/dashboards/DashboardResearchFilters.vue b/plugin-designs/research-dashboard/src/components/dashboards/DashboardResearchFilters.vue index f22c83e3e..18f8f854f 100644 --- a/plugin-designs/research-dashboard/src/components/dashboards/DashboardResearchFilters.vue +++ b/plugin-designs/research-dashboard/src/components/dashboards/DashboardResearchFilters.vue @@ -111,7 +111,7 @@ const dropdownKeys = computed(() => { const configured = rf.value.dropdowns || ['wagf_region', 'wagf_block', 'country_code', 'people_group', 'religion'] return [PRAYER_KEY, ...configured.filter((k) => k !== PRAYER_KEY)] }) -const searchPlaceholder = computed(() => rf.value.searchPlaceholder || 'Search people groups, place, language, religion…') +const searchPlaceholder = computed(() => rf.value.searchPlaceholder || tStatic('dashboard.search_placeholder') || 'Search people groups, place, language, religion…') // Apply a filter-set (facetKey -> label) to a row list — reused for cross-filtered counts. function applyWith(fset, list) { diff --git a/plugin-designs/research-dashboard/src/components/dashboards/DashboardTopBar.vue b/plugin-designs/research-dashboard/src/components/dashboards/DashboardTopBar.vue index d3a69c205..ad2d8401c 100644 --- a/plugin-designs/research-dashboard/src/components/dashboards/DashboardTopBar.vue +++ b/plugin-designs/research-dashboard/src/components/dashboards/DashboardTopBar.vue @@ -119,7 +119,9 @@ function onAlignLayout() { if (bus) bus.emit('resetLayout') } // The value the panels honor lives on the bus; this bar reads/writes it via bus.searchTerm / // bus.setSearch, so it is the SAME free-text search the whole surface re-derives against. const filterSearch = computed(() => bus?.searchTerm?.value || '') -const filterSearchPlaceholder = computed(() => cfg.researchFilters?.searchPlaceholder || 'Search people groups, place, language, religion…') +// The config's own wording wins when an instance sets one; otherwise the catalogue, not an English +// constant — dashboard.search_placeholder is the same sentence, already translated everywhere. +const filterSearchPlaceholder = computed(() => cfg.researchFilters?.searchPlaceholder || L('search_placeholder', 'Search people groups, place, language, religion…')) function onFilterSearch(payload) { bus?.setSearch(payload?.value ?? '') } // ── live typeahead search over the in-memory row store ── @@ -304,7 +306,7 @@ useShadowStyles(` cfg.title alone would run under it. It costs nothing and it is the guard that fails safe if anyone re-adds a variable-width member here. -->
- {{ cfg.title || 'Research Dashboard' }}. + {{ cfg.title || L('title', 'Research Dashboard') }}.