Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
### Fixes
- `failproofai policies --uninstall` interactive CLI selector now says "Remove Hooks" / "Choose where to remove from:" instead of "Install Hooks" / "Choose where to install:" (#236)
- README: replace the GitHub Copilot logo with the current canonical mark and add a dark-mode variant (`copilot-light.svg` + `copilot-dark.svg` via `<picture>`); the previous SVG used outdated path data with a hard-coded black fill that rendered invisibly on GitHub's dark theme (#236)
- Auto-translated MDX: stop the recurring `mintlify validate` parse error in `docs/de/dashboard.mdx` (`<Tab title="Tab „Richtlinien"">`) by adding a `sanitizeJsxAttributes` post-processor to the translation pipeline that strips stray ASCII `"` left after typographic-quote pairs (and any unmatched opening typographic quote) in JSX attribute values, and by tightening the translator system prompt to forbid ASCII `"` inside attribute values. Same regression PR #229 fixed by hand — now it can't recur. Includes the immediate file fix on `docs/de/dashboard.mdx`. (#247)

## 0.0.9 — 2026-04-28

Expand Down
55 changes: 54 additions & 1 deletion __tests__/scripts/translate-docs/mdx-translator.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// @vitest-environment node
import { describe, it, expect } from "vitest";
import { rewriteInternalLinks } from "@/scripts/translate-docs/mdx-translator";
import {
rewriteInternalLinks,
sanitizeJsxAttributes,
} from "@/scripts/translate-docs/mdx-translator";

describe("rewriteInternalLinks", () => {
it("rewrites MDX component href attributes with language prefix", () => {
Expand Down Expand Up @@ -69,3 +72,53 @@ See [config](/configuration) and [testing](/testing).
expect(result).toBe(`[link](/es/getting-started#install)`);
});
});

describe("sanitizeJsxAttributes", () => {
it("strips stray trailing ASCII quotes after a JSX attribute close", () => {
// The exact failure mode that broke `mintlify validate` on de/dashboard.mdx
const input = ` <Tab title="Tab „Richtlinien"">`;
const result = sanitizeJsxAttributes(input);
expect(result).toBe(` <Tab title="Tab Richtlinien">`);
});

it("strips trailing extras when attribute is followed by a self-close", () => {
const input = `<Tab title="Foo bar"" />`;
const result = sanitizeJsxAttributes(input);
expect(result).toBe(`<Tab title="Foo bar" />`);
});

it("strips trailing extras when attribute is followed by another attribute", () => {
const input = `<Card title="Hello"" icon="rocket">`;
const result = sanitizeJsxAttributes(input);
expect(result).toBe(`<Card title="Hello" icon="rocket">`);
});

it("leaves well-formed attributes untouched", () => {
const input = `<Tab title="Activity tab">\n<Card title="Hello" href="/foo">`;
expect(sanitizeJsxAttributes(input)).toBe(input);
});

it("preserves matched typographic quote pairs", () => {
// Japanese 「…」 has matched open/close so should NOT be stripped even if
// there were stray ASCII trailing quotes — though here there are none.
const input = `<Tab title="「ポリシー」タブ">`;
expect(sanitizeJsxAttributes(input)).toBe(input);
});

it("strips unmatched typographic opening quotes when extras are present", () => {
// German „ without a matching " (U+201D) — drop the dangling open
const input = `<Tab title="Tab „Aktivität"">`;
expect(sanitizeJsxAttributes(input)).toBe(`<Tab title="Tab Aktivität">`);
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.

it("does not mangle empty attributes", () => {
const input = `<Tag attr="">`;
expect(sanitizeJsxAttributes(input)).toBe(input);
});

it("handles multiple malformed attributes on the same line", () => {
const input = `<Tabs><Tab title="A"" /><Tab title="B"" /></Tabs>`;
const result = sanitizeJsxAttributes(input);
expect(result).toBe(`<Tabs><Tab title="A" /><Tab title="B" /></Tabs>`);
});
});
4 changes: 2 additions & 2 deletions docs/de/dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ Sie können die Sitzung als ZIP- oder JSONL-Datei über die Download-Schaltfläc
Eine Seite mit zwei Tabs zur Verwaltung von Richtlinien und Einsicht der Aktivitäten.

<Tabs>
<Tab title="Tab „Richtlinien"">
<Tab title="Tab Richtlinien">
- Einzelne Richtlinien mit einem Klick aktivieren oder deaktivieren (schreibt in `~/.failproofai/policies-config.json`)
- Eine Richtlinie aufklappen, um ihre Parameter zu konfigurieren (für Richtlinien, die `policyParams` unterstützen)
- Hooks für einen bestimmten Scope installieren oder entfernen
- Einen benutzerdefinierten Pfad für die Richtliniendatei festlegen
</Tab>
<Tab title="Tab „Aktivität"">
<Tab title="Tab Aktivität">
- Vollständige, seitenweise Übersicht aller Hook-Ereignisse, die über alle Sitzungen hinweg ausgelöst wurden
- Filtern nach Entscheidung, Ereignistyp, CLI (Claude Code / OpenAI Codex / GitHub Copilot _(Beta)_), Richtlinienname oder Sitzungs-ID
- Jede Zeile zeigt: Zeitstempel, Richtlinienname, Entscheidung, CLI-Badge (orange = Claude Code, lila = OpenAI Codex, blau = GitHub Copilot), Tool-Name, Sitzungs-ID und den Grund für deny/instruct-Entscheidungen
Expand Down
45 changes: 43 additions & 2 deletions scripts/translate-docs/mdx-translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,46 @@ import type { TranslationResult, TranslationCache } from "./types";
const __dirname = dirname(fileURLToPath(import.meta.url));
const DOCS_DIR = join(__dirname, "..", "..", "docs");

/**
* Strip stray ASCII `"` that appear right after a JSX attribute's closing
* quote — e.g. `<Tab title="Tab „Richtlinien"">`. The translator sometimes
* wraps an inner phrase in language-specific typographic quotes (`„…"`,
* `「…」`, etc.) but uses an ASCII `"` for the closing instead of the
* proper U+201D, which terminates the attribute and leaves the real
* closing `"` as a stray character that breaks `mintlify validate`.
*
* Also drops unmatched typographic opening quotes inside the same attribute
* value so the rendered title doesn't end with a dangling `„` after we strip
* the extras.
*/
export function sanitizeJsxAttributes(content: string): string {
const openings: Array<[string, string]> = [
["„", "“"], // German „ … "
["“", "”"], // English curly " … "
["«", "»"], // French « … »
["‹", "›"], // French single ‹ … ›
["「", "」"], // Japanese 「 … 」
["『", "』"], // Japanese 『 … 』
];
return content.replace(
/([a-zA-Z_-]+=")([^"\n]*)"+(?=\s|\/|>)/g,
(match, prefix: string, value: string) => {
// If the original had exactly one closing " (i.e. no extras),
// leave it alone — the regex's `"+` would still match a single
// quote, so we need to re-check the match length to be safe.
const expectedMinLen = `${prefix}${value}"`.length;
if (match.length === expectedMinLen) return match;
let cleaned = value;
for (const [open, close] of openings) {
const opens = cleaned.split(open).length - 1;
const closes = cleaned.split(close).length - 1;
if (opens > closes) cleaned = cleaned.split(open).join("");
}
return `${prefix}${cleaned}"`;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
);
}

/**
* Rewrite internal doc links to include the language prefix.
* e.g. href="/built-in-policies" -> href="/es/built-in-policies"
Expand Down Expand Up @@ -94,8 +134,9 @@ export async function translateMdxPage(
options.model,
);

// Rewrite internal links
const withLinks = rewriteInternalLinks(translated, lang);
// Strip stray quote artifacts from JSX attribute values, then rewrite links
const sanitized = sanitizeJsxAttributes(translated);
const withLinks = rewriteInternalLinks(sanitized, lang);

// Write output
mkdirSync(dirname(outputPath), { recursive: true });
Expand Down
2 changes: 1 addition & 1 deletion scripts/translate-docs/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const SYSTEM_PROMPT = `You are a professional technical documentation translator
## Rules

1. **Preserve all code blocks exactly as-is** — never translate content inside backtick-fenced code blocks (\`\`\`...\`\`\`) or inline code (\`...\`).
2. **Preserve MDX component syntax** — tags like <Card>, <CardGroup>, <CodeGroup>, <Steps>, <Step>, <Note>, <Tip>, <Tabs>, <Tab>, <Warning> must remain unchanged. Their attribute names (title, icon, href, cols) must remain in English. Only translate the text content of the \`title\` attribute and the text body between tags.
2. **Preserve MDX component syntax** — tags like <Card>, <CardGroup>, <CodeGroup>, <Steps>, <Step>, <Note>, <Tip>, <Tabs>, <Tab>, <Warning> must remain unchanged. Their attribute names (title, icon, href, cols) must remain in English. Only translate the text content of the \`title\` attribute and the text body between tags. **Never put an ASCII straight \`"\` inside a \`title="…"\` (or any JSX attribute value)** — it terminates the attribute and breaks MDX parsing. If the target language would normally wrap a word in quotation marks (e.g. German „…", Japanese 「…」), drop the inner quotes inside attribute values and rely on the surrounding tag for emphasis.
3. **Preserve YAML frontmatter keys** — only translate the string values of \`title\` and \`description\`. Keep the \`icon\` value unchanged.
4. **Preserve all URLs and paths** — never modify href values, image paths, or links.
5. **Preserve Markdown structure** — headers (#, ##), lists (-, *), tables (|), bold (**), italic (*), links ([text](url)) must keep their Markdown formatting.
Expand Down