Language
Everything the libraries say to a user — rejection messages, tool labels in
defaultToolbarEntries(), action hints, measurement labels — comes from message tables. English
is built in. Every other language is a table you register yourself; the packages register nothing
when they are imported, so you only ship the languages you use.
Programs should not read these texts: every user-visible rejection also carries a stable code, and every edit a stable label (see Events).
German, French, Spanish, Dutch#
@vectorstream/core, @vectorstream/editor and @vectorstream/adapter each ship a German table
(coreDe/editorDe/adapterDe) and, as of this version, a French, Spanish and Dutch one
(coreFr/editorFr/adapterFr, coreEs/editorEs/adapterEs, coreNl/editorNl/adapterNl)
— all four follow exactly the same pattern, shown here for German. Register the three tables of one
language and switch the page locale — once, before anything renders text:
import { adapterDe, adapterMessages } from '@vectorstream/adapter';
import { coreDe, coreMessages, registerLocale, setLocale } from '@vectorstream/core';
import { editorDe, editorMessages } from '@vectorstream/editor';
/**
* German for every library message. The packages ship English only and register nothing on
* import: you register the tables you want, once, before anything renders text.
*/
export function useGerman(): void {
registerLocale('de', coreMessages, coreDe);
registerLocale('de', editorMessages, editorDe);
registerLocale('de', adapterMessages, adapterDe);
setLocale('de');
}
French, Spanish and Dutch work the same way — swap the locale tag ('fr'/'es'/'nl') and the
three table imports for the language's own (coreFr/editorFr/adapterFr, and so on).
French, Spanish and Dutch are machine-translated and have not been reviewed by a native
speaker. This is a deliberate choice (short draft gap 7, user decision of 12.10.2026, risk
knowingly accepted rather than waiting for a review that may never happen) — not a reason to treat
them as less complete: defineTranslation checks every one of the three the same way it checks
German, so no key, placeholder or plural form is missing or mismatched. What a compiler cannot
check is wording — if something reads oddly in one of the three, that is expected until someone who
speaks the language looks it over; corrections are welcome.
The map packages have no message tables of their own. The maps' built-in controls (zoom buttons, attribution) are the map libraries' business; the application in this repository translates them through the maps' own options.
How it works#
- One locale per page.
setLocale(tag)switches every table at once;getLocale()returns the current tag ('en'by default). Library code throws without access to an editor instance, so a per-editor language could not reach those messages. - Lookup goes from the exact tag (
'de-AT') to its base language ('de') to English. A table may therefore be partial: every key it lacks reads in English. - Texts are produced when they are created. An error's
messageand a toolbar entry'slabelare rendered in the locale current at that moment and are not rewritten later. After switching the language, rebuild what shows text — the application in this repository reloads the page. - Numbers follow the locale too:
formatNumber(1234.5)gives1,234.5in English and1.234,5in German. The measurement values of the map adapters use it. - Any well-formed BCP 47 tag is accepted; without a registered table the texts read in English
while numbers still follow the tag. A malformed tag throws a
RangeError.
Codes and parameters#
A user-visible rejection carries:
code— namespaced and stable, e.g.'editor.layerActiveHide'or'core.importNoGeometry';params— the values for the message's placeholders, plus IDs of the elements concerned;message(on an error) ordetail(on aViolation) — the rendered sentence.
formatMessage(code, params) renders any code again in the current locale — for example to
show a logged rejection in the user's language. isCodedError(e) tells whether an error carries a
code. Internal programming errors — a plain TopologyError, StructuralError, RollbackError —
are English only and have no code.
Your own translation#
A table for another language — one of the four above is already shipped, so this is for a fifth one — or your own wording for some keys:
import { registerLocale, setLocale } from '@vectorstream/core';
import { editorMessages } from '@vectorstream/editor';
/**
* Your own language for some editor texts. A table may be partial: every key it leaves out reads
* in English. The compiler checks the keys; keep the placeholders (`{name}`) of the English text.
*/
export function useItalian(): void {
registerLocale('it', editorMessages, {
toolDraw: 'Disegna area',
toolRectangle: 'Rettangolo',
layerActiveHide: 'Il livello «{name}» è attivo e non può essere nascosto.',
});
setLocale('it');
}
registerLocale(tag, messages, table) merges into what is already registered for that tag and
package; later calls override single keys. The compiler checks that every key exists in the
package's English table. For a complete table, defineTranslation(englishSource, table) also
checks that no key is missing and that each text uses the same placeholders as the English one —
the packages' German tables are built that way. A plural entry is an object of plural categories
(one, other, …) selected by the parameter n.
The message sets are exported per package: coreMessages / coreDe / coreFr / coreEs /
coreNl, editorMessages / editorDe / editorFr / editorEs / editorNl, adapterMessages /
adapterDe / adapterFr / adapterEs / adapterNl. Each has t(key, params) to render one of
its texts and code(key) for the code.
The tests of both examples check: English by default; after useGerman() the same rejection has
the same code and parameters but a German message, formatMessage renders the old English error
in German and back; numbers switch format; a partial Italian table translates the given tool labels
and leaves the rest English; 'it-CH' falls back to 'it'; and an unknown key does not compile.
API: setLocale, registerLocale,
formatMessage, defineTranslation.