Skip to content

Interface languages

The editor exists in English and French. The choice is made in the settings (Appearance, Language): automatic (the browser’s language: French for French speakers, English otherwise), English or French. Changing the language reloads the page.

Each text the editor shows has a key. Each language has a catalog in apps/editor/src/i18n/, a JSON file giving the text of every key, grouped by area of the interface:

{
"common": { "newProject": "New project", "export": "Export" },
"home": { "importingName": "Importing {name}" }
}

en.json is the reference: every key exists there. fr.json has the same keys in French. A key missing from the chosen language falls back to English.

Every text shown goes through t() with its key:

import { t } from '../i18n/index.ts';
t('common.newProject') // "New project", "Nouveau projet"
t('home.importingName', { name: file.name }) // {names} are replaced

A text defined in one place and shown elsewhere (a list of formats, a table of labels, a guided tour) is marked with m(), then goes through t() where it is shown:

const LABEL = { cover: m('inspector.cover'), contain: m('inspector.contain') };
// …
<span>{t(LABEL[mode])}</span>

To add a text: pick a key in the area’s group (home, topbar, inspector, panel, timeline, assistant, settings, tours.editor…; common for a text used in several areas), add it to en.json and fr.json, then call t().

Some texts do not come from the editor: the names of the engine’s nodes, effects and modifiers and of their properties (in packages/), the names of plugin nodes, the example projects’ titles, the assistant’s activity lines. They have no key and are translated by their English text, from the content section of fr.json:

"content": { "Stroke width": "Épaisseur du contour", "Reading the document": "Lecture du document" }

t() and tr() look for a key first, then for such a text, and show the text as is when neither exists.

The test apps/editor/test/i18n.test.ts checks that every key used in the code exists in both catalogs, that no key is left unused, that every text defined outside the editor has its French translation, and that each translation keeps the same {names}. To list what French lacks:

Terminal window
node apps/editor/test/i18n-keys.ts

What the assistant’s model reads (tool descriptions, instructions, tool results) is in English. The assistant is told the interface language and asked to answer in it, including the titles of its proposals and the names of the layers it creates. Server and document validation error messages are in English.

A project plugin exports messages, per language, from the text as written to its translation. They cover the names of its nodes and their properties, and its guided tours:

export const messages = {
fr: { 'Star': 'Étoile', 'Points': 'Branches', 'The Star node': 'Le nœud Étoile' },
};

A plugin written in another language can provide en the same way. From a script: window.tramme.i18n.add('fr', { … }), window.tramme.i18n.locale, window.tramme.i18n.t('…').

  1. A catalog apps/editor/src/i18n/<code>.json with the keys of en.json, and a content section modeled on fr.json.
  2. The language in LOCALES and in the catalog loading (i18n/index.ts), and in the settings choice (settings.ts, Preferences['language']).
  3. A test like the French one.

The site (apps/site) has its own catalogs, apps/site/src/i18n/en.json (the reference) and fr.json, with the same rules: keys grouped by area, a missing key falls back to English, a test checks both have the same keys. apps/site/src/lib/i18n.ts reads them (t(locale, key)). English is at the root, French under /fr/ (one pages/[...lang]/ segment serves both); the docs stay English. Long texts (terms, privacy, FAQ answers, plan rows, roadmap entries) are per-language data in the lib/ module that owns them.