Internationalization

CrossInk keeps user-facing UI text in per-language YAML files and generates the C++ lookup tables used by the firmware.

English is the source of truth. Add new STR_* keys to lib/I18n/translations/english.yaml, then propagate translations to the other YAML files when possible. If a non-English file is missing a key, the generator falls back to the English text for that language.

Supported Languages

The current translation files are:

  • English (EN)
  • Español (ES)
  • Français (FR)
  • Deutsch (DE)
  • Čeština (CS)
  • Português (Brasil) (PT)
  • Русский (RU)
  • Svenska (SV)
  • Română (RO)
  • Català (CA)
  • Українська (UK)
  • Беларуская (BE)
  • Italiano (IT)
  • Polski (PL)
  • Suomi (FI)
  • Dansk (DA)
  • Nederlands (NL)
  • Türkçe (TR)
  • Қазақша (KK)
  • Magyar (HU)
  • Lietuvių (LT)
  • Slovenščina (SI)
  • Valencià (CAV)
  • עברית (HE)
  • Tiếng Việt (VI)

Files

Translation sources live in:

lib/I18n/translations/*.yaml

Generated firmware files live in:

lib/I18n/I18nKeys.h
lib/I18n/I18nStrings.h
lib/I18n/I18nStrings.cpp

The generator is:

scripts/gen_i18n.py

Do not edit the generated files directly. Edit the YAML files and rerun the generator.

YAML Format

Each language file is a flat KEY: "value" mapping.

_language_name: "Deutsch"
_language_code: "DE"
_order: "3"

STR_CROSSPOINT: "CrossInk"
STR_BOOTING: "BOOTING"
STR_BROWSE_FILES: "Dateien durchsuchen"

Metadata keys:

  • _language_name is the display name shown to users.
  • _language_code is the generated Language enum value and must be a valid C++ identifier.
  • _order controls enum order. English is always first, and every _order value must be unique.

Rules:

  • Use UTF-8.
  • Keep the file as plain KEY: "value" lines. Do not add comments.
  • Use STR_* keys for translatable strings.
  • Quote every value.
  • Use \n for newlines, \\ for a literal backslash, and \" for a quote inside a value.
  • Keep UI text concise. Many surfaces are small on the e-ink display.

Adding Or Changing Strings

  1. Add or update the key in lib/I18n/translations/english.yaml.
  2. Add or update the same key in every non-English YAML file when you have a translation.
  3. Regenerate the C++ files:
python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n
  1. Build the simulator or firmware to make sure the generated files compile:
pio run -e simulator

For a firmware-target check:

pio run -e default

The generator prints fallback information for missing translations and warns about extra keys that exist outside English.

Adding A Language

  1. Create a new file in lib/I18n/translations/, for example example.yaml.
  2. Add _language_name, _language_code, and a unique _order.
  3. Add any translated STR_* keys you already have.
  4. Run the generator.
  5. Build with PlatformIO.

Missing strings will fall back to English, but a new language should not ship with large English-only areas unless that is intentional.

Generated Runtime Model

scripts/gen_i18n.py reads the YAML files and generates:

  • Language, StrId, language-code arrays, display-name arrays, and migration helpers in I18nKeys.h
  • Flat UTF-8 string blobs and offset tables in I18nStrings.cpp
  • Character sets for each language, used by font tooling

The flat blob plus offset-table layout keeps lookup simple and avoids a large table of individual C string pointers. When a translation falls back to English, the generated offset uses the English string data instead of duplicating the text.

In firmware code, use tr(STR_KEY) for user-facing strings:

renderer.drawText(font, x, y, tr(STR_SETTINGS_TITLE));

Direct API access is also available:

const char* text = I18N.get(StrId::STR_SETTINGS_TITLE);
const char* name = I18N.getLanguageName(Language::DE);
Language lang = I18n::languageFromCode("DE");
const char* charset = I18n::getCharacterSet(Language::DE);

Persisted Language Setting

The selected language is stored with the rest of the device settings in:

/.crosspoint/crossink-settings.json

The language field is stored as a stable language code string such as "EN", "DE", or "HE", not as a raw enum number.

Current firmware falls back to /.crosspoint/settings.json when the namespaced CrossInk settings file is not present, then saves the loaded settings to crossink-settings.json.

Older firmware used:

/.crosspoint/language.bin

Current firmware migrates that legacy file into crossink-settings.json and renames the legacy file to language.bin.bak.

Validation Checklist

Before submitting translation changes:

  1. Run the generator:
python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n
  1. Check for accidental fallback English values where a real translation was expected:
rg -n '^STR_MY_KEY:' lib/I18n/translations
  1. Build at least the simulator:
pio run -e simulator
  1. If the changed text appears in a cramped UI surface, check it on device or in the simulator for clipping.

Notes For Translators

  • Keep translations short where possible.
  • Preserve placeholders, punctuation, and units from the English string unless the target language needs different grammar.
  • Do not translate internal names that are meant to stay as product or file-system labels.
  • If a full translation would be too long for a button hint or compact label, ask for a dedicated short STR_* key instead of shortening the full title globally.

This site uses Just the Docs, a documentation theme for Jekyll.