Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
30 changes: 23 additions & 7 deletions apps/web/src/api/translation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,32 @@ const isSkippableBlock = (block: string): boolean => {
) {
return true;
}
// Tables: a pipe row plus a |---| separator row. Cell-safe translation is
// out of scope, so the whole block is passed through.
return (
lines.some((line) => line.includes("|")) &&
lines.some(
(line) => line.includes("|") && line.includes("-") && TABLE_SEPARATOR_LINE.test(line)
)
// Tables: a |---| separator row directly below a pipe row with the SAME cell
// count (the GFM shape). Cell-safe translation is out of scope, so the whole
// block is passed through; requiring adjacency plus matching column counts
// keeps prose that merely contains pipe characters translatable.
return lines.some(
(line, i) =>
i > 0 &&
line.includes("|") &&
line.includes("-") &&
TABLE_SEPARATOR_LINE.test(line) &&
lines[i - 1].includes("|") &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Matching Counts Still Skip

When adjacent prose has the same pipe-delimited count as a separator-looking line, this still treats the whole block as a table. For example, Valores: min | max | defecto\n-- | -- | -- gives both lines a count of 3, so translateMarkdown skips the block instead of translating the prose. The count check fixes mismatched examples, but matching-count prose still produces the silent untranslated output this change is meant to prevent.

Context Used: CLAUDE.md (source)

Fix in Claude Code

countTableCells(lines[i - 1]) === countTableCells(line)
);
};

const countTableCells = (line: string): number => {
let trimmed = line.trim();
if (trimmed.startsWith("|")) {
trimmed = trimmed.slice(1);
}
if (trimmed.endsWith("|")) {
trimmed = trimmed.slice(0, -1);
}
return trimmed.split("|").length;
};

interface TranslateRequest {
text: string;
apply: (translated: string) => void;
Expand Down
30 changes: 30 additions & 0 deletions apps/web/src/specs/api/translate-markdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,4 +277,34 @@ it("preserves leading indentation on marker-less continuation lines", async () =
).rejects.toThrow("translate-cancelled");
expect(calls).toBe(1);
});
it("still skips a real GFM table (separator directly under the header row)", async () => {
const table = "| a | b |\n|---|---|\n| 1 | 2 |";
const result = await translateMarkdown(`Intro.\n\n${table}`, "es", "en");

expect(result).toBe(`\u00abIntro.\u00bb\n\n${table}`);
});

it("translates prose containing pipes and a stray separator-like line", async () => {
const block = "El valor |x| es absoluto\nnota al margen\n|---|";
const result = await translateMarkdown(block, "es", "en");

expect(result).toContain("\u00ab");
expect(post).toHaveBeenCalled();
});

it("translates pipe prose directly above a separator when cell counts differ", async () => {
const block = "El valor |x| es absoluto\n|---|";
const result = await translateMarkdown(block, "es", "en");

expect(result).toContain("\u00ab");
expect(post).toHaveBeenCalled();
});

it("still skips a borderless GFM table (header without outer pipes)", async () => {
const table = "a | b\n--- | ---\n1 | 2";
const result = await translateMarkdown(table, "es", "en");

expect(result).toBe(table);
expect(post).not.toHaveBeenCalled();
});
});
Loading