Guides
Keys and catalogs
A catalog is a JSON file per language and a key is the name a message answers to. The default is that you never write one: you write text, and the compiler names it for you. This page is about the times you want the key to be yours, which is most projects that already have one.
Four ways, one catalog
They mix freely in the same project and end up in the same file. Pick per message, not per app.
| You write | The key is | Reach for it when |
|---|---|---|
t`Hola {name}` | A hash of the text | Always, unless you have a reason not to. You never name anything and nothing drifts |
t.id('inbox.title')`…` | The id you gave it | You want to read the catalog, or a translator asked you to |
defineKeys({ … }) | The id you declared | The key is shared with something outside this app, and its text lives only in the catalog |
data-verbaly="inbox.title" | The attribute value | The text is in HTML you do not compile, so nothing extracts it |
The default: no key at all
Write the sentence. The compiler hashes the text into a short stable id, writes it to your source catalog and generates the types. Change the wording and you get a new key, which is the point: a translation of the old sentence never silently stands in for the new one.
Readable ids
When you want to open the catalog and recognise things, name the message where you write it with t.id. The text stays in your code and the id is what lands in the file:
The dots are a naming convention, and your catalog can spell them either way: one flat "inbox.title" key, or a title inside an inbox group. Every command reads both and writes back the shape your file already has, so nothing reformats your catalog for you. Dynamic ids (t.id(someVar)) are left untouched, and duplicate ids with different texts trigger the key-collision warning.
Keys you already own
Sometimes the key is the point: it is shared with a mobile app or a translation memory, it outlives any one component, and its text lives only in the catalog. Declare those with defineKeys and Verbaly treats them as used, so extract --prune keeps them and check verifies every one exists:
import { defineKeys } from 'virtual:verbaly';
export const BannerText = defineKeys({
title: 'alerts_banner_title',
button: 'alerts_banner_button',
});The types come from your own catalog, so a key that does not exist is an error right there, on the line that declares it, and your editor completes the ones that do. Read them anywhere with t(BannerText.title). Groups nest as deep as you like, and an empty value is skipped because empty means untranslated.
Keys in plain HTML
Text that lives in HTML nobody compiles has no call site to extract, so the key travels on the element. Write the catalog first and point at it. This site is built that way, and Plain HTML covers the whole interpreter.
Two shapes of catalog
Flat is what extract writes. Nested is what a person writes. Both are read everywhere, and every command writes back the shape the file already had, so a project never ends up half one and half the other.
Which languages you have
If you do not list locales, every JSON file in your catalogs directory is a language: drop a pt.json in and it is picked up. List locales and that list is the whole list, so a file left behind by an earlier run is ignored. npx verbaly doctor tells you which of the two is happening.