From 7b967da50594b494de5af00d818922ddbf771ce7 Mon Sep 17 00:00:00 2001 From: "Sam (automated drift fix)" Date: Thu, 9 Jul 2026 09:37:12 +0300 Subject: [PATCH] docs(sdk): mirror SDK reference into every locale with prefixed paths Emit unique per-locale page paths for the SDK reference and copy the English mdx into each non-default locale directory. Prevents duplicate page records in llms-full.txt and fixes the language switcher falling back to the Documentation tab instead of the in-locale SDK page. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../copy-to-local-docs.js | 137 +++++++++--------- 1 file changed, 71 insertions(+), 66 deletions(-) diff --git a/scripts/mintlify-post-processing/copy-to-local-docs.js b/scripts/mintlify-post-processing/copy-to-local-docs.js index f6c4a96..5d68a64 100644 --- a/scripts/mintlify-post-processing/copy-to-local-docs.js +++ b/scripts/mintlify-post-processing/copy-to-local-docs.js @@ -107,85 +107,69 @@ function updateDocsJson(repoDir, sdkFiles) { const docsContent = fs.readFileSync(docsJsonPath, "utf8"); const docs = JSON.parse(docsContent); - // Build the new SDK Reference groups using the new path structure + // Build the SDK Reference groups. `prefix` is "" for the default (English) + // language and "/" for every other locale. The generated reference + // mdx is English-only, but each locale gets its OWN prefixed page paths + // (e.g. es/developers/references/sdk/docs/...) pointing at English-content + // copies written into that locale's directory. UNIQUE per-locale paths are + // essential: pointing multiple locales at the same un-prefixed English path + // creates duplicate page records and makes Mintlify emit each SDK page N times + // in llms-full.txt. They also make the language switcher resolve to the + // in-locale SDK page instead of falling back to the Documentation tab. const basePath = SDK_DOCS_TARGET_PATH; - const groupMap = new Map(); // group name -> pages array - const addToGroup = (groupName, pages) => { - if (!groupName || pages.length === 0) return; - if (!groupMap.has(groupName)) { - groupMap.set(groupName, []); - } - groupMap.get(groupName).push(...pages); - }; - - if (sdkFiles.functions?.length > 0 && categoryMap.functions) { - addToGroup( - categoryMap.functions, - sdkFiles.functions.map((file) => `${basePath}/functions/${file}`) - ); - } - - if (sdkFiles.interfaces?.length > 0 && categoryMap.interfaces) { - addToGroup( - categoryMap.interfaces, - sdkFiles.interfaces.map((file) => `${basePath}/interfaces/${file}`) - ); - } - - if (sdkFiles.classes?.length > 0 && categoryMap.classes) { - addToGroup( - categoryMap.classes, - sdkFiles.classes.map((file) => `${basePath}/classes/${file}`) - ); - } - - if (sdkFiles["type-aliases"]?.length > 0 && categoryMap["type-aliases"]) { - addToGroup( - categoryMap["type-aliases"], - sdkFiles["type-aliases"].map((file) => `${basePath}/type-aliases/${file}`) - ); - } - - // Convert map to array of nested groups for SDK Reference - const sdkReferencePages = Array.from(groupMap.entries()).map( - ([groupName, pages]) => ({ - group: groupName, + const buildSdkReferencePages = (prefix) => { + const groupMap = new Map(); // group name -> pages array + const addToGroup = (groupName, pages) => { + if (!groupName || pages.length === 0) return; + if (!groupMap.has(groupName)) groupMap.set(groupName, []); + groupMap.get(groupName).push(...pages); + }; + const p = (kind, file) => `${prefix}${basePath}/${kind}/${file}`; + + if (sdkFiles.functions?.length > 0 && categoryMap.functions) + addToGroup(categoryMap.functions, sdkFiles.functions.map((f) => p("functions", f))); + if (sdkFiles.interfaces?.length > 0 && categoryMap.interfaces) + addToGroup(categoryMap.interfaces, sdkFiles.interfaces.map((f) => p("interfaces", f))); + if (sdkFiles.classes?.length > 0 && categoryMap.classes) + addToGroup(categoryMap.classes, sdkFiles.classes.map((f) => p("classes", f))); + if (sdkFiles["type-aliases"]?.length > 0 && categoryMap["type-aliases"]) + addToGroup(categoryMap["type-aliases"], sdkFiles["type-aliases"].map((f) => p("type-aliases", f))); + + return Array.from(groupMap.entries()).map(([group, pages]) => ({ + group, expanded: true, - pages: pages.sort(), // Sort pages alphabetically within each group - }) - ); - - console.debug( - `SDK Reference pages: ${JSON.stringify(sdkReferencePages, null, 2)}` - ); + pages: pages.sort(), + })); + }; // docs.json supports three navigation shapes we've seen in the wild: // 1. top-level tabs (legacy) navigation.tabs // 2. top-level tabs with dropdowns/anchors navigation.tabs[].dropdowns | .anchors // 3. i18n layout (current) navigation.languages[].tabs[]... - // The SDK reference mdx files are English-only, so for every locale we point its - // SDK Reference group at the same English paths. This is what the docs site - // effectively shows today anyway. - // - // We locate the target group by content (any group whose pages reference - // `/sdk/docs/`) rather than by tab/group name, because the surrounding labels - // are translated per locale while the "SDK" dropdown id and the page paths - // stay stable. Preserves the existing translated group label. - const tabsContainers = - docs.navigation.languages?.map((l) => l.tabs).filter(Boolean) ?? - [docs.navigation.tabs].filter(Boolean); - - if (tabsContainers.length === 0) { + // We iterate per language so we know each one's locale prefix, and only touch + // the SDK Reference group WITHIN that language (matched by its own prefixed + // sdk/docs paths). The translated group/subgroup labels are preserved. + const languageEntries = docs.navigation.languages + ? docs.navigation.languages.map((l) => ({ + prefix: l.default ? "" : `${l.language}/`, + tabs: l.tabs, + })) + : [{ prefix: "", tabs: docs.navigation.tabs }]; + + if (languageEntries.every((e) => !e.tabs)) { console.error("Could not find navigation.tabs or navigation.languages in docs.json"); process.exit(1); } - const groupReferencesSdkDocs = (group) => - JSON.stringify(group).includes(`${basePath}/`); - let updatedCount = 0; - for (const tabs of tabsContainers) { + for (const { prefix, tabs } of languageEntries) { + if (!Array.isArray(tabs)) continue; + const localePathPrefix = `${prefix}${basePath}/`; + const groupReferencesSdkDocs = (group) => + JSON.stringify(group).includes(localePathPrefix); + const sdkReferencePages = buildSdkReferencePages(prefix); + for (const tab of tabs) { const sdkAnchor = tab.dropdowns?.find((d) => d.dropdown === "SDK") ?? @@ -282,6 +266,27 @@ function main() { fs.rmSync(readmePath, { force: true }); } + // Mirror the English reference into every non-default locale directory as + // English-content copies (see reference-i18n.json, mode "english-copy"). + // The switcher needs a real page at each locale-prefixed path; the content + // stays English so there is no translation drift on regeneration. Mintlify + // translation must be disabled for these paths (dashboard exclusion). + const docsForLocales = JSON.parse(fs.readFileSync(docsJsonPath, "utf8")); + const locales = (docsForLocales.navigation?.languages ?? []) + .filter((l) => !l.default) + .map((l) => l.language); + for (const locale of locales) { + const localeTarget = path.join(target, locale, SDK_DOCS_TARGET_PATH); + fs.rmSync(localeTarget, { recursive: true, force: true }); + fs.mkdirSync(localeTarget, { recursive: true }); + fs.cpSync(DOCS_SOURCE_PATH, localeTarget, { recursive: true }); + const localeReadme = path.join(localeTarget, "README.mdx"); + if (fs.existsSync(localeReadme)) fs.rmSync(localeReadme, { force: true }); + } + if (locales.length > 0) { + console.log(`Mirrored SDK reference into ${locales.length} locale(s): ${locales.join(", ")}`); + } + // Scan the sdk-docs directory const sdkFiles = scanSdkDocs(sdkDocsTarget); console.debug(`SDK files: ${JSON.stringify(sdkFiles, null, 2)}`);