# Make a Kotowa pack (instructions for an AI agent)

Kotowa is a reader app for Japanese. A pack is the content it reads: lines of Japanese with furigana, word-by-word meanings and translations. You can write one without any special tool. This page tells you how. A person pointed you here to turn something they have (a text, subtitles, comic pages, a script) into a pack they can add to the app.

Files you will use:

- This guide: https://kotowa.gellyfish.dev/make-a-pack.md
- Every field, in words: https://kotowa.gellyfish.dev/pack-format.html
- JSON Schema for `manifest.json`: https://kotowa.gellyfish.dev/pack-manifest.schema.json
- JSON Schema for a unit file: https://kotowa.gellyfish.dev/pack-unit.schema.json
- A checker you run on your folder (Node 18 or newer, no install): https://kotowa.gellyfish.dev/validate-pack.mjs

## 1. Before you start

Ask the person for anything you don't have, once, in one message:

- **The Japanese.** A text, an SRT or ASS subtitle file, page images with the text of each panel, or a script. Use only what they give you. Do not fetch copyrighted text from the web for them.
- **A pack id and title.** The id becomes a folder name on their device: letters, digits, `.`, `_`, `-`, starting with a letter or digit. The title is shown in the Library.
- **The license.** `private` if the content is for their own study and they may not share it. Otherwise the real license, such as `public-domain` or `cc-by-4.0`. When unsure, use `private`.
- **Credits.** Who made the content and where it came from.
- **Languages** for the word meanings: `en` by default, or `es`, or both.
- **Sentence translations**, if they have them.

If they have no sentence translations, write them yourself and add a sentence to the end of `credits` saying so, for example `Sentence translations by an AI assistant; check them before relying on them.` A sentence translation is a full-sentence meaning of an item. It is separate from the per-word meanings.

Do not make a pack that helps share material the person has no right to share. Packs are for the person's own use.

## 2. What a pack is

A folder (later a .zip) with:

```
manifest.json            the pack's title, languages, license and list of units
units/<unit-id>.json     one file per unit: a chapter, scene, page or section
media/<name>.png         images, only if items use them (comic pages)
```

Write only these. Kotowa also reads `search-index.json`, `jmdict.json` and `characters.json` when they exist, but a pack works without them. Do not invent them.

A **unit** is a section. An **item** is one thing on screen: one or several lines read together, with one translation. A **line** is one line of Japanese.

## 3. For every line

Each line has:

- `source`: the Japanese, exactly as written.
- `furigana`: a list of `{"text": ..., "reading": ...}` tokens. Joining every `text` gives `source` again. `reading` is in hiragana and only on tokens that contain kanji. Split so that a reading covers only the kanji and not the okurigana after it: 持って is `{"text": "持", "reading": "も"}` then `{"text": "って"}`. An irregular reading stays on its kanji: 三つ is `{"text": "三", "reading": "みっ"}` then `{"text": "つ"}`, and 一人 is one token `{"text": "一人", "reading": "ひとり"}`. A prefix like お in お弁当 is its own token without a reading. Punctuation and kana tokens have no reading.
- `gloss`: a list of pieces, one per word or particle, in reading order. Each piece has:
  - `raw`: a label for the piece, written `"<meaning> (<word>)"`, for example `"umbrella (傘)"`. Kotowa does not draw it when `japanese` is there.
  - `japanese`: the word as it appears in the line. Always give it: Kotowa draws the piece from this, and a line whose pieces lack it loses the word-by-word view.
  - `reading`: its reading in hiragana.
  - `pos`: `noun`, `verb`, `adjective`, `particle`, `copula`, `adverb`, `pronoun`, `interjection`, `expression` (a set phrase such as いらっしゃいませ) or another plain word. A desiderative like 行きたい is `adjective`.
  - `lemma`: the dictionary form, written with kanji when it is usually written so (行こう has the lemma 行く, いい has 良い, ください has 下さる). Leave it out for words that do not conjugate and for set phrases.
  - `translations`: the meaning in context, per language, such as `{"en": "umbrella"}`. Use the same language codes as the manifest's `targetLanguages`.
  - Optionally `word_class`: `{"cls": "verb"}` with `cls` one of `verb`, `adj`, `noun`, `part` (particles and the copula) or `other`. It sets the colour of the piece.
- `transliteration` (optional): the line in Hepburn romaji.

An item has `id`, `lines` (at least one) and `translations` (the sentence translation: an object such as `{"en": "..."}`). Put lines that are spoken together or share one translation in one item. Item ids only need to be unique within their unit. Optional on an item: `speaker` (`{"text": "太郎"}`; it may also have `furigana` like a line, `romaji`, and `translations` for a role such as `{"en": "mother"}`), `image` (a path inside the folder, such as `media/page1.png`), `timing` (see the format page, for a pack tied to a video).

Give a piece for every word and particle. Leave punctuation out of the gloss, but keep it in `furigana`. Join a prefix such as お to its word in one piece (お弁当). Take the time to get readings and meanings right. A wrong reading teaches a learner the wrong thing.

## 4. A complete small pack

`manifest.json`:

