Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Lints

The build script and mf2 check run the same checks over the messages. Each has a name, which a report prints in brackets and mf2.toml’s [lints] uses to raise or lower it:

  • error: reported, and the build (and mf2 check) fails;
  • warn: reported, and the build goes on (mf2 check --deny-warnings fails on it);
  • allow: not reported.

Any lint can be raised. Some state a rule the rest of the build relies on and cannot be lowered: each section below gives the default and the lowest level mf2.toml may set.

The examples show a message in the source language (en) and its translation (fr), each as it stands in its locales/<tag>/*.mf2 file.

Errors by default

extra-id

Default error; the lowest mf2.toml may set it is error.

A translation has an id the source language does not. Nothing in the application can ask for it, and usually it is a message renamed or removed in the source and left behind in a translation.

en   (no message "mystery")
fr   mystery = Quoi ?

Fix: delete it from the translation, or add it to the source language if the application needs it.

undeclared-variable

Default error; the lowest mf2.toml may set it is error.

A translation uses a variable its source message does not have. A call site passes the source’s arguments, so the variable would never have a value.

en   greeting = Hello, {$name}!
fr   greeting = Bonjour, {$user} !

Fix: use the source’s variable ({$name}). A language that needs more input than the source shows, a grammatical gender for one, gets it by the source declaring it with .input, even where the source’s own text does not use it; the call site then passes it for every language.

undeclared-markup

Default error; the lowest mf2.toml may set it is error.

A translation uses markup its source message does not. The application maps only the source’s markup names to elements or styles.

en   close = Press {#kbd}Esc{/kbd} to close
fr   close = Appuyez sur {#kbd}{#b}Échap{/b}{/kbd} pour fermer

Fix: remove it from the translation, or add it to the source and the call site.

dropped-markup

Default error; the lowest mf2.toml may set it is allow.

A translation leaves out markup its source message has. A link that is gone from the French sentence is gone for French readers, and nothing at run time says so.

en   terms = Accept our {#link}terms{/link}.
fr   terms = Acceptez nos conditions.

Fix: keep the markup around the words that carry it (Acceptez nos {#link}conditions{/link}.). One variant of a .match may leave it out as long as another keeps it. A corpus that drops emphasis on purpose may lower it to warn.

dynamic-select

Default error; the lowest mf2.toml may set it is allow.

A number’s select option (plural, ordinal or exact) comes from a variable, so the build cannot tell which rules the message selects by. The catalog then has to carry both plural rule sets, and the message still reports a bad option when it runs.

en   place = .input {$n :integer select=$how} .match $n one {{…}} * {{…}}

Fix: write the value (select=ordinal), or make two messages, one for each kind of selection.

bad-option-value

Default error; the lowest mf2.toml may set it is allow.

An option that MF2 defines is given a literal value it cannot take. At run time the function would report a bad option and format without it.

en   price = It costs {$amount :currency currency=EUR minimumFractionDigits=lots}

Fix: a value the option takes (minimumFractionDigits=2).

gated-function

Default error; the lowest mf2.toml may set it is error.

A message calls a function whose feature of mf2 is off: :percent, :currency and :unit need fn-number; :datetime, :date and :time need fn-datetime. A translation can never add formatting code to the application by itself.

en   price = It costs {$amount :currency currency=EUR}

with mf2 = { …, features = ["ssr"] }. Fix: turn the feature on (features = ["ssr", "fn-number"], see Features of mf2), or leave the function out of the message. mf2 check reads the features cargo resolves, or --features when it is given.

do-not-translate

Default error; the lowest mf2.toml may set it is allow.

A message the source marks @do-not-translate, a brand or a language’s own name, differs in a translation.

en   @do-not-translate
     brand = Example
fr   brand = Exemple

Fix: delete it from the translation (the source’s text is shown), or copy it exactly.

duplicate-id

Default error; the lowest mf2.toml may set it is error.

One language defines an id twice, in one file or two, so one of them would be dropped without a word.

en   save = Save
     save = Store

Fix: rename or delete one.

locale-mismatch

Default error; the lowest mf2.toml may set it is allow.

A file’s @locale header names another language than the directory it sits in. It is nearly always a copy that was never finished.

locales/fr/main.mf2   @locale de

Fix: make the header name the directory’s language (@locale fr). A corpus that keeps one language’s files under another tag on purpose may lower it.

unknown-function

Default error; the lowest mf2.toml may set it is allow.

A message calls a function that is neither one of MF2’s nor listed under [functions].

en   mood = Today: {$mood :app:emoji}

Fix: correct the name if it is a typo, or register the application’s function under [functions]. Lowered, the call stays in the catalog and the message shows MF2’s fallback for an unknown function when it runs.

Warnings by default

missing-translation

Default warn; the lowest mf2.toml may set it is allow.

A language lacks an id the source language has. The report names the first ten ids of each language, and what the catalog carries in their place ([catalog] missing).

en   save = Save
fr   (no message "save")

Fix: translate it. mf2 stats counts what each language lacks, and mf2 pseudo shows untranslated text in the page. A message marked @do-not-translate is never missing. Raise it to error to keep a release from shipping with gaps.

neutral-numbers

Default warn; the lowest mf2.toml may set it is allow.

mf2’s fn-number feature is off and a placeholder can receive a number: the number would be written with neutral symbols (1234.5), not the language’s separators, grouping or digits. The build cannot see what a call site passes, so a placeholder that only ever receives text raises it too.

en   files = {$count} files

Fix: turn on fn-number; or, if these placeholders only receive text, neutral-numbers = "allow" under [lints].

unpaired-markup

Default warn; the lowest mf2.toml may set it is allow.

Markup opened and not closed, or closed and not opened.

en   close = Press {#kbd}Esc to close

Fix: close it ({#kbd}Esc{/kbd}), or remove the stray tag.

missing-plural-category

Default warn; the lowest mf2.toml may set it is allow.

A plural .match does not have a variant for every plural category of the translation’s own language. The catch-all * covers it, usually with the wrong grammar.

fr   visits = .input {$count :integer} .match $count one {{…}} * {{…}}

French also has many (for a million and more). Fix: add the variant (many {{…}}). A language’s categories are CLDR’s, and they differ from the source’s: Polish has one, few, many and other.

non-nfc-source

Default warn; the lowest mf2.toml may set it is allow.

Text that is not in Unicode Normalization Form C: an é typed as e followed by a combining accent, say. It looks the same, but compares, searches and sorts differently from the composed letter.

Fix: save the file normalized to NFC (most editors and translation tools have the setting).

dropped-placeholder

Default warn; the lowest mf2.toml may set it is allow.

A translation never uses a variable its source message shows.

en   greeting = Hello, {$name}!
fr   greeting = Bonjour !

Fix: put it back (Bonjour, {$name} !). A variable used anywhere in the translation counts, so a plural’s one variant may say “a message” without {$count} while another variant shows it.

unknown-option

Default warn; the lowest mf2.toml may set it is allow.

A built-in function is given an option it does not define. MF2 ignores unknown options, so the message formats as if it were not there.

en   due = Due {$when :datetime dateStyle=long}

dateStyle is the browser’s Intl name; MF2’s is dateLength. Fix: the function’s own option (dateLength=long).

unused-id

Default warn; the lowest mf2.toml may set it is allow.

A message that no tr! in the application’s sources names. It is raised only by mf2 check --src DIR, which reads the sources; the build script does not look.

en   old-banner = Welcome to the beta!

The ids the generated code uses itself count as used: the languages’ names, language.<tag>, which the locale switcher and Locale::name() show. The warning points at the line that defines the id.

Fix: for an id nothing uses, delete it from every language, once nothing will use it again.

suspicious-bidi

Default warn; the lowest mf2.toml may set it is allow.

A bidirectional isolate character (U+2066 to U+2068) opened in literal text and never closed with U+2069, or closed and never opened. The rest of the line, and sometimes of the page, is laid out in the wrong direction.

Fix: close it, or remove it: a placeholder is isolated when it is formatted, so a message rarely needs these characters by hand.

dynamic-currency

Default warn; the lowest mf2.toml may set it is allow.

A :currency whose currency option is a variable, and [locale_data] currencies is not a list, so the catalog carries the data of every currency CLDR has.

en   price = It costs {$amount :currency currency=$code}

Fix: list the codes the variable can hold under [locale_data] currencies (currencies = ["EUR", "USD"]): the catalog carries only those, and the warning stops. Or name the currency (currency=EUR), or accept the size and lower it to allow.

dynamic-unit

Default warn; the lowest mf2.toml may set it is allow.

The same for a :unit whose unit option is a variable, and [locale_data] units is not a list: the catalog carries every unit.

en   distance = {$value :unit unit=$how}

Fix: list the units the variable can hold under [locale_data] units, name the unit (unit=kilometer), or accept the size and lower it.

nonstandard-name

Default warn; the lowest mf2.toml may set it is allow.

A variable, option, function, markup or attribute name that is not an ordinary identifier: it uses a character the Unicode security guidelines advise against in identifiers, or mixes scripts. MF2 accepts it, but it can look identical to another name.

en   greeting = Hello, {$nаme}!

The а above is Cyrillic, so $nаme is not the $name a call site passes. Fix: spell the name in one script, with letters and digits.