Skip to content

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 writeThe key isReach for it when
t`Hola {name}`A hash of the textAlways, unless you have a reason not to. You never name anything and nothing drifts
t.id('inbox.title')`…`The id you gave itYou want to read the catalog, or a translator asked you to
defineKeys({ … })The id you declaredThe key is shared with something outside this app, and its text lives only in the catalog
data-verbaly="inbox.title"The attribute valueThe 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.

t`Hola {name}`;

// locales/es.json
"eYJLZELq": "Hola {name}"

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:

t.id('inbox.title')`Hello {name}`;
// extracted under "inbox.title" and rewritten to:
t('inbox.title', { name });

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:

texts.ts
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.

<h1 data-verbaly="inbox.title">Inbox</h1>

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.

// flat
{ "inbox.title": "Bandeja" }

// nested, the same key
{ "inbox": { "title": "Bandeja" } }

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.

to moveEnterto open
Copied to clipboard