Compare commits

...

1 commit

Author SHA1 Message Date
Doxa Maps
6ded4e8439 My UPG Neighbors: the lenses' chrome speaks the reader's language
The universal-props matrix flagged upg-neighbor as a locale BYPASS: no t() in any of its ten
components. By the agreed scope (dx-shepherd, 2026-09-26), this localizes the CHROME in all 11 locales
through a new neighbor area (103 keys): the brand, view tabs and hints, lens titles, search
placeholders, select/aria labels, loading and error lines, legends, stat labels, tooltips, chart-strip
controls, list actions and copy feedback. Research terms, provenance footers and proper nouns stay in
English as named sources: SEND, doxa-2000, Bounded set, JP, PPLR, IN/CN, the 4-D join notes.
Templates use the globally injected $t; script strings use useNeighborT() (guarded, English fallback).
Stat lines use a "label: n" layout in every locale so no language needs plural rules. Country names
in the lookup tables are data and unchanged.
check:i18n-parity PASS; every locale's placeholders match English; the bundle builds.
A library catalog change: studio lands it (rule 5B).
needs eyes: My UPG Neighbors with ?lang=ar: the view tabs, the Send Map legend and the Overview chart strip.
2026-09-26 01:12:21 +00:00
22 changed files with 1496 additions and 91 deletions

View file

@ -0,0 +1,125 @@
{
"brand": "جيراني من الشعوب غير المُبشَّرة",
"chooseView": "اختر عرضًا",
"views": {
"cockpit": "نظرة عامة",
"sendMap": "خريطة الإرسال",
"flowerMesh": "زهرة الشبكة",
"langCluster": "عناقيد اللغات",
"flowers": "الزهور"
},
"hints": {
"cockpit": "مركز التصفية المتقاطعة",
"sendMap": "أقواس من المُبشَّر إلى غير المُبشَّر",
"flowerMesh": "تفتّح شعاعي",
"langCluster": "مجموعات لغوية محددة",
"flowers": "عائلة · لغة · لهجة، مع بتلات تفصيلية"
},
"title": {
"overview": "جيراني · نظرة عامة",
"send": "جيراني · SEND",
"langClusters": "جيراني · عناقيد اللغات",
"flowerMesh": "جيراني · زهرة الشبكة"
},
"search": "بحث",
"searchCockpit": "ابحث عن هدف / مُرسِل / لغة…",
"searchStrip": "ابحث عن هدف / لغة / مُرسِل…",
"searchBrush": "بحث وتحديد",
"remove": "إزالة {label}",
"selectTarget": "اختر دولة الهدف غير المُبشَّر",
"loadingSend": "جارٍ تحميل خريطة SEND…",
"send": {
"unreachedTarget": "هدف غير مُبشَّر —",
"clickOne": "انقر واحدًا",
"toFocus": "للتركيز عليه",
"reachedSender": "مُرسِل مُبشَّر (SEND →)"
},
"pctChristian": "مسيحيون: {n}%",
"groups": "الشعوب: {n}",
"people": "السكان",
"unreached": "غير مُبشَّر",
"reached": "مُبشَّر",
"unknown": "غير معروف",
"lc": {
"loading": "جارٍ تحميل الشعوب…",
"apiDown": "واجهة doxa المباشرة غير متاحة: {message}",
"clusters": "عناقيد لغوية",
"pgs": "غير مُبشَّرة",
"provinces": "مقاطعات IN/CN",
"withReach": "لها مطابقة مُبشَّرة",
"legendCluster": "عنقود لغوي غير مُبشَّر (الحجم = عدد الشعوب)",
"legendReached": "جار مُبشَّر (مطابق باللغة)",
"legendHelp": "حلقة خضراء = للعنقود مطابقة مُبشَّرة · انقر عنقودًا للتعمّق",
"setCount": "Bounded set — شعوب غير مُبشَّرة: {n}",
"more": "+{n} أخرى",
"reachedSame": "جيران مُبشَّرون (اللغة نفسها): {n}",
"frontier": "لا يشترك أي شعب مُبشَّر في هذه اللغة ضمن هذه البيانات. إنها جبهة جديدة: صلِّ من أجل الفعلة (لوقا 10:2).",
"openFlower": "فتح عرض الزهرة ▸"
},
"fm": {
"languageMesh": "شبكة اللغة",
"reachedNeighbors": "جيران مُبشَّرون",
"countries": "دول",
"upgsInCenter": "UPG في المركز",
"meshLinks": "روابط الشبكة",
"growing": "تنمو الزهرة…",
"upgs": "UPG: {n}",
"people": "السكان: {n}",
"legendTitle": "الزهرة",
"unreachedTarget": "هدف غير مُبشَّر (doxa-2000)",
"reachedDeeper": "جار مُبشَّر (الأغمق = مسيحيون أكثر)",
"sendPath": "مسار SEND ← إلى غير المُبشَّرين",
"viaCluster": "عبر عنقود لغوي قريب",
"mesh": "شبكة تعاون باللغة نفسها",
"whorl": "الدائرة الداخلية = الدولة نفسها · البتلات = الدول المحيطة",
"loadError": "تعذّر تحميل بيانات الجيران المُبشَّرين"
},
"strip": {
"lens": "العدسة",
"lensAria": "عدسة اللغة",
"noLens": "— بلا عدسة —",
"color": "اللون",
"colorAria": "لوّن المُرسِلين المُبشَّرين حسب",
"pctReached": "% مُبشَّر",
"religion": "الدين",
"senderLanguage": "لغة المُرسِل",
"senderCountry": "دولة المُرسِل",
"clear": "مسح",
"noSenders": "لا مُرسِلين",
"byLanguage": "المُرسِلون حسب اللغة",
"byCountry": "المُرسِلون حسب الدولة",
"quantile": "% مُبشَّر (الشرائح)",
"histogram": "مدرّج نسبة المُبشَّرين",
"religionFamily": "عائلة الدين",
"bridgeType": "نوع الجسر",
"donut": "حلقة حسب الفئة"
},
"list": {
"hidden": "· مخفيّة بالمرشحات: {n}",
"clear": "مسح",
"copy": "نسخ",
"csvDownloaded": "تم تنزيل CSV ({n})",
"copied": "تم النسخ ({n})",
"copyFailed": "فشل النسخ — اضغط Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "التجميع حسب",
"flowers": "زهور",
"unreached": "غير مُبشَّرة",
"legendTitle": "عناقيد الزهور",
"legendCore": "عنقود لغوي غير مُبشَّر (نواة، الحجم = عدد الشعوب)",
"legendPetal": "جار مُبشَّر (تفصيلي، إحداثيات حقيقية)",
"legendHelp": "انقر دائرة عليا لإعادة التجميع · انقر نواة لتتفتح بتلاتها",
"clustering": "جارٍ تجميع الحصاد حسب اللغة…",
"signLanguage": "لغة الإشارة"
},
"mc": {
"greenEncoding": "ترميز اللون الأخضر",
"provinces": "مقاطعات IN/CN",
"provincesTitle": "إظهار حدود المقاطعات في IN/CN",
"resetFocus": "✕ إلغاء التركيز",
"needsToken": "تحتاج الخريطة إلى رمز Mapbox (tk في profile-config)…",
"rendering": "جارٍ رسم حقل الحصاد…"
},
"bloom": "تفتّح"
}

View file

@ -0,0 +1,125 @@
{
"brand": "Meine UPG-Nachbarn",
"chooseView": "Ansicht wählen",
"views": {
"cockpit": "Überblick",
"sendMap": "Sendekarte",
"flowerMesh": "Blütennetz",
"langCluster": "Sprachcluster",
"flowers": "Blüten"
},
"hints": {
"cockpit": "Crossfilter-Zentrale",
"sendMap": "Bögen von erreicht zu unerreicht",
"flowerMesh": "radiale Blüte",
"langCluster": "sprachlich begrenzte Mengen",
"flowers": "Familie · Sprache · Dialekt als Kerne, feine Blütenblätter"
},
"title": {
"overview": "Meine UPG-Nachbarn · Überblick",
"send": "Meine UPG-Nachbarn · SEND",
"langClusters": "Meine UPG-Nachbarn · Sprachcluster",
"flowerMesh": "Meine UPG-Nachbarn · Blütennetz"
},
"search": "Suchen",
"searchCockpit": "Ziel / Sender / Sprache suchen…",
"searchStrip": "Ziel / Sprache / Sender suchen…",
"searchBrush": "Suchen und auswählen",
"remove": "{label} entfernen",
"selectTarget": "Unerreichtes Zielland wählen",
"loadingSend": "SEND-Karte wird geladen…",
"send": {
"unreachedTarget": "Unerreichtes Ziel —",
"clickOne": "eines anklicken",
"toFocus": "zum Fokussieren",
"reachedSender": "Erreichter Sender (SEND →)"
},
"pctChristian": "{n} % Christen",
"groups": "Gruppen: {n}",
"people": "Menschen",
"unreached": "unerreicht",
"reached": "erreicht",
"unknown": "Unbekannt",
"lc": {
"loading": "Volksgruppen werden geladen…",
"apiDown": "Die Live-API von doxa ist nicht erreichbar: {message}",
"clusters": "Sprachcluster",
"pgs": "unerreicht",
"provinces": "IN/CN-Provinzen",
"withReach": "mit erreichter Entsprechung",
"legendCluster": "Unerreichter Sprachcluster (Größe = Anzahl Gruppen)",
"legendReached": "Erreichter Nachbar (über die Sprache zugeordnet)",
"legendHelp": "grüner Ring = Cluster hat eine erreichte Entsprechung · Cluster anklicken zum Vertiefen",
"setCount": "Bounded set — unerreichte Gruppen: {n}",
"more": "+{n} weitere",
"reachedSame": "Erreichte Nachbarn (gleiche Sprache): {n}",
"frontier": "Keine erreichte Volksgruppe teilt diese Sprache in diesem Datensatz. Eine Grenze: betet um Arbeiter (Lukas 10,2).",
"openFlower": "Blütenansicht öffnen ▸"
},
"fm": {
"languageMesh": "Sprachnetz",
"reachedNeighbors": "erreichte Nachbarn",
"countries": "Länder",
"upgsInCenter": "UPGs im Zentrum",
"meshLinks": "Netzverbindungen",
"growing": "Die Blüte wächst…",
"upgs": "UPGs: {n}",
"people": "Menschen: {n}",
"legendTitle": "Die Blüte",
"unreachedTarget": "Unerreichtes Ziel (doxa-2000)",
"reachedDeeper": "Erreichter Nachbar (dunkler = mehr Christen)",
"sendPath": "SEND-Pfad → zu den Unerreichten",
"viaCluster": "über einen verwandten Sprachcluster",
"mesh": "Zusammenarbeitsnetz gleicher Sprache",
"whorl": "innerer Kreis = gleiches Land · Blütenblätter = Nachbarländer",
"loadError": "Die Daten der erreichten Nachbarn konnten nicht geladen werden"
},
"strip": {
"lens": "Linse",
"lensAria": "Sprachlinse",
"noLens": "— keine Linse —",
"color": "Farbe",
"colorAria": "Erreichte Sender färben nach",
"pctReached": "% erreicht",
"religion": "Religion",
"senderLanguage": "Sprache des Senders",
"senderCountry": "Land des Senders",
"clear": "löschen",
"noSenders": "keine Sender",
"byLanguage": "Sender nach Sprache",
"byCountry": "Sender nach Land",
"quantile": "% erreicht (Quantile)",
"histogram": "Histogramm des Erreichtheitsanteils",
"religionFamily": "Religionsfamilie",
"bridgeType": "Brückentyp",
"donut": "Ring nach Kategorie"
},
"list": {
"hidden": "· durch Filter ausgeblendet: {n}",
"clear": "Löschen",
"copy": "Kopieren",
"csvDownloaded": "CSV heruntergeladen ({n})",
"copied": "Kopiert ({n})",
"copyFailed": "Kopieren fehlgeschlagen – Strg/Cmd-C drücken"
},
"fc": {
"clusterBy": "Gruppieren nach",
"flowers": "Blüten",
"unreached": "unerreicht",
"legendTitle": "Blütencluster",
"legendCore": "Unerreichter Sprachcluster (Kern, Größe = Anzahl Gruppen)",
"legendPetal": "Erreichter Nachbar (fein, echte Koordinaten)",
"legendHelp": "oberen Kreis anklicken zum Neugruppieren · Kern anklicken, um die Blütenblätter zu öffnen",
"clustering": "Die Ernte wird nach Sprache gruppiert…",
"signLanguage": "Gebärdensprache"
},
"mc": {
"greenEncoding": "Kodierung der Grünfarbe",
"provinces": "IN/CN-Provinzen",
"provincesTitle": "Provinzgrenzen von IN/CN einblenden",
"resetFocus": "✕ Fokus zurücksetzen",
"needsToken": "Die Karte braucht einen Mapbox-Token (tk in profile-config)…",
"rendering": "Das Erntefeld wird gezeichnet…"
},
"bloom": "Blüte"
}

