untranslated() asks what share of a translation's words are Latin. WORDS matched runs of letters, and Han, Kana, Thai, Lao, Khmer and Myanmar write without separators, so a whole sentence came back as one token. Two Latin product names inside it carried that token's latin_share past a half, which made the ratio 1.0 and condemned a sound translation.
Measured on a Japanese rendering of "Runs Playwright in Docker with stable browser settings": one token, latin_share 0.516, ratio 1.00 against a threshold of 0.9. The six-word floor removed in 27d59f4bd4 had hidden this, because a separator-less script never reaches six words; dropping it as vacuous missed that it was the only guard those scripts had.
WORDS now takes one character of such a script as one word, with the single-character branch first so the run branch cannot swallow the sentence again. Ten real entries against both patterns: the old one errs on two, the new one on none, and every true positive survives, including Cyrillic welds like CI Деbugging and an English answer in a Japanese catalog.
The ranges are codepoints because a formatter rewrites an escape into the character it names, and the resulting literal blob cannot be reviewed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
27d59f4bd4 caught a non-Latin target answered in Latin letters. Its
mirror was missing, so the model answering a language it cannot write
with one it can passed every criterion:
ch 'AI Services' -> '人工智能服务' Chamorro, answered in Han
tr 'Sales' -> 'إدارة'
ae (76 entries) -> Arabic, Persian, Hebrew, Chinese
Chamorro writes in the Latin script and lives on Guam; the code 'ch'
reads as Chinese to a model that does not know the language, which is
how the whole catalog came back in Mandarin.
Measured over the 121 Latin-script catalogs before the rule was
written: 79 entries condemned, 76 of them in ae and 3 in tr. All three
Turkish ones were read individually and none is a false positive; two
are an Arabic fragment with a markdown rule and one is English plus
Bengali repeated four times.
Its own criterion rather than an arm of untranslated(): the catalogs
record the reason on the entry, and 'answered in another script' is a
different diagnosis from 'came back untranslated'. The two together
cover the script dimension for all 184 languages.
What it still cannot see: Spanish served as Corsican. Telling two
Latin-script languages apart needs a detector this repository does not
have, so that class stays open.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
untranslated() is the one criterion that knows a catalog's script, and it
never fired. It returned False below LATIN_FLOOR = 6 words, while the
catalogs are mostly one- to four-word UI labels, so the guard sat above
its own corpus.
Measured across the 62 non-Latin catalogs: the floor condemns 0 entries.
Without it, 536, concentrated in the languages the model cannot write:
av 99 as 92 ba 87 be 84 ab 84 am 56 bh 13
Every entry in the low-count tail was read before the floor was removed,
because that tail is where a false positive would live. There is none:
ko 'Applications' -> a refusal sentence from the model
zh 'Updates & Package Mgmt' -> the string plus a version changelog
fa 'Cloud' -> 'Cloud' and a markdown rule
ab 'Communication' -> 'Comunicacion'
A word count was the wrong instrument. reason() consults this only after
an exact echo is ruled out, so for a non-Latin target, Latin text that
DIFFERS from the source cannot be a term the language keeps in English.
That distinction is what an earlier attempt at floor=2 lacked, which is
why it condemned 4053 entries including legitimate ones.
Scope, stated because the numbers invite a larger reading: this guards
the 62 non-Latin catalogs only. For the ~122 Latin-script languages a
wrong-language answer still passes, and catching those needs a detector
this repository does not have.
LATIN_FLOOR and its re-export are deleted rather than left at 1; nothing
else consumed them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The gateway answered two sources with "<source>\n---\n<rendering>" and the
whole answer was stored, so twenty languages shipped the English text as
their own translation and the release gate stayed green. Every criterion
passed it: echo compares for equality and the answer is longer, the markup
comparison counts only []{}`*() so a rule of hyphens is invisible to it, and
the rendering the server appended keeps the spans, the names and the length.
echoes() reports the same "echo" criterion when the translation contains its
whole source, under the floor the equality case already uses: a short source
may legitimately survive inside a longer rendering, a sentence may not.
reason() is the single source of truth for both ends, so the client now drops
such an answer on arrival and the gate reports one that got in earlier; make
i18n-prune clears those.
Swept every core catalog: exactly the 21 damaged entries are reported, all as
echo, and nothing else in the corpus. The old predicate returns no reason at
all for the same input.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>