Skip to content

feat: User-Interface I18n System#728

Merged
daveallie merged 31 commits intocrosspoint-reader:masterfrom
Uri-Tauber:reimplement-i18n
Feb 16, 2026
Merged

feat: User-Interface I18n System#728
daveallie merged 31 commits intocrosspoint-reader:masterfrom
Uri-Tauber:reimplement-i18n

Conversation

@Uri-Tauber
Copy link
Contributor

@Uri-Tauber Uri-Tauber commented Feb 6, 2026

Summary

What is the goal of this PR?
This PR introduces Internationalization (i18n) support, enabling users to switch the UI language dynamically.

What changes are included?

  • Core Logic: Added I18n class (lib/I18n/I18n.h/cpp) to manage language state and string retrieval.

  • Data Structures:

    • lib/I18n/I18nStrings.h/cpp: Static string arrays for each supported language.
    • lib/I18n/I18nKeys.h: Enum definitions for type-safe string access.
    • lib/I18n/translations.csv: single source of truth.
  • Documentation: Added docs/i18n.md detailing the workflow for developers and translators.

  • New Settings activity: src/activities/settings/LanguageSelectActivity.h/cpp

Additional Context

This implementation (building on concepts from #505) prioritizes performance and memory efficiency.

The core approach is to store all localized strings for each language in dedicated arrays and access them via enums. This provides O(1) access with zero runtime overhead, and avoids the heap allocations, hashing, and collision handling required by std::map or std::unordered_map.

The main trade-off is that enums and string arrays must remain perfectly synchronized—any mismatch would result in incorrect strings being displayed in the UI.

To eliminate this risk, I added a Python script that automatically generates I18nStrings.h/.cpp and I18nKeys.h from a CSV file, which will serve as the single source of truth for all translations. The full design and workflow are documented in docs/i18n.md.

Next Steps

  • Python script generate_i18n.py to auto-generate C++ files from CSV
  • Populate translations.csv with initial translations.

Currently available translations: English, Español, Français, Deutsch, Čeština, Português (Brasil), Русский, Svenska.
Thanks, community!

Status: EDIT: ready to be merged.

As a proof of concept, the SPANISH strings currently mirror the English ones, but are fully uppercased.


AI Usage

Did you use AI tools to help write this code? < PARTIALLY >
I used AI for the black work of replacing strings with I18n references across the project, and for generating the documentation. EDIT: also some help with merging changes from master.

Uri-Tauber and others added 5 commits February 5, 2026 20:33
- Refactored I18n system to move string arrays into a separate file (I18nStrings.cpp/h).
- Removed Chinese and Japanese language support.
- Added Spanish, Italian, Swedish, and French as template languages.
- Renamed TR() macro to i18n() for better clarity.
- Performed a comprehensive sweep of the codebase to wrap all hardcoded UI strings in i18n() calls.
- Updated LanguageSelectActivity to dynamically support the new language list.
- Updated documentation in docs/i18n.md.

Co-authored-by: Uri-Tauber <[email protected]>
@Uri-Tauber
Copy link
Contributor Author

Could someone with write access please add feat: to the PR title?

@Uri-Tauber Uri-Tauber marked this pull request as draft February 6, 2026 12:15
@CaptainFrito CaptainFrito changed the title User-Interface I18n System feat: User-Interface I18n System Feb 8, 2026
CaptainFrito
CaptainFrito previously approved these changes Feb 8, 2026
Copy link
Contributor

@CaptainFrito CaptainFrito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very smart implementation 👍🏻

@CaptainFrito
Copy link
Contributor

CaptainFrito commented Feb 8, 2026

Hit me up when we can start translating 🙂

@brbla
Copy link
Contributor

brbla commented Feb 9, 2026

Thumbs up! Now I see we had a very similar idea. If you want to try mine solution, I have a firmware.bin there for you to try and some pictures of my reader with it.

@Uri-Tauber Uri-Tauber marked this pull request as ready for review February 9, 2026 17:11
@Uri-Tauber
Copy link
Contributor Author

I18n Engine Ready!

The Python automation is now working. This PR establishes the core infrastructure for multi-language support.

How to Translate

No manual synchronization is needed. All changes happen in the spreadsheet:

Edit: Open lib/I18n/translations.csv in Excel or LibreOffice.

Translate: Add your text to the corresponding language column.

Sync: Run the script to update the code:

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

Details are available in docs/i18n.md.

Next Steps

I suggest we merge this PR now to establish the workflow (@CaptainFrito @daveallie). Afterwards, Translators can open individual PRs for their specific languages. Until this PR will be merged, feel free to attach your version of the CSV here, and I'll update the PR.

Comment on lines +25 to +28
enum class StrId : uint16_t {
CROSSPOINT,
BOOTING,
SLEEPING,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits, but I would prefer defining those as macro, something like:

#define TEXT_CROSSPOINT i18n(0)
#define TEXT_BOOTING i18n(1)
#define TEXT_SLEEPING i18n(2)

so that the downstream code will look a bit more intuitive, like:

drawCenteredText(TEXT_CROSSPOINT, ...);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the suggestions!
The current i18n(KEY) pattern matches how other modern i18n libraries work (Qt's tr() for example), so it's a well-established pattern. That said, I understand it's a matter of preference.

If there's strong preference for the TEXT_ pattern from other maintainers, I'm happy to adjust.

Copy link
Contributor

@ngxson ngxson Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there are 2 problems with the i18n(KEY) though, firstly it may conflict with other macro or variable naming. Something like AUTH_FAILED can likely introduce conflicts with another enum or#define having the same name

I know namespace enum like i18n(String::LOADING) can also be the solution, but still, it's more verbose than something like T_LOADING or TEXT_LOADING

Secondly, other frameworks uses shorter naming for the text function, like t(), tr(), _e() for the translation function to make DX a bit better. i18n is a bit too verbose I think.

Copy link
Contributor Author

@Uri-Tauber Uri-Tauber Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ngxson You’re right about the second point. Renaming it to tr() would simplify things and make it more clear to developers, especially those who have worked with Qt.

Regarding the first point: Just to clarify, i18n() is actually already a macro
(defined as #define i18n(id) I18n::getInstance().get(StrId::id) in I18n.h).

This means when we write:

drawCenteredText(i18n(CROSSPOINT), ...);

The preprocessor expands it to:

drawCenteredText(I18n::getInstance().get(StrId::CROSSPOINT), ...);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mean it's a problem with the macro, but the enum is the problem.

Generic enum naming like the AUTH_FAILED example I mentioned above can always conflict with any piece of code that declares another enum or macro having the same AUTH_FAILED value, especially C code where they cannot use scope.

At least it is a real problem that I experienced a real production code, so a common practice is to prefix the enum value no matter what.

Ref: https://www.google.com/search?q=enum%20conflicts%20with%20a%20previous%20declaration

Copy link
Contributor

@ngxson ngxson Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It think either STR_ or TEXT_ sounds good. I don't usually add number to variable name (just personal preference though)

Even better, since the discussion above moves i18n() to something shorter like tr(), maybe we can call the whole thing TR_BACK (which expands to something like i18n(STR_ID_BACK) ?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ngxson I Updated the PR according to your suggestions.

Copy link
Contributor

@ngxson ngxson Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks better now, just one more concern is that maybe writing tr(STR_...) potentially making the whole tr(STR_ as a prefix? Just look a bit verbose to me, but not a very big problem.

My suggestion is:

#define TR_BACK tr(STR_BACK)
#define TR_CONFIRM tr(STR_CONFIRM)
...

// then use it: drawText(TR_BACK, ...)

These macros can be autogenerated on the same I18nKeys.h header

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I work a lot with Qt, so, in my eyes, the tr(STR_CONFIRM) style feels more intuitive than TR_CONFIRM macro. If that’s acceptable, I’d like to keep it as is.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually prefer brevity for variable names that use very frequently, but no strong opinion in this case. Maybe asking the team @crosspoint-reader/firmware-maintainers as this seems to be an important decision.

Copy link
Contributor

@ngxson ngxson Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO a csv may not be a very good idea. For example, if 2 translators open 2 PRs for the 2 different languages, each PR will still modify the same line, thus making diff harder to merge.

Maybe something like JSON or YAML can be a better choice?

en:
  CROSSPOINT: CrossPoint
  BOOTING: Booting

Or:

CROSSPOINT:
  en: Crosspoint
  fr: ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ngxson Handling merge conflicts in the CSV is straightforward via a quick column copy-paste in LibreOffice or Excel. I chose CSV because it’s much more accessible for non-technical contributors compared to JSON or YAML, which usually require a specialized editors (like Dadroit) to avoid syntax errors.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's my first stab at German. (German is notorious for having long words, so I'm happy to check this on my device later and shrink strings further down.)

translations_de2.csv

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @DavidOrtmann!
I will wait with updating the PR until you finished the shrinking.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to check the strings on-device yet?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your work! I'm glad I aggressively shorted strings before uploading (I added a column that showed length difference), will now work through anything that is too long still.

Copy link

@DavidOrtmann DavidOrtmann Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, we might need a different string for "Inverted", because this is something different in German (and maybe other languages) if it relates to the device's orientation or colours of an image.

I went through all menus (I hope) and adjusted some lengths in this version of the translation:

translations_de_1102.csv

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ngxson I started migrating to YAML, and while doing that I had an idea that might address the merging issue you mentioned, while keeping the workflow simple.

What if we rotate the CSV by 90 degrees — so languages become rows and strings become columns? That way, adding a new language or a new string would still be straightforward.

It would also allow translators without programming experience to contribute in a familiar spreadsheet-style format, without feeling intimidated by YAML.

If two translators submit PRs for different languages, the merge should happen automatically since each language would be a separate row. The only time manual conflict resolution would be needed is when two people work on the same language, which should be much less common.

What do you think?

Copy link
Contributor

@ngxson ngxson Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we rotate the CSV by 90 degrees — so languages become rows and strings become columns? That way, adding a new language or a new string would still be straightforward.

I imagine that won't be very readable on something like excel. Also, if 2 people change 2 different texts in the same language, git diff will still fail

It would also allow translators without programming experience to contribute in a familiar spreadsheet-style format, without feeling intimidated by YAML.

Honestly I don't see how yaml can be difficult to understand. It looks almost like a todo list and I'm pretty sure that it's intuitive enough that translator can also ask AI for help if needed.

I suggest we do this way: instead of assuming which one is easier, let's try making 2 demo, one yaml, one csv, and ask translators for opinions ?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CaptainFrito
Copy link
Contributor

CaptainFrito commented Feb 10, 2026

Anybody working on translations? I'm doing le French

@CaptainFrito
Copy link
Contributor

CaptainFrito commented Feb 10, 2026

translations.csv

Here is a first stab at the french version. It didn't export with quotes except for the strings that have a coma, is that okay?
I'm gonna checkout your branch and put it in the code now to see what it looks like. The settings are probably going to look horrible with all these long ass strings, but at least it's not German!

@CaptainFrito
Copy link
Contributor

IMG_8037 Medium

HA! Your move, Germans!

@Uri-Tauber
Copy link
Contributor Author

translations.csv

Here is a first stab at the french version. It didn't export with quotes except for the strings that have a coma, is that okay? I'm gonna checkout your branch and put it in the code now to see what it looks like. The settings are probably going to look horrible with all these long ass strings, but at least it's not German!

@CaptainFrito Looks great.

@ariel-lindemann
Copy link
Contributor

Anybody working on translations? I'm doing le French

translation work is being discussed here #719

@madebyKir
Copy link
Contributor

translations.csv

Here is a first stab at the french version. It didn't export with quotes except for the strings that have a coma, is that okay? I'm gonna checkout your branch and put it in the code now to see what it looks like. The settings are probably going to look horrible with all these long ass strings, but at least it's not German!

Do I understand correctly that to add the Russian language, I must add the Russian translation after the comma in each line?

P.S. Sorry for the stupid question, I'm not a programmer, I'm just a person with a great desire to participate in the project

@Uri-Tauber
Copy link
Contributor Author

Do I understand correctly that to add the Russian language, I must add the Russian translation after the comma in each line?

P.S. Sorry for the stupid question, I'm not a programmer, I'm just a person with a great desire to participate in the project

Not a stupid question at all.

You might find it easier to work with CSV files in Excel or LibreOffice. Essentially, yes—just add a comma and your translation at the end of each line. If any value contains a comma, wrap it in quotes so it isn’t interpreted as multiple cells.

In Excel or LibreOffice, this is usually handled automatically when you add a new column.

. Add German and French translations
@Uri-Tauber
Copy link
Contributor Author

  • Switch the i18n() macro to shorter tr(), per @ngxson suggestion.
  • Prefix all new translation keys with SRT_ to avoid clashes with existing keys, per @ngxson suggestion.
  • Add German and French translations. Credit to @CaptainFrito and @DavidOrtmann!

french
german

Firmware file for testing:
firmware.bin.zip

STR_CAPS_OFF: "caps"
STR_OK_BUTTON: "OK"
STR_ON_MARKER: "[ВКЛ]"
STR_SLEEP_COVER_FILTER: "Фильтр обложки сна"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
STR_SLEEP_COVER_FILTER: "Фильтр обложки сна"
STR_SLEEP_COVER_FILTER: "Фильтр экрана сна"

Definition of screen here will fit better I think.

Also:

Image

Copy link
Contributor

@mrtnvgr mrtnvgr Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the filter applies only to the Обложка (cover), the original value can be right. Something worth considering nonetheless :)

@mrtnvgr
Copy link
Contributor

mrtnvgr commented Feb 16, 2026

@mrtnvgr would you mind opening a PR with these changes?

Yes, when I've started the review the PR was open 😆

osteotek pushed a commit that referenced this pull request Feb 16, 2026
## Summary

This PR includes vocabulary and grammar fixes for Russian translation,
originally made as review comments
[here](#728).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
Unintendedsideeffects pushed a commit to Unintendedsideeffects/crosspoint-reader that referenced this pull request Feb 17, 2026
## Summary

**What is the goal of this PR?**
This PR introduces Internationalization (i18n) support, enabling users
to switch the UI language dynamically.

**What changes are included?**
- Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage
language state and string retrieval.

- Data Structures:

- `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported
language.
  - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access.
  - `lib/I18n/translations.csv`: single source of truth. 

- Documentation: Added `docs/i18n.md` detailing the workflow for
developers and translators.

- New Settings activity:
`src/activities/settings/LanguageSelectActivity.h/cpp`

## Additional Context

This implementation (building on concepts from crosspoint-reader#505) prioritizes
performance and memory efficiency.

The core approach is to store all localized strings for each language in
dedicated arrays and access them via enums. This provides O(1) access
with zero runtime overhead, and avoids the heap allocations, hashing,
and collision handling required by `std::map` or `std::unordered_map`.

The main trade-off is that enums and string arrays must remain perfectly
synchronized—any mismatch would result in incorrect strings being
displayed in the UI.

To eliminate this risk, I added a Python script that automatically
generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which
will serve as the single source of truth for all translations. The full
design and workflow are documented in `docs/i18n.md`.

### Next Steps

- [x] Python script `generate_i18n.py` to auto-generate C++ files from
CSV
- [x] Populate translations.csv with initial translations.

Currently available translations: English, Español, Français, Deutsch,
Čeština, Português (Brasil), Русский, Svenska.
Thanks, community!

**Status:** EDIT: ready to be merged.

As a proof of concept, the SPANISH strings currently mirror the English
ones, but are fully uppercased.

---

### AI Usage

Did you use AI tools to help write this code? _**< PARTIALLY >**_
I used AI for the black work of replacing strings with I18n references
across the project, and for generating the documentation. EDIT: also
some help with merging changes from master.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: yeyeto2788 <[email protected]>
Unintendedsideeffects pushed a commit to Unintendedsideeffects/crosspoint-reader that referenced this pull request Feb 17, 2026
## Summary

This PR includes vocabulary and grammar fixes for Russian translation,
originally made as review comments
[here](crosspoint-reader#728).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
osteotek pushed a commit that referenced this pull request Feb 17, 2026
## Summary

* **What is the goal of this PR?** Update translators.md to include all
the contributors from #728

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
znelson pushed a commit to znelson/crosspoint-reader that referenced this pull request Feb 18, 2026
## Summary

* **What is the goal of this PR?** Update translators.md to include all
the contributors from crosspoint-reader#728

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
ngxson pushed a commit that referenced this pull request Feb 18, 2026
## Summary

* **What is the goal of this PR?** Fix a dangling pointer issue caused
by using `.c_str()` on a temporary `std::string`.

`basepath.substr()` creates a temporary `std::string`, and calling
`.c_str()` on it returns a pointer to its internal buffer (not a copy).
Since the temporary string is destroyed at the end of the full
expression, `folderName` ends up holding a dangling pointer, leading to
undefined behavior.

To solve this, we stores the result in a persistent `std::string`
object, ensuring the underlying buffer remains valid for the duration of
its use.

A similar pattern caused the behavior reported in
#728 (comment)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 18, 2026
## Summary

This PR includes vocabulary and grammar fixes for Russian translation,
originally made as review comments
[here](crosspoint-reader#728).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 18, 2026
## Summary

* **What is the goal of this PR?** Update translators.md to include all
the contributors from crosspoint-reader#728

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 18, 2026
## Summary

* **What is the goal of this PR?** Fix a dangling pointer issue caused
by using `.c_str()` on a temporary `std::string`.

`basepath.substr()` creates a temporary `std::string`, and calling
`.c_str()` on it returns a pointer to its internal buffer (not a copy).
Since the temporary string is destroyed at the end of the full
expression, `folderName` ends up holding a dangling pointer, leading to
undefined behavior.

To solve this, we stores the result in a persistent `std::string`
object, ensuring the underlying buffer remains valid for the duration of
its use.

A similar pattern caused the behavior reported in
crosspoint-reader#728 (comment)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
@Uri-Tauber Uri-Tauber deleted the reimplement-i18n branch February 18, 2026 16:12
ariel-lindemann pushed a commit to ariel-lindemann/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

This PR includes vocabulary and grammar fixes for Russian translation,
originally made as review comments
[here](crosspoint-reader#728).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
ariel-lindemann pushed a commit to ariel-lindemann/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

* **What is the goal of this PR?** Update translators.md to include all
the contributors from crosspoint-reader#728

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
ariel-lindemann pushed a commit to ariel-lindemann/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

* **What is the goal of this PR?** Fix a dangling pointer issue caused
by using `.c_str()` on a temporary `std::string`.

`basepath.substr()` creates a temporary `std::string`, and calling
`.c_str()` on it returns a pointer to its internal buffer (not a copy).
Since the temporary string is destroyed at the end of the full
expression, `folderName` ends up holding a dangling pointer, leading to
undefined behavior.

To solve this, we stores the result in a persistent `std::string`
object, ensuring the underlying buffer remains valid for the duration of
its use.

A similar pattern caused the behavior reported in
crosspoint-reader#728 (comment)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
saslv pushed a commit to saslv/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

**What is the goal of this PR?**
This PR introduces Internationalization (i18n) support, enabling users
to switch the UI language dynamically.

**What changes are included?**
- Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage
language state and string retrieval.

- Data Structures:

- `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported
language.
  - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access.
  - `lib/I18n/translations.csv`: single source of truth. 

- Documentation: Added `docs/i18n.md` detailing the workflow for
developers and translators.

- New Settings activity:
`src/activities/settings/LanguageSelectActivity.h/cpp`

## Additional Context

This implementation (building on concepts from crosspoint-reader#505) prioritizes
performance and memory efficiency.

The core approach is to store all localized strings for each language in
dedicated arrays and access them via enums. This provides O(1) access
with zero runtime overhead, and avoids the heap allocations, hashing,
and collision handling required by `std::map` or `std::unordered_map`.

The main trade-off is that enums and string arrays must remain perfectly
synchronized—any mismatch would result in incorrect strings being
displayed in the UI.

To eliminate this risk, I added a Python script that automatically
generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which
will serve as the single source of truth for all translations. The full
design and workflow are documented in `docs/i18n.md`.

### Next Steps

- [x] Python script `generate_i18n.py` to auto-generate C++ files from
CSV
- [x] Populate translations.csv with initial translations.

Currently available translations: English, Español, Français, Deutsch,
Čeština, Português (Brasil), Русский, Svenska.
Thanks, community!

**Status:** EDIT: ready to be merged.

As a proof of concept, the SPANISH strings currently mirror the English
ones, but are fully uppercased.

---

### AI Usage

Did you use AI tools to help write this code? _**< PARTIALLY >**_
I used AI for the black work of replacing strings with I18n references
across the project, and for generating the documentation. EDIT: also
some help with merging changes from master.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: yeyeto2788 <[email protected]>
saslv pushed a commit to saslv/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

This PR includes vocabulary and grammar fixes for Russian translation,
originally made as review comments
[here](crosspoint-reader#728).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
saslv pushed a commit to saslv/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

* **What is the goal of this PR?** Update translators.md to include all
the contributors from crosspoint-reader#728

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
saslv pushed a commit to saslv/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

* **What is the goal of this PR?** Fix a dangling pointer issue caused
by using `.c_str()` on a temporary `std::string`.

`basepath.substr()` creates a temporary `std::string`, and calling
`.c_str()` on it returns a pointer to its internal buffer (not a copy).
Since the temporary string is destroyed at the end of the full
expression, `folderName` ends up holding a dangling pointer, leading to
undefined behavior.

To solve this, we stores the result in a persistent `std::string`
object, ensuring the underlying buffer remains valid for the duration of
its use.

A similar pattern caused the behavior reported in
crosspoint-reader#728 (comment)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 19, 2026
**What is the goal of this PR?**
This PR introduces Internationalization (i18n) support, enabling users
to switch the UI language dynamically.

**What changes are included?**
- Core Logic: Added I18n class (`lib/I18n/I18n.h/cpp`) to manage
language state and string retrieval.

- Data Structures:

- `lib/I18n/I18nStrings.h/cpp`: Static string arrays for each supported
language.
  - `lib/I18n/I18nKeys.h`: Enum definitions for type-safe string access.
  - `lib/I18n/translations.csv`: single source of truth.

- Documentation: Added `docs/i18n.md` detailing the workflow for
developers and translators.

- New Settings activity:
`src/activities/settings/LanguageSelectActivity.h/cpp`

This implementation (building on concepts from crosspoint-reader#505) prioritizes
performance and memory efficiency.

The core approach is to store all localized strings for each language in
dedicated arrays and access them via enums. This provides O(1) access
with zero runtime overhead, and avoids the heap allocations, hashing,
and collision handling required by `std::map` or `std::unordered_map`.

The main trade-off is that enums and string arrays must remain perfectly
synchronized—any mismatch would result in incorrect strings being
displayed in the UI.

To eliminate this risk, I added a Python script that automatically
generates `I18nStrings.h/.cpp` and `I18nKeys.h` from a CSV file, which
will serve as the single source of truth for all translations. The full
design and workflow are documented in `docs/i18n.md`.

- [x] Python script `generate_i18n.py` to auto-generate C++ files from
CSV
- [x] Populate translations.csv with initial translations.

Currently available translations: English, Español, Français, Deutsch,
Čeština, Português (Brasil), Русский, Svenska.
Thanks, community!

**Status:** EDIT: ready to be merged.

As a proof of concept, the SPANISH strings currently mirror the English
ones, but are fully uppercased.

---

Did you use AI tools to help write this code? _**< PARTIALLY >**_
I used AI for the black work of replacing strings with I18n references
across the project, and for generating the documentation. EDIT: also
some help with merging changes from master.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: yeyeto2788 <[email protected]>
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

This PR includes vocabulary and grammar fixes for Russian translation,
originally made as review comments
[here](crosspoint-reader#728).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

* **What is the goal of this PR?** Update translators.md to include all
the contributors from crosspoint-reader#728

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 19, 2026
## Summary

* **What is the goal of this PR?** Fix a dangling pointer issue caused
by using `.c_str()` on a temporary `std::string`.

`basepath.substr()` creates a temporary `std::string`, and calling
`.c_str()` on it returns a pointer to its internal buffer (not a copy).
Since the temporary string is destroyed at the end of the full
expression, `folderName` ends up holding a dangling pointer, leading to
undefined behavior.

To solve this, we stores the result in a persistent `std::string`
object, ensuring the underlying buffer remains valid for the duration of
its use.

A similar pattern caused the behavior reported in
crosspoint-reader#728 (comment)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 20, 2026
## Summary

* **What is the goal of this PR?** Update translators.md to include all
the contributors from crosspoint-reader#728

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
lukestein pushed a commit to lukestein/crosspoint-reader that referenced this pull request Feb 20, 2026
## Summary

* **What is the goal of this PR?** Fix a dangling pointer issue caused
by using `.c_str()` on a temporary `std::string`.

`basepath.substr()` creates a temporary `std::string`, and calling
`.c_str()` on it returns a pointer to its internal buffer (not a copy).
Since the temporary string is destroyed at the end of the full
expression, `folderName` ends up holding a dangling pointer, leading to
undefined behavior.

To solve this, we stores the result in a persistent `std::string`
object, ensuring the underlying buffer remains valid for the duration of
its use.

A similar pattern caused the behavior reported in
crosspoint-reader#728 (comment)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.