Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ webauthn-help-text=Use your Passkey to sign in.
webauthn-passwordless-display-name=Passkey
webauthn-passwordless-help-text=Use your Passkey for passwordless sign in.
passwordless=Passwordless
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}.
recovery-authn-code=My recovery authentication codes
recovery-authn-codes-display-name=Recovery authentication codes
recovery-authn-codes-help-text=These codes can be used to regain your access in case your other 2FA means are not available.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1252,7 +1252,7 @@ onDragCancel=已取消拖动。列表未更改。
removeUser=移除用户
ownerManagedAccess=启用用户管理访问
userModelAttributeNameHelp=从 LDAP 导入用户时要添加的模型属性的名称
templateHelp=用于格式化要导入的用户名的模板。替换包含在 ${} 中。例如:'${ALIAS}.${CLAIM.sub}'。ALIAS 是供应商别名。CLAIM.<NAME > 引用 ID 或访问令牌声明。可以通过将 |uppercase 或 |lowercase 附加到替换值来将替换转换为大写或小写,例如“${CLAIM.sub | lowercase}”。
templateHelp=用于格式化要导入的用户名的模板。替换包含在 ${} 中。例如:'${ALIAS}.${CLAIM.sub}'。ALIAS 是供应商别名。CLAIM.<NAME> 引用 ID 或访问令牌声明。可以通过将 |uppercase 或 |lowercase 附加到替换值来将替换转换为大写或小写,例如“${CLAIM.sub | lowercase}”。
permissions=权限
emptyExecutionInstructions=您可以通过添加子流程或执行器来开始定义此流程
offlineSessionSettings=离线会话设置
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3133,7 +3133,7 @@ bruteForceMode.PermanentLockout=Lockout permanently
bruteForceMode.TemporaryLockout=Lockout temporarily
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).

multivalued=Multivalued
multivaluedHelp=If this attribute supports multiple values. This setting is an indicator and does not enable any validation.
to the attribute. For that, make sure to use any of the built-in validators to properly validate the size and the values.
Expand Down
11 changes: 11 additions & 0 deletions misc/theme-verifier/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@
<version>2.2</version>
<scope>test</scope>
</dependency>
<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>
Comment on lines +75 to +85

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.

</dependencies>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,22 @@
package org.keycloak.themeverifier;

import org.apache.maven.plugin.MojoExecutionException;
import org.owasp.html.PolicyFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.MissingResourceException;
import java.util.Objects;
import java.util.PropertyResourceBundle;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class VerifyMessageProperties {

Expand All @@ -41,12 +48,129 @@ public List<String> verify() throws MojoExecutionException {
try {
String contents = Files.readString(file.toPath());
verifyNoDuplicateKeys(contents);
verifySafeHtml();
} catch (IOException e) {
throw new MojoExecutionException("Can not read file " + file, e);
}
return messages;
}

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 void verifySafeHtml() {
PropertyResourceBundle bundle;
try (FileInputStream fis = new FileInputStream(file)) {
bundle = new PropertyResourceBundle(fis);
} catch (IOException e) {
throw new RuntimeException("unable to read file " + file, e);
}

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);
}
Comment on lines +73 to +80

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.


bundle.getKeys().asIterator().forEachRemaining(key -> {
String value = bundle.getString(key);
value = normalizeValue(key, value);
String englishValue = getEnglishValue(key, bundleEnglish);
englishValue = normalizeValue(key, englishValue);

value = santizeAnchors(key, value, englishValue);

// Only if the English source string contains HTML we also allow HTML in the translation
PolicyFactory policy = containsHtml(englishValue) ? POLICY_SOME_HTML : POLICY_NO_HTML;
String sanitized = policy.sanitize(value);

// Sanitizer will escape HTML entities for quotes and also for numberic tags like '<1>'
sanitized = org.apache.commons.text.StringEscapeUtils.unescapeHtml4(sanitized);
// Sanitizer will add them when there are double curly braces
sanitized = sanitized.replace("<!-- -->", "");

if (!Objects.equals(sanitized, value)) {

// Strip identical characters from the beginning and the end to show where the difference is
int start = 0;
while (start < sanitized.length() && start < value.length() && value.charAt(start) == sanitized.charAt(start)) {
start++;
}
int end = 0;
while (end < sanitized.length() && end < value.length() && value.charAt(value.length() - end - 1) == sanitized.charAt(sanitized.length() - end - 1)) {
end++;
}

messages.add("Illegal HTML in key " + key + " for file " + file + ": '" + value.substring(start, value.length() - end) + "' vs. '" + sanitized.substring(start, sanitized.length() - end) + "'");
}

});
}

