Skip to content

Fix double-escaped quote regex in translation catalogs - #64

Open
YoannChabert wants to merge 2 commits into
FriendsOfBehat:masterfrom
short-edition:fix-i18n-escaped-quote-regex
Open

Fix double-escaped quote regex in translation catalogs#64
YoannChabert wants to merge 2 commits into
FriendsOfBehat:masterfrom
short-edition:fix-i18n-escaped-quote-regex

Conversation

@YoannChabert

@YoannChabert YoannChabert commented Jul 15, 2026

Copy link
Copy Markdown

Summary

Every non-English translation catalog under i18n/*.xliff (17 files: cs, da, de, es, fr, hu, id, it, ja, nl, pl, pt, pt-br, ro, ru, sk, sv, zh-CN) has a <source> (and, where the same construct appears, <target>) entry that is a byte-for-byte mismatch with the actual regex Behat looks up as a translation key, for every step whose pattern contains an escaped-quote alternative such as (?:[^"]|\\")*. In practice this is most of MinkContext's string-argument steps: press, follow, fill in, select, check, uncheck, attach the file, should (not) see, the field should (not) contain, the element should (not) contain, etc.

Root cause

MinkContext's step patterns are declared as PHP single-quoted string literals, e.g.:

#[\Behat\Step\When('/^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/')]

PHP decodes the \\" escape sequence in a single-quoted string to a single backslash followed by a literal quote before Behat ever sees the string. That decoded string — one backslash, one quote — is what Behat's translator uses as the lookup key (msgid) against each catalog's <source>.

The i18n/*.xliff files are raw XML CDATA, not PHP string literals, so there is no such decoding step for their contents. Every affected <source> still contained the literal two-backslash sequence \\" (i.e. an actual backslash character followed by a quote character, four bytes wide: \, \, "), rather than the one-backslash sequence Behat actually looks for.

Because Symfony's translator does an exact match on the msgid, the lookup silently fails for all of these entries. The practical effect: any step that uses this escaped-quote pattern is never translated in any locale — writing a # language: xx feature file and using, say, "I press" translated into that language for these steps doesn't work, with no error, just a step definition that doesn't match.

Fix

Strip the redundant backslash so each <source> (and <target>, where it also used the pattern) exactly mirrors the string PHP actually decodes from the corresponding attribute in MinkContext.php. Applied identically across all 17 locale files — this is the same bug repeated in every catalog, not a per-language issue.

Verified byte-for-byte for a sample unit (i-press-button) via ReflectionClass/ReflectionAttribute against the live MinkContext::pressButton step definition (both hex-dumped to 43 identical bytes) rather than by inspection alone.

Test plan

  • Confirmed via hex_dump/ReflectionAttribute comparison that the fixed fr.xliff <source> for i-press-button is now byte-identical to the actual PHP-decoded regex used by MinkContext::pressButton.
  • Confirmed no remaining occurrences of the buggy double-backslash sequence in any of the 17 catalogs.
  • Added features/search.fr.feature, a French translation of the existing search.feature, exercising 4 of the affected steps. Confirmed it reproduces the regression against the pre-fix catalog (6 of 8 steps undefined) and passes fully with the fix (8/8 steps, full suite green against Wikipedia) — see comment below for details.

MinkContext's step definitions are declared as PHP single-quoted
string attributes, e.g. '/^(?:|I )press "(?P<button>(?:[^"]|\\")*)"$/'.
PHP decodes \\" to a single backslash followed by a quote before
Behat uses that decoded string as the translation msgid.

The i18n/*.xliff catalogs are raw XML CDATA, not PHP string literals,
so they go through no such decoding. Every <source> entry that used
\\" therefore held a different byte sequence (backslash-backslash-quote)
than the actual msgid Behat looks up (backslash-quote), causing an
exact-match failure in the translator for every step whose regex
contains an escaped-quote alternative — the large majority of
MinkContext's string-argument steps (press, follow, fill in, select,
check, uncheck, attach file, should (not) see, field should (not)
contain, etc.).

Because the msgid never matched, Behat silently fell back to
untranslated matching for these steps in every non-English locale,
so translated feature files could not use them at all.

Fix: strip the extra backslash so <source> exactly mirrors the
PHP-decoded regex, and apply the same correction to <target> where
present, across all 17 translation catalogs (all locales but English,
which needs no catalog). Verified byte-for-byte against the actual
attribute string via reflection for the i-press-button unit.
features/search.fr.feature mirrors search.feature but is written
entirely in French (Gherkin keywords via '# language: fr', step
text via the fr.xliff translations), exercising four of the steps
affected by the previous double-escaping bug: "je suis sur",
"je remplis ... avec ...", "je presse", "je devrais voir".

Confirmed the regression this guards against: running this feature
with a --dry-run against the pre-fix fr.xliff reports 6 of 8 steps
as undefined (every affected step except "je suis sur", whose regex
doesn't use the escaped-quote alternative). With the fix, all 8
steps are recognized, and the full suite passes against Wikipedia
the same way the existing English suite does.
@YoannChabert

YoannChabert commented Jul 15, 2026

Copy link
Copy Markdown
Author

Added `features/search.fr.feature` — a French translation of the existing `search.feature`, exercising 4 of the affected steps (`je suis sur`, `je remplis ... avec ...`, `je presse`, `je devrais voir`).

Confirmed the regression this guards against by running it with `--dry-run` against the pre-fix `fr.xliff`:

2 scénarios (2 indéfinis)
8 étapes (6 indéfinis, 2 ignorés)

(`je suis sur` is the one step here whose regex doesn't use the escaped-quote alternative, so it was already matching.)

With the fix, all 8 steps are recognized and the suite passes for real against Wikipedia, the same way the existing English `search.feature` does in CI:

2 scénarios (2 succès)
8 étapes (8 succès)

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