Skip to content

Read the English before calling a translation narration - #30

Merged
tamnd merged 1 commit into
mainfrom
m5-textguard-pairs
Aug 17, 2026
Merged

tamnd merged 1 commit into
mainfrom
m5-textguard-pairs

Conversation

@tamnd

@tamnd tamnd commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Found by running tier 1 against the fleet for the first time rather than by a test.

What happened

A 28-entry batch of library/functions.po came back with all 28 translated correctly and every one was thrown away:

rejected: P06: entry 5: 'Lưu ý:' at offset 0
msgid:    Note: Unlike :func:`iter`, :func:`aiter` has no 2-argument variant.
msgstr:   Lưu ý: Không giống như ⟦1⟧, ⟦2⟧ không có biến thể 2 đối số.

P06 is one of the three rules that rejects the whole batch, so one wrong verdict cost 27 correct translations sitting beside it.

Why the guard was wrong

It was asymmetric, and nobody decided it should be. The English pattern is (?:note|please note) that, which needs the word "that", so a msgid opening Note: passes. The Vietnamese pattern is lưu ý(?: rằng)?:, which matches bare, so the faithful translation of that same string fails.

Reading the Vietnamese alone cannot tell "the model is adding a note" from "the model is translating a note", because those produce the same characters.

The fix

Narration is a property of the pair. Every phrase carries the English that would make it a translation rather than an aside:

(r"lưu ý(?: rằng)?:", r"\bnotes?\b"),
(r"dưới đây là",      r"\b(?:the following|below|as follows|here(?:'s| is| are))\b"),
(r"xin lỗi",          r"\bsorry\b"),
(r"as an ai\b",       ""),          # never a translation of anything

A phrase whose licence the msgid matches is left alone. A phrase with no licence fires regardless of what the source says, which is right for "as an AI" and for a fence, since a literal block reaches this module as a placeholder and never as three backticks.

Licences are looser than the phrases they excuse, deliberately. They ask whether the source raised the subject, not where it raised it. Vietnamese puts "lưu ý" wherever the sentence needs it, and a licence that demanded the same position would fail on every sentence the language reorders. Being loose costs a narrated entry that happens to sit beside a source mentioning notes. Being strict costs correct translations by the hundred.

Measured

Against the 1,435 human translations in upstream 3.15, which are the ceiling and by definition not narration:

Guard reads False positives
the Vietnamese alone 5
the pair 0

The five rescued:

  • Note: Unlike :func: + "iter" + ... rendered as Lưu ý: Không giống như...
  • two doctest transcripts from the tutorial cheeseshop sketch, where the English itself is -- I'm sorry, we're all out of Limburger
  • Here's an example of computing an inverse... rendered as Dưới đây là ví dụ tính nghịch đảo...
  • Here are some conventions about... rendered as Dưới đây là một số quy ước về...

Re-scoring the two real answers from the run that found this, same bytes on disk:

file before after
bugs.po 31/31 31/31
library/functions.po 0/28 28/28
total 31/59 (52.5%) 59/59 (100%)

Corpus exposure this avoids

89 of the 2,776 batches contain a string whose faithful translation trips one of these phrases, holding 2,804 entries. Every one of those batches would have failed all three rungs of the retry ladder the same way, so the 2,804 would have fallen through to attempt 3, which is one call per entry. That is 2,804 calls in place of 89, and the offending entries would still have died and stayed English.

Also

The phrase reported is now the leftmost match in the string rather than the first pattern in the list. That is what a reader expects from an offset, and it no longer depends on the order the patterns happen to be written in.

Coverage on both changed modules is 100%. Suite is 956 passing.

The first real run over tier 1 threw away a batch of 28 correct translations
because entry 5 came back as "Lưu ý: Không giống như ..." and the guard read
"lưu ý:" as the model talking. Its msgid is "Note: Unlike :func:`iter`,
:func:`aiter` has no 2-argument variant." The Vietnamese was right. The guard
was wrong, and P06 rejects the whole batch, so being wrong once cost 27
correct translations beside it.

The guard was asymmetric without anyone deciding it should be. Its English
pattern requires "note that", so a msgid opening "Note:" passes, while its
Vietnamese pattern matches "lưu ý:" bare and refuses the faithful rendering
of that same string.

Narration is now a property of the pair. Each phrase carries the English that
would make it a translation rather than an aside, and a phrase whose licence
the msgid matches is left alone. Phrases that are never a translation of
anything keep no licence and fire regardless: the documentation does not say
"as an AI", and a fence in an answer is the model presenting its work whatever
the source looked like.

Licences are looser than the phrases they excuse, on purpose. They ask only
whether the source raised the subject, not where, because Vietnamese puts
"lưu ý" wherever the sentence needs it and a licence that demanded the same
position would fail on every sentence the language reorders.

Measured against the 1 435 human translations in upstream 3.15, which are the
ceiling and by definition not narration: 5 were refused reading the Vietnamese
alone, 0 reading the pair. The rescued five include two doctest transcripts
from the tutorial cheeseshop sketch, where the English itself says "I'm
sorry", and "Here's an example" rendered as "Dưới đây là ví dụ".

Re-scoring the two answers from the run that found this, same bytes: 31 of 59
entries accepted before, 59 of 59 now.

Reporting changes with it. The phrase named is the leftmost in the string
rather than the first in the list, which is what a reader expects from an
offset and no longer depends on the order the patterns happen to be written
in.
@tamnd
tamnd merged commit 8b91b13 into main Aug 17, 2026
7 checks passed
@tamnd
tamnd deleted the m5-textguard-pairs branch August 17, 2026 15:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant