Add 'docs/' from commit '51dddcb3f891d60bc38645e88b2dbd515344da81'

git-subtree-dir: docs
git-subtree-mainline: 0582d1b3d4
git-subtree-split: 51dddcb3f8
This commit is contained in:
RockChinQ
2026-10-27 08:04:35 +00:00
847 changed files with 87063 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
@import "tailwindcss";
@import "fumadocs-ui/css/neutral.css";
@import "fumadocs-ui/css/preset.css";
@import "fumapress/css/preset.css";
@import "fumadocs-openapi/css/preset.css";
:root {
--color-fd-primary: #4692dd;
}
body {
font-family: "Noto Sans SC", "Noto Sans", system-ui, sans-serif;
}
/* Keep the page outline visually quiet: one rail, no stepped connectors. */
#nd-toc > div > div,
[data-toc-popover-content] div.relative.flex.flex-col:has(> a[href^="#"]) {
border-inline-start: 1px solid color-mix(in oklab, var(--color-fd-foreground) 10%, transparent);
margin-inline-start: 0.53125rem;
}
#nd-toc > div > div > a > svg,
#nd-toc > div > div > div.absolute,
[data-toc-popover-content] a[href^="#"] > svg,
[data-toc-popover-content] div.relative.flex.flex-col:has(> a[href^="#"]) > div.absolute {
display: none;
}
/* Keep every active segment on one x-axis, even when labels are indented. */
#nd-toc > div > div > a[data-active="true"]::before,
[data-toc-popover-content] a[href^="#"][data-active="true"]::before {
content: "";
position: absolute;
inset-block: -0.375rem;
inset-inline-start: 0.5rem;
width: 1px;
background: var(--color-fd-primary);
border-radius: 0;
z-index: 1;
}
/* Mintlify-style section labels: headings, not expandable folder controls. */
#nd-sidebar p.inline-flex.items-center.gap-2 {
margin-block: 1.15rem 0.35rem;
padding-inline: 0.5rem;
color: var(--color-fd-muted-foreground);
font-size: 0.6875rem;
font-weight: 650;
letter-spacing: 0.065em;
line-height: 1.25rem;
text-transform: uppercase;
}
+25
View File
@@ -0,0 +1,25 @@
"use client";
import { useEffect, type ReactNode } from "react";
import { I18nProvider, type I18nProviderProps } from "fumadocs-ui/contexts/i18n";
import { useRouter } from "fumapress/client";
import { preferredLocale, saveLocale, localeDestination, LOCALES } from "./locale-preference.mjs";
/** Reuse both stock selectors via their native onLocaleChange callback. */
export function LocalePreferenceProvider({ i18n, children }: {
i18n: I18nProviderProps;
children: ReactNode;
}) {
const router = useRouter();
useEffect(() => { preferredLocale(); }, []);
return <I18nProvider {...i18n} onLocaleChange={(locale) => {
if (!LOCALES.includes(locale)) return;
saveLocale(locale);
const alternates = Array.from(document.querySelectorAll<HTMLLinkElement>('link[rel="alternate"][hreflang]'))
.map((link) => ({ language: link.hreflang, href: link.href }));
// Fumapress adds /docs itself. Use canonical hreflang routes for API pages
// whose translated slugs differ, and a guide when no translation exists.
const target = localeDestination(locale, alternates, location.origin);
router.push(target.slice("/docs".length) + location.search + location.hash);
}}>{children}</I18nProvider>;
}
+7
View File
@@ -0,0 +1,7 @@
export const STORAGE_KEY: string;
export const LOCALES: readonly string[];
export function detectLocale(saved: unknown, languages?: readonly unknown[]): string;
export function saveLocale(locale: string, browser?: Window): void;
export function preferredLocale(browser?: Window): string;
export function localeDestination(locale: string, alternates: {language: string; href: string}[], origin: string): string;
export function enterDocs(browser?: Window): void;
+48
View File
@@ -0,0 +1,48 @@
export const STORAGE_KEY = "langbot-docs-locale";
export const LOCALES = ["en", "zh", "ja"];
export function detectLocale(saved, languages = []) {
if (LOCALES.includes(saved)) return saved;
for (const language of languages) {
if (typeof language !== "string") continue;
const base = language.toLowerCase().split("-")[0];
if (LOCALES.includes(base)) return base;
}
return "en";
}
export function saveLocale(locale, browser = window) {
if (!LOCALES.includes(locale)) return;
try { browser.localStorage.setItem(STORAGE_KEY, locale); } catch { /* Private/blocked storage. */ }
}
export function preferredLocale(browser = window) {
let saved;
try { saved = browser.localStorage.getItem(STORAGE_KEY); } catch { /* Storage may throw on access. */ }
const locale = detectLocale(saved, browser.navigator.languages?.length
? browser.navigator.languages : [browser.navigator.language]);
// Initialize from the browser, never from an explicit shared page's locale.
if (saved !== locale) saveLocale(locale, browser);
return locale;
}
export function localeDestination(locale, alternates, origin) {
if (!LOCALES.includes(locale)) locale = "en";
for (const alternate of alternates) {
if (alternate.language.toLowerCase().split("-")[0] !== locale) continue;
try {
const url = new URL(alternate.href, origin);
// Exported canonicals point to production even on previews. Only accept
// our canonical host or the current origin, and an exact locale prefix.
if ((url.origin === origin || url.origin === "https://langbot.app") &&
url.pathname.startsWith(`/docs/${locale}/`)) return url.pathname;
} catch { /* Ignore malformed alternate metadata. */ }
}
return `/docs/${locale}/insight/guide`;
}
export function enterDocs(browser = window) {
if (!/^\/docs\/?$/.test(browser.location.pathname)) return;
const locale = preferredLocale(browser);
browser.location.replace(`/docs/${locale}/insight/guide${browser.location.search}${browser.location.hash}`);
}