Internationalisation
Every string Astrolabe shows comes from a message catalog: no UI text is hard-coded and issues are codes, never sentences. The UI and the content languages are Polish and English. Both catalogs of every package are always on, whatever options you pass; your own catalogs change the wording on top of them.
Two language settings
Section titled “Two language settings”| Setting | Where | Decides |
|---|---|---|
| UI locale | locale on <astl-player> and <astl-editor>, locale in createAuthoring, the locale argument of engine.describe |
buttons, verdict words, counters, announcements, issue messages, the authoring kit |
| Content language | lang on <astl-doc> (required), or on any element inside |
grading (decimal comma, case rules, diacritics), text direction, screen reader voice |
They never mix: a Polish teacher can play an English exercise with Polish buttons. The player and the editor pick
their UI locale from locale, else their own lang attribute, else the browser languages, and match it against the
locales they have catalogs for, falling back to English. Matching is by language: pl-PL uses the pl catalogs and
en-GB the en ones.
The content language does real work:
- Number gaps accept a decimal comma in a Polish document:
0,5is correct foranswer="0.5", and spaces may group digits (1 234). An English document takes0.5and1,234.5. - Text gaps lower-case with the content language, and compare diacritics strictly by default: in Polish,
zolwis notżółw. dirdefaults fromlang(ltrfor Polish and English); an explicitdirwins.- Text in the other language goes in
<span lang="...">, so screen readers switch voices.
Catalogs
Section titled “Catalogs”A catalog is the messages of one namespace in one locale:
interface MessageCatalog { readonly namespace: string; // e.g. 'astl.player' readonly locale: string; // 'en' or 'pl' readonly messages: Readonly<Record<string, string>>; // key -> ICU MessageFormat 1}| Namespace | Package | Keys |
|---|---|---|
astl.player |
@astrolabe/player (playerMessages) |
player chrome and words shared by all elements (verdict.*) |
astl.engine |
@astrolabe/engine (engineMessages) |
document issue codes |
astl.editor |
@astrolabe/editor (editorMessages) |
the authoring kit and the surface of <astl-editor> |
astl.gap, astl.choice, ... |
each component (messages/ui/<locale>.json, messages/<locale>.json) |
ui.* for students in the first, the only strings its element loads; its issue codes, editor.* and template.* for authors in the second |
astl.style.playful, astl.style.notebook |
@astrolabe/tokens |
the label and description of each built-in look |
Every component page in the reference lists all its keys in both locales.
ICU MessageFormat 1
Section titled “ICU MessageFormat 1”Messages use a dependency-free subset of ICU MessageFormat 1, implemented in @astrolabe/core:
| Syntax | Example |
|---|---|
| argument | Gap {n} |
| number | {max, number}, {share, number, percent}, {n, number, integer} |
plural, with # and exact matches |
{count, plural, =0 {No checks left} one {# check left} other {# checks left}} |
| plural with an offset | {count, plural, offset:1 one {...} other {...}} |
| ordinal | {n, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} |
| select | {state, select, done {answered} partial {partly answered} other {not answered}} |
| quoting | '' is an apostrophe; '{' is a literal brace |
Dates and times are not supported on purpose: hosts format them and pass strings.
Plurals
Section titled “Plurals”A plural message must cover every plural category of its locale. English needs one and other; Polish needs
one, few, many and other:
{ "checks.left": "{count, plural, =0 {Nie masz już sprawdzeń} one {Zostało # sprawdzenie} few {Zostały # sprawdzenia} many {Zostało # sprawdzeń} other {Zostało # sprawdzenia}}"}pluralCategories(locale) from @astrolabe/core lists the categories a locale needs (from Intl.PluralRules).
Your own wording
Section titled “Your own wording”To reword a message, pass a catalog with just the keys you change, in the namespace and locale they belong to. Your keys win over the built-in ones; every other message stays.
import type { MessageCatalog } from 'astrolabe/player';
const myWording: MessageCatalog[] = [ { namespace: 'astl.player', locale: 'en', messages: { 'action.check': 'Check my answers' } }, { namespace: 'astl.player', locale: 'pl', messages: { 'action.check': 'Sprawdź odpowiedzi' } },];Register them where the strings are shown. catalogs is an option of all three:
definePlayer({ catalogs: myWording }); // astrolabe/player: the player and its elementscreateEngine({ secret, catalogs: myWording }); // astrolabe/server or astrolabe/engine: engine.describe(issue, 'pl')defineEditor({ catalogs: myWording }); // astrolabe/editor: the kit and its TipTap surfaceThe player inside the editor's preview is a player like any other: it takes the catalogs given to definePlayer on
that page. Reword both locales, so Polish and English readers get the same message. checkCatalog(catalog) from
@astrolabe/core (also exported by the astrolabe entries) returns the problems of a catalog: messages that do not
parse, and plurals that miss a category of their locale:
import { checkCatalog } from 'astrolabe/player';
for (const catalog of myWording) expect(checkCatalog(catalog)).toEqual([]);With the @astrolabe/* packages, the same catalogs option is on definePlayer, createEngine and
createAuthoring. Catalogs passed later override keys of the same namespace and locale.
Rules for message authors
Section titled “Rules for message authors”- Keys are semantic (
action.check), never the English text. - One message per sentence the reader sees; no concatenation of translated fragments.
- Issue messages say what is wrong and what to do ("Gap g1 has no answer."), in the reader's words.
- No em dash characters; quotes as the language writes them (“English”, „polski”).