Rank the translation priority by the place a team is sent

"Can hear it, no text" is a list of 2,556 groups. A team is not sent to
a group, it is sent to a country, and nothing aggregated that set —
the existing country table counts the groups with NOTHING, which is a
different population and does not contain one of these rows, because
none of them is tier 0.

154 countries, with two columns that disagree on purpose. Nigeria and
the DRC lead on LANGUAGES, 221 and 103, which is the translation
workload. China and Pakistan lead on PEOPLE, 218 and 195 million,
across far fewer languages. Which number matters depends on whether
you are funding translators or broadcasters, so both are shown and
neither is ranked for the reader.

The aggregate reconciles with its source exactly — 2,556 groups and
1,329,044,699 people both ways — and the basis travels as a column, so
a CSV that outlives this page still says "no text found in the pools
checked" rather than "no text exists".

Built without a browser: the box was low on memory this tick and a
gate that needs chromium was the wrong thing to add to it.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 22:29:38 +00:00
parent cc3ab8b822
commit 4b4cb30eb8
12 changed files with 3320 additions and 13 deletions

File diff suppressed because it is too large Load diff

View file

@ -51,6 +51,10 @@ const TABS = [
// no text to read. It was selectable on the map and nowhere else, and a filter cannot be
// sorted, exported, or handed to a committee — which is precisely what this list is for.
{ id: 'translation', label: 'Can Hear It, No Text', file: 'translation-priority' },
// The same population aggregated to the place a team is SENT. Its two columns disagree on
// purpose: Nigeria and the DRC lead on languages, which is the translation workload, while
// China and Pakistan lead on people across far fewer languages.
{ id: 'translationcountry', label: 'Where to Translate', file: 'translation-priority-by-country' },
// GAPS & OPPORTUNITIES leads: it is the table that answers "where can the work
// actually happen", which is the whole point of the corpus.
{ id: 'gaps', label: 'Gaps & Opportunities', file: 'gaps-and-opportunities' },

View file

@ -0,0 +1,111 @@
/**
* build-translation-priority-by-country.mjs — where to SEND a translation team.
*
* WHY THIS IS NOT THE COUNTRY GAP TABLE. `country-gap` aggregates the groups with NOTHING —
* where to send a first messenger. This aggregates the opposite population: groups that can
* already hear the gospel and still have no text to read. They are not in that table at all,
* because none of them is tier 0, and a country can rank high here while looking well served
* there.
*
* TWO COLUMNS THAT DISAGREE, ON PURPOSE. Nigeria and the DRC hold the most LANGUAGES in this
* set — 221 and 103 — which is the translation workload. China and Pakistan hold the most
* PEOPLE — 218 and 195 million — across far fewer languages. Which number matters depends
* entirely on whether you are funding translators or broadcasters, so both are here and
* neither is chosen for the reader. Sorted by people because that is the commoner question,
* not because it is the truer one.
*
* The caveat travels with the data: "no text found" means not found in the pools checked, and
* the per-row table carries the checked and unchecked pools for exactly that reason. An
* aggregate cannot carry them row by row, so it says so in its meta and in a column that
* survives a CSV export.
*/
import { readFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { writeSnapshot } from './_snapshot-guard.mjs'
const HERE = dirname(fileURLToPath(import.meta.url))
const REPO = resolve(HERE, '../../..')
const SRC = resolve(REPO, 'data/tables/translation-priority.json')
const OUT = resolve(REPO, 'data/tables/translation-priority-by-country.json')
const src = JSON.parse(readFileSync(SRC, 'utf8'))
const by = new Map()
for (const r of src.rows || []) {
const iso3 = r.iso3 || ''
if (!iso3) continue
let e = by.get(iso3)
if (!e) {
e = { iso3, country: r.country || iso3, groups: 0, languages: new Set(), people: 0, largest: '', largestPop: 0 }
by.set(iso3, e)
}
e.groups += 1
e.languages.add(r.iso639_3)
e.people += r.population || 0
if ((r.population || 0) > e.largestPop) {
e.largestPop = r.population || 0
e.largest = r.people_group || ''
}
}
const rows = [...by.values()]
.map((e) => ({
country: e.country,
iso3: e.iso3,
groups: e.groups,
languages: e.languages.size,
people: e.people,
largest_group: e.largest,
largest_group_population: e.largestPop,
basis: 'no text Scripture found in the pools checked',
}))
.sort((a, b) => b.people - a.people)
writeSnapshot(
OUT,
`${JSON.stringify(
{
meta: {
title: 'Translation priority by country — can hear it, no text found',
source: `data/tables/translation-priority.json, itself built from the pin index — so this
table, that one and the map cannot disagree.`.replace(/\s+/g, ' '),
generated: new Date().toISOString().slice(0, 10),
countries: rows.length,
groups: rows.reduce((n, r) => n + r.groups, 0),
people: rows.reduce((n, r) => n + r.people, 0),
definition:
'People groups with recorded audio, the JESUS Film, a Bible video or Words of Life ' +
'in their language and NO text Scripture found in the pools checked, aggregated to ' +
'the country a team would be sent to. "Not found" is a statement about those pools, ' +
'never a claim that no text exists; the per-group table carries the checked and ' +
'unchecked pools on every row.',
two_columns_note:
'groups and languages answer different questions from people. Nigeria and the DRC ' +
'lead on languages, which is the translation workload; China and Pakistan lead on ' +
'population across far fewer languages. Neither is ranked for you.',
},
columns: [
{ title: 'Country', field: 'country', type: 'string' },
{ title: 'ISO3', field: 'iso3', type: 'string' },
{ title: 'Groups', field: 'groups', type: 'number' },
{ title: 'Languages', field: 'languages', type: 'number' },
{ title: 'People', field: 'people', type: 'number' },
{ title: 'Largest group', field: 'largest_group', type: 'string' },
{ title: 'Its population', field: 'largest_group_population', type: 'number' },
{ title: 'Basis', field: 'basis', type: 'string' },
],
rows,
},
null,
1,
)}\n`,
(j) => (j.rows || []).length,
'translation-priority-by-country row(s)',
)
console.log(
`translation-priority-by-country: ${rows.length} countries · ` +
`${rows.reduce((n, r) => n + r.groups, 0)} groups · ` +
`${rows.reduce((n, r) => n + r.people, 0).toLocaleString()} people`,
)
console.log(`wrote ${OUT}`)

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -596,7 +596,7 @@
"bundle": "gospel-resources",
"scriptUrl": "./gospel-resources/gospel-resources.js",
"output": "doxa-maps/gospel-resources/gospel-resources.js",
"integrity": "sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge",
"integrity": "sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha",
"elements": [
"gospel-resources-map"
],
@ -616,7 +616,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/charts/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -630,7 +630,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -644,7 +644,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/gospel-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -658,7 +658,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -672,7 +672,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-8UEOnYeQjLWBgc3P5zyoMIifZM5k/vOhFWUXzLAaJWoYE66hGZ21nXf3rRPpRWge\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-5yvjn7r2q52JpjK/dNXfEXIEMZpaA5MmdNN3aXb7TlBkHGULcAYtFcBnHifcDjha\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
}
]