从硬编码到多语言:AI辅助下Next.js应用国际化重构实战

从硬编码到多语言:AI辅助下Next.js应用国际化重构实战 1. 项目缘起一个“氛围编程”天才的觉醒我最近完成了一个项目一个为日本市场设计的AA制账单分摊应用叫FAMI-KAN。整个开发过程我采用了一种被称为“氛围编程”Vibe Coding的方式——说白了就是完全依赖像Claude、Gemini这类AI助手通过迭代对话来写代码。作为一个毫无专业编程背景的人这感觉就像突然获得了超能力。我用Next.js 15的App Router和Supabase功能开发快得飞起一个全栈应用就这么从零到一地跑起来了。那段时间我觉得自己简直是个天才。转折点发生在X平台原Twitter大力推广其多语言功能之后。一夜之间我的应用链接有可能被全球用户点击。这个可能性让我瞬间惊醒如果一个西班牙或法国的用户满怀期待地点开我的链接迎面而来的却是一屏日文那么整个用户体验在第一步就彻底崩塌了。流量来了我却亲手关上了门。于是我下定决心要把这个应用翻译成8种语言。“有AI在这还不简单”我当时天真地想。事实证明我大错特错。没有在项目第一天就引入国际化方案成了这个项目最大的败笔。即便拥有“氛围编程”这种超级外挂去迁移一个已经成型、代码纠缠在一起的单体代码库过程堪称地狱。这不是一次简单的翻译而是一次伤筋动骨的重构。下面我就来分享一下这次“幸存”下来的全过程聊聊AI在这个过程中意想不到的局限性以及最终我是如何让一个日文应用开口说八国语言的。2. 噩梦开端直面五千行组件的“硬编码”遗产问题的根源在于最初的天真和便捷。在“氛围编程”的高效幻觉下我追求的是功能的快速实现。所有的UI文本——按钮标签、提示信息、标题、表单占位符——都被我直接以日文字符串的形式硬编码在了各个React组件里。这在初期只有一种语言时看起来毫无问题甚至显得很“直白”。2.1 五千行“巨无霸”组件的诞生这种做法的恶果在我的核心页面——活动详情页page.tsx上体现得淋漓尽致。这个页面承载了应用最主要的功能逻辑成员管理、金额计算、费用明细、实时更新等等。在AI助手的帮助下我不断地往里添加状态、效果和条件渲染。就像滚雪球一样这个文件最终膨胀到了超过5000行代码。它不再是一个简单的组件而是一个盘根错节、状态复杂的庞然大物。里面散落着上百个日文字符串从“参加者を追加”到“精算済み”它们和业务逻辑紧紧耦合在一起。当我决定要国际化时第一个挑战就是这五千行代码。我的第一反应很“氛围编程”我把整个文件扔给AI并下达指令“找出这个5000行文件里所有的日文文本并把它们替换成useTranslations()钩子。”2.2 AI的首次“宕机”与我的策略转变AI的回应给了我当头一棒。它完全处理不了。首先它遇到了上下文窗口的限制无法一次性消化和理解如此庞大的代码结构。输出的代码是截断的、不完整的。更糟糕的是它开始“幻觉”Hallucinate即编造一些不存在的变量名或函数试图去匹配它认为应该存在的模式。结果就是替换后的代码根本无法通过编译错误百出。这次失败是一个关键教训。我意识到不能把AI当作一个可以处理任何规模任务的“魔法棒”。对于这种大规模、结构化的代码转换让AI直接操作源文件是低效且危险的。我需要改变策略不让AI当“编辑”而让AI当“工具匠”。我的新指令变成了“请为我编写一个Node.js脚本使用AST抽象语法树来解析这个React组件文件精准地找出所有JSX文本节点中的日文字符串并将它们安全地替换为t(key)的调用形式同时生成一个对应的键值对映射文件。”2.3 利用AST脚本进行外科手术式替换AI这次完美地完成了任务。它生成了一个脚本其工作原理大致如下解析使用像babel/parser这样的工具将我的TSX代码解析成一棵AST抽象语法树。这棵树精确地描述了代码的每一个结构变量声明、函数调用、JSX元素等。遍历与识别脚本遍历这棵AST树专门寻找类型为JSXText的节点并通过正则表达式或字符范围判断其内容是否包含日文或非ASCII字符。替换与生成对于每一个找到的日文文本脚本在AST中将其替换为一个函数调用如t(page.eventDetail.addParticipant)。同时它会把原始的日文文本和这个生成的键key记录到一个JSON对象中。输出最后脚本将修改后的AST重新生成为新的TSX代码文件并将收集到的键值对输出为一个JSON文件例如ja.json。我拿到脚本后并没有一次性处理整个五千行文件。而是将文件按功能模块拆分成几个较小的部分分批次运行脚本。每处理完一个模块我就手动检查生成的代码和JSON确保替换准确无误然后再进行编译测试。这种方式虽然不如“一键替换”听起来酷但安全、可控最终成功地将这个巨无霸组件中的硬编码文本全部剥离了出来。实操心得面对大型代码库的批量修改让AI编写自动化脚本如AST操作脚本、正则表达式处理脚本远比让它直接修改代码更可靠。这相当于给了你一把精准的手术刀而不是一把可能误伤的大锤。3. 框架之困在Next.js 15的“知识断层”中挣扎解决了代码中的文本替换问题只是万里长征第一步。接下来我需要为我的Next.js 15项目选择一个合适的国际化库并按照它的方式搭建架构。我选择了next-intl因为它对App Router的支持最为成熟和友好。然而在这里“氛围编程”遇到了真正的硬骨头AI模型的知识截止日期。3.1 “知识截止”陷阱与编译器的怒吼我像往常一样向AI描述需求“请使用next-intl为我的Next.js 15 App Router项目配置国际化包含中间件和基于[locale]的动态路由。” AI流畅地给出了代码包括middleware.ts、i18n.ts配置以及在app/[locale]/layout.tsx中包装NextIntlClientProvider。看起来一切正常。但当我运行tsc --noEmitTypeScript编译器检查时控制台瞬间被红色的错误信息淹没。核心错误指向动态路由文件app/[locale]/page.tsx中的params对象。AI生成的代码是这样的export default function Home({ params }: { params: { locale: string } }) { // ... 使用 params.locale }问题在于Next.js 15 引入了一项重大变更在App Router中params以及searchParams现在是一个Promise。这是为了支持React的并发特性如Suspense和更高效的流式渲染。正确的类型应该是export default async function Home({ params }: { params: Promise{ locale: string } }) { const { locale } await params; // 需要 await 解析 // ... 使用 locale }或者使用React的usehook实验性import { use } from react; export default function Home({ params }: { params: Promise{ locale: string } }) { const { locale } use(params); // ... 使用 locale }而我的AI助手其训练数据截止到2023年或2024年初它“学到的”还是Next.js 14甚至13的写法。它无法理解这个新的、基于Promise的API。3.2 与AI的“编译器驱动调试”拉锯战作为一个非专业程序员直接阅读Next.js官方文档去理解这个变更并修正所有地方对我来说门槛很高。我的策略变得简单而粗暴“编译器驱动调试”。我将tsc抛出的一长串错误信息全部复制。粘贴给AI并质问“为什么这些代码有类型错误请根据错误信息修正它们。”AI会根据错误信息调整代码可能修正了一部分但可能又因为理解偏差引入了新的错误。我再次复制新的错误信息重复步骤2。这个过程就像教一个聪明但信息过时的学生。我需要用编译器老师的即时反馈不断纠正AI学生的过时认知。通过这种“暴力”的迭代方式最终迫使AI的上下文里充满了正确的、基于Next.js 15的代码模式从而为后续的代码生成提供了正确的范本。注意事项使用AI进行现代前端开发时务必对框架的最新重大变更Major Changes保持警惕。Next.js、React等生态更新频繁。在向AI提问时明确加上“针对Next.js 15 App Router”这样的限定词并在遇到编译错误时优先考虑是否是AI的知识滞后导致。将官方博客的更新日志作为重要参考。4. 架构设计在拥抱全球与守护本土之间平衡技术问题解决后更宏观的架构挑战摆在了面前。我的应用已经在日本市场运行了一段时间积累了一些自然流量和宝贵的SEO权重。如果为了国际化粗暴地将根路径/重定向到某种语言选择页或者默认变成英文那么我之前所有被搜索引擎索引的日文页面链接都将失效这无异于一场SEO灾难。4.1 核心策略保留根路径的SEO资产我的核心诉求非常明确必须保持根域名/下的所有现有日文链接完全不变且可访问。新的语言如英语、西班牙语应该作为子目录添加而不是取代日文。 我向AI下达了清晰的架构指令 “配置next-intl中间件实现以下路由规则当用户访问根路径/或任何/xxx路径时如果未检测到语言环境则默认视为日语ja但不在URL中添加/ja/前缀。即/event/123直接显示日文内容。当用户访问/en/event/123时显示英文内容。当用户访问/es/event/123时显示西班牙语内容。已有的日文页面URL结构必须保持不变。”4.2 利用中间件实现路径重写next-intl的中间件完美支持了这一需求。关键的配置逻辑如下// middleware.ts import createMiddleware from next-intl/middleware; export default createMiddleware({ // 定义支持的语言 locales: [ja, en, es, fr, de, hi, pt, zh], // 默认语言日语 defaultLocale: ja, // 关键设置 localePrefix 为 as-needed // 这意味着对于默认语言jaURL中不会显示前缀 localePrefix: as-needed, // 可以配置域名映射未来支持不同国家顶级域名 // domains: [...] }); export const config { matcher: [/((?!api|_next|_vercel|.*\\..*).*)] };通过将localePrefix设置为as-needed中间件会智能地处理路由访问/或/event/123- 被内部识别为ja区域但URL不变内容为日文。访问/en或/en/event/123- 识别为en区域显示英文内容。访问/es- 识别为es区域显示西班牙语内容。这样我既为全球用户打开了大门通过/en/,/es/又牢牢守住了日本市场的本土入口和SEO资产根路径/。4.3 动态站点地图与hreflang标签为了保护并扩展SEO仅有多语言路由还不够。我需要告诉搜索引擎同一个内容存在不同语言的版本它们之间的关系是什么。这就需要hreflang标签。手动为每个页面维护这些标签是不可能的我再次求助AI让它帮我生成一个动态的sitemap.ts文件。 这个脚本的核心逻辑是遍历所有可能的页面路径和配置的语言为每个页面组合生成对应的URL并输出正确的hreflang注解。例如对于event/123这个页面站点地图会告诉搜索引擎https://myapp.com/event/123对应ja日语版本。https://myapp.com/en/event/123对应en英语版本。https://myapp.com/es/event/123对应es西班牙语版本。 这有助于搜索引擎理解网站的多语言结构避免将不同语言页面判为重复内容并能将用户引导至最合适的语言版本。5. 超越字面翻译AI驱动的“文化本地化”实践文本替换和架构搭建解决的只是“能看”的问题。但要让用户感觉舒服、自然就必须超越简单的字面翻译进入“本地化”的层面。本地化要求考虑目标语言用户的文化背景、习惯用语和认知语境。在这里AI展现出了令人惊喜的另一面。5.1 从“翻译”到“文化适配”的思维转变我的应用有一个功能会为新用户生成一组演示数据帮助他们快速理解应用用法。在日文原版中演示事件是“田部家の沖縄旅行”田部家的冲绳之旅。冲绳是日本著名的度假胜地日本用户一看就有亲切感。 如果仅仅进行字面翻译英文用户会看到 “The Tabei Familys Trip to Okinawa”。这固然没错但对许多不熟悉日本地理的全球用户来说“Okinawa”可能只是一个陌生的地名无法立即唤起“家庭度假”的共鸣感。我最初给AI的指令是“将这些演示数据翻译成英文。”结果就是上面那种直白的翻译。我意识到这不够好于是改进了指令“请让这些演示数据对全球用户显得更自然、更亲切。” 我原本期待AI会把地点换成“东京”或“京都”这类全球认知度更高的日本城市。5.2 AI的“创造性”本地化方案AI的回应超出了我的预期。它没有简单地替换地名而是进行了一次彻底的“文化移植”西班牙语版本 “El Viaje de la Familia García a Cancún”加西亚家的坎昆之旅。坎昆是墨西哥著名的海滨度假胜地在西班牙语世界家喻户晓。法语版本 “Le Voyage de la Famille Martin à Nice”马丁一家的尼斯之旅。尼斯是法国蔚蓝海岸的明珠。印地语版本 “शर्मा परिवार की शिमला यात्रा”夏尔马一家的西姆拉之旅。西姆拉是印度受欢迎的避暑山城。AI完全跳出了我原有的“日本语境”为每个语言版本选择了一个在该文化中具有同等“家庭理想度假地”象征意义的地点并配上了该语言文化中常见的姓氏。这个举动起初让我觉得有点好笑——它完全“无视”了我的原始设定。但仔细一想这恰恰是最高级的本地化不是让用户来适应你的文化背景而是让你的产品去融入用户的文化背景。这种细节上的文化共情能极大地提升应用的亲和力和专业感。实操心得在利用AI进行翻译时不要只下达“翻译”指令。尝试使用“本地化”、“使它对[某语言]用户更自然”、“采用[某地区]用户熟悉的表达方式”等指令。你可以提供一些关键的文化触点如节日、常用名、标志性地点作为参考引导AI产出更具文化适应性的内容。6. 面向未来的优化让AI爬虫也“读懂”你的多语言应用在今天搜索引擎优化已经超越了传统的谷歌爬虫。我们进入了生成式引擎优化时代。用户不仅在用谷歌搜索更在直接向ChatGPT、Claude、Gemini提问“有没有好用的按比例分摊旅行费用的应用”如果你的应用只存在于传统的搜索引擎索引中你很可能会错过这波来自AI助手的流量。6.1 创建专属AI爬虫的入口文件为了让AI助手能更好地理解并推荐我的应用我决定为每个语言版本创建专门的AI爬虫入口文件。这不是一个标准的SEO文件而是一种针对大语言模型爬虫的优化策略。 我让AI帮我创建了两个文件/en/llms.txt一个精简版介绍包含应用的核心价值主张、主要功能、目标用户和调用语Call to Action。/en/llms-full.txt一个更详细的版本可能包含使用场景、技术架构、用户评价等更丰富的上下文信息。这些文件使用纯文本或Markdown格式语言清晰、结构分明方便AI爬虫摄取和理解。例如llms.txt的内容可能类似于# FAMI-KAN: Smart Group Expense Splitting FAMI-KAN is a web application designed to simplify splitting bills and shared expenses among groups, especially useful for travel, dinners, and shared households. ## Key Features - Split costs by custom ratios (not just equally). - Real-time updates and calculations. - Support for multiple currencies. - Simple, intuitive interface for non-tech users. ## Ideal For - Friends on a trip. - Families managing household expenses. - Roommates sharing utilities and rent. Try it now at: https://myapp.com/en6.2 GEO策略的价值当英语用户向AI助手提出相关问题时AI在组织答案时可能会检索网络信息。一份精心准备、语言匹配、信息结构清晰的llms.txt文件就像一份递给AI的“推荐说明书”大大增加了我的应用被AI提及和推荐的机会。这是一种面向未来的、前瞻性的流量获取策略。我将这一步骤整合到了国际化构建流程中。在构建脚本中除了生成各语言的UI翻译文件外也调用AI生成或人工审核润色这些LLM入口文件并确保它们被正确地放置在对应的语言目录下如/public/en/。7. 迁移实战拆解多语言重构的核心工作流经过前面的教训和策略规划我将整个迁移过程总结为一个可操作的工作流。如果你也面临类似的任务可以遵循以下步骤它会帮你节省大量时间避免很多坑。7.1 第一阶段评估与准备代码库扫描使用简单的脚本或工具如grep -r配合正则表达式全局搜索所有包含非ASCII字符如中文、日文、韩文、西文特殊字符的字符串。这能帮你快速估算工作量。选择技术栈根据你的框架确定国际化库。Next.js App Router首选next-intlPages Router可考虑next-i18next或react-i18next。记录下库的版本和对应框架版本的要求。建立字典结构在项目根目录创建如messages/的文件夹里面为每种语言建立JSON文件en.json,ja.json,es.json等。建议采用命名空间来组织例如messages/ ├── en/ │ ├── common.json │ ├── homePage.json │ └── eventDetail.json ├── ja/ │ ├── common.json │ ├── homePage.json │ └── eventDetail.json └── ...7.2 第二阶段核心迁移与替换AST工具辅助提取对于大型组件文件采用我之前提到的策略让AI编写或自己编写AST解析脚本批量、安全地将硬编码文本替换为翻译键如t(‘ns.key’)并同步提取原文到默认语言如ja.json字典中。组件改造在需要使用翻译的客户端组件中使用next-intl提供的useTranslations(‘namespace’)钩子。在服务端组件Server Components或布局、页面中使用getTranslations(‘namespace’)异步函数来获取翻译方法。确保所有动态路由的params和searchParams都按照Next.js 15的Promise规范正确处理。配置中间件与路由正确配置middleware.ts特别是localePrefix选项根据你的SEO策略决定是否对默认语言隐藏前缀。在app/[locale]/layout.tsx中设置NextIntlClientProvider并确保能正确加载当前语言的消息。7.3 第三阶段翻译、本地化与测试AI辅助翻译将提取出来的默认语言字典如ja.json提交给AI进行批量翻译。关键点提供上下文不要只给键值对。告诉AI这个键用在哪个页面、哪个UI组件例如“‘addMemberButton’是事件详情页面中一个蓝色按钮上的文字”。这能极大提升翻译的准确性。文化本地化审核对AI的翻译结果进行人工审核特别是品牌名、标语、文化特定内容如我之前提到的演示数据。检查是否有生硬的直译确保用语符合目标语言的文化习惯。全面测试功能测试切换语言确保所有UI文本都正确变化。路由测试测试根路径、带语言前缀的路径、语言切换器的行为是否符合预期。SEO测试检查生成的HTML中lang属性是否正确使用工具验证hreflang标签是否被正确添加到页面头部和站点地图中。性能测试确保语言包是按需加载的没有因为引入国际化而显著增加初始加载包体积。8. 常见问题与排查技巧实录在实际操作中你一定会遇到各种各样的问题。以下是我在迁移过程中遇到的一些典型问题及其解决方案希望能帮你提前避坑。8.1 编译与类型错误问题现象可能原因解决方案Type ‘Promise{ locale: string; }’ is not assignable to type ‘{ locale: string; }’.Next.js 15中params和searchParams的类型已变为Promise。AI可能生成了旧的类型定义。1. 确保页面或组件函数是async的。2. 使用await params或use(params)来解析。3. 更新函数签名({ params }: { params: Promise{ locale: string } })useTranslationshook 在服务端组件中报错useTranslations是客户端钩子不能在React服务端组件中使用。在服务端组件中使用从next-intl导入的getTranslations异步函数。例如const t await getTranslations(‘Namespace’);中间件导致无限重定向或404中间件的matcher配置可能拦截了不该拦截的路径如静态文件、API路由。检查middleware.ts中的matcher。标准的next-intl配置matcher: [‘/((?!api|_next|_vercel|.*\\..*).*)’]通常能排除API、Next.js内部资源和带扩展名的文件。8.2 运行时与显示问题问题现象可能原因解决方案页面显示翻译键如common.submit而非实际文本1. 翻译键在当前语言字典中不存在。2.NextIntlClientProvider未正确接收到消息。1. 检查键名是否拼写错误确保在所有语言字典中存在对应键。2. 检查layout.tsx中是否将messages属性正确传递给了NextIntlClientProvider。切换语言后页面内容部分未更新可能使用了React状态或Context来存储某些文本而这些状态未随语言切换而重置。确保所有依赖于语言的文本都通过t()函数获取。对于复杂状态可以考虑使用useEffect监听语言变化并更新状态或使用next-intl提供的useLocale和useRouter进行导航刷新。站点地图或hreflang标签生成错误动态站点地图脚本逻辑有误未能遍历所有语言和页面组合。1. 使用console.log调试站点地图生成函数检查输出的URL列表。2. 确保baseUrl配置正确并且语言前缀逻辑与中间件配置一致。3. 使用在线hreflang检查工具验证。8.3 AI协作与流程问题问题现象可能原因解决方案AI翻译质量不佳上下文缺失你只给了AI一个干巴巴的JSON键值对列表它缺乏使用场景信息。提供上下文包将翻译键所在的组件代码片段、UI截图、甚至产品原型图一并提供给AI。说明文本的用途是按钮、标题、错误提示还是帮助文本。AST替换脚本误删或误改代码脚本的正则表达式或AST遍历逻辑有缺陷匹配了不该匹配的文本如代码注释、变量名。1.先备份运行脚本前务必提交代码或创建备份分支。2.分块测试先在小型、简单的文件上测试脚本确认无误后再处理大型文件。3.代码审查使用Git diff工具仔细检查脚本修改后的代码。多语言文案管理后期混乱新增功能时只在默认语言字典中添加了键忘记同步到其他语言字典。建立流程规范1. 任何新增UI文本必须先在默认语言字典中定义键。2. 将更新其他语言字典作为提测PR前的必要检查项。可以考虑使用i18n管理平台如Lokalise, Transifex或编写简单的同步检查脚本。这次将FAMI-KAN从单一语言迁移到八种语言的经历虽然过程曲折但最终让我这个“氛围程序员”对现代前端国际化有了刻骨铭心的理解。最核心的教训无比清晰无论你的项目初期多么简单只要存在一丝一毫走向全球的可能性就必须在第一天就把国际化i18n的架子搭好。这不仅仅是添加一个库而是一种思维方式——将UI文本与组件逻辑分离。初期多花的一两个小时将来会为你省下数十甚至上百小时的痛苦重构时间。“氛围编程”或者说AI辅助开发它极大地降低了开发门槛赋予了个人开发者巨大的能力。但在与框架深度集成、处理复杂架构变迁时AI的知识滞后性和对上下文理解的局限性就会暴露出来。它更像一个能力超强但需要明确指引和即时反馈的实习生而不是全知全能的架构师。你需要用编译器的错误信息来训练它用清晰的架构图来指挥它。最后本地化远不止翻译。一个“Trip to Okinawa”和“Trip to Cancún”的差别背后是对用户文化背景的尊重。而像为AI爬虫准备llms.txt这样的举措则是在为下一个流量入口提前布局。技术实现是基础但产品和用户体验的思维才是让一个多语言应用真正焕发光彩的关键。现在当看到来自世界不同角落的用户顺畅地使用FAMI-KAN管理他们的共同开销时我知道那几千行代码的“地狱”之旅值了。