Формат словаря для импорта
od-dict/1 · контракт импорта пользовательских словарей
jmdict-<язык>-*.json.zip (или .tgz), например jmdict-rus,
jmdict-eng, jmdict-ger — и импортируйте архив как есть. Наш формат это
надмножество, так что ни конвертировать, ни распаковывать не нужно. А jmdict-all
(все языки в одном файле) брать не стоит — один огромный словарь неудобен.
→ релизы jmdict-simplifiedХотите собрать свой словарь из другого источника (словарь Yomitan, CSV, ваши данные)? Дальше — подробное описание формата.
Приложение умеет подключать ваши собственные словари. Один словарь — это
один JSON-файл в формате od-dict/1. Вы готовите такой файл, конвертируя
существующий словарь (например, словарь Yomitan) — вручную или с помощью LLM, — а приложение строит
из него свою базу.
Формат — это надмножество jmdict-simplified
(scriptin/jmdict-simplified): корректное
слово из jmdict-simplified уже валидно здесь, так что при таком источнике конвертация почти не нужна.
docs/od-dict-format.md.
1. Файл целиком
{
"format": "od-dict/1", // обязательно, точная строка
"source": "custom", // обязательно; id этого словаря (§2)
"metadata": { // необязательно; только для отображения/происхождения
"title": "My Dictionary",
"license": "CC BY-SA 4.0",
"url": "https://…"
},
"tags": { "v1": "Ichidan verb" }, // необязательно; НЕ используется (§6)
"words": [
{
"id": "1259420", // обязательно; УНИКАЛЕН в пределах файла (§3)
"kanji": ["食べる"], // необязательно; опустите для слов только из каны
"kana": ["たべる"], // хотя бы одна форма (kanji или kana) обязана быть
"sense": [ // обязательно; ≥1 (пусто — только у корпусной статьи, §3)
{
"partOfSpeech": ["v1"], // необязательно; ТОЛЬКО коды POS, не текст (§4)
"misc": ["transitive"], // необязательно; свободные подписи, показываются как есть
"gloss": [ // переводы
{ "lang": "ru", "text": "есть; кушать" },
"пожирать" // голая строка = { lang: "ru", origin: "original" }
],
"examples": [ ["ご飯を食べる", "есть рис"] ], // необязательно; примеры (§5a)
"references": [] // необязательно; перекрёстные ссылки (§5)
}
]
}
]
}
2. Верхний уровень
| поле | обяз. | значение |
|---|---|---|
format | да | Ровно "od-dict/1". |
source | да | Строка-id всего словаря. Стабильный слаг ("custom", "jitendex"). Это пространство имён для id и перекрёстных ссылок. Неизвестный источник просто ранжируется после встроенных словарей — никогда не отвергается. |
metadata | нет | Свободный объект (title, license, url). Хранится только для отображения; сборщик его не читает. |
tags | нет | Карта код → подпись для совместимости с jmdict-simplified. Не используется (§6). Нужные подписи кладите прямо в misc. |
words | да | Массив статей. |
Безопасность: задумано принудительно выставлять source = "custom" при импорте, чтобы файл не выдал себя за "warodai"/"jmdict". Это пока не реализовано — считайте source рекомендательным до появления проверяющего импортёра.
3. Слова и идентичность
{
"id": "1259420",
"homograph": "II", // необязательно; различитель, если head+чтение совпадают
"label": "colloquial", // необязательно; тег уровня статьи, показывается на карточке
"headnote": "文語形は「食ぶ」", // необязательно; свободный текст перед значениями (см. ниже)
"isExpression": true, // необязательно; фраза из нескольких слов
"common": true, // необязательно; высокочастотная статья
"kanji": ["食べる", "喰べる"], // 0…n письменных форм
"kana": ["たべる"], // 0…n чтений
"sense": [ /* … */ ],
"examples": [ /* … */ ] // необязательно; примеры уровня статьи — корпус (§5a)
}
Идентичность — это (source, id), и она UNIQUE.
idобязан быть уникальным в файле. Таблица пишется обычнымINSERTс ограничениемUNIQUE(source, source_entry_id), поэтому дубликатidроняет и прерывает весь батч импорта — не молча. Берите собственный id источника; если его нет — синтезируйте стабильный (счётчик или сам заголовок).- Элементы
kanji/kana— голая строка (норма) или объект jmdict-simplified{ "text": "…" }; читается толькоtext, остальное принимается, но отбрасывается. Голая строка предпочтительнее. - У слова обязана быть хотя бы одна форма. Для японского давайте
kana; для слов только из каны опускайтеkanji. headnote— текст статьи до значений: то, что ваш словарь печатает между заголовком и первым значением (грамматическая справка, вводная о употреблении), одной свободной строкой. Показывается дословно обычным текстом над значениями, у каждого источника — свой. Поле только для отображения: оно не индексируется поиском и не читается морфологией — не пытайтесь кодировать в нём машиночитаемую грамматику. Отличие отlabel: тот — короткий тег-чип. Строка из одних пробелов считается отсутствующей.examplesна уровне статьи — для словаря-корпуса (только примеры): предложения, относящиеся к слову целиком, а не к отдельному значению; у каждого может быть цитата-источникtag(§5a, форма (c)). Такая корпусная статья — единственный случай, когда допустим пустойsense; обычная статья кладёт примеры внутрь значений, иsenseобязан быть ≥1.
Группировка. Приложение показывает одну карточку на группу
(заголовок + чтение), объединяя словари. В своём файле выдавайте
одно слово на статью — все письменные формы в kanji, все чтения в
kana, все значения в sense. Если источник разбивает статью на строки
(Yomitan — через общий sequence), сначала слейте их в одно слово, иначе получите
несколько карточек на одну статью.
4. partOfSpeech — единственное поле, тихо ломающее поиск
Это единственное поле, где неверное значение убирает слово из поиска без всякой ошибки.
POS не влияет на поиск по словарной форме или чтению — 食べる и
たべる находятся всегда. POS используется ровно в одном месте: ворота
деинфлексии — нахождение слова по словоформе, которую набрал пользователь
(食べた, 静かな).
| POS статьи | вердикт | эффект |
|---|---|---|
| отсутствует / не класс спряжения | weak | всё равно находится, чуть ниже в ранге |
| класс спряжения, который совпал | confirmed | находится, обычный ранг |
| класс спряжения, который не совпал | rejected | выбрасывается — не находится из этой словоформы |
Не уверены в классе спряжения — опускайте partOfSpeech целиком.
Отсутствие безопасно (weak), ошибка фатальна (rejected). Опустить лучше, чем угадать.
Правила:
- Только коды, никогда свободный текст. Читаемые подписи («verb», «honorific»)
кладите в
misc— там они показываются дословно. - Значимы только классы спряжения. Прочие коды (
n,exp,vt,adv…) поиском игнорируются — безвредны, но бесполезны. - Ловушка Yomitan: никогда не копируйте схлопнутый
v5. У движка нет классаv5— нужен конкретный (v5k,v5r…). Голый"v5"не совпадает ни с чем →rejected, то есть хуже, чем без тега. Берите конкретный код изdefinitionTags, либо разворачивайте по последней кане словарной формы, либо опускайте.
Классы спряжения, которые понимает движок:
v1 ичидан (-ru): 食べる, 見る
v5u v5k v5g v5s v5t v5n v5b v5m v5r годан, по финальной кане словарной формы
vk 来る (kuru)
vs vs-i vs-s する-глаголы
adj-i い-прилагательные: 高い, 良い
adj-na な-прилагательные: 静か, 綺麗
5. Глоссы, языки и перекрёстные ссылки
"гулять" // голая строка = { "lang": "ru", "origin": "original" }
{ "lang": "en", "text": "to take a walk" }
{ "lang": "ru", "text": "то же, что {0}", "origin": "original" }
- Голая строка — это русский (
lang: "ru"). Для любого другого языка нужен объект сlang. origin—"original"(по умолчанию) или"machine"(создано ИИ); только отображение.- Как
langвлияет на поиск: глосс сlang: "ru"попадает в русский поисковый канал, любой другойlang— в английский. Канал выбирается по письменности запроса: кириллица → ru, латиница → en. Поэтому для японско-русского словаря глоссы должны бытьlang: "ru", иначе кириллический поиск их не найдёт. Одноязычный японский глосс (lang: "ja") попадает в en-канал и фактически становится только отображаемым — слово всё ещё находится по заголовку и чтению, но не по тексту определения.
Перекрёстные ссылки — ICU-плейсхолдеры {0}, {1} … в тексте
глосса, разрешаемые через массив references:
{
"gloss": [ { "lang": "ru", "text": "вежливая форма {0}" } ],
"references": [ { "label": "", "to": "1234567", "text": "言う" } ]
}
{0}заменяется на месте наreferences[0].textкак нажимаемая ссылка.to— этоidцелевого слова в этом же файле; ссылка открывает{ source: <этот словарь>, id: to }. Несуществующийtoпросто ничего не открывает.label— маркер связи ("see","cf."), для ссылок без{n}(рендерятся после глосса). Литеральная{в тексте пишется как'{'(ICU-экранирование).
5a. Примеры предложений — три допустимые формы
Каждый элемент examples может иметь любую из трёх форм —
импортёр распознаёт её сам:
// (a) позиционная пара od-dict/1 — [японский, перевод];
// необязательный 3-й элемент помечает идиому.
"examples": [ ["ご飯を食べる", "есть рис"] ]
// (b) объект jmdict-simplified — как в релизе jmdict-examples-eng.
// Предложение jpn становится примером, eng — его переводом.
"examples": [ {
"source": { "type": "tatoeba", "value": "162365" },
"text": "食べる",
"sentences": [ { "lang": "jpn", "text": "私はリンゴを食べる。" },
{ "lang": "eng", "text": "I eat an apple." } ]
} ]
// (c) корпусный объект od-dict/1 — то, чего не умеет пара:
// цитата-источник tag у каждого примера.
"examples": [ { "text": "彼は走った。", "translation": "He ran.", "tag": "一/坊っちゃん" } ]
- Для формы (b): ключ предложения —
lang(трёхбуквенный ISO 639-3:jpn/eng). Элемент без предложенияjpnпропускается; отсутствующий перевод даёт пустую строку. Именно поэтому сырой файлjmdict-examples-engимпортируется сразу с примерами. - Форма (c) — для примеров, взятых из текстов:
tag— свободная подпись «откуда цитата» (глава, произведение), показывается рядом с примером. Такие примеры корпусный сборщик кладёт на уровень статьи (§3 — словарь из одних примеров), но форма равно принимается и вsense.examples.
6. Чего формат НЕ несёт
Эти данные приложение добавляет само, по поверхности/чтению, чтобы все словари были согласованы —
их не нужно задавать: тоновое ударение, фуригана, ромадзи/Поливанов,
частотность/«common», аудио/TTS, уровни JLPT/WaniKani, разбор кандзи. Также не используются (хотя
принимаются ради совместимости): верхнеуровневый tags и пер-форменные/пер-глоссовые
extras. Карточка рендерит misc/field/dialect как
свободные строки дословно — раскрытия кодов нет, кладите туда читаемые подписи.
7. Минимальные примеры
Самое маленькое слово — только кана и один русский глосс:
{ "id": "w1", "kana": ["ありがとう"], "sense": [ { "gloss": ["спасибо"] } ] }
// (a) JA→EN годан. POS — КОНКРЕТНЫЙ класс v5k, а не "v5" из Yomitan.
{ "id": "1578850", "kanji": ["書く"], "kana": ["かく"],
"sense": [ { "partOfSpeech": ["v5k", "vt"],
"gloss": [ { "lang": "en", "text": "to write; to compose; to pen" } ] } ] }
// (b) JA→RU с внутренней перекрёстной ссылкой.
{ "id": "敷衍", "kanji": ["敷衍", "敷延"], "kana": ["ふえん"],
"sense": [ { "gloss": [ { "lang": "ru", "text": "развёртывание мысли (ср. {0})" } ],
"references": [ { "label": "", "to": "演繹", "text": "演繹" } ] } ] }
// (c) な-прилагательное только из каны. adj-na — класс деинфлексии, тег уместен.
{ "id": "1000230", "kana": ["きれい"],
"sense": [ { "partOfSpeech": ["adj-na"], "gloss": ["красивый; чистый; опрятный"] } ] }
// (d) настоящая корпусная статья из встроенного текста «Боттян» (坊っちゃん):
// примеры уровня статьи, цитата tag — номер главы, sense отсутствует (§3, §5a).
// Перевод английский (PD, Morri 1918) — переводы корпуса только английские.
{ "id": "無鉄砲·むてっぽう", "kanji": ["無鉄砲"], "kana": ["むてっぽう"],
"examples": [ { "text": "親譲りの無鉄砲で小供の時から損ばかりしている。",
"translation": "Because of an hereditary recklessness, I have been playing always a losing game since my childhood.",
"tag": "一" } ] }
8. POS-allowlist
Помещайте код в partOfSpeech, только если он один из этих (или подкласс jmdict,
начинающийся с одного из них). Всё прочее — человеческую подпись в misc, а здесь опустить.
v1
v5u v5k v5g v5s v5t v5n v5b v5m v5r
vk
vs vs-i vs-s
adj-i
adj-na
Схлопнутый Yomitan "v5" → развернуть по финальной кане:
う→v5u く→v5k ぐ→v5g す→v5s つ→v5t ぬ→v5n ぶ→v5b む→v5m る→v5r.
Не уверены — опускайте.