Files

6.3 KiB

Internationalization (I18N)

This guide explains the multi-language support system in CrossPoint Reader.

Supported Languages

  • English
  • Español
  • Français
  • Deutsch
  • Čeština
  • Português (Brasil)
  • Русский
  • Svenska
  • Română
  • Català
  • Українська
  • Беларуская
  • Italiano
  • Polski
  • Suomi
  • Dansk
  • Nederlands
  • Türkçe
  • Қазақша
  • Magyar
  • Lietuvių
  • Slovenščina
  • Valencià
  • עברית

For Developers

Translation System Architecture

The I18N system uses per-language YAML files to maintain translations and a Python script to generate C++ code:

lib/I18n/
├── translations/                # One YAML file per language
│   ├── english.yaml
│   ├── spanish.yaml
│   ├── french.yaml
│   └── ...
├── I18n.h
├── I18n.cpp
├── I18nKeys.h                   # Enums (auto-generated)
├── I18nStrings.h                # String array declarations (auto-generated)
└── I18nStrings.cpp              # String array definitions (auto-generated)

scripts/
└── gen_i18n.py                  # Code generator script

Key principle: All translations are managed in the YAML files under lib/I18n/translations/. The Python script generates the necessary C++ code automatically.


YAML File Format

Each language has its own file in lib/I18n/translations/ (e.g. spanish.yaml).

A file looks like this:

_language_name: "Español"
_language_code: "ES"
_order: "1"

STR_CROSSPOINT: "CrossPoint"
STR_BOOTING: "BOOTING"
STR_BROWSE_FILES: "Buscar archivos"

Metadata keys (prefixed with _):

  • _language_name — Native display name shown to the user (e.g. "Français")
  • _language_code — C++ enum name (e.g. "FR"). Please use the ISO Code of the language. Must be a valid C++ identifier.
  • _order — Controls the position in the Language enum (English is always 0)

Rules:

  • Use UTF-8 encoding
  • Every line must follow the format: KEY: "value"
  • Keys must be valid C++ identifiers (uppercase, starts with STR_)
  • Keys must be unique within a file
  • String values must be quoted
  • Use \n for newlines, \\ for literal backslashes, \" for literal quotes inside values

Adding New Strings

To add a new translatable string:

1. Edit the English YAML file

Add the key to lib/I18n/translations/english.yaml:

STR_MY_NEW_STRING: "My New String"

Then add translations in each language file. If a key is missing from a language file, the generator will automatically use the English text as a fallback (and print a warning).

2. Run the generator script

python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/

This automatically:

  • Fills missing translations from English
  • Updates the StrId enum in I18nKeys.h
  • Regenerates all language arrays in I18nStrings.cpp

3. Use in code

#include <CrossPointSettings.h>
#include <I18n.h>
#include <Logging.h>

// Using the tr() macro (recommended)
renderer.drawText(font, x, y, tr(STR_MY_NEW_STRING));

// Using I18N.get() directly
const char* text = I18N.get(StrId::STR_MY_NEW_STRING);

That's it! No manual array synchronization needed.


Adding a New Language

To add support for a new language (e.g., Italian):

1. Create a new YAML file

Create lib/I18n/translations/italian.yaml:

_language_name: "Italiano"
_language_code: "IT"
_order: "7"

STR_CROSSPOINT: "CrossPoint"
STR_BOOTING: "AVVIO"

You only need to include the strings you have translations for. Missing keys will fall back to English automatically.

2. Run the generator

python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/

This automatically updates all necessary code.


Modifying Existing Translations

Simply edit the relevant YAML file and regenerate:

python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/

UTF-8 Encoding

The YAML files use UTF-8 encoding. Special characters are automatically converted to C++ UTF-8 hex sequences by the generator.


I18N API Reference

// === Convenience Macros (Recommended) ===

// tr(id) - Get translated string without StrId:: prefix
const char* text = tr(STR_SETTINGS_TITLE);
renderer.drawText(font, x, y, tr(STR_BROWSE_FILES));
LOG_INF("I18N", "Status: %s", tr(STR_CONNECTED));

// I18N - Shorthand for I18n::getInstance()
I18N.setLanguage(Language::ES);
Language lang = I18N.getLanguage();

// === Full API ===

// Get the singleton instance
I18n& instance = I18n::getInstance();

// Get translated string (three equivalent ways)
const char* text = tr(STR_SETTINGS_TITLE);              // Macro (recommended)
const char* text = I18N.get(StrId::STR_SETTINGS_TITLE);   // Direct call
const char* text = I18N[StrId::STR_SETTINGS_TITLE];       // Operator overload

// Set runtime language
I18N.setLanguage(Language::ES);

// Get current language
Language lang = I18N.getLanguage();

// Get character set for font subsetting (static method)
const char* chars = I18n::getCharacterSet(Language::FR);

// Persist a user language choice
SETTINGS.language = static_cast<uint8_t>(Language::ES);
SETTINGS.saveToFile();

File Storage

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

/.crosspoint/settings.json

The JSON field is language, stored as a stable language code string such as "EN", "DE", or "HE" rather than a raw enum value.

Older firmware versions used:

/.crosspoint/language.bin

That legacy file is no longer read. Upgrading from a firmware old enough to have written it resets the language to the default; re-select it once and it persists in settings.json.


Translation Workflow

For Developers (Adding Features)

  1. Add new strings to lib/I18n/translations/english.yaml
  2. Run python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/
  3. Use the new StrId in your code
  4. Request translations from translators

For Translators

  1. Open the YAML file for your language in lib/I18n/translations/
  2. Add or update translations using the format STR_KEY: "translated text"
  3. Keep translations concise (E-ink space constraints)
  4. Make sure the file is in UTF-8 encoding
  5. Run python3 scripts/gen_i18n.py lib/I18n/translations lib/I18n/ to verify
  6. Test on device or submit for review