<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="zh_CN">
  <title>老木哟的小站</title>
  <subtitle>OldWood&apos;s Space</subtitle>
  <link href="https://oldwood-u.fun/" rel="alternate" type="text/html"/>
  <link href="https://oldwood-u.fun/atom.xml" rel="self" type="application/atom+xml"/>
  <id>https://oldwood-u.fun/</id>
  <updated>2026-09-02T11:12:20.986Z</updated>
  <entry>
    <title>用 AI 给 Unreal Engine 做本地化：AILocalization 插件介绍</title>
    <link href="https://oldwood-u.fun/posts/ai-localization-plugin/" rel="alternate" type="text/html"/>
    <id>https://oldwood-u.fun/posts/ai-localization-plugin/</id>
    <published>2026-08-18T00:00:00.000Z</published>
    <updated>2026-08-18T00:00:00.000Z</updated>
    <summary>从拖拽 PO 文件的翻译脚本，到直接集成进 Unreal Editor 的 AI 本地化工作流。</summary>
    <content type="html"><![CDATA[<p>最近把自己写的一个 Unreal Engine 插件开源了：</p>
<a id="GCn7um7r-card" class="card-github fetch-waiting no-styling" href="https://github.com/OldWood-u/AILocalization" target="_blank"><div class="gc-titlebar"><div class="gc-titlebar-left"><div class="gc-owner"><div id="GCn7um7r-avatar" class="gc-avatar"></div><div class="gc-user">OldWood-u</div></div><div class="gc-divider">/</div><div class="gc-repo">AILocalization</div></div><div class="github-logo"></div></div><div id="GCn7um7r-description" class="gc-description">Waiting for api.github.com...</div><div class="gc-infobar"><div id="GCn7um7r-stars" class="gc-stars">00K</div><div id="GCn7um7r-forks" class="gc-forks">0K</div><div id="GCn7um7r-license" class="gc-license">0K</div><span id="GCn7um7r-language" class="gc-language">Waiting...</span></div></a>
<p>它叫 <strong>AI Localization</strong>，是一个只在 Unreal Editor 中运行的 AI 辅助本地化插件。简单来说，就是把原本需要导出、翻译、再导回去的本地化流程，尽量收进编辑器里完成。</p>
<p>这个插件最初是为我的游戏 <a href="https://store.steampowered.com/app/4786590/_/" data-content-link-kind="external" target="_blank" rel="nofollow noopener noreferrer">Lover Completion Plan</a> 做的。它不是什么特别成熟的大型工具，更像是一个从真实需求里长出来的小项目：够我自己用，也顺手把它放出来了。</p>
<section><h2 id="事情是怎么开始的">事情是怎么开始的<a class="anchor" href="https://oldwood-u.fun/posts/ai-localization-plugin/#%E4%BA%8B%E6%83%85%E6%98%AF%E6%80%8E%E4%B9%88%E5%BC%80%E5%A7%8B%E7%9A%84"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h2><p>在写这个插件之前，我已经有一套能工作的翻译流水线。</p><p>先从 Unreal 导出某种目标语言的 <code>.po</code> 文件，再把文件拖到一个 Python 脚本上。脚本会调用 AI，把内容翻译成文件里指定的语言，并输出一份翻译完成的 <code>.po</code> 文件。</p><p>这套方式本身没有问题，甚至已经帮我省下不少时间。但我的游戏需要支持十种语言，如果每一种语言都要单独导出、拖文件、等待翻译、检查结果、再导回项目，重复操作还是很多。语言一多，真正浪费时间的往往不是 AI 翻译本身，而是这些围绕文件来回处理的步骤。</p><p>后来我看到了 <a href="https://solutions.georgy.dev/ai-localization-automator" data-content-link-kind="external" target="_blank" rel="nofollow noopener noreferrer">AI Localization Automator</a>。它的思路和完成度都不错，我也很喜欢这种把翻译流程直接放进编辑器的方式。不过对我这个项目来说，它的价格有点高，于是我就开始想：既然需求已经很明确，能不能自己做一个只满足当前项目需求的版本？</p><p>我先把需要解决的流程和大致规划写下来，再让 AI 协助实现。最后得到的就是现在这个插件。它不算精致，但能够配置自己的 API 供应商，把游戏本地化内容整体交给 AI 处理，已经把我最在意的效率问题解决掉了。</p></section>
<section><h2 id="插件现在能做什么">插件现在能做什么<a class="anchor" href="https://oldwood-u.fun/posts/ai-localization-plugin/#%E6%8F%92%E4%BB%B6%E7%8E%B0%E5%9C%A8%E8%83%BD%E5%81%9A%E4%BB%80%E4%B9%88"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h2><p>AI Localization 会接入 Unreal Editor 的本地化工具：</p><ul>
<li>可以从 <strong>Tools &gt; Localization &gt; AI Localization Manager</strong> 打开管理器。</li>
<li>也会出现在 Localization Dashboard 中，并为本地化 Target 和 Target Set 加上 <strong>AI Translate</strong> 按钮。</li>
<li>可以选择本地化 Target、一个或多个目标语言，以及适合不同文本类型的预设。</li>
<li>支持 OpenAI-compatible 的 <code>/chat/completions</code> 接口，因此可以配置自己的 endpoint、模型和 API key。</li>
<li>可以控制批大小、并发数、重试次数、超时、最大输出 token、temperature、仅翻译缺失项和是否覆盖已有译文。</li>
<li>翻译结束后，成功结果会先暂存，再通过 <strong>Apply &amp; Compile</strong> 走 Unreal 原本的导入和编译流程。</li>
</ul><p>基本使用流程就是：选择 Target 和目标语言，配置 Provider，点击 <strong>Start Translation</strong>，检查表格中的结果，最后再 <strong>Apply &amp; Compile</strong>。这样不需要把 <code>.po</code> 文件反复拖到外部脚本上，翻译、检查和应用都留在同一个工作流里。</p></section>
<section><h2 id="我是怎么给-ai-补上下文的">我是怎么给 AI 补上下文的<a class="anchor" href="https://oldwood-u.fun/posts/ai-localization-plugin/#%E6%88%91%E6%98%AF%E6%80%8E%E4%B9%88%E7%BB%99-ai-%E8%A1%A5%E4%B8%8A%E4%B8%8B%E6%96%87%E7%9A%84"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h2><p>游戏文本不是单独一句一句存在的。尤其是剧情、对话和角色台词，如果模型只看到一条孤立文本，翻译很容易丢掉说话人、场景和前后顺序。</p><p>所以这个插件里用了一个比较简单的上下文策略：</p><ol>
<li><strong>场景区分</strong>：先用 <code>namespace</code> 区分文本所属的场景或上下文。</li>
<li><strong>顺序整理</strong>：再按 <code>key</code> 对说话人和文本顺序排序。</li>
</ol><p>这样一来，同一场景里的内容会尽量一起交给模型处理，模型能看到更连续的上下文。它当然不是万能的剧情理解系统，但对我项目中的对话翻译来说已经比完全随机、逐条发送稳定不少。</p><p>插件还会保护一些不能被翻坏的结构：Unreal 占位符、printf 格式符、富文本标签、首尾空白和多行换行数量都会在接受译文前校验。校验失败的条目会保留给人检查，不会被自动导入。多行文本也会使用受保护的换行 token，减少模型擅自重排换行带来的问题。</p></section>
<section><h2 id="成本和取舍">成本和取舍<a class="anchor" href="https://oldwood-u.fun/posts/ai-localization-plugin/#%E6%88%90%E6%9C%AC%E5%92%8C%E5%8F%96%E8%88%8D"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h2><p>整个插件从规划到可用，AI 开销大概只花了四十多元，不到五十元。对比当时我看到的商业方案，自己做这个小工具的成本还是挺有意思的。</p><p>当然，这不是说它能直接替代成熟产品。商业插件有更完整的支持、长期维护和更广泛的兼容性；我这个版本的目标只是把自己的实际翻译流程跑顺。它目前只支持 OpenAI-compatible chat-completions 接口，不支持 streaming response、模型发现和费用统计；它也是 Editor-only 插件，不负责打包后游戏运行时的实时翻译。</p><p>API key 也不会写进项目源码。Windows 下会使用 Windows Credential Manager 保存凭据，其他平台只在当前 Editor 会话内保存；预设文件只记录是否存在凭据，不保存 key 本身。</p></section>
<section><h2 id="开源出来">开源出来<a class="anchor" href="https://oldwood-u.fun/posts/ai-localization-plugin/#%E5%BC%80%E6%BA%90%E5%87%BA%E6%9D%A5"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h2><p>现在这个项目已经按照 MIT License 开源：</p><ul>
<li>GitHub 仓库：<a href="https://github.com/OldWood-u/AILocalization" data-content-link-kind="external" target="_blank" rel="nofollow noopener noreferrer">OldWood-u/AILocalization</a></li>
<li>安装方式：将插件放到项目的 <code>Plugins/AILocalization</code> 目录，启用后从 Unreal Editor 的 Tools 菜单打开管理器。</li>
</ul><p>说到底，它就是我为自己的游戏随手做出来的一个小工具，估计大部分时间也只会在自己的项目里使用。不过目前该有的流程基本都能跑，实际用起来也还不错。</p><p>如果它刚好能帮到同样需要给 Unreal 项目做多语言本地化的人，欢迎直接使用、修改或 fork；Issue、Pull Request 和改进建议也都欢迎。</p></section>]]></content>
    <author><name>老木哟 (OldWood_u)</name></author>
    <category term="项目"/>
  </entry>
  <entry>
    <title>如何部署一个属于自己的网站</title>
    <link href="https://oldwood-u.fun/posts/hello-world/" rel="alternate" type="text/html"/>
    <id>https://oldwood-u.fun/posts/hello-world/</id>
    <published>2026-08-09T00:00:00.000Z</published>
    <updated>2026-09-02T11:12:20.986Z</updated>
    <summary>小站的第一篇文章</summary>
    <content type="html"><![CDATA[<p>这是 <strong>OldWood 的小站</strong> 的第一篇文章 🎉</p>
<p>本站基于 <a href="https://astro.build/" data-content-link-kind="external" target="_blank" rel="nofollow noopener noreferrer">Astro</a> 和 <a href="https://github.com/LyraVoid/Mizuki" data-content-link-kind="external" target="_blank" rel="nofollow noopener noreferrer">Mizuki</a> 主题构建，静态站点部署在 Cloudflare Pages 上。</p>
<section><h2 id="我是怎么写作的">我是怎么写作的？<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#%E6%88%91%E6%98%AF%E6%80%8E%E4%B9%88%E5%86%99%E4%BD%9C%E7%9A%84"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h2><p>我可以在网站后台添加日记或文章。后台保存并不是直接改服务器上的数据库，而是由 Cloudflare Pages Functions 校验请求后，调用 GitHub Contents API，把内容提交到 <code>OldWood-u/oldwood-blog</code> 的 <code>main</code> 分支。之后 GitHub Actions 会异步执行检查、翻译和部署，所以保存完成到网站完全更新之间可能会有一小段时间。</p><p>公开文章会交给 DeepSeek 生成英语、日语和韩语的翻译 sidecar。翻译工作流会记录源内容哈希，检查受保护的链接、日期、Markdown 和其它结构化内容；如果中文源文件发生变化，旧译文会被识别为过期，而不是悄悄当成最新译文使用。它不是保存请求里同步完成的翻译，也不能保证每一次提交都立刻完成部署。</p><p><del>已经变成全流程流水线啦（骄傲.jpg）</del></p></section>
<section><h2 id="网站制作">网站制作<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#%E7%BD%91%E7%AB%99%E5%88%B6%E4%BD%9C"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h2><p>由于我主要做游戏开发（程序），所以对于网页搭建并不是那么熟悉。</p><p>但我又想做一个好看的网站，该怎么办呢？</p><p><del>睡大觉啦，梦里什么都有</del></p><p>于是我使用了现成的博客模板，就是 <a href="https://astro.build/" data-content-link-kind="external" target="_blank" rel="nofollow noopener noreferrer">Astro</a> + <a href="https://github.com/LyraVoid/Mizuki" data-content-link-kind="external" target="_blank" rel="nofollow noopener noreferrer">Mizuki</a> 啦～</p><p>这里的“后端”需要稍微解释准确一点：Cloudflare Pages 负责静态页面的构建和发布，Pages Functions 负责登录、内容读写、媒体上传等 API；GitHub 仓库则是文章和结构化内容的持久化来源。域名是在阿里云购买的，一个属于我自己的网站就这样慢慢搭起来了。</p><blockquote>
<p>我从高中，不对，初中就想做个自己的网站了，居然到现在才正式做出来（哭.jpg）</p>
</blockquote><p>但很显然，如果只是简单地使用模板、改成自己的信息，那就太 low 啦。换言之又是一个换皮网站罢了。</p><p>所以我需要定制化修改。<del>其实就是改下布局，把不需要的功能删除/隐藏，新增些自己认为重要的功能</del></p><section><h3 id="修改配置">修改配置<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#%E4%BF%AE%E6%94%B9%E9%85%8D%E7%BD%AE"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h3><p>**这部分才是最折磨的。**我一开始还想自己手搓来着，但很快就放弃了，不是说太难，而是太累了。整个网站的内容比我想的要多，光是改 Config 文件就要费好大劲儿，更别提后面还要新增的内容了。</p><p>但现在是什么时代？**AI 时代！**所以我使用 <em><strong>Opencode + GPT5.6 Sol</strong></em> 帮我梳理整个网站，由 AI 帮我填入，我只需要提供信息就行了。整个流程还是相对顺利的。</p></section></section>
<section><h2 id="网站后端是怎么做的">网站后端是怎么做的？<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#%E7%BD%91%E7%AB%99%E5%90%8E%E7%AB%AF%E6%98%AF%E6%80%8E%E4%B9%88%E5%81%9A%E7%9A%84"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h2><section><h3 id="文章和结构化内容分开存储">文章和结构化内容分开存储<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#%E6%96%87%E7%AB%A0%E5%92%8C%E7%BB%93%E6%9E%84%E5%8C%96%E5%86%85%E5%AE%B9%E5%88%86%E5%BC%80%E5%AD%98%E5%82%A8"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h3><p>长篇文章放在 <code>src/content/posts/</code> 中，每篇文章是一个 Markdown 或 MDX 文件。Astro 的内容集合会读取 frontmatter，并校验标题、发布日期、分类、标签、草稿状态等字段；文章详情页再把正文交给 Astro 的 Markdown 管线渲染。</p><p>日记、项目、首页横幅和其它频繁编辑的结构化内容则集中在 <code>src/data/admin-content.json</code>。这样做的好处是：文章正文仍然适合用 Markdown 写作，日记和项目则可以在后台用卡片、日期和图片控件管理，不需要把所有内容都塞进一个格式里。</p></section><section><h3 id="cloudflare-pages-和-pages-functions">Cloudflare Pages 和 Pages Functions<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#cloudflare-pages-%E5%92%8C-pages-functions"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h3><p>Cloudflare Pages 负责把仓库里的 Astro 项目构建成静态网站并发布。Pages Functions 是同一个部署环境里的 API 层，后台登录、会话校验、文章读写、结构化内容保存和图片上传都通过它完成。</p><p>上传图片时，API 会检查文件大小、允许的 MIME 类型和文件签名，只把安全的图片写入 <code>public/images/uploads/</code>。文章正文中保存的是最终站内路径，而不是临时的浏览器对象地址。正文预览会在必要时使用本地临时预览，这样刚上传但还没完成新一轮部署的图片也能立即看到。</p></section><section><h3 id="github-apisha-和更新时间">GitHub API、SHA 和更新时间<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#github-apisha-%E5%92%8C%E6%9B%B4%E6%96%B0%E6%97%B6%E9%97%B4"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h3><p>管理后台使用 GitHub Contents API 读写 <code>main</code> 分支。更新文章时，客户端会带上它读取到的文件 SHA，服务端在写入前再次比较远端 SHA。如果另一个后台窗口已经先改过同一篇文章，SHA 就会不一致，服务端会拒绝这次写入，避免我的修改覆盖别人的修改。</p><p>每次编辑已有文章时，服务端会生成新的 <code>updated</code> ISO 时间并写入 frontmatter；新建文章则不强行写入这个字段。文章页面底部因此可以显示真正的最后修改时间，而不是把发布日期伪装成更新时间。</p></section><section><h3 id="翻译和部署工作流">翻译和部署工作流<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#%E7%BF%BB%E8%AF%91%E5%92%8C%E9%83%A8%E7%BD%B2%E5%B7%A5%E4%BD%9C%E6%B5%81"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h3><p>文章保存到 GitHub 后，GitHub Actions 会根据中文源文件运行翻译脚本。翻译结果保存在 <code>translations/content/en</code>、<code>ja</code> 和 <code>ko</code> 目录的 sidecar 文件中，并带有 <code>sourceHash</code>、模型和生成状态等信息。草稿、加密文章和密码文章不会发送到翻译服务。</p><p>翻译工作流完成后会提交翻译 sidecar，部署工作流再构建 Astro 项目并通过 Wrangler 发布到 Cloudflare Pages。由于这些步骤是异步的，后台提示“已提交”代表 GitHub 写入成功，不代表所有语言的页面已经在同一秒完成更新。</p></section><section><h3 id="安全边界">安全边界<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#%E5%AE%89%E5%85%A8%E8%BE%B9%E7%95%8C"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h3><p>后台 API 需要有效的管理会话，写请求还要通过严格的同源检查。GitHub token、会话签名密钥和 DeepSeek API key 只放在运行环境或 GitHub Secrets 中，不会进入文章、前端代码或日志。Markdown 预览会经过安全清洗，过滤脚本、事件处理器和危险 URL；文件路径、图片类型、请求大小和 GitHub 文件版本也都会在服务端再次校验。</p><p>这套方案没有传统意义上的数据库：GitHub 既保存内容源文件，也提供版本历史；Cloudflare Pages 提供静态站点，Pages Functions 提供必要的 API。它不一定是最复杂的架构，但对一个个人站点来说，修改可追踪、部署自动化，而且我可以在后台继续写作，已经足够实用了。</p></section><section><h3 id="前端修改">前端修改<a class="anchor" href="https://oldwood-u.fun/posts/hello-world/#%E5%89%8D%E7%AB%AF%E4%BF%AE%E6%94%B9"><span class="anchor-icon" data-pagefind-ignore>#</span></a></h3><p>首先先说明，Mizuki 兼具丰富功能与优雅设计，本人才疏学浅，如果有任何不规范或错误的地方那都是我的错！可以直接提出！<em>（我也不知道为什么要写这个，反正我就是写了）</em></p><p>整个前端我基本都是 Vibe Coding，因为效率真的很高。而且 Mizuki 包含很多现成的组件可以直接使用，这样也不用担心 AI 会自我发挥，做出些不符合主题的东西了。</p><p>基本总结为以下改动：</p><ul>
<li>修改顶栏标题顺序和内容</li>
<li>除了深色、浅色模式，还加了个跟随系统</li>
<li>添加语言更改选项，可以切换中、英、日、韩四语</li>
<li>日记界面添加日历，可以点进二级菜单</li>
<li>我的项目添加二级菜单</li>
<li>主页添加“精选项目”和“日记”的分类</li>
<li>为后台文章、日记和项目补充 Markdown 编辑、图片媒体库和草稿保护</li>
<li>将媒体上传、SHA 冲突、更新时间和翻译流程接到同一套发布链路里</li>
</ul><p>后面还会继续记录新的功能和踩坑。先这样，下一篇见！</p></section></section>]]></content>
    <author><name>老木哟 (OldWood_u)</name></author>
    <category term="随笔"/>
  </entry>
</feed>