private String normalizeValue(String key, String value) {
if (key.equals("templateHelp")) {
// Allow "CLAIM.<NAME>" here
value = value.replaceAll("CLAIM\\.<[A-Z]*>", "");
} else if (key.equals("optimizeLookupHelp")) {
// Allow "<Extensions>" here
value = value.replaceAll("<Extensions>", "");
} else if (key.startsWith("linkExpirationFormatter.timePeriodUnit") || key.equals("error-invalid-multivalued-size")) {
// The problem is the "<" that appears in the choice
value = value.replaceAll("\\{[0-9]+,choice,[^}]*}", "...");
}

// Unescape HTML entities, as we later also unescape HTML entities in the sanitized value
value = org.apache.commons.text.StringEscapeUtils.unescapeHtml4(value);

if (file.getAbsolutePath().contains("email")) {
// TODO: move the RTL information for emails
value = value.replaceAll(Pattern.quote(" style=\"direction: rtl;\""), "");
}
return value;
}

Pattern HTML_TAGS = Pattern.compile("<[a-z]+[^>]*>");

private boolean containsHtml(String englishValue) {
return HTML_TAGS.matcher(englishValue).find();
}

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;
}
Comment on lines +145 to +162

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.


private static String getEnglishValue(String key, PropertyResourceBundle bundleEnglish) {
String englishValue;
try {
englishValue = bundleEnglish.getString(key);
} catch (MissingResourceException ex) {
englishValue = "";
}
return englishValue;
}

