Guides
Studio
Your JSON catalogs are the database and git is the history. Studio is a window onto them, which is why it cannot drift from your code: it does not own anything.
What you get
It opens on what needs you. The first screen lists what is waiting, one line each: translations that break your site, messages with nothing in them, machine drafts nobody has read, keys your code calls that no catalog defines, and keys only a translation has. The first three open the table on exactly those messages; the last two have no row to open, so they name their keys instead. Beside that it shows how many of doctor’s checks pass, and the last commits that touched your catalog folder. The commits card needs a git repository; without one it is simply not there.
The search box reads your text, never the key. It looks in every language you have, not only the ones you ticked, and each result says where it matched. Command K opens it, the arrows move, enter takes you to that message in the table.
The first part of a key is its group: nav.home belongs to nav. The rail lists every group with how many messages it holds and how many still need you, and picking one narrows the table. A key with no dot has no group, and Verbaly does not invent one.
The table holds every message in your project, in as many languages as you tick. Each one shows its state: ready, missing, written by a machine and unread, or broken. You fix it in the row itself, and the check runs while you type.
Two commands run from the page. Find new text reads your code and adds what your catalogs do not have. Translate what is missing calls your provider: it shows the count and the languages before anything is spent, what comes back stays a draft until you read it, and a message you save while it runs keeps your text.
Every row has a menu. Copy the key, copy the source text, start editing without leaving the table, see the files that use that message, and see the commit that last changed it.
The commit is read from your catalogs’ history, by comparing what each message said and not the line it sat on. Adding a key rewrites the comma on the line above it, so asking git who touched a line names the wrong commit often enough to matter. When there is no answer the menu says which kind: a message you have not committed yet, one older than the history it read, or a project with no git in it.
Run it
pnpm add -D @verbaly/studioIt prints the address it is serving, the project it read and what is in it. The address carries a key, minted fresh on every start, so nothing else on your machine can reach it by guessing the port.
The command
| Flag | What it does |
|---|---|
--root <path> | Point at another project instead of the folder you are in |
--port <n> | Start somewhere other than 4747; a busy port climbs to the next free one |
--json | Print the whole state and exit, no server. This is the panel without the panel: the same object the screen will read, so you can pipe it, diff it and script it |
It never edits your source text
Your source text lives in your code, and the key comes from that text. Writing over it here would be undone by your next extract, so Studio refuses it. It also refuses a key your source catalog does not have: accepting one would invent a message no line of your code asks for.
A translation you save runs the same two checks your build runs. A lost {name}, a lost tag or a plural missing its catch-all case is refused, with the reason. Saving is also accepting: once a person writes it, it stops counting as a machine draft.
Your file keeps the order you gave it. A save rewrites the one line it changed, never the whole catalog. A group you ordered by hand stays in that order and a new key lands at its end; a group that was already alphabetical keeps sorting. The diff you review is the edit you made.
The addresses
Every one of them calls the same function the CLI calls, so there is no second implementation to keep in step. Every answer is JSON, errors included.
| Address | What it gives you |
|---|---|
GET /api/state | The whole project in one answer: your settings, every catalog, where each message is used, the coverage, what verbaly check says, the machine drafts, the triage and anything it could not read |
GET /api/health | The same diagnosis verbaly doctor prints |
GET /api/commit/:key | The commit that last changed that message, or which of the three reasons there is none |
PUT /api/message/:locale/:key | Write one translation and stop counting it as a draft |
POST /api/approve | Accept machine drafts: a list of keys, or the whole language at once |
POST /api/extract | Read your code and add what your catalogs do not have. It answers inside the request, because it is local and costs nothing |
GET /api/translate | The bill: how many messages and in which languages, without calling your provider. Looking at the cost cannot start the spending, which is why it is its own address |
POST /api/translate | Start the run and get back a job. One at a time, because two runs over the same catalogs is a race over the same files |
GET /api/job/:id | How that run is going, to ask once a second |
GET /api/job | The run in progress, or nothing. A reloaded page has lost the id, and a job you cannot ask about again is a stream with extra steps |
The state comes in one piece, not in pages. Even a catalog of a few thousand messages across three languages answers in a fraction of a second on your own machine.
Nothing it reads can take it down
A catalog it cannot read, a drafts file it cannot read, a source file that will not compile, a folder with no catalogs in it: each one is reported by name, and everything else still loads. Studio is what you open to fix those files, so it has to open.
Which machine translations to read first
Adding a language writes hundreds of drafts at once. Reading them one by one is how people end up skipping the review altogether, so Studio ranks them instead of queuing them. Your build already caught the structure; what is left is meaning. These signals do not score a translation, they point at the few worth your eyes.
Tick a single language and the table becomes that language. A bar above it says how many messages it holds, that they are already on your site, and that your build check passes. Three chips split them into everything, worth a look, and nothing odd. Every flagged row carries its reason beside it: which link changed, which number moved. Tick a second language and the comparison table is back, because a reason belongs to a language, not to a row.
| Signal | What it means |
|---|---|
divergent | A language you already reviewed wrote this same source text two different ways, so the source itself is ambiguous |
collision | The same doubt seen in the result, when no reviewed language can settle it |
echo | Still word for word the source, while every language with an opinion did translate it |
digits | A number the source has came back changed, ignoring the comma or dot a language writes differently |
url | A link changed |
code | What is inside a code span changed |
The signals were tuned against real translations, not invented at a desk. Two of them are gone, because everything they flagged turned out to be correct text. What is left is a short list, not a queue.
It writes files, so it defends itself
Any page open in your browser can talk to a server running on your machine, so three defences are on and cannot be turned off.
- It listens only on your own machine, never on the network.
- It refuses a request that claims to come from anywhere else, which is what stops another site from pointing its own name at your machine.
- The key it mints at startup is compared whole, so it cannot be guessed one character at a time.
Requests are capped, and the full path of your project is stripped out of every error before the browser sees it.