View file

@ -0,0 +1,125 @@
{
"brand": "My UPG Neighbors",
"chooseView": "Choose a view",
"views": {
"cockpit": "Overview",
"sendMap": "Send Map",
"flowerMesh": "Flower Mesh",
"langCluster": "Language Clusters",
"flowers": "Flowers"
},
"hints": {
"cockpit": "crossfilter hub",
"sendMap": "reached → unreached arcs",
"flowerMesh": "radial bloom",
"langCluster": "language bounded sets",
"flowers": "family · language · dialect cores + granular petals"
},
"title": {
"overview": "My UPG Neighbor · Overview",
"send": "My UPG Neighbor · SEND",
"langClusters": "My UPG Neighbor · Language Clusters",
"flowerMesh": "My UPG Neighbor · Flower-Mesh"
},
"search": "Search",
"searchCockpit": "Search target / sender / language…",
"searchStrip": "Search target / language / sender…",
"searchBrush": "Search and brush",
"remove": "remove {label}",
"selectTarget": "Select unreached target country",
"loadingSend": "Loading SEND map…",
"send": {
"unreachedTarget": "Unreached target —",
"clickOne": "click one",
"toFocus": "to focus it",
"reachedSender": "Reached sender (SEND →)"
},
"pctChristian": "{n}% Christian",
"groups": "{n} groups",
"people": "people",
"unreached": "unreached",
"reached": "reached",
"unknown": "Unknown",
"lc": {
"loading": "Loading people groups…",
"apiDown": "The live doxa API is unreachable: {message}",
"clusters": "language clusters",
"pgs": "unreached",
"provinces": "IN/CN provinces",
"withReach": "with a reached match",
"legendCluster": "Unreached language cluster (size = number of groups)",
"legendReached": "Reached neighbor (matched by language)",
"legendHelp": "green ring = the cluster has a reached match · click a cluster to drill in",
"setCount": "Bounded set — unreached groups: {n}",
"more": "+{n} more",
"reachedSame": "Reached neighbors (same language): {n}",
"frontier": "No reached people group shares this language in this data set. A frontier: pray for laborers (Luke 10:2).",
"openFlower": "Open flower view ▸"
},
"fm": {
"languageMesh": "language mesh",
"reachedNeighbors": "reached neighbors",
"countries": "countries",
"upgsInCenter": "UPGs in the center",
"meshLinks": "mesh links",
"growing": "Growing the flower…",
"upgs": "{n} UPGs",
"people": "{n} people",
"legendTitle": "The flower",
"unreachedTarget": "Unreached target (doxa-2000)",
"reachedDeeper": "Reached neighbor (deeper = more Christian)",
"sendPath": "SEND path → into the unreached",
"viaCluster": "via a related language cluster",
"mesh": "Same-language collaboration mesh",
"whorl": "inner whorl = same country · petals = surrounding countries",
"loadError": "Could not load the reached-neighbor data"
},
"strip": {
"lens": "Lens",
"lensAria": "Language lens",
"noLens": "— no lens —",
"color": "Color",
"colorAria": "Color reached senders by",
"pctReached": "% reached",
"religion": "religion",
"senderLanguage": "Sender language",
"senderCountry": "Sender country",
"clear": "clear",
"noSenders": "no senders",
"byLanguage": "Senders by language",
"byCountry": "Senders by country",
"quantile": "% reached (quantile)",
"histogram": "Reached percent histogram",
"religionFamily": "Religion family",
"bridgeType": "Bridge type",
"donut": "Donut by category"
},
"list": {
"hidden": "· {n} hidden by filters",
"clear": "Clear",
"copy": "Copy",
"csvDownloaded": "CSV downloaded ({n})",
"copied": "Copied ({n})",
"copyFailed": "Copy failed — press Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "Cluster by",
"flowers": "flowers",
"unreached": "unreached",
"legendTitle": "Flower clusters",
"legendCore": "Unreached language cluster (core, size = number of groups)",
"legendPetal": "Reached neighbor (granular, real coordinates)",
"legendHelp": "click a top circle to re-cluster · click a core to bloom its petals",
"clustering": "Clustering the harvest by language…",
"signLanguage": "Sign Language"
},
"mc": {
"greenEncoding": "Green color encoding",
"provinces": "IN/CN provinces",
"provincesTitle": "Overlay IN/CN admin-1 boundaries",
"resetFocus": "✕ reset focus",
"needsToken": "The map needs a Mapbox token (tk in profile-config)…",
"rendering": "Rendering the harvest field…"
},
"bloom": "Bloom"
}

View file

@ -0,0 +1,125 @@
{
"brand": "Mis vecinos UPG",
"chooseView": "Elige una vista",
"views": {
"cockpit": "Resumen",
"sendMap": "Mapa de envío",
"flowerMesh": "Malla floral",
"langCluster": "Grupos lingüísticos",
"flowers": "Flores"
},
"hints": {
"cockpit": "centro de filtros cruzados",
"sendMap": "arcos de alcanzado a no alcanzado",
"flowerMesh": "floración radial",
"langCluster": "conjuntos acotados por idioma",
"flowers": "núcleos de familia · idioma · dialecto y pétalos detallados"
},
"title": {
"overview": "Mis vecinos UPG · Resumen",
"send": "Mis vecinos UPG · SEND",
"langClusters": "Mis vecinos UPG · Grupos lingüísticos",
"flowerMesh": "Mis vecinos UPG · Malla floral"
},
"search": "Buscar",
"searchCockpit": "Buscar destino / emisor / idioma…",
"searchStrip": "Buscar destino / idioma / emisor…",
"searchBrush": "Buscar y seleccionar",
"remove": "quitar {label}",
"selectTarget": "Elige el país destino no alcanzado",
"loadingSend": "Cargando mapa SEND…",
"send": {
"unreachedTarget": "Destino no alcanzado —",
"clickOne": "haz clic en uno",
"toFocus": "para enfocarlo",
"reachedSender": "Emisor alcanzado (SEND →)"
},
"pctChristian": "{n} % cristianos",
"groups": "grupos: {n}",
"people": "personas",
"unreached": "no alcanzado",
"reached": "alcanzado",
"unknown": "Desconocido",
"lc": {
"loading": "Cargando pueblos…",
"apiDown": "La API en vivo de doxa no responde: {message}",
"clusters": "grupos lingüísticos",
"pgs": "no alcanzados",
"provinces": "provincias IN/CN",
"withReach": "con coincidencia alcanzada",
"legendCluster": "Grupo lingüístico no alcanzado (tamaño = número de pueblos)",
"legendReached": "Vecino alcanzado (emparejado por idioma)",
"legendHelp": "anillo verde = el grupo tiene una coincidencia alcanzada · haz clic en un grupo para profundizar",
"setCount": "Bounded set — pueblos no alcanzados: {n}",
"more": "+{n} más",
"reachedSame": "Vecinos alcanzados (mismo idioma): {n}",
"frontier": "Ningún pueblo alcanzado comparte este idioma en estos datos. Una frontera: oren por obreros (Lucas 10:2).",
"openFlower": "Abrir vista de flor ▸"
},
"fm": {
"languageMesh": "malla de idiomas",
"reachedNeighbors": "vecinos alcanzados",
"countries": "países",
"upgsInCenter": "UPG en el centro",
"meshLinks": "enlaces de la malla",
"growing": "Creciendo la flor…",
"upgs": "UPG: {n}",
"people": "personas: {n}",
"legendTitle": "La flor",
"unreachedTarget": "Destino no alcanzado (doxa-2000)",
"reachedDeeper": "Vecino alcanzado (más intenso = más cristianos)",
"sendPath": "Ruta SEND → hacia los no alcanzados",
"viaCluster": "por un grupo lingüístico afín",
"mesh": "Malla de colaboración del mismo idioma",
"whorl": "círculo interior = mismo país · pétalos = países vecinos",
"loadError": "No se pudieron cargar los datos de vecinos alcanzados"
},
"strip": {
"lens": "Lente",
"lensAria": "Lente de idioma",
"noLens": "— sin lente —",
"color": "Color",
"colorAria": "Colorear emisores alcanzados por",
"pctReached": "% alcanzado",
"religion": "religión",
"senderLanguage": "Idioma del emisor",
"senderCountry": "País del emisor",
"clear": "borrar",
"noSenders": "sin emisores",
"byLanguage": "Emisores por idioma",
"byCountry": "Emisores por país",
"quantile": "% alcanzado (cuantiles)",
"histogram": "Histograma del porcentaje alcanzado",
"religionFamily": "Familia religiosa",
"bridgeType": "Tipo de puente",
"donut": "Anillo por categoría"
},
"list": {
"hidden": "· ocultos por filtros: {n}",
"clear": "Borrar",
"copy": "Copiar",
"csvDownloaded": "CSV descargado ({n})",
"copied": "Copiado ({n})",
"copyFailed": "No se pudo copiar: pulsa Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "Agrupar por",
"flowers": "flores",
"unreached": "no alcanzados",
"legendTitle": "Grupos florales",
"legendCore": "Grupo lingüístico no alcanzado (núcleo, tamaño = número de pueblos)",
"legendPetal": "Vecino alcanzado (detallado, coordenadas reales)",
"legendHelp": "haz clic en un círculo superior para reagrupar · haz clic en un núcleo para abrir sus pétalos",
"clustering": "Agrupando la cosecha por idioma…",
"signLanguage": "Lengua de señas"
},
"mc": {
"greenEncoding": "Codificación del color verde",
"provinces": "provincias IN/CN",
"provincesTitle": "Mostrar límites provinciales de IN/CN",
"resetFocus": "✕ quitar enfoque",
"needsToken": "El mapa necesita un token de Mapbox (tk en profile-config)…",
"rendering": "Dibujando el campo de cosecha…"
},
"bloom": "Floración"
}

View file

