Jev takes a query in any language, so a query written in French is a query this checks. Two things have to follow it: the words the tool prints, and the words the checks match against. Only the second can turn a clean query into a finding.
English ships. Nothing else does yet, and a locale with no file is answered in English.
Choosing a locale
--lang=<locale> on any command, or JEVLINT_LANG, then LC_ALL, LC_MESSAGES, LANG,
in that order. A tag falls back a step at a time, so fr_CA reaches fr before it reaches
English, and C or POSIX names no language and is read as none.
Adding one
A locale is one PHP file returning a map of key to pattern, named for the tag:
<?php // fr.php
return [
'report.nothing_to_report' => 'Rien à signaler.',
'skipped.jev_not_asked' => 'Jev n\'a pas été interrogé, donc {count, plural, one {# vérification n\'a} other {# vérifications n\'ont}} pas été exécutée{count, plural, one {} other {s}}.',
];
Put it in php/lang/, or anywhere and point JEVLINT_LANG_DIR at the directory. That
directory is read before the shipped one and does not replace it, so a file holding only
fr.php is a translation and English is still found behind it. A locale needs only the
keys it translates; anything absent is read from English, so a part-finished file prints
English and never a key. php/lang/en.php is the list to work from.
Patterns are ICU MessageFormat, so a plural is chosen by the locale's own CLDR rules instead of by a rule written in PHP. English takes two forms and Polish four, and the pattern names which:
'{count, plural, one {# sprawdzenie} few {# sprawdzenia} many {# sprawdzeń} other {# sprawdzenia}}'
A literal { or } is quoted with apostrophes, and a run of them is quoted together:
'{' gives one brace and '}}' gives two. Quoting them one at a time puts '' in the
middle, which ICU reads as a literal apostrophe. A lone apostrophe before an ordinary
letter is already literal, so the SDK's needs nothing.
Two tests hold the file to its job: every pattern has to parse and leave no argument unfilled, and every key the source asks for has to exist.
What a check reports about your query
A check's title, message, hint and suggest live in the catalogue, not in
php/lang/, because the catalogue is not PHP and an implementation in another language
reads the same file. They are translated in checks/lang/<locale>.json, keyed by check id:
{
"choice/no-fallback": {
"title": "Le Choice n'a pas d'option de repli",
"suggest": "Ajoutez `\"autre\": \"Tout ce que les autres options ne couvrent pas\"`."
}
}
JEVLINT_CHECKS_DIR covers this file too, in a lang/ beside catalogue.json.
The question a model check puts to Jev is never translated. It is what the check
measures, and every figure in Evidence was taken with the wording the
catalogue carries; a translated question is a different check with the same id and no
measurements. docs, trigger and the check id are left alone for the same kind of
reason. A test asserts a translation cannot reach question.
The words a check matches against
This is the half that changes findings instead of wording. Three lists are language data, not display text:
| what reads it | |
|---|---|
words.fallback_labels |
choice/no-fallback, and the type inference that treats a catch-all as evidence of a Choice |
words.catch_all_phrases |
the same checks, reading what an option's description covers |
words.grammar |
question/instruction-is-id, which subtracts grammar to see whether an instruction adds anything to its id |
A French Choice offering autre has a catch-all. Against the English list alone,
choice/no-fallback fires on it and tells you to add the option that is already there.
With words.fallback_labels translated, it clears.
The locale's list and English are both read. A query written in French can still name its options in English, and a check given only the French list would stop finding those.
Instructions are normalised with \p{L} and mb_strtolower instead of [a-z] and
strtolower, so an accented word survives instead of being cut into the pieces between
its accents.