From 164287f0569a271d073dbffc9e8e0657cf00ca0b Mon Sep 17 00:00:00 2001 From: Mason Date: Fri, 10 Apr 2026 00:01:17 +0800 Subject: [PATCH] docs-i18n: avoid ambiguous body-only wrapper unwrap (#63808) * docs-i18n: avoid ambiguous body-only wrapper unwrap * docs: clarify targeted testing tip * changelog: include docs-i18n follow-up thanks --- CHANGELOG.md | 2 +- docs/help/testing.md | 1 + scripts/docs-i18n/doc_chunked_raw.go | 11 +++++++++-- scripts/docs-i18n/doc_mode_test.go | 24 ++++++++++++++++++------ 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da615a23640..9b5da49a4ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes - macOS/Talk: add an experimental local MLX speech provider for Talk Mode, with explicit provider selection, local utterance playback, interruption handling, and system-voice fallback. (#63539) Thanks @ImLukeF. -- Docs i18n: chunk raw doc translation, reject truncated tagged outputs, and recover from terminated Pi translation sessions without changing the default `openai/gpt-5.4` path. (#62969) Thanks @hxy91819. +- Docs i18n: chunk raw doc translation, reject truncated tagged outputs, avoid ambiguous body-only wrapper unwrapping, and recover from terminated Pi translation sessions without changing the default `openai/gpt-5.4` path. (#62969, #63808) Thanks @hxy91819. ### Fixes diff --git a/docs/help/testing.md b/docs/help/testing.md index b2f4e49fcda..511c1a88fdd 100644 --- a/docs/help/testing.md +++ b/docs/help/testing.md @@ -26,6 +26,7 @@ Most days: - Faster local full-suite run on a roomy machine: `pnpm test:max` - Direct Vitest watch loop: `pnpm test:watch` - Direct file targeting now routes extension/channel paths too: `pnpm test extensions/discord/src/monitor/message-handler.preflight.test.ts` +- Prefer targeted runs first when you are iterating on a single failure. - Docker-backed QA site: `pnpm qa:lab:up` When you touch tests or want extra confidence: diff --git a/scripts/docs-i18n/doc_chunked_raw.go b/scripts/docs-i18n/doc_chunked_raw.go index bb14da5524f..07a4e4669b0 100644 --- a/scripts/docs-i18n/doc_chunked_raw.go +++ b/scripts/docs-i18n/doc_chunked_raw.go @@ -231,14 +231,21 @@ func sanitizeDocChunkProtocolWrappers(source, translated string) string { return body } } - body, ok := stripBodyOnlyWrapper(trimmedTranslated) + body, ok := stripBodyOnlyWrapper(source, trimmedTranslated) if !ok || strings.TrimSpace(body) == "" { return translated } return body } -func stripBodyOnlyWrapper(text string) (string, bool) { +func stripBodyOnlyWrapper(source, text string) (string, bool) { + sourceLower := strings.ToLower(source) + // When the source itself documents tokens, a bare body-only payload is + // ambiguous: the trailing can be literal translated content instead of + // a real wrapper close. Keep it for validation/retry instead of truncating. + if strings.Contains(sourceLower, strings.ToLower(bodyTagStart)) || strings.Contains(sourceLower, strings.ToLower(bodyTagEnd)) { + return "", false + } lower := strings.ToLower(text) bodyStartLower := strings.ToLower(bodyTagStart) bodyEndLower := strings.ToLower(bodyTagEnd) diff --git a/scripts/docs-i18n/doc_mode_test.go b/scripts/docs-i18n/doc_mode_test.go index 19cacba72ca..db2b14f6f12 100644 --- a/scripts/docs-i18n/doc_mode_test.go +++ b/scripts/docs-i18n/doc_mode_test.go @@ -512,18 +512,15 @@ func TestTranslateDocBodyChunkedStripsUppercaseBodyWrapper(t *testing.T) { } } -func TestSanitizeDocChunkProtocolWrappersStripsTopLevelWrapperEvenWhenSourceMentionsBodyTag(t *testing.T) { +func TestSanitizeDocChunkProtocolWrappersKeepsBodyOnlyWrapperWhenSourceMentionsBodyTag(t *testing.T) { t.Parallel() source := "Use `` and `` in examples, but keep the paragraph text plain.\n" translated := "\nTranslated paragraph.\n\n" got := sanitizeDocChunkProtocolWrappers(source, translated) - if strings.Contains(got, "") || strings.Contains(got, "") { - t.Fatalf("expected top-level wrapper to be stripped, got %q", got) - } - if strings.TrimSpace(got) != "Translated paragraph." { - t.Fatalf("unexpected sanitized body %q", got) + if got != translated { + t.Fatalf("expected ambiguous body-only wrapper to remain unchanged for retry\nwant:\n%s\ngot:\n%s", translated, got) } } @@ -539,6 +536,21 @@ func TestSanitizeDocChunkProtocolWrappersKeepsLegitimateTopLevelBodyBlock(t *tes } } +func TestSanitizeDocChunkProtocolWrappersStripsBodyOnlyWrapperWhenSourceHasNoBodyTokens(t *testing.T) { + t.Parallel() + + source := "Regular paragraph.\n" + translated := "\nTranslated paragraph.\n\n" + + got := sanitizeDocChunkProtocolWrappers(source, translated) + if strings.Contains(got, "") || strings.Contains(got, "") { + t.Fatalf("expected body-only wrapper to be stripped, got %q", got) + } + if strings.TrimSpace(got) != "Translated paragraph." { + t.Fatalf("unexpected sanitized body %q", got) + } +} + func TestSanitizeDocChunkProtocolWrappersKeepsAmbiguousTaggedWrapperForRetry(t *testing.T) { t.Parallel()