@ -0,0 +1,125 @@
{
"brand": "Mes voisins UPG",
"chooseView": "Choisir une vue",
"views": {
"cockpit": "Vue d’ensemble",
"sendMap": "Carte d’envoi",
"flowerMesh": "Maillage floral",
"langCluster": "Groupes linguistiques",
"flowers": "Fleurs"
},
"hints": {
"cockpit": "centre de filtrage croisé",
"sendMap": "arcs des atteints vers les non-atteints",
"flowerMesh": "floraison radiale",
"langCluster": "ensembles bornés par langue",
"flowers": "cœurs famille · langue · dialecte et pétales détaillés"
},
"title": {
"overview": "Mes voisins UPG · Vue d’ensemble",
"send": "Mes voisins UPG · SEND",
"langClusters": "Mes voisins UPG · Groupes linguistiques",
"flowerMesh": "Mes voisins UPG · Maillage floral"
},
"search": "Rechercher",
"searchCockpit": "Rechercher une cible / un envoyeur / une langue…",
"searchStrip": "Rechercher une cible / une langue / un envoyeur…",
"searchBrush": "Rechercher et sélectionner",
"remove": "retirer {label}",
"selectTarget": "Choisir le pays cible non atteint",
"loadingSend": "Chargement de la carte SEND…",
"send": {
"unreachedTarget": "Cible non atteinte —",
"clickOne": "cliquez sur une",
"toFocus": "pour la cibler",
"reachedSender": "Envoyeur atteint (SEND →)"
},
"pctChristian": "{n} % de chrétiens",
"groups": "groupes : {n}",
"people": "personnes",
"unreached": "non atteint",
"reached": "atteint",
"unknown": "Inconnu",
"lc": {
"loading": "Chargement des peuples…",
"apiDown": "L’API doxa en direct est injoignable : {message}",
"clusters": "groupes linguistiques",
"pgs": "non atteints",
"provinces": "provinces IN/CN",
"withReach": "avec une correspondance atteinte",
"legendCluster": "Groupe linguistique non atteint (taille = nombre de peuples)",
"legendReached": "Voisin atteint (associé par la langue)",
"legendHelp": "anneau vert = le groupe a une correspondance atteinte · cliquez sur un groupe pour l’explorer",
"setCount": "Bounded set — peuples non atteints : {n}",
"more": "+{n} de plus",
"reachedSame": "Voisins atteints (même langue) : {n}",
"frontier": "Aucun peuple atteint ne partage cette langue dans ces données. Une frontière : priez pour des ouvriers (Luc 10.2).",
"openFlower": "Ouvrir la vue fleur ▸"
},
"fm": {
"languageMesh": "maillage linguistique",
"reachedNeighbors": "voisins atteints",
"countries": "pays",
"upgsInCenter": "UPG au centre",
"meshLinks": "liens du maillage",
"growing": "La fleur pousse…",
"upgs": "UPG : {n}",
"people": "personnes : {n}",
"legendTitle": "La fleur",
"unreachedTarget": "Cible non atteinte (doxa-2000)",
"reachedDeeper": "Voisin atteint (plus foncé = plus de chrétiens)",
"sendPath": "Chemin SEND → vers les non-atteints",
"viaCluster": "via un groupe linguistique apparenté",
"mesh": "Maillage de collaboration de même langue",
"whorl": "cercle intérieur = même pays · pétales = pays voisins",
"loadError": "Impossible de charger les données des voisins atteints"
},
"strip": {
"lens": "Lentille",
"lensAria": "Lentille linguistique",
"noLens": "— aucune lentille —",
"color": "Couleur",
"colorAria": "Colorer les envoyeurs atteints par",
"pctReached": "% atteint",
"religion": "religion",
"senderLanguage": "Langue de l’envoyeur",
"senderCountry": "Pays de l’envoyeur",
"clear": "effacer",
"noSenders": "aucun envoyeur",
"byLanguage": "Envoyeurs par langue",
"byCountry": "Envoyeurs par pays",
"quantile": "% atteint (quantiles)",
"histogram": "Histogramme du pourcentage atteint",
"religionFamily": "Famille religieuse",
"bridgeType": "Type de pont",
"donut": "Anneau par catégorie"
},
"list": {
"hidden": "· masqués par les filtres : {n}",
"clear": "Effacer",
"copy": "Copier",
"csvDownloaded": "CSV téléchargé ({n})",
"copied": "Copié ({n})",
"copyFailed": "Échec de la copie : appuyez sur Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "Regrouper par",
"flowers": "fleurs",
"unreached": "non atteints",
"legendTitle": "Groupes floraux",
"legendCore": "Groupe linguistique non atteint (cœur, taille = nombre de peuples)",
"legendPetal": "Voisin atteint (détaillé, coordonnées réelles)",
"legendHelp": "cliquez sur un cercle du haut pour regrouper · cliquez sur un cœur pour ouvrir ses pétales",
"clustering": "Regroupement de la moisson par langue…",
"signLanguage": "Langue des signes"
},
"mc": {
"greenEncoding": "Codage de la couleur verte",
"provinces": "provinces IN/CN",
"provincesTitle": "Afficher les limites des provinces IN/CN",
"resetFocus": "✕ annuler le ciblage",
"needsToken": "La carte a besoin d’un jeton Mapbox (tk dans profile-config)…",
"rendering": "Dessin du champ de la moisson…"
},
"bloom": "Floraison"
}

View file

@ -0,0 +1,125 @@
{
"brand": "मेरे UPG पड़ोसी",
"chooseView": "दृश्य चुनें",
"views": {
"cockpit": "सारांश",
"sendMap": "भेजने का नक्शा",
"flowerMesh": "फूल-जाल",
"langCluster": "भाषा समूह",
"flowers": "फूल"
},
"hints": {
"cockpit": "क्रॉस-फ़िल्टर केंद्र",
"sendMap": "पहुँचे से अपहुँचे तक चाप",
"flowerMesh": "वृत्ताकार खिलना",
"langCluster": "भाषा से सीमित समुच्चय",
"flowers": "परिवार · भाषा · बोली के केंद्र और बारीक पंखुड़ियाँ"
},
"title": {
"overview": "मेरे UPG पड़ोसी · सारांश",
"send": "मेरे UPG पड़ोसी · SEND",
"langClusters": "मेरे UPG पड़ोसी · भाषा समूह",
"flowerMesh": "मेरे UPG पड़ोसी · फूल-जाल"
},
"search": "खोजें",
"searchCockpit": "लक्ष्य / भेजने वाला / भाषा खोजें…",
"searchStrip": "लक्ष्य / भाषा / भेजने वाला खोजें…",
"searchBrush": "खोजें और चुनें",
"remove": "{label} हटाएँ",
"selectTarget": "अपहुँचा लक्ष्य देश चुनें",
"loadingSend": "SEND नक्शा लोड हो रहा है…",
"send": {
"unreachedTarget": "अपहुँचा लक्ष्य —",
"clickOne": "किसी एक पर क्लिक करें",
"toFocus": "उस पर ध्यान केंद्रित करने के लिए",
"reachedSender": "पहुँचा भेजने वाला (SEND →)"
},
"pctChristian": "ईसाई: {n}%",
"groups": "समूह: {n}",
"people": "लोग",
"unreached": "अपहुँचा",
"reached": "पहुँचा",
"unknown": "अज्ञात",
"lc": {
"loading": "जनसमूह लोड हो रहे हैं…",
"apiDown": "doxa का लाइव API उपलब्ध नहीं: {message}",
"clusters": "भाषा समूह",
"pgs": "अपहुँचे",
"provinces": "IN/CN प्रांत",
"withReach": "पहुँचे मेल वाले",
"legendCluster": "अपहुँचा भाषा समूह (आकार = समूहों की संख्या)",
"legendReached": "पहुँचा पड़ोसी (भाषा से मिलाया गया)",
"legendHelp": "हरा घेरा = समूह का पहुँचा मेल है · गहराई से देखने के लिए किसी समूह पर क्लिक करें",
"setCount": "Bounded set — अपहुँचे समूह: {n}",
"more": "+{n} और",
"reachedSame": "पहुँचे पड़ोसी (एक ही भाषा): {n}",
"frontier": "इस डेटा में कोई पहुँचा जनसमूह यह भाषा नहीं बोलता। यह एक सीमांत है: मज़दूरों के लिए प्रार्थना करें (लूका 10:2)।",
"openFlower": "फूल दृश्य खोलें ▸"
},
"fm": {
"languageMesh": "भाषा-जाल",
"reachedNeighbors": "पहुँचे पड़ोसी",
"countries": "देश",
"upgsInCenter": "केंद्र में UPG",
"meshLinks": "जाल की कड़ियाँ",
"growing": "फूल बढ़ रहा है…",
"upgs": "UPG: {n}",
"people": "लोग: {n}",
"legendTitle": "फूल",
"unreachedTarget": "अपहुँचा लक्ष्य (doxa-2000)",
"reachedDeeper": "पहुँचा पड़ोसी (गहरा = अधिक ईसाई)",
"sendPath": "SEND मार्ग → अपहुँचों की ओर",
"viaCluster": "संबंधित भाषा समूह के द्वारा",
"mesh": "एक ही भाषा का सहयोग-जाल",
"whorl": "भीतरी घेरा = वही देश · पंखुड़ियाँ = आसपास के देश",
"loadError": "पहुँचे पड़ोसियों का डेटा लोड नहीं हो सका"
},
"strip": {
"lens": "लेंस",
"lensAria": "भाषा लेंस",
"noLens": "— कोई लेंस नहीं —",
"color": "रंग",
"colorAria": "पहुँचे भेजने वालों को इस आधार पर रंगें",
"pctReached": "% पहुँचा",
"religion": "धर्म",
"senderLanguage": "भेजने वाले की भाषा",
"senderCountry": "भेजने वाले का देश",
"clear": "साफ़ करें",
"noSenders": "कोई भेजने वाला नहीं",
"byLanguage": "भाषा के अनुसार भेजने वाले",
"byCountry": "देश के अनुसार भेजने वाले",
"quantile": "% पहुँचा (क्वांटाइल)",
"histogram": "पहुँच प्रतिशत का हिस्टोग्राम",
"religionFamily": "धर्म परिवार",
"bridgeType": "पुल का प्रकार",
"donut": "श्रेणी के अनुसार रिंग"
},
"list": {
"hidden": "· फ़िल्टर से छिपे: {n}",
"clear": "साफ़ करें",
"copy": "कॉपी करें",
"csvDownloaded": "CSV डाउनलोड हुई ({n})",
"copied": "कॉपी हुआ ({n})",
"copyFailed": "कॉपी नहीं हुआ: Ctrl/Cmd-C दबाएँ"
},
"fc": {
"clusterBy": "समूह बनाएँ",
"flowers": "फूल",
"unreached": "अपहुँचे",
"legendTitle": "फूल-समूह",
"legendCore": "अपहुँचा भाषा समूह (केंद्र, आकार = समूहों की संख्या)",
"legendPetal": "पहुँचा पड़ोसी (बारीक, असली निर्देशांक)",
"legendHelp": "फिर से समूह बनाने के लिए ऊपर के घेरे पर क्लिक करें · पंखुड़ियाँ खोलने के लिए केंद्र पर क्लिक करें",
"clustering": "फ़सल को भाषा के अनुसार समूहित किया जा रहा है…",
"signLanguage": "सांकेतिक भाषा"
},
"mc": {
"greenEncoding": "हरे रंग की कोडिंग",
"provinces": "IN/CN प्रांत",
"provincesTitle": "IN/CN प्रांतों की सीमाएँ दिखाएँ",
"resetFocus": "✕ ध्यान हटाएँ",
"needsToken": "नक्शे को Mapbox टोकन चाहिए (profile-config में tk)…",
"rendering": "फ़सल का खेत बनाया जा रहा है…"
},
"bloom": "खिलना"
}

View file

