Skip to content

Repository files navigation

kanjiapi.dev: a modern JSON API for Kanji

Over 13,000 kanji served at https://kanjiapi.dev

Links

Buy me a coffee

https://buymeacoffee.com/onlyskin

Examples

Kanji /v1/kanji/{character}

$ curl https://kanjiapi.dev/v1/kanji/猫
{
  "alternate_stroke_counts": [],
  "freq_mainichi_shinbun": 1702,
  "grade": 8,
  "heisig_en": "cat",
  "jlpt": 3,
  "kanji": "猫",
  "kun_readings": [
    "ねこ"
  ],
  "meanings": [
    "cat"
  ],
  "name_readings": [],
  "notes": [],
  "on_readings": [
    "ビョウ"
  ],
  "stroke_count": 11,
  "unicode": "732B"
}
> fetch('https://kanjiapi.dev/v1/kanji/猫').then(r => r.json()).then(console.log);
{
  "alternate_stroke_counts": [],
  "freq_mainichi_shinbun": 1702,
  "grade": 8,
  "heisig_en": "cat",
  "jlpt": 3,
  "kanji": "猫",
  "kun_readings": [
    "ねこ"
  ],
  "meanings": [
    "cat"
  ],
  "name_readings": [],
  "notes": [],
  "on_readings": [
    "ビョウ"
  ],
  "stroke_count": 11,
  "unicode": "732B"
}

See the documentation for what each field means. The unihan_cjk_compatibility_variant field appears only on the CJK compatibility characters described below.

List of all supported kanji

$ curl https://kanjiapi.dev/v1/kanji/all

List of joyo kanji

$ curl https://kanjiapi.dev/v1/kanji/joyo (also /jouyou)

JOYO kanji are general use kanji outlined by the Japanese government. The Unihan Database labels four additional kanji as Joyo kanji, bringing the total to 2140 codepoints. This is because four of the Joyo kanji are missing in the JIS X 0208 encoding, so a different kanji which is present in JIS X 0208 was historically used. Therefore, in the Unihan Database, both the four official kanji and the four JIS X 0208-compatible kanji are marked as Joyo kanji. This is mirrored in the /joyo list provided by kanjiapi.dev.

The affected characters are:

official Joyo historical JIS X 0208 compatible
𠮟 U+20B9F 叱 U+53F1
塡 U+5861 填 U+586B
剝 U+525D 剥 U+5265
頰 U+9830 頬 U+982C

List of jinmeiyo kanji

$ curl https://kanjiapi.dev/v1/kanji/jinmeiyo (also /jinmeiyou)

Note, 82 kanji in the Jinmeiyo list have codepoints in the Unicode CJK compatibility code block. Therefore, they will sometimes be treated as "the same kanji" as another character. This is specified by Unicode in the Unihan Database.

kanjiapi.dev provides /kanji/{character} endpoints for these CJK compatibility codepoints, but adds a special field to them for ease of accessing the unified version of the character. In addition, these compatibility characters mostly have no words listed in their equivalent /words/{character} endpoint file, and much more limited information in the /kanji/{character} endpoint fields. This reflects the data which is present in the KANJIDIC file.

For example, the Jinmeiyo character 海 (U+FA45) is considered by Unicode to be the same as the Joyo character 海 (U+6d77). The Jinmeiyo version is therefore present in the CJK compatibility block.

This is an issue because any layer of software (e.g. browser caching, url encoding) may perform unicode normalisation, which would convert the Jinmeiyo character to a different character. If you expect to see a Jinmeiyo character, but you see a Joyo character, this is probably the reason.

If necessary, the css property font-variant-east-asian: traditional; can be used to tell the browser to display the unified equivalent character in the traditional way, which should mean that a Joyo character codepoint displaysa s its Jinmeiyo equivalent.

List of heisig kanji

$ curl https://kanjiapi.dev/v1/kanji/heisig

List of characters which have a Heisig keyword assigned. Note, there are four extra kanji in the heisig list labeled with '[alt]' after the normal keyword. These are the four official joyo variants of the missing JIS X 0208 kanji (the Heisig book series assigned these keywords to the JIS compatible characters, but a person looking for them could come from either the JIS compatible version or the official version). See the List of joyo kanji section above.

