Translations
A game shows each player its text in their language. Every word a player reads is a message in a catalog, and every catalog but the default one translates it. To add a language to a game, follow Translate a game.
| Text | Where you write it | Where it is translated |
|---|---|---|
| The client's words | /src/i18n/<locale>.json, shown with react-intl (Text in the player's language) |
the catalog of each locale |
| Log lines, offer labels, refusal reasons | msg! in the game logic, with its message in the catalogs (Messages from the game logic) |
the catalog of each locale |
| Bot names and descriptions | the catalog messages a bot's NAME and DESCRIPTION keys name (Bots) |
the catalog of each locale |
| The game's name and description | update_game's name and description |
update_game's translations |
| Rules pages and the marketing page | /src/rules/*.bwss, /src/marketing/index.bwss |
a copy of each page in a folder named for its locale (Write the rules pages) |
Catalogs
A game's messages are catalogs, one per locale, in /src/i18n/:
- Each catalog is
/src/i18n/<locale>.json, where the locale is one ofen,de,es,fr,jaandzh-CN. The game's locales are its default locale and each locale it has a catalog for. - A catalog is one JSON object that maps each message key to its message, a string in ICU MessageFormat, such as
{"log.draws": "{count, plural, one {Draws a card} other {Draws # cards}}"}. Write a literal brace as'{'. - A catalog holds at most 256 KiB.
- Every other locale falls back to the default locale, message by message. The default locale is
defaultLocale:get_gamereturns it,update_gamesets it, and it isenuntil set. A game with any catalog needs one for its default locale. - Boardweaver makes
en-XA, its pseudo-locale, from the default catalog: each letter accented, the text longer and each message in brackets. Never write anen-XAcatalog.
write_file, patch_file and rename_file refuse a catalog Boardweaver could never use, with INVALID_CATALOG. That is any other .json path under /src/i18n/, a size over the limit, content that is not one object of strings, or a message that is not ICU MessageFormat. validate_code reports the same problems as errors, and also a missing catalog for the default locale. commit refuses them with BUILD_FAILED before it builds anything. When a catalog is deleted or renamed, or the default locale changes, while the game builds, so that the default locale has no catalog, commit refuses with SOURCE_CHANGED. Every code is in Tool errors.
Messages from the game logic
The game logic sends no finished text. A log line, an offer's label and a refusal's reason are each a message: a key in the catalogs and the values of its placeholders, built with msg!. Each viewer's client formats it in their own language.
table.log(msg!("log.marks", cell = space_id.as_str()));
{ "log.marks": "{player} marks cell {cell}" }
{player}is the line's player. Boardweaver fills it, so amsg!never passes it. Place it where the language needs the name, or leave it out of a message that names no one.- A value is a number, text, a seated player, another message or a link. The table of values is in Log lines.
- The build checks each
msg!whose key is a string literal against the default catalog. It does not check a key built at run time. Such a key shows as itself when no catalog has it.

Messages in the client
The client shows its words with react-intl, by key: <FormattedMessage id="status.yourMove" />. The frame gives the client one react-intl provider with the game's messages in the player's language. Pass only the id. The build checks each literal id against the default catalog, so the catalog always holds the text, and a defaultMessage would be a second copy of it. How to write and format the client's messages is in Text in the player's language and react-intl.
Which locale a player sees
The frame shows the game in the first of the player's languages that is one of the game's locales, else in the default locale. The player's languages are the language they chose on Boardweaver, else their browser's. The game's locale is chosen apart from the website's, so a game can show a locale before Boardweaver itself is translated into it.
- A missing message falls back. A message a translation lacks shows in the default locale.
- A message no catalog has shows its key.
- The game's name, description and pages follow the same order: the first of the player's languages that has a version, else the default locale.
Translations
Every other catalog translates the default one. A translation keeps each message's placeholders, its tags, its select keys and the style of each number, date or time. It keeps {player} where the default message has it. A plural may list only its own language's categories, and may show # in only some of them.
validate_code reports a message whose placeholders differ as an error. commit refuses it with CATALOG_PLACEHOLDERS_MISMATCH, naming the file, the key and both sets of placeholders. When a translation changes while the game builds so that its placeholders differ, commit refuses with SOURCE_CHANGED and the same lines.
Players see the game's name and description in the first of their languages that the game has one in, else in the default locale. They see them in the game list, on the game's page and in a match's lobby. Boardweaver's emails and notices name the game in the reader's account language the same way. The game's page lists its languages: the default locale, then each locale with a catalog, a translated name or a translated description.
A translation may lack messages: each one shows in the default locale until the translation has it. A missing translation is never an error. validate_code lists these lines as warnings, and commit returns them in catalogCoverage:
- the messages each translation lacks, and the ones it has that the default catalog does not, which nothing shows;
- each locale with a catalog whose game name or description has no translation, where a player sees the default locale's until
update_game'stranslationssets it; - a translation for the default locale, which nothing stores: its name and description are
update_game'snameanddescription; - a translated name or description of a game that has none in its default locale, which nothing stores either.
A game with no description, or an empty one, needs no translated description.
A bot's name and description are messages too (Bots). validate_code and commit check them against the catalogs once the Rust builds. When a catalog changes while the game builds so that a bot's text no longer reads, commit refuses with SOURCE_CHANGED.
Translating in the studio
A designer can also translate on the game's Translations page in the studio. The page edits the same catalogs, so read a catalog the designer saved there with read_file.
- The page lists the game's locales, the default locale first. For each translation it shows how many of the default catalog's messages it translates, the messages it lacks, the messages only it has and each placeholder mismatch.
- The page shows each message of the default catalog beside a field for the chosen locale. It edits translations only: edit the default catalog as a file.
- A save writes
/src/i18n/<locale>.jsonwith the same checks aswrite_file: one JSON object, two spaces deep, the default catalog's keys first, and no key whose field is empty. Add a language writes the new locale's catalog as{}. - The page lists what
commitrefuses, with the code and the linevalidate_codeandcommitgive it. - The page writes only what the designer types. Boardweaver translates nothing.

Checking a locale
Play a session to see the game in a locale before a player does (Playing a session):
start_gameandrender_gametakelocale: the default locale, a locale the game has a catalog for, oren-XA. It replaces the player's languages.render_gameshows the session instart_game'slocaleuntil arender_gamecall names its own. A render's answer names itslocale.- Only these tools show
en-XA. A player cannot choose it as an account or website language. - Render in
en-XAto find text with no message: such text shows in plain English, with no accents and no brackets.en-XAis also longer than English, so it shows text that overflows its box. - A render keeps two things in English in every locale. One is Boardweaver's own words in
<History />and its log panel, which a player sees in their language. The other is the players' names,Player <id>, where a player sees each player's own name. The game translates neither. - Render in each locale with glyphs English lacks, such as
jaorzh-CN, to check that the game's fonts have them. apply_actionanswers each committed decision's lines, andinspect_statewithout apathanswers the last committed decision's. Each line comes as its message (its key and values), its player and kind, and astext.textis formatted in the default locale, with each seat namedPlayer <id>. A line whose key the default catalog lacks reads as its key.
start_game and render_game read a session's catalogs when it starts. Start a new session after you edit a catalog.