@ -0,0 +1,125 @@
{
"brand": "I miei vicini UPG",
"chooseView": "Scegli una vista",
"views": {
"cockpit": "Panoramica",
"sendMap": "Mappa di invio",
"flowerMesh": "Rete floreale",
"langCluster": "Gruppi linguistici",
"flowers": "Fiori"
},
"hints": {
"cockpit": "centro dei filtri incrociati",
"sendMap": "archi da raggiunti a non raggiunti",
"flowerMesh": "fioritura radiale",
"langCluster": "insiemi delimitati per lingua",
"flowers": "nuclei di famiglia · lingua · dialetto e petali dettagliati"
},
"title": {
"overview": "I miei vicini UPG · Panoramica",
"send": "I miei vicini UPG · SEND",
"langClusters": "I miei vicini UPG · Gruppi linguistici",
"flowerMesh": "I miei vicini UPG · Rete floreale"
},
"search": "Cerca",
"searchCockpit": "Cerca obiettivo / mittente / lingua…",
"searchStrip": "Cerca obiettivo / lingua / mittente…",
"searchBrush": "Cerca e seleziona",
"remove": "rimuovi {label}",
"selectTarget": "Scegli il paese obiettivo non raggiunto",
"loadingSend": "Caricamento della mappa SEND…",
"send": {
"unreachedTarget": "Obiettivo non raggiunto —",
"clickOne": "cliccane uno",
"toFocus": "per metterlo a fuoco",
"reachedSender": "Mittente raggiunto (SEND →)"
},
"pctChristian": "{n}% cristiani",
"groups": "gruppi: {n}",
"people": "persone",
"unreached": "non raggiunto",
"reached": "raggiunto",
"unknown": "Sconosciuto",
"lc": {
"loading": "Caricamento dei popoli…",
"apiDown": "L’API doxa in tempo reale non risponde: {message}",
"clusters": "gruppi linguistici",
"pgs": "non raggiunti",
"provinces": "province IN/CN",
"withReach": "con corrispondenza raggiunta",
"legendCluster": "Gruppo linguistico non raggiunto (dimensione = numero di popoli)",
"legendReached": "Vicino raggiunto (abbinato per lingua)",
"legendHelp": "anello verde = il gruppo ha una corrispondenza raggiunta · clicca un gruppo per approfondire",
"setCount": "Bounded set — popoli non raggiunti: {n}",
"more": "+{n} altri",
"reachedSame": "Vicini raggiunti (stessa lingua): {n}",
"frontier": "Nessun popolo raggiunto condivide questa lingua in questi dati. Una frontiera: pregate per gli operai (Luca 10:2).",
"openFlower": "Apri la vista fiore ▸"
},
"fm": {
"languageMesh": "rete linguistica",
"reachedNeighbors": "vicini raggiunti",
"countries": "paesi",
"upgsInCenter": "UPG al centro",
"meshLinks": "collegamenti della rete",
"growing": "Il fiore sta crescendo…",
"upgs": "UPG: {n}",
"people": "persone: {n}",
"legendTitle": "Il fiore",
"unreachedTarget": "Obiettivo non raggiunto (doxa-2000)",
"reachedDeeper": "Vicino raggiunto (più scuro = più cristiani)",
"sendPath": "Percorso SEND → verso i non raggiunti",
"viaCluster": "tramite un gruppo linguistico affine",
"mesh": "Rete di collaborazione nella stessa lingua",
"whorl": "cerchio interno = stesso paese · petali = paesi circostanti",
"loadError": "Impossibile caricare i dati dei vicini raggiunti"
},
"strip": {
"lens": "Lente",
"lensAria": "Lente linguistica",
"noLens": "— nessuna lente —",
"color": "Colore",
"colorAria": "Colora i mittenti raggiunti per",
"pctReached": "% raggiunto",
"religion": "religione",
"senderLanguage": "Lingua del mittente",
"senderCountry": "Paese del mittente",
"clear": "azzera",
"noSenders": "nessun mittente",
"byLanguage": "Mittenti per lingua",
"byCountry": "Mittenti per paese",
"quantile": "% raggiunto (quantili)",
"histogram": "Istogramma della percentuale raggiunta",
"religionFamily": "Famiglia religiosa",
"bridgeType": "Tipo di ponte",
"donut": "Anello per categoria"
},
"list": {
"hidden": "· nascosti dai filtri: {n}",
"clear": "Azzera",
"copy": "Copia",
"csvDownloaded": "CSV scaricato ({n})",
"copied": "Copiato ({n})",
"copyFailed": "Copia non riuscita: premi Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "Raggruppa per",
"flowers": "fiori",
"unreached": "non raggiunti",
"legendTitle": "Gruppi floreali",
"legendCore": "Gruppo linguistico non raggiunto (nucleo, dimensione = numero di popoli)",
"legendPetal": "Vicino raggiunto (dettagliato, coordinate reali)",
"legendHelp": "clicca un cerchio in alto per raggruppare · clicca un nucleo per aprire i petali",
"clustering": "Raggruppamento del raccolto per lingua…",
"signLanguage": "Lingua dei segni"
},
"mc": {
"greenEncoding": "Codifica del colore verde",
"provinces": "province IN/CN",
"provincesTitle": "Mostra i confini provinciali di IN/CN",
"resetFocus": "✕ togli il focus",
"needsToken": "La mappa richiede un token Mapbox (tk in profile-config)…",
"rendering": "Disegno del campo del raccolto…"
},
"bloom": "Fioritura"
}

View file

@ -0,0 +1,125 @@
{
"brand": "Meus vizinhos UPG",
"chooseView": "Escolha uma vista",
"views": {
"cockpit": "Visão geral",
"sendMap": "Mapa de envio",
"flowerMesh": "Malha floral",
"langCluster": "Grupos linguísticos",
"flowers": "Flores"
},
"hints": {
"cockpit": "centro de filtros cruzados",
"sendMap": "arcos de alcançado para não alcançado",
"flowerMesh": "floração radial",
"langCluster": "conjuntos delimitados por idioma",
"flowers": "núcleos de família · idioma · dialeto e pétalas detalhadas"
},
"title": {
"overview": "Meus vizinhos UPG · Visão geral",
"send": "Meus vizinhos UPG · SEND",
"langClusters": "Meus vizinhos UPG · Grupos linguísticos",
"flowerMesh": "Meus vizinhos UPG · Malha floral"
},
"search": "Pesquisar",
"searchCockpit": "Pesquisar destino / emissor / idioma…",
"searchStrip": "Pesquisar destino / idioma / emissor…",
"searchBrush": "Pesquisar e selecionar",
"remove": "remover {label}",
"selectTarget": "Escolha o país destino não alcançado",
"loadingSend": "A carregar mapa SEND…",
"send": {
"unreachedTarget": "Destino não alcançado —",
"clickOne": "clique num",
"toFocus": "para o focar",
"reachedSender": "Emissor alcançado (SEND →)"
},
"pctChristian": "{n}% cristãos",
"groups": "grupos: {n}",
"people": "pessoas",
"unreached": "não alcançado",
"reached": "alcançado",
"unknown": "Desconhecido",
"lc": {
"loading": "A carregar povos…",
"apiDown": "A API em direto da doxa não responde: {message}",
"clusters": "grupos linguísticos",
"pgs": "não alcançados",
"provinces": "províncias IN/CN",
"withReach": "com correspondência alcançada",
"legendCluster": "Grupo linguístico não alcançado (tamanho = número de povos)",
"legendReached": "Vizinho alcançado (associado pelo idioma)",
"legendHelp": "anel verde = o grupo tem uma correspondência alcançada · clique num grupo para aprofundar",
"setCount": "Bounded set — povos não alcançados: {n}",
"more": "+{n} mais",
"reachedSame": "Vizinhos alcançados (mesmo idioma): {n}",
"frontier": "Nenhum povo alcançado partilha este idioma nestes dados. Uma fronteira: orem por trabalhadores (Lucas 10:2).",
"openFlower": "Abrir vista de flor ▸"
},
"fm": {
"languageMesh": "malha de idiomas",
"reachedNeighbors": "vizinhos alcançados",
"countries": "países",
"upgsInCenter": "UPG no centro",
"meshLinks": "ligações da malha",
"growing": "A flor está a crescer…",
"upgs": "UPG: {n}",
"people": "pessoas: {n}",
"legendTitle": "A flor",
"unreachedTarget": "Destino não alcançado (doxa-2000)",
"reachedDeeper": "Vizinho alcançado (mais escuro = mais cristãos)",
"sendPath": "Caminho SEND → para os não alcançados",
"viaCluster": "por um grupo linguístico relacionado",
"mesh": "Malha de colaboração do mesmo idioma",
"whorl": "círculo interior = mesmo país · pétalas = países vizinhos",
"loadError": "Não foi possível carregar os dados dos vizinhos alcançados"
},
"strip": {
"lens": "Lente",
"lensAria": "Lente de idioma",
"noLens": "— sem lente —",
"color": "Cor",
"colorAria": "Colorir emissores alcançados por",
"pctReached": "% alcançado",
"religion": "religião",
"senderLanguage": "Idioma do emissor",
"senderCountry": "País do emissor",
"clear": "limpar",
"noSenders": "sem emissores",
"byLanguage": "Emissores por idioma",
"byCountry": "Emissores por país",
"quantile": "% alcançado (quantis)",
"histogram": "Histograma da percentagem alcançada",
"religionFamily": "Família religiosa",
"bridgeType": "Tipo de ponte",
"donut": "Anel por categoria"
},
"list": {
"hidden": "· ocultos pelos filtros: {n}",
"clear": "Limpar",
"copy": "Copiar",
"csvDownloaded": "CSV transferido ({n})",
"copied": "Copiado ({n})",
"copyFailed": "Falha ao copiar: prima Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "Agrupar por",
"flowers": "flores",
"unreached": "não alcançados",
"legendTitle": "Grupos florais",
"legendCore": "Grupo linguístico não alcançado (núcleo, tamanho = número de povos)",
"legendPetal": "Vizinho alcançado (detalhado, coordenadas reais)",
"legendHelp": "clique num círculo superior para reagrupar · clique num núcleo para abrir as pétalas",
"clustering": "A agrupar a colheita por idioma…",
"signLanguage": "Língua de sinais"
},
"mc": {
"greenEncoding": "Codificação da cor verde",
"provinces": "províncias IN/CN",
"provincesTitle": "Mostrar limites provinciais de IN/CN",
"resetFocus": "✕ tirar foco",
"needsToken": "O mapa precisa de um token Mapbox (tk em profile-config)…",
"rendering": "A desenhar o campo da colheita…"
},
"bloom": "Floração"
}

View file

@ -0,0 +1,125 @@
{
"brand": "Vecinii mei UPG",
"chooseView": "Alegeți o vedere",
"views": {
"cockpit": "Prezentare",
"sendMap": "Harta de trimitere",
"flowerMesh": "Rețea florală",
"langCluster": "Grupuri lingvistice",
"flowers": "Flori"
},
"hints": {
"cockpit": "centru de filtrare încrucișată",
"sendMap": "arce de la atinși la neatinși",
"flowerMesh": "înflorire radială",
"langCluster": "mulțimi delimitate după limbă",
"flowers": "nuclee familie · limbă · dialect și petale detaliate"
},
"title": {
"overview": "Vecinii mei UPG · Prezentare",
"send": "Vecinii mei UPG · SEND",
"langClusters": "Vecinii mei UPG · Grupuri lingvistice",
"flowerMesh": "Vecinii mei UPG · Rețea florală"
},
"search": "Căutare",
"searchCockpit": "Căutați țintă / expeditor / limbă…",
"searchStrip": "Căutați țintă / limbă / expeditor…",
"searchBrush": "Căutare și selecție",
"remove": "eliminați {label}",
"selectTarget": "Alegeți țara țintă neatinsă",
"loadingSend": "Se încarcă harta SEND…",
"send": {
"unreachedTarget": "Țintă neatinsă —",
"clickOne": "dați clic pe una",
"toFocus": "pentru a o evidenția",
"reachedSender": "Expeditor atins (SEND →)"
},
"pctChristian": "{n}% creștini",
"groups": "grupuri: {n}",
"people": "oameni",
"unreached": "neatins",
"reached": "atins",
"unknown": "Necunoscut",
"lc": {
"loading": "Se încarcă popoarele…",
"apiDown": "API-ul doxa în timp real nu răspunde: {message}",
"clusters": "grupuri lingvistice",
"pgs": "neatinse",
"provinces": "provincii IN/CN",
"withReach": "cu corespondență atinsă",
"legendCluster": "Grup lingvistic neatins (mărime = numărul de popoare)",
"legendReached": "Vecin atins (potrivit după limbă)",
"legendHelp": "inel verde = grupul are o corespondență atinsă · dați clic pe un grup pentru detalii",
"setCount": "Bounded set — popoare neatinse: {n}",
"more": "+{n} în plus",
"reachedSame": "Vecini atinși (aceeași limbă): {n}",
"frontier": "Niciun popor atins nu vorbește această limbă în aceste date. O frontieră: rugați-vă pentru lucrători (Luca 10:2).",
"openFlower": "Deschideți vederea floare ▸"
},
"fm": {
"languageMesh": "rețea lingvistică",
"reachedNeighbors": "vecini atinși",
"countries": "țări",
"upgsInCenter": "UPG în centru",
"meshLinks": "legături ale rețelei",
"growing": "Floarea crește…",
"upgs": "UPG: {n}",
"people": "oameni: {n}",
"legendTitle": "Floarea",
"unreachedTarget": "Țintă neatinsă (doxa-2000)",
"reachedDeeper": "Vecin atins (mai închis = mai mulți creștini)",
"sendPath": "Calea SEND → spre cei neatinși",
"viaCluster": "printr-un grup lingvistic înrudit",
"mesh": "Rețea de colaborare în aceeași limbă",
"whorl": "cercul interior = aceeași țară · petalele = țările din jur",
"loadError": "Datele vecinilor atinși nu au putut fi încărcate"
},
"strip": {
"lens": "Lentilă",
"lensAria": "Lentilă lingvistică",
"noLens": "— fără lentilă —",
"color": "Culoare",
"colorAria": "Colorați expeditorii atinși după",
"pctReached": "% atins",
"religion": "religie",
"senderLanguage": "Limba expeditorului",
"senderCountry": "Țara expeditorului",
"clear": "ștergeți",
"noSenders": "niciun expeditor",
"byLanguage": "Expeditori după limbă",
"byCountry": "Expeditori după țară",
"quantile": "% atins (cuantile)",
"histogram": "Histograma procentului atins",
"religionFamily": "Familie religioasă",
"bridgeType": "Tip de punte",
"donut": "Inel pe categorii"
},
"list": {
"hidden": "· ascunși de filtre: {n}",
"clear": "Ștergeți",
"copy": "Copiați",
"csvDownloaded": "CSV descărcat ({n})",
"copied": "Copiat ({n})",
"copyFailed": "Copierea a eșuat: apăsați Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "Grupați după",
"flowers": "flori",
"unreached": "neatinse",
"legendTitle": "Grupuri florale",
"legendCore": "Grup lingvistic neatins (nucleu, mărime = numărul de popoare)",
"legendPetal": "Vecin atins (detaliat, coordonate reale)",
"legendHelp": "clic pe un cerc de sus pentru regrupare · clic pe un nucleu pentru a-i deschide petalele",
"clustering": "Se grupează recolta după limbă…",
"signLanguage": "Limbajul semnelor"
},
"mc": {
"greenEncoding": "Codificarea culorii verzi",
"provinces": "provincii IN/CN",
"provincesTitle": "Afișați granițele provinciilor IN/CN",
"resetFocus": "✕ anulați evidențierea",
"needsToken": "Harta are nevoie de un token Mapbox (tk în profile-config)…",
"rendering": "Se desenează câmpul recoltei…"
},
"bloom": "Înflorire"
}