List of kanji of a certain grade

$ curl https://kanjiapi.dev/v1/kanji/grade-1 (school grades 1-6, with grade 8 signalling remaining highschool grade)

Reading /v1/reading/{reading}

$ curl https://kanjiapi.dev/v1/reading/クウ
{
  "main_kanji": [
    "供",
    "咼",
    "啌",
    "喎",
    "垙",
    "宮",
    "瘸",
    "盉",
    "空",
    "舙",
    "骷"
  ],
  "name_kanji": [],
  "reading": "クウ"
}
> fetch('https://kanjiapi.dev/v1/reading/クウ').then(r => r.json()).then(console.log);
{
  "main_kanji": [
    "供",
    "咼",
    "啌",
    "喎",
    "垙",
    "宮",
    "瘸",
    "盉",
    "空",
    "舙",
    "骷"
  ],
  "name_kanji": [],
  "reading": "クウ"
}

Words /v1/words/{character}

Every JMdict entry containing the character, 141 of them for 猫. One entry, with its senses and the rest of the response elided:

$ curl https://kanjiapi.dev/v1/words/猫
[
  ...
  {
    "meanings": [
      {
        "glosses": [
          "cat (esp. the domestic cat, Felis catus)"
        ]
      },
      {
        "glosses": [
          "shamisen"
        ]
      },
      ...
    ],
    "variants": [
      {
        "priorities": [
          "ichi1",
          "news1",
          "nf07"
        ],
        "pronounced": "ねこ",
        "written": "猫"
      },
      {
        "priorities": [],
        "pronounced": "ネコ",
        "written": "猫"
      }
    ]
  },
  ...
]
> fetch('https://kanjiapi.dev/v1/words/猫').then(r => r.json()).then(console.log);
[
  ...
  {
    "meanings": [
      {
        "glosses": [
          "cat (esp. the domestic cat, Felis catus)"
        ]
      },
      {
        "glosses": [
          "shamisen"
        ]
      },
      ...
    ],
    "variants": [
      {
        "priorities": [
          "ichi1",
          "news1",
          "nf07"
        ],
        "pronounced": "ねこ",
        "written": "猫"
      },
      {
        "priorities": [],
        "pronounced": "ネコ",
        "written": "猫"
      }
    ]
  },
  ...
]

Development:

Requirements:

Assumes python 3, make and node are available.

Setup:

Install python libraries using requirements.txt

Install node dependencies using yarn or npm install.

Run make update-dictionaries to fetch the source dictionaries into data/. See Updating the dictionaries below for what it downloads and from where.

Ensure system has System/Library/Fonts/ヒラギノ丸ゴ\ ProN\ W4.ttc font available (for favicon generation).

Updating the dictionaries:

The source dictionaries live in data/ and are not in the repo. EDRDG rebuilds theirs daily and Unicode publishes Unihan with each release, so refresh them monthly:

make update-dictionaries   # download, verify, archive, install
make                       # rebuild out/ with the new data
python -m pytest tests     # some tests assert exact dictionary values

Split into two steps if you want to inspect the download before committing to the swap:

make download-dictionaries   # fetch to .dictionary_staging/ and verify
make install-dictionaries    # archive data/ then move staging into place

Neither is wired into make, which must never touch the network.

The files fetched are:

installed as source
data/kanjidic2.xml http://ftp.edrdg.org/pub/Nihongo/kanjidic2.xml.gz
data/JMdict_e_NG http://ftp.edrdg.org/pub/Nihongo/JMdict_e_NG.gz
data/JMnedict.xml http://ftp.edrdg.org/pub/Nihongo/JMnedict.xml.gz
data/Unihan_*.txt https://www.unicode.org/Public/UCD/latest/ucd/Unihan.zip

JMdict_e_NG.gz is the English-only "New Generation" (rev 1.10) file. EDRDG publish several JMdict variants and the files are kept under their published names, so it is always clear which one the build reads. The Unihan URL tracks whatever Unicode release is current, so an update can move between Unicode versions.

verify_dictionaries.py compares each download against the file it would replace and refuses to install anything that has lost more than 5% of its content, so a truncated download cannot silently rebuild the API with data missing. The Unihan files are checked by the fields the build actually reads (kJoyoKanji, kJinmeiyoKanji, kCompatibilityVariant) rather than by line count, because they carry many unrelated fields which come and go between Unicode releases.

