Skip to content

Add HTML sanitizer for translated message resources#7

Open
ShashankFC wants to merge 1 commit into
feature-html-sanitizer-baselinefrom
feature-html-sanitizer-implementation
Open

Add HTML sanitizer for translated message resources#7
ShashankFC wants to merge 1 commit into
feature-html-sanitizer-baselinefrom
feature-html-sanitizer-implementation

Conversation

@ShashankFC

@ShashankFC ShashankFC commented Jan 5, 2026

Copy link
Copy Markdown
Collaborator

Test 7

Summary by CodeRabbit

Release Notes

  • New Features

    • Added recovery codes display and management localization
    • Introduced attribute translation management interface
    • Added logout token configuration settings
  • Improvements

    • Updated TOTP setup guidance across multiple languages
    • Fixed HTML formatting issues in email notifications and authentication error messages
    • Enhanced validation messages with context-aware pluralization

✏️ Tip: You can customize this high-level summary in your review settings.


Replicated from ai-code-review-evaluation/keycloak-coderabbit#7

Closes #37428

Signed-off-by: Alexander Schwartz <alexander.schwartz@gmx.net>
@ShashankFC

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jan 12, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jan 12, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request introduces HTML sanitization for theme message properties, adds internationalization keys for recovery codes and translations, refactors multi-step TOTP setup messages across multiple languages, and normalizes HTML formatting (WebAuthn error tags, email templates) in numerous locale-specific message property files.

Changes

Cohort / File(s) Summary
Theme Verifier HTML Sanitization
misc/theme-verifier/pom.xml, misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java
Added two new dependencies (owasp-java-html-sanitizer, commons-text). Integrated HTML policy-based sanitization with two policies (POLICY_SOME_HTML for HTML-containing sources; POLICY_NO_HTML otherwise), per-key normalization, anchor tag synchronization, and detailed error reporting for illegal HTML.
Theme Verifier Tests
misc/theme-verifier/src/test/java/org/keycloak/themeverifier/VerifyMessagePropertiesTest.java, misc/theme-verifier/src/test/resources/illegalHtmlTag_*.properties, misc/theme-verifier/src/test/resources/noHtml_*.properties, misc/theme-verifier/src/test/resources/changedAnchor_*.properties
Added three new test methods (verifyIllegalHtmlTagDetected, verifyNoHtmlAllowed, verifyNoChangedAnchors) with corresponding test fixture files to validate HTML sanitization logic.
Admin & Account UI Message Updates
js/apps/account-ui/maven-resources/theme/keycloak.v3/account/messages/messages_en.properties, js/apps/admin-ui/maven-resources-community/theme/keycloak.v2/admin/messages/messages_zh_CN.properties, js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
Updated error-invalid-multivalued-size to ICU-style pluralization format. Added recovery-codes localization keys (recovery-codes-number-used, recovery-codes-number-remaining, recovery-codes-generate-new-codes). Added extensive attribute translation keys (sendIdTokenOnLogout, addAttributeTranslation, translationKey, etc.). Fixed HTML spacing in templateHelp line.
Community Theme TOTP Messages (Multiple Locales)
themes/src/main/resources-community/theme/base/account/messages/messages_fi.properties, messages_lt.properties, messages_sk.properties, messages_sv.properties, messages_zh_CN.properties, themes/src/main/resources-community/theme/base/login/messages/messages_lt.properties, messages_no.properties, messages_sk.properties, messages_sv.properties
Refactored TOTP setup flows from single link-based instructions to multi-step sequences (loginTotpStep1/2/3) with device naming support and manual entry guidance across Finnish, Lithuanian, Slovak, Swedish, Chinese, and Norwegian locales.
Email Template HTML Fixes (Multiple Locales)
themes/src/main/resources-community/theme/base/email/messages/messages_da.properties, messages_fa.properties, messages_hu.properties, messages_lt.properties, messages_nl.properties, messages_no.properties, messages_tr.properties, messages_uk.properties
Fixed malformed HTML tags: removed extraneous spaces, corrected stray formatting artifacts, wrapped placeholders with bold tags, and fixed inconsistent href attribute quoting across Danish, Persian, Hungarian, Lithuanian, Dutch, Norwegian, Turkish, and Ukrainian locales.
WebAuthn Error Message Formatting (Multiple Locales)
themes/src/main/resources-community/theme/base/login/messages/messages_ar.properties, messages_ca.properties, messages_cs.properties, messages_de.properties, messages_el.properties, messages_es.properties, messages_fa.properties, messages_fi.properties, messages_fr.properties, messages_hr.properties, messages_ka.properties, messages_ko.properties, messages_th.properties, messages_tr.properties, messages_uk.properties, messages_zh_CN.properties, messages_zh_TW.properties
Standardized HTML line break tags from <br/> to <br /> in four WebAuthn error message keys (webauthn-error-registration, webauthn-error-api-get, webauthn-error-auth-verification, webauthn-error-register-verification) across 17 locales for consistent formatting.
Base Theme Admin & Login Message Updates
themes/src/main/resources/theme/base/admin/messages/messages_en.properties, themes/src/main/resources/theme/base/login/messages/messages_en.properties
Updated error-invalid-multivalued-size to ICU pluralization format. Standardized WebAuthn error HTML break tags to <br /> and corrected error message pluralization across English locale variants.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Hoppity-hop, HTML's now clean!
Messages sorted, in every locale seen,
Recovery codes and translations align,
TOTP steps structured—now beautifully designed,
Sanitized safely, from <br/> to <br />
Theme files harmonized, what a delight!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete and generic. It contains only 'Test 7' and lacks substantive information about the changes, motivations, testing, or impacts required by the contribution guidelines. Provide a comprehensive description including: the problem being solved, the solution implemented, testing approach, and any breaking changes or migration steps needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main change: introducing an HTML sanitizer for translated message resources, which is reflected throughout the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
themes/src/main/resources-community/theme/base/login/messages/messages_sv.properties (1)

68-72: Fix loginTotpStep3 to reference the correct button label

The message text instructs users to "klicka på Spara för att avsluta inställningarna" (click on "Save"), but the actual TOTP setup button displays "Skicka" (the translation of doSubmit). Update the message to: "...klicka på Skicka för att avsluta inställningarna".

Also consider adding consistent trailing punctuation to Step2 and Step3 (Step1 ends with :, others have none).

🤖 Fix all issues with AI agents
In
@js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties:
- Line 3136: The message key error-invalid-multivalued-size uses Java
MessageFormat choice syntax which i18next doesn't support; update the value to
use i18next-compatible interpolation and pluralization by replacing the
{2,choice,...} fragment with an interpolated count placeholder and i18next
plural logic so the label uses the singular "value" when the count is 1 and
"values" otherwise (keep placeholders for the attribute and min/max counts so
Admin UI can provide those values).

In @misc/theme-verifier/pom.xml:
- Around line 75-85: The pom currently pins
com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1
which is vulnerable (CVE-2025-66021); either upgrade to a patched release when
available or mitigate in code by changing your HtmlPolicyBuilder usage to
disallow the risky combination (no <noscript> + <style> with allowTextIn) or
ensure all output of the sanitizer is treated as untrusted and re-encoded before
rendering; update the dependency entry (artifactId owasp-java-html-sanitizer) to
a fixed version once released and/or audit places using HtmlPolicyBuilder
(search for HtmlPolicyBuilder, allowTextIn, noscript, style) to implement the
policy change or additional output encoding.

In
@misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java:
- Around line 145-162: The method santizeAnchors contains three issues: rename
it to sanitizeAnchors everywhere; when removing a matched anchor use the current
target anchor string (matcher.group()) instead of englishMatcher.group(); and
avoid mutating value while iterating its Matcher — instead, first collect
anchors from value and englishValue using ANCHOR_PATTERN into lists, then
iterate comparing corresponding elements and remove only matching anchors from
the value (e.g., perform safe removals or rebuild the string after comparisons).
Ensure you also update any callers to the new sanitizeAnchors name.
- Around line 73-80: The code currently re-opens the same properties file when
processing an English file because englishFile can equal file.getAbsolutePath();
update the logic around englishFile and bundleEnglish in VerifyMessageProperties
so that if englishFile.equals(file.getAbsolutePath()) you set bundleEnglish =
bundle (the already-loaded PropertyResourceBundle) and skip the FileInputStream
open; otherwise proceed to open englishFile in a try-with-resources and
construct bundleEnglish as before, preserving the existing IOException handling.