View file

@ -0,0 +1,125 @@
{
"brand": "Мои соседи UPG",
"chooseView": "Выберите вид",
"views": {
"cockpit": "Обзор",
"sendMap": "Карта отправки",
"flowerMesh": "Цветочная сеть",
"langCluster": "Языковые кластеры",
"flowers": "Цветы"
},
"hints": {
"cockpit": "центр перекрёстной фильтрации",
"sendMap": "дуги от охваченных к неохваченным",
"flowerMesh": "радиальное цветение",
"langCluster": "ограниченные языковые множества",
"flowers": "ядра семья · язык · диалект и детальные лепестки"
},
"title": {
"overview": "Мои соседи UPG · Обзор",
"send": "Мои соседи UPG · SEND",
"langClusters": "Мои соседи UPG · Языковые кластеры",
"flowerMesh": "Мои соседи UPG · Цветочная сеть"
},
"search": "Поиск",
"searchCockpit": "Искать цель / отправителя / язык…",
"searchStrip": "Искать цель / язык / отправителя…",
"searchBrush": "Поиск и выбор",
"remove": "убрать {label}",
"selectTarget": "Выберите неохваченную страну-цель",
"loadingSend": "Загрузка карты SEND…",
"send": {
"unreachedTarget": "Неохваченная цель —",
"clickOne": "нажмите на одну",
"toFocus": "чтобы выделить её",
"reachedSender": "Охваченный отправитель (SEND →)"
},
"pctChristian": "христиан: {n}%",
"groups": "групп: {n}",
"people": "человек",
"unreached": "неохвачен",
"reached": "охвачен",
"unknown": "Неизвестно",
"lc": {
"loading": "Загрузка народов…",
"apiDown": "Живой API doxa недоступен: {message}",
"clusters": "языковых кластеров",
"pgs": "неохваченных",
"provinces": "провинций IN/CN",
"withReach": "с охваченным соответствием",
"legendCluster": "Неохваченный языковой кластер (размер = число групп)",
"legendReached": "Охваченный сосед (сопоставлен по языку)",
"legendHelp": "зелёное кольцо = у кластера есть охваченное соответствие · нажмите на кластер, чтобы углубиться",
"setCount": "Bounded set — неохваченных групп: {n}",
"more": "ещё {n}",
"reachedSame": "Охваченные соседи (тот же язык): {n}",
"frontier": "Ни один охваченный народ не говорит на этом языке в этих данных. Это рубеж: молитесь о делателях (Лк 10:2).",
"openFlower": "Открыть вид цветка ▸"
},
"fm": {
"languageMesh": "языковая сеть",
"reachedNeighbors": "охваченных соседей",
"countries": "стран",
"upgsInCenter": "UPG в центре",
"meshLinks": "связей сети",
"growing": "Цветок растёт…",
"upgs": "UPG: {n}",
"people": "человек: {n}",
"legendTitle": "Цветок",
"unreachedTarget": "Неохваченная цель (doxa-2000)",
"reachedDeeper": "Охваченный сосед (темнее = больше христиан)",
"sendPath": "Путь SEND → к неохваченным",
"viaCluster": "через родственный языковой кластер",
"mesh": "Сеть сотрудничества на одном языке",
"whorl": "внутренний круг = та же страна · лепестки = соседние страны",
"loadError": "Не удалось загрузить данные об охваченных соседях"
},
"strip": {
"lens": "Линза",
"lensAria": "Языковая линза",
"noLens": "— без линзы —",
"color": "Цвет",
"colorAria": "Окрашивать охваченных отправителей по",
"pctReached": "% охвата",
"religion": "религия",
"senderLanguage": "Язык отправителя",
"senderCountry": "Страна отправителя",
"clear": "сбросить",
"noSenders": "нет отправителей",
"byLanguage": "Отправители по языку",
"byCountry": "Отправители по стране",
"quantile": "% охвата (квантили)",
"histogram": "Гистограмма доли охвата",
"religionFamily": "Религиозная семья",
"bridgeType": "Тип моста",
"donut": "Кольцо по категориям"
},
"list": {
"hidden": "· скрыто фильтрами: {n}",
"clear": "Сбросить",
"copy": "Копировать",
"csvDownloaded": "CSV загружен ({n})",
"copied": "Скопировано ({n})",
"copyFailed": "Не удалось скопировать: нажмите Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "Группировать по",
"flowers": "цветов",
"unreached": "неохваченных",
"legendTitle": "Цветочные кластеры",
"legendCore": "Неохваченный языковой кластер (ядро, размер = число групп)",
"legendPetal": "Охваченный сосед (детально, реальные координаты)",
"legendHelp": "нажмите верхний круг, чтобы перегруппировать · нажмите ядро, чтобы раскрыть лепестки",
"clustering": "Группировка жатвы по языку…",
"signLanguage": "Жестовый язык"
},
"mc": {
"greenEncoding": "Кодирование зелёного цвета",
"provinces": "провинции IN/CN",
"provincesTitle": "Показать границы провинций IN/CN",
"resetFocus": "✕ сбросить выделение",
"needsToken": "Карте нужен токен Mapbox (tk в profile-config)…",
"rendering": "Рисуется поле жатвы…"
},
"bloom": "Цветение"
}

View file

@ -0,0 +1,125 @@
{
"brand": "我的 UPG 邻居",
"chooseView": "选择视图",
"views": {
"cockpit": "概览",
"sendMap": "差派地图",
"flowerMesh": "花网",
"langCluster": "语言群",
"flowers": "花朵"
},
"hints": {
"cockpit": "交叉筛选中心",
"sendMap": "从已得之民到未得之民的弧线",
"flowerMesh": "放射状绽放",
"langCluster": "按语言划定的集合",
"flowers": "语系 · 语言 · 方言为核心,细分花瓣"
},
"title": {
"overview": "我的 UPG 邻居 · 概览",
"send": "我的 UPG 邻居 · SEND",
"langClusters": "我的 UPG 邻居 · 语言群",
"flowerMesh": "我的 UPG 邻居 · 花网"
},
"search": "搜索",
"searchCockpit": "搜索目标 / 差派方 / 语言…",
"searchStrip": "搜索目标 / 语言 / 差派方…",
"searchBrush": "搜索并选择",
"remove": "移除 {label}",
"selectTarget": "选择未得之民目标国家",
"loadingSend": "正在加载 SEND 地图…",
"send": {
"unreachedTarget": "未得之民目标 —",
"clickOne": "点击其中一个",
"toFocus": "进行聚焦",
"reachedSender": "已得之民差派方(SEND →)"
},
"pctChristian": "基督徒 {n}%",
"groups": "族群:{n}",
"people": "人口",
"unreached": "未得",
"reached": "已得",
"unknown": "未知",
"lc": {
"loading": "正在加载族群…",
"apiDown": "doxa 实时接口无法访问:{message}",
"clusters": "语言群",
"pgs": "未得",
"provinces": "IN/CN 省份",
"withReach": "有已得匹配",
"legendCluster": "未得语言群(大小 = 族群数)",
"legendReached": "已得邻居(按语言匹配)",
"legendHelp": "绿环 = 该语言群有已得匹配 · 点击语言群深入查看",
"setCount": "Bounded set — 未得族群:{n}",
"more": "另有 {n} 个",
"reachedSame": "已得邻居(同一语言):{n}",
"frontier": "在此数据中没有已得族群使用这种语言。这是前沿:请为工人祷告(路加福音 10:2)。",
"openFlower": "打开花朵视图 ▸"
},
"fm": {
"languageMesh": "语言网",
"reachedNeighbors": "已得邻居",
"countries": "国家",
"upgsInCenter": "中心的 UPG",
"meshLinks": "网络连线",
"growing": "花朵正在生长…",
"upgs": "UPG:{n}",
"people": "人口:{n}",
"legendTitle": "花朵",
"unreachedTarget": "未得之民目标(doxa-2000)",
"reachedDeeper": "已得邻居(越深 = 基督徒越多)",
"sendPath": "SEND 路径 → 通往未得之民",
"viaCluster": "经由相关语言群",
"mesh": "同语言协作网",
"whorl": "内圈 = 同一国家 · 花瓣 = 周边国家",
"loadError": "无法加载已得邻居数据"
},
"strip": {
"lens": "透镜",
"lensAria": "语言透镜",
"noLens": "— 无透镜 —",
"color": "颜色",
"colorAria": "已得差派方的着色依据",
"pctReached": "已得比例 %",
"religion": "宗教",
"senderLanguage": "差派方语言",
"senderCountry": "差派方国家",
"clear": "清除",
"noSenders": "没有差派方",
"byLanguage": "按语言统计差派方",
"byCountry": "按国家统计差派方",
"quantile": "已得比例 %(分位数)",
"histogram": "已得比例直方图",
"religionFamily": "宗教类别",
"bridgeType": "桥梁类型",
"donut": "按类别的环形图"
},
"list": {
"hidden": "· 被筛选隐藏:{n}",
"clear": "清除",
"copy": "复制",
"csvDownloaded": "已下载 CSV({n})",
"copied": "已复制({n})",
"copyFailed": "复制失败:请按 Ctrl/Cmd-C"
},
"fc": {
"clusterBy": "分组依据",
"flowers": "花朵",
"unreached": "未得",
"legendTitle": "花朵集群",
"legendCore": "未得语言群(核心,大小 = 族群数)",
"legendPetal": "已得邻居(细分,真实坐标)",
"legendHelp": "点击上方圆圈重新分组 · 点击核心展开花瓣",
"clustering": "正在按语言整理庄稼…",
"signLanguage": "手语"
},
"mc": {
"greenEncoding": "绿色编码",
"provinces": "IN/CN 省份",
"provincesTitle": "显示 IN/CN 省级边界",
"resetFocus": "✕ 取消聚焦",
"needsToken": "地图需要 Mapbox 令牌(profile-config 中的 tk)…",
"rendering": "正在绘制庄稼田…"
},
"bloom": "绽放"
}

View file