install-dictionaries gzips the files it replaces into data_archive/ first, so the previous release is always recoverable. Each is named for the release date it declares about itself rather than the day it was replaced, e.g. kanjidic2.xml.2023-09-08.gz, since the four sources are on independent release cycles and a single date for a batch would be wrong for most of it.

After updating, expect a large out/ diff and close to a full bucket re-upload on the next deploy. Expect the occasional test failure too, where EDRDG has revised an entry the tests assert on; check whether the data genuinely changed before editing the expectation.

Building:

Run make to build the site and API endpoints as static assets.

Run python -m pytest tests to run the tests.

Note: in order to fetch data from the local build of the API from the local build of the site, you can set the root of your fileserver to out/site (e.g. with serveit: serveit -s out/site make). There is a symlink to out/v1 inside out/site to enable this.

Note: endpoint files for characters in the Unicode CJK Compatibility block are written out to a separate directory as some filesystems normalise them with the non-compatibility equivalents. This means that a few of the /kanji/{character} and /words/{character} endpoints don't show up under the normal path in the API during local development.

Deployment (Requires google cloud account credentials):

Versioning

The API version for deployment is hardcoded in api_data.py and the makefile.

Uploading to bucket

After building, to sync the built assets to the website bucket run:

NB: it's a good idea to run all of these commands with rsync -n for a dry-run first

To sync the built site dir (out/site) up with the root of the bucket, but non-recursively: gcloud storage rsync out/site gs://kanjiapi.dev --checksums-only --delete-unmatched-destination-objects --exclude=".*\.map$" --cache-control="public, max-age=60"

To sync the built api dir folders (out/{version}) up with the dir /{version} in the bucket recursively based on file hashes: gcloud storage rsync out/v1/kanji/ gs://kanjiapi.dev/v1/kanji/ --recursive --checksums-only --delete-unmatched-destination-objects --content-type="application/json" gcloud storage rsync out/v1/kanji_cjk/ gs://kanjiapi.dev/v1/kanji/ --recursive --checksums-only --content-type="application/json" gcloud storage rsync out/v1/words/ gs://kanjiapi.dev/v1/words/ --recursive --checksums-only --delete-unmatched-destination-objects --content-type="application/json" gcloud storage rsync out/v1/words_cjk/ gs://kanjiapi.dev/v1/words/ --recursive --checksums-only --content-type="application/json" gcloud storage rsync out/v1/reading/ gs://kanjiapi.dev/v1/reading/ --recursive --checksums-only --delete-unmatched-destination-objects --content-type="application/json"

Setting CORS policy:

The CORS policy is stored in cors.json, it can be updated by editing this file and running gsutil cors set cors.json gs://kanjiapi.dev

Logging:

Logfiles are generated by the cloud storage bucket for the API, there is a cloud function which is triggered whenever a usage logfile is written. The cloud function reads the logfiles to aggregate some useful information which is displayed on the logs page

The cloud function is found in the popularity-contest directory.

To deploy it, run gcloud functions deploy popularity-contest --gen2 --region=us-west1 --runtime=python310 --source popularity-contest --entry-point handle --trigger-bucket=kanjiapi-dev-logging --trigger-location=us from the root of the project. (needs credentials)

To run the cloud function locally, cd into the popularity-contest directory, create and/or activate a virtual environment based on the requirements.txt file in that subdirectory, then run python main.py. This is useful to manually (re)process some log files without deploying or triggering the cloud function. (needs credentials)

Don't cache /v1/kanji/ at the CDN

Only requests that actually reach the bucket produce a usage log line, so the logs page counts exactly what Cloudflare passes through. It proxies but does not cache these responses today, because the endpoints have no file extension and so miss Cloudflare's default cache rules. Adding a cache rule over /v1/kanji/ would be a silent regression: hit rate rises with popularity, so common kanji would be undercounted more than rare ones and the ranking would flatten in the one dimension the page exists to show.

About

A modern JSON API for Kanji

Resources

Stars

167 stars

Watchers

6 watching

Forks

Releases

Packages

Used by

Contributors

Languages