Contents
NipponDailyHow it is built

Chapter 4

Daily words

One entry per day, in data/words/. This chapter says what an entry holds, when its day opens, and how the app reads across the entries without adding a claim.

An entry

A WordEntry (types/index.ts). The range written so far is May 2005 to February 2027 (7959 words).

FieldMeaning
dateYYYY-MM-DD, JST.
term, kana, meaning, levelFrom the JLPT word list, after the corrections in shared/meanings.ts.
posJMdict's own part-of-speech tags, verbatim, for the sense the meaning came from.
priority?JMdict's priority codes for the spelling and reading (ichi1, news1, nf05…), verbatim; left out when JMdict tags neither.
register?, field?, dialect?JMdict's tags for how the word is used (colloquial, polite (teineigo) language, slang…), the field it belongs to (medicine, baseball…) and its dialect (Kansai-ben…), verbatim; each is a tag every sense the meaning matched carries, and is left out when there is none.
pitch?The mora after which the pitch falls, as the pinned Kanjium accent list gives it for this exact spelling and reading (0: it never falls, 1: after the first mora…) and UniDic gives it too; several when both give several. Left out when they share none.
pitchDisagreement?{ kanjium, unidic }: the accents each source gave where both have one for this spelling and reading and the sets differ; the ones they share are in pitch. Left out when they agree, or UniDic has none.
antonyms?, seeAlso?{ term, kana? }[]: the words JMdict gives as the opposite (ant) of the sense the meaning came from, and the words it says to see (xref), as it writes them. Left out when it gives none.
stratum?wago, kango, gairaigo or hybrid; stated only when the spelling, KANJIDIC2, JMdict's loan source or the quoted text establishes it.
processesKeyword tags found in the quoted text (WORD_PROCESSES), plus the ones JMdict independently records: a coinage made in Japan, an ateji, gikun or jukujikun spelling or reading (for a word written with kanji), a loan source.
headline?The only hand-written field. Left out for a word with no origin to hook.
morphemes[]text, surface reading (hiragana; the single part of a loanword keeps its katakana), base? when rendaku or sokuon changed it, meaning, irregular?, and glossSource? (kanjidic2 when the meaning is KANJIDIC2's, not Wiktionary's; a part may carry the okurigana written after its kanji). Empty when the source gives no clean split.
sources[]{ quote }: one verbatim line of Wiktionary's Etymology section for this reading. Empty when the dump has none for it.
otherReadings[]?{ readings, quotes }: the Etymology sections the same Wiktionary page declares for other readings of the spelling (抱く だく next to いだく), verbatim and in the page's order, one entry per section. Shown apart from the origin and never used for the parts, layer or processes. Left out when the page has none or the entry has no origin.
examples[]?{ id, ja, en, enId, form, furigana? }: up to 2 example sentences from the pinned Tatoeba export, with their translations, unchanged. id and enId are Tatoeba's numbers and form is the word as the sentence writes it. furigana cuts ja into parts that join back to it, [text] or [kanji, reading], and is left out when no kanji got a verified reading; a kanji whose reading the sources cannot settle stays bare in it. The whole field is left out when no sentence qualifies.
wiktionaryDump?Date of the pinned Wiktionary dump; left out with the sources.