private void verifyNoDuplicateKeys(String contents) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new StringReader(contents));
String line;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,26 @@ class VerifyMessagePropertiesTest {

@Test
void verifyDuplicateKeysDetected() throws MojoExecutionException {
List<String> verify = getFile("duplicate_keys.properties").verify();
MatcherAssert.assertThat(verify, Matchers.contains(Matchers.containsString("Duplicate keys in file")));
List<String> verify = getFile("duplicateKeys_en.properties").verify();
MatcherAssert.assertThat(verify, Matchers.hasItem(Matchers.containsString("Duplicate keys in file")));
}

@Test
void verifyIllegalHtmlTagDetected() throws MojoExecutionException {
List<String> verify = getFile("illegalHtmlTag_en.properties").verify();
MatcherAssert.assertThat(verify, Matchers.hasItem(Matchers.containsString("Illegal HTML")));
}

@Test
void verifyNoHtmlAllowed() throws MojoExecutionException {
List<String> verify = getFile("noHtml_de.properties").verify();
MatcherAssert.assertThat(verify, Matchers.hasItem(Matchers.containsString("Illegal HTML")));
}

@Test
void verifyNoChangedAnchors() throws MojoExecutionException {
List<String> verify = getFile("changedAnchor_de.properties").verify();
MatcherAssert.assertThat(verify, Matchers.hasItem(Matchers.containsString("Didn't find anchor tag")));
}

private static VerifyMessageProperties getFile(String fixture) {
Expand Down
17 changes: 17 additions & 0 deletions misc/theme-verifier/src/test/resources/changedAnchor_de.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright 2025 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
key=Some <a href="http://malicious.com">link</a>
17 changes: 17 additions & 0 deletions misc/theme-verifier/src/test/resources/changedAnchor_en.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright 2025 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
key=Some <a href="http://example.com">link</a>
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright 2025 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
key=Some <div>tag</div
17 changes: 17 additions & 0 deletions misc/theme-verifier/src/test/resources/noHtml_de.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright 2025 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
key=Some <b>HTML</b>
17 changes: 17 additions & 0 deletions misc/theme-verifier/src/test/resources/noHtml_en.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#
# Copyright 2025 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
key=No HTML
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ revoke=Kumoa oikeudet

configureAuthenticators=Konfiguroitu kaksivaiheinen kirjautuminen
mobile=Mobiili
totpStep1=Asenna <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> tai Google Authenticator ohjelma laiteellesi. Kummatkin sovellukset ovat saatavilla <a href="https://play.google.com">Google Play</a> ja Apple App Store kaupoissa.
totpStep1=Asenna jokin seuraavista sovelluksista matkapuhelimeesi:
totpStep2=Avaa sovellus ja skannaa QR-koodi tai kirjoita avain.
totpStep3=Täytä saamasi kertaluontoinen koodisi allaolevaan kenttään ja paina Tallenna.
totpStep3DeviceName=Anna laitteelle nimi, jotta voit hallinnoida OTP-laitteitasi.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ revoke=Atšaukti įgaliojimą

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.

totpStep2=Atidarykite programėlę ir nuskenuokite barkodą arba įveskite kodą.
totpStep3=Įveskite programėlėje sugeneruotą vieną kartą galiojantį kodą ir paspauskite Saugoti norėdami prisijungti.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ revoke=Zrušiť oprávnenie

configureAuthenticators=Nakonfigurované autentifikátory
mobile=Mobilný
totpStep1=Nainštalujte vo svojom zariadení <a href="https://freeotp.github.io/" target="_blank"> FreeOTP </a> alebo Google Authenticator. Obidve aplikácie sú k dispozícii v <a href="https://play.google.com"> Google Play </a> a Apple App Store.
totpStep1=Nainštalujte si do mobilu jednu z nasledujúcich aplikácií:
totpStep2=Otvorte aplikáciu a naskenujte čiarový kód alebo zadajte kľúč.
totpStep3=Zadajte jednorazový kód poskytnutý aplikáciou a kliknutím na tlačidlo Uložiť dokončíte nastavenie.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ revoke=Upphäv rättighet

configureAuthenticators=Konfigurerade autentiserare
mobile=Mobil
totpStep1=Installera <a href="https://freeotp.github.io/" target="_blank">FreeOTP</a> eller Google Authenticator på din enhet. Båda applikationerna finns tillgängliga på <a href="https://play.google.com">Google Play</a> och Apple App Store.
totpStep1=Installera en av följande applikationer på din mobil:
totpStep2=Öppna applikationen och skanna streckkoden eller skriv i nyckeln.
totpStep3=Fyll i engångskoden som tillhandahålls av applikationen och klicka på Spara för att avsluta inställningarna.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ revoke=收回授权

configureAuthenticators=配置的认证者
mobile=手机
totpStep1=在你的设备上安装 <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> 或者 Google Authenticator.两个应用可以从 <a href="https://play.google.com">Google Play</a> 和 Apple App Store下载。
totpStep1=在您的手機上安裝以下應用程式之一:

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

Language variant mismatch: Traditional Chinese used in Simplified Chinese file.

The text uses Traditional Chinese characters ("手機", "應用程式") in a file designated for Simplified Chinese (zh_CN). Users in mainland China expect Simplified Chinese.

🌐 Proposed fix using Simplified Chinese characters
-totpStep1=在您的手機上安裝以下應用程式之一:
+totpStep1=在您的手机上安装以下应用程序之一:
📝 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=在您的手機上安裝以下應用程式之一
totpStep1=在您的手机上安装以下应用程序之一
🤖 Prompt for AI Agents
In
@themes/src/main/resources-community/theme/base/account/messages/messages_zh_CN.properties
at 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.

totpStep2=打开应用扫描二维码输入验证码
totpStep3=输入应用提供的一次性验证码单击保存

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ emailTestBody=Dette er en test besked
emailTestBodyHtml=<p>Dette er en test besked</p>
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...").

passwordResetSubject=Gendan adgangskode
passwordResetBody=Nogen har forsøgt at nulstille adgangskoden til {2}. Hvis dette var dig, bedes du klikke på linket herunder for at nulstille adgangskoden.\n\n{0}\n\nDette link og kode vil udløbe efter {3}.\n\nHvis du ikke ønsker at nulstille din adgangskode, kan du se bort fra denne besked.
passwordResetBodyHtml=<p>Nogen har forsøgt at nulstille adgangskoden til {2}. Hvis dette var dig, bedes du klikke på linket herunder for at nulstille adgangskoden.</p><p><a href="{0}">Nulstil adgangskode</a></p><p>Dette link og kode vil udløbe efter {3}.</p><p>Hvis du ikke ønsker at nulstille din adgangskode, kan du se bort fra denne besked.</p>
Expand Down
Loading