Nuxt Content + i18n终极集成方案：一套内容支持多语言的完整实现

h1Nuxt Content + i18n终极集成方案：一套内容支持多语言的完整实现blockquotep适用版本：Nuxt 4、Nuxt Content v3、 Nuxt I18n v10br 如果你使用其他版本，核心思路仍可参考，但具体 API 可能需要调整。h2概述p在构建多语言技术文档站点时，我们常面临一个矛盾：strong内容维护成本与strong多语言用户体验如何兼得？官方方案往往建议为每种语言维护独立的文档集合，但这对于独立开发者或小团队来说负担沉重。p本文介绍一种创新方案：strong只维护一套核心文档（如英文或中文），利用 code@nuxtjs/i18n 的路由前缀功能，为用户提供完整的多语言界面体验。访问 code/en/article 与 code/zh_cn/article 将显示相同的文档内容，但界面语言、导航菜单等将根据URL前缀自动切换。h2核心优势ullistrong维护极简：只需维护单一语言的内容源listrong体验完整：用户仍能获得地址栏、导航、界面完全本地化的体验listrong技术解耦：Content 管内容，i18n 管路由与界面，各司其职listrong无缝扩展：未来可为特定文档添加翻译，无需改造架构hrh2问题背景：当 Content 遇上 i18npcode@nuxt/content 模块本身提供了基础的国际化支持，但其设计初衷是strong内容与语言强绑定：ulli每个语言独立的文件夹（code/content/en/, code/content/zh_cn/）li每个语言独立的内容集合配置li自动回退机制：当 code/es/article 不存在时，跳回默认语言p但这带来了两个实际问题：ollistrong内容同步压力：任何更新都需要在所有语言副本中重复listrong架构复杂：需要配置多个内容集合，查询时需要额外逻辑p而我们真正的需求往往是：blockquotep“我只有精力维护一套技术文档，但希望网站支持多语言界面。”hrh2解决方案：路径转换中间层p核心思路是建立一个strong路径转换层，在用户访问时动态处理 URL：precodespanspangraph LR spanspan A[用户访问
/ja/docs/nuxt-guide] --> B{i18n 模块}; spanspan B --> C[识别语言前缀 ja]; spanspan C --> D[移除前缀 /ja]; spanspan D --> E[Content 查询 /docs/nuxt-guide]; spanspan E --> F[返回统一内容]; spanspan F --> G[界面元素使用 ja 语言包]; spanspan G --> H[用户看到日文界面
英文内容]; olli实现细节：稳定查询路径p以下是基于 Vue 3 与 Nuxt 3 的完整实现示例：blockquotep路由层处理precodespanspan blockquotep内容层：由 @nuxt/content 处理（移除语言前缀）precodespanspan spanspanimportspan {span withLeadingSlashspan }span fromspan "spanufospan"span; spanspanconstspan {span locale span}span =span useI18nspan()span; spanspanconstspan route span=span useRoutespan()span; spanspan spanspan// 核心：移除语言前缀，得到原始路径 spanspan// 例如：/en/docs/welcome -> /docs/welcome spanspanconstspan slug span=span computedspan(span()span =>span { spanspan constspan pathspan =span withLeadingSlashspan(spanStringspan(spanroutespan.spanparamsspan.spanslugspan ||span "span/span"span))span; spanspan // 移除语言前缀部分 spanspan returnspan pathspan.spanreplacespan(spannewspan RegExpspan(span`span^/(span${spanlocalespan.spanvaluespan}span)span`span)span,span ""span) span||span "span/span"span; spanspan}span)span; spanspan spanspan// 稳定查询：永远只查询 'docs' 这个集合 spanspanconstspan {span dataspan:span page span}span =span awaitspan useAsyncDataspan( spanspan routespan.spanpathspan, spanspan ()span =>span { spanspan returnspan queryCollectionspan(span"spandocsspan"span)span.spanpathspan(span`span/docsspan${spanslugspan.spanvaluespan}`span)span.spanfirstspan()span; spanspan // 注意：查询路径需要加上 '/docs' 前缀，以匹配 content/docs/ 下的文件 spanspan }, spanspan { spanspan // 设置 transform 确保数据一致性 spanspan transformspan:span (spandataspan)span =>span { spanspan ifspan (span!spandataspan) spanreturnspan null; spanspan returnspan dataspan; spanspan }, spanspan }, spanspan)span; spanspan h2📁 推荐的目录结构p保持您的单语言内容结构：precodecontent/ docs/ nuxt-content-guide.md getting-started.md i18n-config.md about.md blockquotepdocs文件夹是我专门为项目建立的，无需跟我一模一样h2🔄 路由映射关系tabletheadtrth访问 URLthi18n 处理thContent 查询th实际文件tbodytrtdcode/zh_cn/docs/nuxt-guidetd识别为中文tdcode/docs/nuxt-guidetdcodecontent/docs/nuxt-guide.mdtrtdcode/en/docs/nuxt-guidetd识别为英文tdcode/docs/nuxt-guidetdcodecontent/docs/nuxt-guide.mdtrtdcode/ja/docs/nuxt-guidetd识别为日文tdcode/docs/nuxt-guidetdcodecontent/docs/nuxt-guide.mdh2总结p通过 code@nuxtjs/i18n 的路由前缀与 code@nuxt/content 的路径转换相结合，我们实现了：ollistrong维护成本最小化 - 单一内容源listrong用户体验最大化 - 完整的多语言界面支持listrong查询稳定性 - 使用固定的内容集合查询，避免路径解析问题p这种模式特别适合：ulli技术文档、API参考li个人技术博客li初创公司产品文档li任何需要快速支持多语言但翻译资源有限的场景hrpem本文采用所述方案编写，访问 code/en/docs/single-content-multilingual-routes 或 code/zh_cn/docs/single-content-multilingual-routes 可体验实际效果。stylehtml pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}