mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
feat(docs-ui): [slug]/page.tsx serves locale-translated docs with English fallback
Reads the current request locale via `getLocale()` from next-intl and prefers a translated markdown copy under `docs/i18n/<locale>/docs/<fileName>` when one exists. Falls back to the English source otherwise, so locales with partial coverage stay readable instead of 404ing. Path resolution is hardened: every read is resolved against an absolute docs root and verified to live inside it, so a stray filename in the docs index cannot escape the directory. This is the runtime counterpart to the DocsI18n removal: the locale preference set by the shared LanguageSelector in the docs layout now materially affects what content the page renders. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import matter from "gray-matter";
|
||||
import { marked } from "marked";
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
import { Metadata } from "next";
|
||||
import { getLocale } from "next-intl/server";
|
||||
import { DocCodeBlocks } from "../components/DocCodeBlocks";
|
||||
import { FeedbackWidget } from "../components/FeedbackWidget";
|
||||
import { DocsPageAnalytics } from "../components/DocsPageAnalytics";
|
||||
@@ -127,8 +128,6 @@ function addProseClasses(html: string): string {
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export async function generateMetadata({
|
||||
params,
|
||||
}: {
|
||||
@@ -168,8 +167,27 @@ export default async function DocPage({ params }: { params: Promise<{ slug: stri
|
||||
let mermaidCharts: string[] = [];
|
||||
|
||||
try {
|
||||
const docsRoot = path.join(process.cwd(), "docs");
|
||||
const fileContent = fs.readFileSync(path.join(docsRoot, item.fileName), "utf8");
|
||||
// Resolve the current request locale via next-intl. When it isn't `en`
|
||||
// and a translated copy exists under `docs/i18n/<locale>/docs/<file>`,
|
||||
// prefer the translated file. Otherwise fall back to the English source.
|
||||
// Path resolution is hardened: the docs index supplies the safe filename
|
||||
// (never user input), and we constrain the resolved path to the docs
|
||||
// root so traversal sequences in a stray fileName cannot escape it.
|
||||
const locale = await getLocale();
|
||||
const docsRoot = path.resolve(process.cwd(), "docs");
|
||||
const englishAbs = path.resolve(docsRoot, item.fileName);
|
||||
if (!englishAbs.startsWith(docsRoot + path.sep) && englishAbs !== docsRoot) {
|
||||
throw new Error("Refusing to read doc outside the docs root");
|
||||
}
|
||||
let sourceAbs = englishAbs;
|
||||
if (locale && locale !== "en") {
|
||||
const localeRoot = path.resolve(docsRoot, "i18n", locale, "docs");
|
||||
const localeAbs = path.resolve(localeRoot, item.fileName);
|
||||
if (localeAbs.startsWith(localeRoot + path.sep) && fs.existsSync(localeAbs)) {
|
||||
sourceAbs = localeAbs;
|
||||
}
|
||||
}
|
||||
const fileContent = fs.readFileSync(sourceAbs, "utf8");
|
||||
const { content, data: frontmatter } = matter(fileContent);
|
||||
pageTitle = (frontmatter.title as string) || item.title;
|
||||
version = (frontmatter.version as string) || null;
|
||||
|
||||
Reference in New Issue
Block a user