Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/i18n-docs-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"leadtype": patch
---

Add first-class docs i18n support with locale-aware generation, localized source loading, per-locale search/LLM/readability artifacts, and a new `leadtype/i18n` helper surface. Locale-scoped search generation now uses URL-path document ids to align generated indexes with the source API.
45 changes: 42 additions & 3 deletions bun.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
"@biomejs/biome": "2.4.14",
"@changesets/changelog-github": "0.7.0",
"@changesets/cli": "2.31.0",
"leadtype": "workspace:*",
"@mdx-js/react": "^3.1.1",
"@typescript/native-preview": "7.0.0-dev.20260509.2",
"@vitest/coverage-v8": "4.1.5",
"husky": "^9.1.7",
"leadtype": "workspace:*",
"turbo": "^2.9.12",
"typescript": "6.0.3",
"ultracite": "7.6.5"
Expand Down
4 changes: 4 additions & 0 deletions packages/leadtype/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
"types": "./dist/fumadocs/index.d.ts",
"import": "./dist/fumadocs/index.js"
},
"./i18n": {
"types": "./dist/i18n/index.d.ts",
"import": "./dist/i18n/index.js"
},
"./remark": {
"types": "./dist/remark/index.d.ts",
"import": "./dist/remark/index.js"
Expand Down
1 change: 1 addition & 0 deletions packages/leadtype/rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const entries = {
index: "src/index.ts",
"mdx/index": "src/mdx/index.ts",
"fumadocs/index": "src/fumadocs/index.ts",
"i18n/index": "src/i18n/index.ts",
"remark/index": "src/remark/index.ts",
"convert/index": "src/convert/index.ts",
"llm/index": "src/llm/index.ts",
Expand Down
91 changes: 91 additions & 0 deletions packages/leadtype/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,97 @@ describe("leadtype CLI", () => {
);
});

it("generates locale-scoped i18n artifacts while keeping default URLs stable", async () => {
const srcDir = await createTempDir();
const outDir = await createTempDir();
const capture = createCapture();

await mkdir(path.join(srcDir, "docs"), { recursive: true });
await writeFile(
path.join(srcDir, "docs", "docs.config.ts"),
`export default {
product: {
name: "Localized Product",
summary: "Localized product summary.",
},
groups: [{ slug: "get-started", title: "Get Started" }],
i18n: {
defaultLocale: "en",
locales: ["en", "zh"],
},
};`
);
await writeMdxPage(
srcDir,
"quickstart.mdx",
'title: "Quickstart"\ndescription: "English quickstart."\ngroup: get-started',
"English body."
);
await writeMdxPage(
srcDir,
"setup.mdx",
'title: "Setup"\ndescription: "English setup."\ngroup: get-started',
"English setup."
);
await writeMdxPage(
srcDir,
"zh/quickstart.mdx",
'title: "快速开始"\ndescription: "中文快速开始。"\ngroup: get-started',
"中文正文。"
);

const code = await runCli(
["generate", "--src", srcDir, "--out", outDir, "--format", "json"],
capture.io
);

expect(code).toBe(0);
const result = JSON.parse(capture.stdout) as {
files: { i18nManifest?: string };
};
expect(result.files.i18nManifest).toBe(
path.join(outDir, "docs", "i18n-manifest.json")
);

const manifest = JSON.parse(
await readFile(path.join(outDir, "docs", "i18n-manifest.json"), "utf8")
) as {
defaultLocale: string;
artifacts: Array<{ locale: string; searchIndex: string }>;
};
expect(manifest.defaultLocale).toBe("en");
expect(manifest.artifacts).toContainEqual(
expect.objectContaining({
locale: "zh",
searchIndex: "/docs/zh/search-index.json",
})
);

const defaultSummary = await readFile(
path.join(outDir, "docs", "llms.txt"),
"utf8"
);
expect(defaultSummary).toContain("](/docs/quickstart.md)");

const zhSummary = await readFile(
path.join(outDir, "docs", "zh", "llms.txt"),
"utf8"
);
expect(zhSummary).toContain("快速开始");
expect(zhSummary).toContain("](/docs/zh/quickstart.md)");
expect(zhSummary).not.toContain("Setup");

const zhSearch = JSON.parse(
await readFile(
path.join(outDir, "docs", "zh", "search-index.json"),
"utf8"
)
) as { documents: [string, string, string, string][] };
expect(zhSearch.documents.map((entry) => entry[3])).toEqual([
"/docs/zh/quickstart",
]);
});

it("lets --name and --summary override docs config product fields", async () => {
const srcDir = await createTempDir();
const outDir = await createTempDir();
Expand Down
Loading
Loading