Next.js更优雅的多语言方案
最近给老婆@暴走哒柠檬 的木炭业务重构了一个B2B的独立站。
需求主要有:
1.多语言
2.有询盘表单
3.完善的SEO优化
最终实际的SEO效果如下,当然这只是基础。
今天也主要分享一下如何做好多语言的建设。
欢迎访问:CharcoalGo
一、目录结构
-app/
|-[lang]/
|-products/
|-news/
|-other/
|-page.js
以上的app就是整个项目的主要目录,其中[lang]代表url中的语言参数,可以被页面获取到;
二、利用中间件避免重复目录
从上边的目录其实可以看到,如果访问/en/products,一切正常,但访问/products,难道还需要在[lang]的同级复制一层吗?这样修改起来也太麻烦了。
最终,利用中间件实现如下重写:
javascript
import { locales } from './lib/i18n';
import { NextRequest, NextResponse } from 'next/server';
const rewritePaths = [
{ pattern: /^\/$/, destination: '/en/' }, // 匹配根路径 /
{ pattern: /^\/products(\/)?$/, destination: '/en/products' },
{ pattern: /^\/products\/([^\/]+)(\/)?$/, destination: '/en/products/$1' }, // 匹配 /products/1 等动态路径
{ pattern: /^\/news(\/)?$/, destination: '/en/news' },
{ pattern: /^\/news\/([^\/]+)(\/)?$/, destination: '/en/news/$1' }, // 匹配 /news/1 等动态路径
{ pattern: /^\/about$/, destination: '/en/about' },
{ pattern: /^\/contact$/, destination: '/en/contact' },
{ pattern: /^\/tags$/, destination: '/en/tags' },
{ pattern: /^\/tags\/([^\/]+)(\/)?$/, destination: '/en/tags/$1' }, // 匹配 /tags/1 等动态路径
];
export function middleware(request) {
const { pathname } = request.nextUrl;
for (const { pattern, destination } of rewritePaths) {
const match = pathname.match(pattern);
if (match) {
request.nextUrl.pathname = pathname.replace(pattern, destination);
return NextResponse.rewrite(request.nextUrl);
}
}
const isExit = locales.some((locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`);
if (isExit) return;
request.nextUrl.pathname = `/`;
return NextResponse.redirect(request.nextUrl);
}
export const config = {
matcher: ['/((?!_next)(?!.*\\.(?:ico|png|gif|svg|jpg|jpeg|webp|xml|txt|mp4)$)(?!/api).*)'],
};
三、内链优化
上述设计之后,随之而来的问题就是,当访问/en/products时,页面展示了产品列表。
此时,点击产品跳转到的页面应该是/en/products/[name],那这个/en/,如何拼接进去呢?而且在很多页面、组件中都需要拼接,每个地方都写一次判断,太麻烦了。
最终,选择了使用函数方法+Hooks的方式共享了几个参数:
javascript
// hooks/xxx.js
import { useEffect, useState } from 'react';
import { usePathname } from 'next/navigation';
import { defaultLocale, locales } from '@/lib/i18n';
export const useLocalizedPath = () => {
const pathname = usePathname();
const [langName, setLangName] = useState(defaultLocale);
useEffect(() => {
if (pathname !== '/' && locales.includes(pathname.split('/')[1])) {
setLangName(pathname.split('/')[1]);
}
}, [pathname]);
return { pathname, langName };
};
javascript
export const getLocalizedUrl = (path, lang, link) => {
if (!path.includes(lang)) {
if(link == '/'){
return '/'
}else{
return link
}
}
return `/${lang}${link}`;
};
这样,在实际页面中,引用这两个方法进行判断即可。
目前也只是做到这里,如果你有更好的方法,欢迎讨论。