Manifest
Read this first: it lists the available languages, domains (civics / reading / writing), editions, test rules (including the 65/20 exemption), and states. Every other path is derived from it.
Dynamic data (language-neutral)
Proper nouns are identical in every language, so they live once here and carry an as_of date. Election updates touch only these files.
- /data/federal.json — national office holders
- /data/states.json — per-state facts, keyed by code (with special cases for D.C./territories)
- /data/distractors.json — proper-noun decoy pools
Question skeletons (language-neutral structure)
Structure only — ids, types, categories, the senior (65/20) flag, num_required, and references into the data files. No translatable text.
- /questions/civics/2025.json — 2025 civics skeleton
- /questions/civics/2008.json — 2008 civics skeleton
- /questions/reading/vocab.json — English read-aloud content
- /questions/writing/vocab.json — English dictation content
Translations (text overlays)
Each language adds only translatable text: UI labels plus civics overlays keyed by question id. Reading/writing content is English-only; only its instructions are translated (in ui.json).
Path convention
Files follow a predictable pattern, so the app builds URLs from the manifest:
/data/<federal|states|distractors>.json— shared, language-neutral/questions/<domain>/<edition>.json— language-neutral skeletons/i18n/<lang>/ui.json/i18n/<lang>/civics/<edition>.json— text overlay keyed by question id
At load the app merges a skeleton with the selected language's overlay. Anything missing for a language falls back to the manifest's default_language. Run node schema/validate.mjs to check cross-file integrity.