{dict_root_folder}/
├── {dict_id_1}/ # 文件夹名必须与 metadata.json 中的 id 一致
│ ├── metadata.json # 词典元数据
│ ├── logo.png # 词典 Logo
│ ├── dictionary.db # 词条数据库
│ └── media.db # 媒体资源数据库(可选)
├── {dict_id_2}/
│ └── ...
└── {dict_id_3}/
└── ...
{
"id": "example_dict", //必填
"source_language": "en", //必填
"target_language": ["en", "zh"], //必填
"name": "Example Dictionary",
"description": "An example dictionary for demonstration purposes",
"publisher": "Example Publisher",
"maintainer": "example_user",
"encode": "utf-8",
"contact_maintainer": "example@example.com",
"version": 13 //一定要是整型!!!
}
CREATE TABLE config (
key TEXT PRIMARY KEY,--唯一键值为'zstd_dict'
value BLOB --这里储存zstd的字典,用于压缩和解压
);--只有一行
CREATE TABLE entries (
entry_id INTEGER PRIMARY KEY,
json_data BLOB--储存使用zstd压缩后的json数据
);--数据表
CREATE TABLE indices (
id INTEGER PRIMARY KEY,
headword TEXT NOT NULL,--原始headword
headword_normalized TEXT NOT NULL,--标准化后的headword。建立索引
phonetic TEXT,--标准化后的phonetic。建立索引
entry_type TEXT,--entry类型
entry_id INTEGER NOT NULL,--关联entries表
anchor TEXT,--JSON路径,用于定位词条内的具体位置
FOREIGN KEY (entry_id) REFERENCES entries(entry_id) ON DELETE CASCADE
);--索引表,每个entry可能有多条索引记录
CREATE TABLE groups (
group_id TEXT PRIMARY KEY,
parent_id TEXT, -- 父级组ID
name TEXT NOT NULL, -- 组名
description TEXT, -- 组的描述,JSON文本
item_list TEXT DEFAULT '[]', -- 组内项目列表 [{"e": 212, "a": "sense_group.0.sense.1"}]
sub_group_count INTEGER DEFAULT 0, -- 直接子组数量
item_count INTEGER DEFAULT 0, -- item_list 长度
FOREIGN KEY (parent_id) REFERENCES groups(group_id) ON DELETE CASCADE
);--分组表,组织词条结构
CREATE INDEX idx_groups_parent ON groups(parent_id);
CREATE INDEX idx_headword_norm ON indices(headword_normalized);
CREATE INDEX idx_phonetic ON indices(phonetic);
CREATE INDEX idx_indices_entry_id ON indices(entry_id);
CREATE TABLE audios (
name TEXT PRIMARY KEY,--音频名,带文件后缀
blob BLOB NOT NULL--无压缩,二进制数据
);
CREATE TABLE images (
name TEXT PRIMARY KEY,--图片名,带文件后缀
blob BLOB NOT NULL--无压缩,二进制数据
);
CREATE INDEX idx_audios_name ON audios(name);
CREATE INDEX idx_images_name ON images(name);
词典作者需要准备以下文件,然后调用 auxi_tools/build_dictionary.py 脚本生成词典数据库。
| 文件 |
必需 |
说明 |
entries.jsonl |
是 |
词条数据文件,每行一个 JSON 对象,详见 |
groups.jsonl |
否 |
分组数据文件,每行一个 JSON 对象,详见 |
audio/ 文件夹 |
否 |
音频文件目录,文件名需与词条中的 audio_file 字段对应 |
image/ 文件夹 |
否 |
图片文件目录,文件名需与词条中的 image_file 字段对应 |
python build_dictionary.py <jsonl_path> <lang> [options]
| 参数 |
说明 |
默认值 |
jsonl_path |
JSONL 文件路径 |
- |
lang |
语言代码(如 zh, jp, en) |
- |
| 参数 |
说明 |
默认值 |
--dict-size <KB> |
Zstd 字典大小 (KB) |
112 |
--compress-level <N> |
Zstd 压缩级别 |
7 |
--page-size <BYTES> |
SQLite 页大小 (字节) |
4096 |
--audio-dir <path> |
音频文件夹路径 |
- |
--image-dir <path> |
图片文件夹路径 |
- |
--groups <path> |
groups.jsonl 文件路径 |
- |
-o, --output <path> |
输出目录路径(默认为 JSONL 文件所在目录) |
- |
# 基础用法:仅生成词典数据库
python auxi_tools/build_dictionary.py data/entries.jsonl en
# 自定义压缩参数
python auxi_tools/build_dictionary.py data/entries.jsonl zh \
--dict-size 128 \
--compress-level 9
# 完整用法:包含媒体资源和分组
python auxi_tools/build_dictionary.py data/entries.jsonl zh-hant \
--audio-dir data/audio \
--image-dir data/image \
--groups data/groups.jsonl \
-o output/my_dict
执行成功后会在输出目录生成以下文件:
dictionary.db - 词典主数据库
media.db - 媒体资源数据库(如有音频或图片)
- jsonl格式,每行一个json格式的
entry数据,entry是词典组织内容的基础单位。
- 同一个词头可下涵多个
entry,entry有两个重要属性,page和section。
- 同一个词头的诸多
entry按照page属性分类,同一个page的多个entry组成一个独立单元,比如“药学词典”page、”儿童词典“page、“美语词典”page、”英语词典“page等。
- 同一个page的各个
entry之间通过section属性区分,section可以表示不同起源,或是不同词性等等。
- data和board内部还可以继续嵌套data或board
- 强烈建议data和board里需要显示的文本键名为语言代码,比如
"zh":"这是一句话"。
- pronunciation、sense、sense_group、example后面可以是符合格式的map,也可以是符合格式的map组成的列表
- 文本中可以使用
[{someword}](anchor)的格式化文本,若如此做,查词{someword}时可以查到本entry,并滚动到此为止
| 语法 |
说明 |
strike |
删除线 |
underline |
下划线 |
double_underline |
双下划线 |
dashed |
虚线 |
wavy |
波浪线 |
bold |
加粗 |
italic |
斜体 |
sup |
上标 |
sub |
下标 |
color |
主题色 |
special |
主题色、斜体 |
label |
一个带背景和边框的标签 |
ai |
AI生成的内容 |
:かん |
日文振假名(Ruby) |
~apple.svg |
行内图片,与文本等高 |
->headword |
查词headword |
=>group_id |
指向group_id的界面 |
==entry_id::path |
根据entry_id和path精确跳转 |
==entry_id |
跳转到entry_id |
::path |
跳转到目标json_path |
"Fruit, such as apple, [banana](banana)."
"For more information, please [see here](==18551::sense_group.0.sense.1)."
"Wow, you are so [pretty](color,bold)!"
储存jsonl格式,每行的json格式要求如下
# 安装依赖
flutter pub get
# 运行应用
flutter run
| 平台 |
命令 |
| Windows |
flutter build windows |
| Android |
flutter build apk |
| macOS |
flutter build macos |
| iOS |
flutter build ios |
| Linux |
flutter build linux |
| 参数 |
说明 |
ENABLE_LOG=true |
启用日志输出(默认关闭),调试时使用 |
LOG_TO_FILE=true |
将日志同时写入文件,适合 Release 模式调试 |
两个参数可以同时使用:
flutter run --dart-define=ENABLE_LOG=true --dart-define=LOG_TO_FILE=true
flutter build windows --dart-define=LOG_TO_FILE=true
{ "dict_id": "my_dict", // 必填,词典id "entry_id": 212, // 必填,**不重复**的entry标识符,**整型** "headword": "fog", // 与headline二选一。可重复的词头 "headline": "つける【付ける・附ける】", // 与headword二选一。如果选择headline,则必须使用links字段,用来表明查什么词可以查到本词头 "headword_syllable": "en·thu·si·asm", //记录音节 "links": "from_word", //可以是string或者是list of string,查询"from_word"时也能查到本词条 "phonetic": "pinyin", // 可选,辅助搜索词,主要用于表意文字 "entry_type": "word", // 可选,word或phrase等等 "groups": [122, 254], // 可选,对应groups表中的group_id,可以是数字,也可以是数字列表 "page": "medical", // 可选,比如“药学词典”、“美语词典”,查词界面会根据不同的page给entry分组,同时只会显示一组page相同的entry "section": "noun", // 可选,区分同一个page下不同的entry,section可以是不同起源,也可以是不同词性 "certifications": ["IELTS", "TOEFL", "CET-4"], // 可选,还没想好怎么实现 "frequency": { "level": "B1", "stars": "3/5", "source": "Oxford 3000", }, // 可选,还没想好怎么实现 "stroke": "3", // 可选,笔画数 "pos": "n", // 可选,词性,仅在当前entry只有一个词性时使用!!否则请通过sense_group的group_name实现 "pronunciation": [ { "region": "US", "notation": "/fɔːɡ/", "audio_file": "fog_us.mp3", "note": "something to add", }, { "region": "UK", "notation": "/fɒɡ/", "audio_file": "fog_uk.opus", }, ], //可选,可以是map,也可以是list of map "phrases": ["fog in", "fog of"], // 可选,短语部分,点击后将以小窗的形式展开短语内容 "data": { "key1": {}, "key2": {}, }, //可选,本部分为自定义数据部分,会渲染为tab组件,key1,key2会显示为tab名。**value一定要是map**,data可以放在词典的任何地方 "customKey": {}, //除了规范里给定的键值外,还可以添加自定义键值,这会被渲染为board元素。board标题为customKey,customKey对应的值需要是一个map,map里的键值对会被渲染为board的内容。 "table": { "column": ["id", "姓名", "职位"], "content": [ [1, "张三", "工程师"], [2, "李四", "设计师"], ], }, //表格元素可以放在json的任意位置 "sense": [ { "index": 1, //必选 "label": { "signpost": "same opinion", "word": "someword", "pos": "n", "grammar": ["U", "S"], "pronunciation": "/dɔːɡ/", "variant": "foggy", "region": "global", "pattern": ["in a ~", "mental ~"], "register": "informal", "usage": ["figurative"], "tone": "neutral", "complex": "雜", "topic": ["psychology"], "others": "other label", //可以使用自定义的键名 }, //里面全部是可选,里面所有的值都既可以是string,也可以是string list。label的值既可以是一个map,也可以是map list。 "definition": { "zh": "困惑,迷惘;(理智、感情等)混浊不清的状态", "en": "A state of mental confusion or uncertainty.", }, //释义字段,map里可以有多个键值对,但键值一定要是metadata.json中target_language列表里有的值 "tail": { "synonym": "test", "antonym": ["test", "test2"], "related": ["test", "test2"], "others": "", }, //里面任意元素可以是string,也可以是list of string,现实在definition后面 "image": { "image_file": "fog.jpg", }, //可选 "note": { "zh": "常用于 'in a fog' 结构,描述因疲倦或震惊而无法正常思考。", }, //可选,注释部分,键名需要是语言名 "example": [ { "usage": "take courage/guts", //例句的用法 "en": "He was walking around in a mental fog after the accident.", "zh": "事故发生后,他整个人都陷入了意识模糊的状态中。", "source": { "cited_in": { "title": "《太平广记》", "volume": "卷九", }, "head": "some", "year": 2025, // 可选:年份(中文词典表示朝代) "author": "Robert Louis", // 可选:作者 "title": "Mental States and Trauma", // 可选:书名 "publisher": "Health Press", // 可选:出版社 "page": "45-47", // 可选:页码 "edition": "2nd", // 可选:版次 "tail": "some", }, //可选,例句来源。渲染格式:非中文 (年份) 作者. 书名. 出版社, 页码.;中文 朝代 · 作者 · 书名 "comment": {}, //结构和example相同 "audios": [ { "region": "UK", // 可选,例句音频地区 "audio_file": "fog_ex1_uk.mp3", }, ], //可选,例句音频 }, { "usage_group": "take (sb) sth (to do sth)", //一个例句用法中有多组例句 "example": [ { "en": "Repairs take time to carry out." }, { "en": "[It took](bold) a few minutes for his eyes to adjust to the dark.", }, ], }, //特殊例句,里面包括一个usage_group名,和一个嵌套的example元素 { "en": "[It takes](bold) courage to admit you are wrong.", }, ], //可选 "subsense": [ { "index": "a", "definition": {}, }, { "index": "b", "definition": {}, }, ], //释义的子释义,格式与释义的格式相同 }, ], "sense_group": [ { "group_name": "noun", //释义组的组名(可选,无则不渲染组名行) "group_sub_name": "obsoleted sense", //释义组的副组名(可选,无则不渲染副组名行) "sense": [{}, {}], }, { "sense": [{}, {}], // 无 group_name 和 group_sub_name 时,仅渲染 sense 列表 }, ], //释义组 "child_xxxx": [ { "headword": "someword1", "pos": "adj", "sense": [], }, //以"child_xxxx"作为键名的元素,会被渲染为一个可折叠的标题为xxxx的组件,值里的字段支持使用和根节点相同的字段名,适用于不想单独被提取为entry的衍生词、习语等内容。 { "headword": "someword2", "sense_group": {} }, ], "text": "any text", //在这里显示任意文本 "clob": "any text", //在这里显示任意文本,并且不会使用格式化文本渲染 }