fix(docs): language switcher generates incorrect URLs in single-language builds (#9233)

This commit is contained in:
PiEgg
2026-04-25 20:54:07 +08:00
committed by GitHub
parent eb60d7d5b8
commit 6d6fb5724b
3 changed files with 62 additions and 83 deletions
+50 -4
View File
@@ -134,11 +134,57 @@ export function NavMenuDivider() {
export function NavLangs() {
const { items, activeValue } = useLangsMenu();
// Language links must use plain <a> tags instead of Rspress's Link component,
// because each language is built separately with its own base (e.g., /cn/).
// Rspress's Link would prepend the current base to the href, producing
// incorrect URLs like /cn/es/ instead of /es/.
const customChildren = (
<>
{items.map(item => {
const isActive = item.text === activeValue;
return (
<li
key={item.text}
className={cls(
'rp-hover-group__item',
isActive && 'rp-hover-group__item--active',
)}
>
<a
href={item.link}
aria-label={item.text}
className="rp-hover-group__item__link rp-link"
hrefLang={item.lang}
lang={item.lang}
rel={item.rel}
>
{item.text}
</a>
</li>
);
})}
</>
);
const { handleMouseEnter, handleMouseLeave, hoverGroup } = useHoverGroup({
items: [],
activeMatcher: item => item.text === activeValue,
customChildren,
});
return items.length > 1 ? (
<NavMenuItemWithChildren
menuItem={{ text: activeValue, items }}
activeMatcher={item => item.text === activeValue}
/>
<li
className="rp-nav-menu__item"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
onClick={handleMouseEnter}
>
<div className="rp-nav-menu__item__container">
{activeValue}
<SvgDown className="rp-nav-menu__item__icon" />
</div>
{hoverGroup}
</li>
) : null;
}
+9 -75
View File
@@ -9,91 +9,25 @@ import {
} from '@rspress/core/runtime';
import { locales } from '../../locales';
function replaceLang(
rawUrl: string,
lang: {
current: string;
target: string;
default: string;
},
version: {
current: string;
default: string;
},
cleanUrls: boolean,
isPageNotFound: boolean,
) {
let url = removeBase(rawUrl);
// rspress.rs/builder + switch to en -> rspress.rs/builder/en/index.html
if (!url || isPageNotFound) {
url = '/';
}
url = normalizeHrefInRuntime(url);
let versionPart = '';
let langPart = '';
let purePathPart = '';
const parts = url.split('/').filter(Boolean);
if (version.current && version.current !== version.default) {
versionPart = parts.shift() || '';
}
// Should we remove the lang part?
// The answer is as follows:
if (lang.target !== lang.default) {
langPart = lang.target;
if (lang.current !== lang.default) {
parts.shift();
}
} else {
parts.shift();
}
purePathPart = parts.join('/') || '';
if ((versionPart || langPart) && !purePathPart) {
purePathPart = cleanUrls ? 'index' : 'index.html';
}
return addLeadingSlash(
[versionPart, langPart, purePathPart].filter(Boolean).join('/'),
);
}
export function useLangsMenu() {
const { page } = usePage();
const { site } = useSite();
const currentVersion = useVersion();
const { pathname, search } = useLocation();
const defaultLang = site.lang || '';
const defaultVersion = site.multiVersion.default || '';
const { pathname } = useLocation();
const { lang: currentLang } = page;
const localeLanguages = locales;
const cleanUrls = site.route?.cleanUrls || false;
const hasMultiLanguage = localeLanguages.length > 1;
const { lang: currentLang, pageType } = page;
// In single-language builds, removeBase strips the current base prefix
// (e.g., /cn/) leaving the page-relative path (e.g., /ai/quick-start).
// We then prepend the target language's base to build the correct URL.
const pagePath = removeBase(pathname);
const translationMenuData = hasMultiLanguage
? {
items: localeLanguages.map(item => {
const targetBase = item.code === 'en' ? '' : `/${item.code}`;
return {
text: item?.label,
link: replaceLang(
pathname + search,
{
current: currentLang,
target: item.code,
default: defaultLang,
},
{
current: currentVersion,
default: defaultVersion,
},
cleanUrls,
pageType === '404',
),
link: `${targetBase}${pagePath}` || '/',
lang: item.code,
rel: 'alternate',
};
+3 -4
View File
@@ -2,7 +2,6 @@ import { useState } from 'react';
import { useLangsMenu } from '../Nav/hooks';
import './NavScreenLangs.scss';
import { useI18n } from '@rspress/core/runtime';
import { Link } from '@rspress/core/theme';
import clsx from 'clsx';
import { SvgDown } from './NavScreenMenuItem';
@@ -48,16 +47,16 @@ export function NavScreenLangs() {
{item.text}
</span>
) : (
<Link
<a
key={item.text}
href={item.link}
className={className}
className={clsx(className, 'rp-link')}
hrefLang={item.lang}
lang={item.lang}
rel="alternate"
>
{item.text}
</Link>
</a>
);
})}
</div>