In
@themes/src/main/resources-community/theme/base/account/messages/messages_lt.properties:
- Line 101: The property totpStep1 contains Italian text in the Lithuanian
locale; replace its value with the correct Lithuanian string (e.g., "Įdiekite
vieną iš šių programėlių savo telefone:") so the totpStep1 key shows proper
Lithuanian instructions during TOTP setup.

In
@themes/src/main/resources-community/theme/base/account/messages/messages_zh_CN.properties:
- Line 112: The value for the key totpStep1 uses Traditional Chinese characters
in a Simplified Chinese file; update the string to Simplified Chinese (replace
手機 with 手机 and 應用程式 with 应用程序) so totpStep1 reads using mainland China
simplified characters.

In
@themes/src/main/resources-community/theme/base/email/messages/messages_da.properties:
- Line 9: The property identityProviderLinkBodyHtml contains a typographical
artifact "nHvis" at the start of the final paragraph; remove the stray leading
"n" so the sentence begins with "Hvis" and ensure the HTML paragraph remains
valid (i.e., change "...<p>nHvis du ikke..." to "...<p>Hvis du ikke...").

In
@themes/src/main/resources-community/theme/base/email/messages/messages_hu.properties:
- Line 6: The Hungarian message key emailUpdateConfirmationBodyHtml includes
bold tags around the realm placeholder (<b>{2}</b>), causing an inconsistency
with other locales; remove the <b> and </b> so the placeholder is plain {2} to
match the rest of the translations (or if you prefer bold, apply the same
<b>{2}</b> consistently across all locale files), updating only the
emailUpdateConfirmationBodyHtml value to keep formatting uniform.

In
@themes/src/main/resources-community/theme/base/email/messages/messages_lt.properties:
- Line 3: In the emailVerificationBodyHtml property fix two typos: change "tao"
to "tai" in the sentence "Jei tao buvote Jūs" and remove the stray leading "n"
so "nJei paskyros nekūrėte" becomes "Jei paskyros nekūrėte"; update the value of
emailVerificationBodyHtml accordingly to preserve the same placeholders ({0},
{1}, {2}) and HTML structure.
- Line 6: The Lithuanian translation for the property
identityProviderLinkBodyHtml has a truncated word at the start ("žas
pageidauja"); update the string so the opening phrase reads "Kažkas pageidauja"
instead (match the plain text version), preserving the rest of the message and
placeholders {0}..{4} intact.

In
@themes/src/main/resources-community/theme/base/login/messages/messages_lt.properties:
- Line 71: The loginTotpStep1 message contains Italian text; replace the value
for the key loginTotpStep1 with the correct Lithuanian translation (for example:
"Įdiekite vieną iš šių programėlių savo telefone:") so Lithuanian users see the
proper localized TOTP setup instruction.
🧹 Nitpick comments (6)
themes/src/main/resources-community/theme/base/login/messages/messages_ca.properties (1)

423-427: LGTM! HTML tag normalization is correct.

The HTML break tag normalization from <br/> to <br /> is valid and aligns with the PR's objective to add HTML sanitization for translated message resources. Both forms are functionally equivalent and render identically.

Optional: There's a minor spacing inconsistency where lines 423-424 have a space before {0} (<br /> {0}) while lines 426-427 don't (<br />{0}). This was likely pre-existing and doesn't affect functionality, but standardizing it could improve consistency.

themes/src/main/resources-community/theme/base/email/messages/messages_tr.properties (1)

9-9: Move spaces outside bold tags for better HTML formatting.

The spaces inside the bold tags (<b> {1} </b> and <b> {0} </b>) will cause the bold styling to include leading and trailing whitespace. Standard HTML practice is to place spaces outside the tags for cleaner markup and better accessibility with screen readers.

♻️ Proposed fix
-identityProviderLinkBodyHtml=<p>Birisi <b> {1} </b> hesabınızı {2} kullanıcısı <b> {0} </b> hesabına bağlamak istiyor. Bu sizseniz, bağlantı vermek için aşağıdaki bağlantıyı tıklayın</p><p><a href="{3}">Hesap bağlantısını onaylamak için bağlantı</a></p><p>Bu bağlantının süresi {5} içerisinde sona erecek.</p><p>Hesabı bağlamak istemiyorsanız, bu mesajı göz ardı edin. Hesapları bağlarsanız, {1} ile {0} arasında oturum açabilirsiniz.</p>
+identityProviderLinkBodyHtml=<p>Birisi <b>{1}</b> hesabınızı {2} kullanıcısı <b>{0}</b> hesabına bağlamak istiyor. Bu sizseniz, bağlantı vermek için aşağıdaki bağlantıyı tıklayın</p><p><a href="{3}">Hesap bağlantısını onaylamak için bağlantı</a></p><p>Bu bağlantının süresi {5} içerisinde sona erecek.</p><p>Hesabı bağlamak istemiyorsanız, bu mesajı göz ardı edin. Hesapları bağlarsanız, {1} ile {0} arasında oturum açabilirsiniz.</p>
misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java (3)

58-63: Convert policy fields to static final constants.

POLICY_SOME_HTML and POLICY_NO_HTML are named with SCREAMING_SNAKE_CASE convention, which typically indicates constants. These should be static final since the policies are immutable and shared across all instances.

♻️ Suggested fix
-    PolicyFactory POLICY_SOME_HTML = new org.owasp.html.HtmlPolicyBuilder()
+    private static final PolicyFactory POLICY_SOME_HTML = new org.owasp.html.HtmlPolicyBuilder()
             .allowElements(
                     "br", "p", "strong", "b"
             ).toFactory();

-    PolicyFactory POLICY_NO_HTML = new org.owasp.html.HtmlPolicyBuilder().toFactory();
+    private static final PolicyFactory POLICY_NO_HTML = new org.owasp.html.HtmlPolicyBuilder().toFactory();

139-143: Make HTML_TAGS pattern a static final constant.

Like ANCHOR_PATTERN on line 145, this compiled pattern should be static final for consistency and to avoid recompilation per instance.

♻️ Suggested fix
-    Pattern HTML_TAGS = Pattern.compile("<[a-z]+[^>]*>");
+    private static final Pattern HTML_TAGS = Pattern.compile("<[a-z]+[^>]*>");

65-81: Consider graceful handling when English file is missing.

If the English properties file doesn't exist (line 78-80), the code throws a RuntimeException. For robustness, consider logging a warning and skipping HTML validation for that file instead, or document that the English file must exist.

misc/theme-verifier/src/test/java/org/keycloak/themeverifier/VerifyMessagePropertiesTest.java (1)

28-52: Consider adding a positive test case for valid properties.

All existing tests verify error detection (that invalid inputs produce errors), but there's no test confirming that valid properties files pass without errors. This would guard against regressions where verify() might incorrectly report errors on valid files.

💡 Suggested positive test case
@Test
void verifyValidPropertiesHaveNoErrors() throws MojoExecutionException {
    List<String> verify = getFile("validProperties_en.properties").verify();
    MatcherAssert.assertThat(verify, Matchers.empty());
}

This would require creating a corresponding valid test fixture file.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21d5311 and 5d77e7e.

📒 Files selected for processing (48)
  • js/apps/account-ui/maven-resources/theme/keycloak.v3/account/messages/messages_en.properties
  • js/apps/admin-ui/maven-resources-community/theme/keycloak.v2/admin/messages/messages_zh_CN.properties
  • js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
  • misc/theme-verifier/pom.xml
  • misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java
  • misc/theme-verifier/src/test/java/org/keycloak/themeverifier/VerifyMessagePropertiesTest.java
  • misc/theme-verifier/src/test/resources/changedAnchor_de.properties
  • misc/theme-verifier/src/test/resources/changedAnchor_en.properties
  • misc/theme-verifier/src/test/resources/duplicateKeys_en.properties
  • misc/theme-verifier/src/test/resources/illegalHtmlTag_en.properties
  • misc/theme-verifier/src/test/resources/noHtml_de.properties
  • misc/theme-verifier/src/test/resources/noHtml_en.properties
  • themes/src/main/resources-community/theme/base/account/messages/messages_fi.properties
  • themes/src/main/resources-community/theme/base/account/messages/messages_lt.properties
  • themes/src/main/resources-community/theme/base/account/messages/messages_sk.properties
  • themes/src/main/resources-community/theme/base/account/messages/messages_sv.properties
  • themes/src/main/resources-community/theme/base/account/messages/messages_zh_CN.properties
  • themes/src/main/resources-community/theme/base/email/messages/messages_da.properties
  • themes/src/main/resources-community/theme/base/email/messages/messages_fa.properties
  • themes/src/main/resources-community/theme/base/email/messages/messages_hu.properties
  • themes/src/main/resources-community/theme/base/email/messages/messages_lt.properties
  • themes/src/main/resources-community/theme/base/email/messages/messages_nl.properties
  • themes/src/main/resources-community/theme/base/email/messages/messages_no.properties
  • themes/src/main/resources-community/theme/base/email/messages/messages_tr.properties
  • themes/src/main/resources-community/theme/base/email/messages/messages_uk.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_ar.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_ca.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_cs.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_de.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_el.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_es.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_fa.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_fi.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_fr.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_hr.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_ka.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_ko.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_lt.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_no.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_sk.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_sv.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_th.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_tr.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_uk.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_zh_CN.properties
  • themes/src/main/resources-community/theme/base/login/messages/messages_zh_TW.properties
  • themes/src/main/resources/theme/base/admin/messages/messages_en.properties
  • themes/src/main/resources/theme/base/login/messages/messages_en.properties
🔇 Additional comments (45)
themes/src/main/resources-community/theme/base/email/messages/messages_uk.properties (1)

6-6: LGTM! HTML formatting cleanup aligns with sanitization objectives.

The HTML structure is well-formed with properly closed tags and correct placeholder usage. The formatting cleanup removes extraneous whitespace, improving consistency across the message templates.

themes/src/main/resources-community/theme/base/email/messages/messages_fa.properties (1)

6-6: LGTM! HTML formatting is now correct.

The malformed HTML tag has been properly fixed. The HTML structure is well-formed with correctly closed tags and proper spacing.

js/apps/admin-ui/maven-resources-community/theme/keycloak.v2/admin/messages/messages_zh_CN.properties (2)

1255-1255: LGTM! Spacing correction improves consistency.

The removal of the extra space in CLAIM.<NAME> (previously CLAIM.<NAME >) improves the formatting consistency of the placeholder description in the Chinese translation.


1259-1259: LGTM! New translation key added correctly.

The new unAssignRole key with Chinese translation "取消分配" (Unassign) is properly formatted and follows the existing naming conventions in the properties file.

themes/src/main/resources-community/theme/base/email/messages/messages_lt.properties (1)

9-9: LGTM! HTML formatting corrections applied correctly.

The href attributes are now properly formatted as href="{0}", and the HTML structure and Lithuanian text appear correct.

Also applies to: 12-12

themes/src/main/resources-community/theme/base/email/messages/messages_nl.properties (1)

3-3: LGTM! HTML structure properly normalized.

The HTML tags are correctly nested and closed. The placeholders ({0}, {2}, {3}) are properly positioned and align with the plain text version on line 2.

Since this PR introduces an HTML sanitizer, ensure that the sanitizer correctly handles these message template placeholders to prevent XSS vulnerabilities when user-controlled data is inserted.

themes/src/main/resources-community/theme/base/login/messages/messages_sk.properties (4)

30-30: LGTM! HTML tag fix corrects rendering issue.

The removal of the extraneous </strong> tag fixes the HTML structure for the impersonation title.


466-470: LGTM! HTML formatting standardized for WebAuthn error messages.

The <br /> tag format (with space before /) is XHTML-compliant and improves consistency across error messages.


501-501: Important bug fix: corrected placeholder for maximum value.

The placeholder for the maximum value has been corrected from {1} to {2}, ensuring that validation error messages display both minimum and maximum values correctly instead of showing the minimum value twice.


123-126: TOTP setup instructions are consistent across all locale files.

The Slovak translation entries (lines 123-126) match the key structure and message format used in all 30 other locale files. All required keys are present: loginTotpStep1, loginTotpStep2, loginTotpStep3, and loginTotpStep3DeviceName.

Translation accuracy requires review by a Slovak language speaker to ensure the terminology aligns with the TOTP setup UI flow.

themes/src/main/resources-community/theme/base/email/messages/messages_no.properties (1)

5-5: Placeholder ordering is consistent across all locale files.

The change to identityProviderLinkBody in the Norwegian locale aligns with all other language files, which consistently use the placeholder order {1}, {0}, {2} (user's identity provider account, target identity provider account, username) in both plain text and HTML variants. No cross-locale inconsistencies were detected across 30+ language files.

Calling code verification could not be completed due to limitations in locating the Java implementation, but the locale consistency across plain text and HTML versions suggests this change is intentional and correct.

themes/src/main/resources-community/theme/base/login/messages/messages_ka.properties (2)

172-174: LGTM: HTML break tag standardization.

The formatting change from <br/> to <br /> standardizes HTML break tags across WebAuthn error messages. Both forms are valid HTML5, and this aligns with the project-wide formatting normalization.


451-452: LGTM: Consistent HTML formatting.

The break tag formatting is consistently standardized across all WebAuthn error messages.

themes/src/main/resources-community/theme/base/login/messages/messages_de.properties (1)

420-421: LGTM: HTML break tag standardization.

The formatting change from <br/> to <br /> across all WebAuthn error messages maintains consistency with the project-wide HTML formatting standardization documented in the PR summary.

Also applies to: 423-424

themes/src/main/resources-community/theme/base/login/messages/messages_ar.properties (1)

466-470: LGTM! Consistent HTML formatting normalization.

The HTML break tags have been normalized from <br/> to <br /> across all WebAuthn error messages. This formatting change is consistent, maintains message functionality, and aligns with the PR's objective of HTML sanitization for translated resources.

themes/src/main/resources-community/theme/base/login/messages/messages_cs.properties (2)

466-470: LGTM! Consistent HTML formatting normalization.

The HTML break tags have been normalized from <br/> to <br /> across all WebAuthn error messages, consistent with the broader HTML sanitization effort in this PR.


501-501: LGTM! Proper ICU pluralization for Czech locale.

The ICU MessageFormat plural syntax correctly implements Czech plural rules (0/1/2-4/5+), providing proper grammatical agreement for the multivalued size error message.

themes/src/main/resources-community/theme/base/login/messages/messages_uk.properties (1)

459-463: LGTM! Consistent HTML formatting normalization.

The HTML break tags have been normalized from <br/> to <br /> across all WebAuthn error messages. This change is consistent with the HTML sanitization improvements applied throughout the PR and maintains full message functionality.

themes/src/main/resources-community/theme/base/login/messages/messages_es.properties (1)

442-446: LGTM! Consistent HTML formatting normalization.

The HTML break tag formatting has been standardized from <br/> to <br /> across all WebAuthn error messages. Both forms are valid, and this change has no functional impact—it's purely cosmetic to maintain consistency across locale files.

themes/src/main/resources-community/theme/base/login/messages/messages_ko.properties (1)

480-484: LGTM! HTML break tag formatting normalized.

The same formatting normalization applied here as in other locale files. The change from <br/> to <br /> maintains consistency across the codebase without affecting functionality.

themes/src/main/resources-community/theme/base/login/messages/messages_zh_CN.properties (1)

466-470: LGTM! HTML formatting consistent with other locales.

The WebAuthn error message HTML break tags have been normalized to match the formatting standard applied across all locale files in this PR. No functional changes—purely cosmetic standardization.

themes/src/main/resources-community/theme/base/login/messages/messages_zh_TW.properties (1)

473-477: LGTM: HTML formatting normalization for WebAuthn error messages.

The HTML break tags have been standardized from <br/> to <br /> across the WebAuthn error messages. This aligns with the PR's HTML sanitization objectives and improves consistency.

themes/src/main/resources-community/theme/base/login/messages/messages_fa.properties (1)

457-461: LGTM: HTML formatting normalization for WebAuthn error messages.

The HTML break tags have been standardized from <br/> to <br /> across the WebAuthn error messages. This is consistent with similar changes in other locale files and aligns with the PR's HTML sanitization objectives.

themes/src/main/resources-community/theme/base/login/messages/messages_el.properties (1)

418-422: LGTM! HTML break tag normalization applied correctly.

The standardization of HTML line break tags from <br/> to <br /> (with space before the closing tag) across these four WebAuthn error messages is consistent and maintains the original Greek text content.

themes/src/main/resources-community/theme/base/login/messages/messages_tr.properties (1)

469-473: LGTM! HTML break tag normalization applied correctly.

The HTML line break formatting has been standardized from <br/> to <br /> across these four WebAuthn error messages, maintaining the original Turkish translations.

themes/src/main/resources-community/theme/base/login/messages/messages_fr.properties (1)

377-381: LGTM! HTML break tag normalization applied correctly.

The standardization of HTML line break tags from <br/> to <br /> has been consistently applied across these four WebAuthn error messages while preserving the original French text. This formatting change aligns with the broader HTML sanitization effort described in the PR summary.

themes/src/main/resources-community/theme/base/login/messages/messages_no.properties (1)

71-71: LGTM! TOTP instruction simplified appropriately.

The Norwegian translation for loginTotpStep1 has been simplified to a generic app installation instruction, which aligns with the broader TOTP setup refactoring across locales.

themes/src/main/resources-community/theme/base/account/messages/messages_sk.properties (1)

101-103: LGTM! Complete TOTP setup flow implemented.

The Slovak translations for totpStep1, totpStep2, and totpStep3 provide a clear, step-by-step TOTP configuration flow that aligns with the broader multi-locale refactoring effort.

themes/src/main/resources-community/theme/base/login/messages/messages_th.properties (2)

462-466: LGTM!

The HTML break tag formatting normalization is consistent with the Finnish locale file and aligns with the PR's objective to standardize HTML formatting across translated message resources.


470-476: No action required. There are no duplicate Identity Provider translation keys in this file. The keys at lines 470-475 appear only once in the file, and lines 497-500 are empty. The review comment is incorrect.

Likely an incorrect or invalid review comment.

themes/src/main/resources-community/theme/base/login/messages/messages_hr.properties (1)

479-483: LGTM!

The HTML break tag formatting is consistent with the normalization applied across other locale files (Finnish and Thai). This change maintains consistency in HTML formatting across the internationalized message resources.

themes/src/main/resources-community/theme/base/login/messages/messages_fi.properties (1)

400-404: HTML sanitization is properly applied to these messages.

The WebAuthn error messages follow a secure rendering pattern: error parameters from the WebAuthn API are substituted into message templates via MessageFormat, then the complete formatted string is sanitized using kcSanitize() (which applies OWASP's html-sanitizer) before rendering with ?no_esc in FreeMarker. The <br/> to <br /> normalization is purely cosmetic and has no security impact.

js/apps/account-ui/maven-resources/theme/keycloak.v3/account/messages/messages_en.properties (2)

192-194: LGTM! New recovery code localization keys are well-structured.

The new recovery code keys follow consistent naming conventions and provide clear, user-friendly messages. The parameter usage is appropriate for displaying counts.


188-188: No action needed. The ICU choice format {2,choice,0#values|1#value|1<values} is valid Java MessageFormat syntax already in use across multiple Keycloak message files in different languages. There is no HTML sanitizer in the codebase that would affect these server-side property files, and this message is not consumed by the JavaScript account UI.

Likely an incorrect or invalid review comment.

themes/src/main/resources/theme/base/login/messages/messages_en.properties (1)

487-492: All WebAuthn error messages properly use <br /> tags that are allowed by the HTML sanitizer.

The <br /> tag is explicitly included in the sanitizer's allowlist (KeycloakSanitizerPolicy.java), and it is not restricted with specific attributes, so these tags will be preserved without stripping or escaping. The format is consistently applied across all locales including English, German, French, and Spanish.

themes/src/main/resources-community/theme/base/account/messages/messages_sv.properties (1)

94-99: Good simplification of totpStep1 (less HTML, clearer step text)

This looks consistent with totpStep2/totpStep3 and reduces risk from HTML-heavy translations.

misc/theme-verifier/src/test/resources/changedAnchor_de.properties (1)

1-17: LGTM!

Test fixture correctly uses a different anchor URL (http://malicious.com) compared to the English source (http://example.com) to verify the anchor synchronization logic in santizeAnchors.

misc/theme-verifier/src/test/resources/noHtml_en.properties (1)

1-17: LGTM!

Test fixture correctly provides a plain-text English baseline (key=No HTML) to test that translations should not introduce HTML where the English source has none.

misc/theme-verifier/src/test/resources/noHtml_de.properties (1)

1-17: LGTM!

Test fixture correctly introduces <b>HTML</b> in the German translation where the English source has no HTML, enabling verification of POLICY_NO_HTML enforcement.

misc/theme-verifier/src/test/resources/changedAnchor_en.properties (1)

1-17: LGTM!

Test fixture correctly provides the English anchor baseline (http://example.com) for testing anchor synchronization against the German variant with http://malicious.com.

misc/theme-verifier/src/test/resources/illegalHtmlTag_en.properties (1)

1-17: LGTM!

Test fixture contains a <div> tag which is not in the allowed elements list (br, p, strong, b), correctly testing illegal HTML tag detection. The malformed closing tag </div (missing >) appears intentional for this test scenario.

misc/theme-verifier/src/test/java/org/keycloak/themeverifier/VerifyMessagePropertiesTest.java (4)

31-34: LGTM!

The updated assertion using hasItem(containsString(...)) is appropriate for verifying that at least one item in the returned list contains the expected error substring. The test correctly validates duplicate key detection.


36-40: LGTM!

New test for illegal HTML tag detection follows the established pattern and clearly validates the expected behavior.


42-46: LGTM!

Test correctly validates that HTML is disallowed in non-English locale files (German in this case).


48-52: LGTM!

Test correctly validates anchor tag synchronization against the English source file.

bruteForceMode.PermanentAfterTemporaryLockout=Lockout permanently after temporary lockout
bruteForceMode=Brute Force Mode
error-invalid-multivalued-size=Attribute {{0}} must have at least {{1}} and at most {{2}} value(s).
error-invalid-multivalued-size=Attribute {0} must have at least {1} and at most {2} {2,choice,0#values|1#value|1<values}.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check the Admin UI structure and identify i18n dependencies
find js/apps/admin-ui -name "package.json" | head -5

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 113


🏁 Script executed:

# Search for i18n related imports and dependencies in Admin UI
rg -t js "import.*i18n|require.*i18n|intl|formatjs|formatMessage" js/apps/admin-ui --max-count=20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check if there are existing choice patterns in the properties files
rg "choice" js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 294


🏁 Script executed:

# Look at the package.json to see i18n dependencies
cat js/apps/admin-ui/package.json | grep -A 5 -B 5 "i18n\|intl\|format" | head -50

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 669


🏁 Script executed:

# Check how messages are loaded and used in the Admin UI
rg -t ts -t tsx "messages_en|\.properties|i18n" js/apps/admin-ui/src --max-count=15

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 111


🏁 Script executed:

# Search for i18next configuration files
find js/apps/admin-ui/src -name "*.ts" -o -name "*.js" -o -name "*.tsx" | xargs rg "i18next\|i18n" | head -30

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for i18next initialization or config
rg "i18n\." js/apps/admin-ui --max-count=20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 1139


🏁 Script executed:

# Check existing pluralization patterns in the properties file to see current conventions
rg "choice|plural|\{[0-9]+," js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 205


🏁 Script executed:

# Look at how the backend loads messages
rg "http-backend|\.properties" js/apps/admin-ui/src --max-count=15

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 3682


🏁 Script executed:

# Examine the i18n configuration file
cat js/apps/admin-ui/src/i18n/i18n.ts

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 1040


🏁 Script executed:

# Check if there are any existing plural/choice patterns in other language property files or comments
rg "\{[0-9]+,plural" js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look at the HttpBackend configuration to understand how properties are parsed
rg -A 10 -B 10 "HttpBackend" js/apps/admin-ui/src/i18n/i18n.ts

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 719


🏁 Script executed:

# Check if there are any references to MessageFormat or ICU in the codebase
rg "MessageFormat|ICU|choice" js/apps/admin-ui/src --max-count=20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for where messages_en.properties is referenced or built
rg "messages_en" js/apps/admin-ui --max-count=20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check how the backend serves these messages - look for the endpoint
rg "resources.*admin.*lng\|loadPath" js/apps/admin-ui/src --max-count=20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for any Java/backend code that processes these properties
find . -name "*.java" -path "*/admin/*" -o -name "*.java" -path "*/theme/*" | head -20 | xargs grep -l "messages_en\|choice" 2>/dev/null | head -5

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check if there's any build configuration for theme resources
cat js/apps/admin-ui/pom.xml 2>/dev/null | grep -A 10 -B 10 "messages\|properties" | head -50

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for backend code that converts properties to JSON for Admin UI
find . -name "*.java" | xargs grep -l "messages_en\|admin/messages" 2>/dev/null | head -10

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 213


🏁 Script executed:

# Check the theme resource handling in the server
find . -path "*/keycloak/theme/*" -name "*.java" | head -10

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 803


🏁 Script executed:

# Search for message parsing/conversion logic
rg "properties.*json|json.*properties" --type java | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 319


🏁 Script executed:

# Let's look at what format the backend actually sends - check for endpoints
rg "resources/.*admin" --type java | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 3318


🏁 Script executed:

# Look for message resource conversion logic
rg "messages_en\.properties|theme.*messages" --type java | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 4698


🏁 Script executed:

# Check for REST endpoint that serves theme resources
rg "class.*Resource|@Path" --type java | rg "admin.*message\|theme.*message" | head -10

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look at Theme.java to understand message handling
cat ./server-spi/src/main/java/org/keycloak/theme/Theme.java | head -100

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 3091


🏁 Script executed:

# Search for message loading endpoints
rg "@Path.*resource\|@GET.*message" server-spi/src/main/java/org/keycloak/theme/ --type java -A 5 | head -50

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check how the error-invalid-multivalued-size message is used in the codebase
rg "error-invalid-multivalued-size" .

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 7335


🏁 Script executed:

# Look for i18next pluralization examples in the codebase
rg "plural|i18next" js/apps/admin-ui/src --max-count=15

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 39636


🏁 Script executed:

# Check what messages the Admin UI actually uses - is it frontend or backend rendered?
rg "i18n\.t\(|useTranslation\|i18next\(" js/apps/admin-ui/src | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for where the messages are formatted/interpolated with values
rg "error.*format|interpolation|values|1.*2.*3" js/apps/admin-ui/src --max-count=10

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 18989


🏁 Script executed:

# Check if backend uses these messages for validation errors
rg "error-invalid-multivalued" . --type java

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 7335


🏁 Script executed:

# Find where error-invalid-multivalued-size is actually used
rg "error-invalid-multivalued-size\|MESSAGE_INVALID_SIZE" --type java -B 3 -A 3

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check if this message is rendered in Java/backend or frontend
rg "error-invalid-multivalued-size" js/apps/admin-ui/src --type ts --type tsx

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 111


🏁 Script executed:

# Look at MultiValueValidator to understand how the message is used
cat services/src/main/java/org/keycloak/userprofile/validator/MultiValueValidator.java

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 5650


🏁 Script executed:

# Check how validation errors are returned to frontend
rg "ValidationError\|addError" services/src/main/java/org/keycloak/validate/ --type java -B 2 -A 2 | head -50

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 169


🏁 Script executed:

# Look at ValidationError to see how it formats messages
find . -name "ValidationError.java" -type f | head -5

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 233


🏁 Script executed:

# Check if the message template is used on backend or sent to frontend
rg "MESSAGE_INVALID_SIZE" . --type java -B 5 -A 5

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 7897


🏁 Script executed:

# Verify the backend error formatting - look for message resolution
find . -path "*/validate/*" -name "*.java" -type f | xargs grep -l "getMessage\|format" | head -5

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 532


🏁 Script executed:

# Examine ValidationError to understand message handling
cat server-spi/src/main/java/org/keycloak/validate/ValidationError.java | head -80

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 2751


🏁 Script executed:

# Check where validation errors are returned to REST clients
rg "ValidationError\|ValidationException" services/src/main/java/org/keycloak/userprofile/ --type java -B 2 -A 2 | head -40

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# See how the backend converts these messages to REST responses
rg "error-invalid\|ValidationError" services/src/main/java/org/keycloak/services/ --type java | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check how backend sends validation errors to frontend REST responses
find services -name "*Resource.java" -type f | xargs rg "ValidationError\|ValidationException" | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for REST endpoint that handles user profile validation
rg "ValidationException\|ValidationError" services/src/main/java/org/keycloak/services/resources/ --type java | head -15

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check how the error response is constructed
rg "getErrorMessage\|formatMessage\|getMessage" services/src/main/java/org/keycloak/userprofile/ --type java -B 2 -A 2 | head -40

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check what format the REST API returns for validation errors
rg "error-invalid\|errorMessage\|message.*key" services/src/main/java/org/keycloak/services/ --type java -B 1 -A 1 | head -30

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for UserProfileResourceImpl or similar to see validation error handling
find services -name "*UserProfile*Resource*.java" -type f | head -10

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 169


🏁 Script executed:

# Check REST endpoint error responses for user profile
rg "class.*UserProfile.*Resource\|@POST\|@PUT" services/src/main/java/org/keycloak/services/resources/ --type java -A 5 | head -50

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Search for where ValidationException is caught and converted to REST response
rg "catch.*ValidationException\|ValidationException.*catch" services/src/main/java/ --type java -B 3 -A 5 | head -40

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look at backend message templating - does it use MessageFormat.format?
rg "MessageFormat|format.*message|{.*choice" services/src/main/java/ --type java | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 228


🏁 Script executed:

# Look at UserProfileResource to see how validation errors are handled
cat services/src/main/java/org/keycloak/services/resources/admin/UserProfileResource.java

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 5023


🏁 Script executed:

# Check how ValidationError is serialized to JSON
rg "class.*ValidationError\|toJson\|@JsonProperty" server-spi/src/main/java/org/keycloak/validate/ --type java -A 3 | head -40

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for error response builders
rg "error\|message.*key" services/src/main/java/org/keycloak/services/resources/ --type java | grep -i "validation\|profile" | head -15

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check how the backend converts properties to JSON for Admin UI
find . -path "*/resources/*" -name "*converter*" -o -name "*loader*" | grep -i message | head -10

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check if there's theme resource serving code that formats messages
rg "loadPath.*resources.*admin\|getMessage\|format" services/src/main/java/org/keycloak/services/ --type java | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for how themes are loaded and served to Admin UI
rg "keycloak.v2\|admin.*messages" services/src/main/java/org/keycloak/services/ --type java | head -15

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check the actual backend response for message resources
find . -name "*AbstractTheme*" -o -name "*Theme.java" | xargs grep -l "getMessages\|getMessage" | head -5

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 267


🏁 Script executed:

# Check i18next pluralization syntax - see what it actually supports
rg "i18next.*plural\|plural.*i18next" js/apps/admin-ui --type json --type js | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for any existing plural patterns in the properties that would show correct syntax
rg "plural\|_plural\|::" js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check if there are any message transformation/conversion utilities
find js/apps/admin-ui/src -name "*message*" -o -name "*i18n*" | head -15

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 177


🏁 Script executed:

# Look for the theme message loader code
find . -path "*/theme/*" -name "*.ts" -o -name "*.js" | xargs grep -l "messages_en\|load.*message" | head -10

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Check the exact difference between backend and frontend message formatting
diff <(rg "error-invalid-multivalued-size" ./themes/src/main/resources/theme/base/admin/messages/messages_en.properties) <(rg "error-invalid-multivalued-size" ./js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties)

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 171


🏁 Script executed:

# Look at the actual message in both locations
echo "=== Backend version ===" && cat ./themes/src/main/resources/theme/base/admin/messages/messages_en.properties | rg "error-invalid-multivalued-size"
echo ""
echo "=== Frontend version ===" && cat ./js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties | rg "error-invalid-multivalued-size"

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 377


🏁 Script executed:

# Check i18next documentation for pluralization - search codebase for plural examples
rg "plural\|_one\|_other" js/apps/admin-ui/src/i18n/ --type ts --type js -A 2

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

# Look for any message that works correctly with i18next to see the pattern
rg "{{[0-9]" js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties | head -10

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 166


Replace choice syntax with i18next pluralization

The Admin UI uses i18next (not Java MessageFormat), which does not support {n,choice,...} syntax. The message will not be formatted correctly if rendered by the Admin UI.

Replace with i18next-compatible pluralization:

Proposed change
-error-invalid-multivalued-size=Attribute {0} must have at least {1} and at most {2} {2,choice,0#values|1#value|1<values}.
+error-invalid-multivalued-size=Attribute {0} must have at least {1} and at most {2} {2, plural, one {value} other {values}}.
📝 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
error-invalid-multivalued-size=Attribute {0} must have at least {1} and at most {2} {2,choice,0#values|1#value|1<values}.
error-invalid-multivalued-size=Attribute {0} must have at least {1} and at most {2} {2, plural, one {value} other {values}}.
🤖 Prompt for AI Agents
In
@js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties
at line 3136, The message key error-invalid-multivalued-size uses Java
MessageFormat choice syntax which i18next doesn't support; update the value to
use i18next-compatible interpolation and pluralization by replacing the
{2,choice,...} fragment with an interpolated count placeholder and i18next
plural logic so the label uses the singular "value" when the count is 1 and
"values" otherwise (keep placeholders for the attribute and min/max counts so
Admin UI can provide those values).

Comment on lines +75 to +85
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20240325.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.13.0</version>
<scope>compile</scope>
</dependency>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

OWASP Java HTML Sanitizer version 20240325.1 latest security vulnerabilities

💡 Result:

Summary — latest known vulnerability affecting OWASP Java HTML Sanitizer v20240325.1:

  • One public high‑severity XSS (Cross‑Site Scripting) vulnerability: CVE‑2025‑66021 — exploitable when a policy permits both and <style> with allowTextIn inside the style tag; published Nov 26, 2025. No patched version was available at publication. [1][2][3]

Recommended immediate actions:

  • Avoid allowing + <style> with allowTextIn in your HtmlPolicyBuilder. If you already allow that combination, treat inputs as untrusted and block or strip those tags. [1][3]
  • Monitor the official OWASP/GitHub advisory and NVD for a vendor patch and update as soon as one is released. [1][2]

Sources:
[1] OWASP / GitHub security advisory (GHSA-g9gq-3pfx-2gw2).
[2] NVD — CVE-2025-66021 (published Nov 26, 2025).
[3] OSV / other vulnerability databases summarizing CVE-2025-66021.


🌐 Web query:

Apache Commons Text version 1.13.0 latest release security

💡 Result:

Short answer: Apache Commons Text 1.13.0 (released Dec 10, 2024) is not listed as containing new security fixes beyond earlier advisories (the known RCE class CVE-2022-42889 affects versions before 1.10.0 and was addressed by the project). The Commons Text project has continued releases after 1.13.0 (1.13.1, 1.14.0, 1.15.0), so you should upgrade to a supported/latest release to get any subsequent fixes and improvements. [1][2][3]

References

  • Release notes / history (shows 1.13.0 release date and later releases). [1]
  • Commons Text security page (discusses CVE-2022-42889 and guidance). [2]
  • Maven central / artifact page for 1.13.0 (artifact date). [3]

If you want, I can check the very latest Commons Text release now and recommend the minimum safe version to upgrade to.


Do not use OWASP HTML Sanitizer v20240325.1 without mitigation: CVE-2025-66021 (high-severity XSS).

A high-severity XSS vulnerability (CVE-2025-66021, published Nov 26, 2025) affects this exact version. It is exploitable when the HTML policy permits both <noscript> and <style> tags with allowTextIn set inside the style tag. Either:

  • Ensure your HtmlPolicyBuilder configuration does not allow this tag combination, or
  • Treat all sanitized HTML as untrusted input and apply additional output encoding.

Monitor the OWASP/GitHub advisory (GHSA-g9gq-3pfx-2gw2) for a patched version release and upgrade as soon as available.

For Apache Commons Text: v1.13.0 is secure from CVE-2022-42889 (which affected versions < 1.10.0). Newer releases (1.14.0, 1.15.0) are available if you want to capture subsequent fixes.

🤖 Prompt for AI Agents
In @misc/theme-verifier/pom.xml around lines 75 - 85, The pom currently pins
com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1
which is vulnerable (CVE-2025-66021); either upgrade to a patched release when
available or mitigate in code by changing your HtmlPolicyBuilder usage to
disallow the risky combination (no <noscript> + <style> with allowTextIn) or
ensure all output of the sanitizer is treated as untrusted and re-encoded before
rendering; update the dependency entry (artifactId owasp-java-html-sanitizer) to
a fixed version once released and/or audit places using HtmlPolicyBuilder
(search for HtmlPolicyBuilder, allowTextIn, noscript, style) to implement the
policy change or additional output encoding.

Comment on lines +73 to +80
PropertyResourceBundle bundleEnglish;
String englishFile = file.getAbsolutePath().replaceAll("resources-community", "resources")
.replaceAll("_[a-zA-Z-_]*\\.properties", "_en.properties");
try (FileInputStream fis = new FileInputStream(englishFile)) {
bundleEnglish = new PropertyResourceBundle(fis);
} catch (IOException e) {
throw new RuntimeException("unable to read file " + englishFile, e);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, examine the full context of the Java file to understand the code flow
echo "=== Reading VerifyMessageProperties.java ==="
wc -l misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 222


🏁 Script executed:

#!/bin/bash
# Get full context around lines 73-80
head -120 misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java | tail -60

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 2994


🏁 Script executed:

#!/bin/bash
# Check what property files exist and their naming patterns
echo "=== Property files in the repository ==="
fd -e properties . --exec bash -c 'echo "{}"; basename "{}"' \; | head -100

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 5408


🏁 Script executed:

#!/bin/bash
# Read the complete VerifyMessageProperties.java file
cat -n misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 9448


🏁 Script executed:

#!/bin/bash
# Also check if there's any filtering of files before calling verifySafeHtml
# Search for where this method is called
grep -n "verifySafeHtml" misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 155


🏁 Script executed:

#!/bin/bash
# Check what types of files are actually being processed by looking at the main entry point
grep -n "public\|private\|void\|static" misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java | head -20

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 830


🏁 Script executed:

#!/bin/bash
# Search for where VerifyMessageProperties is instantiated
rg "new VerifyMessageProperties\|VerifyMessageProperties\(" --type java -B 3 -A 3

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 66


🏁 Script executed:

#!/antml/bash
# Look for the Maven Mojo or entry point
fd -e java . -path "*/themeverifier/*" --exec basename {} \;

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 252


🏁 Script executed:

#!/bin/bash
# Find all Java files in the themeverifier module
fd -e java . -path "*/themeverifier/*"

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 252


🏁 Script executed:

#!/bin/bash
# Find all Java files in the themeverifier module
find misc/theme-verifier -name "*.java"

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 441


🏁 Script executed:

#!/bin/bash
# Search for where VerifyMessageProperties is instantiated
grep -r "new VerifyMessageProperties\|VerifyMessageProperties(" misc/theme-verifier --include="*.java" -B 3 -A 3

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 2675


🏁 Script executed:

#!/bin/bash
# Look for the entry point/Mojo
grep -r "Mojo\|execute\|getMojoDescriptor" misc/theme-verifier --include="*.java" -l

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 351


🏁 Script executed:

#!/bin/bash
# Check the MessagePropertiesFilter to see what files are being selected
cat -n misc/theme-verifier/src/main/java/org/keycloak/themeverifier/MessagePropertiesFilter.java

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 1397


🏁 Script executed:

#!/bin/bash
# Also check the test file to understand expected behavior
cat -n misc/theme-verifier/src/test/java/org/keycloak/themeverifier/VerifyMessagePropertiesTest.java

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 2911


🏁 Script executed:

#!/bin/bash
# Check the test fixtures to see what patterns are used
find misc/theme-verifier/src/test/resources -name "*.properties" -exec basename {} \;

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 238


🏁 Script executed:

#!/bin/bash
# Let's verify the regex behavior with a concrete example
# If messages_en.properties is processed, what does the transformation produce?
python3 << 'EOF'
import re

test_cases = [
    "/path/to/messages_en.properties",
    "/path/to/messages_de.properties",
    "/path/to/messages_pt_BR.properties",
    "/path/to/messages_zh_TW.properties",
    "/path/to/resources-community/messages_hu.properties",
    "/path/to/resources-community/messages_pt_BR.properties",
]

for path in test_cases:
    result = path.replace("resources-community", "resources")
    result = re.sub(r"_[a-zA-Z-_]*\.properties", "_en.properties", result)
    is_same = result == path
    print(f"Input:  {path}")
    print(f"Output: {result}")
    print(f"Same file? {is_same}\n")
EOF

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Test the regex behavior with basic shell
# Simulate what happens when messages_en.properties is processed
path="/path/to/messages_en.properties"
echo "Input: $path"
# First replaceall: resources-community -> resources
result="${path/resources-community/resources}"
echo "After first replaceall: $result"
# Second replaceall: _[a-zA-Z-_]*.properties -> _en.properties
# Using sed instead
result=$(echo "$result" | sed -E 's/_[a-zA-Z_-]*\.properties/_en.properties/')
echo "After second replaceall: $result"
echo "---"

# Test with a different locale
path="/path/to/messages_de.properties"
echo "Input: $path"
result="${path/resources-community/resources}"
echo "After first replaceall: $result"
result=$(echo "$result" | sed -E 's/_[a-zA-Z_-]*\.properties/_en.properties/')
echo "After second replaceall: $result"
echo "---"

# Test with community variant
path="/path/to/resources-community/messages_pt_BR.properties"
echo "Input: $path"
result="${path/resources-community/resources}"
echo "After first replaceall: $result"
result=$(echo "$result" | sed -E 's/_[a-zA-Z_-]*\.properties/_en.properties/')
echo "After second replaceall: $result"

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 593


Unnecessary file load when English file is already the input.

When messages_en.properties is processed, the regex replacement keeps the filename unchanged, causing the same file to be loaded twice (lines 67 and 76). Consider adding a check to skip the redundant load:

if (englishFile.equals(file.getAbsolutePath())) {
    bundleEnglish = bundle;
} else {
    try (FileInputStream fis = new FileInputStream(englishFile)) {
        bundleEnglish = new PropertyResourceBundle(fis);
    } catch (IOException e) {
        throw new RuntimeException("unable to read file " + englishFile, e);
    }
}

Note: The MessagePropertiesFilter ensures only files matching the messages_* pattern reach this method, so unusual filename patterns without locale suffixes are not a concern.

🤖 Prompt for AI Agents
In
@misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java
around lines 73 - 80, The code currently re-opens the same properties file when
processing an English file because englishFile can equal file.getAbsolutePath();
update the logic around englishFile and bundleEnglish in VerifyMessageProperties
so that if englishFile.equals(file.getAbsolutePath()) you set bundleEnglish =
bundle (the already-loaded PropertyResourceBundle) and skip the FileInputStream
open; otherwise proceed to open englishFile in a try-with-resources and
construct bundleEnglish as before, preserving the existing IOException handling.

Comment on lines +145 to +162
private static final Pattern ANCHOR_PATTERN = Pattern.compile("</?a[^>]*>");

/**
* Allow only those anchor tags from the source key to also appear in the target key.
*/
private String santizeAnchors(String key, String value, String englishValue) {
Matcher matcher = ANCHOR_PATTERN.matcher(value);
Matcher englishMatcher = ANCHOR_PATTERN.matcher(englishValue);
while (matcher.find()) {
if (englishMatcher.find() && Objects.equals(matcher.group(), englishMatcher.group())) {
value = value.replaceFirst(Pattern.quote(englishMatcher.group()), "");
} else {
messages.add("Didn't find anchor tag " + matcher.group() + " in original string");
break;
}
}
return value;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Anchor synchronization logic has issues.

  1. Typo: Method name santizeAnchors should be sanitizeAnchors.

  2. Logic bug on line 155: When removing matched anchors, it uses englishMatcher.group() instead of matcher.group(). This could remove the wrong anchor if they differ.

  3. Matcher invalidation: The Matcher is created from the original value, but value is modified with replaceFirst() inside the loop. The matcher's internal state no longer aligns with the modified string, which can cause missed matches or incorrect behavior.

🔧 Suggested approach

Consider building a list of anchors from both strings first, then comparing them:

     private String santizeAnchors(String key, String value, String englishValue) {
+        List<String> valueAnchors = new ArrayList<>();
+        List<String> englishAnchors = new ArrayList<>();
+        
         Matcher matcher = ANCHOR_PATTERN.matcher(value);
-        Matcher englishMatcher = ANCHOR_PATTERN.matcher(englishValue);
-        while (matcher.find()) {
-            if (englishMatcher.find() && Objects.equals(matcher.group(), englishMatcher.group())) {
-                value = value.replaceFirst(Pattern.quote(englishMatcher.group()), "");
-            } else {
-                messages.add("Didn't find anchor tag " + matcher.group() + " in original string");
-                break;
-            }
+        while (matcher.find()) {
+            valueAnchors.add(matcher.group());
+        }
+        
+        Matcher englishMatcher = ANCHOR_PATTERN.matcher(englishValue);
+        while (englishMatcher.find()) {
+            englishAnchors.add(englishMatcher.group());
+        }
+        
+        // Compare and validate anchors, then remove matched ones from value
+        for (int i = 0; i < valueAnchors.size(); i++) {
+            String anchor = valueAnchors.get(i);
+            if (i < englishAnchors.size() && Objects.equals(anchor, englishAnchors.get(i))) {
+                value = value.replaceFirst(Pattern.quote(anchor), "");
+            } else {
+                messages.add("Didn't find anchor tag " + anchor + " in original string");
+                break;
+            }
         }
         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.

Suggested change
private static final Pattern ANCHOR_PATTERN = Pattern.compile("</?a[^>]*>");
/**
* Allow only those anchor tags from the source key to also appear in the target key.
*/
private String santizeAnchors(String key, String value, String englishValue) {
Matcher matcher = ANCHOR_PATTERN.matcher(value);
Matcher englishMatcher = ANCHOR_PATTERN.matcher(englishValue);
while (matcher.find()) {
if (englishMatcher.find() && Objects.equals(matcher.group(), englishMatcher.group())) {
value = value.replaceFirst(Pattern.quote(englishMatcher.group()), "");
} else {
messages.add("Didn't find anchor tag " + matcher.group() + " in original string");
break;
}
}
return value;
}
private String santizeAnchors(String key, String value, String englishValue) {
List<String> valueAnchors = new ArrayList<>();
List<String> englishAnchors = new ArrayList<>();
Matcher matcher = ANCHOR_PATTERN.matcher(value);
while (matcher.find()) {
valueAnchors.add(matcher.group());
}
Matcher englishMatcher = ANCHOR_PATTERN.matcher(englishValue);
while (englishMatcher.find()) {
englishAnchors.add(englishMatcher.group());
}
// Compare and validate anchors, then remove matched ones from value
for (int i = 0; i < valueAnchors.size(); i++) {
String anchor = valueAnchors.get(i);
if (i < englishAnchors.size() && Objects.equals(anchor, englishAnchors.get(i))) {
value = value.replaceFirst(Pattern.quote(anchor), "");
} else {
messages.add("Didn't find anchor tag " + anchor + " in original string");
break;
}
}
return value;
}
🤖 Prompt for AI Agents
In
@misc/theme-verifier/src/main/java/org/keycloak/themeverifier/VerifyMessageProperties.java
around lines 145 - 162, The method santizeAnchors contains three issues: rename
it to sanitizeAnchors everywhere; when removing a matched anchor use the current
target anchor string (matcher.group()) instead of englishMatcher.group(); and
avoid mutating value while iterating its Matcher — instead, first collect
anchors from value and englishValue using ANCHOR_PATTERN into lists, then
iterate comparing corresponding elements and remove only matching anchors from
the value (e.g., perform safe removals or rebuild the string after comparisons).
Ensure you also update any callers to the new sanitizeAnchors name.

configureAuthenticators=Sukonfigūruotas autentifikatorius
mobile=Mobilus
totpStep1=Įdiekite <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> arba Google Authenticator savo įrenginyje. Programėlės prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.
totpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Wrong language in Lithuanian locale file.

Line 101 contains Italian text ("Installa una delle seguenti applicazioni sul tuo cellulare:") in a Lithuanian locale file. This breaks the TOTP setup flow for Lithuanian users who will not understand the Italian instructions.

🌍 Proposed fix with proper Lithuanian translation
-totpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
+totpStep1=Įdiekite vieną iš šių programėlių į savo mobilųjį telefoną:
📝 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
totpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
totpStep1=Įdiekite vieną iš šių programėlių į savo mobilųjį telefoną:
🤖 Prompt for AI Agents
In
@themes/src/main/resources-community/theme/base/account/messages/messages_lt.properties
at line 101, The property totpStep1 contains Italian text in the Lithuanian
locale; replace its value with the correct Lithuanian string (e.g., "Įdiekite
vieną iš šių programėlių savo telefone:") so the totpStep1 key shows proper
Lithuanian instructions during TOTP setup.

identityProviderLinkSubject=Link {0}
identityProviderLinkBody=Nogen vil forbinde din "{1}" konto med "{0}" kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti\n\n{3}\n\nDette link vil udløbe efter {5}.\n\nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.
identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.
identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typographical error in Danish text.

The last paragraph contains "nHvis" which should be "Hvis". This appears to be a leftover artifact from a newline character when converting to HTML format. The plain text version on line 8 correctly uses "Hvis".

📝 Proposed fix
-identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.</p>
+identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>Hvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.</p>
📝 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
identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>nHvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.</p>
identityProviderLinkBodyHtml=<p>Nogen vil forbinde din <b>{1}</b> konto med <b>{0}</b> kontoen som er tilknyttet brugeren {2}. Hvis dette var dig, bedes du klikke på forbindet herunder for at forbinde de to konti</p><p><a href="{3}">Bekræft</a></p><p>Dette link vil udløbe efter {5}.</p><p>Hvis du ikke vil forbinde disse konti, kan du bare ignore denne besked. Hvis du vælger at forbinde de to konti, kan du logge ind som {1} via {0}.</p>
🤖 Prompt for AI Agents
In
@themes/src/main/resources-community/theme/base/email/messages/messages_da.properties
at line 9, The property identityProviderLinkBodyHtml contains a typographical
artifact "nHvis" at the start of the final paragraph; remove the stray leading
"n" so the sentence begins with "Hvis" and ensure the HTML paragraph remains
valid (i.e., change "...<p>nHvis du ikke..." to "...<p>Hvis du ikke...").

emailUpdateConfirmationSubject=Új e-mail cím megerősítése
emailUpdateConfirmationBody=Valaki módosítani szeretné az Ön "{2}" tartományi fiókjának e-mail címét. Amennyiben az e-mail cím módosítását Ön kezdeményezte, kérem, kattintson a lenti hivatkozásra a jóváhagyáshoz\n\n{0}\n\nA hivatkozás érvényét veszti {3} múlva.\n\nHa nem ön kérte az e-mail cím megváltoztatását, akkor kérem, hagyja figyelmen kívül ezt az üzenetet, az e-mail címe nem fog megváltozni.
***emailUpdateConfirmationBodyHtml=<p>Valaki módosítani szeretné az Ön <b>{2}</b> tartományi fiókjának e-mail címét. Amennyiben az e-mail cím módosítását Ön kezdeményezte, kérem, kattintson a lenti hivatkozásra a jóváhagyáshoz</p><p><a href="{0}">{0}</a></p><p>A hivatkozás érvényét veszti {3} múlva.</p><p>Ha nem ön kérte az e-mail cím megváltoztatását, akkor kérem, hagyja figyelmen kívül ezt az üzenetet, az e-mail címe nem fog megváltozni.</p>
emailUpdateConfirmationBodyHtml=<p>Valaki módosítani szeretné az Ön <b>{2}</b> tartományi fiókjának e-mail címét. Amennyiben az e-mail cím módosítását Ön kezdeményezte, kérem, kattintson a lenti hivatkozásra a jóváhagyáshoz</p><p><a href="{0}">{0}</a></p><p>A hivatkozás érvényét veszti {3} múlva.</p><p>Ha nem ön kérte az e-mail cím megváltoztatását, akkor kérem, hagyja figyelmen kívül ezt az üzenetet, az e-mail címe nem fog megváltozni.</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for emailUpdateConfirmationBodyHtml across all email message property files
echo "=== Searching for emailUpdateConfirmationBodyHtml in all locales ==="
find themes -name "messages*.properties" -path "*/email/messages/*" | head -20

echo -e "\n=== Finding all emailUpdateConfirmationBodyHtml entries ==="
rg -n "emailUpdateConfirmationBodyHtml" themes -A 0 --max-count=50

echo -e "\n=== Checking for bold tag pattern around {2} ==="
rg -n "<b>\{2\}</b>" themes/src -A 0 --max-count=50

Repository: AI-Code-Review-Evals/coderabbit-keycloak

Length of output: 8914


Address inconsistency: Hungarian is the only locale with bold formatting around realm name.

The bold tags added around the realm placeholder <b>{2}</b> in the Hungarian locale create an inconsistency with all other language files. A search across ~18 locale files shows that no other locale (English, German, French, Spanish, Czech, Catalan, Ukrainian, Thai, Portuguese, Croatian, Farsi, Greek, Arabic, Slovak, Georgian, etc.) applies any formatting to the realm name placeholder—they all use plain {2}.

This inconsistency should be resolved: either apply the bold formatting uniformly across all locales, or revert it to match the standard formatting used elsewhere.

🤖 Prompt for AI Agents
In
@themes/src/main/resources-community/theme/base/email/messages/messages_hu.properties
at line 6, The Hungarian message key emailUpdateConfirmationBodyHtml includes
bold tags around the realm placeholder (<b>{2}</b>), causing an inconsistency
with other locales; remove the <b> and </b> so the placeholder is plain {2} to
match the rest of the translations (or if you prefer bold, apply the same
<b>{2}</b> consistently across all locale files), updating only the
emailUpdateConfirmationBodyHtml value to keep formatting uniform.

emailVerificationSubject=El. pašto patvirtinimas
emailVerificationBody=Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą\n\n{0}\n\nŠi nuoroda galioja {1} min.\n\nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.
emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href=LT"{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix typographical errors in Lithuanian translation.

Two typos are present in the email verification HTML body:

  1. "tao" should be "tai" (comparing with the plain text version on line 2: "Jei tai buvote Jūs")
  2. "nJei" should be "Jei" (extra 'n' at the beginning)
📝 Proposed fix
-emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
+emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>Jei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
📝 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
emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tao buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>nJei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
emailVerificationBodyHtml=<p>Paskyra {2} sukurta naudojant šį el. pašto adresą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą</p><p><a href="{0}">{0}</a></p><p>Ši nuoroda galioja {1} min.</p><p>Jei paskyros nekūrėte, tuomet ignuoruokite šį laišką.</p>
🤖 Prompt for AI Agents
In
@themes/src/main/resources-community/theme/base/email/messages/messages_lt.properties
at line 3, In the emailVerificationBodyHtml property fix two typos: change "tao"
to "tai" in the sentence "Jei tao buvote Jūs" and remove the stray leading "n"
so "nJei paskyros nekūrėte" becomes "Jei paskyros nekūrėte"; update the value of
emailVerificationBodyHtml accordingly to preserve the same placeholders ({0},
{1}, {2}) and HTML structure.

identityProviderLinkSubject=Sąsaja {0}
identityProviderLinkBody=Kažas pageidauja susieti Jūsų "{1}" paskyrą su "{0}" {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras\n\n{3}\n\nŠi nuoroda galioja {4} min.\n\nJei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.
identityProviderLinkBodyHtml=<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href=LT"{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
identityProviderLinkBodyHtml=<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix truncated word in Lithuanian translation.

The word at the beginning of the message is truncated. Comparing with the plain text version on line 5 ("Kažas pageidauja"), it should be "Kažkas pageidauja" instead of "žas pageidauja".

📝 Proposed fix
-identityProviderLinkBodyHtml=<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
+identityProviderLinkBodyHtml=<p>Kažkas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
📝 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
identityProviderLinkBodyHtml=<p>žas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
identityProviderLinkBodyHtml=<p>Kažkas pageidauja susieti Jūsų <b>{1}</b> paskyrą su <b>{0}</b> {2} naudotojo paskyrą. Jei tai buvote Jūs, tuomet paspauskite žemiau esančią nuorodą norėdami susieti paskyras</p><p><a href="{3}">{3}</a></p><p>Ši nuoroda galioja {4} min.</p><p>Jei paskyrų susieti nenorite, tuomet ignoruokite šį laišką. Jei paskyras susiesite, tuomet prie {1} galėsiste prisijungti per {0}.</p>
🤖 Prompt for AI Agents
In
@themes/src/main/resources-community/theme/base/email/messages/messages_lt.properties
at line 6, The Lithuanian translation for the property
identityProviderLinkBodyHtml has a truncated word at the start ("žas
pageidauja"); update the string so the opening phrase reads "Kažkas pageidauja"
instead (match the plain text version), preserving the rest of the message and
placeholders {0}..{4} intact.

gssDelegationCredential=GSS prisijungimo duomenų delegavimas

loginTotpStep1=Įdiekite <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> arba Google Authenticator savo įrenginyje. Programėlės prieinamos <a href="https://play.google.com">Google Play</a> ir Apple App Store.
loginTotpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Italian text in Lithuanian locale file.

The loginTotpStep1 value contains Italian text ("Installa una delle seguenti applicazioni sul tuo cellulare:") instead of Lithuanian. This will display incorrect language to Lithuanian users during TOTP setup.

Proposed fix

The text should be translated to Lithuanian. Based on the context from lines 72-73 and similar keys in other locale files, the Lithuanian translation should be something like:

-loginTotpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
+loginTotpStep1=Įdiekite vieną iš šių programų savo mobiliajame telefone:
📝 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
loginTotpStep1=Installa una delle seguenti applicazioni sul tuo cellulare:
loginTotpStep1=Įdiekite vieną iš šių programų savo mobiliajame telefone:
🤖 Prompt for AI Agents
In
@themes/src/main/resources-community/theme/base/login/messages/messages_lt.properties
at line 71, The loginTotpStep1 message contains Italian text; replace the value
for the key loginTotpStep1 with the correct Lithuanian translation (for example:
"Įdiekite vieną iš šių programėlių savo telefone:") so Lithuanian users see the
proper localized TOTP setup instruction.

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