i18n#3
Conversation
…messages across the application
|
Warning Review limit reached
Next review available in: 41 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
WalkthroughDie PR führt ein vollständiges i18n-System ein (app/i18n.py mit YAML-Übersetzungen für Deutsch/Englisch), verdrahtet es in der App-Initialisierung (Locale-Erkennung, Jinja-Global t, Kontext-Globals), und ersetzt sämtliche fest kodierten Texte in Backend-Services, Routen und über 30 Templates durch übersetzbare Changesi18n-Einführung
Estimated code review effort: 3 (Moderate) | ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…tion in picker response
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
app/routes/settings.py (1)
215-216: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winHardcodierte englische Strings in
test_email()nicht lokalisiert.
subjectundbodyintest_email()sind noch fest kodierte englische Strings, während der HTML-Body bereits lokalisiert wird. Nutzer mit deutscher Spracheinstellung sehen eine englische Betreffzeile und Klartext-E-Mail.🌐 Vorgeschlagene Lokalisierung
- subject="SpoolBeacon Test Email", - body="SpoolBeacon test message: email integration is working.", + subject=t("settings.email_test.subject"), + body=t("settings.email_test.body"),🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/routes/settings.py` around lines 215 - 216, The test_email() response still hardcodes English text in the email subject and plain-text body while the HTML body is localized. Update the subject and body values in test_email() to use the same localization pattern as the HTML content, using the existing translation mechanism and related message keys so the email is fully localized for the user’s language.app/templates/inventory/spool_label.html (1)
2-2: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
lang-Attribut ist hartkodiert auf"en".Das
lang="en"im<html>-Tag sollte die aktuelle Sprache des Nutzers widerspiegeln. Da dieses Template nichtbase.htmlerweitert, muss die Locale direkt referenziert werden. Der Stack-Kontext erwähnt, dasscurrent_localeals Kontext-Global verfügbar ist.Alternativ kann
get_locale()verwendet werden, falls es als Jinja-Global registriert ist.🛡️ Proposed fix
-<html lang="en" class="h-full"> +<html lang="{{ current_locale }}" class="h-full">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/templates/inventory/spool_label.html` at line 2, The `<html>` tag in the spool label template is hardcoded with `lang="en"`, so update the `spool_label.html` markup to derive the language from the active locale instead of a fixed value. Use the available locale source mentioned in the stack context, such as `current_locale`, or `get_locale()` if that Jinja global is registered, and wire it directly into the `html` language attribute so the template reflects the user’s current language.app/templates/inventory/snapshot_history.html (1)
55-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winHardcodiertes Datumsformat ist nicht lokalisierbar.
strftime('%d.%m.%Y')verwendet das deutsche Datumsformat (TT.MM.JJJJ). Für englischsprachige Nutzer sollte das Format%m/%d/%Yoder ein lokalisiertes Äquivalent verwendet werden. Da dieser PR die Internationalisierung einführt, sollte auch die Datumsformatierung lokalisierbar sein.Eine Möglichkeit wäre, das Format über die YAML-Übersetzungsdateien bereitzustellen und
strftimedamit zu parametrisieren, z. B.{{ snap.captured_at.strftime(t('common.date_format')) }}.🛡️ Vorschlag zur Lokalisierung des Datumsformats
- <span class="text-gray-300">{{ snap.captured_at.strftime('%d.%m.%Y') }}</span> + <span class="text-gray-300">{{ snap.captured_at.strftime(t('common.date_format')) }}</span>Und in den YAML-Dateien ergänzen:
# de.yaml common: date_format: "%d.%m.%Y" # en.yaml common: date_format: "%m/%d/%Y"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/templates/inventory/snapshot_history.html` around lines 55 - 56, The date shown in the snapshot history template is hardcoded with a German-only format, so make the date formatting localization-aware in the snapshot rendering path. Update the `snap.captured_at.strftime(...)` usage in `snapshot_history.html` to read the format string from translations (for example via `t('common.date_format')`), and add matching localized `common.date_format` values in the YAML language files so `snap.captured_at` renders correctly for each locale.
🧹 Nitpick comments (4)
app/__init__.py (1)
46-56: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winDB-Abfrage bei jedem Request für Locale-Erkennung — Setting ändert sich selten.
load_localefragt bei jedem nicht-static Request die Datenbank nachapp.languageab. Da sich die Spracheinstellung selten ändert, fügt dies jeder Anfrage eine unnötige Datenbankabfrage hinzu. Ein einfacher TTL-Cache oder ein In-Memory-Cache würde die Latenz reduzieren.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/__init__.py` around lines 46 - 56, The load_locale before_request hook is doing a database lookup on every non-static request for app.language, which adds avoidable latency. Update the load_locale logic to use a small TTL or in-memory cache for the locale value instead of querying through get_db and session.scalar each time, while still falling back to DEFAULT_LOCALE when the cached or loaded value is not in SUPPORTED_LOCALES.app/routes/inventory.py (1)
1288-1298: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winKonkatenation übersetzter Textfragmente vermeiden.
msgwird aus bis zu drei separat übersetzten Teilstrings zusammengesetzt (price_captured+ optionalprice_availability_suffix+ optionaltarget_reached_suffix). Das ist ein klassisches i18n-Antipattern: Satzbau und Wortstellung unterscheiden sich je nach Sprache, wodurch die konkatenierte Nachricht in anderen Locales grammatikalisch falsch oder unpassend formatiert sein kann.Empfehlung: vollständige Satz-Templates mit optionalen Platzhaltern in der YAML definieren (z. B. via bedingter Formulierung oder separaten, vollständigen Varianten je Kombination), statt Fragmente zur Laufzeit anzuhängen.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/routes/inventory.py` around lines 1288 - 1298, Die Flash-Nachricht in inventory.py wird derzeit aus mehreren übersetzten Fragmenten zusammengesetzt, was für i18n problematisch ist. Passe die Logik rund um die Nachrichtenerzeugung in der Flash-Branch so an, dass statt des schrittweisen Anhängens von inventory.flash.price_captured, inventory.flash.price_availability_suffix und inventory.flash.target_reached_suffix vollständige, locale-spezifische Satzvorlagen verwendet werden. Lege dafür in den Übersetzungen passende komplette Varianten mit Platzhaltern bzw. kombinationsabhängigen Texten an und rufe in der aktuellen Flash-Logik nur die jeweils fertige Nachricht ab.app/templates/settings/index.html (1)
325-327: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
confirm()-Dialoge sollten| tojsonverwendenBeide
confirm()-Aufrufe betten Übersetzungstexte in einfachen Anführungszeichen in JS-Strings innerhalb von HTML-Attributen ein. Bei Apostrophen in der Übersetzung bricht der JS-String. Verwende| tojsonfür robustes Escaping.♻️ Vorgeschlagene Fixes
- onclick="return confirm('{{ t('settings.inventory.spool_code.regenerate_confirm') }}')" + onclick="return confirm({{ t('settings.inventory.spool_code.regenerate_confirm') | tojson }})"- onclick="return confirm('{{ t('settings.backup.import.confirm') }}')" + onclick="return confirm({{ t('settings.backup.import.confirm') | tojson }})"Also applies to: 377-377
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/templates/settings/index.html` around lines 325 - 327, The confirm() handlers in the settings template are interpolating translated text directly into JavaScript strings, which can break when the translation contains apostrophes. Update the affected onclick confirm calls in the settings page template to use the translation filter with tojson so the message is safely escaped in the HTML attribute. Apply the same fix to both confirm() usages referenced in this template and keep the existing confirm behavior unchanged.app/templates/prints/index.html (1)
57-57: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
confirm()-Dialog sollte| tojsonverwendenDer Übersetzungstext wird in einfachen Anführungszeichen in einen JS-String innerhalb eines HTML-Attributs eingebettet. Wenn die Übersetzung Apostrophe enthält (z. B.
"Don't delete"), bricht der JS-String. Verwende| tojsonfür robustes Escaping – dieser Pattern wird inrule_form.html:368bereits korrekt angewendet.♻️ Vorgeschlagener Fix
- onsubmit="return confirm('{{ t('prints.index.delete_confirm') }}')"> + onsubmit="return confirm({{ t('prints.index.delete_confirm') | tojson }})">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/templates/prints/index.html` at line 57, The onsubmit confirm handler in the prints index template embeds a translated string directly into a JavaScript string, which can break when the text contains apostrophes. Update the confirm call in the template to use the same escaping pattern already used in rule_form.html by applying tojson to the translation output, and keep the change localized to the onsubmit attribute so the confirm dialog remains functionally identical.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/i18n.py`:
- Around line 48-56: The t() translation helper can crash requests when a YAML
string contains format placeholders that are missing from kwargs. Update t() to
handle formatting errors safely around value.format(**kwargs), using a fallback
behavior (for example returning the unformatted translation or a safe
error-tolerant render) instead of letting KeyError propagate. Keep the change
localized to t() and preserve the existing locale lookup/fallback behavior in
_lookup/_load.
- Around line 41-45: Scheduled price-warning notifications are always rendered
with the fallback locale because get_locale() in app/i18n.py defaults to
DEFAULT_LOCALE when no request context exists. Update the scheduler flow in
app/scheduler.py and the downstream calls in
price_check_service/alert_service/notification_service to carry the user or job
locale explicitly, or set g.locale before rendering, so the notification
templates use the intended language instead of always English.
In `@app/templates/dashboard/index.html`:
- Around line 2-8: The Scheduler-Status date display in the dashboard template
is using a hardcoded German strftime format, which should be localized. Update
the relevant datetime rendering in the template using the existing translation
helper t() and a locale-specific format key (for example under
common.datetime_format) instead of embedding '%d.%m. %H:%M'. Apply the same
localized formatting anywhere in the dashboard template where the current
hardcoded date format is used, so the output depends on the active locale.
In `@app/templates/inventory/detail.html`:
- Around line 514-526: The `onsubmit` handler in `inventory/detail.html` is
building a JavaScript string from `t('inventory.detail.confirm_delete_spool',
code=spool.spool_code)` in a single-quoted `confirm()` call, which can break out
when `spool_code` contains quotes. Fix it by making the delete confirmation
message safe for JavaScript context, ideally by using a JSON-encoded/JS-escaped
value instead of direct template interpolation, and keep the change localized to
the delete form’s `confirm()` usage.
In `@app/templates/inventory/spool_form.html`:
- Line 19: The confirm() message is being interpolated into a JavaScript string
inside an HTML attribute via t(), which allows JS-context injection when values
like spool.spool_code contain quotes. Update the confirm_delete usage in the
spool_form template to pass the translated string through the JS-safe tojson
filter, and apply the same fix to the matching confirm() calls in the
manufacturers index and manufacturer_detail templates that use mfr.name and
manufacturer.name.
In `@app/templates/shop_rules/index.html`:
- Around line 94-97: The delete confirmation in the shop_rules index template is
unsafe because rule.domain is being interpolated directly into a JavaScript
string inside the onsubmit confirm() call. Update the confirm text generation
around t('shop_rules.index.delete_confirm', domain=rule.domain) so the domain is
passed through a JS-safe escaping/serialization step (not just HTML
autoescaping), or move the string construction out of inline JS entirely. Keep
the delete form behavior the same, but ensure the value used in confirm() cannot
break out of the string and trigger XSS.
In `@app/templates/users/index.html`:
- Around line 78-80: The onsubmit confirm text in the users delete form is being
inserted into a single-quoted JavaScript string without escaping, so a username
can break the handler and inject script. Update the template around the delete
form in users/index.html to serialize the translated confirm message as a safe
JS string literal before passing it to confirm(), using the existing
t('users.index.delete_confirm', username=user.username) value in a way that
escapes quotes and other special characters.
In `@app/translations/de.yaml`:
- Around line 494-495: The validation strings in the translation entries are
incomplete and cut off before the actual format description; update the
`invalid_color_hex` and `invalid_color_hex_2` messages in the translation files
so they include the full color format placeholder (for example the hex format
text) and proper sentence ending. Make the same correction in both `de.yaml` and
the matching `en.yaml` keys so the messages are consistent across locales.
In `@app/translations/en.yaml`:
- Around line 494-495: The translation entries for invalid_color_hex and
invalid_color_hex_2 are incomplete and stop mid-sentence. Update the strings in
en.yaml so they explicitly include the expected hex color format (for example,
mention `#RRGGBB`) and keep the wording consistent with the surrounding validation
messages. Locate the fix via the invalid_color_hex and invalid_color_hex_2 keys.
---
Outside diff comments:
In `@app/routes/settings.py`:
- Around line 215-216: The test_email() response still hardcodes English text in
the email subject and plain-text body while the HTML body is localized. Update
the subject and body values in test_email() to use the same localization pattern
as the HTML content, using the existing translation mechanism and related
message keys so the email is fully localized for the user’s language.
In `@app/templates/inventory/snapshot_history.html`:
- Around line 55-56: The date shown in the snapshot history template is
hardcoded with a German-only format, so make the date formatting
localization-aware in the snapshot rendering path. Update the
`snap.captured_at.strftime(...)` usage in `snapshot_history.html` to read the
format string from translations (for example via `t('common.date_format')`), and
add matching localized `common.date_format` values in the YAML language files so
`snap.captured_at` renders correctly for each locale.
In `@app/templates/inventory/spool_label.html`:
- Line 2: The `<html>` tag in the spool label template is hardcoded with
`lang="en"`, so update the `spool_label.html` markup to derive the language from
the active locale instead of a fixed value. Use the available locale source
mentioned in the stack context, such as `current_locale`, or `get_locale()` if
that Jinja global is registered, and wire it directly into the `html` language
attribute so the template reflects the user’s current language.
---
Nitpick comments:
In `@app/__init__.py`:
- Around line 46-56: The load_locale before_request hook is doing a database
lookup on every non-static request for app.language, which adds avoidable
latency. Update the load_locale logic to use a small TTL or in-memory cache for
the locale value instead of querying through get_db and session.scalar each
time, while still falling back to DEFAULT_LOCALE when the cached or loaded value
is not in SUPPORTED_LOCALES.
In `@app/routes/inventory.py`:
- Around line 1288-1298: Die Flash-Nachricht in inventory.py wird derzeit aus
mehreren übersetzten Fragmenten zusammengesetzt, was für i18n problematisch ist.
Passe die Logik rund um die Nachrichtenerzeugung in der Flash-Branch so an, dass
statt des schrittweisen Anhängens von inventory.flash.price_captured,
inventory.flash.price_availability_suffix und
inventory.flash.target_reached_suffix vollständige, locale-spezifische
Satzvorlagen verwendet werden. Lege dafür in den Übersetzungen passende
komplette Varianten mit Platzhaltern bzw. kombinationsabhängigen Texten an und
rufe in der aktuellen Flash-Logik nur die jeweils fertige Nachricht ab.
In `@app/templates/prints/index.html`:
- Line 57: The onsubmit confirm handler in the prints index template embeds a
translated string directly into a JavaScript string, which can break when the
text contains apostrophes. Update the confirm call in the template to use the
same escaping pattern already used in rule_form.html by applying tojson to the
translation output, and keep the change localized to the onsubmit attribute so
the confirm dialog remains functionally identical.
In `@app/templates/settings/index.html`:
- Around line 325-327: The confirm() handlers in the settings template are
interpolating translated text directly into JavaScript strings, which can break
when the translation contains apostrophes. Update the affected onclick confirm
calls in the settings page template to use the translation filter with tojson so
the message is safely escaped in the HTML attribute. Apply the same fix to both
confirm() usages referenced in this template and keep the existing confirm
behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: c2da5a01-7369-491c-a223-ba312700b067
📒 Files selected for processing (42)
app/__init__.pyapp/alert_service.pyapp/i18n.pyapp/notification_service.pyapp/routes/api_keys.pyapp/routes/auth.pyapp/routes/inventory.pyapp/routes/manufacturers.pyapp/routes/prints.pyapp/routes/settings.pyapp/routes/shop_rules.pyapp/routes/users.pyapp/settings_service.pyapp/templates/api_keys/index.htmlapp/templates/auth/login.htmlapp/templates/auth/setup.htmlapp/templates/base.htmlapp/templates/dashboard/index.htmlapp/templates/errors/403.htmlapp/templates/errors/404.htmlapp/templates/errors/500.htmlapp/templates/inventory/detail.htmlapp/templates/inventory/filament_form.htmlapp/templates/inventory/index.htmlapp/templates/inventory/purchase_form.htmlapp/templates/inventory/shoplink_form.htmlapp/templates/inventory/snapshot_form.htmlapp/templates/inventory/snapshot_history.htmlapp/templates/inventory/spool_form.htmlapp/templates/inventory/spool_label.htmlapp/templates/manufacturers/index.htmlapp/templates/manufacturers/manufacturer_detail.htmlapp/templates/manufacturers/manufacturer_form.htmlapp/templates/prints/index.htmlapp/templates/prints/print_form.htmlapp/templates/settings/index.htmlapp/templates/shop_rules/index.htmlapp/templates/shop_rules/rule_form.htmlapp/templates/users/index.htmlapp/templates/users/user_form.htmlapp/translations/de.yamlapp/translations/en.yaml
| def get_locale() -> str: | ||
| try: | ||
| return g.locale | ||
| except (RuntimeError, AttributeError): | ||
| return DEFAULT_LOCALE |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Description: Suche nach Scheduler-Aufrufen von notification_service-Funktionen
# um zu verifizieren, ob der Locale außerhalb des Request-Kontexts gesetzt wird.
# Suche nach Aufrufen von build_price_alert in alert_service.py oder scheduler.py
rg -n -C5 'build_price_alert_discord_embed\|build_price_alert_email_html\|send_discord\|send_email' --type py app/alert_service.py app/scheduler.py 2>/dev/null
# Prüfe, ob g.locale im Scheduler gesetzt wird
rg -n 'g\.locale\|get_locale\|set_locale\|locale' --type py app/scheduler.py 2>/dev/nullRepository: SytxLabs/SpoolBeacon
Length of output: 158
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Überblick über relevante Dateien
git ls-files | rg '^(app/|.*scheduler.*\.py$|.*notification_service.*\.py$|.*i18n\.py$)'
# Relevante Stellen in app/i18n.py anzeigen
if [ -f app/i18n.py ]; then
echo "### app/i18n.py"
cat -n app/i18n.py | sed -n '1,220p'
fi
# Relevante Stellen in Notification-/Scheduler-Dateien suchen
rg -n -C 4 'build_price_alert_discord_embed|build_price_alert_email_html|send_discord|send_email|get_locale|g\.locale|before_request|load_locale|locale' app 2>/dev/null
# Falls Dateien existieren, gezielt die relevanten Abschnitte anzeigen
for f in app/notification_service.py app/scheduler.py app/alert_service.py; do
if [ -f "$f" ]; then
echo "### $f"
cat -n "$f" | sed -n '1,260p'
fi
doneRepository: SytxLabs/SpoolBeacon
Length of output: 42394
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Wer ruft die Alert-Benachrichtigungen auf?
rg -n -C 4 'dispatch_alert_notifications|maybe_create_alerts|build_price_alert_discord_embed|build_price_alert_email_html' app
# Den Preischeck-Pfad genauer ansehen
cat -n app/price_check_service.py | sed -n '1,260p'Repository: SytxLabs/SpoolBeacon
Length of output: 17534
Geplante Preiswarnungen lokalisieren get_locale() fällt außerhalb eines Request-Kontexts immer auf DEFAULT_LOCALE zurück. Der Scheduler-Pfad (app/scheduler.py → app/price_check_service.py → app/alert_service.py → app/notification_service.py) setzt keinen Locale-Kontext, ამიტომ die geplanten Discord-/E-Mail-Benachrichtigungen werden immer auf Englisch erzeugt. Den Locale im Scheduler-Pfad explizit mitführen oder vor dem Rendern setzen.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/i18n.py` around lines 41 - 45, Scheduled price-warning notifications are
always rendered with the fallback locale because get_locale() in app/i18n.py
defaults to DEFAULT_LOCALE when no request context exists. Update the scheduler
flow in app/scheduler.py and the downstream calls in
price_check_service/alert_service/notification_service to carry the user or job
locale explicitly, or set g.locale before rendering, so the notification
templates use the intended language instead of always English.
| def t(key: str, **kwargs) -> str: | ||
| """Translate `key` (dot-notation path into the yaml files) for the current request locale.""" | ||
| locale = get_locale() | ||
| value = _lookup(_load(locale), key) | ||
| if value is None and locale != DEFAULT_LOCALE: | ||
| value = _lookup(_load(DEFAULT_LOCALE), key) | ||
| if value is None: | ||
| return key | ||
| return value.format(**kwargs) if kwargs else value |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
str.format() ohne Fehlerbehandlung kann Requests zum Absturz bringen.
Wenn ein YAML-Übersetzungstext einen Platzhalter enthält, der nicht in den übergebenen kwargs steht (z. B. Tippfehler wie {nam} statt {name}), löst value.format(**kwargs) eine KeyError-Ausnahme aus. Da t() in Templates und Route-Handlern aufgerufen wird, führt dies zu einem 500-Fehler für den Endbenutzer.
🛡️ Vorgeschlagene Fehlerbehandlung
def t(key: str, **kwargs) -> str:
"""Translate `key` (dot-notation path into the yaml files) for the current request locale."""
locale = get_locale()
value = _lookup(_load(locale), key)
if value is None and locale != DEFAULT_LOCALE:
value = _lookup(_load(DEFAULT_LOCALE), key)
if value is None:
return key
- return value.format(**kwargs) if kwargs else value
+ if kwargs:
+ try:
+ return value.format(**kwargs)
+ except (KeyError, IndexError):
+ return value
+ return value📝 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.
| def t(key: str, **kwargs) -> str: | |
| """Translate `key` (dot-notation path into the yaml files) for the current request locale.""" | |
| locale = get_locale() | |
| value = _lookup(_load(locale), key) | |
| if value is None and locale != DEFAULT_LOCALE: | |
| value = _lookup(_load(DEFAULT_LOCALE), key) | |
| if value is None: | |
| return key | |
| return value.format(**kwargs) if kwargs else value | |
| def t(key: str, **kwargs) -> str: | |
| """Translate `key` (dot-notation path into the yaml files) for the current request locale.""" | |
| locale = get_locale() | |
| value = _lookup(_load(locale), key) | |
| if value is None and locale != DEFAULT_LOCALE: | |
| value = _lookup(_load(DEFAULT_LOCALE), key) | |
| if value is None: | |
| return key | |
| if kwargs: | |
| try: | |
| return value.format(**kwargs) | |
| except (KeyError, IndexError): | |
| return value | |
| return value |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/i18n.py` around lines 48 - 56, The t() translation helper can crash
requests when a YAML string contains format placeholders that are missing from
kwargs. Update t() to handle formatting errors safely around
value.format(**kwargs), using a fallback behavior (for example returning the
unformatted translation or a safe error-tolerant render) instead of letting
KeyError propagate. Keep the change localized to t() and preserve the existing
locale lookup/fallback behavior in _lookup/_load.
| {% block title %}{{ t('dashboard.page_title') }} – {{ t('app.title') }}{% endblock %} | ||
|
|
||
| {% block content %} | ||
|
|
||
| <div class="mb-8"> | ||
| <h1 class="page-title">Dashboard</h1> | ||
| <p class="page-subtitle">Filament inventory at a glance</p> | ||
| <h1 class="page-title">{{ t('dashboard.heading') }}</h1> | ||
| <p class="page-subtitle">{{ t('dashboard.subtitle') }}</p> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Datumsformat in Scheduler-Status nicht lokalisiert.
Die strftime('%d.%m. %H:%M')-Aufrufe auf den Zeilen 338 und 345 verwenden ein hartcodiertes deutsches Datumsformat. Für eine korrekte i18n-Implementierung sollte das Datumsformat locale-abhängig sein.
🌐 Vorschlag: Locale-abhängiges Datumsformat
Fügen Sie einen Übersetzungsschlüssel für das Datumsformat hinzu (z. B. in de.yaml/en.yaml):
# de.yaml
common:
datetime_format: '%d.%m. %H:%M'
# en.yaml
common:
datetime_format: '%m/%d/%Y %H:%M'Verwendung im Template:
- {{ t('dashboard.scheduler.next_run_label') }} <span class="text-gray-400 font-mono">{{ scheduler.next_run.strftime('%d.%m. %H:%M') }}</span>
+ {{ t('dashboard.scheduler.next_run_label') }} <span class="text-gray-400 font-mono">{{ scheduler.next_run.strftime(t('common.datetime_format')) }}</span>- {{ t('dashboard.scheduler.last_run_label') }} <span class="text-gray-400 font-mono">{{ scheduler.last_run.strftime('%d.%m. %H:%M') }}</span>
+ {{ t('dashboard.scheduler.last_run_label') }} <span class="text-gray-400 font-mono">{{ scheduler.last_run.strftime(t('common.datetime_format')) }}</span>Also applies to: 17-17, 27-33, 42-48, 59-65, 77-77, 86-86, 112-112, 127-137, 162-162, 174-186, 195-212, 243-243, 255-255, 264-264, 274-280, 297-297, 307-307, 326-352
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/templates/dashboard/index.html` around lines 2 - 8, The Scheduler-Status
date display in the dashboard template is using a hardcoded German strftime
format, which should be localized. Update the relevant datetime rendering in the
template using the existing translation helper t() and a locale-specific format
key (for example under common.datetime_format) instead of embedding '%d.%m.
%H:%M'. Apply the same localized formatting anywhere in the dashboard template
where the current hardcoded date format is used, so the output depends on the
active locale.
| <a href="/inventory/{{ product.id }}/spool/{{ spool.id }}/label" title="{{ t('inventory.detail.title_print_label') }}" target="_blank" | ||
| class="p-1.5 rounded-lg text-gray-500 hover:text-white hover:bg-gray-800 transition-colors"> | ||
| {{ icon_qr() }} | ||
| </a> | ||
| {% if not is_viewer %} | ||
| <a href="/inventory/{{ product.id }}/spool/{{ spool.id }}/edit" title="Edit spool" | ||
| <a href="/inventory/{{ product.id }}/spool/{{ spool.id }}/edit" title="{{ t('inventory.detail.title_edit_spool') }}" | ||
| class="p-1.5 rounded-lg text-gray-500 hover:text-white hover:bg-gray-800 transition-colors"> | ||
| {{ icon_pencil() }} | ||
| </a> | ||
| <form method="POST" action="/inventory/{{ product.id }}/spool/{{ spool.id }}/delete" | ||
| class="inline" onsubmit="return confirm('Delete spool {{ spool.spool_code }}? This cannot be undone.')"> | ||
| class="inline" onsubmit="return confirm('{{ t('inventory.detail.confirm_delete_spool', code=spool.spool_code) }}')"> | ||
| <input type="hidden" name="_csrf_token" value="{{ csrf_token() }}"> | ||
| <button type="submit" title="Delete spool" | ||
| <button type="submit" title="{{ t('inventory.detail.title_delete_spool') }}" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Unsichere String-Interpolation im confirm()-Aufruf (Zeile 524).
spool.spool_code wird über t('inventory.detail.confirm_delete_spool', code=spool.spool_code) in einen einfach gequoteten JS-String innerhalb des onsubmit-Attributs eingesetzt. Jinja escaped zwar HTML-Sonderzeichen (' → '), aber der Browser dekodiert HTML-Entities im Attributwert, bevor der Inline-Handler als JS ausgeführt wird. Enthält spool_code ein ', bricht der String aus und ermöglicht Skript-Injection.
🛡️ Vorschlag: sichere Einbettung via `tojson`
<form method="POST" action="/inventory/{{ product.id }}/spool/{{ spool.id }}/delete"
- class="inline" onsubmit="return confirm('{{ t('inventory.detail.confirm_delete_spool', code=spool.spool_code) }}')">
+ class="inline" onsubmit="return confirm({{ t('inventory.detail.confirm_delete_spool', code=spool.spool_code)|tojson }})">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/templates/inventory/detail.html` around lines 514 - 526, The `onsubmit`
handler in `inventory/detail.html` is building a JavaScript string from
`t('inventory.detail.confirm_delete_spool', code=spool.spool_code)` in a
single-quoted `confirm()` call, which can break out when `spool_code` contains
quotes. Fix it by making the delete confirmation message safe for JavaScript
context, ideally by using a JSON-encoded/JS-escaped value instead of direct
template interpolation, and keep the change localized to the delete form’s
`confirm()` usage.
|
|
||
| <form id="form-delete-spool" method="POST" action="/inventory/{{ product.id }}/spool/{{ spool.id }}/delete" | ||
| onsubmit="return confirm('Delete spool {{ spool.spool_code }}? This cannot be undone.')"> | ||
| onsubmit="return confirm('{{ t('inventory.spool_form.confirm_delete', code=spool.spool_code) }}')"> |
There was a problem hiding this comment.
🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win
XSS-Schwachstelle in confirm()-Dialog durch JS-Kontext-Injektion.
spool.spool_code wird über t() in einen JavaScript-String innerhalb eines HTML-Attributs interpoliert. Jinja-Autoescaping wandelt ' in ' um, aber der Browser dekodiert HTML-Entities in Attributwerten, bevor der JS-Code ausgeführt wird. Enthält spool.spool_code ein einfaches Anführungszeichen, bricht die JS-Syntax. Mit einem präparierten Wert wie '); alert('xss entsteht eine XSS-Lücke.
Dieselbe Schwachstelle exists auch in manufacturers/index.html (Line 76) und manufacturers/manufacturer_detail.html (Line 26) mit name=mfr.name bzw. name=manufacturer.name.
Fix: Den |tojson-Filter verwenden, um den String sicher für JS-Kontext zu escapen.
🔒 Proposed fix: Use |tojson for safe JS interpolation
- onsubmit="return confirm('{{ t('inventory.spool_form.confirm_delete', code=spool.spool_code) }}')">
+ onsubmit="return confirm({{ t('inventory.spool_form.confirm_delete', code=spool.spool_code)|tojson }})">|tojson erzeugt einen gültigen JS-String-Literal (z. B. "Delete SPOOL-001?") und escapet alle gefährlichen Zeichen korrekt für den JS-Kontext.
📝 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.
| onsubmit="return confirm('{{ t('inventory.spool_form.confirm_delete', code=spool.spool_code) }}')"> | |
| onsubmit="return confirm({{ t('inventory.spool_form.confirm_delete', code=spool.spool_code)|tojson }})"> |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/templates/inventory/spool_form.html` at line 19, The confirm() message is
being interpolated into a JavaScript string inside an HTML attribute via t(),
which allows JS-context injection when values like spool.spool_code contain
quotes. Update the confirm_delete usage in the spool_form template to pass the
translated string through the JS-safe tojson filter, and apply the same fix to
the matching confirm() calls in the manufacturers index and manufacturer_detail
templates that use mfr.name and manufacturer.name.
| <form method="POST" action="/shop-rules/{{ rule.id }}/delete" class="inline" | ||
| onsubmit="return confirm('Delete rule for {{ rule.domain }}?')"> | ||
| onsubmit="return confirm('{{ t('shop_rules.index.delete_confirm', domain=rule.domain) }}')"> | ||
| <input type="hidden" name="_csrf_token" value="{{ csrf_token() }}"> | ||
| <button type="submit" title="Delete rule" | ||
| <button type="submit" title="{{ t('shop_rules.index.delete_rule_title') }}" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Stored XSS durch rule.domain in confirm()-Dialog
rule.domain ist Benutzereingabe und wird via t(..., domain=rule.domain) in einen JavaScript-String innerhalb eines HTML-Attributs interpoliert. Jinja2s Autoescaping schützt hier nicht: ' wird vom HTML-Parser zu ' dekodiert, bevor der JS-Interpreter es sieht. Ein Domain-Wert wie x');alert('y bricht den JS-String und ermöglicht Stored XSS – ein Angreifer kann eine Rule mit bösartiger Domain anlegen, die bei jedem Admin beim Löschen-Versuch XSS auslöst.
🔒 Vorgeschlagener Fix
- onsubmit="return confirm('{{ t('shop_rules.index.delete_confirm', domain=rule.domain) }}')">
+ onsubmit="return confirm({{ t('shop_rules.index.delete_confirm', domain=rule.domain) | tojson }})">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/templates/shop_rules/index.html` around lines 94 - 97, The delete
confirmation in the shop_rules index template is unsafe because rule.domain is
being interpolated directly into a JavaScript string inside the onsubmit
confirm() call. Update the confirm text generation around
t('shop_rules.index.delete_confirm', domain=rule.domain) so the domain is passed
through a JS-safe escaping/serialization step (not just HTML autoescaping), or
move the string construction out of inline JS entirely. Keep the delete form
behavior the same, but ensure the value used in confirm() cannot break out of
the string and trigger XSS.
| onsubmit="return confirm('{{ t('users.index.delete_confirm', username=user.username) }}')"> | ||
| <input type="hidden" name="_csrf_token" value="{{ csrf_token() }}"> | ||
| <button type="submit" title="Delete user" | ||
| <button type="submit" title="{{ t('users.index.delete_title') }}" |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Prüfen, ob der Username serverseitig validiert wird (z.B. Zeichen-Whitelist)
rg -n -A5 -B2 "username" app/routes/users.py 2>/dev/nullRepository: SytxLabs/SpoolBeacon
Length of output: 4053
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== users model =="
rg -n -A6 -B4 "class User|username" app/models.py app/models/*.py 2>/dev/null || true
echo
echo "== user form template =="
rg -n -A8 -B4 "username|maxlength|pattern" app/templates/users/user_form.html 2>/dev/null || true
echo
echo "== translation strings =="
rg -n -A3 -B1 "users.index.delete_confirm|delete_confirm" app 2>/dev/null || trueRepository: SytxLabs/SpoolBeacon
Length of output: 11145
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the template render path and any escaping helpers used for translations.
rg -n -A6 -B4 "def t\(|render_template\\(\"users/index.html\"|tojson|autoescape|Markup" app 2>/dev/null || trueRepository: SytxLabs/SpoolBeacon
Length of output: 4613
🏁 Script executed:
#!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [Path("app/templates/users/index.html"), Path("app/templates/users/user_form.html")]:
if p.exists():
print(f"== {p} ==")
for i, line in enumerate(p.read_text().splitlines(), 1):
if 1 <= i <= 130:
if "delete_confirm" in line or "username" in line or "confirm(" in line or "tojson" in line:
print(f"{i}: {line}")
PYRepository: SytxLabs/SpoolBeacon
Length of output: 1339
confirm()-Text als JS-Literal serialisieren
user.username landet ungefiltert in einem einfach gequoteten JS-String. Ein Username mit ' kann den Handler brechen und beim Absenden beliebigen JS-Code einschleusen.
Vorschlag
- onsubmit="return confirm('{{ t('users.index.delete_confirm', username=user.username) }}')">
+ onsubmit="return confirm({{ t('users.index.delete_confirm', username=user.username)|tojson }})">🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/templates/users/index.html` around lines 78 - 80, The onsubmit confirm
text in the users delete form is being inserted into a single-quoted JavaScript
string without escaping, so a username can break the handler and inject script.
Update the template around the delete form in users/index.html to serialize the
translated confirm message as a safe JS string literal before passing it to
confirm(), using the existing t('users.index.delete_confirm',
username=user.username) value in a way that escapes quotes and other special
characters.
| invalid_color_hex: Farbwert muss im Format | ||
| invalid_color_hex_2: Zweiter Farbwert muss im Format |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Originaltext der Validierungsmeldung vor der i18n-Umstellung suchen
rg -n "invalid_color_hex|Farbwert muss|Color value must" app/routes app/*.py 2>/dev/nullRepository: SytxLabs/SpoolBeacon
Length of output: 334
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== de.yaml around target lines =="
sed -n '486,502p' app/translations/de.yaml
echo
echo "== en.yaml matching section =="
rg -n -A3 -B3 "invalid_color_hex|invalid_color_hex_2" app/translations/en.yaml app/translations/de.yaml
echo
echo "== inventory validation context =="
sed -n '890,920p' app/routes/inventory.py
echo
echo "== search for color hex formatting text across repo =="
rg -n "RRGGBB|hex.*format|format.*hex|invalid_color_hex" app -g '!**/*.pyc'Repository: SytxLabs/SpoolBeacon
Length of output: 4227
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== HEX regex definition =="
rg -n "_HEX_RE|color_hex" app/routes/inventory.py app -g '!**/*.pyc'
echo
echo "== surrounding lines near regex definition =="
sed -n '1,120p' app/routes/inventory.pyRepository: SytxLabs/SpoolBeacon
Length of output: 12068
Validierungsmeldungen vervollständigen
invalid_color_hex und invalid_color_hex_2 brechen mitten im Satz ab; hier fehlt die eigentliche Formatangabe (#RRGGBB) und der Satzschluss. Dasselbe gilt auch für app/translations/en.yaml.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/translations/de.yaml` around lines 494 - 495, The validation strings in
the translation entries are incomplete and cut off before the actual format
description; update the `invalid_color_hex` and `invalid_color_hex_2` messages
in the translation files so they include the full color format placeholder (for
example the hex format text) and proper sentence ending. Make the same
correction in both `de.yaml` and the matching `en.yaml` keys so the messages are
consistent across locales.
| invalid_color_hex: Color value must be in | ||
| invalid_color_hex_2: Second color value must be in |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== en.yaml excerpt ==\n'
sed -n '485,505p' app/translations/en.yaml
printf '\n== de.yaml excerpt ==\n'
sed -n '485,505p' app/translations/de.yaml
printf '\n== usages of invalid_color_hex keys ==\n'
rg -n "invalid_color_hex(_2)?|`#RRGGBB`|color value must be in|Second color value must be in" app -SRepository: SytxLabs/SpoolBeacon
Length of output: 2996
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== app/routes/inventory.py around color validation ==\n'
sed -n '880,925p' app/routes/inventory.py
printf '\n== search for translation helper concatenation or key joining ==\n'
rg -n "invalid_color_hex_2|invalid_color_hex|format\)|t\\(\"inventory\\.validation\\." app -SRepository: SytxLabs/SpoolBeacon
Length of output: 5583
Unvollständige Validierungsmeldungen ergänzen
app/translations/en.yaml bricht bei invalid_color_hex und invalid_color_hex_2 mitten im Satz ab. Die Meldungen sollten den erwarteten Farbcode ergänzen, z. B. #RRGGBB, sonst bleibt die Fehlermeldung im UI unvollständig.
Vorschlag
- invalid_color_hex: Color value must be in
- invalid_color_hex_2: Second color value must be in
+ invalid_color_hex: Color value must be in `#RRGGBB` format.
+ invalid_color_hex_2: Second color value must be in `#RRGGBB` format.📝 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.
| invalid_color_hex: Color value must be in | |
| invalid_color_hex_2: Second color value must be in | |
| invalid_color_hex: Color value must be in `#RRGGBB` format. | |
| invalid_color_hex_2: Second color value must be in `#RRGGBB` format. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@app/translations/en.yaml` around lines 494 - 495, The translation entries for
invalid_color_hex and invalid_color_hex_2 are incomplete and stop mid-sentence.
Update the strings in en.yaml so they explicitly include the expected hex color
format (for example, mention `#RRGGBB`) and keep the wording consistent with the
surrounding validation messages. Locate the fix via the invalid_color_hex and
invalid_color_hex_2 keys.
Summary by CodeRabbit
New Features
Bug Fixes