Contents
NipponDailyThe words

Chapter 9

Data integrity

NipponDaily teaches Japanese, so a wrong reading, meaning or origin is a bug, not a typo. Accuracy comes from sources, never from a person's or a model's memory. The only hand-written text is a headline; everything else is generated by pnpm data:words from JMdict, KANJIDIC2, pinned Wiktionary text, a pinned Tatoeba export and a pinned pitch accent list that a second dictionary has to bear out.

Where the data comes from

DataSourceLives in
JLPT vocabulary (N5–N1)elzup/jlpt-word-list (one CSV per level), cross-referenced against the JMdict and KANJIDIC2 files EDRDG publishes, pinned by size and checksum data/reference/n{5,4,3,2,1}-reference.json
OriginsEnglish Wiktionary Etymology sections, read from one pinned, dated dump (via Kaikki.org) data/reference/etymology/
Example sentencesTatoeba sentences with an English translation that its own index lists under the word, read from one pinned, dated export; the readings over their kanji are Tatoeba's own furigana of each sentence, each checked against KANJIDIC2 or JMdict and kept only where a contributor wrote it or kuromoji, UniDic or Tatoeba's index agrees data/reference/sentences/
Pitch accent The accent list of Kanjium, read at one pinned commit: the mora after which the pitch falls, for a word whose exact spelling and reading are both on a line of it. An accent is kept only where UniDic, which annotates its own accent types, gives the same one for that spelling and reading; the snapshot lists the words that lost one data/reference/pitch.json
Kanji pages KANJIDIC2's record of every kanji the entries are written with, copied from the level snapshots data/reference/kanji.json
Stroke orderKanjiVG strokes in drawing order, read from one pinned release and kept for a kanji only where KANJIDIC2 counts the same number of strokes data/reference/strokes.json
Shapes inside a kanji The KRADFILE of EDRDG, read from one pinned archive: the radicals and other elements each kanji is listed as containing, in the file's own notation (a lookup aid, not an etymology) data/reference/components.json
Daily words Generated from the word-list snapshots, the Etymology pins, the sentence snapshot and the pitch snapshot, plus the hand-written headlines in data/word-plan/data/words/YYYY-MM.json

The evidence is committed, because dictionary data isn't in the repo's tests and etymology isn't in a dictionary at all. CI checks every entry against it, offline. Word-list commits are pinned in scripts/word-list-source.mjs, and the JMdict and KANJIDIC2 files in scripts/lib/jmdict.mjs (EDRDG overwrites both every day, so a pin is the only way to rebuild the same data, and the files are kept beside the code, git-ignored), so no builder can fetch different upstream data for the same level. The Wiktionary dump's date and checksum are pinned in scripts/lib/wiktionary-dump.mjs; the dump itself is too large to commit, so only the sections the entries quote are. A pool word with no Etymology section for its reading is still an entry, but it quotes nothing and is taken apart into nothing (Daily words). The Tatoeba export is pinned the same way, by size and checksum in scripts/lib/tatoeba-export.mjs, and is too large to commit, so only the sentences the entries show are. The accent list, the UniDic archive that checks it and the KanjiVG release are pinned by size and checksum in scripts/build-pitch-reference.mjs, scripts/lib/unidic-accent.mjs and scripts/build-strokes-reference.mjs, and only the accents and strokes the entries use are committed. The KRADFILE archive is pinned by size and checksum in scripts/build-components-reference.mjs and read the same way (pnpm data:components).

Data is processed offline once the pinned files are in place. The reference builders read the pinned JMdict and KANJIDIC2 files from the repo root (downloading a missing one, checksum-verified) and fetch the word lists; pnpm data:pitch and pnpm data:strokes download their pinned files the first time and cache them (the UniDic archive is large, and only its dictionary file is kept; Adding and fixing words says how much); pnpm data:sentences reuses the same JMdict and KANJIDIC2 files to check furigana, and every other step, from pnpm data:etymology to pnpm data:kanji, pnpm data:words and the tests, reads only local files. The running site reads none of these sources: the data is committed and served by the app.

How an entry is built

