为Markdoc + Next.js文档站点添加i18n多语言支持的问题
问题描述
我正在使用Markdoc + Next.js搭建文档站点,想要添加i18n多语言支持。已修改next.config.js,但不知道如何根据locale变量调用不同的.md文件。
我的文档文件结构如下:
- src/pages/docs/analytics.en.md(对应locale=en)
- src/pages/docs/analytics.fr.md(对应locale=fr)
- src/pages/docs/analytics.es.md(对应locale=es)
next.config.js
const withMarkdoc = require('@markdoc/next.js') /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, pageExtensions: ['js', 'jsx', 'md'], experimental: { scrollRestoration: true, }, i18n: { locales: ['en', 'fr', 'es', 'de', 'it'], defaultLocale: 'en' }, } module.exports = withMarkdoc()(nextConfig)
src/pages/_document.jsx
class MyDocument extends Document { static async getInitialProps(ctx) { const originalRenderPage = ctx.renderPage console.log("Locale: ", ctx.locale) // Run the React rendering logic synchronously ctx.renderPage = () => originalRenderPage({ // Useful for wrapping the whole react tree enhanceApp: (App) => (props) => { let pageProps = props.pageProps; //console.log("props", pageProps.markdoc.file) return <App {...props} /> }, // Useful for wrapping in a per-page basis enhanceComponent: (Component) => Component, }) // Run the parent `getInitialProps`, it now includes the custom `renderPage` const initialProps = await Document.getInitialProps(ctx) return initialProps } render() { return ( <Html className="antialiased [font-feature-settings:'ss01']" lang="en"> <Head> <script dangerouslySetInnerHTML={{ __html: themeScript }} /> </Head> <body className="bg-white dark:bg-slate-900"> <Main /> <NextScript /> </body> </Html> ) } } export default MyDocument
src/pages/_app.jsx
import Head from 'next/head' import { slugifyWithCounter } from '@sindresorhus/slugify' import { useRouter } from 'next/router' import { Layout } from '@/components/Layout' import 'focus-visible' import '@/styles/tailwind.css' function getNodeText(node) { let text = '' for (let child of node.children ?? []) { if (typeof child === 'string') { text += child } text += getNodeText(child) } return text } function collectHeadings(nodes, slugify = slugifyWithCounter()) { let sections = [] for (let node of nodes) { if (node.name === 'h2' || node.name === 'h3') { let title = getNodeText(node) if (title) { let id = slugify(title) node.attributes.id = id if (node.name === 'h3') { if (!sections[sections.length - 1]) { throw new Error( 'Cannot add `h3` to table of contents without a preceding `h2`' ) } sections[sections.length - 1].children.push({ ...node.attributes, title, }) } else { sections.push({ ...node.attributes, title, children: [] }) } } } sections.push(...collectHeadings(node.children ?? [], slugify)) } return sections } export default function App({ Component, pageProps }) { let router = useRouter() let title = pageProps.markdoc?.frontmatter.title let pageTitle = pageProps.markdoc?.frontmatter.pageTitle || `${pageProps.markdoc?.frontmatter.title} - Guides` let description = pageProps.markdoc?.frontmatter.description let tableOfContents = pageProps.markdoc?.content ? collectHeadings(pageProps.markdoc.content) : [] return ( <> <Head> <title>{pageTitle}</title> {description && <meta name="description" content={description} />} </Head> <Layout title={title} tableOfContents={tableOfContents}> <Component {...pageProps} /> </Layout> </> ) }
markdoc/nodes.js
import { Fence } from '@/components/Fence' import { nodes as defaultNodes } from '@markdoc/markdoc' const nodes = { document: { render: undefined, allowComments: true }, th: { ...defaultNodes.th, attributes: { ...defaultNodes.th.attributes, scope: { type: String, default: 'col', }, }, }, fence: { render: Fence, attributes: { language: { type: String, }, }, }, } export default nodes
解决方案
方法一:动态路由+getStaticProps按需加载
在src/pages/docs/[slug].jsx创建动态路由页面,通过getStaticProps根据当前locale拼接文件名,加载对应语言的Markdoc文件:
import React from 'react'; import { getMarkdocProps } from '@markdoc/next.js'; import Markdoc from '@markdoc/markdoc'; import nodes from '@/markdoc/nodes'; export default function DocsPage({ content, frontmatter }) { return ( <div> <h1>{frontmatter.title}</h1> {content} </div> ); } export async function getStaticProps({ params, locale }) { const fs = require('fs'); const path = require('path'); // 拼接本地化文件名 const localizedPath = path.join(process.cwd(), `src/pages/docs/${params.slug}.${locale}.md`); // 备用默认语言路径 const defaultPath = path.join(process.cwd(), `src/pages/docs/${params.slug}.en.md`); // 优先加载本地化文件,不存在则用默认语言 const targetPath = fs.existsSync(localizedPath) ? localizedPath : defaultPath; const content = fs.readFileSync(targetPath, 'utf8'); const ast = Markdoc.parse(content); const transformed = Markdoc.transform(ast, { nodes }); const reactContent = Markdoc.renderers.react(transformed, React); return { props: { content: reactContent, frontmatter: ast.attributes.frontmatter || {} }, }; } export async function getStaticPaths({ locales }) { // 生成所有语言的文档路径(可根据实际目录动态获取slug列表) const slugs = ['analytics']; const paths = locales.flatMap(locale => slugs.map(slug => ({ params: { slug }, locale })) ); return { paths, fallback: false }; }
方法二:自定义Markdoc加载器(全局配置)
修改next.config.js,扩展withMarkdoc的配置,添加自定义解析逻辑,让它自动根据locale匹配对应文件:
const withMarkdoc = require('@markdoc/next.js'); const fs = require('fs'); const path = require('path'); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, pageExtensions: ['js', 'jsx', 'md'], experimental: { scrollRestoration: true, }, i18n: { locales: ['en', 'fr', 'es', 'de', 'it'], defaultLocale: 'en' }, }; module.exports = withMarkdoc({ nodes: './markdoc/nodes.js', // 自定义文件解析逻辑 resolve: (filePath, ctx) => { const { locale = 'en' } = ctx; const dir = path.dirname(filePath); const baseName = path.basename(filePath, '.md'); const localizedFilePath = path.join(dir, `${baseName}.${locale}.md`); if (fs.existsSync(localizedFilePath)) { return fs.readFileSync(localizedFilePath, 'utf8'); } // fallback到默认文件 return fs.readFileSync(filePath, 'utf8'); }, })(nextConfig);
额外优化:修正_document的lang属性
在_document.jsx中把固定的lang="en"改为动态获取locale:
static async getInitialProps(ctx) { const initialProps = await Document.getInitialProps(ctx); // 传递locale到props return { ...initialProps, locale: ctx.locale }; } render() { return ( <Html className="antialiased [font-feature-settings:'ss01']" lang={this.props.locale}> {/* ...其他内容 */} </Html> ) }
内容的提问来源于stack exchange,提问作者sparkle
相关产品推荐
相关产品推荐

