mirror of
https://github.com/nocobase/nocobase.git
synced 2026-08-28 17:43:07 +08:00
305 lines
8.4 KiB
TypeScript
305 lines
8.4 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import { pluginSass } from '@rsbuild/plugin-sass';
|
|
import { defineConfig, type RspressPlugin } from '@rspress/core';
|
|
import { pluginLlms } from '@rspress/plugin-llms';
|
|
import { pluginSchema } from './plugins/pluginSchema';
|
|
import { pluginOgDescription } from './plugins/pluginOgDescription';
|
|
import { pluginRemoveGenerator } from './plugins/pluginRemoveGenerator';
|
|
import { pluginPreview } from '@rspress/plugin-preview';
|
|
import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
|
|
import {
|
|
pluginCrossRefSidebar,
|
|
crossRefCanonicalMap,
|
|
} from './plugins/pluginCrossRef';
|
|
import { pluginSearchSections } from './plugins/pluginSearchSections';
|
|
import { pluginSearchIndex } from './plugins/pluginSearchIndex';
|
|
import * as path from 'node:path';
|
|
import * as fs from 'node:fs/promises';
|
|
|
|
const lang = process.env.DOCS_LANG || 'en';
|
|
const base = process.env.DOCS_BASE || lang === 'en' ? '/' : `/${lang}/`;
|
|
const checkDeadLinks = process.env.CHECK_DEAD_LINKS !== 'false';
|
|
const rspressI18nAliases: Readonly<Record<string, string>> = {
|
|
cn: 'zh',
|
|
};
|
|
|
|
const locales = {
|
|
en: {
|
|
title: 'NocoBase Documentation',
|
|
description: 'Learn and master NocoBase quickly',
|
|
},
|
|
cn: {
|
|
title: 'NocoBase 文档',
|
|
description: '快速学习和掌握 NocoBase',
|
|
},
|
|
};
|
|
|
|
const currentLocale = locales[lang as keyof typeof locales] || locales.en;
|
|
|
|
const indexLanguages = [
|
|
'en',
|
|
'cn',
|
|
'ja',
|
|
'es',
|
|
'pt',
|
|
'de',
|
|
'fr',
|
|
'ru',
|
|
'id',
|
|
'vi',
|
|
];
|
|
|
|
const langMap = {
|
|
en: 'en-US',
|
|
cn: 'zh-CN',
|
|
ja: 'ja-JP',
|
|
es: 'es-ES',
|
|
pt: 'pt-PT',
|
|
de: 'de-DE',
|
|
fr: 'fr-FR',
|
|
ru: 'ru-RU',
|
|
id: 'id-ID',
|
|
vi: 'vi-VN',
|
|
};
|
|
|
|
/**
|
|
* 搜索结果分组标题里两个固定分组的文案。其余分组的标签由 `pluginSearchSections` 从 `_nav.json` 和
|
|
* 各目录 `index.md` 里读出来,本来就是本地化好的,不需要在这里维护。
|
|
*
|
|
* 缺失语言由 Rspress 自动回落到 `en`,所以只写常用语种即可。
|
|
*/
|
|
const searchI18nSource: Record<string, Record<string, string>> = {
|
|
searchSectionPlugins: {
|
|
en: 'Plugins',
|
|
zh: '插件',
|
|
ja: 'プラグイン',
|
|
de: 'Plugins',
|
|
es: 'Plugins',
|
|
fr: 'Plugins',
|
|
pt: 'Plugins',
|
|
ru: 'Плагины',
|
|
id: 'Plugin',
|
|
vi: 'Plugin',
|
|
},
|
|
searchSectionOthers: {
|
|
en: 'Others',
|
|
zh: '其他',
|
|
ja: 'その他',
|
|
de: 'Sonstiges',
|
|
es: 'Otros',
|
|
fr: 'Autres',
|
|
pt: 'Outros',
|
|
ru: 'Прочее',
|
|
id: 'Lainnya',
|
|
vi: 'Khác',
|
|
},
|
|
};
|
|
|
|
function withRspressI18nAliases(
|
|
source: Record<string, Record<string, string>>,
|
|
) {
|
|
return Object.fromEntries(
|
|
Object.entries({ ...source, ...searchI18nSource }).map(
|
|
([key, translations]) => {
|
|
const nextTranslations = { ...translations };
|
|
|
|
for (const [alias, original] of Object.entries(rspressI18nAliases)) {
|
|
if (!nextTranslations[alias] && nextTranslations[original]) {
|
|
nextTranslations[alias] = nextTranslations[original];
|
|
}
|
|
}
|
|
|
|
return [key, nextTranslations];
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
function sitemap(): RspressPlugin {
|
|
const routes = new Set<string>();
|
|
|
|
return {
|
|
name: '@nocobase/custom-sitemap',
|
|
|
|
// Collect all route paths during build
|
|
async extendPageData(pageData: any, isProd: boolean) {
|
|
if (!isProd) {
|
|
return;
|
|
}
|
|
if (lang !== 'en') {
|
|
return;
|
|
}
|
|
if (pageData?.routePath) {
|
|
routes.add(pageData.routePath as string);
|
|
}
|
|
},
|
|
|
|
// Generate sitemap.xml after build
|
|
async afterBuild(config: any, isProd: boolean) {
|
|
if (!isProd) {
|
|
return;
|
|
}
|
|
|
|
if (lang !== 'en') {
|
|
return;
|
|
}
|
|
|
|
const baseDomain = 'https://docs.nocobase.com';
|
|
|
|
const urlEntries = Array.from(routes)
|
|
.sort()
|
|
.map((routePath) => {
|
|
const links: string[] = [];
|
|
|
|
// <loc> uses the canonical English URL
|
|
const loc = `${baseDomain}${routePath}`;
|
|
|
|
// Alternate links for each language (same logic as head canonical/alternate)
|
|
for (const language of indexLanguages) {
|
|
if (language === 'en') {
|
|
links.push(
|
|
` <xhtml:link rel="alternate" hreflang="en-US" href="${baseDomain}${routePath}" />`,
|
|
);
|
|
} else {
|
|
const hreflang = langMap[language as keyof typeof langMap];
|
|
links.push(
|
|
` <xhtml:link rel="alternate" hreflang="${hreflang}" href="${baseDomain}/${language}${routePath}" />`,
|
|
);
|
|
}
|
|
}
|
|
|
|
// x-default points to the English URL
|
|
links.push(
|
|
` <xhtml:link rel="alternate" hreflang="x-default" href="${baseDomain}${routePath}" />`,
|
|
);
|
|
|
|
return [
|
|
' <url>',
|
|
` <loc>${loc}</loc>`,
|
|
...links,
|
|
' </url>',
|
|
].join('\n');
|
|
})
|
|
.join('\n');
|
|
|
|
const sitemapXml = [
|
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"',
|
|
' xmlns:xhtml="http://www.w3.org/1999/xhtml">',
|
|
urlEntries,
|
|
'</urlset>',
|
|
'',
|
|
].join('\n');
|
|
|
|
const outDir: string = config.outDir;
|
|
const sitemapPath = path.join(outDir, 'sitemap.xml');
|
|
|
|
await fs.mkdir(outDir, { recursive: true });
|
|
await fs.writeFile(sitemapPath, sitemapXml, 'utf-8');
|
|
},
|
|
};
|
|
}
|
|
export default defineConfig({
|
|
head: [
|
|
[
|
|
'meta',
|
|
{
|
|
name: 'robots',
|
|
content: indexLanguages.includes(lang)
|
|
? 'index,follow'
|
|
: 'noindex,nofollow',
|
|
},
|
|
],
|
|
(route) => {
|
|
// 跨模块虚拟路由通过 frontmatter canonicalPath 指向原始页面
|
|
const canonicalRoute =
|
|
crossRefCanonicalMap?.[route.routePath] || route.routePath;
|
|
if (lang !== 'en') {
|
|
return `<link rel="canonical" href="https://docs.nocobase.com/${lang}${canonicalRoute}" />`;
|
|
}
|
|
return `<link rel="canonical" href="https://docs.nocobase.com${canonicalRoute}" />`;
|
|
},
|
|
(route) => {
|
|
const links = [];
|
|
links.push(
|
|
...indexLanguages.map((language) => {
|
|
if (language === 'en') {
|
|
return `<link rel="alternate" hreflang="en-US" href="https://docs.nocobase.com${route.routePath}" />`;
|
|
}
|
|
const hreflang = langMap[language as keyof typeof langMap];
|
|
return `<link rel="alternate" hreflang="${hreflang}" href="https://docs.nocobase.com/${language}${route.routePath}" />`;
|
|
}),
|
|
);
|
|
links.push(
|
|
`<link rel="alternate" hreflang="x-default" href="https://docs.nocobase.com${route.routePath}" />`,
|
|
);
|
|
return links.join('\n');
|
|
},
|
|
],
|
|
root: path.join(__dirname, `docs/${lang}`),
|
|
outDir: path.join(__dirname, lang === 'en' ? 'dist' : `dist/${lang}`),
|
|
themeDir: path.join(__dirname, 'theme'),
|
|
base,
|
|
title: currentLocale.title,
|
|
description: currentLocale.description,
|
|
icon: 'https://www.nocobase.com/images/favicon/apple-touch-icon.png',
|
|
logo: {
|
|
light: 'https://static-docs.nocobase.com/20260119193433.png',
|
|
dark: 'https://static-docs.nocobase.com/20260119193447.png',
|
|
},
|
|
route: {
|
|
cleanUrls: true,
|
|
},
|
|
builderConfig: {
|
|
html: {
|
|
tags: [
|
|
{
|
|
tag: 'script',
|
|
// 通过 window.RSPRESS_THEME 变量来指定默认的主题模式
|
|
children: "window.RSPRESS_THEME = 'light';",
|
|
},
|
|
],
|
|
},
|
|
source: {
|
|
tsconfigPath: path.join(__dirname, 'tsconfig.json'),
|
|
},
|
|
plugins: [pluginSass(), pluginNodePolyfill()],
|
|
resolve: {
|
|
aliasStrategy: 'prefer-tsconfig',
|
|
},
|
|
},
|
|
markdown: {
|
|
link: {
|
|
checkDeadLinks,
|
|
},
|
|
},
|
|
// Rspress ships built-in Simplified Chinese strings under `zh`, while this site uses `/cn/`.
|
|
i18nSource: (value) => withRspressI18nAliases(value),
|
|
plugins: [
|
|
pluginPreview(),
|
|
pluginLlms(),
|
|
pluginSchema(),
|
|
pluginOgDescription(),
|
|
pluginRemoveGenerator(),
|
|
pluginCrossRefSidebar(),
|
|
pluginSearchSections(),
|
|
pluginSearchIndex(),
|
|
sitemap(),
|
|
],
|
|
search: {
|
|
// 自定义搜索:结果按文档区分组,插件元信息页沉底。见 theme/search/searchHooks.ts。
|
|
searchHooks: path.join(__dirname, 'theme/search/searchHooks.ts'),
|
|
},
|
|
lang,
|
|
themeConfig: {
|
|
darkMode: false,
|
|
socialLinks: [
|
|
{
|
|
icon: 'github',
|
|
mode: 'link',
|
|
content: 'https://github.com/nocobase/nocobase',
|
|
},
|
|
],
|
|
},
|
|
});
|