A bundle carries a translation area only where its code uses it, not wherever the area's name appears

i18n-areas kept an area whenever its name showed up as any string literal
in a bundle's source graph, to cover keys assembled at runtime. But
'gospel', 'religion', 'regions', 'detail', 'poster' and others are also
ordinary ids (a colour strategy, a data source, a field, a mode), so every
bundle importing those modules carried catalogues it never shows, in all
11 locales: 80 area inclusions across 26 bundles, and gospel's catalogue
alone pushed country-posters 9.5 KB over the ceiling.

A bare name now counts only where it really builds a key: a namespace
helper (useOptionalT, useOptionalI18n, qualify) or a translator reading a
whole area (tm('regions') in useRegionsI18n is the one in use). Key
prefixes ('area.' / `area.${x}`) count as before. A bundle that assembles a
key with a leading `${…}` keeps the old generous rule, since its area
cannot be read from the source.
This commit is contained in:
Doxa Maps 2026-09-29 08:53:26 +00:00
parent 6416412891
commit 89471b2880

View file

@ -97,9 +97,21 @@ export function areasForBundle(root, bundle, { extra = [] } = {}) {
const files = bundleGraph(root, bundle)
const text = files.map((f) => { try { return readFileSync(f, 'utf8') } catch { return '' } }).join('\n')
const used = new Set(extra)
// ★ A BARE NAME COUNTS ONLY WHERE IT BUILDS A KEY. The first version kept an area wherever its name
// appeared as ANY string literal, to cover keys assembled at runtime. But several area names are also
// ordinary ids — 'gospel' is a colour strategy, a data source and a chart id; 'religion', 'regions',
// 'detail', 'poster' are fields and modes — so every bundle importing those modules carried catalogues
// it never shows: measured, 80 area inclusions across 26 bundles, and gospel's catalogue alone pushed
// country-posters 9.5 KB over the 1.5 MB ceiling. The ways a bare name really does build a key here are
// few and named: a namespace helper (useOptionalT / useOptionalI18n / qualify) or a translator reading a
// whole area (tm / t / te / tStatic / rt with the name alone). A bundle whose code assembles a key with a
// leading `${…}` keeps the old, generous rule, since its area cannot be read from the source.
const assemblesUnknownKeys = /\b(?:t|te|tm|tStatic|T)\(\s*`\$\{/.test(text)
for (const a of areas) {
const esc = a.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
if (new RegExp(`['"\`]${esc}[.'"\`]`).test(text)) used.add(a)
if (new RegExp(`['"\`]${esc}\\.`).test(text)) { used.add(a); continue } // 'area.key'
if (new RegExp(`\\b(?:useOptionalT|useOptionalI18n|qualify|tm|t|te|tStatic|rt)\\(\\s*['"\`]${esc}['"\`]`).test(text)) { used.add(a); continue }
if (assemblesUnknownKeys && new RegExp(`['"\`]${esc}['"\`]`).test(text)) used.add(a)
}
return { areas: [...used].filter((a) => areas.includes(a)).sort(), files: files.map((f) => relative(root, f)) }
}