Conversation
nijel
commented
Feb 25, 2026
- use format_html to format HTML safe blocks, otherwise they end up double escaped
- avoid formatting lazy strings, these would be evaluated prematurely
- add pre-commit rules to catch these
There was a problem hiding this comment.
Pull request overview
This pull request fixes HTML double-escaping issues and prevents premature evaluation of lazy translation strings. The changes replace percent-formatting operations on format_html_join_comma() results with format_html() to properly combine HTML-safe strings, and switch from gettext_lazy() to gettext() where string formatting is needed.
Changes:
- Refactored HTML formatting to use
format_html()instead of percent-formatting withformat_html_join_comma()to avoid double escaping - Changed
gettext_lazy()togettext()where dynamic formatting is required to prevent premature evaluation - Added pre-commit hooks to catch these patterns in future code
- Added test coverage for HTML entity escaping in ICU check descriptions
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| weblate/trans/views/edit.py | Fixed display_fixups to use format_html with ngettext and format_html_join_comma |
| weblate/trans/models/component.py | Fixed error message formatting in clean_new_lang validation |
| weblate/trans/forms.py | Fixed ValidationError message in project access control validation |
| weblate/middleware.py | Changed gettext_lazy to gettext for formatted message |
| weblate/memory/forms.py | Moved help_text formatting to init to use gettext instead of gettext_lazy |
| weblate/checks/tests/test_icu_checks.py | Added test to verify HTML entity escaping in check descriptions |
| weblate/checks/icu.py | Updated format_result method to use format_html pattern throughout |
| weblate/checks/format.py | Fixed format_result to use format_html pattern |
| weblate/checks/consistency.py | Fixed get_description to use format_html pattern |
| .pre-commit-config.yaml | Added hooks to detect percent-formatting with format_html_join_comma and formatting lazy strings |
Comments suppressed due to low confidence (2)
.pre-commit-config.yaml:155
- The regex pattern has a potential issue: the dot before 'format' should be escaped as '.' to match a literal dot character. Currently, '.' matches any single character, which could lead to false positives. The pattern should be: 'gettext_lazy([^\)])(\s%|.format('
entry: gettext_lazy\([^\)]*\)(\s*%|.format\()
weblate/checks/consistency.py:198
- The percent formatting operation on the gettext result could lead to improper HTML escaping if the source string contains HTML special characters like '<', '>', or '&'. Consider using format_html() to properly escape the source value before passing it to format_html_join_comma. For example: format_html_join_comma("{}", ((format_html('"{}"', source),) for source in other_sources))
"{}", ((gettext("“%s”") % source,) for source in other_sources)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
.pre-commit-config.yaml:156
- The
py-lazy-formathook regex looks incorrect:.format\(is using an unescaped.(matches any char) and also doesn’t allow whitespace before the dot, so it won’t match common patterns likegettext_lazy(...) .format(...)orgettext_lazy(...) .format(...). Consider adjusting the pattern to something like\s*%|\s*\.format\((with the dot escaped) so the hook reliably catches lazy-string formatting.
- id: py-lazy-format
name: Formatting a lazy translation
types: [python]
args: [--multiline]
entry: gettext_lazy\([^\)]*\)(\s*%|.format\()
language: pygrep
weblate/trans/views/edit.py:89
fixupscan contain lazy translation objects (e.g.,AutoFix.nameis oftengettext_lazy(...)), not just plainstr. The new annotationfixups: list[str]is therefore misleading for typing/mypy; consider using a type likeSequence[StrOrPromise](or similar) to reflect actual values passed in.
def display_fixups(request: AuthenticatedHttpRequest, fixups: list[str]) -> None:
Codecov Report❌ Patch coverage is
❌ Your patch status has failed because the patch coverage (62.50%) is below the target coverage (100.00%). You can increase the patch coverage or adjust the target coverage. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
- use format_html to format HTML safe blocks, otherwise they end up double escaped - avoid formatting lazy strings, these would be evaluated prematurely - add pre-commit rules to catch these