故障排查
这里的每一条都是真实有人遇到过的故障模式。每条都列出了症状、成因和解决方法——大多数问题可归结为以下三种情况之一:短语不在项目中、SDK 从未初始化,或是占位符的写法与所处上下文不符。
没有任何内容被翻译——所有短语都保持源语言
当所有内容都显示源文本时,几乎可以肯定是 SDK 初始化失败。它被设计为静默失败(你的界面绝不能因此崩溃),所以唯一明显的提示在浏览器控制台——请先检查那里。常见原因按可能性从高到低依次如下:
- CORS。SDK 的第一个请求是
authorize-project;如果你的 API 没有将应用的来源加入允许列表,浏览器会拦截该请求,初始化就会在此处失败。请记住,来源是由 协议 + 主机 + 端口 精确构成的——localhost:5173和localhost:5174是不同的来源,而且当常用端口被占用时,开发服务器会自动更换端口。请将你实际用于开发的每个端口都加入允许列表。 - 凭据错误。缺少或输入错误的项目 ID / API 密钥会导致同一个授权调用失败。控制台会显示失败的请求和 URL。
- 您正在查看的是基础语言区域。在基础语言区域中,“译文”就是源文本——所以看不到任何明显变化。请切换到目标语言区域进行测试。
# The SDK's first call is an authorize-project request.
# If the browser blocks it, NOTHING translates. Check the console for:
Access to fetch at 'https://your-api/api/authorize-project/...'
from origin 'http://localhost:5173' has been blocked by CORS policy
# Fix: allowlist the app's EXACT origin (scheme + host + port) on the API.
CORS_ALLOWED_ORIGINS=http://localhost:5173,https://app.example.com 某个短语无法翻译(但其他短语可以)
代码中的短语就是查找键——短语即键。如果某个短语显示为源文本,而其相邻短语都已翻译,说明该键不在项目的词库中:
- 它从未被注册。注册是通过词条发现完成的,而且只有写入密钥才能执行。如果该短语是在应用使用只读密钥运行时产生的,那么它从未被发送到任何地方——请在开发环境中使用写入密钥运行一次该界面,或在控制台中添加该短语。
- 文本已更改。任何修改——一个词、标点,甚至空白字符——都会使其成为新的短语,尚无任何翻译。旧的翻译仍然保留在旧键之下。这对内容块的影响尤其明显:
<Translate>块是根据其标记进行令牌化的,因此编辑块的文本或结构会改变查找的内容,在新形式被注册并翻译之前,该块会悄悄回退为源文本。 - 机器翻译尚未跟上。刚注册的短语,其译文可能会稍有延迟;而不在项目目标列表中的语言区域则根本不会生成译文。请在仪表板中检查该短语:是否已注册?是否已翻译成此语言区域?
为什么缺失的短语不会抛出错误?
这是有意为之。在生产环境中,翻译系统必须降级为可读的源文本,而绝不能导致页面损坏——因此未注册的短语会直接显示源文本,SDK 也不会提示。反面是:使用只读密钥时,缺失的短语是静默的。不会记录日志,也不会注册,等你察觉时往往是用户看到了英文。避免这种情况的做法就是密钥拆分——开发时使用写入密钥,让界面渲染的每个短语都被发现,然后再以只读方式发布。
# Development — write key: new phrases are discovered,
# registered, and machine-translated as you build.
VITE_LANGSYS_API_KEY=write-key
# Production — read-only key: fetches the finished catalog only.
# Unregistered phrases silently render their source text.
VITE_LANGSYS_API_KEY=read-only-key 我的占位符被原样显示——"Hi %name%" 或 "Hi {name}"
- 在
t()调用中,占位符使用单花括号{name},且只有在传入参数时才会执行插值:t('Hello, {name}!', 'Greetings', { name })。 - 在 React、Vue 或 Svelte 的
<Translate>/<Phrase>标记中,请将占位符写成%name%。框架编译器会在 SDK 看到之前,就把裸写的{name}当作表达式处理掉;而%name%可以顺利通过编译,SDK 会在捕获时将其规范化还原为{name}。在原生 HTML 中直接使用{name}即可,因为那里没有会与之冲突的模板语法。 - 参数缺失?如果没有提供
params,模板会按原样渲染——你看到的正是字面上的%name%。
一开始正常,来回切换语言环境后就出问题
请更新 SDK。此方面已修复的实际缺陷:返回初始/SSR 语言环境时,之前的词库仍处于活动状态(已在 langsys-js-typescript 0.2.2 中修复);内容块的 Translate/Phrase 使用方在服务器预置的词库上可能会永久挂起(已在 0.2.3 中修复)。如果您看到过期语言的字符串或始终无法解析的内容块,请先对照基础 SDK 的更新日志检查已安装的版本,再去调试您自己的代码。
服务端:页面返回 500 错误,而不是平稳降级
PHP SDK 的客户端在没有凭据的情况下构造时会抛出异常——因此,如果 Laravel(或纯 PHP)应用的 LANGSYS_API_KEY 为空,就会在容器解析阶段失败,此时尚未执行任何翻译。请设置凭据(如果应用必须在没有凭据的情况下启动,则可以像演示应用那样绑定一个替代客户端)。凭据就绪后,运行时的 API 故障会降级处理:API 无法访问或拒绝请求时,会显示源文本并上报异常——绝不会导致页面崩溃。
服务端:首次请求挂起,之后一切都很快
PHP SDK 的 HTTP 超时时间为 30 秒,而从空闲状态唤醒的 API 实例在第一次调用时可能接近这一时限。该请求要么缓慢完成(并预热目录缓存——此后的所有请求都直接从缓存提供),要么超时并渲染源文本。如果在一段空闲期之后首次加载页面很慢,原因就在这里:这是 API 在预热,而不是您的集成出了问题。
服务端:我使用写入密钥进行了渲染,但短语没有出现在控制台中
发现的短语会在请求期间加入队列,并在响应发出之后发送——这就是 Laravel 的可终止中间件。请在页面加载完成后稍等片刻再刷新仪表盘,不要在加载过程中刷新。在长生命周期的 worker 运行时(Octane、FrankenPHP、RoadRunner)中,请确保使用 SDK 提供的集成——普通的 PHP shutdown 钩子在 worker 处理的各次请求之间不会触发。
服务端渲染:一个请求的翻译串到了另一个请求中
JS 基础 SDK 一次只保存一个语言区域的目录——它是模块级单例。如果 Node 服务器为并发请求渲染不同的语言区域,就会出现竞态。请按请求获取目录并据此初始化客户端,具体模式见 Next.js、Nuxt 和 SvelteKit 指南,或者每个进程只运行一个语言区域。
仍然无法解决?请在 SDK 的代码仓库中提交 issue——typescript、react、vue、svelte、php 或 laravel——并附上失败请求的控制台输出(或服务器日志)。也可以发邮件至 [email protected]。