English
Contributing locales
@templatical/quality ships locale-aware data sets keyed by language:
Accessibility rule messages (
src/accessibility/messages/{locale}.ts) — strings the editor shows for eacha11y.*issue.Vague-text dictionaries (
src/accessibility/dictionaries/{locale}.ts) — phrase lists used bya11y.link-vague-text,a11y.button-vague-label, anda11y.img-linked-no-context.Structure rule messages (
src/structure/messages/{locale}.ts) — strings for eachstructure.*issue.Link rule messages (
src/links/messages/{locale}.ts) — strings for eachlink.*issue.Deliverability rule messages (
src/deliverability/messages/{locale}.ts) — strings for eachdeliverability.*issue.Compatibility rule messages (
src/compatibility/messages/{locale}.ts) —messageandsuggestionfor eachcompat.*rule, plus client grouping labels.
Each set mirrors the editor's locale set. The structure, link, and deliverability linters have no equivalent of vague-text dictionaries — their rules are deterministic and locale-agnostic, only the message text needs translating.
File layout
packages/quality/src/accessibility/messages/
en.ts ← source of truth (typed implicitly)
de.ts ← annotated `typeof en`
index.ts ← exports formatMessage(), getMessages()
packages/quality/src/accessibility/dictionaries/
en.ts
de.ts
index.ts ← exports getDictionary(), normalizeForMatch()
packages/quality/src/structure/messages/
en.ts ← source of truth
de.ts ← annotated `typeof en`
index.ts ← exports formatStructureMessage(), getStructureMessages()
packages/quality/src/links/messages/
en.ts ← source of truth
de.ts ← annotated `typeof en`
index.ts ← exports formatLinkMessage(), getLinkMessages()
packages/quality/src/compatibility/messages/
en.ts
de.ts
index.ts
packages/quality/src/deliverability/messages/
en.ts ← source of truth
de.ts ← annotated `typeof en`
index.ts ← exports formatDeliverabilityMessage(), getDeliverabilityMessages()Adding a locale
You need one message file per linter and optionally a vague-text dictionary. Drop the files and they're picked up automatically — every locale registry is built at compile time via import.meta.glob, so there's no map to update.
Follow the typeof en pattern for every file. The annotation is the contract: any missing key, extra key, or wrong type fails pnpm run typecheck. Runtime parity tests verify {name} placeholder positions match across locales.
1. Accessibility rule messages
Drop accessibility/messages/<lang>.ts and translate both message and suggestion for every value, preserving {name} placeholders exactly:
ts
// accessibility/messages/pt.ts
import type en from "./en";
const pt: typeof en = {
"a11y.img-missing-alt":
{ message: "Imagem sem texto alternativo.", suggestion: "Adicione uma descrição curta ou marque a imagem como decorativa." },
"a11y.img-alt-too-long":
{ message: "Texto alternativo tem {length} caracteres.", suggestion: "Mantenha abaixo de {max}." },
// …one key per accessibility rule
};
export default pt;2. Vague-text dictionary
Drop accessibility/dictionaries/<lang>.ts:
ts
// accessibility/dictionaries/pt.ts
import type en from "./en";
const pt: typeof en = {
vagueLinkText: ["clique aqui", "aqui", "leia mais", "saiba mais"],
vagueButtonLabels: ["clique aqui", "clique", "enviar"],
linkedImageActionHints: ["compre", "leia", "veja", "baixe", "descubra"],
};
export default pt;3. Structure rule messages
Drop structure/messages/<lang>.ts:
ts
// structure/messages/pt.ts
import type en from "./en";
const pt: typeof en = {
"structure.duplicate-block-id":
{ message: "ID de bloco aparece {count} vezes na árvore.", suggestion: "Dê a cada bloco um ID único." },
"structure.section-column-mismatch":
{ message: "Seção usa layout {layout}: esperadas {expected} colunas, encontradas {actual}.", suggestion: "Corrija as colunas." },
// …one key per structure rule
};
export default pt;4. Link rule messages
Drop links/messages/<lang>.ts:
ts
// links/messages/pt.ts
import type en from "./en";
const pt: typeof en = {
"link.javascript-protocol":
{ message: "URL usa o protocolo javascript:.", suggestion: "Substitua por um link real ou remova a URL." },
"link.unsupported-protocol":
{ message: "URL usa o protocolo {protocol}, que muitos clientes não suportam.", suggestion: "Use http, https, mailto, tel ou sms." },
// …one key per link rule
};
export default pt;5. Deliverability rule messages
Add deliverability/messages/<lang>.ts and translate every key in en.ts. Preserve the {textHost}, {linkHost}, {host}, {count}, and {min} placeholders.
ts
import en from "./en";
const pt: typeof en = {
...en,
"deliverability.link-text-domain-mismatch":
{ message: "O texto mostra {textHost}, mas o link leva a {linkHost}.", suggestion: "Alinhe o texto com o destino." },
};
export default pt;That's it — SUPPORTED_MESSAGE_LOCALES, SUPPORTED_DICTIONARY_LOCALES, SUPPORTED_STRUCTURE_MESSAGE_LOCALES, SUPPORTED_LINK_MESSAGE_LOCALES, and SUPPORTED_DELIVERABILITY_MESSAGE_LOCALES reflect the new locale automatically. No registry edit, no test update.
Phrase guidelines (vague-text dictionary)
- Match, not regex. The vague-text rules normalize the anchor / button text — lowercase, collapse whitespace, strip leading/trailing non-alphanumeric characters (punctuation, arrows, decorative quotes) — then test
phrases.includes(text). So"Click here!","→ click here", and"»click here«"all collapse toclick hereand match the same dictionary entry. Don't add punctuation variants — they're redundant. Each entry is still an exact phrase match; don't try to encode regex patterns. - Lowercase only. Comparison is case-insensitive on the input side.
- Common, not exhaustive. The point is to catch the most frequent vague phrases native authors fall into. A 50-entry list does more harm than good (false positives).
- Don't translate English phrases. The dictionary is a cross-locale union — every registered locale's phrases match regardless of the active
localeoption. So yourpt.tsonly needs Portuguese phrases; Englishclick hereis already covered by the union. - No region duplicates.
de-ATresolves to the same union; one entry per language. linkedImageActionHintsis per-token, not per-phrase.a11y.img-linked-no-contexttokenizes the alt text on non-letter/digit boundaries and checks each token against the hint list. Add single action verbs in the form authors actually write them ("buy", "kaufen", "compre"), not multi-word phrases — a phrase like"jetzt kaufen"will never match because tokens are checked individually.
Match order
- Vague-text dictionary —
getDictionary(locale)returns a union of every registered locale's phrases (and action hints). Thelocaleargument is accepted for API symmetry but currently doesn't change the returned set; a vague phrase is universally vague, and an action verb in any registered language counts as link-destination context, so detection is cross-locale by design. - Rule messages —
formatMessage(locale, ruleId, params?)(accessibility),formatStructureMessage(locale, ruleId, params?)(structure),formatLinkMessage(locale, ruleId, params?)(links), andformatDeliverabilityMessage(locale, ruleId, params?)(deliverability) resolve the localized template via the matchingmessages/{locale}.tsfile and interpolate{name}placeholders. All five fall back to English when the locale isn't bundled.
6. Compatibility rule messages
Translate both fields of each { message, suggestion } entry in compatibility/messages/en.ts. Preserve {feature}, {unsupported}, {partial}, {size}, {max}, and the feature placeholders. Keep family/platform labels and the more count localized. The formatter returns { message, suggestion }; add the locale file to the existing glob layout.