给一个作品目录加上多语言,最直接的办法是复制页面,逐个翻译。
但如果作品还在不断增加,维护成本很快就会从“翻译多少段文字”变成“每次更新要修改多少份数据”。上游 README 改了,网站要不要再抄一次?某个语言的内容没拉到,其他语言是否也跟着失败?
我在 Astra Games 上采用的方式,是先把内容归属说清楚:作品记录由 awesome-gpt-6-astra 维护,网站负责读取和呈现,不在站点代码里另外维护一份作品清单。
一份权威来源,不代表只有一个文件
上游有不同语言的 README。网站用语言注册表记录语言代码、对应文件、界面名称、HTML 语言标记和文字方向。英文读 README.md,简体中文读 README.zh-CN.md,其他语言也有明确的对应关系。
这仍然需要翻译和维护不同语言的内容。“单一来源”解决的是所有权问题:作品信息只在上游维护,站点不再形成另一套需要手动对齐的副本。
页面自己的按钮、导航和提示使用本地字典,作品标题、介绍和链接来自对应语言的目录。这样既能让站点文案独立迭代,也能避免修改一个按钮时碰到作品数据。语言注册与字典
解析层也有来处:当前目录解析器沿用了上游网站的实现,再适配 Next.js 的打包方式。复用已有解析规则,比让两个项目逐渐长出不同的内容解释更稳妥。目录解析器
自动更新,需要一个明确的新鲜度约定
当前实现为每种语言建立独立的目录服务,保存缓存、ETag 和最近一次成功结果。成功读取后,结果在五分钟内复用;过期后的下一次访问才会触发重新获取。同一进程内的并发读取会复用正在进行的请求。目录服务
所以“自动同步”在这里指按访问刷新,有缓存窗口,并不是上游一提交,页面就立刻收到推送。
如果 API 的 CDN 缓存又从零开始计五分钟,已经在服务端放了四分钟的数据,可能再被当作新数据缓存五分钟。当前实现把服务端剩余的新鲜度时间交给 CDN,避免两层缓存重复增加有效期。
这让我更愿意把缓存当成产品约定来写:读者最晚大概何时能看到变化,失败时看到什么,以及页面能不能诚实地表达这些状态。
回退内容也有来源和寿命
上游暂时不可达时,清空整个目录并不是唯一选择。当前加载过程大致是:
text缓存仍有效 → 使用缓存 缓存已过期 → 请求对应语言的 README 成功 → 更新目录与检查时间 失败 → 最近成功结果 → 随包快照 → 不可用状态
这几个结果不能统一叫作“最新”。实现里会区分 fresh、stale、fallback 与 unavailable,失败后缩短重试间隔。当前随包快照只有英文和简体中文;其他语言在冷启动且上游不可达时,可能进入不可用状态,不能宣称十二种语言都有离线回退。
还有一个部署边界:进程内保存的最近成功结果不是跨实例共享的持久化存储。站点重启或新的运行实例启动后,能使用什么,仍由上游与打包快照决定。缓存与回退实现
把这些限制写清楚,才能判断未来是否真的需要共享缓存,而不是一开始就为小目录堆上复杂基础设施。
翻译之后,作品身份不能跟着丢掉
同一个游戏在不同语言里可能有不同标题,也就可能生成不同的详情页 slug。如果简单地把 URL 中的语言代码替换掉,不保证能找到对应页面。
当前实现会优先用试玩链接,再依次考虑源码或仓库链接,去关联不同语言里的同一件作品;只为确实找到的对应条目生成语言关联信息。某种语言尚未收录或加载失败,就跳过那一个对应项。跨语言作品关联
这种方法依赖链接稳定。如果将来出现同一个作品有多个语言专属试玩域名,显式的作品 ID 会是更可靠的方向。当前方法解决了已有目录的问题,也应该保留它的适用条件。
阿拉伯语还带来另一个提醒:语言不仅是一组字符串。项目把 rtl 写入语言定义,再传到页面的 dir 属性;翻译文件有内容,不等于页面阅读方向已经处理完。
十二种语言最后考验的是同一套维护方式:信息在哪儿修改,缓存何时更新,失败如何回退,以及不同页面如何继续表达同一件作品。把这些关系确定下来,再增加语言才比较从容。
本文依据 2026 年 9 月 11 日检查的代码,描述当前实现及其边界。
