Hunspell 拼写检查完全教程 / 第 06 章:自定义词典开发
第 06 章:自定义词典开发
6.1 为什么需要自定义词典
标准词典无法覆盖所有场景:
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 项目术语 | 技术词汇不在标准词典中 | 项目级自定义词典 |
| 专有名词 | 人名、地名、公司名 | 个人词典 |
| 行业术语 | 医学、法律、金融专业词 | 行业词典 |
| 缩略语 | API、HTTP、JSON 等 | 缩略语词典 |
| 新造词 | 新出现的网络用语、品牌名 | 动态更新词典 |
| 多语言文档 | 混合语言内容 | 多语言合并词典 |
6.2 个人词典(Personal Dictionary)
6.2.1 文件格式
个人词典是简单的文本文件,每行一个单词:
# ~/.hunspell_personal
# 个人词典 - 第一行可选编码声明
Hunspell
API
APIs
JSON
HTTP
GitHub
Docker
kubernetes
golang
6.2.2 使用个人词典
# 检查时加载个人词典
hunspell -d en_US -p ~/.hunspell_personal document.txt
# 多个个人词典合并
hunspell -d en_US -p ~/.hunspell_personal -p ./.hunspell_project document.txt
6.2.3 在交互模式中管理
# 启动交互模式,指定个人词典
hunspell -c -p ~/.hunspell_personal document.txt
# 遇到新词时的交互命令:
# A — 将当前词加入个人词典(保留大小写)
# I — 将当前词小写形式加入个人词典
# U — 将当前词词根形式加入个人词典
6.2.4 带 affix 标志的个人词典
# 个人词典也可以使用 affix 标志
# 格式与 .dic 文件中的条目相同
API/S # API → APIs(S 标志 = 复数)
Kubernetes # 只接受此形式
docker/S # docker → dockers
6.3 项目词典
6.3.1 典型项目词典结构
myproject/
├── docs/
│ ├── README.md
│ └── ...
├── .hunspell/ # 词典目录
│ ├── project.dic # 项目词典
│ └── project.aff # 项目词缀文件(可选)
└── .gitignore # 可选择是否忽略词典
6.3.2 创建项目词典
# 创建项目词典
mkdir -p .hunspell
# 从项目文件中提取专有名词和技术术语
cat > .hunspell/project.dic << 'EOF'
# 项目术语词典
# 编程语言
golang
TypeScript
JavaScript
Python
Rust
# 框架和工具
Docker
Kubernetes
Redis
PostgreSQL
GraphQL
RESTful
gRPC
# 项目特有术语
BaaS
microservice
webhook
tokenization
autoscaler
# 人名/团队
GitHub
GitLab
EOF
6.3.3 自动提取项目术语
#!/bin/bash
# extract_terms.sh - 从项目文件中提取候选术语
# 找出被 Hunspell 标记为错误但出现频率较高的词
DIR="${1:-.}"
DICT="${2:-en_US}"
FREQ_THRESHOLD=3
echo "=== 项目术语提取 ==="
# 收集所有拼写"错误"的词
find "$DIR" -type f \( -name "*.md" -o -name "*.txt" -o -name "*.rst" \) -print0 | \
xargs -0 hunspell -l -d "$DICT" 2>/dev/null | \
sort | uniq -c | sort -rn | \
while read count word; do
if [ "$count" -ge "$FREQ_THRESHOLD" ]; then
echo "$count $word"
fi
done
输出示例:
=== 项目术语提取 ===
47 kubernetes
35 golang
28 webhook
19 microservice
12 tokenization
6.3.4 词典生成脚本
#!/bin/bash
# gen_project_dict.sh - 自动生成项目词典
# 用法: ./gen_project_dict.sh <目录> [主词典]
DIR="${1:-.}"
MAIN_DICT="${2:-en_US}"
OUTPUT=".hunspell/project.dic"
FREQ_THRESHOLD=2
mkdir -p .hunspell
echo "# 自动生成的项目词典" > "$OUTPUT"
echo "# 生成时间: $(date)" >> "$OUTPUT"
echo "# 频率阈值: $FREQ_THRESHOLD" >> "$OUTPUT"
find "$DIR" -type f \( -name "*.md" -o -name "*.txt" -o -name "*.rst" -o -name "*.html" \) -print0 | \
xargs -0 hunspell -l -d "$MAIN_DICT" 2>/dev/null | \
sort | uniq -c | sort -rn | \
while read count word; do
if [ "$count" -ge "$FREQ_THRESHOLD" ]; then
echo "$word" >> "$OUTPUT"
fi
done
TOTAL=$(grep -cv "^#" "$OUTPUT")
echo "已生成项目词典: $OUTPUT"
echo "包含 $TOTAL 个术语"
6.4 词表添加技巧
6.4.1 分类词表管理
# 按类别组织词典
.hunspell/
├── project.dic # 主项目词典(合并所有)
├── terms/
│ ├── programming.dic # 编程术语
│ ├── company.dic # 公司/产品名
│ ├── people.dic # 人名
│ └── abbreviations.dic # 缩略语
└── build.sh # 合并脚本
# build.sh - 合并分类词典
#!/bin/bash
cat .hunspell/terms/*.dic | grep -v "^#" | sort -u > .hunspell/project.dic
echo "合并完成: $(wc -l < .hunspell/project.dic) 个术语"
6.4.2 编程术语词表示例
# programming.dic
# 编程语言
TypeScript
JavaScript
Python
Rust
Golang
golang
Kotlin
Swift
Ruby
Perl
Lua
Haskell
Scala
Clojure
Erlang
Elixir
# 概念和模式
async
await
callback
closure
middleware
polymorphism
abstraction
encapsulation
inheritance
multithreading
concurrency
idempotent
refactor
deserialization
serialization
normalization
denormalization
# 工具和平台
Docker
Kubernetes
K8s
Redis
PostgreSQL
MySQL
MongoDB
Elasticsearch
Kafka
RabbitMQ
Nginx
Grafana
Prometheus
Jenkins
GitLab
GitHub
Bitbucket
Ansible
Terraform
Vagrant
6.4.3 公司/产品名词表示例
# company.dic
# 公司名
Google
Microsoft
Apple
Amazon
Meta
Netflix
Uber
Airbnb
Slack
Stripe
Cloudflare
Datadog
NewRelic
PagerDuty
Twilio
Auth0
Okta
Algolia
Terraform
Vercel
Netlify
DigitalOcean
Hetzner
OVH
Linode
6.4.4 从 CSV 导入词表
#!/bin/bash
# import_csv_dict.sh - 从 CSV 文件导入词表
# CSV 格式: word,category,notes
INPUT="$1"
OUTPUT="${2:-.hunspell/project.dic}"
echo "# 从 CSV 导入的词表" > "$OUTPUT"
echo "# 来源: $INPUT" >> "$OUTPUT"
# 提取第一列(word),跳过标题行
tail -n +2 "$INPUT" | cut -d',' -f1 | sort -u >> "$OUTPUT"
echo "导入完成: $(grep -cv "^#" "$OUTPUT") 个词条"
6.4.5 从 NPM/PyPI 包名提取
# 从 package.json 提取依赖名
cat package.json | python3 -c "
import json, sys
data = json.load(sys.stdin)
deps = list(data.get('dependencies', {}).keys())
deps += list(data.get('devDependencies', {}).keys())
for d in sorted(set(deps)):
print(d)
" > .hunspell/npm_deps.dic
# 从 requirements.txt 提取 Python 包名
grep -v '^#' requirements.txt | grep -v '^$' | \
sed 's/[>=<].*//' | sort -u > .hunspell/pip_deps.dic
6.5 词频与优先级
6.5.1 词频在建议中的作用
虽然 Hunspell 本身不直接支持词频(词典是无权重的),但可以通过以下方式影响建议质量:
# TRY 指令中的字符频率
TRY esianrtolcdugmphbyfvkwz
# 这个顺序影响建议排序,常用字母开头的词优先
# REP 指令中的替换优先级
REP 5
REP ie ei # recieve → receive(更常见的错误)
REP ei ie # 这个较少见
REP gh f # enouf → enough
6.5.2 使用 affix 标志控制展开
# 不同词根使用不同标志,控制生成的词形数量
# 高频词:给予更多标志
run/DGS # 可生成:runs, ran, running
# 低频词:只给复数标志
abbreviation/SM # 只生成:abbreviations
# 技术术语:不展开
API # 只接受原始形式
6.6 Affix 标志使用技巧
6.6.1 标志设计原则
# 原则 1: 按词性分组标志
# 名词相关
SFX S ... # 复数 -s/-es
SFX M ... # 所有格 -'s
# 动词相关
SFX D ... # 过去式 -ed
SFX G ... # 现在分词 -ing
SFX T ... # 第三人称 -s
# 形容词相关
SFX R ... # 比较级 -er
SFX E ... # 最高级 -est
SFX L ... # 副词 -ly
SFX N ... # 名词化 -ness
# 前缀
PFX U ... # un-
PFX R ... # re-
PFX D ... # dis-
# 原则 2: 保留特殊标志
KEEPCASE K # 大小写敏感词
NOSUGGEST X # 不提供建议
NEEDAFFIX N # 必须有词缀
ONLYINCOMPOUND O # 仅复合词
FORBIDDENWORD F # 禁用词
6.6.2 标志组合策略
# 动词标志组合示例
walk/DGS # walk, walked, walking, walks
run/DGS # run, ran? running, runs (ran 需手动处理)
# 名词标志组合示例
cat/SM # cat, cats, cat's
child/SM # child, children? child's (不规则需手动处理)
# 形容词标志组合示例
happy/RYLN # happy, happier, happiest, happily, happiness
great/RYL # great, greater, greatest, greatly
6.6.3 创建完整的项目 affix 文件
# .hunspell/project.aff
SET UTF-8
FLAG long
# 通用复数
SFX S Y 2
SFX S 0 s [^sxzh]
SFX S 0 es [sxzh]
# 技术词汇复数 (y → ies)
SFX J Y 1
SFX J y ies [^aeiou]y
# 动词 -ing
SFX G Y 2
SFX G e ing e
SFX G 0 ing [^e]
# 动词 -ed
SFX D Y 2
SFX D 0 d e
SFX D 0 ed [^e]
# 不使用建议
NOSUGGEST X
# 保持大小写
KEEPCASE K
# 建议字符频率
TRY esianrtolcdugmphbyfvkwz
# .hunspell/project.dic
100
Docker/K
Kubernetes/K
API/S
APIs
TypeScript/K
JSON
HTTP
HTTPS
GraphQL/K
gRPC
webhook/S
microservice/JS
tokenization/S
autoscaler/S
6.7 大规模词典管理
6.7.1 词典版本控制
# .gitignore 建议配置
# 排除临时文件
.hunspell/*.tmp
.hunspell/*.bak
# 保留词典文件(团队共享)
# .hunspell/project.dic ← 不忽略
# .hunspell/project.aff ← 不忽略
6.7.2 词典合并脚本
#!/bin/bash
# merge_dicts.sh - 合并多个词典
# 用法: ./merge_dicts.sh dict1.dic dict2.dic dict3.dic > merged.dic
{
echo "# 合并词典"
echo "# 生成时间: $(date)"
for f in "$@"; do
echo "# 来源: $f"
done
# 合并所有词典,跳过注释和空行,去重排序
cat "$@" | grep -v "^#" | grep -v "^$" | sort -u
} | tee /dev/stderr | wc -l | xargs -I{} echo "合并后: {} 个词条"
6.7.7 词典差异比较
#!/bin/bash
# diff_dicts.sh - 比较两个词典的差异
DICT_A="$1"
DICT_B="$2"
echo "=== 词典差异比较 ==="
echo "词典 A: $DICT_A ($(grep -cv "^#" "$DICT_A") 个词条)"
echo "词典 B: $DICT_B ($(grep -cv "^#" "$DICT_B") 个词条)"
echo ""
# 只在 A 中
comm -23 <(grep -v "^#" "$DICT_A" | sort) <(grep -v "^#" "$DICT_B" | sort) > /tmp/only_a.txt
echo "只在 A 中: $(wc -l < /tmp/only_a.txt) 个"
head -10 /tmp/only_a.txt | sed 's/^/ /'
# 只在 B 中
comm -13 <(grep -v "^#" "$DICT_A" | sort) <(grep -v "^#" "$DICT_B" | sort) > /tmp/only_b.txt
echo "只在 B 中: $(wc -l < /tmp/only_b.txt) 个"
head -10 /tmp/only_b.txt | sed 's/^/ /'
# 两者共有
comm -12 <(grep -v "^#" "$DICT_A" | sort) <(grep -v "^#" "$DICT_B" | sort) > /tmp/common.txt
echo "共有: $(wc -l < /tmp/common.txt) 个"
6.7.4 词典验证脚本
#!/bin/bash
# validate_dict.sh - 验证自定义词典格式
DICT_FILE="$1"
echo "=== 词典验证: $DICT_FILE ==="
# 检查文件存在
if [ ! -f "$DICT_FILE" ]; then
echo "错误: 文件不存在"
exit 1
fi
# 统计行数
TOTAL=$(wc -l < "$DICT_FILE")
COMMENT=$(grep -c "^#" "$DICT_FILE")
EMPTY=$(grep -c "^$" "$DICT_FILE")
VALID=$((TOTAL - COMMENT - EMPTY))
echo "总行数: $TOTAL"
echo "注释行: $COMMENT"
echo "空行: $EMPTY"
echo "有效词条: $VALID"
echo ""
# 检查是否有重复
DUPES=$(grep -v "^#" "$DICT_FILE" | grep -v "^$" | sort | uniq -d)
if [ -n "$DUPES" ]; then
echo "警告: 发现重复词条:"
echo "$DUPES" | head -10 | sed 's/^/ /'
else
echo "✓ 无重复词条"
fi
# 检查空格开头的行
SPACE_LINES=$(grep -c "^ " "$DICT_FILE")
if [ "$SPACE_LINES" -gt 0 ]; then
echo "警告: $SPACE_LINES 行以空格开头"
fi
# 检查行尾空格
TRAIL_SPACE=$(grep -c " $" "$DICT_FILE")
if [ "$TRAIL_SPACE" -gt 0 ]; then
echo "警告: $TRAIL_SPACE 行有尾部空格"
fi
echo ""
echo "=== 验证完成 ==="
6.8 CI/CD 集成词典
6.8.1 GitHub Actions 词典检查
# .github/workflows/spellcheck.yml
name: Spell Check
on: [push, pull_request]
jobs:
spellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Hunspell
run: sudo apt-get install -y hunspell hunspell-en-us
- name: Run spell check
run: |
# 使用项目词典
ERRORS=$(find . -name "*.md" -not -path "./.git/*" \
-exec cat {} \; | \
hunspell -d en_US -p .hunspell/project.dic -l | \
sort -u)
if [ -n "$ERRORS" ]; then
echo "发现拼写错误:"
echo "$ERRORS"
exit 1
fi
echo "拼写检查通过 ✓"
6.8.2 pre-commit 钩子
#!/bin/bash
# .git/hooks/pre-commit - 拼写检查钩子
# 获取暂存的 Markdown 文件
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.md$')
if [ -z "$STAGED_FILES" ]; then
exit 0
fi
echo "运行拼写检查..."
ERRORS=""
for file in $STAGED_FILES; do
file_errors=$(hunspell -l -d en_US -p .hunspell/project.dic "$file" 2>/dev/null)
if [ -n "$file_errors" ]; then
ERRORS="$ERRORS\n$file:\n$file_errors"
fi
done
if [ -n "$ERRORS" ]; then
echo -e "拼写检查失败:$ERRORS"
echo ""
echo "提示: 将正确的词添加到 .hunspell/project.dic"
echo "或使用 git commit --no-verify 跳过检查"
exit 1
fi
echo "拼写检查通过 ✓"
exit 0
6.9 业务场景案例
6.9.1 技术文档团队
# 技术文档团队的词典管理策略
1. 公司级词典 (.hunspell/company.dic)
- 公司名称、产品名称、品牌名
- 公司内部术语
- 由文档团队负责人维护
2. 产品级词典 (.hunspell/product.dic)
- 产品特有的技术术语
- API 名称、端点名称
- 由各产品团队维护
3. 个人词典 (~/.hunspell_personal)
- 个人常用词汇
- 拼写习惯差异(如 color vs colour)
- 各自维护
4. 加载顺序:
hunspell -d en_US \
-p ~/.hunspell_personal \
-p .hunspell/company.dic \
-p .hunspell/product.dic \
document.md
6.9.2 多语言文档
# 多语言文档的词典管理
# 文档中混合中英文技术内容
# 方案 1: 分段检查
# 英文段落用英文词典
grep -P '[a-zA-Z]{3,}' document.md | hunspell -d en_US -l
# 方案 2: 使用排除列表
# 提取所有英文单词,排除已知正确词
cat document.md | \
grep -oP '\b[a-zA-Z]{3,}\b' | \
sort -u | \
hunspell -d en_US -p .hunspell/mixed.dic -l
6.9.3 学术论文
# 学术论文词典
# 学科术语
neural
backpropagation
epoch
tensor
gradient
lstm
transformer
attention
embedding
tokenization
tokenizer
# 引用格式
doi
arxiv
preprint
# 作者常见引用
Vaswani
Bahdanau
Cho
Bengio
Hinton
LeCun
6.10 本章小结
| 词典类型 | 用途 | 维护者 | 更新频率 |
|---|---|---|---|
| 系统词典 | 基础语言覆盖 | 发行版维护者 | 随系统更新 |
| 个人词典 | 个人习惯词汇 | 用户本人 | 按需 |
| 项目词典 | 项目专有术语 | 项目团队 | 随项目迭代 |
| 行业词典 | 专业领域词汇 | 行业组织 | 定期 |