Skip to the content

Jisho辞書

The words of JMdict looked up by reading, written form, English meaning, a kanji in them or romaji, in small pieces fetched as needed, safe by default or unfiltered with every sense's marks, and the school grades, JLPT levels, readings, meanings and look-alike pairs of 2,999 kanji, each table an entry point of its own.

Jisho is the Japanese word 辞書 for a dictionary: this one holds Japanese words and kanji. About the name

Contents

How-to

React, Vue, Svelte and Angular

Show a lookup in a component of React, Vue, Svelte or Angular, and render the words on a server.

The lookup is a promise, so a component starts it when its text changes, keeps the words in its own state, and drops an answer that arrives after the text has changed. The words are plain rows: show written, reading and meaning as text and nothing needs to be marked safe.

React

import { loadWords, type Word, wordsOf } from "@johnmorrisdotca/jisho/words";
import { useEffect, useState } from "react";

export function WordList({ words }: { words: readonly Word[] }) {
  return (
    <ul id="words" lang="ja">
      {words.slice(0, 5).map((one) => (
        <li key={`${one.written}-${one.reading}`}>
          {one.written} {one.meaning}
        </li>
      ))}
    </ul>
  );
}

export function Lookup({ text }: { text: string }) {
  const [words, setWords] = useState<readonly Word[]>([]);
  useEffect(() => {
    let current = true;
    void loadWords(text).then((loaded) => current && setWords(wordsOf(loaded, text)));
    return () => {
      current = false;
    };
  }, [text]);
  return <WordList words={words} />;
}

WordList only draws the words it is given, and Lookup fetches them. Keep the two apart and the first works on a server too.

Server rendering

import { loadWords, wordsOf } from "@johnmorrisdotca/jisho/words";
import { renderToString } from "react-dom/server";
import { WordList } from "./WordList";

const words = wordsOf(await loadWords("かみ"), "かみ");
console.log(renderToString(<WordList words={words} />));

A server render cannot wait for an effect, so load the words first (a route's loader, a server component, a script) and hand them to WordList. The piece files are read from node_modules at run time; keep the package external to the server's bundle so the whole dictionary is not carried in it.

Vue 3

<script setup lang="ts">
import { loadWords, type Word, wordsOf } from "@johnmorrisdotca/jisho/words";
import { ref, watchEffect } from "vue";

const props = defineProps<{ text: string }>();
const words = ref<readonly Word[]>([]);

watchEffect(async () => {
  const text = props.text;
  const loaded = await loadWords(text);
  if (text === props.text) words.value = wordsOf(loaded, text);
});
</script>

<template>
  <ul id="words" lang="ja">
    <li v-for="one in words.slice(0, 5)" :key="one.written + one.reading">{{ one.written }} {{ one.meaning }}</li>
  </ul>
</template>
import { createApp } from "vue";
import WordList from "./WordList.vue";

createApp(WordList, { text: "かみ" }).mount("#app");

Svelte

<script lang="ts">
import { loadWords, type Word, wordsOf } from "@johnmorrisdotca/jisho/words";

let { text }: { text: string } = $props();
let words = $state<readonly Word[]>([]);

$effect(() => {
  const asked = text;
  void loadWords(asked).then((loaded) => {
    if (asked === text) words = wordsOf(loaded, asked);
  });
});
</script>

<ul id="words" lang="ja">
  {#each words.slice(0, 5) as one (one.written + one.reading)}
    <li>{one.written} {one.meaning}</li>
  {/each}
</ul>

Angular

import { Component, Input, type OnChanges, type OnInit, type SimpleChanges, signal } from "@angular/core";
import { loadWords, type Word, wordsOf } from "@johnmorrisdotca/jisho/words";

@Component({
  selector: "app-word-list",
  standalone: true,
  template: `
    <ul id="words" lang="ja">
      @for (one of words(); track one.written + one.reading) {
        <li>{{ one.written }} {{ one.meaning }}</li>
      }
    </ul>
  `,
})
export class WordListComponent implements OnInit, OnChanges {
  @Input() text = "かみ";
  readonly words = signal<readonly Word[]>([]);

  ngOnInit(): void {
    void this.show();
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes["text"]?.firstChange === false) void this.show();
  }

  private async show(): Promise<void> {
    const asked = this.text;
    const loaded = await loadWords(asked);
    if (asked === this.text) this.words.set(wordsOf(loaded, asked).slice(0, 5));
  }
}

The component uses signals and control flow, and works zoneless. Bootstrap it as in any standalone Angular app.

View sourceEdit on GitHubMade from the source of @johnmorrisdotca/jisho 2.11.0.