参考项目: https://github.com/vercel/next.js/tree/canary/examples/with-i18n-rosetta
首先,是本地 i18n
目录下存储翻译文件,可以使用 js/ts/json/yaml 等格式。
这里以最复杂的 yaml 为例。
// i18n/index.ts
import './index.d';
import zh from './zh.yml';
import en from './en.yml';
export default {
zh,
en
};
添加声明文件。可以引入,或者直接加入到 tsconfig 中(为了避免意外,就没有这么做)。
// i18n/index.d.ts
declare module '*';
添加 js-yaml-loader
到 next.config.js
中:
module.exports = {
// 配置语言
i18n: {
locales: ['en', 'zh'],
defaultLocale: 'zh',
localeDetection: true
},
webpack: (config, { dev, isServer }) => {
// 注入 js-yaml-loader
config.module.rules.push({
test: /\.ya?ml$/,
use: 'js-yaml-loader'
});
return config;
}
};
// lib/i18n.tsx
import { useRouter } from 'next/router';
import { createContext, useState, useRef, ReactNode } from 'react';
import rosetta from 'rosetta';
import localeMessages from '../../i18n';
interface iI18nContext {
activeLocale: Languages;
// eslint-disable-next-line no-unused-vars
t: (key: string, ...args: any[]) => string;
}
type Languages = keyof typeof localeMessages;
const i18n = rosetta();
i18n.locale('en');
export const I18nContext = createContext<iI18nContext>({} as iI18nContext);
export default function I18n({ children }: { children: ReactNode }) {
const { locale, defaultLocale } = useRouter();
const activeLocaleRef = useRef<Languages>((locale as Languages) || (defaultLocale as Languages) || 'en');
const [, setTick] = useState(0);
const firstRender = useRef(true);
const i18nWrapper = {
activeLocale: activeLocaleRef.current,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
t: (key: string, ...args: any[]) => i18n.t(key, ...args),
locale: (l: Languages) => {
i18n.locale(l);
i18n.set(l, localeMessages[l]);
// force rerender to update view
setTick((tick) => tick + 1);
}
};
// for initial SSR render
if (firstRender.current === true) {
firstRender.current = false;
i18nWrapper.locale(activeLocaleRef.current);
}
return <I18nContext.Provider value={i18nWrapper}>{children}</I18nContext.Provider>;
}
// hooks/use-i18n.ts
import { useContext } from 'react';
import { I18nContext } from '../lib/i18n';
export default function useI18n() {
const i18n = useContext(I18nContext);
return i18n;
}
注入 I18nContext:
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import Layout from '../components/layout';
import I18n from '../lib/i18n';
function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout>
<I18n>
<Component {...pageProps} />
</I18n>
</Layout>
);
}
export default MyApp;
到这里,就基本完成了。参考本示例项目: https://github.com/willin/nextjs-blog-poc/tree/7e8943897de8665c1b02ee1ad369ea8102fe3bfe
需要安装 globby
。
yarn add --dev globby
创建一个脚本,我的文件名为: scripts/generate-sitemap.mjs
import { writeFileSync } from 'fs';
import { globby } from 'globby';
import prettier from 'prettier';
const DOMAIN = process.env.DOMAIN || 'willin.wang';
async function generate() {
const prettierConfig = await prettier.resolveConfig('./.prettierrc.js');
const pages = await globby([
'src/pages/*.tsx',
'data/**/*.mdx',
'!data/*.mdx',
'!src/pages/_*.tsx',
'!src/pages/api',
'!src/pages/404.tsx'
]);
const sitemap = `
${pages
.map((page) => {
const path = page.replace('src/pages', '').replace('data', '').replace('.tsx', '').replace('.mdx', '');
const route = path === '/index' ? '' : path;
return `
${`https://${DOMAIN}${route}`}
`;
})
.join('')}
`;
const formatted = prettier.format(sitemap, {
...prettierConfig,
parser: 'html'
});
// eslint-disable-next-line no-sync
writeFileSync('public/sitemap.xml', formatted);
}
generate();
注意一下对应的目录。 我的源码是放置于 src/pages
如果不是,就批量替换回 pages
,如果不是用 Typescript,可以改文件后缀为 .jsx
,可以使用正则。还有文件,我是放在 data
目录下,均为 .mdx
文件。
需要安装 rss
。
yarn add --dev rss
获取全部文章,然后生成 RSS Feed:
import { writeFileSync } from 'fs';
import RSS from 'rss';
import { allBlogs } from '.contentlayer/data';
const DOMAIN = process.env.DOMAIN || 'willin.wang';
async function generate() {
const feed = new RSS({
title: 'Lee Robinson',
site_url: `https://${DOMAIN}`,
feed_url: `https://${DOMAIN}/feed.xml`
});
allBlogs.map((post) => {
feed.item({
title: post.title,
url: `https://${DOMAIN}/blog/${post.slug}`,
date: post.publishedAt,
description: post.summary
});
});
writeFileSync('./public/feed.xml', feed.xml({ indent: true }));
}
generate();
其中,这里的 allBlogs
来自于 contentlayer
。也可以是其他 data source。