Omni AI Translator · 翻译加速服务(Relay)
加速节点是一个只缓存、不代理的译文缓存服务。插件在翻译前先向它查询缓存, 命中的直接展示;未命中的照常发往翻译接口,翻译完成后再把结果异步写回缓存。
它永远拿不到你的 API Key,也从不代理翻译请求。
项目方官方节点:https://translator.makabonka.com/api。
自 v0.5.0 起,插件设置中的「官方加速节点」即指向该地址;下文的自建配置与它一致,
可以直接照抄。
| 会做 | 不会做 |
|---|---|
| 按「原文 + 源语言 + 目标语言」缓存译文 | 不持有、不转发、不存储你的 API Key |
| 被查询时返回命中的译文 | 不代理翻译请求,不代替翻译接口 |
| 接受匿名写入(upsert 覆盖) | 不做用户身份认证——任何人都能读写 |
| 按真实客户端 IP 做基础限流 | 不记录原文与译文日志 |
远端部署推荐:从 GHCR 拉取与 release 同 tag 的预构建镜像,不在服务器编译:
curl -fsSL https://raw.githubusercontent.com/Aiden-FE/omni-ai-translator/master/docker-compose.yml -o docker-compose.yml
curl -fsSL https://raw.githubusercontent.com/Aiden-FE/omni-ai-translator/master/.env.example-compose -o .env
# 编辑 .env,锁定与已发布 Release 一致的镜像 tag(示例)
# RELAY_IMAGE=ghcr.io/aiden-fe/omni-relay:v0.5.1
docker compose pull
docker compose up -d
从 v0.5.0 起,GitHub Releases 页面可查看当前版本号;镜像同时提供完整 tag(v0.5.1)、
纯版本 tag(0.5.1)和正式版的 latest。
也可以克隆仓库并从源码构建:
git clone https://github.com/Aiden-FE/omni-ai-translator.git
cd omni-ai-translator
cp .env.example-compose .env
docker compose build relay
docker compose up -d
验证服务与缓存是否都就绪:
curl http://localhost:3000/healthz
# {"status":"ok","version":"0.1.0","redis":"ok"}
redis:"ok" 表示容器和缓存都正常。若返回 "redis":"down" 且 HTTP 503,
说明 relay 起来了但 Redis 没通——检查 docker compose logs redis。
本地开发或调试服务代码时,用 compose 启动真实 Redis,宿主运行 relay:
pnpm install
docker compose -f docker-compose.dev.yml up -d redis
REDIS_URL=redis://127.0.0.1:16379 pnpm --filter @omni/relay dev
Redis 默认映射到宿主 16379,避免与本机 Redis 冲突。停止并清理数据:
docker compose -f docker-compose.dev.yml down --volumes。
不配TRUST_PROXY会导致限流误伤。 加速节点按真实客户端 IP 计桶限流。如果你把服务放在 nginx / Cloudflare / Caddy 等反向代理后面,而没有开启TRUST_PROXY,所有请求都会被算作来自127.0.0.1,于是所有用户共用一个限流桶——用得多的用户会把其他人一起限流掉。
确认自己确实位于反向代理之后(查看访问日志里的来源地址不是客户端真实 IP),然后:
# .env
TRUST_PROXY=true
docker compose up -d
反过来,如果端口直接暴露给客户端、前面没有任何代理,就保持 TRUST_PROXY=false——
此时客户端可以伪造 X-Forwarded-For 绕过限流。
nginx 参考配置:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT | 3000 | 监听端口 |
REDIS_URL | redis://localhost:6379 | Redis 连接串 |
CACHE_TTL_SECONDS | 2592000(30 天) | 缓存条目过期时间 |
TRUST_PROXY | false | 是否信任反向代理的 X-Forwarded-For;仅在确实位于代理之后时开启 |
RATE_LIMIT_WINDOW_SECONDS | 60 | 限流窗口(秒) |
RATE_LIMIT_LOOKUP | 120 | 每窗口每 IP 的查询次数上限(宽松) |
RATE_LIMIT_COMMIT | 30 | 每窗口每 IP 的写入次数上限(严格——写才是成本大头) |
限流阈值是防误伤的默认值,不是安全边界。若节点面向公网开放,建议在边缘层(Cloudflare 等) 再加一层规则。
https://accel.example.com(不带结尾斜杠)。redis:"ok"。加速默认关闭。启用是显式选择:原文会发往你所选的节点。
加速节点能看到你翻译的每一段原文,并且可以返回任意内容作为「译文」。 服务没有任何机制能验证返回的译文是否被篡改——这是开放缓存模型的固有代价。
三个端点,路径带 /v1 前缀;契约只增字段不删字段,可向后兼容。
curl -X POST http://localhost:3000/v1/cache/lookup \
-H 'content-type: application/json' \
-d '{
"items": [
{ "id": "a", "text": "Hello World", "targetLang": "zh" }
]
}'
响应只包含命中的条目——未出现在 hits 里的 id 即未命中:
{ "hits": [ { "id": "a", "translatedText": "你好,世界" } ] }
curl -X POST http://localhost:3000/v1/cache/commit \
-H 'content-type: application/json' \
-d '{
"items": [
{ "id": "a", "text": "Hello World", "targetLang": "zh",
"translatedText": "你好,世界" }
]
}'
# { "accepted": 1 }
写入是 upsert:同一条目再次提交会覆盖旧译文并重置 TTL。
{ "status": "ok", "version": "0.1.0", "redis": "ok" }
| 项 | 行为 |
|---|---|
| 单次请求条数 | 最多 200 条,超出部分被忽略 |
| 单条原文长度 | 最多 5000 字符 |
| 非法单条 | 跳过该条,同批合法条目照常处理(不整批失败) |
| 错误响应 | { "error": "..." },配合 4xx / 5xx 状态码 |
缓存身份 = 规范化原文(NFKC + 去首尾空白 + 折叠连续空白)+ 源语言 + 目标语言, 再取 sha256。规范化与哈希都在服务端完成,客户端不参与。
| 现象 | 原因与处理 |
|---|---|
healthz 返回 redis:"down" | Redis 未就绪或连不上。检查 docker compose logs redis 与 REDIS_URL。 |
| 多人同时被限流(429) | 大概率是没设 TRUST_PROXY=true,所有流量共用一个桶。回看第 3 节。 |
| 命中率很低 | 缓存按「原文 + 语言」精确匹配。页面内容差异大、或目标语言频繁切换时命中率天然低。 |
| 译文不更新 | 该条目已被缓存。用插件的重试功能覆盖,或等待 TTL(默认 30 天)过期。 |
| 内存持续增长 | 确认 Redis 使用了 maxmemory + allkeys-lru(compose 已默认配置)。 |
查看日志:
docker compose logs -f relay
服务只记录条数与命中数,不记录原文和译文。