From 138c58b9cbc40a7030c081a8798dc0f53d582ef2 Mon Sep 17 00:00:00 2001 From: ecency Date: Mon, 6 Jul 2026 19:40:31 +0000 Subject: [PATCH 1/2] fix(publish): require GFM adjacency for translate table detection --- apps/web/src/api/translation.ts | 18 +++++++++++------- .../src/specs/api/translate-markdown.spec.ts | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/web/src/api/translation.ts b/apps/web/src/api/translation.ts index a46ac0f590..bfb340e61d 100644 --- a/apps/web/src/api/translation.ts +++ b/apps/web/src/api/translation.ts @@ -263,13 +263,17 @@ 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 (the GFM shape). + // Cell-safe translation is out of scope, so the whole block is passed + // through; requiring adjacency 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("|") ); }; diff --git a/apps/web/src/specs/api/translate-markdown.spec.ts b/apps/web/src/specs/api/translate-markdown.spec.ts index f377f602b6..456b598e16 100644 --- a/apps/web/src/specs/api/translate-markdown.spec.ts +++ b/apps/web/src/specs/api/translate-markdown.spec.ts @@ -277,4 +277,18 @@ 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(); + }); }); From 3f8733bd798316a3cc776f7ab82537528edd78c5 Mon Sep 17 00:00:00 2001 From: ecency Date: Mon, 6 Jul 2026 19:51:42 +0000 Subject: [PATCH 2/2] fix(publish): match table cell counts in translate table detection --- apps/web/src/api/translation.ts | 22 ++++++++++++++----- .../src/specs/api/translate-markdown.spec.ts | 16 ++++++++++++++ 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/apps/web/src/api/translation.ts b/apps/web/src/api/translation.ts index bfb340e61d..2181bbf6ef 100644 --- a/apps/web/src/api/translation.ts +++ b/apps/web/src/api/translation.ts @@ -263,20 +263,32 @@ const isSkippableBlock = (block: string): boolean => { ) { return true; } - // Tables: a |---| separator row directly below a pipe row (the GFM shape). - // Cell-safe translation is out of scope, so the whole block is passed - // through; requiring adjacency keeps prose that merely contains pipe - // characters translatable. + // 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("|") + lines[i - 1].includes("|") && + 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; diff --git a/apps/web/src/specs/api/translate-markdown.spec.ts b/apps/web/src/specs/api/translate-markdown.spec.ts index 456b598e16..400cd10252 100644 --- a/apps/web/src/specs/api/translate-markdown.spec.ts +++ b/apps/web/src/specs/api/translate-markdown.spec.ts @@ -291,4 +291,20 @@ it("still skips a real GFM table (separator directly under the header row)", asy 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(); + }); });