@ -136,15 +136,15 @@ useShadowStyles(`
<div ref="rootEl" class="cpt-root">
<!-- HEADER: brand · removable chips · search · honest counter · provenance pill -->
<div class="cpt-header">
<span class="cpt-brand">My UPG Neighbor · Overview</span>
<span class="cpt-brand">{{ $t('neighbor.title.overview') }}</span>
<div class="cpt-chips">
<button v-for="(c, i) in store.chips.value" :key="i" class="cpt-chip" :class="{ focus: c.kind === 'focus' }"
@click="store.removeChip(c)" :title="'remove ' + c.label">
@click="store.removeChip(c)" :title="$t('neighbor.remove', { label: c.label })">
{{ c.label }} <span class="x">×</span>
</button>
</div>
<input class="cpt-search" type="search" :value="store.search.value" @input="onSearch"
placeholder="Search target / sender / language…" aria-label="Search" />
:placeholder="$t('neighbor.searchCockpit')" :aria-label="$t('neighbor.search')" />
<span class="cpt-counter">
<b>{{ store.counts.value.senders.toLocaleString() }}</b> senders →
<b>{{ store.counts.value.clusters.toLocaleString() }}</b> clusters · {{ store.counts.value.hidden }} hidden

View file

@ -243,7 +243,7 @@ useShadowStyles(`
<input class="cs-search" type="search" :value="store.search.value"
@input="onSearchInput" @focus="showSuggest = true"
@blur="showSuggest = false"
placeholder="Search target / language / sender…" aria-label="Search and brush" />
:placeholder="$t('neighbor.searchStrip')" :aria-label="$t('neighbor.searchBrush')" />
<ul v-if="showSuggest && suggestions.length" class="cs-suggest" role="listbox">
<li v-for="(s, i) in suggestions" :key="i" :class="'t-' + s.type" role="option"
@mousedown.prevent="pickSuggestion(s)">
@ -254,19 +254,19 @@ useShadowStyles(`
</div>
<label class="cs-lens">
<span>Lens</span>
<select :value="store.languageLens.value || ''" @change="onLens" aria-label="Language lens">
<option value="">— no lens —</option>
<span>{{ $t('neighbor.strip.lens') }}</span>
<select :value="store.languageLens.value || ''" @change="onLens" :aria-label="$t('neighbor.strip.lensAria')">
<option value="">{{ $t('neighbor.strip.noLens') }}</option>
<option v-for="l in langCounts.slice(0, 40)" :key="l.lang" :value="l.lang">
{{ l.lang }} ({{ l.count }})
</option>
</select>
</label>
<div class="cs-colorby" role="group" aria-label="Color reached senders by">
<span>Color</span>
<button :class="{ on: store.colorBy.value === 'pct' }" @click="store.setColorBy('pct')">% reached</button>
<button :class="{ on: store.colorBy.value === 'religion' }" @click="store.setColorBy('religion')">religion</button>
<div class="cs-colorby" role="group" :aria-label="$t('neighbor.strip.colorAria')">
<span>{{ $t('neighbor.strip.color') }}</span>
<button :class="{ on: store.colorBy.value === 'pct' }" @click="store.setColorBy('pct')">{{ $t('neighbor.strip.pctReached') }}</button>
<button :class="{ on: store.colorBy.value === 'religion' }" @click="store.setColorBy('religion')">{{ $t('neighbor.strip.religion') }}</button>
</div>
</div>
@ -275,12 +275,12 @@ useShadowStyles(`
<!-- 1) sender language bar -->
<section class="cs-card">
<header>
<h4>Sender language</h4>
<h4>{{ $t('neighbor.strip.senderLanguage') }}</h4>
<button v-if="store.facets.value.languages.size" class="cs-clr"
@click="store.facets.value.languages.forEach(v => store.toggleFacet('languages', v))">clear</button>
@click="store.facets.value.languages.forEach(v => store.toggleFacet('languages', v))">{{ $t('neighbor.strip.clear') }}</button>
</header>
<svg v-if="langRows.length" class="cs-svg" :viewBox="'0 0 ' + BAR_VB_W + ' ' + barVbH(cd.languageBar)"
preserveAspectRatio="xMinYMin meet" role="img" aria-label="Senders by language">
preserveAspectRatio="xMinYMin meet" role="img" :aria-label="$t('neighbor.strip.byLanguage')">
<g v-for="r in langRows" :key="r.key" class="cs-barrow" :class="{ active: r.active, dim: r.dim }"
@click="store.toggleFacet('languages', r.key)">
<text class="cs-blabel" :x="BAR_X - 4" :y="r.ty" text-anchor="end">{{ trunc(r.label, 13) }}</text>
@ -288,18 +288,18 @@ useShadowStyles(`
<text class="cs-bval" :x="r.vx" :y="r.ty">{{ r.value }}</text>
</g>
</svg>
<p v-else class="cs-empty">no senders</p>
<p v-else class="cs-empty">{{ $t('neighbor.strip.noSenders') }}</p>
</section>
<!-- 2) sender country bar -->
<section class="cs-card">
<header>
<h4>Sender country</h4>
<h4>{{ $t('neighbor.strip.senderCountry') }}</h4>
<button v-if="store.facets.value.countries.size" class="cs-clr"
@click="store.facets.value.countries.forEach(v => store.toggleFacet('countries', v))">clear</button>
@click="store.facets.value.countries.forEach(v => store.toggleFacet('countries', v))">{{ $t('neighbor.strip.clear') }}</button>
</header>
<svg v-if="countryRows.length" class="cs-svg" :viewBox="'0 0 ' + BAR_VB_W + ' ' + barVbH(cd.countryBar)"
preserveAspectRatio="xMinYMin meet" role="img" aria-label="Senders by country">
preserveAspectRatio="xMinYMin meet" role="img" :aria-label="$t('neighbor.strip.byCountry')">
<g v-for="r in countryRows" :key="r.key" class="cs-barrow cs-barrow--country" :class="{ active: r.active, dim: r.dim }"
@click="store.toggleFacet('countries', r.key)">
<text class="cs-blabel" :x="BAR_X - 4" :y="r.ty" text-anchor="end">{{ trunc(r.label, 13) }}</text>
@ -307,17 +307,17 @@ useShadowStyles(`
<text class="cs-bval" :x="r.vx" :y="r.ty">{{ r.value }}</text>
</g>
</svg>
<p v-else class="cs-empty">no senders</p>
<p v-else class="cs-empty">{{ $t('neighbor.strip.noSenders') }}</p>
</section>
<!-- 3) reached% quantile histogram (5 GREEN bins → reached-floor brush) -->
<section class="cs-card">
<header>
<h4>% reached (quantile)</h4>
<button v-if="store.facets.value.reachedMin != null" class="cs-clr" @click="store.setReachedMin(null)">clear</button>
<h4>{{ $t('neighbor.strip.quantile') }}</h4>
<button v-if="store.facets.value.reachedMin != null" class="cs-clr" @click="store.setReachedMin(null)">{{ $t('neighbor.strip.clear') }}</button>
</header>
<svg class="cs-svg" :viewBox="'0 0 ' + HIST_VB_W + ' ' + HIST_VB_H"
preserveAspectRatio="xMidYMid meet" role="img" aria-label="Reached percent histogram">
preserveAspectRatio="xMidYMid meet" role="img" :aria-label="$t('neighbor.strip.histogram')">
<line class="cs-axis" :x1="HIST_L" :y1="HIST_BOT" :x2="HIST_VB_W - 4" :y2="HIST_BOT" />
<g v-for="b in histBars" :key="b.key" class="cs-histbar" :class="{ active: b.active, dim: b.dim }"
@click="onBin(b)">
@ -331,13 +331,13 @@ useShadowStyles(`
<!-- 4) bridge / religion donut -->
<section class="cs-card">
<header>
<h4>{{ store.colorBy.value === 'religion' ? 'Religion family' : 'Bridge type' }}</h4>
<h4>{{ store.colorBy.value === 'religion' ? $t('neighbor.strip.religionFamily') : $t('neighbor.strip.bridgeType') }}</h4>
<button v-if="store.facets.value[donutKind].size" class="cs-clr"
@click="store.facets.value[donutKind].forEach(v => store.toggleFacet(donutKind, v))">clear</button>
@click="store.facets.value[donutKind].forEach(v => store.toggleFacet(donutKind, v))">{{ $t('neighbor.strip.clear') }}</button>
</header>
<div class="cs-donutwrap">
<svg v-if="donutTotal" class="cs-donut" viewBox="0 0 160 160"
preserveAspectRatio="xMidYMid meet" role="img" aria-label="Donut by category">
preserveAspectRatio="xMidYMid meet" role="img" :aria-label="$t('neighbor.strip.donut')">
<path v-for="d in donutArcs" :key="d.key" class="cs-arc" :class="{ active: d.active, dim: d.dim }"
:d="d.path" :fill="d.color" @click="store.toggleFacet(donutKind, d.key)" />
<text class="cs-dctr" x="80" y="84" text-anchor="middle">{{ donutTotal }}</text>
@ -349,7 +349,7 @@ useShadowStyles(`
</li>
</ul>
</div>
<p v-if="!donutTotal" class="cs-empty">no senders</p>
<p v-if="!donutTotal" class="cs-empty">{{ $t('neighbor.strip.noSenders') }}</p>
</section>
</div>
</div>

View file

@ -320,7 +320,7 @@ useShadowStyles(
<div class="cpt-flower" @mousemove="onMove">
<!-- header -->
<div class="cf-bar">
<span class="cf-title">Bloom</span>
<span class="cf-title">{{ $t('neighbor.bloom') }}</span>
<span v-if="placed" class="cf-meta">
<b>{{ placed.center.country }}</b> · {{ placed.center.count }} unreached ·
<b>{{ placed.shown }}</b>/{{ placed.total }} senders

View file

@ -30,6 +30,8 @@ import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { useLanguageFamilyLegendData } from '@map/composables/legends/useLanguageFamilyLegendData.js'
import langFamilyByLanguage from '@map/data/langFamilyByLanguage.json'
import bakedReached from '../data/reached-neighbors.json'
import { useNeighborT } from '../composables/useNeighborT.js'
const nt = useNeighborT()
const C_RED = '#e11d48', C_RED_DK = '#9f1239', C_NAVY = '#002e6e', C_GOLD = '#f59e0b'
function reachedColor(pct) {
@ -45,7 +47,7 @@ const cfg = computed(() => profileConfig?.value || {})
const fcRoot = ref(null)
const mapContainer = ref(null)
const appReady = ref(false)
const statusMsg = ref('Clustering the harvest by language…')
const statusMsg = ref(nt('neighbor.fc.clustering'))
const mode = ref('language') // 'family' | 'language' | 'dialect'
const selectedKey = ref(null)
const doxaFeatures = ref([]) // normalized doxa unreached for the taxonomy composable
@ -333,7 +335,7 @@ const MODES = [
<div v-if="!appReady" class="fc-loading">{{ statusMsg }}</div>
<!-- the 3 cluster-by MODE CIRCLES -->
<div class="fc-modes" role="tablist" aria-label="Cluster by">
<div class="fc-modes" role="tablist" :aria-label="$t('neighbor.fc.clusterBy')">
<button v-for="md in MODES" :key="md.key" class="fc-mode" :class="{ on: mode === md.key }"
role="tab" :aria-selected="mode === md.key" @click="setMode(md.key)">
<span class="fc-circle">{{ md.label.slice(0,3).toUpperCase() }}</span>
@ -343,15 +345,15 @@ const MODES = [
</div>
<div v-if="appReady" class="fc-stats">
<b>{{ stats.cores }}</b> flowers · <b>{{ stats.unreached.toLocaleString() }}</b> unreached<br>
{{ $t('neighbor.fc.flowers') }}: <b>{{ stats.cores }}</b> · {{ $t('neighbor.fc.unreached') }}: <b>{{ stats.unreached.toLocaleString() }}</b><br>
<b>{{ stats.petals.toLocaleString() }}</b> reached petals · by {{ mode }}
</div>
<div v-if="appReady" class="fc-legend">
<h4>Flower clusters</h4>
<div class="fc-lrow"><span class="fc-dot" :style="{ background: C_RED }"></span> Unreached language cluster (core, size = #PGs)</div>
<div class="fc-lrow"><span class="fc-dot" :style="{ background: reachedColor(80) }"></span> Reached neighbor (granular, real coord)</div>
<div class="fc-lrow" style="color:#6b7280;font-size:10px;margin-top:4px">click a top circle to re-cluster · click a core to bloom its petals</div>
<h4>{{ $t('neighbor.fc.legendTitle') }}</h4>
<div class="fc-lrow"><span class="fc-dot" :style="{ background: C_RED }"></span> {{ $t('neighbor.fc.legendCore') }}</div>
<div class="fc-lrow"><span class="fc-dot" :style="{ background: reachedColor(80) }"></span> {{ $t('neighbor.fc.legendPetal') }}</div>
<div class="fc-lrow" style="color:#6b7280;font-size:10px;margin-top:4px">{{ $t('neighbor.fc.legendHelp') }}</div>
</div>
<div v-if="appReady" class="fc-footer">

View file

@ -28,6 +28,8 @@ import {
useCockpitStore, COLORS, GREEN_BIN_LABELS,
reachedColor, quantileBin, bridgeDash, fmtPop
} from './useCockpit.js'
import { useNeighborT } from '../composables/useNeighborT.js'
const nt = useNeighborT()
const store = useCockpitStore()
@ -95,7 +97,7 @@ const exportRows = computed(() => selCount.value ? selectedRows.value : rows.val
function exportCsv() {
if (!exportRows.value.length) return
store.exportSelectionCSV(exportRows.value)
copyFlash.value = 'CSV downloaded (' + exportRows.value.length + ')'
copyFlash.value = nt('neighbor.list.csvDownloaded', { n: exportRows.value.length })
flashClear()
}
@ -138,7 +140,7 @@ async function copyList() {
await navigator.clipboard.writeText(text); ok = true
} else { ok = legacyCopy(text) }
} catch (_) { ok = legacyCopy(text) }
copyFlash.value = ok ? 'Copied (' + list.length + ')' : 'Copy failed — press Ctrl/Cmd-C'
copyFlash.value = ok ? nt('neighbor.list.copied', { n: list.length }) : nt('neighbor.list.copyFailed')
flashClear()
}
@ -291,7 +293,7 @@ useShadowStyles(`
<span class="cpt-l-badge">
<b>{{ availableTotal.toLocaleString() }}</b> senders available
</span>
<span v-if="hiddenCount" class="cpt-l-hidden">· {{ hiddenCount.toLocaleString() }} hidden by filters</span>
<span v-if="hiddenCount" class="cpt-l-hidden">{{ $t('neighbor.list.hidden', { n: hiddenCount.toLocaleString() }) }}</span>
</div>
<div class="cpt-l-sorts" v-if="focusedTarget">
<button
@ -314,8 +316,8 @@ useShadowStyles(`
</label>
<span class="cpt-l-spacer"></span>
<span class="cpt-l-flash" :class="{ warn: copyFlash.indexOf('fail') !== -1 }">{{ copyFlash }}</span>
<button class="cpt-l-btn ghost" type="button" v-if="selCount" @click="clearSelection">Clear</button>
<button class="cpt-l-btn" type="button" :disabled="!exportRows.length" @click="copyList">Copy</button>
<button class="cpt-l-btn ghost" type="button" v-if="selCount" @click="clearSelection">{{ $t('neighbor.list.clear') }}</button>
<button class="cpt-l-btn" type="button" :disabled="!exportRows.length" @click="copyList">{{ $t('neighbor.list.copy') }}</button>
<button class="cpt-l-btn primary" type="button" :disabled="!exportRows.length" @click="exportCsv">
Export {{ selCount ? 'selection' : 'all' }} · CSV
</button>

View file

@ -458,13 +458,13 @@ useShadowStyles(`
<!-- on-map controls: exposes color-by (8) + province drill (9) + reset focus -->
<div v-if="ready" class="cpt-mc-ctl">
<div class="cpt-mc-seg" role="group" aria-label="Green color encoding">
<button :class="{ on: store.colorBy.value === 'pct' }" @click="store.setColorBy('pct')">% reached</button>
<button :class="{ on: store.colorBy.value === 'religion' }" @click="store.setColorBy('religion')">religion</button>
<div class="cpt-mc-seg" role="group" :aria-label="$t('neighbor.mc.greenEncoding')">
<button :class="{ on: store.colorBy.value === 'pct' }" @click="store.setColorBy('pct')">{{ $t('neighbor.strip.pctReached') }}</button>
<button :class="{ on: store.colorBy.value === 'religion' }" @click="store.setColorBy('religion')">{{ $t('neighbor.strip.religion') }}</button>
</div>
<button class="cpt-mc-btn" :class="{ on: store.provinceDrill.value }" @click="store.toggleProvince()"
title="Overlay IN/CN admin-1 boundaries">IN/CN provinces</button>
<button v-if="store.focusedTarget.value" class="cpt-mc-btn gold" @click="store.clearFocus()">✕ reset focus</button>
:title="$t('neighbor.mc.provincesTitle')">{{ $t('neighbor.mc.provinces') }}</button>
<button v-if="store.focusedTarget.value" class="cpt-mc-btn gold" @click="store.clearFocus()">{{ $t('neighbor.mc.resetFocus') }}</button>
</div>
<!-- SEND headline when focused -->
@ -472,8 +472,8 @@ useShadowStyles(`
<!-- token/load hint (map never initialized — store still drives the other cells) -->
<div v-if="!ready" class="cpt-mc-loading">
<span v-if="tokenMissing">Map needs a Mapbox token (tk in profile-config)…</span>
<span v-else>Rendering the harvest field…</span>
<span v-if="tokenMissing">{{ $t('neighbor.mc.needsToken') }}</span>
<span v-else>{{ $t('neighbor.mc.rendering') }}</span>
</div>
<!-- hover-pin tooltip -->

View file

@ -0,0 +1,19 @@
// useNeighborT — My UPG Neighbors' UI text: library/i18n/locales/<lc>/neighbor.json (11 locales).
// Chrome only, by decision (dx-shepherd, 2026-09-26): research terms, provenance lines and proper
// nouns (SEND, doxa-2000, Bounded set, JP, PPLR…) stay in English as named sources.
// i18n is OPTIONAL: useI18n() throws on a host that never installed it, so the English catalog is
// the fallback, never a blank or a raw key. Call it inside setup().
import { useI18n } from 'vue-i18n'
import enNeighbor from '@map/i18n/locales/en/neighbor.json'
const enLookup = (key) => String(key).replace(/^neighbor\./, '').split('.').reduce((o, k) => (o == null ? o : o[k]), enNeighbor)
const fill = (str, params) => (params ? String(str).replace(/\{(\w+)\}/g, (m, k) => (k in params ? params[k] : m)) : str)
export function useNeighborT() {
try {
const i18n = useI18n()
return (key, params) => { const v = i18n.t(key, params || {}); return v === key ? fill(enLookup(key) ?? key, params) : v }
} catch (_) {
return (key, params) => fill(enLookup(key) ?? String(key), params)
}
}

View file

@ -261,9 +261,9 @@ function onNodeEnter(p, ev) {
x: ev.clientX, y: ev.clientY,
html: `<strong>${esc(n.name)}</strong>`
+ `<div class="fm-tip-sub">${esc(n.country)}${prov ? ' · ' + esc(prov) : ''}</div>`
+ `<div class="fm-tip-row"><span class="fm-tip-k">reached</span> ${Math.round(n.pct_christian || 0)}% Christian</div>`
+ `<div class="fm-tip-row"><span class="fm-tip-k">${nt('neighbor.reached')}</span> ${nt('neighbor.pctChristian', { n: Math.round(n.pct_christian || 0) })}</div>`
+ `<div class="fm-tip-row"><span class="fm-tip-k">language</span> ${esc(n.lang || '—')}</div>`
+ `<div class="fm-tip-row"><span class="fm-tip-k">people</span> ${fmtPop(n.pop)}</div>`
+ `<div class="fm-tip-row"><span class="fm-tip-k">${nt('neighbor.people')}</span> ${fmtPop(n.pop)}</div>`
+ `<div class="fm-tip-row"><span class="fm-tip-k">bridge</span> ${esc(n.bridge || '—')}</div>`
+ `<div class="fm-tip-row"><span class="fm-tip-k">near</span> ${esc(n.adjacency || '—')}</div>`
}
@ -286,6 +286,8 @@ function esc(s) {
// platform gem-frame (it worked in `npm run dev` only — Vite serves the source data dir there). A
// profile-config `dataUrl` still overrides live. Same harness-vs-platform class as the ProfileLoader fix.
import bakedReached from './data/reached-neighbors.json'
import { useNeighborT } from './composables/useNeighborT.js'
const nt = useNeighborT()
async function loadDataset() {
const cfgUrl = cfg.value.dataUrl
if (cfgUrl) {
@ -314,7 +316,7 @@ async function enrichCenterLive() {
onMounted(async () => {
dataset.value = await loadDataset()
if (!dataset.value) {
loadError.value = 'Could not load reached-neighbors.json'
loadError.value = nt('neighbor.fm.loadError')
appReady.value = true
return
}
@ -414,26 +416,26 @@ useShadowStyles(`
<div class="fm-root" @mousemove="onMove">
<!-- top bar -->
<div class="fm-top">
<span class="fm-title">My UPG Neighbor · Flower-Mesh</span>
<span class="fm-title">{{ $t('neighbor.title.flowerMesh') }}</span>
<select v-if="targetNames.length" class="fm-select"
:value="activeTarget" @change="onSelectTarget"
aria-label="Select unreached target country">
:aria-label="$t('neighbor.selectTarget')">
<option v-for="name in targetNames" :key="name" :value="name">{{ name }}</option>
</select>
<span class="fm-toggle" :class="{ on: showMesh }" role="switch" :aria-checked="showMesh"
tabindex="0" @click="showMesh = !showMesh"
@keydown.enter.prevent="showMesh = !showMesh" @keydown.space.prevent="showMesh = !showMesh">
<span class="fm-sw"></span> language mesh
<span class="fm-sw"></span> {{ $t('neighbor.fm.languageMesh') }}
</span>
<span v-if="targetRec" class="fm-stats">
<b>{{ stats.total }}</b> reached neighbors · <b>{{ stats.countries }}</b> countries<br>
<b>{{ fmt(center.upg) }}</b> UPGs in center · <b>{{ stats.meshes }}</b> mesh links
{{ $t('neighbor.fm.reachedNeighbors') }}: <b>{{ stats.total }}</b> · {{ $t('neighbor.fm.countries') }}: <b>{{ stats.countries }}</b><br>
{{ $t('neighbor.fm.upgsInCenter') }}: <b>{{ fmt(center.upg) }}</b> · {{ $t('neighbor.fm.meshLinks') }}: <b>{{ stats.meshes }}</b>
</span>
</div>
<!-- the flower -->
<div class="fm-stage">
<div v-if="!appReady" class="fm-loading">Growing the flower…</div>
<div v-if="!appReady" class="fm-loading">{{ $t('neighbor.fm.growing') }}</div>
<div v-else-if="loadError" class="fm-loading">{{ loadError }}</div>
<svg v-else class="fm-svg" :viewBox="`0 0 ${VB} ${VB}`"
@ -469,7 +471,7 @@ useShadowStyles(`
:class="{ dim: !isNodeLit(p), sel: p.id === selectedId }"
@click.stop="onNodeClick(p)"
@mouseenter="onNodeEnter(p, $event)" @mouseleave="onNodeLeave">
<title>{{ p.node.name }} — {{ Math.round(p.node.pct_christian || 0) }}% Christian, {{ p.node.lang }}</title>
<title>{{ p.node.name }} — {{ $t('neighbor.pctChristian', { n: Math.round(p.node.pct_christian || 0) }) }}, {{ p.node.lang }}</title>
<circle :cx="p.x" :cy="p.y" :r="p.r" :fill="p.color" />
<text v-if="p.r >= 9 || p.id === selectedId" class="fm-node-label"
:x="p.x" :y="p.y + p.r + 14">{{ p.node.name }}</text>
@ -481,20 +483,20 @@ useShadowStyles(`
<circle class="fm-center-glow" :cx="CX" :cy="CY" :r="R_CTR + 26" />
<circle :cx="CX" :cy="CY" :r="R_CTR" />
<text class="fm-center-name" :x="CX" :y="CY - 2">{{ center.name }}</text>
<text class="fm-center-sub" :x="CX" :y="CY + 16">{{ fmt(center.upg) }} UPGs</text>
<text class="fm-center-sub" :x="CX" :y="CY + 30" style="opacity:.85">{{ fmtPop(center.pop) }} people</text>
<text class="fm-center-sub" :x="CX" :y="CY + 16">{{ $t('neighbor.fm.upgs', { n: fmt(center.upg) }) }}</text>
<text class="fm-center-sub" :x="CX" :y="CY + 30" style="opacity:.85">{{ $t('neighbor.fm.people', { n: fmtPop(center.pop) }) }}</text>
</g>
</svg>
<!-- legend -->
<div v-if="appReady && !loadError" class="fm-legend">
<h4>The flower</h4>
<div class="fm-lrow"><span class="fm-dot" :style="{ background: C_ORANGE }"></span> Unreached target (doxa-2000)</div>
<div class="fm-lrow"><span class="fm-dot" :style="{ background: reachedColor(90) }"></span> Reached neighbor (deeper = more Christian)</div>
<div class="fm-lrow"><span class="fm-line" style="border-color:#5b7fd0"></span> SEND path → into the unreached</div>
<div class="fm-lrow"><span class="fm-line" style="border-color:#3a4f86;border-top-style:dashed"></span> via related language cluster</div>
<div class="fm-lrow"><span class="fm-line" style="border-color:#ffae66"></span> Same-language collaboration mesh</div>
<div class="fm-lrow" style="color:#7e90b6;margin-top:5px;font-size:10px">inner whorl = same country · petals = surrounding countries</div>
<h4>{{ $t('neighbor.fm.legendTitle') }}</h4>
<div class="fm-lrow"><span class="fm-dot" :style="{ background: C_ORANGE }"></span> {{ $t('neighbor.fm.unreachedTarget') }}</div>
<div class="fm-lrow"><span class="fm-dot" :style="{ background: reachedColor(90) }"></span> {{ $t('neighbor.fm.reachedDeeper') }}</div>
<div class="fm-lrow"><span class="fm-line" style="border-color:#5b7fd0"></span> {{ $t('neighbor.fm.sendPath') }}</div>
<div class="fm-lrow"><span class="fm-line" style="border-color:#3a4f86;border-top-style:dashed"></span> {{ $t('neighbor.fm.viaCluster') }}</div>
<div class="fm-lrow"><span class="fm-line" style="border-color:#ffae66"></span> {{ $t('neighbor.fm.mesh') }}</div>
<div class="fm-lrow" style="color:#7e90b6;margin-top:5px;font-size:10px">{{ $t('neighbor.fm.whorl') }}</div>
</div>
</div>

View file

@ -65,7 +65,7 @@ const mapId = (injectedInstanceId?.value || injectedInstanceId) ||
const lcRoot = ref(null)
const mapContainer = ref(null)
const appReady = ref(false)
const statusMsg = ref('Loading people groups…')
const statusMsg = ref(nt('neighbor.lc.loading'))
const clusters = ref([]) // [{key,lang,country,province,scope,count,lat,lng,reached:[]}]
const reachedAll = ref([]) // flat reached list with placement coords
const selectedKey = ref(null)
@ -382,6 +382,8 @@ function onMove(ev) { if (tip.value.show) { tip.value.x = ev.clientX; tip.value.
// adminUrl/dataUrl/dataBase still override live (loadJSON below).
import bakedReached from './data/reached-neighbors.json'
import bakedAdmin1 from './data/admin1-membership.json'
import { useNeighborT } from './composables/useNeighborT.js'
const nt = useNeighborT()
async function loadJSON(relCandidates) {
for (const url of relCandidates) {
try { const r = await fetch(url); if (r.ok) return await r.json() } catch (_) {}
@ -417,7 +419,7 @@ onMounted(async () => {
// live unreached doxa-2000
let records = []
try { records = await fetchDoxa() }
catch (e) { statusMsg.value = 'Live doxa API unreachable — ' + e.message; appReady.value = true; return }
catch (e) { statusMsg.value = nt('neighbor.lc.apiDown', { message: e.message }); appReady.value = true; return }
clusters.value = buildClusters(records, adminProv)
reachedAll.value = buildReached(reachedJson, clusters.value)
@ -446,11 +448,11 @@ onMounted(async () => {
})
}
hover(L_CL, p => `<strong>${esc(p.lang)}</strong><div class="lc-tip-sub">${esc(p.key)}</div>`
+ `<div class="lc-tip-row"><span class="lc-tip-k">unreached</span> ${p.count} groups</div>`
+ `<div class="lc-tip-row"><span class="lc-tip-k">${nt('neighbor.unreached')}</span> ${nt('neighbor.groups', { n: p.count })}</div>`
+ `<div class="lc-tip-row"><span class="lc-tip-k">reached</span> ${p.hasReach ? 'matching neighbors ✓' : 'none found'}</div>`
+ `<div class="lc-tip-row" style="color:#9fb3d9">click → bounded set + flower</div>`)
hover(L_RE, p => `<strong>${esc(p.name)}</strong><div class="lc-tip-sub">${esc(p.country)} · ${esc(p.lang)}</div>`
+ `<div class="lc-tip-row"><span class="lc-tip-k">reached</span> ${Math.round(p.pct)}% Christian</div>`)
+ `<div class="lc-tip-row"><span class="lc-tip-k">${nt('neighbor.reached')}</span> ${nt('neighbor.pctChristian', { n: Math.round(p.pct) })}</div>`)
}
if (m?.isStyleLoaded?.()) wire()
else m?.once?.('load', wire)
@ -524,19 +526,19 @@ useShadowStyles(`
<!-- top bar -->
<div class="lc-top">
<span class="lc-title">My UPG Neighbor · Language Clusters</span>
<span class="lc-title">{{ $t('neighbor.title.langClusters') }}</span>
<span v-if="appReady" class="lc-stats">
<b>{{ stats.clusters }}</b> language clusters · <b>{{ stats.pgs }}</b> unreached<br>
<b>{{ stats.provinces }}</b> IN/CN provinces · <b>{{ stats.withReach }}</b> with reached match
{{ $t('neighbor.lc.clusters') }}: <b>{{ stats.clusters }}</b> · {{ $t('neighbor.lc.pgs') }}: <b>{{ stats.pgs }}</b><br>
{{ $t('neighbor.lc.provinces') }}: <b>{{ stats.provinces }}</b> · {{ $t('neighbor.lc.withReach') }}: <b>{{ stats.withReach }}</b>
</span>
</div>
<!-- legend -->
<div v-if="appReady" class="lc-legend">
<h4>Bounded sets</h4>
<div class="lc-lrow"><span class="lc-dot" :style="{ background: C_RED }"></span> Unreached language cluster (size = #groups)</div>
<div class="lc-lrow"><span class="lc-dot" :style="{ background: reachedColor(80) }"></span> Reached neighbor (matched by language)</div>
<div class="lc-lrow" style="color:#6b7280;font-size:10px;margin-top:4px">green ring = cluster has a reached match · click a cluster to drill</div>
<div class="lc-lrow"><span class="lc-dot" :style="{ background: C_RED }"></span> {{ $t('neighbor.lc.legendCluster') }}</div>
<div class="lc-lrow"><span class="lc-dot" :style="{ background: reachedColor(80) }"></span> {{ $t('neighbor.lc.legendReached') }}</div>
<div class="lc-lrow" style="color:#6b7280;font-size:10px;margin-top:4px">{{ $t('neighbor.lc.legendHelp') }}</div>
</div>
<!-- selected bounded-set panel -->
@ -546,18 +548,18 @@ useShadowStyles(`
<div class="lc-h-sub">{{ selected.scope === 'province' ? selected.province + ' · ' + selected.country : selected.country }}</div>
</div>
<div class="lc-panel-body">
<div class="lc-sec">Bounded set — {{ selected.count }} unreached groups</div>
<div class="lc-sec">{{ $t('neighbor.lc.setCount', { n: selected.count }) }}</div>
<span v-for="(m, i) in selected.members.slice(0, 18)" :key="i" class="lc-chip">{{ m.name }}</span>
<span v-if="selected.members.length > 18" class="lc-chip">+{{ selected.members.length - 18 }} more</span>
<span v-if="selected.members.length > 18" class="lc-chip">{{ $t('neighbor.lc.more', { n: selected.members.length - 18 }) }}</span>
<div class="lc-sec" style="margin-top:10px">Reached neighbors (same language) — {{ selected.reached.length }}</div>
<div v-if="!selected.reached.length" class="lc-muted">No reached people-group shares this language in the baked set. A frontier — pray for laborers (Luke 10:2).</div>
<div class="lc-sec" style="margin-top:10px">{{ $t('neighbor.lc.reachedSame', { n: selected.reached.length }) }}</div>
<div v-if="!selected.reached.length" class="lc-muted">{{ $t('neighbor.lc.frontier') }}</div>
<div v-for="(r, i) in selected.reached" :key="'r'+i" class="lc-re">
<div class="lc-re-name"><span class="lc-dot" :style="{ background: reachedColor(r.pct) }"></span>{{ r.name }}</div>
<div class="lc-re-meta">{{ r.country }} · {{ Math.round(r.pct) }}% Christian · {{ r.lang }}</div>
<div class="lc-re-meta">{{ r.country }} · {{ $t('neighbor.pctChristian', { n: Math.round(r.pct) }) }} · {{ r.lang }}</div>
</div>
<button class="lc-drill" @click="drillToFlower">Open flower view ▸</button>
<button class="lc-drill" @click="drillToFlower">{{ $t('neighbor.lc.openFlower') }}</button>
</div>
</div>

View file

@ -459,14 +459,14 @@ onBeforeUnmount(() => { destroy() })
<div class="sn-map-area">
<div ref="mapContainer" class="sn-map-canvas"></div>
<div v-if="!appReady" class="sn-loading">Loading SEND map…</div>
<div v-if="!appReady" class="sn-loading">{{ $t('neighbor.loadingSend') }}</div>
<!-- Top: title + target selector -->
<div class="sn-top">
<span class="sn-title">My UPG Neighbor · SEND</span>
<span class="sn-title">{{ $t('neighbor.title.send') }}</span>
<select v-if="targetNames.length" class="sn-select"
:value="activeTarget" @change="onSelectTarget"
aria-label="Select unreached target country">
:aria-label="$t('neighbor.selectTarget')">
<option v-for="name in targetNames" :key="name" :value="name">{{ name }}</option>
</select>
</div>
@ -479,8 +479,8 @@ onBeforeUnmount(() => { destroy() })
<div class="sn-legend-row"><span class="sn-num">3·</span><span><strong>Reached %</strong> — sender ≥10% Christian (more = more laborers)</span></div>
<div class="sn-legend-row"><span class="sn-num">4·</span><span><strong>Lower persecution</strong> — sender from a freer place</span></div>
<div style="border-top:1px solid #e5e7eb;margin:7px 0 5px;"></div>
<div class="sn-legend-row"><span class="sn-dot" :style="{ background: C_ORANGE }"></span><span>Unreached target — <strong>click one</strong> to SEND-focus</span></div>
<div class="sn-legend-row"><span class="sn-dot" :style="{ background: C_GREEN }"></span><span>Reached sender (SEND →)</span></div>
<div class="sn-legend-row"><span class="sn-dot" :style="{ background: C_ORANGE }"></span><span>{{ $t('neighbor.send.unreachedTarget') }} <strong>{{ $t('neighbor.send.clickOne') }}</strong> {{ $t('neighbor.send.toFocus') }}</span></div>
<div class="sn-legend-row"><span class="sn-dot" :style="{ background: C_GREEN }"></span><span>{{ $t('neighbor.send.reachedSender') }}</span></div>
</div>
<!-- Right: worked sender table for the selected target -->
@ -499,7 +499,7 @@ onBeforeUnmount(() => { destroy() })
{{ n.name }}
</div>
<div class="sn-sender-meta">
<span class="sn-chip pct">{{ Math.round(n.pct_christian || 0) }}% Christian</span>
<span class="sn-chip pct">{{ $t('neighbor.pctChristian', { n: Math.round(n.pct_christian || 0) }) }}</span>
<span class="sn-chip bridge">{{ n.bridge }}</span>
<span class="sn-chip adj">{{ n.adjacency }}</span>
<span class="sn-chip">{{ n.lang }}</span>

View file

@ -29,6 +29,8 @@ import SendMap from './src/send-map.vue'
import FlowerMesh from './src/flower-mesh.vue'
import LangCluster from './src/lang-cluster.vue'
import FlowerClusters from './src/cockpit/FlowerClusters.vue'
import { useNeighborT } from './src/composables/useNeighborT.js'
const nt = useNeighborT()
const profileConfig = inject('profileConfig')
const cfg = computed(() => profileConfig?.value || {})
@ -82,17 +84,18 @@ useShadowStyles(`
@media (max-width:640px){ .dun-hint{ display:none; } .dun-brand{ display:none; } }
`, 'upg-neighbor')
const activeHint = computed(() => VIEWS.find(v => v.key === view.value)?.hint || '')
const VIEW_KEY = { 'cockpit': 'cockpit', 'send-map': 'sendMap', 'flower-mesh': 'flowerMesh', 'lang-cluster': 'langCluster', 'flowers': 'flowers' }
const activeHint = computed(() => nt('neighbor.hints.' + (VIEW_KEY[view.value] || 'sendMap')))
</script>
<template>
<div ref="rootEl" class="dun-root">
<!-- view-switcher -->
<div class="dun-bar">
<span class="dun-brand">My UPG Neighbors</span>
<div class="dun-seg" role="tablist" aria-label="Choose a view">
<span class="dun-brand">{{ $t('neighbor.brand') }}</span>
<div class="dun-seg" role="tablist" :aria-label="$t('neighbor.chooseView')">
<button v-for="v in VIEWS" :key="v.key" role="tab" :aria-selected="view === v.key"
:class="{ on: view === v.key }" @click="setView(v.key)">{{ v.label }}</button>
:class="{ on: view === v.key }" @click="setView(v.key)">{{ $t('neighbor.views.' + VIEW_KEY[v.key]) }}</button>
</div>
<span class="dun-hint">{{ activeHint }}</span>
</div>