Skip to content

Blog ·

The verb four words away

Changing one Dutch pronoun rewrites verbs it never touches. A table of 84 words that exits on anything it does not know, and agreement four words away.

By Provibr

Dutch has two ways of addressing a reader. u is formal and je is informal, and the choice is not merely a matter of politeness: it changes the verb, and unlike the French or German equivalent, which form you get depends on where the verb stands. This site had drifted into using both, so we decided on je and converted everything. That is two conversions, because the Dutch text lives in two places with nothing in common: 102 markdown articles, and a message catalogue with 18841 keys. The measurement before the first conversion: 445 formal pronouns in 44 of the 102 articles, at 362 plain, 82 possessive and one reflexive. The other 58 were already informal.

If this were a language where a pronoun is only a pronoun, the whole thing would be one replace command. It is not, and the interesting failures are all downstream of that.

The table that silently skipped things

Dutch inverts the subject and the verb in a question or after a fronted element, and the verb ending disappears when it does. kunt u becomes kun je, not kunt je. Whether the ending disappears depends on the stem: zet u stays zet je, but schrijft u becomes schrijf je.

So the script needs to know, for every word that can stand in front of the pronoun, what happens to that word. The first version had a table of the verbs I had seen in a sample, and it left everything else alone. That produced Duwt u creditnota's becoming Duwt je creditnota's and Verlaagt u een tijdstempel becoming Verlaagt je een tijdstempel. Both wrong, both grammatical looking, and both entirely silent. The script reported success.

This is the same failure this codebase has been bitten by before in a completely different domain: a guard that walked the syntax tree of our own source bound one kind of variable declaration and skipped every other kind, so it reported thousands of covered translation keys while being blind to the pattern the whole panel uses. A table that silently skips an unknown form looks complete and is not.

The table now contains all 84 words that really appear in front of a formal pronoun, counted from the corpus rather than from memory, and an unknown word is exit code 1. Of the 84, 51 change nothing: conjunctions, relative pronouns and prepositions, in front of which the pronoun is simply the subject or the object. Two thirds of the table exists purely so that the third that matters cannot hide behind a default.

Three errors only reading found

Once the table was exhaustive, three sentences were still wrong after the run, and none of them is a table problem. tegen u kan liegen had become tegen je kan liegen where it should be tegen jou kunt liegen: the pronoun there is the object of a preposition and turns into the stressed form. van u uw had become van je uw, because the first pass consumed the position a later pass needed. And u zich had become je zich, where the reflexive of the informal pronoun is je je.

Every one of those requires holding a whole clause in mind. No pass over a window of two or three words can produce them, and no test that compares word counts would notice.

Agreement at a distance

The finding of the round is a sentence that is correct with the formal pronoun and wrong with the informal one, where the two words involved are four words apart.

als u die heeft ingesteld is right. als je die heeft ingesteld is wrong; it has to be hebt. The pronoun and the verb agree, and die is sitting between them, so a pass that requires the verb to follow the pronoun directly sees nothing at all. The pass that fired here was the one that changes the pronoun, and it has no reason to look further along the clause.

The check that closes this is a regular expression over the finished text: the informal pronoun, up to four intervening words, then one of exactly three verbs. Every match must be read by a person, and what has been read and approved is written down with its reason; a new match is exit code 1.

Exactly three verbs, because those are the only ones whose second person form differs between the two registers. The five obvious neighbours are identical in both, and including them produced four matches that could never be a defect. A check that produces lines nobody has to act on is a check people stop reading.

Up to four words, because that is what the corpus contains, and because an unbounded gap crosses clause boundaries. Six of the ten matches on the finished catalogue were correct precisely because the verb had another subject.

Two guards, because the masking cannot be shared

Both bodies of text now have a permanent check, and they are separate files.

The reason is that finding the running text is a different problem in each. In markdown you mask code fences, inline code and link targets. In a message catalogue you mask the argument name, the style word, the branch labels and the rich text tags of the placeholder syntax, while keeping the text inside a branch, because that text is Dutch and a pronoun in it counts. That masking is a brace walker and not a regular expression, for the same reason the placeholder guard next door is one: a branch whose text is one word looks exactly like an argument.

What the two guards do share is one rule, and it lives in one file so they cannot drift: a capital pronoun only counts at the start of a sentence.

That rule is a measurement, not a convenience. In the articles, ten bare matches survive the conversion and all ten are mid sentence: five rack units from the datacentre chapter, five the letter itself from an alphabet that deliberately omits it. In the catalogue there are 61, at 54 mid sentence, six as a label and one as a unit in a field name. All 61 were read and none is a pronoun. Writing that as a list of file names would have missed the second spelling of the same idea; writing it as a form catches both.

The case insensitive flag on that pattern was forgotten once, which made a capitalised pronoun at the start of a sentence invisible: exactly the form that appears in a heading or after a full stop. A probe test with known cases caught it immediately. A guard without a probe is a guard you are trusting rather than testing.

Two ways to be vacuously green

Verifying the catalogue conversion in a browser produced two false greens, and they are the transferable half of this round.

A page behind a rollout flag answers with an error page, and an error page contains no formal pronouns. Two panel screens are gated that way and the scan reported zero findings on both, which reads identically to a clean page. What made it visible was a lower bound on the number of characters: the gated response was 173 characters, and with the flags switched on the same two screens were 1335 and 557. A scan of an empty page and a scan of a clean page are the same number.

And the development server caches the catalogue. The first deliberately broken run, the one that is supposed to go red so you know the check works, was green, because the swapped file was only served after a restart. A red proof that is green for an unrelated reason is worse than no red proof: it certifies the check.

What we would write down

  1. A pronoun in an inflected language is not a string. Changing it rewrites words it does not touch.
  2. An exhaustive table beats a sampled one, and the difference is only visible when it exits on what it does not know.
  3. Two thirds of a good table can be no ops. They exist so the rest cannot hide behind a default.
  4. A window of two words cannot see a clause. Some errors need a reader and there is no way around it.
  5. Agreement can act at a distance. Ours was four words, and no pass in the pipeline was looking that far.
  6. Restrict a check to the forms that can actually differ. Five extra verbs meant four lines nobody could act on.
  7. A check that yields unactionable lines gets skipped, which is worse than not having it.
  8. Write down the approved matches with their reason, and fail on a new one. Silence is not a result.
  9. A gated page scans clean. Put a lower bound on the size of what you scanned.
  10. Prove the red proof. A development cache made ours green and would have certified nothing.

For clarity about what this is: the article conversion made 491 replacements across 44 files with the diff exactly 306 lines in and 306 out and nothing touched outside the Dutch source tree; the catalogue conversion made 169 replacements over 115 keys, with the key count unchanged at 18841 and in the same order, and the whole file's character delta reconciled letter by letter. Both bodies now measure zero formal pronouns, and the word invariant that compares every pair of words which is not the address form reported zero differences and was proven red three times. Not proven: one reader, no second native speaker, no accessibility measurement, and the error path text of three gated screens was never rendered in a browser.

From the platform work on Provibr. What nine catalogues do to a set of fixed controls is in sixteen thousand keys, nine languages, a transformation script that damaged three articles it was never pointed at is in a conversion script that ran too wide, and another guard that could not see the thing it was written for is in a vague plural is not a number.

Start with one host and one agent

Create an account, install the agent with a single command and connect your first Proxmox host. The trial runs for 14 days and asks for no payment details.