Word listspinned commitsJMdict, KANJIDIC2checksum-verifiedWiktionary dumpEtymology sectionsdata:reference+ meanings.ts fixesdata:etymologyfrom a pinned dumpReference snapshotsdata/reference/Headlinesdata/word-plan/Etymology pinsdata/reference/etymology/data:wordsfetches nothingEntriesdata/words/*.json
Sources become committed snapshots, and the generator builds every entry from the snapshots alone

scripts/lib/word-entry.mjs builds an entry from sources only (scripts/generate-word-entries.mjs runs it, and fetches nothing). Each step refuses rather than guess:

  • pickSections picks the Wiktionary section whose declared reading covers the word's.
  • evidenceLines yields one verbatim quote per line, minus Wikipedia furniture.
  • parseMorphemes reads A (a, “gloss”) + B (b, “gloss”) chains and keeps them only if the parts spell the word and join to its reading (rendaku allowed). It also accepts a stem glossed through its base verb (缶詰), a dropped okurigana (出る → 出口) and bare kana parts (お), and picks nothing when two different splits fit.
  • parseLiteral pairs a “warm + spring” style gloss with KANJIDIC2's unambiguous per-kanji readings; parseLoan handles loanwords.
  • parseKanji is the last resort for a word written with kanji (and the okurigana after them) that the text doesn't split: one part per kanji, with the okurigana that follow it (手続き is 手 + 続き, 言い出す is 言い + 出す), KANJIDIC2's reading and a meaning the text or the JMdict meaning already uses, marked glossSource: "kanjidic2". It is refused for ateji, jukujikun, an ambiguous split, kana KANJIDIC2 does not list as okurigana (何でも), and a single kanji with its okurigana, which would only repeat the word.
  • processesOf finds keyword tags in the quoted text and adds what JMdict, a source independent of Wiktionary, records for the matched sense: a coinage made in Japan, an ateji or jukujikun spelling, a loan source (jmdictFactsOf). stratumOf analyses KANJIDIC2's on and kun readings, and takes a JMdict loan source as the loanword layer unless the cited text names Chinese.

What each field comes from

FieldSource
kana, meaning, levelThe pool (data/reference/n*-reference.json), after the shared/meanings.ts corrections.
posJMdict's tags for the sense matching the pool meaning, verbatim.
priorityJMdict's ke_pri and re_pri codes for the spelling and reading, verbatim.
register, field, dialectJMdict's misc tags that name a register (colloquial, polite, slang…, the list is REGISTER_TAGS), and its field and dial tags, verbatim, for the sense(s) matching the pool meaning; a tag is kept only if every matched sense carries it.
pitchThe Kanjium accent list: the mora after which the pitch falls (0: it does not), for the exact spelling and reading; the accents UniDic gives for that spelling and reading too, in Kanjium's order.
pitchDisagreement?What Kanjium's list and UniDic each gave (kanjium, unidic) where both give an accent for the spelling and reading and the sets differ, copied from heldBack in data/reference/pitch.json. Nothing is shown for a word UniDic is silent on.
antonyms, seeAlsoJMdict's ant and xref of the sense(s) matching the pool meaning, as it writes them (丸・まる・1 is 丸, まる), each once and never the word itself.
stratumA katakana spelling (hybrid when mixed with kanji), a loan source named by the text or by JMdict, KANJIDIC2's on/kun analysis of the spelling, or else the text's “Middle Chinese” or “Old Japanese”. Omitted when none settles it (irregular spellings).
sources[]The lines of Wiktionary's Etymology section for this reading, verbatim. Empty where the dump has none, or has the section of another reading.
otherReadings[]?The sections of the same Wiktionary page that declare other readings of the spelling, verbatim and labelled with those readings. Nothing else in the entry is derived from them.
morphemes[]Parsed from those lines, or a literal gloss plus KANJIDIC2 readings, or per-kanji KANJIDIC2 data; only if the parts spell the word and join to its reading.
processesKeyword tags found in the quoted text (rendaku, clipping, ateji…), plus wasei, ateji and borrowing where JMdict records them for the matched sense (a note on a spelling or reading counts only for a word written with kanji).
examples[]Tatoeba sentences the pinned export indexes under the word, as picked by scripts/lib/example-sentences.mjs: with a translation, short, the word's own form in the text. Quoted unchanged. furigana is Tatoeba's own transcription of the sentence, kept by scripts/lib/furigana.mjs only where each kanji's reading is one KANJIDIC2 (or JMdict, for the whole word) gives it and a contributor wrote it, or kuromoji, UniDic or Tatoeba's index agrees. Where the older checks (the index, JMdict) disagree with a contributor, the contributor's reading is used; where they disagree with Tatoeba's software, theirs is.
wiktionaryDump?The snapshot's dump date, when the entry quotes it.
headline?Hand-written (data/word-plan/); a word without one has no origin to hook.

Where AI may help, and where it may not

A model's output can be wrong in ways that read as confident, so AI is only given tasks whose output is allowed to be inaccurate: nothing it produces is shown to a reader as a fact unless a source backs it.

  • OK: choosing which words go in a month (a poor pick costs nothing; the generator refuses a word with no usable source), drafting a headline (a hook, not a claim; a test limits its Japanese to what the evidence or pool contains), and writing code or tests, which CI then checks.
  • Not OK: supplying a reading, meaning, level, layer, morpheme split, etymology or any other claim about a word.

Before handing a task to a model, ask: if this came back wrong, would a check catch it or would it simply not matter? If neither, it must come from a source. The same goes for any new feature's data.

What the checks prove

Reference snapshotsEtymology pinsEntriesdata/words/*.jsontest/contentverifies each claimpnpm testCIPassesevery entry checks outFailsa wrong claim found
The content tests read the committed evidence and every entry; a wrong claim fails CI

The content tests (test/content/, their own Vitest project, run by pnpm test in CI) regenerate every month and check each entry independently of the generator, so a generator bug can't vouch for itself.

  • Generation (word-generation.test.ts): a committed entry equals, field for field, what the generator derives, so a derived field can't be edited by hand or left stale.
  • Catalogue: every date is unique and so is every word (a spelling with its reading), no day between the first entry and the last is missing (the first and last months may be partial), and entries are sorted.
  • Every entry (words.test.ts):
    • the term, reading, level and meaning equal the word list's, and its layer and processes are ones the site knows;
    • pos holds only tags JMdict gives that word, and register, field and dialect only tags every matched sense carries;
    • every sources[].quote is found verbatim (modulo whitespace and direction marks) in a Wiktionary Etymology section declared for the entry's own reading, from the dump the entry names (a page like 大人 has one section per reading);
    • every otherReadings quote is found verbatim in a section of the same page that declares other readings than the entry's, and an entry with no sources has none;
    • an entry with no sources has no headline, no parts and no dump date, and the dump has no section for its reading (or only one that opens “From Middle Chinese” for a native word);
    • the morphemes literally spell the word and join to kana; each non-irregular single-kanji morpheme has a reading KANJIDIC2 lists and a gloss KANJIDIC2 or the cited text backs;
    • the headline mentions only Japanese the entry's evidence or the pool contains;
    • an unclear entry quotes a hedged line, no snapshot pin is orphaned, each pin sits in the shard of the month that plans its word, and the snapshot records its source and licence.
  • JMdict, independently (jmdict-crosscheck.test.ts): an entry's priority codes are ones JMdict gives its spelling or reading; its register, field and dialect are exactly the tags every matched sense carries (and the register tags still occur in the pinned JMdict, so a renamed tag cannot silently stop matching); a word written with kanji that JMdict marks as a coinage made in Japan or an ateji, gikun or jukujikun spelling or reading carries that process; a European loan source means the borrowing process and the loanword layer (unless the cited text itself puts Chinese forward and hedges, as for 缶). It only confirms: the JMdict file pinned here gives no loan source for many loanwords (カメラ, ノート), so silence from JMdict proves nothing.
  • Cross-references (also in jmdict-crosscheck.test.ts): each of an entry's antonyms and seeAlso is an ant or xref of a sense its meaning matches, and every one JMdict gives (but the word itself) is there.
  • Example sentences (examples.test.ts): each sentence is the committed snapshot's, holds the form it says the word takes, is a form of that word (its own spelling, an inflection that keeps the first character, or kana), has a translation and a readable length, and the snapshot names the export it came from. The sentences are not reviewed one by one: the export is the community's, and a short filter keeps out the plainly unsuitable; of a word's qualifying sentences, those with every kanji read come first. Each sentence's furigana joins back to its text, puts a reading only over kanji, in hiragana, and never across the edge of the word's form; almost every kanji of the sentences shown has one; the snapshot says how readings were checked.
  • Pitch accent (pitch.test.ts): the snapshot names its source, licence and the file it was read from, holds only words in the catalogue with their own reading, gives each accent as a whole number no larger than the word's count of morae, and every entry shows the snapshot's accents and no others. The second source is named too, and every word that lost an accent is listed with what each source said, adding up to what was kept.
  • Kanji (kanji.test.ts): data/reference/kanji.json holds exactly the kanji the entries are written with, each as the level snapshots have it.
  • Shapes inside a kanji (components.test.ts): the snapshot names its source, licence and the archive it was read from, holds only kanji of kanji.json, each with at least one shape, and covers nearly every kanji the entries use.
  • Stroke order (strokes.test.ts): the snapshot names its source, licence and release, holds only kanji of kanji.json, each with as many strokes as KANJIDIC2 counts, and covers nearly every kanji the entries use.
  • Every word-list word, at every level (vocabulary.test.ts in test/content/ and test/content/{n4,n3,n2,n1}/): it is in JMdict with that reading, has no meaning that reverses JMdict's, has its reading attested (reading-attested.test.ts), and every enrichment in shared/meanings.ts is backed by JMdict. A staleness check fails if a reference file no longer matches what servedVocab() would produce.

To change a word or add a month, see Adding and fixing words.

Attribution

Written once in shared/sources.ts; the footer, each entry's citation and the README read it from there.

JMdict, KANJIDIC2 and KRADFILE are property of the Electronic Dictionary Research and Development Group, used under CC BY-SA 4.0, from the dated files the group publishes.

Etymology text is quoted from English Wiktionary under CC BY-SA 4.0, as extracted by wiktextract and distributed by Kaikki.org, which is maintained by Tatu Ylonen. See Ylonen, Wiktextract: Wiktionary as Machine-Readable Structured Data, Proceedings of the 13th Conference on Language Resources and Evaluation (LREC), 2022, pp. 1317–1325. Each entry names the dated dump it quotes, links the page and quotes it verbatim; the one-line headline is NipponDaily's own.

Example sentences, their translations and their furigana are from Tatoeba, a collection written by its community, under CC BY 2.0 FR. Each shows its Tatoeba number, which links to the sentence and its authors. They are picked from a dated export by fixed rules and quoted unchanged; nobody reviews them one by one.

The word lists come from the community list originally compiled at tanos.co.uk (CC BY; credit required), via elzup/jlpt-word-list (MIT licence).

Kana conversion in the data scripts and checks, and in Explore's search (a romaji search reads as kana), uses wanakana (MIT licence).

Tatoeba's furigana is first checked against kuromoji (Apache licence 2.0) with its IPADIC dictionary, copyright Nara Institute of Science and Technology, which permits its use and distribution. The analyser runs only when the data is built; nothing of it is sent to or shipped to a reader.

It is then checked against a second analyser, Lindera (MIT licence) with the UniDic dictionary of the National Institute for Japanese Language and Linguistics (BSD, LGPL or GPL). The same dictionary's accent types check the pitch accents: one is kept only where UniDic gives it too. The analyser and the dictionary are used only when the data is built; nothing of them is sent to or shipped to a reader.

Pitch accents are from the accent list of Kanjium, under CC BY-SA 4.0, which credits its accent data to Uros O.'s free database. A word is shown an accent only where the list gives that exact spelling and reading and UniDic gives the same accent.

Stroke order is from KanjiVG, copyright Ulrich Apel, under CC BY-SA 3.0. A kanji is drawn only where KanjiVG and KANJIDIC2 count the same number of strokes.

The site is set in Zen Old Mincho, Outfit and Noto Serif JP, served from the app by way of the @fontsource packages, and the share images are drawn in the first two; all are under the SIL Open Font License 1.1. The share-image licences ship beside the font files in server/assets/og/.

© 2025 - 2026 NipponDaily. Released under the Apache-2.0 License.

Origins quoted from Wiktionary (CC BY-SA 4.0) via Kaikki.org; meanings & kanji from JMdict, KANJIDIC2 & KRADFILE (EDRDG, CC BY-SA 4.0) — how we check it