```json manifest.json
{
  "id": "morning-demo",
  "title": "A morning",
  "sourceLanguage": "ja",
  "targetLanguages": ["en"],
  "license": "private",
  "credits": "Written for this example. Sentence translations by an AI assistant; check them before relying on them.",
  "units": [
    {
      "id": "morning",
      "title": "朝",
      "order": "0001",
      "path": "units/morning.json",
      "itemCount": 0,
      "bytes": 0,
      "mediaBytes": 0
    }
  ]
}
```

`units/morning.json`:

```json units/morning.json
{
  "id": "morning",
  "title": "朝",
  "order": "0001",
  "items": [
    {
      "id": "t1",
      "lines": [
        {
          "source": "今日は雨だ。",
          "transliteration": "kyou wa ame da.",
          "furigana": [
            { "text": "今日", "reading": "きょう" },
            { "text": "は" },
            { "text": "雨", "reading": "あめ" },
            { "text": "だ" },
            { "text": "。" }
          ],
          "gloss": [
            { "raw": "today (今日)", "japanese": "今日", "reading": "きょう", "pos": "noun", "translations": { "en": "today" }, "word_class": { "cls": "noun" } },
            { "raw": "topic marker (は)", "japanese": "は", "reading": "は", "pos": "particle", "translations": { "en": "topic marker" }, "word_class": { "cls": "part" } },
            { "raw": "rain (雨)", "japanese": "雨", "reading": "あめ", "pos": "noun", "translations": { "en": "rain" }, "word_class": { "cls": "noun" } },
            { "raw": "is (だ)", "japanese": "だ", "reading": "だ", "pos": "copula", "translations": { "en": "is" }, "word_class": { "cls": "part" } }
          ]
        }
      ],
      "translations": { "en": "It is raining today." }
    },
    {
      "id": "t2",
      "speaker": { "text": "太郎" },
      "lines": [
        {
          "source": "傘を持って行こう。",
          "transliteration": "kasa o motte ikou.",
          "furigana": [
            { "text": "傘", "reading": "かさ" },
            { "text": "を" },
            { "text": "持", "reading": "も" },
            { "text": "って" },
            { "text": "行", "reading": "い" },
            { "text": "こう" },
            { "text": "。" }
          ],
          "gloss": [
            { "raw": "umbrella (傘)", "japanese": "傘", "reading": "かさ", "pos": "noun", "translations": { "en": "umbrella" }, "word_class": { "cls": "noun" } },
            { "raw": "object marker (を)", "japanese": "を", "reading": "を", "pos": "particle", "translations": { "en": "object marker" }, "word_class": { "cls": "part" } },
            { "raw": "hold (持って)", "japanese": "持って", "reading": "もって", "pos": "verb", "lemma": "持つ", "translations": { "en": "hold, carry" }, "word_class": { "cls": "verb" } },
            { "raw": "let's go (行こう)", "japanese": "行こう", "reading": "いこう", "pos": "verb", "lemma": "行く", "translations": { "en": "let's go" }, "word_class": { "cls": "verb" } }
          ]
        }
      ],
      "translations": { "en": "Let's take an umbrella." }
    }
  ]
}
```

The `itemCount`, `bytes` and `mediaBytes` in the manifest are filled in by the checker in step 6, so leave them as `0` while you write.

## 5. How to lay out each kind of input

- **A text.** One unit per chapter or section (a heading starts a new one), or one unit for a short text. Name the items `t1`, `t2`, and so on. Put the lines of a paragraph that read together in one item. Keep a line under about 40 characters when you can by splitting after a sentence end, and after a comma (、) in a sentence that is still longer.
- **Subtitles.** One unit for the video, one item per subtitle, in time order. Add `timing` to each item and a `videos` entry to the manifest (see the format page). The pack never contains the video.
- **Comic pages.** One unit per page, one item per panel, with the page's image on every item of that page (`"image": "media/page1.png"`). Copy the images into `media/` (PNG, JPEG, WebP or GIF). Type the text of each panel yourself from the image. Put the speaker in `speaker`.
- **A script.** One unit (or one per scene), one item per spoken line, with `speaker`.
- **Several units.** Give each an `order` that sorts as text (`0001`, `0002`, ...). Use `parentId` to nest units in a folder (see the format page).
- **Long inputs.** Write one unit file at a time. Do not try to hold everything at once.

## 6. Check it, zip it, hand it over

1. Download the checker next to your folder: `curl -O https://kotowa.gellyfish.dev/validate-pack.mjs`
2. Fill in the sizes: `node validate-pack.mjs <folder> --fix-sizes`
3. Check: `node validate-pack.mjs <folder>`. It must print `OK`. Fix every problem it lists and run it again. The checker knows the same rules as the app, and a few more.
4. Zip the contents of the folder so that `manifest.json` is at the top: `cd <folder> && zip -r ../<pack-id>.zip .`
5. Tell the person to put the .zip on their iPhone or iPad (AirDrop, or iCloud Drive or any folder in Files), then open Kotowa, go to the Library and tap **Add pack**.

If you change a pack and make it again with the same id, Kotowa asks whether to replace the old one.

The JSON Schemas above can validate the files as well, with any validator for JSON Schema draft 2020-12 (for example Python's `jsonschema`). The checker above is enough on its own; the schemas also check types, and some required fields that the app does not check.
