fix: preserve locale in documentation links (#3717)

This commit is contained in:
Huixin615 2026-06-23 07:53:53 +08:00 committed by GitHub
parent 90fdda77e8
commit 0ee35ca38f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 241 additions and 48 deletions

View File

@ -0,0 +1,13 @@
import { Cards as NextraCards } from "nextra/components";
import type { ComponentProps } from "react";
import { LocalizedCard } from "./localized-mdx-components";
function LocalizedCardsRoot(props: ComponentProps<typeof NextraCards>) {
return <NextraCards {...props} />;
}
export const LocalizedCards = Object.assign(LocalizedCardsRoot, {
Card: LocalizedCard,
displayName: "LocalizedCards",
});

View File

@ -0,0 +1,15 @@
const SUPPORTED_DOC_LANGUAGES = new Set(["en", "zh"]);
const UNLOCALIZED_DOCS_PATH = /^\/docs(?=\/|[?#]|$)/;
export function localizeDocsHref(
href: string,
lang: string | undefined,
): string {
if (!lang || !SUPPORTED_DOC_LANGUAGES.has(lang)) {
return href;
}
if (!UNLOCALIZED_DOCS_PATH.test(href)) {
return href;
}
return `/${lang}${href}`;
}

View File

@ -0,0 +1,43 @@
"use client";
import { useParams } from "next/navigation";
import { Anchor, Cards as NextraCards } from "nextra/components";
import type { ComponentProps } from "react";
import { cn } from "@/lib/utils";
import { localizeDocsHref } from "./localized-links";
const DOCS_LINK_CLASS_NAME =
"x:text-primary-600 x:underline x:hover:no-underline x:decoration-from-font x:[text-underline-position:from-font]";
function useDocumentLanguage(): string | undefined {
const { lang } = useParams<{ lang?: string }>();
return lang;
}
export function LocalizedDocsLink({
href,
className,
...props
}: ComponentProps<typeof Anchor>) {
const lang = useDocumentLanguage();
const localizedHref =
typeof href === "string" ? localizeDocsHref(href, lang) : href;
return (
<Anchor
{...props}
className={cn(DOCS_LINK_CLASS_NAME, className)}
href={localizedHref}
/>
);
}
export function LocalizedCard({
href,
...props
}: ComponentProps<typeof NextraCards.Card>) {
const lang = useDocumentLanguage();
return <NextraCards.Card {...props} href={localizeDocsHref(href, lang)} />;
}

View File

@ -3,7 +3,7 @@ title: Agents and Threads
description: DeerFlow App supports multiple named agents and maintains conversation state across sessions through threads and checkpointing.
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# Agents and Threads

View File

@ -3,7 +3,7 @@ title: Configuration
description: DeerFlow App is configured through two files and a set of environment variables. This page covers the application-level configuration that most operators need to set up before deploying.
---
import { Callout, Cards, Tabs } from "nextra/components";
import { Callout, Tabs } from "nextra/components";
# Configuration

View File

@ -3,7 +3,7 @@ title: Deployment Guide
description: "This guide covers all supported deployment methods for DeerFlow App: local development, Docker Compose, and production with Kubernetes-managed sandboxes."
---
import { Callout, Cards, Steps, Tabs } from "nextra/components";
import { Callout, Steps, Tabs } from "nextra/components";
# Deployment Guide

View File

@ -3,7 +3,7 @@ title: DeerFlow App
description: DeerFlow App is the reference implementation of what a production DeerFlow experience looks like. It assembles the Harness runtime, a web-based conversation workspace, an API gateway, and a reverse proxy into a single deployable system.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# DeerFlow App

View File

@ -3,7 +3,7 @@ title: Operations and Troubleshooting
description: This page covers day-to-day operational tasks and solutions to common problems when running DeerFlow App.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Operations and Troubleshooting

View File

@ -3,7 +3,7 @@ title: Quick Start
description: This guide walks you through starting DeerFlow App on your local machine using the `make dev` workflow. Gateway, Frontend, and nginx start together and are accessible through a single URL.
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# Quick Start

View File

@ -3,7 +3,7 @@ title: Workspace Usage
description: The DeerFlow App workspace is a browser-based interface for having multi-turn conversations with the agent, tracking task progress, viewing artifacts, and managing files.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Workspace Usage

View File

@ -3,7 +3,7 @@ title: Configuration
description: "DeerFlow's configuration system is designed around one goal: every meaningful behavior should be expressible in a config file, not hardcoded in the application. This makes deployments reproducible, auditable, and easy to customize per environment."
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Configuration

View File

@ -3,7 +3,7 @@ title: Customization
description: DeerFlow's pluggable architecture means most parts of the system can be replaced or extended without forking the core. This page maps the extension points and explains how to use each one.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Customization

View File

@ -3,7 +3,7 @@ title: Design Principles
description: Understanding the design principles behind DeerFlow Harness helps you use it effectively, extend it confidently, and reason about how your agents will behave in production.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Design Principles

View File

@ -3,7 +3,7 @@ title: Install DeerFlow Harness
description: The DeerFlow Harness is the Python SDK and runtime foundation for building your own Super Agent systems.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Install DeerFlow Harness

View File

@ -3,7 +3,7 @@ title: Integration Guide
description: DeerFlow Harness is not only a standalone application. It is a Python library you can import and use inside your own backend, API server, automation system, or multi-agent orchestrator.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Integration Guide

View File

@ -3,7 +3,7 @@ title: Lead Agent
description: The Lead Agent is the central executor in a DeerFlow thread. Every conversation, task, and workflow flows through it. Understanding how it works helps you configure it effectively and extend it when needed.
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# Lead Agent

View File

@ -3,7 +3,7 @@ title: MCP Integration
description: The **Model Context Protocol (MCP)** is an open standard for connecting language models to external tools and data sources. DeerFlow's MCP integration allows you to extend the agent with any tool server that implements the MCP protocol — without modifying the harness itself.
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# MCP Integration

View File

@ -3,7 +3,7 @@ title: Memory
description: Memory is a runtime feature of the DeerFlow Harness. It is not a simple conversation log — it is a structured store of facts and context summaries that persist across separate sessions and inform the agent's behavior in future conversations.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Memory

View File

@ -3,7 +3,7 @@ title: Quick Start
description: Learn how to create and run a DeerFlow agent with create_deerflow_agent, from model setup to streaming responses.
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# Quick Start

View File

@ -3,7 +3,7 @@ title: Sandbox
description: The sandbox gives the Lead Agent a controlled environment where it can read files, write outputs, run shell commands, and produce artifacts. Without a sandbox, the agent can only generate text. With a sandbox, it can write and execute code, process data files, generate charts, and build deliverables.
---
import { Callout, Cards, Tabs } from "nextra/components";
import { Callout, Tabs } from "nextra/components";
# Sandbox

View File

@ -3,7 +3,7 @@ title: Skills
description: A skill is more than a prompt. It is a self-contained capability package that can include structured instructions, step-by-step workflows, domain-specific best practices, supporting resources, and tool configurations. Skills are loaded on demand — they inject their content when a task calls for them and stay out of the context otherwise.
---
import { Callout, Cards, FileTree, Steps } from "nextra/components";
import { Callout, FileTree, Steps } from "nextra/components";
# Skills

View File

@ -3,7 +3,7 @@ title: Subagents
description: When a task is too broad for a single reasoning thread, or when parts of it can be done in parallel, the Lead Agent delegates work to **subagents**. A subagent is a self-contained agent invocation that receives a specific task, executes it, and returns the result.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Subagents

View File

@ -3,7 +3,7 @@ title: Tools
description: "The Lead Agent is a tool-calling agent. Tools are how it interacts with the world: searching the web, reading and writing files, running commands, delegating tasks, and presenting outputs to the user."
---
import { Callout, Cards, Tabs } from "nextra/components";
import { Callout, Tabs } from "nextra/components";
# Tools

View File

@ -3,7 +3,7 @@ title: Core Concepts
description: Before you go deeper into DeerFlow, it helps to anchor on a few concepts that appear throughout the system. These concepts explain what DeerFlow is optimizing for and why its architecture looks the way it does.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Core Concepts

View File

@ -3,7 +3,7 @@ title: Harness vs App
description: "DeerFlow has two layers that are closely related but serve different purposes."
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Harness vs App

View File

@ -3,7 +3,6 @@ title: Introduction
description: DeerFlow is a runtime harness for building long-horizon agent systems with skills, memory, tools, and sandboxed execution.
---
import { Cards } from "nextra/components";
# Introduction

View File

@ -3,7 +3,7 @@ title: Why DeerFlow
description: DeerFlow exists because modern agent systems need more than a chat loop. A useful agent must plan over long horizons, break work into sub-tasks, use tools, manipulate files, run code safely, and preserve enough context to stay coherent across a complex task. DeerFlow was built to provide that runtime foundation.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Why DeerFlow

View File

@ -3,7 +3,7 @@ title: Agent 与线程
description: 了解 DeerFlow 中 Agent 与线程的关系,以及如何管理自定义 Agent 和对话线程。
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# Agent 与线程

View File

@ -3,7 +3,7 @@ title: 配置
description: 本页面涵盖 DeerFlow 应用的所有配置层——`config.yaml`、前端环境变量、`extensions_config.json` 和运行时环境变量。
---
import { Callout, Cards, Tabs } from "nextra/components";
import { Callout, Tabs } from "nextra/components";
# 配置

View File

@ -3,7 +3,7 @@ title: 部署指南
description: 本指南涵盖 DeerFlow 应用所有支持的部署方式本地开发、Docker Compose 以及使用 Kubernetes 管理沙箱的生产环境。
---
import { Callout, Cards, Steps, Tabs } from "nextra/components";
import { Callout, Steps, Tabs } from "nextra/components";
# 部署指南

View File

@ -3,7 +3,7 @@ title: DeerFlow 应用
description: DeerFlow 应用是 DeerFlow 生产体验的参考实现。它将 Harness 运行时、基于 Web 的对话工作区、API Gateway 和反向代理组合成一个可部署的完整系统。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# DeerFlow 应用

View File

@ -3,7 +3,7 @@ title: 运维与排障
description: 本页面涵盖运行 DeerFlow 应用的操作信息:日志记录、常见问题和维护任务。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 运维与排障

View File

@ -3,7 +3,7 @@ title: 快速上手
description: 本指南引导你使用 `make dev` 工作流在本地机器上启动 DeerFlow 应用。Gateway、前端和 nginx 会一起启动,通过单个 URL 访问。
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# 快速上手

View File

@ -3,7 +3,7 @@ title: 工作区使用
description: DeerFlow 工作区是一个基于浏览器的对话界面,你可以在其中向 Agent 发送消息、上传文件、查看中间步骤,以及下载生成的产出物。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 工作区使用

View File

@ -3,7 +3,7 @@ title: 配置
description: DeerFlow 的配置系统围绕一个目标设计:每一个有意义的行为都应该可以在配置文件中表达,而不是硬编码在应用程序中。这使部署可重现、可审计,并且易于按环境定制。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 配置

View File

@ -3,7 +3,7 @@ title: 自定义与扩展
description: DeerFlow 的可插拔架构意味着系统的大多数部分都可以在不 fork 核心的情况下被替换或扩展。本页面列举了扩展点,并解释如何使用每一个。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 自定义与扩展

View File

@ -3,7 +3,7 @@ title: 设计理念
description: 了解 DeerFlow Harness 背后的设计理念,有助于你有效地使用它、自信地扩展它,并推断 Agent 在生产环境中的行为方式。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 设计理念

View File

@ -3,7 +3,7 @@ title: 安装 DeerFlow Harness
description: DeerFlow Harness 是构建自己 Super Agent 系统的 Python SDK 和运行时基础。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 安装 DeerFlow Harness

View File

@ -3,7 +3,7 @@ title: 集成指南
description: DeerFlow Harness 不仅仅是一个独立应用程序——它是一个可以导入并在你自己的后端、API 服务器、自动化系统或多 Agent 协调器中使用的 Python 库。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 集成指南

View File

@ -3,7 +3,7 @@ title: Lead Agent
description: Lead Agent 是 DeerFlow 线程中的核心执行者。每个对话、任务和工作流都通过它进行。理解它的工作方式有助于你有效地配置它,并在需要时扩展它。
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# Lead Agent

View File

@ -3,7 +3,7 @@ title: MCP 集成
description: Model Context ProtocolMCP 是连接语言模型与外部工具和数据源的开放标准。DeerFlow 的 MCP 集成允许你用任何实现了 MCP 协议的工具服务器扩展 Agent——无需修改 Harness 本身。
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# MCP 集成

View File

@ -3,7 +3,7 @@ title: 记忆系统
description: 记忆是 DeerFlow Harness 的一个运行时功能。它不是简单的对话日志,而是跨多个独立会话持久化、在未来对话中影响 Agent 行为的结构化事实和上下文摘要存储。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 记忆系统

View File

@ -3,7 +3,7 @@ title: 快速上手
description: 学习如何使用 create_deerflow_agent 创建并运行 DeerFlow Agent从模型初始化到流式响应。
---
import { Callout, Cards, Steps } from "nextra/components";
import { Callout, Steps } from "nextra/components";
# 快速上手

View File

@ -3,7 +3,7 @@ title: 沙箱
description: 沙箱为 Lead Agent 提供一个受控环境,在其中可以读取文件、写入输出、运行 Shell 命令并生成产出物。没有沙箱Agent 只能生成文本;有了沙箱,它可以编写和执行代码、处理数据文件、生成图表并构建交付物。
---
import { Callout, Cards, Tabs } from "nextra/components";
import { Callout, Tabs } from "nextra/components";
# 沙箱

View File

@ -3,7 +3,7 @@ title: 技能
description: 技能不仅仅是提示词。它是一个自包含的能力包,可以包含结构化指令、分步工作流、领域最佳实践、支撑资源和工具配置。技能按需加载——在任务需要时注入内容,否则不影响上下文。
---
import { Callout, Cards, FileTree, Steps } from "nextra/components";
import { Callout, FileTree, Steps } from "nextra/components";
# 技能

View File

@ -3,7 +3,7 @@ title: 子 Agent
description: 当一个任务对单个推理线程来说太宽泛或者部分任务可以并行完成时Lead Agent 将工作委派给**子 Agent**。子 Agent 是一个独立的 Agent 调用,接收特定任务、执行并返回结果。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 子 Agent

View File

@ -3,7 +3,7 @@ title: 工具
description: Lead Agent 是一个工具调用 Agent。工具是它与世界交互的方式搜索网络、读写文件、运行命令、委派任务以及向用户呈现输出。
---
import { Callout, Cards, Tabs } from "nextra/components";
import { Callout, Tabs } from "nextra/components";
# 工具

View File

@ -3,7 +3,7 @@ title: 核心概念
description: 在深入了解 DeerFlow 之前,先建立一些贯穿整个系统的核心概念。这些概念解释了 DeerFlow 的优化目标以及其架构设计的原因。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 核心概念

View File

@ -3,7 +3,7 @@ title: Harness 与应用
description: DeerFlow 有两个紧密相关但服务于不同目的的层次:.
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# Harness 与应用

View File

@ -2,7 +2,6 @@
title: 简介
description: DeerFlow 是一个运行时 Harness用于构建支持技能、记忆、工具和沙箱执行的长时序 Agent 系统。
---
import { Cards } from "nextra/components";
# 简介

View File

@ -3,7 +3,7 @@ title: 为什么选择 DeerFlow
description: DeerFlow 的诞生是因为现代 Agent 系统需要的不仅仅是一个聊天循环。一个真正有用的 Agent 必须能够进行长时序规划、将任务拆解为子任务、使用工具、操作文件、安全地运行代码并在复杂任务中保持足够的上下文连贯性。DeerFlow 正是为提供这样的运行时基础而构建的。
---
import { Callout, Cards } from "nextra/components";
import { Callout } from "nextra/components";
# 为什么选择 DeerFlow

View File

@ -1,5 +1,8 @@
import { useMDXComponents as getThemeComponents } from "nextra-theme-docs"; // nextra-theme-blog or your custom theme
import { LocalizedCards } from "@/components/docs/localized-cards";
import { LocalizedDocsLink } from "@/components/docs/localized-mdx-components";
// Get the default MDX components
const themeComponents = getThemeComponents();
@ -7,5 +10,7 @@ const themeComponents = getThemeComponents();
export function useMDXComponents() {
return {
...themeComponents,
a: LocalizedDocsLink,
Cards: LocalizedCards,
};
}

View File

@ -0,0 +1,54 @@
import { expect, test } from "@playwright/test";
test.describe("Localized documentation links", () => {
test("keeps English card navigation in the English docs", async ({
page,
}) => {
await page.goto("/en/docs/introduction/core-concepts");
const card = page.locator("a.nextra-card", { hasText: "Why DeerFlow" });
await expect(card).toHaveAttribute(
"href",
"/en/docs/introduction/why-deerflow",
);
await card.click();
await expect(page).toHaveURL(/\/en\/docs\/introduction\/why-deerflow$/);
await expect(page.locator("main h1")).toContainText("Why DeerFlow");
});
test("keeps Chinese card navigation in the Chinese docs", async ({
page,
}) => {
await page.goto("/zh/docs/introduction/core-concepts");
const card = page.locator("a.nextra-card", { hasText: "Harness 与应用" });
await expect(card).toHaveAttribute(
"href",
"/zh/docs/introduction/harness-vs-app",
);
await card.click();
await expect(page).toHaveURL(/\/zh\/docs\/introduction\/harness-vs-app$/);
await expect(page.locator("main h1")).toContainText("Harness 与应用");
});
test("localizes regular Markdown links", async ({ page }) => {
await page.goto("/en/docs/application/workspace-usage");
const link = page
.locator("main")
.getByRole("link", { name: "Agents and Threads" })
.first();
await expect(link).toHaveAttribute(
"href",
"/en/docs/application/agents-and-threads",
);
await link.click();
await expect(page).toHaveURL(
/\/en\/docs\/application\/agents-and-threads$/,
);
await expect(page.locator("main h1")).toContainText("Agents and Threads");
});
});

View File

@ -0,0 +1,65 @@
import { readFileSync, readdirSync } from "node:fs";
import { join, relative } from "node:path";
import { describe, expect, it } from "@rstest/core";
import { localizeDocsHref } from "@/components/docs/localized-links";
const CONTENT_ROOT = join(process.cwd(), "src/content");
function findMdxFiles(directory: string): string[] {
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
const path = join(directory, entry.name);
return entry.isDirectory()
? findMdxFiles(path)
: entry.name.endsWith(".mdx")
? [path]
: [];
});
}
describe("localizeDocsHref", () => {
it.each([
["/docs", "en", "/en/docs"],
["/docs/introduction", "en", "/en/docs/introduction"],
[
"/docs/introduction?tab=overview#next",
"zh",
"/zh/docs/introduction?tab=overview#next",
],
])("localizes %s for %s", (href, lang, expected) => {
expect(localizeDocsHref(href, lang)).toBe(expected);
});
it.each([
["/en/docs/introduction", "zh"],
["/zh/docs/introduction", "en"],
["https://example.com/docs", "en"],
["#overview", "zh"],
["mailto:docs@example.com", "en"],
["/workspace", "zh"],
["/docs-extra", "en"],
["/docs/introduction", "fr"],
["/docs/introduction", undefined],
])("leaves %s unchanged for language %s", (href, lang) => {
expect(localizeDocsHref(href, lang)).toBe(href);
});
});
describe("documentation Cards imports", () => {
it("does not bypass the locale-aware MDX Cards component", () => {
const violations = findMdxFiles(CONTENT_ROOT)
.filter((path) => {
const source = readFileSync(path, "utf8");
const nextraImports = source.matchAll(
/import\s*\{([^}]*)\}\s*from\s*["']nextra\/components["']/g,
);
return [...nextraImports].some((match) =>
match[1]?.split(",").some((name) => name.trim() === "Cards"),
);
})
.map((path) => relative(CONTENT_ROOT, path));
expect(violations).toEqual([]);
});
});