Files
Bohan Jiangandmultica-agent 920c3ea6d9 feat(docs): add French documentation aligned with UI terminology (MUL-7637) (#8757)
* docs(i18n): translate documentation corpus to French (MUL-7637)

Add French (.fr.mdx) translations for all 45 docs pages plus meta.fr.json
navigation, mirroring the English source. Product and UI terms follow the
in-app French locale at packages/views/locales/fr/ (issue -> tâche,
run -> exécution, autopilot -> automatisation, Chat -> Discussion,
Settings -> Paramètres, etc.). Status and role identifiers stay lowercase
English per the conventions glossary, with the French UI label shown once
where they are defined.

Co-authored-by: multica-agent <[email protected]>

* feat(docs): serve French docs locale (MUL-7637)

Register fr in the docs i18n config and add the French Fumadocs chrome,
language-switcher label, and home hero copy. Fumadocs maps fr to Orama's
built-in French tokenizer, so search needs no custom localeMap entry.

Co-authored-by: multica-agent <[email protected]>

* feat(i18n): link French UI to French docs (MUL-7637)

Route French users from the landing page, runtime guides, the autopilot
webhook filter help, and the Slack/Lark/DingTalk/Telegram setup links to
/docs/fr. The four integration tabs and the runtime helper each carried a
copy of the same locale-prefix ternary; they now share docsLocalePrefix.

Co-authored-by: multica-agent <[email protected]>

* fix(i18n): localize remaining docs entry points (MUL-7637)

French users still reached English docs from the Help menu, the Agents and
Skills "Learn more" links, and the landing footer. The views links now use
docsLocalePrefix; the landing dictionaries take the docs href from
docsHrefForLocale, so locales that reuse another dictionary (French reuses
English) still link to their own docs.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: multica-agent <[email protected]>
2026-09-23 18:30:31 +08:00

119 lines
3.8 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const existingDocs = vi.hoisted(() => new Set<string>());
vi.mock("node:fs", () => ({
existsSync: vi.fn((path: string) => {
const normalized = path.replaceAll("\\", "/");
return [...existingDocs].some((suffix) => normalized.endsWith(suffix));
}),
}));
const pages = new Map<string, { url: string }>([
["en:", { url: "/" }],
["zh:", { url: "/zh" }],
["ko:", { url: "/ko" }],
["ja:", { url: "/ja" }],
["fr:", { url: "/fr" }],
["en:agents", { url: "/agents" }],
["zh:agents", { url: "/zh/agents" }],
["ko:agents", { url: "/ko/agents" }],
["ja:agents", { url: "/ja/agents" }],
["fr:agents", { url: "/fr/agents" }],
]);
vi.mock("@/lib/source", () => ({
source: {
getPage: vi.fn((slugs: string[], lang: string) => {
return pages.get(`${lang}:${slugs.join("/")}`) ?? null;
}),
},
}));
beforeEach(() => {
existingDocs.clear();
existingDocs.add("index.mdx");
existingDocs.add("index.zh.mdx");
existingDocs.add("agents.mdx");
existingDocs.add("agents.zh.mdx");
});
describe("docsAlternates", () => {
it("omits Korean hreflang when no Korean MDX file exists for the page", async () => {
const { docsAlternates } = await import("./site");
expect(docsAlternates(["agents"])).toEqual({
canonical: "https://www.multica.ai/docs/agents",
languages: {
en: "https://www.multica.ai/docs/agents",
zh: "https://www.multica.ai/docs/zh/agents",
"x-default": "https://www.multica.ai/docs/agents",
},
});
});
it("omits Korean hreflang even when source.getPage returns a page for Korean", async () => {
const { docsAlternates } = await import("./site");
expect(docsAlternates(["agents"]).languages).not.toHaveProperty("ko");
});
it("includes Korean hreflang when a real *.ko.mdx page exists", async () => {
existingDocs.add("agents.ko.mdx");
const { docsAlternates } = await import("./site");
expect(docsAlternates(["agents"])).toEqual({
canonical: "https://www.multica.ai/docs/agents",
languages: {
en: "https://www.multica.ai/docs/agents",
zh: "https://www.multica.ai/docs/zh/agents",
ko: "https://www.multica.ai/docs/ko/agents",
"x-default": "https://www.multica.ai/docs/agents",
},
});
});
it("includes Japanese hreflang when a real *.ja.mdx page exists", async () => {
existingDocs.add("agents.ja.mdx");
const { docsAlternates } = await import("./site");
expect(docsAlternates(["agents"])).toEqual({
canonical: "https://www.multica.ai/docs/agents",
languages: {
en: "https://www.multica.ai/docs/agents",
zh: "https://www.multica.ai/docs/zh/agents",
ja: "https://www.multica.ai/docs/ja/agents",
"x-default": "https://www.multica.ai/docs/agents",
},
});
});
it("includes French hreflang when a real *.fr.mdx page exists", async () => {
existingDocs.add("agents.fr.mdx");
const { docsAlternates } = await import("./site");
expect(docsAlternates(["agents"])).toEqual({
canonical: "https://www.multica.ai/docs/agents",
languages: {
en: "https://www.multica.ai/docs/agents",
zh: "https://www.multica.ai/docs/zh/agents",
fr: "https://www.multica.ai/docs/fr/agents",
"x-default": "https://www.multica.ai/docs/agents",
},
});
});
it("keeps the locale root alternates limited to real localized MDX pages", async () => {
const { docsAlternates } = await import("./site");
expect(docsAlternates([])).toEqual({
canonical: "https://www.multica.ai/docs",
languages: {
en: "https://www.multica.ai/docs",
zh: "https://www.multica.ai/docs/zh",
"x-default": "https://www.multica.ai/docs",
},
});
});
});