The page shows register, field and dialect as outlined badges beside the part-of-speech tags, and pitch as the reading with a line over the high morae and a ↓ after the one the pitch falls on, named heiban, atamadaka, nakadaka or odaka (app/utils/pitch.ts only lays the number over the word's own kana), with a short note under the line saying how to read it. The accent is the word's own: how it changes in a compound, with a particle or in a sentence is not shown, and where several accents are kept all are shown, without saying which is more usual. Neither adds a claim.

A hedge in a quoted line (“probably”, “unknown”…) is detected when the page renders (isHedged in shared/word-labels.ts) and shown as “Not settled”. Where each field comes from is in Data integrity.

When a day opens

Dates are JST (shared/jst.ts). todayJst() shifts UTC to Japan's calendar, so a new word opens at midnight there. With no date, GET /api/daily-word serves today's word. Once the catalogue has run out it starts a new lap (lapEntryForDate()): the day after the last word shows the first, the next day the second, and so on, round again after the last. Until the last written day it serves the newest word that is open, on lap 1. The payload's lap says which one (1 until then), and the home page names the lap from 2 on. A word shown on a later lap has always opened already, so nothing leaks; an explicit date is always lap 1. The calendar fills those days too: calendarForMonth() gives each open day after the last entry the lap word, linked to that word's own page. The page is never empty, and before the first day it is a 404.

Request?date=…400future or malformedThe day's entryno date: today, by lap404past day, no entryEntry + prev/nextnext once arrived
How GET /api/daily-word decides what to answer
  • No future leaks. The API rejects a future date (400); payloadFor() reveals next only once that day has arrived; calendarForMonth() gives an upcoming day only its date, and exploreCalendar() never matches one, so filtering the calendar cannot find a word early.
  • Server rendering. A server fetch that fails with 400/404 makes the HTTP response a 404, and such pages are noindex.

Reading across the words

Everything here derives from fields entries already have, reads open days only (today is always a parameter), and adds no new claim.

Parts

/parts and /parts/<text>: the morpheme index. A part page groups its words by the reading the part has in each (日 → び, ひ, か, にち), with rendaku shown as “from ひ”. alsoIn lists open words whose spelling contains a kanji part but whose breakdown doesn't name it, flagged on the page as claiming nothing. Coverage is bounded by the sources and the parsers: a word the dump has no Etymology section for has no parts, nor do single-character words, a single kanji with its okurigana (抱く), many loanwords and any word whose text gives no clean split. The sitemap lists parts seen in more than one open word.

Kanji

/kanji and /kanji/<character>: every kanji an open word is written with, from KANJIDIC2's own record of it (its on'yomi and kun'yomi, meanings, school grade, stroke count and newspaper frequency rank) and the order its strokes are drawn in, beside the open words that contain it, grouped by their JLPT level (easiest first, oldest first within a level). It is built from the spellings of the entries alone, so it adds no claim about a word: the record describes the character, and a word does not use every reading or meaning listed. A kanji used only by an upcoming word is a 404, like a part. The records are data/reference/kanji.json, made offline by pnpm data:kanji from the level snapshots, and data/reference/strokes.json, made by pnpm data:strokes from a pinned KanjiVG release, which keeps a kanji's strokes only where KanjiVG draws as many as KANJIDIC2 counts (a kanji with fewer or more shows none). shared/kanji.ts reads those and the shapes (server only, like the other modules that see every entry), and the page draws the strokes one at a time, numbered at their start (KanjiStrokes). Under them it names the shapes the character contains, from data/reference/components.json (pnpm data:components, EDRDG's KRADFILE): a lookup aid that says what the character contains and not where it comes from, each shape that is itself a kanji of an open word linked, with a few other kanji that contain it. Each entry links its kanji, and the sitemap lists kanji seen in more than one open word.

Explore

/explore filters by q (term, kana, meaning; katakana folded to hiragana, and romaji read as kana and matched against the readings only), level, stratum, process, pos, frequency, pitch and part, newest first. count is every match, but words holds only the newest 100; the page's “Show more” asks again with limit raised by a page, so the list (which grows by a word a day) never reaches the browser whole. Different filters always narrow together. Within level, stratum, process, pos, frequency and pitch several choices are joined by commas (?process=rendaku,compound), and match says how they combine: by default a word needs any of them, with match=all it needs every process, every part of speech and every accent pattern. A word has one level, one layer and one frequency group, so those always read as “any”. /words (the calendar) takes the same filters beside month: GET /api/word-calendar marks each open day of the month match or not and returns the same counts, plus how many words match in every month. Both endpoints read the filters through server/utils/explore-filters.ts, so a filter means the same thing in each; the page shares one form, WordFilters.

The layer unstated picks the words with no stated layer. pos filters on a coarse group of JMdict's tags (noun, verb, adjective, adverb, prefix or suffix, other, not stated; POS_GROUPS in shared/word-labels.ts), because the verbatim tags are long. The entry keeps the tags as they are, and a word can fall in more than one group. frequency filters on how common JMdict says a word is, from the priority codes its entry keeps (FREQUENCY_GROUPS): common when its spelling or reading is in the first tier of a list JMdict draws on (ichi1, news1, spec1, spec2 or gai1, the codes JMdict itself counts as common), less common when it has only other codes, not ranked when it has none, which says the word is outside those lists and not that it is rare. pitch filters on the patterns of the accents the entry shows (flat, head-high, middle-high or tail-high, named by pitchTypesOf in app/utils/pitch.ts, the same naming the entry page and Patterns use), and unstated on the words that show none; a word given several accents has the pattern of each. Each facet's counts are taken over the words the other filters leave, so an option never promises a count it can't deliver. Filters live in the URL; an unknown value in a hand-edited URL is dropped, while the API answers 400.

Patterns

/patterns: counts by layer, level and process; level × layer; process × layer; process pairs and, beyond them, sets of three to four tags (processes plus the layer, such as rendaku, compound and native) with up to three example words each, linking into Explore with match=all; and a rendaku section. It lists every part whose recorded base differs from its reading, classed from the two spellings as a voiced first kana (ひ → び; ち → じ and つ → ず count, being the merged voiced sounds), a reading ending in っ, or other. The page says the counts describe these entries (a JLPT N5–N1 sample, parser-derived tags), not the language. The catalogue is lopsided by how it was filled (the words chosen for their origin, then each list in turn), so a stretch of days can hold mostly one level or one script, and the counts show more of it than the language would. Two sections read fields other than the tags: the pitch accents the entries show, sorted into flat, head-high, middle-high and tail-high for each layer (an accent counts once, so a word with two counts in both, and a word with none shown is left out), and the mean number of morae and kanji of each level's words. A last section compares readings alone: how many readings two or more open words share, how many of those show an accent for every word, how many the pitch alone tells apart (every word shows one and no two share one), and the 12 readings with the most words, each word with the accents its entry shows.

Related words

Under an entry on /words/<date>: up to 6 other open words, scored 3 per shared part plus 1 − (fraction of open words carrying it) for each shared process and for a shared layer. Below 1.5 a word is not offered. Each card names what is shared and links to /parts/<text> or /explore. The row is hidden when the fetch fails or nothing clears the minimum.

Words read alike

Under an entry, the other open words whose reading is the same once katakana is folded to hiragana (readingKey in shared/related.ts) and whose spelling differs: 橋, 箸 and 端 are all はし. Each is shown with the pitch accents its own entry shows, marked with ↓ where the pitch falls, and the entry itself comes first so the accents stand side by side; a word whose entry shows none says so. Only the readings are compared, so the block claims nothing about how the words are related, and nothing that has not opened is listed.

The same day in other years

Under an entry, and under the word on the home page, the open words whose day has the same month and day in another year, oldest first, each linking to its own page. Only the dates are compared: the words share nothing else, and the block says so. On a later lap the home page lists the days that match the word's own first day, which its heading names. They come with the entry in GET /api/daily-word (sameDay, from sameDayWords in shared/words.ts), so the home page renders them on the server with its word and fetches nothing more.

Sharing a word

The share image, GET /og.png?date=…, is what a shared link shows as its preview. The app serves it and sends nothing to anyone. It is derived from fields the entry already has, reads open days only and adds no claim. It is a 1200 by 630 PNG of the word, its reading, meaning, level, layer and headline, in the palette of the season its day falls in. The page's Open Graph and Twitter tags point at it, so a shared link unfolds into the card. It is drawn on the server by satori and resvg (shared/og-card.ts, server/utils/og-image.ts) and is served outside /api/ so that robots.txt does not stop a link preview from fetching it. A future, malformed or unknown date is a 404 rather than a 400, so the image cannot be used to learn a word early.

The card draws Japanese in Zen Old Mincho and Latin in Outfit, the site's two faces, from subsets bundled in server/assets/og/ (pnpm assets:og-font, see Adding and fixing words). Zen Old Mincho has no macron vowels, which Outfit supplies, and neither has IPA symbols or a few rare kanji. A headline with a character neither can draw is left off the card, never shown with empty boxes, and glyphs.json lists what can be drawn so a test can say how often that happens.

The word pool

The pool is the community JLPT lists (one CSV per level, N5–N1), cross-referenced against JMdict and KANJIDIC2. It exists only as the committed data/reference/{n5,n4,n3,n2,n1}-reference.json snapshots. scripts/lib/word-list.mjs holds the parsing, the reading and meaning overrides and the gloss cross-check.

A handful of words appear at more than one level with the same reading; dedupeAcrossLevels() keeps each at the lowest, and the few that coincide once their corrections are applied (キロ) count as one. A shared spelling with a different reading (開く あく and ひらく) is a different word. An entry's term, kana, level and meaning must equal a pool word.

The catalogue is the pool, one word a day. The words that were chosen for their origin come first and carry a headline; the rest of the pool follows, easiest list first, without one. Last come the words that share a spelling with an earlier one (後 あと, then 後 ご), easiest list first again; such a word carries a headline when the dump has an Etymology section for its reading. A word shows an origin only if the pinned dump has an Etymology section for its own reading, because every claim is quoted from one; without it the entry has no sources, no morphemes, no headline and no dump date, and the page says the data is missing in those sections. Its layer and processes come from KANJIDIC2 and JMdict alone. The same goes for the example sentences and the pitch accent. One spelling can be several words: 後 あと and 後 ご are two pool words and two entries, so the plan names the reading (kana), and the sentence and pitch snapshots are keyed by spelling and reading together.

© 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