Skip to content

Conversation

@ManfredRichthofen
Copy link
Contributor

@ManfredRichthofen ManfredRichthofen commented Aug 20, 2025

This is very much a draft but I want the rabbit reviews

  • Finish Translations
  • WebUI Selector
  • Use string file (TranslationStrings.h)
  • Document Adding translations

Summary by CodeRabbit

  • New Features

    • Multi-language display support (English, German, French, Spanish) with English fallbacks.
    • Language preference persisted and restored; runtime language changes update UI immediately via a new language controller and plugin.
    • UI strings replaced with translation lookups and a formatting helper.
  • Documentation

    • Added "Adding Translations" guidance for contributors (Controller and WebUI).

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 20, 2025

Warning

Rate limit exceeded

@ManfredRichthofen has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 36 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 63ee27c and d0cb1b4.

📒 Files selected for processing (2)
  • src/display/core/TranslationStrings.h (1 hunks)
  • web/src/pages/Settings/index.jsx (1 hunks)

Walkthrough

Adds runtime localization: new Translation module with language enums, tables and API; persists language in Settings (DEFAULT_LANGUAGE fallback) and exposes set/get; new LanguagePlugin to change/persist language and emit UI refresh; DefaultUI initializes Translation and replaces literals with TR(...) keys; docs added for translations.

Changes

Cohort / File(s) Summary
Settings: persistence
src/display/core/Settings.h, src/display/core/Settings.cpp, src/display/core/constants.h
Adds language member with getLanguage()/setLanguage(int); loads "lang" from preferences (falls back to DEFAULT_LANGUAGE); doSave() writes "lang" via preferences.putInt("lang", language); adds DEFAULT_LANGUAGE macro.
Translation framework
src/display/core/Translation.h, src/display/core/Translation.cpp, src/display/core/TranslationStrings.h, src/display/core/TranslationStrings.cpp
New Language and TranslationKey enums; Translation class with setLanguage/getLanguage/get(key)/format(...); maintains currentLanguage (default ENGLISH), selects language tables (EN/DE/FR/ES), bounds keys, falls back to English when missing; translation string arrays implemented.
Language plugin
src/display/plugins/LanguagePlugin.h, src/display/plugins/LanguagePlugin.cpp
New LanguagePlugin that registers with plugin manager, listens for language:change, applies language to Translation, persists via Settings::setLanguage, emits ui:refresh; provides JSON getConfig()/setConfig() for language.
UI integration
src/display/ui/default/DefaultUI.h, src/display/ui/default/DefaultUI.cpp
Includes Translation header; DefaultUI constructor initializes Translation from Settings; replaces hard-coded UI strings with TR(...) translation keys across screens; UI control flow unchanged beyond text lookups.
Docs
CONTRIBUTING.md
Adds "Adding Translations" guidance for Controller and WebUI.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Controller
  participant S as Settings
  participant UI as DefaultUI
  participant T as Translation

  C->>S: load()
  note right of S #DDDDFF: read "lang" (DEFAULT_LANGUAGE fallback)
  C->>UI: construct DefaultUI(controller)
  UI->>S: getLanguage()
  UI->>T: setLanguage(savedLang)
  T-->>UI: ack
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant EB as EventBus
  participant LP as LanguagePlugin
  participant T as Translation
  participant S as Settings
  participant LT as LoopTask
  participant UI as UI

  U->>EB: emit("language:change", langId)
  EB-->>LP: event(langId)
  LP->>T: setLanguage((Language)langId)
  LP->>S: setLanguage(langId)
  note right of S #FFF4DD: schedules save()
  LT->>S: periodic flush (~5s)
  S-->>LT: doSave() writes "lang"
  LP->>EB: emit("ui:refresh")
  EB-->>UI: refresh
Loading
sequenceDiagram
  autonumber
  participant UI as View
  participant T as Translation
  participant EN as EnglishTable
  participant DE as GermanTable
  participant FR as FrenchTable
  participant ES as SpanishTable

  UI->>T: get(key)
  alt currentLanguage == DE/FR/ES
    T->>DE: lookup(key)
    DE-->>T: text or empty
  else
    T->>EN: lookup(key)
    EN-->>T: text
  end
  opt missing
    T->>EN: fallback lookup
    EN-->>T: text
  end
  T-->>UI: const char* label
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Suggested reviewers

  • nayrod485

Poem

I nibble keys and hop with glee,
Languages bloom for all to see.
Settings saved, the UI sings,
Plugins leap with tiny springs.
Rabbity translations, hop — so free! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/display/ui/default/DefaultUI.cpp (1)

