diff --git a/.cursor/skills/llm-seo-readiness/SKILL.md b/.cursor/skills/llm-seo-readiness/SKILL.md new file mode 100644 index 00000000..f486276c --- /dev/null +++ b/.cursor/skills/llm-seo-readiness/SKILL.md @@ -0,0 +1,50 @@ +--- +name: llm-seo-readiness +description: >- + Validates Aptos docs changes for LLM consumption (llms.txt, curated feeds, Markdown exports, HTML→Markdown) + and for SEO (metadata, sitemap, robots, structured data). Use when adding or restructuring docs, editing + llms routes or curation, changing Head/meta/OG, updating robots.txt or sitemap behavior, or when the user + mentions LLMs.txt, AI tool ingestion, crawlers, discoverability, or SEO. +--- + +# LLM and SEO readiness (Aptos docs) + +## LLM / machine-readable + +- **Frontmatter**: Every new or renamed doc needs accurate `title` and `description` in English; they surface in `/llms.txt` and `.md` exports. Mirror updates in `src/content/docs/zh/` per agent guidelines (Spanish `es/` is out of scope for agent-maintained docs). +- **Curated lists**: If a page should appear in the LLM index or early in full corpus, update `src/lib/llms-curated-ids.ts` (`LLMS_INDEX_SECTIONS`, `LLMS_SMALL_DOC_IDS`, `FULL_PRIORITY_DOC_IDS` as appropriate). Build fails if an id is missing from English non-draft docs. +- **Index copy**: User-facing explanations live in `src/content/docs/llms-txt.mdx`, `src/content/docs/build/ai.mdx`, and the Chinese `zh/` counterparts—keep URLs and feed names aligned with `src/pages/llms-index.ts`. +- **HTML → Markdown**: Shared logic is `src/lib/llms-html-sanitize.ts`. When minifying for `llms-small.txt`, collapse **spaces/tabs only**—never all `\s` (newlines must survive for fenced code and Markdown structure). +- **Route wiring**: Custom handlers are swapped in via `src/integrations/llms-txt-index.ts`; endpoint implementations live under `src/pages/llms-index.ts`, `src/endpoints/llms-small.txt.ts`, `src/endpoints/llms-full.txt.ts`. +- **Robots**: `public/robots.txt` should stay consistent with sitemap URL and, when feeds change, the commented LLMs.txt pointers at the bottom. + +## SEO + +- **Per-page metadata**: Starlight frontmatter `title` / `description` feed OG, Twitter, and schema in `src/starlight-overrides/Head.astro`—avoid empty or placeholder descriptions on public pages. +- **Sitemap**: Produced by `@astrojs/sitemap` in `astro.config.mjs`; ensure new top-level routes or major URL changes still make sense for indexing. +- **Hreflang / alternates**: Head override builds language alternates from `SUPPORTED_LANGUAGES`—new locales need config updates, not only content folders. +- **Draft / hidden content**: Do not rely on curated LLM ids or public sitemap for content that must stay off production; follow existing draft filtering in the `.md` pipeline and curation tests. + +## Verification + +After substantive changes: + +```bash +pnpm test tests/llms-curated-ids.test.ts tests/llms-html-sanitize.test.ts +pnpm lint && pnpm check +``` + +For full coverage: `pnpm test` and a production `pnpm build` when touching routes or integrations. + +## File map + +| Area | Primary locations | +|------|-------------------| +| Curation | `src/lib/llms-curated-ids.ts`, `src/lib/llms.ts` | +| llms.txt body | `src/pages/llms-index.ts` | +| Feed endpoints | `src/endpoints/llms-small.txt.ts`, `src/endpoints/llms-full.txt.ts` | +| Plugin override | `src/integrations/llms-txt-index.ts` | +| Markdown export / sanitize | `src/lib/llms-html-sanitize.ts`, `src/pages/[...slug].md.ts` | +| User docs | `src/content/docs/llms-txt.mdx`, `build/ai.mdx`, `zh/` | +| SEO head | `src/starlight-overrides/Head.astro` | +| Crawlers / sitemap hint | `public/robots.txt` | diff --git a/CLAUDE.md b/CLAUDE.md index bcae51f8..642dab2d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,11 @@ This document provides essential guidelines for AI agents working on the Aptos D ## Project Overview -This repository contains the official Aptos Developer Documentation, built using [Astro](https://astro.build/) and [Starlight](https://starlight.astro.build/). The documentation is available in multiple languages including English, Spanish (es), and Chinese (zh). +This repository contains the official Aptos Developer Documentation, built using [Astro](https://astro.build/) and [Starlight](https://starlight.astro.build/). Published languages include English and Chinese (zh). Agent workflows here do **not** include creating or updating Spanish (`es`) documentation. + +## Machine-readable documentation for agents + +Production docs are indexed for LLMs and coding agents at [https://aptos.dev/llms.txt](https://aptos.dev/llms.txt) (same content as [https://aptos.dev/.well-known/llms.txt](https://aptos.dev/.well-known/llms.txt)). For IDE access to Aptos APIs and on-chain data, use the Aptos MCP server (`npx @aptos-labs/aptos-mcp`); see the live [AI tools](https://aptos.dev/build/ai) page. ## Development Setup @@ -38,7 +42,6 @@ pnpm dev # Start development server (http://localhost:4321) src/ ├── content/ │ ├── docs/ # Main English documentation -│ │ ├── es/ # Spanish translations │ │ └── zh/ # Chinese translations │ ├── i18n/ # UI translations │ └── nav/ # Sidebar translations @@ -47,6 +50,8 @@ src/ └── assets/ # Site assets ``` +A legacy `docs/es/` tree or URLs may still exist for redirects; do not add or maintain Spanish doc content under these agent guidelines. + ## Critical Guidelines ### 1. Linting and Formatting @@ -62,18 +67,16 @@ Run these commands after making changes to ensure code quality. ### 2. Translation Requirements -**All documentation changes must be translated and verified in both Chinese and Spanish versions.** +**Documentation changes that need localization must include the Chinese (`zh`) version.** Do not add or update Spanish (`es`) translations as part of agent work. - **English docs:** `src/content/docs/` -- **Spanish docs:** `src/content/docs/es/` - **Chinese docs:** `src/content/docs/zh/` When modifying documentation: 1. Make the change in the English version first -2. Create or update the corresponding Spanish translation in `es/` -3. Create or update the corresponding Chinese translation in `zh/` -4. Ensure all internal links use the correct language prefix (e.g., `/es/network/` for Spanish) +2. Create or update the corresponding Chinese translation in `zh/` +3. Ensure Chinese internal links use the `/zh/...` prefix (see Internal Links below) ### 3. Commit Message Requirements @@ -101,7 +104,7 @@ Example: docs: add glossary entry for BlockSTM Added definition and example for BlockSTM parallel execution engine. -Updated Spanish and Chinese translations accordingly. +Updated Chinese translation accordingly. ``` ### 4. Grammar and Style @@ -121,7 +124,6 @@ Updated Spanish and Chinese translations accordingly. The glossary is located at: - **English:** `src/content/docs/network/glossary.mdx` -- **Spanish:** `src/content/docs/es/network/glossary.mdx` - **Chinese:** `src/content/docs/zh/network/glossary.mdx` When adding a new term to the glossary: @@ -160,7 +162,6 @@ sidebar: Use relative paths without the file extension: - English: `/network/blockchain/accounts` -- Spanish: `/es/network/blockchain/accounts` - Chinese: `/zh/network/blockchain/accounts` ## Content Guidelines @@ -190,21 +191,20 @@ module example::hello { 1. Create the MDX file in the appropriate directory 2. Add frontmatter with title and description -3. Create Spanish translation in `es/` subdirectory -4. Create Chinese translation in `zh/` subdirectory -5. Update sidebar configuration if needed (`astro.sidebar.ts`) -6. Run `pnpm lint` and `pnpm format` +3. Create Chinese translation in `zh/` subdirectory +4. Update sidebar configuration if needed (`astro.sidebar.ts`) +5. Run `pnpm lint` and `pnpm format` ### Updating the Glossary 1. Add the term to `src/content/docs/network/glossary.mdx` -2. Add Spanish translation to `src/content/docs/es/network/glossary.mdx` -3. Add Chinese translation to `src/content/docs/zh/network/glossary.mdx` -4. Ensure alphabetical ordering within each section -5. Include definition, examples, and links to related content +2. Add Chinese translation to `src/content/docs/zh/network/glossary.mdx` +3. Ensure alphabetical ordering within each section +4. Include definition, examples, and links to related content ## Resources +- [LLM and SEO readiness (Cursor skill)](.cursor/skills/llm-seo-readiness/SKILL.md) — checklists for `llms.txt`, curated feeds, `.md` exports, and metadata/crawlers - [Astro Documentation](https://docs.astro.build/) - [Starlight Documentation](https://starlight.astro.build/) - [MDX Documentation](https://mdxjs.com/) diff --git a/astro.config.mjs b/astro.config.mjs index 09bd0fad..3964518e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -139,6 +139,11 @@ export default defineConfig({ // Exclude autogenerated content and non-translatable static resources const excludePaths = ["/rest-api", "/move-reference", "/gas-profiling", "/scripts"]; + // Plain-text LLM exports (injected routes; no HTML page for the validator to crawl) + if (link.includes("/llms-small.txt") || link.includes("/llms-full.txt")) { + return true; + } + // Exclude specific problematic links from external move-reference content const excludeLinks = [ "https://aptos.dev/move/book/SUMMARY", @@ -151,42 +156,10 @@ export default defineConfig({ ); }, }), + // Registers /llms.txt, /llms-small.txt, /llms-full.txt routes; local handlers override output + // (see src/integrations/llms-txt-index.ts). Curation lives in src/lib/llms-curated-ids.ts + src/lib/llms.ts. starlightLlmsTxt({ rawContent: true, - description: - "Developer documentation for the Aptos blockchain — Move smart contracts, SDKs, APIs, indexer, node operations, and AI tools.", - details: [ - "For AI coding tools, install the Aptos MCP server: `npx @aptos-labs/aptos-mcp`", - "It gives your IDE direct access to Aptos APIs, on-chain data, and Move contract helpers.", - "See for setup guides (Claude Code, Cursor, and more).", - ].join("\n"), - optionalLinks: [ - { - label: "Aptos MCP Server", - url: "https://www.npmjs.com/package/@aptos-labs/aptos-mcp", - description: "MCP server for AI coding tools", - }, - { - label: "Aptos GitHub", - url: "https://github.com/aptos-labs", - description: "Official source code repositories", - }, - ], - promote: [ - "index*", - "get-started", - "network/glossary", - "build/smart-contracts*", - "network/blockchain*", - "build/sdks*", - "build/apis*", - "build/cli*", - "build/indexer*", - "build/guides*", - "build/ai*", - ], - demote: ["404"], - exclude: ["404"], }), ...(hasAlgoliaConfig ? [ @@ -217,9 +190,9 @@ export default defineConfig({ sidebar, customCss: ["./src/styles/global.css", "katex/dist/katex.min.css"], }), - // Override the starlight-llms-txt plugin's /llms.txt index with a structured - // version that lists page titles, descriptions, and per-page .md URLs. - // Must be after Starlight so our injected route takes priority. + // Override the starlight-llms-txt plugin's generated llms routes with + // local handlers so we can curate the index and tune the small/full exports. + // Must be after Starlight so our injected routes take priority. llmsTxtIndex(), sitemap({ serialize(item) { diff --git a/public/robots.txt b/public/robots.txt index 20fa4f44..4f053b7b 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,6 +1,26 @@ User-agent: Algolia Crawler Allow: / +# OpenAI search crawler +User-agent: OAI-SearchBot +Allow: / + +# OpenAI training crawler +User-agent: GPTBot +Allow: / + +# Anthropic +User-agent: ClaudeBot +Allow: / + +# Perplexity +User-agent: PerplexityBot +Allow: / + +# Google Gemini / grounding +User-agent: Google-Extended +Allow: / + User-agent: * Allow: / @@ -8,6 +28,7 @@ Sitemap: https://aptos.dev/sitemap-index.xml # LLMs.txt - Machine-readable documentation for AI tools # https://aptos.dev/llms.txt +# https://aptos.dev/llms-small.txt # https://aptos.dev/llms-full.txt # Algolia-Crawler-Verif: F9CC1F2E397396BF diff --git a/scripts/fix-i18n-links/README.md b/scripts/fix-i18n-links/README.md index cae22959..16b60734 100644 --- a/scripts/fix-i18n-links/README.md +++ b/scripts/fix-i18n-links/README.md @@ -4,7 +4,7 @@ A high-performance Rust tool for automatically fixing localized documentation li ## Problem -Localized documentation (Spanish, Chinese, etc.) often contains internal links that lack proper locale prefixes. For example, a link to `/guide/setup` in Spanish docs should be `/es/guide/setup`. +Localized documentation (Chinese, etc.) often contains internal links that lack proper locale prefixes. For example, a link to `/guide/setup` in Chinese docs should be `/zh/guide/setup`. ## Solution @@ -41,7 +41,7 @@ pnpm fix-i18n-links The tool will: -1. Auto-discover locale directories (e.g., `es/`, `zh/`) +1. Auto-discover locale directories (e.g., `zh/`) 2. Process all `.mdx` files in each locale 3. Fix internal links by adding locale prefixes 4. Report the number of files and links fixed diff --git a/scripts/fix-i18n-links/src/main.rs b/scripts/fix-i18n-links/src/main.rs index b7c18744..2eafc795 100644 --- a/scripts/fix-i18n-links/src/main.rs +++ b/scripts/fix-i18n-links/src/main.rs @@ -58,7 +58,7 @@ fn main() -> Result<(), Box> { } if locales.is_empty() { - println!("{}", "ℹ️ No locale directories found (looking for 2-letter codes like es, zh)".yellow()); + println!("{}", "ℹ️ No locale directories found (looking for 2-letter locale folders like zh)".yellow()); return Ok(()); } diff --git a/src/components/SearchFallback.astro b/src/components/SearchFallback.astro index 1375cc04..80135a62 100644 --- a/src/components/SearchFallback.astro +++ b/src/components/SearchFallback.astro @@ -19,7 +19,6 @@ fr: ["en"], // French falls back to English zh: ["en"], // Chinese falls back to English ja: ["en"], // Japanese falls back to English - es: ["en"], // Spanish falls back to English }; function enhanceSearchWithFallbacks() { diff --git a/src/components/SpanishBetaBanner.astro b/src/components/SpanishBetaBanner.astro deleted file mode 100644 index c6d9f445..00000000 --- a/src/components/SpanishBetaBanner.astro +++ /dev/null @@ -1,109 +0,0 @@ ---- -// SimpleBanner component that shows a warning for Spanish translations being in Beta -const currentLocale = Astro.currentLocale; - -// Only show the banner for Spanish locale -const shouldShowBanner = currentLocale === "es"; ---- - -{ - shouldShowBanner && ( -
- -
- ) -} - - - - diff --git a/src/content/docs/build/ai.mdx b/src/content/docs/build/ai.mdx index 4984656d..124fd24c 100644 --- a/src/content/docs/build/ai.mdx +++ b/src/content/docs/build/ai.mdx @@ -51,4 +51,8 @@ can ingest to understand the full Aptos documentation. We publish three feeds to | **llms-small.txt** | [`/llms-small.txt`](/llms-small.txt) | Condensed docs for smaller context windows | | **llms-full.txt** | [`/llms-full.txt`](/llms-full.txt) | Full documentation — all pages concatenated | +Some clients resolve `/.well-known/llms.txt`; production serves the same index via a redirect to [`/llms.txt`](/llms.txt) ([`https://aptos.dev/.well-known/llms.txt`](https://aptos.dev/.well-known/llms.txt)). + +The [`llms.txt`](/llms.txt) router also surfaces machine-readable **API** links—[`/aptos-spec.json`](/aptos-spec.json) (OpenAPI 3 JSON for the node REST API) and [`/rest-api`](/rest-api) (HTML reference)—plus **MCP**, Agent Skills, Explorer, GitHub, standards, and Indexer GraphQL `.md` links in one place. + diff --git a/src/content/docs/llms-txt.mdx b/src/content/docs/llms-txt.mdx index 381587d1..a0ea6f72 100644 --- a/src/content/docs/llms-txt.mdx +++ b/src/content/docs/llms-txt.mdx @@ -12,20 +12,27 @@ We support [LLMs.txt](https://llmstxt.org/) files for making the Aptos documenta We provide the following LLMs.txt routes to help AI tools access our documentation: -- [llms.txt](/llms.txt) - Contains a structured overview of Aptos LLMs.txt files -- [llms-small.txt](/llms-small.txt) - Provides a condensed version of the documentation, optimized for smaller context windows -- [llms-full.txt](/llms-full.txt) - Provides the full comprehensive documentation for all Aptos concepts +- [llms.txt](/llms.txt) - A compact routing file that points AI tools to the most useful Aptos docs and machine-readable exports +- [llms-small.txt](/llms-small.txt) - A curated low-token subset of the most useful Aptos documentation for IDE assistants and smaller context windows +- [llms-full.txt](/llms-full.txt) - The full rendered Aptos documentation corpus for large-context ingestion + +**Well-known URL:** Some tools look for [`https://aptos.dev/.well-known/llms.txt`](https://aptos.dev/.well-known/llms.txt). That URL permanently redirects to [`/llms.txt`](/llms.txt) (same content). + +The [`llms.txt`](/llms.txt) index also lists **structured API assets** that are not prose docs—for example the [Aptos Fullnode OpenAPI spec](/aptos-spec.json) (JSON) and the [REST API reference](/rest-api) (HTML) generated from that spec. + +It also surfaces **agent tooling** (Aptos MCP, Agent Skills, Explorer, GitHub org) and key **Markdown** deep links (AI hub, standards, Indexer GraphQL reference) so models can fetch the right surface without guessing URLs. ## Per-Page Markdown Access -Every documentation page is also available as raw Markdown by appending `.md` to the URL: +Every documentation page is also available as rendered Markdown by appending `.md` to the URL: -| Page URL | Markdown URL | -| -------------------------------------------------- | ----------------------------------------------------- | -| `https://aptos.dev/build/guides/first-transaction` | `https://aptos.dev/build/guides/first-transaction.md` | -| `https://aptos.dev/build/sdks/ts-sdk` | `https://aptos.dev/build/sdks/ts-sdk.md` | +| Page URL | Markdown URL | +| ----------------------------------------------------- | -------------------------------------------------------- | +| `https://aptos.dev/build/guides/first-transaction` | `https://aptos.dev/build/guides/first-transaction.md` | +| `https://aptos.dev/build/sdks/ts-sdk` | `https://aptos.dev/build/sdks/ts-sdk.md` | +| `https://aptos.dev/zh/build/guides/first-transaction` | `https://aptos.dev/zh/build/guides/first-transaction.md` | -This is useful for AI tools that need to fetch individual pages with minimal tokens, rather than ingesting the full documentation. The [llms.txt](/llms.txt) index lists all pages with their `.md` URLs and descriptions so your AI tool can pick exactly which pages to read. +This is useful for AI tools that need to fetch individual pages with minimal tokens, rather than ingesting the full documentation. The [llms.txt](/llms.txt) file acts as a compact router, while [llms-small.txt](/llms-small.txt) and [llms-full.txt](/llms-full.txt) provide curated and comprehensive corpus exports. ## Usage with AI Tools diff --git a/src/content/docs/zh/build/ai.mdx b/src/content/docs/zh/build/ai.mdx index 9053e1de..b5ff5795 100644 --- a/src/content/docs/zh/build/ai.mdx +++ b/src/content/docs/zh/build/ai.mdx @@ -48,4 +48,8 @@ npx skills add aptos-labs/aptos-agent-skills | **llms-small.txt** | [`/llms-small.txt`](/llms-small.txt) | 适合较小上下文窗口的压缩文档 | | **llms-full.txt** | [`/llms-full.txt`](/llms-full.txt) | 完整文档——所有页面串联在一起 | +部分客户端会解析 `/.well-known/llms.txt`;生产环境会通过重定向提供与 [`/llms.txt`](/llms.txt) 相同的索引([`https://aptos.dev/.well-known/llms.txt`](https://aptos.dev/.well-known/llms.txt))。 + +[`llms.txt`](/llms.txt) 路由还会列出机器可读的 **API** 链接——[`/aptos-spec.json`](/aptos-spec.json)(全节点 REST API 的 OpenAPI 3 JSON)以及 [`/zh/rest-api`](/zh/rest-api)(HTML 参考文档)——并在同一处汇总 **MCP**、Agent Skills、浏览器、GitHub、标准与 Indexer GraphQL 等 `.md` 链接。 + diff --git a/src/content/docs/zh/llms-txt.mdx b/src/content/docs/zh/llms-txt.mdx index 9483b785..b8ca1177 100644 --- a/src/content/docs/zh/llms-txt.mdx +++ b/src/content/docs/zh/llms-txt.mdx @@ -12,20 +12,27 @@ description: 如何让 Cursor、GitHub Copilot、ChatGPT 和 Claude 等工具理 我们提供以下 LLMs.txt 路由来帮助 AI 工具访问我们的文档: -- [llms.txt](/llms.txt) - 包含 Aptos LLMs.txt 文件的结构化概述 -- [llms-small.txt](/llms-small.txt) - 提供文档的压缩版本,针对较小的上下文窗口进行了优化 -- [llms-full.txt](/llms-full.txt) - 提供所有 Aptos 概念的完整综合文档 +- [llms.txt](/llms.txt) - 一个紧凑的路由文件,引导 AI 工具找到最重要的 Aptos 文档和机器可读导出 +- [llms-small.txt](/llms-small.txt) - 经过精选的低 token 文档子集,适用于 IDE 助手和较小的上下文窗口 +- [llms-full.txt](/llms-full.txt) - 完整的 Aptos 渲染后文档语料,适用于大上下文摄取 + +**Well-known URL:** 部分工具会请求 [`https://aptos.dev/.well-known/llms.txt`](https://aptos.dev/.well-known/llms.txt)。该地址会永久重定向到 [`/llms.txt`](/llms.txt)(内容相同)。 + +[`llms.txt`](/llms.txt) 索引中还会列出**非 Markdown 的结构化 API 资源**——例如 [Aptos 全节点 OpenAPI 规范](/aptos-spec.json)(JSON),以及本站由同一规范生成的 [REST API 参考](/zh/rest-api)(HTML)。 + +此外还会列出 **Agent 工具链**(Aptos MCP、Agent Skills、浏览器、GitHub 组织)以及重要 **Markdown** 深链(AI 总览、标准、Indexer GraphQL 参考),便于模型直接拉取正确资源。 ## 逐页 Markdown 访问 -每个文档页面也可以通过在 URL 后添加 `.md` 获取原始 Markdown 格式: +每个文档页面也可以通过在 URL 后添加 `.md` 获取渲染后的 Markdown 格式: -| 页面 URL | Markdown URL | -| -------------------------------------------------- | ----------------------------------------------------- | -| `https://aptos.dev/build/guides/first-transaction` | `https://aptos.dev/build/guides/first-transaction.md` | -| `https://aptos.dev/build/sdks/ts-sdk` | `https://aptos.dev/build/sdks/ts-sdk.md` | +| 页面 URL | Markdown URL | +| ----------------------------------------------------- | -------------------------------------------------------- | +| `https://aptos.dev/build/guides/first-transaction` | `https://aptos.dev/build/guides/first-transaction.md` | +| `https://aptos.dev/build/sdks/ts-sdk` | `https://aptos.dev/build/sdks/ts-sdk.md` | +| `https://aptos.dev/zh/build/guides/first-transaction` | `https://aptos.dev/zh/build/guides/first-transaction.md` | -这对于需要以最少 token 获取单个页面的 AI 工具非常有用,而不必摄取完整的文档。[llms.txt](/llms.txt) 索引列出了所有页面及其 `.md` URL 和描述,以便您的 AI 工具可以准确选择要阅读的页面。 +这对于需要用最少 token 获取单个页面的 AI 工具非常有用,而不必摄取完整文档。[llms.txt](/llms.txt) 作为紧凑路由文件使用,而 [llms-small.txt](/llms-small.txt) 和 [llms-full.txt](/llms-full.txt) 分别提供精选版和完整版语料导出。 ## 与 AI 工具的使用 diff --git a/src/endpoints/llms-full.txt.ts b/src/endpoints/llms-full.txt.ts new file mode 100644 index 00000000..3f264c45 --- /dev/null +++ b/src/endpoints/llms-full.txt.ts @@ -0,0 +1,18 @@ +import type { APIRoute } from "astro"; +import { cacheHeaders, generateLlmsDocument, getEnglishDocs, orderDocs } from "../lib/llms"; +import { FULL_PRIORITY_DOC_IDS } from "../lib/llms-curated-ids"; + +export const prerender = true; + +export const GET: APIRoute = async (context) => { + const docs = orderDocs(await getEnglishDocs(), FULL_PRIORITY_DOC_IDS); + + const body = await generateLlmsDocument(docs, context, { + description: "This is the full Aptos developer documentation corpus in rendered Markdown.", + }); + + return new Response(body, { + status: 200, + headers: cacheHeaders(), + }); +}; diff --git a/src/endpoints/llms-small.txt.ts b/src/endpoints/llms-small.txt.ts new file mode 100644 index 00000000..1fa569fd --- /dev/null +++ b/src/endpoints/llms-small.txt.ts @@ -0,0 +1,27 @@ +import type { APIRoute } from "astro"; +import { + cacheHeaders, + generateLlmsDocument, + getEnglishDocs, + LLMS_SMALL_DOC_IDS, + orderDocs, +} from "../lib/llms"; + +export const prerender = true; + +export const GET: APIRoute = async (context) => { + const docs = orderDocs(await getEnglishDocs(), LLMS_SMALL_DOC_IDS).filter((doc) => + LLMS_SMALL_DOC_IDS.includes(doc.id), + ); + + const body = await generateLlmsDocument(docs, context, { + minify: true, + description: + "This is the curated low-token Aptos developer documentation set for AI agents and IDE assistants.", + }); + + return new Response(body, { + status: 200, + headers: cacheHeaders(), + }); +}; diff --git a/src/integrations/llms-txt-index.ts b/src/integrations/llms-txt-index.ts index 1f1f5033..f5a0f2eb 100644 --- a/src/integrations/llms-txt-index.ts +++ b/src/integrations/llms-txt-index.ts @@ -1,23 +1,37 @@ import type { AstroIntegration, RouteOptions } from "astro"; /** - * Overrides the starlight-llms-txt plugin's /llms.txt index route with a structured - * version that lists all pages with titles, descriptions, and per-page .md URLs. + * Overrides the starlight-llms-txt plugin's generated llms routes with local handlers. * - * Uses the astro:route:setup hook to swap the plugin's entrypoint for our custom handler - * while keeping the llms-full.txt and llms-small.txt routes from the plugin untouched. + * Uses the astro:route:setup hook to swap the plugin's entrypoints for local routes that + * provide a curated llms.txt index plus custom llms-small.txt and llms-full.txt outputs. + * + * Upgrade note: this depends on `starlight-llms-txt` injecting routes whose `route.component` + * path contains `starlight-llms-txt` and ends with `/llms.txt.ts`, `/llms-small.txt.ts`, or + * `/llms-full.txt.ts`. If the package changes those filenames or stops using that path + * substring, update the matching logic here. The plugin may also register + * `/_llms-txt/[slug].txt` for custom sets; that route is not overridden. */ export function llmsTxtIndex(): AstroIntegration { + const overrides = new Map([ + ["/llms.txt.ts", "./src/pages/llms-index.ts"], + ["/llms-small.txt.ts", "./src/endpoints/llms-small.txt.ts"], + ["/llms-full.txt.ts", "./src/endpoints/llms-full.txt.ts"], + ]); + return { name: "llms-txt-index", hooks: { "astro:route:setup": ({ route }: { route: RouteOptions }) => { - // Only override the plugin's /llms.txt index route (not full/small/custom) - if ( - route.component.includes("starlight-llms-txt") && - route.component.endsWith("/llms.txt.ts") - ) { - (route as { component: string }).component = "./src/pages/llms-index.ts"; + if (!route.component.includes("starlight-llms-txt")) { + return; + } + + for (const [suffix, component] of overrides) { + if (route.component.endsWith(suffix)) { + (route as { component: string }).component = component; + return; + } } }, }, diff --git a/src/lib/llms-curated-ids.ts b/src/lib/llms-curated-ids.ts new file mode 100644 index 00000000..c9fb4f47 --- /dev/null +++ b/src/lib/llms-curated-ids.ts @@ -0,0 +1,110 @@ +/** + * Curated doc ids for /llms.txt, llms-small.txt, and llms-full.txt ordering. + * Kept free of Astro-heavy imports so tests and scripts can validate ids without booting the MDX pipeline. + * + * Maintenance: When you add prominent English landing pages (for example new AI, SDK, or guide hubs), add their + * doc id to the appropriate section below and, if they should appear early in llms-full.txt, to FULL_PRIORITY_DOC_IDS. + * `pnpm test` runs tests/llms-curated-ids.test.ts to ensure every id exists on disk and is not draft; /llms.txt also + * throws at build time if a curated id is missing from the English collection. + */ + +export interface LlmsSection { + title: string; + ids: string[]; +} + +export const LLMS_INDEX_SECTIONS: LlmsSection[] = [ + { + title: "Start Here", + ids: [ + "build/get-started", + "build/guides/first-transaction", + "build/guides/first-move-module", + "build/sdks", + "build/cli", + ], + }, + { + title: "Smart Contracts", + ids: [ + "build/smart-contracts", + "build/smart-contracts/book", + "build/smart-contracts/objects", + "build/smart-contracts/randomness", + ], + }, + { + title: "APIs And Data", + ids: [ + "build/apis", + "build/apis/fullnode-rest-api", + "build/indexer/indexer-api", + "build/indexer/indexer-api/indexer-reference", + "build/indexer/txn-stream", + ], + }, + { + title: "SDKs", + ids: ["build/sdks/ts-sdk"], + }, + { + title: "Advanced Topics", + ids: [ + "build/guides/sponsored-transactions", + "build/guides/orderless-transactions", + "build/guides/aptos-keyless", + "build/guides/transaction-management", + ], + }, + { + title: "AI Tooling", + ids: ["build/ai", "build/ai/aptos-mcp", "llms-txt"], + }, + { + title: "Nodes And Operations", + ids: [ + "network/nodes", + "network/nodes/full-node", + "network/nodes/validator-node", + "network/releases", + "network/glossary", + ], + }, +]; + +export const LLMS_SMALL_DOC_IDS = [ + "index", + ...new Set(LLMS_INDEX_SECTIONS.flatMap((section) => section.ids)), +]; + +/** Doc ids listed first in llms-full.txt before alphabetical remainder. */ +export const FULL_PRIORITY_DOC_IDS = [ + "index", + "build/get-started", + "build/guides/first-transaction", + "build/guides/first-move-module", + "build/sdks", + "build/apis", + "build/indexer/indexer-api", + "build/cli", + "build/smart-contracts", + "build/ai", + "network/nodes", + "network/glossary", + "llms-txt", +]; + +export const EXCLUDED_DOC_IDS = new Set(["contribute/components/themed-image"]); + +const LOCALE_PREFIXES = ["es/", "zh/"]; + +/** Id/path rules for English docs in the llms-full corpus (excludes draft — check frontmatter separately). */ +export function isEnglishDocId(id: string): boolean { + return ( + !LOCALE_PREFIXES.some((prefix) => id.startsWith(prefix)) && + id !== "es" && + id !== "zh" && + !id.includes("404") && + !EXCLUDED_DOC_IDS.has(id) + ); +} diff --git a/src/lib/llms-html-sanitize.ts b/src/lib/llms-html-sanitize.ts new file mode 100644 index 00000000..35de2044 --- /dev/null +++ b/src/lib/llms-html-sanitize.ts @@ -0,0 +1,38 @@ +import TurndownService from "turndown"; + +const turndown = new TurndownService({ + codeBlockStyle: "fenced", + headingStyle: "atx", +}); + +turndown.remove(["script", "style", "button"]); +turndown.keep(["table"]); + +/** Strip noisy HTML before Turndown (shared by .md exports and tests). */ +export function stripHtmlForLlmsExport(html: string, shouldMinify: boolean): string { + let sanitizedHtml = html + .replace(//gi, "") + .replace(//gi, "") + .replace(//gi, "") + .replace(//gi, "") + .replace(/]*class="[^"]*sr-only[^"]*"[^>]*>[\s\S]*?<\/span>/gi, ""); + + if (shouldMinify) { + sanitizedHtml = sanitizedHtml + .replace(/]*starlight-aside--note[^>]*>[\s\S]*?<\/aside>/gi, "") + .replace(/]*starlight-aside--tip[^>]*>[\s\S]*?<\/aside>/gi, "") + .replace(//gi, ""); + } + + return sanitizedHtml; +} + +export function htmlToLlmsMarkdown(html: string, shouldMinify = false): string { + const sanitizedHtml = stripHtmlForLlmsExport(html, shouldMinify); + let markdown = turndown.turndown(sanitizedHtml).trim(); + if (shouldMinify) { + // Collapse horizontal whitespace only; keep newlines so fenced code, lists, and headings stay valid Markdown. + markdown = markdown.replace(/[ \t]+/g, " "); + } + return markdown; +} diff --git a/src/lib/llms.ts b/src/lib/llms.ts new file mode 100644 index 00000000..891b6a17 --- /dev/null +++ b/src/lib/llms.ts @@ -0,0 +1,94 @@ +import { type CollectionEntry, getCollection, render } from "astro:content"; +import reactServer from "@astrojs/react/server.js"; +import type { APIContext } from "astro"; +import { experimental_AstroContainer } from "astro/container"; +import { isEnglishDocId } from "./llms-curated-ids"; +import { htmlToLlmsMarkdown } from "./llms-html-sanitize"; + +const CACHE_CONTROL_TTL = 60 * 60; + +const astroContainer = await experimental_AstroContainer.create({ + renderers: [{ name: "@astrojs/react", ssr: reactServer }], +}); + +export type { LlmsSection } from "./llms-curated-ids"; +export { LLMS_INDEX_SECTIONS, LLMS_SMALL_DOC_IDS } from "./llms-curated-ids"; + +export function isEnglishDoc(doc: CollectionEntry<"docs">): boolean { + return isEnglishDocId(doc.id) && !doc.data.draft; +} + +export async function getEnglishDocs() { + const docs = await getCollection("docs"); + return docs.filter(isEnglishDoc); +} + +export function orderDocs( + docs: CollectionEntry<"docs">[], + priorityIds: string[] = [], +): CollectionEntry<"docs">[] { + const docsById = new Map(docs.map((doc) => [doc.id, doc])); + const ordered: CollectionEntry<"docs">[] = []; + const seen = new Set(); + + for (const id of priorityIds) { + const doc = docsById.get(id); + if (!doc || seen.has(id)) continue; + ordered.push(doc); + seen.add(id); + } + + const remaining = docs + .filter((doc) => !seen.has(doc.id)) + .sort((a, b) => a.id.localeCompare(b.id)); + + return [...ordered, ...remaining]; +} + +export async function renderDocToMarkdown( + entry: CollectionEntry<"docs">, + context: APIContext, + shouldMinify = false, +) { + const { Content } = await render(entry); + const html = await astroContainer.renderToString(Content, context); + return htmlToLlmsMarkdown(html, shouldMinify); +} + +export async function generateLlmsDocument( + docs: CollectionEntry<"docs">[], + context: APIContext, + { + description, + minify = false, + }: { + description: string; + minify?: boolean; + }, +) { + const segments: string[] = [`${description}`]; + + for (const doc of docs) { + const docSegments = [`# ${doc.data.hero?.title || doc.data.title}`]; + const descriptionText = doc.data.hero?.tagline || doc.data.description; + if (descriptionText) { + docSegments.push(`> ${descriptionText}`); + } + docSegments.push(await renderDocToMarkdown(doc, context, minify)); + segments.push(docSegments.join("\n\n")); + } + + return segments.join("\n\n"); +} + +export function markdownUrl(siteUrl: string, docId: string) { + return `${siteUrl.replace(/\/$/, "")}/${docId}.md`; +} + +export function cacheHeaders(contentType = "text/plain; charset=utf-8") { + return { + "Content-Type": contentType, + "Cache-Control": `public, max-age=${String(CACHE_CONTROL_TTL)}`, + "X-Content-Type-Options": "nosniff", + }; +} diff --git a/src/pages/[...slug].md.ts b/src/pages/[...slug].md.ts index 34d7dea7..bfc21891 100644 --- a/src/pages/[...slug].md.ts +++ b/src/pages/[...slug].md.ts @@ -1,61 +1,39 @@ import { getCollection } from "astro:content"; -import fs from "node:fs/promises"; -import path from "node:path"; import type { APIRoute, GetStaticPaths } from "astro"; - -// Cache control settings -const CACHE_CONTROL_TTL = 60 * 60; // 1 hour for markdown files +import { cacheHeaders, renderDocToMarkdown } from "../lib/llms"; /** - * Get static paths for all documentation pages - * This generates .md endpoints for every docs page + * Per-page Markdown mirrors the HTML site for non-draft docs only (draft pages are excluded from the public site). */ export const getStaticPaths: GetStaticPaths = async () => { const docs = await getCollection("docs"); - return docs.map((doc: { id: string }) => ({ - params: { slug: doc.id }, - props: { doc }, - })); + return docs + .filter((doc) => !doc.data.draft) + .map((doc: { id: string }) => ({ + params: { slug: doc.id }, + props: { doc }, + })); }; /** - * API route handler to serve raw markdown content + * API route handler to serve rendered markdown content * Accessible by appending .md to any documentation page URL */ -export const GET: APIRoute = async ({ params }) => { - const slug = params.slug; +export const GET: APIRoute = async (context) => { + const slug = context.params.slug; + const doc = context.props?.doc; - if (!slug) { + if (!slug || !doc) { return new Response("Not found", { status: 404 }); } try { - // Construct the path to the MDX file - const mdxPath = path.resolve(process.cwd(), "src/content/docs", `${slug}.mdx`); - - // Try to read the MDX file - let content: string; - try { - content = await fs.readFile(mdxPath, "utf-8"); - } catch { - // Try with /index.mdx for directory-style routes - const indexPath = path.resolve(process.cwd(), "src/content/docs", slug, "index.mdx"); - try { - content = await fs.readFile(indexPath, "utf-8"); - } catch { - return new Response(`Markdown file not found for: ${slug}`, { status: 404 }); - } - } + const content = await renderDocToMarkdown(doc, context); - // Return the raw markdown/MDX content return new Response(content, { status: 200, - headers: { - "Content-Type": "text/markdown; charset=utf-8", - "Cache-Control": `public, max-age=${String(CACHE_CONTROL_TTL)}`, - "X-Content-Type-Options": "nosniff", - }, + headers: cacheHeaders("text/markdown; charset=utf-8"), }); } catch (error) { console.error(`Error serving markdown for ${slug}:`, error); diff --git a/src/pages/llms-index.ts b/src/pages/llms-index.ts index 7e48b2f2..001b0c6c 100644 --- a/src/pages/llms-index.ts +++ b/src/pages/llms-index.ts @@ -1,182 +1,90 @@ -import { getCollection } from "astro:content"; import type { APIRoute } from "astro"; +import { + cacheHeaders, + getEnglishDocs, + LLMS_INDEX_SECTIONS, + type LlmsSection, + markdownUrl, +} from "../lib/llms"; export const prerender = true; const SITE_URL = import.meta.env.SITE; -// Pages to exclude from the index entirely -const EXCLUDE_PAGES = new Set(["contribute/components/themed-image"]); - -// Top-level section grouping and display order -const SECTION_CONFIG: Record = { - "build/get-started": "Getting Started", - "build/guides": "Guides", - "build/sdks": "SDKs", - "build/smart-contracts": "Smart Contracts", - "build/apis": "APIs", - "build/cli": "CLI", - "build/indexer": "Indexer", - "build/ai": "AI Tools", - "build/aips": "AIPs (Aptos Improvement Proposals)", - "build/create-aptos-dapp": "Create Aptos Dapp", - "network/blockchain": "Blockchain Concepts", - "network/nodes": "Nodes", - "network/faucet": "Faucet", - "network/glossary": "Glossary", - "network/releases": "Releases", -}; - interface Doc { id: string; data: { title: string; description?: string }; } -function getSectionKey(slug: string): string { - const keys = Object.keys(SECTION_CONFIG).sort((a, b) => b.length - a.length); - for (const key of keys) { - if (slug.startsWith(key)) return key; - } - return "other"; -} - -/** - * Derive sub-section key from page path. For a page like - * "build/sdks/ts-sdk/account", the sub-section is "build/sdks/ts-sdk". - * Only creates a sub-section if there's a directory level between the - * section root and the page (i.e., depth >= section + 2 segments). - */ -function getSubSectionKey(slug: string, sectionKey: string): string | null { - const remainder = slug.slice(sectionKey.length + 1); // after "build/sdks/" - if (!remainder) return null; - const parts = remainder.split("/"); - const first = parts[0]; - if (parts.length < 2 || !first) return null; // direct child of section, no sub-section - return `${sectionKey}/${first}`; -} - function formatEntry(doc: Doc): string { - const url = `${SITE_URL}/${doc.id}.md`; + const url = markdownUrl(SITE_URL, doc.id); const desc = doc.data.description ? `: ${doc.data.description}` : ""; return `- [${doc.data.title}](${url})${desc}`; } -export const GET: APIRoute = async () => { - const docs = await getCollection("docs"); - - // Filter to English-only, exclude 404, locale roots, and excluded pages - const englishDocs = (docs as Doc[]) - .filter( - (doc) => - !doc.id.startsWith("es/") && - !doc.id.startsWith("zh/") && - doc.id !== "es" && - doc.id !== "zh" && - !doc.id.includes("404") && - !EXCLUDE_PAGES.has(doc.id), - ) - .sort((a, b) => a.id.localeCompare(b.id)); - - // Group by section - const sections = new Map(); - const topLevel: Doc[] = []; - - for (const doc of englishDocs) { - const sectionKey = getSectionKey(doc.id); - if (sectionKey === "other") { - topLevel.push(doc); - } else { - if (!sections.has(sectionKey)) sections.set(sectionKey, []); - const sectionList = sections.get(sectionKey); - if (sectionList) sectionList.push(doc); - } +function resolveSectionDocs(section: LlmsSection, docsById: Map): Doc[] { + const missing = section.ids.filter((id) => !docsById.has(id)); + if (missing.length > 0) { + throw new Error( + `llms.txt section "${section.title}" is missing ${String(missing.length)} English (non-draft) doc(s): ${missing.join(", ")}`, + ); } + return section.ids.map((id) => docsById.get(id) as Doc); +} - // Build a lookup of slug -> doc for sub-section title resolution - const docsBySlug = new Map(); - for (const doc of englishDocs) { - docsBySlug.set(doc.id, doc); - } +export const GET: APIRoute = async () => { + const englishDocs = (await getEnglishDocs()) as Doc[]; + const docsById = new Map(englishDocs.map((doc) => [doc.id, doc])); const lines: string[] = [ "# Aptos Developer Documentation", "", "> Developer documentation for the Aptos blockchain — Move smart contracts, SDKs, APIs, indexer, node operations, and AI tools.", "", - "This file lists all documentation pages with descriptions. Each page is also available as raw Markdown by appending `.md` to its URL (e.g. `https://aptos.dev/build/guides/first-transaction.md`).", + `Canonical site: ${SITE_URL}`, + `Sitemap: ${SITE_URL}/sitemap-index.xml`, "", - "## Full Documentation", + "This file is a compact router for LLMs and coding agents. It highlights the most useful Aptos docs first, while the full corpus remains available separately.", "", - `- [Complete documentation](${SITE_URL}/llms-full.txt): All pages concatenated into a single file`, - `- [Abridged documentation](${SITE_URL}/llms-small.txt): Condensed version for smaller context windows`, + "Machine-readable documentation:", + `- [Curated low-token docs](${SITE_URL}/llms-small.txt): concise Aptos reference for agents and IDE assistants.`, + `- [Full documentation corpus](${SITE_URL}/llms-full.txt): complete rendered Aptos documentation for large-context ingestion.`, + "", + "Structured API assets (non-Markdown):", + `- [Aptos Fullnode OpenAPI 3 spec](${SITE_URL}/aptos-spec.json): OpenAPI document for the node REST API (accounts, transactions, view functions, events).`, + `- [REST API reference (HTML)](${SITE_URL}/rest-api): browsable docs generated from the same spec on this site.`, + "", + "Agent tooling and canonical sources:", + "- [Aptos MCP on npm](https://www.npmjs.com/package/@aptos-labs/aptos-mcp): run `npx @aptos-labs/aptos-mcp` to give IDEs on-chain queries, REST helpers, and Move tooling.", + "- [Aptos Agent Skills](https://github.com/aptos-labs/aptos-agent-skills): installable skills for Move, TypeScript SDK, and dApp scaffolding.", + `- [AI tools hub](${markdownUrl(SITE_URL, "build/ai")}): MCP setup, Agent Skills, and LLMs.txt usage on one page (Markdown).`, + "- [Aptos Labs on GitHub](https://github.com/aptos-labs): official SDKs, tools, and sample code.", + "- [Aptos Explorer](https://explorer.aptoslabs.com/): look up accounts, transactions, validators, and network health.", + `- [Aptos standards (AIPs)](${markdownUrl(SITE_URL, "build/smart-contracts/aptos-standards")}): tokens, naming, and other Aptos Improvement Proposal standards.`, + `- [Indexer GraphQL reference](${markdownUrl(SITE_URL, "build/indexer/indexer-api/indexer-reference")}): schema-oriented reference for historical on-chain data (pairs with REST OpenAPI above).`, + "", + "Markdown access:", + "- Documentation pages are available as rendered Markdown by appending `.md` to the canonical page URL.", + `- Example: ${SITE_URL}/build/guides/first-transaction -> ${SITE_URL}/build/guides/first-transaction.md`, "", ]; - // Top-level pages - for (const doc of topLevel) { - lines.push(formatEntry(doc)); - } - if (topLevel.length > 0) lines.push(""); - - // Grouped sections with auto-derived sub-sections - for (const [sectionKey, sectionLabel] of Object.entries(SECTION_CONFIG)) { - const sectionDocs = sections.get(sectionKey); - if (!sectionDocs || sectionDocs.length === 0) continue; - - lines.push(`## ${sectionLabel}`); - lines.push(""); - - // Split into direct children and sub-sectioned pages - const directChildren: Doc[] = []; - const subSections = new Map(); - const subSectionOrder: string[] = []; - - for (const doc of sectionDocs) { - const subKey = getSubSectionKey(doc.id, sectionKey); - if (subKey) { - if (!subSections.has(subKey)) { - subSections.set(subKey, []); - subSectionOrder.push(subKey); - } - const subList = subSections.get(subKey); - if (subList) subList.push(doc); - } else { - directChildren.push(doc); - } + for (const section of LLMS_INDEX_SECTIONS) { + if (section.ids.length === 0) { + continue; } + const docs = resolveSectionDocs(section, docsById); - // Print direct children first - for (const doc of directChildren) { + lines.push(`## ${section.title}`); + lines.push(""); + for (const doc of docs) { lines.push(formatEntry(doc)); } - - // Print sub-sections - for (const subKey of subSectionOrder) { - const subDocs = subSections.get(subKey); - if (!subDocs || subDocs.length === 0) continue; - - // Use the index page's title as the sub-section header, or derive from path - const indexDoc = docsBySlug.get(subKey); - const fallback = subKey.split("/").pop() ?? subKey; - const subLabel = indexDoc ? indexDoc.data.title : fallback; - - lines.push(""); - lines.push(`### ${subLabel}`); - lines.push(""); - - for (const doc of subDocs) { - lines.push(formatEntry(doc)); - } - } - lines.push(""); } return new Response(lines.join("\n"), { status: 200, - headers: { - "Content-Type": "text/plain; charset=utf-8", - "Cache-Control": "public, max-age=3600", - }, + headers: cacheHeaders(), }); }; diff --git a/src/starlight-overrides/Head.astro b/src/starlight-overrides/Head.astro index 00b358a5..ebcc4420 100644 --- a/src/starlight-overrides/Head.astro +++ b/src/starlight-overrides/Head.astro @@ -73,9 +73,7 @@ const orgName = "Aptos Foundation"; const baseKeywords = ["Aptos", "blockchain", "web3"]; const keywordTranslations = { en: { "Move language": "Move language" }, - es: { "Move language": "Lenguaje Move" }, zh: { "Move language": "Move 语言" }, - //ja: { "Move language": "Move言語" }, }; const keywords = Object.fromEntries( @@ -93,7 +91,7 @@ const lastUpdatedDate = lastUpdated ? lastUpdated.toISOString() : new Date().toI // Generate BreadcrumbList structured data from URL path segments const basePath = Astro.site?.href.replace(/\/$/, "") || ""; -// Use a broader regex that also matches locale roots without trailing slash (e.g., /es, /zh) +// Locale roots without trailing slash (e.g., /zh) const breadcrumbLangRegex = new RegExp(`^/(${nonEnglishLangCodes.join("|")})(?=/|$)`); const pathWithoutLocale = Astro.url.pathname.replace(breadcrumbLangRegex, "") || "/"; const pathSegments = pathWithoutLocale.split("/").filter(Boolean); @@ -108,7 +106,6 @@ function formatSegmentName(segment: string): string { // Translated "Home" label for breadcrumbs const homeLabelByLang: Record = { zh: "首页", - es: "Inicio", }; const homeLabel = homeLabelByLang[lang] || "Home"; diff --git a/src/starlight-overrides/LanguageSelect.astro b/src/starlight-overrides/LanguageSelect.astro index 5d69d9bf..6f42806e 100644 --- a/src/starlight-overrides/LanguageSelect.astro +++ b/src/starlight-overrides/LanguageSelect.astro @@ -22,7 +22,7 @@ import Default from "@astrojs/starlight/components/LanguageSelect.astro"; if (e.currentTarget instanceof HTMLSelectElement) { // Get the selected locale from the pathname const pathname = e.currentTarget.value; - const localeMatch = pathname.match(/^\/(es|zh|ja)/); + const localeMatch = pathname.match(/^\/(zh)(?=\/|$)/); const locale = localeMatch ? localeMatch[1] : "en"; // Set the cookie with a 1-year expiration diff --git a/src/starlight-overrides/PageFrame.astro b/src/starlight-overrides/PageFrame.astro index 4276197f..73b6426f 100644 --- a/src/starlight-overrides/PageFrame.astro +++ b/src/starlight-overrides/PageFrame.astro @@ -1,7 +1,6 @@ --- import MobileMenuToggle from "virtual:starlight/components/MobileMenuToggle"; import Analytics from "@components/Analytics.astro"; -import SpanishBetaBanner from "@components/SpanishBetaBanner.astro"; const { hasSidebar } = Astro.locals.starlightRoute; --- @@ -20,7 +19,7 @@ const { hasSidebar } = Astro.locals.starlightRoute; ) } -
+