82-91: Subscribe to ui:refresh to make language changes visible immediately

LanguagePlugin emits "ui:refresh" on change, but DefaultUI does not subscribe. Without this, many labels that are only updated by reactive effects tied to other state may not re-render on language change.

Proposed minimal fix:

 void DefaultUI::init() {
     auto triggerRender = [this](Event const &) { rerender = true; };
+    // Refresh UI texts when language changes
+    pluginManager->on("ui:refresh", triggerRender);

Note: If your EffectManager only runs effects when dependencies change, you may also need to explicitly rebind static labels (or add a 'languageVersion' dependency) to ensure text is re-applied on refresh.

🧹 Nitpick comments (14)
src/display/core/constants.h (1)

26-26: DEFAULT_LANGUAGE magic number risks drifting from Language::ENGLISH; add validation or derive from enum

Using a hardcoded 0 assumes Language::ENGLISH stays first. If the enum order changes, persisted defaults can mismatch silently.

  • Minimal: add a compile-time check in Translation.cpp to ensure alignment.
  • Better: validate/coerce loaded/assigned values (see suggested changes in Settings.cpp/Translation.h).

Add this compile-time guard in src/display/core/Translation.cpp to catch drift early:

#include <display/core/constants.h>
static_assert(static_cast<int>(Language::ENGLISH) == DEFAULT_LANGUAGE, "DEFAULT_LANGUAGE must match Language::ENGLISH");
src/display/core/Settings.h (1)

80-82: Type-safety and no-op optimization for language setter

API looks fine. Consider:

  • Return/accept Language instead of int for type-safety, or add an overload returning Language.
  • Make setLanguage a no-op when the value is unchanged to avoid unnecessary flash writes.

See inline setter change suggested in Settings.cpp comment.

src/display/ui/default/DefaultUI.h (1)

7-7: OK to include Translation here; consider moving to .cpp if not needed in the header

If TR(...) or Translation symbols are only used in DefaultUI.cpp, including Translation.h here increases compile-time coupling. Moving the include to the .cpp would keep the header lean.

src/display/core/Settings.cpp (2)

69-69: Validate persisted language before using it

Reading an arbitrary int and later casting to Language elsewhere can yield invalid enum values. While Translation::get falls back via default: case, validating upfront is cleaner and prevents surprising state.

Apply this diff to validate the stored value:

-    language = preferences.getInt("lang", DEFAULT_LANGUAGE);
+    int lang = preferences.getInt("lang", DEFAULT_LANGUAGE);
+    // Coerce invalid values to default
+    language = (lang >= 0 && lang <= 3) ? lang : DEFAULT_LANGUAGE;

Additionally, prefer centralizing validation via Translation helpers (see proposed additions to Translation.h below). If you add those helpers, replace the ternary with:

language = Translation::isValid(lang) ? lang : DEFAULT_LANGUAGE;

To support that, add this include near the top of this file:

#include <display/core/Translation.h>

383-386: Coerce/ignore unchanged language to avoid unnecessary writes and invalid states

Avoid saving when unchanged and coerce invalid inputs to the default. This reduces flash wear and keeps state consistent.

Apply this diff:

-void Settings::setLanguage(int language) {
-    this->language = language;
-    save();
-}
+void Settings::setLanguage(int language) {
+    // Coerce invalid values; replace with Translation::isValid(...) if added
+    int next = (language >= 0 && language <= 3) ? language : DEFAULT_LANGUAGE;
+    if (this->language == next) {
+        return; // no-op
+    }
+    this->language = next;
+    save();
+}
src/display/core/Translation.h (2)

6-11: Add COUNT sentinel to future-proof validation/clamping

A COUNT enumerator makes validation robust without hardcoding limits elsewhere.

Apply this diff:

-enum class Language {
-    ENGLISH,
-    GERMAN,
-    FRENCH,
-    SPANISH
-};
+enum class Language {
+    ENGLISH,
+    GERMAN,
+    FRENCH,
+    SPANISH,
+    COUNT
+};

34-47: Expose validation/coercion helpers to centralize handling of int<->Language

Callers (Settings, LanguagePlugin) can use these to prevent invalid enum states and reduce duplication.

Apply this diff:

 class Translation {
 public:
     static void setLanguage(Language lang);
     static Language getLanguage();
     static const char* get(TranslationKey key);
     static String format(const char* format, ...);
-    
+    // Helpers for int-based settings/APIs
+    static bool isValid(int v) { return v >= 0 && v < static_cast<int>(Language::COUNT); }
+    static Language coerce(int v) { return isValid(v) ? static_cast<Language>(v) : Language::ENGLISH; }
     
 private:
     static Language currentLanguage;
     static const char* getEnglish(TranslationKey key);
     static const char* getGerman(TranslationKey key);
     static const char* getFrench(TranslationKey key);
     static const char* getSpanish(TranslationKey key);
 };

Optional: add a compile-time guard (in Translation.cpp) to ensure DEFAULT_LANGUAGE maps to ENGLISH as noted in constants.h review.

src/display/ui/default/DefaultUI.cpp (2)

546-550: Brew screen: split name and caption — OK, but ensure caption updates on language change

The split between profile name and a localized caption is fine. With the ui:refresh subscription (suggested above), this will update when the language changes.


693-696: Format string nit: use %.1f, not %.1lf, for printf-style APIs

"%.1lfg" works because the trailing "g" is literal text, but "l" is unnecessary with printf (unlike scanf) and inconsistent with the rest of the file.

-        lv_label_set_text_fmt(ui_StatusScreen_brewVolume, "%.1lfg", brewProcess->currentVolume);
+        lv_label_set_text_fmt(ui_StatusScreen_brewVolume, "%.1fg", brewProcess->currentVolume);
src/display/plugins/LanguagePlugin.cpp (1)

8-13: Event wiring is fine; consider debouncing identical selections

The subscription is correct. Optional: avoid re-saving and emitting refresh when the selected language is unchanged.

 void LanguagePlugin::init() {
     controller->getPluginManager()->on("language:change", [this](Event const &event) {
         int language = event.getInt("language");
-        setLanguage(language);
+        setLanguage(language);
     });
 }

And inside setLanguage (see next comment) add an early-return when the language matches.

src/display/core/Translation.cpp (4)

1-8: Default language should follow DEFAULT_LANGUAGE constant

Hardcoding ENGLISH can drift from the configured default. Use the macro to keep behavior consistent with Settings.

 #include "Translation.h"
+#include <display/core/constants.h>
 #include <stdarg.h>
 
-Language Translation::currentLanguage = Language::ENGLISH;
+Language Translation::currentLanguage = static_cast<Language>(DEFAULT_LANGUAGE);

37-44: Static buffer in format() — acceptable, but consider longer texts

256 bytes may be tight for concatenated translations on some screens. Optional: increase to 512 or document constraints.


70-92: German translations: quality is good, but check capitalization consistency

Minor nit: "Autotune..." vs "Autotuning..." and "Neustart benötigt" vs "Neustart erforderlich". Not blocking.


94-116: French and Spanish tables are placeholders in English

Most keys are untranslated. If shipping multi-language, this will feel incomplete.

Would you like help providing proper FR/ES translations for the existing keys, or should we gate language choices to only those with >90% coverage?

Also applies to: 118-140

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7fd4181 and fc7ec6f.

📒 Files selected for processing (9)
  • src/display/core/Settings.cpp (3 hunks)
  • src/display/core/Settings.h (2 hunks)
  • src/display/core/Translation.cpp (1 hunks)
  • src/display/core/Translation.h (1 hunks)
  • src/display/core/constants.h (1 hunks)
  • src/display/plugins/LanguagePlugin.cpp (1 hunks)
  • src/display/plugins/LanguagePlugin.h (1 hunks)
  • src/display/ui/default/DefaultUI.cpp (8 hunks)
  • src/display/ui/default/DefaultUI.h (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
src/display/plugins/LanguagePlugin.h (4)
src/display/plugins/LanguagePlugin.cpp (17)
  • LanguagePlugin (4-6)
  • init (8-13)
  • init (8-8)
  • getName (21-23)
  • getName (21-21)
  • getDescription (25-27)
  • getDescription (25-25)
  • isEnabled (29-31)
  • isEnabled (29-29)
  • setEnabled (33-34)
  • setEnabled (33-33)
  • getConfig (36-38)
  • getConfig (36-36)
  • setConfig (40-51)
  • setConfig (40-40)
  • setLanguage (15-19)
  • setLanguage (15-15)
src/display/core/Plugin.h (1)
  • Plugin (6-12)
src/display/core/Translation.cpp (2)
  • setLanguage (6-8)
  • setLanguage (6-6)
src/display/core/Settings.cpp (2)
  • setLanguage (383-386)
  • setLanguage (383-383)
src/display/core/Translation.h (3)
src/display/plugins/LanguagePlugin.cpp (2)
  • setLanguage (15-19)
  • setLanguage (15-15)
src/display/core/Translation.cpp (16)
  • setLanguage (6-8)
  • setLanguage (6-6)
  • getLanguage (10-12)
  • getLanguage (10-10)
  • get (14-35)
  • get (14-14)
  • format (37-44)
  • format (37-37)
  • getEnglish (46-68)
  • getEnglish (46-46)
  • getGerman (70-92)
  • getGerman (70-70)
  • getFrench (94-116)
  • getFrench (94-94)
  • getSpanish (118-140)
  • getSpanish (118-118)
src/display/core/Settings.cpp (2)
  • setLanguage (383-386)
  • setLanguage (383-383)
src/display/core/Settings.h (3)
src/display/core/Translation.cpp (4)
  • getLanguage (10-12)
  • getLanguage (10-10)
  • setLanguage (6-8)
  • setLanguage (6-6)
src/display/plugins/LanguagePlugin.cpp (2)
  • setLanguage (15-19)
  • setLanguage (15-15)
src/display/core/Settings.cpp (2)
  • setLanguage (383-386)
  • setLanguage (383-383)
src/display/plugins/LanguagePlugin.cpp (4)
src/display/plugins/LanguagePlugin.h (1)
  • LanguagePlugin (6-20)
src/display/ui/default/DefaultUI.cpp (2)
  • init (82-189)
  • init (82-82)
src/display/core/Translation.cpp (2)
  • setLanguage (6-8)
  • setLanguage (6-6)
src/display/core/Settings.cpp (2)
  • setLanguage (383-386)
  • setLanguage (383-383)
src/display/ui/default/DefaultUI.cpp (2)
src/display/plugins/LanguagePlugin.cpp (2)
  • setLanguage (15-19)
  • setLanguage (15-15)
src/display/core/Translation.cpp (2)
  • setLanguage (6-8)
  • setLanguage (6-6)
src/display/core/Translation.cpp (2)
src/display/plugins/LanguagePlugin.cpp (2)
  • setLanguage (15-19)
  • setLanguage (15-15)
src/display/core/Settings.cpp (2)
  • setLanguage (383-386)
  • setLanguage (383-383)
src/display/core/Settings.cpp (2)
src/display/plugins/LanguagePlugin.cpp (2)
  • setLanguage (15-19)
  • setLanguage (15-15)
src/display/core/Translation.cpp (2)
  • setLanguage (6-8)
  • setLanguage (6-6)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: deploy
  • GitHub Check: test
🔇 Additional comments (10)
src/display/core/Settings.h (1)

210-210: Sane default initialization

Initializing to DEFAULT_LANGUAGE is correct and consistent with preferences fallback.

src/display/core/Settings.cpp (1)

457-457: Persisting 'lang' key looks correct

The key name aligns with the constructor load. No concerns.

src/display/core/Translation.h (1)

49-49: TR macro is straightforward and useful

Macro indirection for keys is fine and keeps call sites tidy.

src/display/ui/default/DefaultUI.cpp (5)

75-81: Initialize language from settings early — looks good

Setting the translation language in the constructor ensures UI comes up in the persisted language. No concerns here.


336-337: Good use of TR() for mode-specific label

Replacing literals with translations is correct and keeps UI reactive to language.


441-448: Good: status messages now localized

UPDATING, TEMPERATURE_ERROR, and AUTOTUNING use TR() as expected.


644-646: Localized phase labels — looks good

Phase caption and finished state correctly use translations.


657-663: Consistent formatting — good

Translating only the unit-less labels while keeping numeric formats is appropriate.

src/display/plugins/LanguagePlugin.cpp (1)

36-38: Config JSON serialization is OK as a minimal payload

Simple and sufficient for current needs.

src/display/core/Translation.cpp (1)

14-35: Graceful fallback logic — good

Dispatcher and English fallback are correct and safe.

Comment on lines +15 to +19
void LanguagePlugin::setLanguage(int language) {
Translation::setLanguage(static_cast<Language>(language));
controller->getSettings().setLanguage(language);
controller->getPluginManager()->emit("ui:refresh", {});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Validate and short-circuit language changes to prevent invalid state and unnecessary writes

Currently, any integer is cast to Language and persisted. Add range checking and skip if unchanged.

 void LanguagePlugin::setLanguage(int language) {
-    Translation::setLanguage(static_cast<Language>(language));
-    controller->getSettings().setLanguage(language);
-    controller->getPluginManager()->emit("ui:refresh", {});
+    auto newLang = static_cast<Language>(language);
+    // Clamp to known enum values
+    if (newLang != Language::ENGLISH && newLang != Language::GERMAN &&
+        newLang != Language::FRENCH && newLang != Language::SPANISH) {
+        // Ignore invalid values
+        return;
+    }
+    if (Translation::getLanguage() == newLang) {
+        return; // no-op if same language
+    }
+    Translation::setLanguage(newLang);
+    controller->getSettings().setLanguage(static_cast<int>(newLang));
+    controller->getPluginManager()->emit("ui:refresh", {});
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void LanguagePlugin::setLanguage(int language) {
Translation::setLanguage(static_cast<Language>(language));
controller->getSettings().setLanguage(language);
controller->getPluginManager()->emit("ui:refresh", {});
}
void LanguagePlugin::setLanguage(int language) {
auto newLang = static_cast<Language>(language);
// Clamp to known enum values
if (newLang != Language::ENGLISH && newLang != Language::GERMAN &&
newLang != Language::FRENCH && newLang != Language::SPANISH) {
// Ignore invalid values
return;
}
if (Translation::getLanguage() == newLang) {
return; // no-op if same language
}
Translation::setLanguage(newLang);
controller->getSettings().setLanguage(static_cast<int>(newLang));
controller->getPluginManager()->emit("ui:refresh", {});
}
🤖 Prompt for AI Agents
In src/display/plugins/LanguagePlugin.cpp around lines 15-19, add validation and
a short-circuit so only valid enum values are applied and persisted: first check
that the passed int is within the Language enum range (reject out-of-range
values), then if the target Language equals the current language (from
Translation::getLanguage() or controller->getSettings().getLanguage()) return
early; otherwise call Translation::setLanguage(...) and persist via
controller->getSettings().setLanguage(...) and emit "ui:refresh" — perform the
checks before mutating state to avoid invalid state and unnecessary writes.

Comment on lines +40 to +51
void LanguagePlugin::setConfig(const String &config) {
if (config.indexOf("\"language\":") != -1) {
int start = config.indexOf("\"language\":") + 11;
int end = config.indexOf(",", start);
if (end == -1) end = config.indexOf("}", start);
if (end != -1) {
String langStr = config.substring(start, end);
int language = langStr.toInt();
setLanguage(language);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make config parsing tolerant to whitespace and validate bounds

Parsing by fixed offset fails with spaces and will silently accept invalid values. Trim and bound-check before applying.

 void LanguagePlugin::setConfig(const String &config) {
-    if (config.indexOf("\"language\":") != -1) {
-        int start = config.indexOf("\"language\":") + 11;
-        int end = config.indexOf(",", start);
-        if (end == -1) end = config.indexOf("}", start);
-        if (end != -1) {
-            String langStr = config.substring(start, end);
-            int language = langStr.toInt();
-            setLanguage(language);
-        }
-    }
+    int keyPos = config.indexOf("\"language\"");
+    if (keyPos == -1) return;
+    int colon = config.indexOf(":", keyPos);
+    if (colon == -1) return;
+    int end = config.indexOf(",", colon);
+    if (end == -1) end = config.indexOf("}", colon);
+    if (end == -1) return;
+    String langStr = config.substring(colon + 1, end);
+    langStr.trim();
+    if (langStr.length() == 0) return;
+    int language = langStr.toInt(); // tolerates leading '+'/'-' and spaces
+    setLanguage(language);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void LanguagePlugin::setConfig(const String &config) {
if (config.indexOf("\"language\":") != -1) {
int start = config.indexOf("\"language\":") + 11;
int end = config.indexOf(",", start);
if (end == -1) end = config.indexOf("}", start);
if (end != -1) {
String langStr = config.substring(start, end);
int language = langStr.toInt();
setLanguage(language);
}
}
}
void LanguagePlugin::setConfig(const String &config) {
int keyPos = config.indexOf("\"language\"");
if (keyPos == -1) return;
int colon = config.indexOf(":", keyPos);
if (colon == -1) return;
int end = config.indexOf(",", colon);
if (end == -1) end = config.indexOf("}", colon);
if (end == -1) return;
String langStr = config.substring(colon + 1, end);
langStr.trim();
if (langStr.length() == 0) return;
int language = langStr.toInt(); // tolerates leading '+'/'-' and spaces
setLanguage(language);
}
🤖 Prompt for AI Agents
In src/display/plugins/LanguagePlugin.cpp around lines 40 to 51, the config
parsing uses a fixed offset after "\"language\":" which breaks when there is
whitespace and then silently accepts invalid values; change the logic to locate
the "\"language\"" token, find the next ':' position dynamically, set start =
colonIndex + 1, extract substring from start to the following ',' or '}', then
trim surrounding whitespace and optional quotes from that substring, parse it to
an integer only after trimming, and validate the parsed value against the
allowed language bounds (e.g. non-negative and <= your MAX_LANGUAGE or check
against the Language enum range) before calling setLanguage; if parsing fails or
the value is out of bounds, do not call setLanguage and handle the error (log or
ignore) accordingly.

Comment on lines +560 to 565
lv_label_set_text_fmt(ui_ProfileScreen_targetTemp2, "%d°C", static_cast<int>(currentProfileChoice.temperature));
unsigned int phaseCount = currentProfileChoice.getPhaseCount();
unsigned int stepCount = currentProfileChoice.phases.size();
lv_label_set_text_fmt(ui_ProfileScreen_stepsLabel, "%d step%s", stepCount, stepCount > 1 ? "s" : "");
lv_label_set_text_fmt(ui_ProfileScreen_phasesLabel, "%d phase%s", phaseCount, phaseCount > 1 ? "s" : "");
lv_label_set_text_fmt(ui_ProfileScreen_stepsLabel, "%d %s%s", stepCount, TR(STEP), stepCount > 1 ? "s" : "");
lv_label_set_text_fmt(ui_ProfileScreen_phasesLabel, "%d %s%s", phaseCount, TR(PHASE), phaseCount > 1 ? "s" : "");

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Pluralization will be wrong for non-English — use separate plural keys

Appending "s" breaks in German/French/Spanish. Since Translation already provides STEPS/PHASES, switch to singular/plural keys based on count.

-            lv_label_set_text_fmt(ui_ProfileScreen_stepsLabel, "%d %s%s", stepCount, TR(STEP), stepCount > 1 ? "s" : "");
-            lv_label_set_text_fmt(ui_ProfileScreen_phasesLabel, "%d %s%s", phaseCount, TR(PHASE), phaseCount > 1 ? "s" : "");
+            lv_label_set_text_fmt(ui_ProfileScreen_stepsLabel, "%d %s", stepCount, stepCount == 1 ? TR(STEP) : TR(STEPS));
+            lv_label_set_text_fmt(ui_ProfileScreen_phasesLabel, "%d %s", phaseCount, phaseCount == 1 ? TR(PHASE) : TR(PHASES));
🤖 Prompt for AI Agents
In src/display/ui/default/DefaultUI.cpp around lines 560 to 565, the code
appends "s" to create plurals which breaks for non-English locales; instead
select the correct translation key for singular vs plural. Replace the
concatenation logic with a conditional that chooses the plural translation key
provided by the translations (e.g. use TR(STEP) when stepCount == 1 and
TR(STEPS) otherwise, and likewise TR(PHASE) vs TR(PHASES)), and use that
translation in the lv_label_set_text_fmt calls so the UI shows proper localized
singular/plural words.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
src/display/plugins/LanguagePlugin.h (2)

11-12: Overrides now match Plugin’s interface (setup/loop) — good fix

This aligns with Plugin.h’s pure virtuals and resolves the earlier mismatch.


1-6: Include Plugin.h; deriving from an incomplete base will not compile

This header derives from Plugin but does not include its definition. Forward-declaring a base is insufficient; the base must be complete at the point of inheritance.

Apply:

 #ifndef LANGUAGE_PLUGIN_H
 #define LANGUAGE_PLUGIN_H
 
+// Required for base-class definition
+#include <display/core/Plugin.h>
+
 class Controller;
 class PluginManager;
🧹 Nitpick comments (3)
src/display/plugins/LanguagePlugin.h (1)

14-15: Consider using the strongly-typed Language enum for setLanguage

For type safety and self-documenting intent, prefer Language over int. This will require including Translation.h in this header (or moving setLanguage to the .cpp and keeping it out of the header if it’s not used externally).

Option A (type-safe):

-#include <display/core/Plugin.h>
+#include <display/core/Plugin.h>
+#include <display/core/Translation.h>
 ...
 private:
-    void setLanguage(int language);
+    void setLanguage(Language language);

Option B (keep header lean): remove setLanguage from the header and define it only in the .cpp as a private helper not exposed in the class declaration.

src/display/core/TranslationStrings.h (1)

4-4: Remove unused include to keep headers slim

Translation.h isn’t referenced in this header; consider dropping it.

-#include "Translation.h"
src/display/core/Translation.cpp (1)

46-53: Optional: prefer and consistent C++ headers

Minor style nit: prefer over <stdarg.h> to align with C++ headers.

-#include <stdarg.h>
+#include <cstdarg>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fc7ec6f and c49eb97.

📒 Files selected for processing (5)
  • CONTRIBUTING.md (1 hunks)
  • src/display/core/Translation.cpp (1 hunks)
  • src/display/core/Translation.h (1 hunks)
  • src/display/core/TranslationStrings.h (1 hunks)
  • src/display/plugins/LanguagePlugin.h (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CONTRIBUTING.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/display/core/Translation.h
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/display/plugins/LanguagePlugin.h (5)
src/display/core/PluginManager.h (1)
  • PluginManager (14-33)
src/display/plugins/LanguagePlugin.cpp (3)
  • LanguagePlugin (4-6)
  • setLanguage (15-19)
  • setLanguage (15-15)
src/display/core/Plugin.h (1)
  • Plugin (6-12)
src/display/core/Translation.cpp (2)
  • setLanguage (7-9)
  • setLanguage (7-7)
src/display/core/Settings.cpp (2)
  • setLanguage (383-386)
  • setLanguage (383-383)
src/display/core/Translation.cpp (2)
src/display/plugins/LanguagePlugin.cpp (2)
  • setLanguage (15-19)
  • setLanguage (15-15)
src/display/core/Settings.cpp (2)
  • setLanguage (383-386)
  • setLanguage (383-383)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: deploy
  • GitHub Check: test
🔇 Additional comments (1)
src/display/core/TranslationStrings.h (1)

30-49: German umlauts on embedded displays

Verify the display font supports ä/ö/ü/ß; otherwise consider transliteration (ae/oe/ue/ss) or updating fonts.

@@ -0,0 +1,55 @@
#include "Translation.h"
#include "TranslationStrings.h"
#include <stdarg.h>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Include cstdio for vsnprintf declaration

vsnprintf is declared in /<stdio.h>. Without it, some toolchains will error.

 #include <stdarg.h>
+#include <cstdio>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#include <stdarg.h>
#include <stdarg.h>
#include <cstdio>
🤖 Prompt for AI Agents
In src/display/core/Translation.cpp around line 3, add the standard C header
that declares vsnprintf (e.g., include <cstdio> or <stdio.h>) because the
current file only includes <stdarg.h> and some toolchains require the proper
declaration for vsnprintf; update the includes to #include <cstdio> (or #include
<stdio.h>) alongside <stdarg.h> so the vsnprintf prototype is available.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/display/core/TranslationStrings.h (3)

1-3: Include what you use: add and prefer std::size_t for NUM_KEYS

Using size_t without including relies on transitive includes. Let’s make the header self-contained and use std::size_t.

 #ifndef TRANSLATION_STRINGS_H
 #define TRANSLATION_STRINGS_H
+#include <cstddef>
@@
-    static constexpr size_t NUM_KEYS = 18;
+    static constexpr std::size_t NUM_KEYS = 18;

Also applies to: 12-12


4-4: Remove unused forward declaration of TranslationKey (and avoid potential namespace mismatch)

This header doesn’t use TranslationKey. Keeping an unused forward decl in the global namespace risks type duplication if the real enum lives in a namespace.

-enum class TranslationKey;

12-12: Single source of truth for key count

NUM_KEYS should originate from the enum (e.g., a COUNT sentinel) to prevent drift. If feasible, define COUNT in Translation.h and derive NUM_KEYS from it; then include Translation.h here and drop the hard-coded 18.

Example (outside this diff, shown for clarity):

// Translation.h
#include <cstddef>
enum class TranslationKey : unsigned char {
  BREW, STEAM, WATER, GRIND, SELECT_PROFILE, STARTING, UPDATING,
  TEMPERATURE_ERROR, AUTOTUNING, FINISHED, INFUSION, BREW_PHASE,
  STEPS, PHASES, STEP, PHASE, SELECTED_PROFILE, RESTART_REQUIRED,
  COUNT
};
inline constexpr std::size_t NUM_KEYS =
  static_cast<std::size_t>(TranslationKey::COUNT);

// TranslationStrings.h
#include "Translation.h"
// ... use NUM_KEYS from above and sized extern arrays

Happy to generate a follow-up patch if you confirm where TranslationKey is declared.

src/display/core/TranslationStrings.cpp (1)

91-91: Add static_asserts to prevent out-of-sync tables at compile time

If a table’s element count diverges from NUM_KEYS, you’ll get an immediate compile error instead of a runtime misindex.

 }
+// Compile-time sanity checks
+static_assert(sizeof(TranslationStrings::ENGLISH) / sizeof(TranslationStrings::ENGLISH[0]) == TranslationStrings::NUM_KEYS, "ENGLISH length mismatch");
+static_assert(sizeof(TranslationStrings::GERMAN)  / sizeof(TranslationStrings::GERMAN[0])  == TranslationStrings::NUM_KEYS, "GERMAN length mismatch");
+static_assert(sizeof(TranslationStrings::FRENCH)  / sizeof(TranslationStrings::FRENCH[0])  == TranslationStrings::NUM_KEYS, "FRENCH length mismatch");
+static_assert(sizeof(TranslationStrings::SPANISH) / sizeof(TranslationStrings::SPANISH[0]) == TranslationStrings::NUM_KEYS, "SPANISH length mismatch");
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8012c4f and 5516d9f.

📒 Files selected for processing (2)
  • src/display/core/TranslationStrings.cpp (1 hunks)
  • src/display/core/TranslationStrings.h (1 hunks)
🔇 Additional comments (3)
src/display/core/TranslationStrings.h (1)

6-10: Bold move to extern declarations; this fixes per-TU duplication and aligns with prior review guidance.

This isolates definitions to the .cpp, improving flash/RAM usage on embedded targets.

src/display/core/TranslationStrings.cpp (2)

27-46: Verify UTF-8 rendering for non-ASCII characters on target display/font

German and Spanish strings include diacritics (ä, ö, Ó). Ensure:

  • Source files are saved as UTF-8 without BOM.
  • Toolchain treats input as UTF-8.
  • The glyphs exist in the runtime font and your text renderer handles UTF-8 correctly.

If your display stack is ASCII-only, consider ASCII fallbacks (e.g., “auswaehlen”, “benoetigt”, “INFUSION”) or adding the required glyphs to the font.

Also applies to: 71-90


3-24: Definitions centralized in .cpp look good

Good job moving from header to a single definition unit. The arrays align with 18 keys and comments map each entry unambiguously.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/display/core/TranslationStrings.h (2)

7-7: Avoid drift: derive NUM_KEYS from the canonical key enum

If the translation key enum/count already exists (e.g., in Translation.h), consider deriving NUM_KEYS from it instead of hardcoding 18. This removes a maintenance hotspot when keys are added/removed.


9-12: Add compile-time guards in the .cpp to enforce table sizes

As a safety net, add static_asserts in TranslationStrings.cpp to verify each table’s length equals NUM_KEYS. This gives immediate diagnostics if a new key is added and a table isn’t updated.

Example to add in src/display/core/TranslationStrings.cpp:

#include <type_traits> // for std::extent if needed
#include "TranslationStrings.h"

namespace {
    static_assert(std::extent<decltype(TranslationStrings::ENGLISH)>::value == TranslationStrings::NUM_KEYS, "ENGLISH table size mismatch");
    static_assert(std::extent<decltype(TranslationStrings::GERMAN)>::value  == TranslationStrings::NUM_KEYS, "GERMAN table size mismatch");
    static_assert(std::extent<decltype(TranslationStrings::FRENCH)>::value  == TranslationStrings::NUM_KEYS, "FRENCH table size mismatch");
    static_assert(std::extent<decltype(TranslationStrings::SPANISH)>::value == TranslationStrings::NUM_KEYS, "SPANISH table size mismatch");
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5516d9f and 63ee27c.

📒 Files selected for processing (2)
  • src/display/core/TranslationStrings.cpp (1 hunks)
  • src/display/core/TranslationStrings.h (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/display/core/TranslationStrings.cpp
🔇 Additional comments (1)
src/display/core/TranslationStrings.h (1)

9-12: Good: extern arrays with explicit compile-time bounds

Declaring the language tables as extern with [NUM_KEYS] addresses ODR bloat and catches mismatches at compile time. Nice follow-through on prior feedback.

@sonarqubecloud
Copy link

@ManfredRichthofen
Copy link
Contributor Author

Per the discord conversation the umlaut and other special ansi characters probably not render they can be converted to standard ü -> u or we can just not translate the display there are not many words

@jniebuhr jniebuhr added this to the v1.7.0 milestone Aug 21, 2025
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.

2 participants