Add support of RTL UI in login themes (#29907)
Closes #29974 Signed-off-by: Fouad Almalki <me@fouad.io>
This commit is contained in:
parent
eedd167e70
commit
780ec71672
18 changed files with 43 additions and 20 deletions
|
@ -20,9 +20,11 @@ package org.keycloak.theme.beans;
|
|||
import org.keycloak.models.RealmModel;
|
||||
|
||||
import jakarta.ws.rs.core.UriBuilder;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
@ -30,13 +32,18 @@ import java.util.stream.Collectors;
|
|||
*/
|
||||
public class LocaleBean {
|
||||
|
||||
private static final Set<String> RTL_LANGUAGE_CODES =
|
||||
Set.of("ar", "dv", "fa", "ha", "he", "iw", "ji", "ps", "sd", "ug", "ur", "yi");
|
||||
|
||||
private String current;
|
||||
private String currentLanguageTag;
|
||||
private boolean rtl; // right-to-left language
|
||||
private List<Locale> supported;
|
||||
|
||||
public LocaleBean(RealmModel realm, java.util.Locale current, UriBuilder uriBuilder, Properties messages) {
|
||||
this.currentLanguageTag = current.toLanguageTag();
|
||||
this.current = messages.getProperty("locale_" + this.currentLanguageTag, this.currentLanguageTag);
|
||||
this.rtl = RTL_LANGUAGE_CODES.contains(current.getLanguage());
|
||||
|
||||
Collator collator = Collator.getInstance(current);
|
||||
collator.setStrength(Collator.PRIMARY); // ignore case and accents
|
||||
|
@ -59,6 +66,13 @@ public class LocaleBean {
|
|||
return currentLanguageTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether it is Right-to-Left language or not.
|
||||
*/
|
||||
public boolean isRtl() {
|
||||
return rtl;
|
||||
}
|
||||
|
||||
public List<Locale> getSupported() {
|
||||
return supported;
|
||||
}
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="totp" name="totp" autocomplete="off" class="${properties.kcInputClass!}"
|
||||
aria-invalid="<#if messagesPerField.existsError('totp')>true</#if>"
|
||||
dir="ltr"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('totp')>
|
||||
|
@ -78,7 +79,7 @@
|
|||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" class="${properties.kcInputClass!}" id="userLabel" name="userLabel" autocomplete="off"
|
||||
aria-invalid="<#if messagesPerField.existsError('userLabel')>true</#if>"
|
||||
aria-invalid="<#if messagesPerField.existsError('userLabel')>true</#if>" dir="ltr"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('userLabel')>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="device-user-code" name="device_user_code" autocomplete="off" type="text" class="${properties.kcInputClass!}" autofocus />
|
||||
<input id="device-user-code" name="device_user_code" autocomplete="off" type="text" class="${properties.kcInputClass!}" autofocus dir="ltr" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input id="otp" name="otp" autocomplete="off" type="text" class="${properties.kcInputClass!}"
|
||||
autofocus aria-invalid="<#if messagesPerField.existsError('totp')>true</#if>"/>
|
||||
autofocus aria-invalid="<#if messagesPerField.existsError('totp')>true</#if>"
|
||||
dir="ltr" />
|
||||
|
||||
<#if messagesPerField.existsError('totp')>
|
||||
<span id="input-error-otp-code" class="${properties.kcInputErrorMessageClass!}"
|
||||
|
|
|
@ -79,7 +79,8 @@
|
|||
class="${properties.kcInputClass!}" name="username"
|
||||
value="${(login.username!'')}"
|
||||
autocomplete="username webauthn"
|
||||
type="text" autofocus autocomplete="off"/>
|
||||
type="text" autofocus autocomplete="off"
|
||||
dir="ltr"/>
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('username'))?no_esc}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<div class="${properties.kcFormGroupClass!} no-bottom-margin">
|
||||
<hr/>
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<div class="${properties.kcInputGroup!}" dir="ltr">
|
||||
<input tabindex="2" id="password" class="${properties.kcInputClass!}" name="password"
|
||||
type="password" autocomplete="on" autofocus
|
||||
aria-invalid="<#if messagesPerField.existsError('password')>true</#if>"
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
autocomplete="off"
|
||||
type="text"
|
||||
class="${properties.kcInputClass!}"
|
||||
autofocus/>
|
||||
autofocus
|
||||
dir="ltr"/>
|
||||
|
||||
<#if messagesPerField.existsError('recoveryCodeInput')>
|
||||
<span id="input-error" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<label for="username" class="${properties.kcLabelClass!}"><#if !realm.loginWithEmailAllowed>${msg("username")}<#elseif !realm.registrationEmailAsUsername>${msg("usernameOrEmail")}<#else>${msg("email")}</#if></label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus value="${(auth.attemptedUsername!'')}" aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"/>
|
||||
<input type="text" id="username" name="username" class="${properties.kcInputClass!}" autofocus value="${(auth.attemptedUsername!'')}" aria-invalid="<#if messagesPerField.existsError('username')>true</#if>" dir="ltr"/>
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
${kcSanitize(messagesPerField.get('username'))?no_esc}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<label for="password-new" class="${properties.kcLabelClass!}">${msg("passwordNew")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<div class="${properties.kcInputGroup!}" dir="ltr">
|
||||
<input type="password" id="password-new" name="password-new" class="${properties.kcInputClass!}"
|
||||
autofocus autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
|
@ -36,7 +36,7 @@
|
|||
<label for="password-confirm" class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<div class="${properties.kcInputGroup!}" dir="ltr">
|
||||
<input type="password" id="password-confirm" name="password-confirm"
|
||||
class="${properties.kcInputClass!}"
|
||||
autocomplete="new-password"
|
||||
|
|
|
@ -17,7 +17,8 @@
|
|||
aria-invalid="<#if messagesPerField.existsError('username')>true</#if>"
|
||||
class="${properties.kcInputClass!}" name="username"
|
||||
value="${(login.username!'')}"
|
||||
type="text" autofocus autocomplete="off"/>
|
||||
type="text" autofocus autocomplete="off"
|
||||
dir="ltr"/>
|
||||
|
||||
<#if messagesPerField.existsError('username')>
|
||||
<span id="input-error-username" class="${properties.kcInputErrorMessageClass!}" aria-live="polite">
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
<input tabindex="2" id="username" class="${properties.kcInputClass!}" name="username" value="${(login.username!'')}" type="text" autofocus autocomplete="username"
|
||||
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
|
||||
dir="ltr"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('username','password')>
|
||||
|
@ -27,7 +28,7 @@
|
|||
<div class="${properties.kcFormGroupClass!}">
|
||||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label>
|
||||
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<div class="${properties.kcInputGroup!}" dir="ltr">
|
||||
<input tabindex="3" id="password" class="${properties.kcInputClass!}" name="password" type="password" autocomplete="current-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
|
||||
/>
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<label for="password" class="${properties.kcLabelClass!}">${msg("password")}</label> *
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<div class="${properties.kcInputGroup!}" dir="ltr">
|
||||
<input type="password" id="password" class="${properties.kcInputClass!}" name="password"
|
||||
autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
|
@ -47,7 +47,7 @@
|
|||
class="${properties.kcLabelClass!}">${msg("passwordConfirm")}</label> *
|
||||
</div>
|
||||
<div class="${properties.kcInputWrapperClass!}">
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<div class="${properties.kcInputGroup!}" dir="ltr">
|
||||
<input type="password" id="password-confirm" class="${properties.kcInputClass!}"
|
||||
name="password-confirm"
|
||||
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false>
|
||||
<!DOCTYPE html>
|
||||
<html class="${properties.kcHtmlClass!}"<#if realm.internationalizationEnabled> lang="${locale.currentLanguageTag}"</#if>>
|
||||
<html class="${properties.kcHtmlClass!}"<#if realm.internationalizationEnabled> lang="${locale.currentLanguageTag}" dir="${(locale.rtl)?then('rtl','ltr')}"</#if>>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
<div class="${properties.kcInputClass!} <#if messagesPerField.existsError('totp')>pf-m-error</#if>">
|
||||
<input type="text" required id="totp" name="totp" autocomplete="off"
|
||||
aria-invalid="<#if messagesPerField.existsError('totp')>true</#if>"
|
||||
dir="ltr"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('totp')>
|
||||
|
@ -88,6 +89,7 @@
|
|||
<div class="${properties.kcInputClass!}">
|
||||
<input type="text" id="userLabel" name="userLabel" autocomplete="off"
|
||||
aria-invalid="<#if messagesPerField.existsError('userLabel')>true</#if>"
|
||||
dir="ltr"
|
||||
/>
|
||||
|
||||
<#if messagesPerField.existsError('userLabel')>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</span>
|
||||
</label>
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<span class="${properties.kcInputClass!}">
|
||||
<span class="${properties.kcInputClass!}" dir="ltr">
|
||||
<input type="password" id="password-new" name="password-new" autofocus autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
/>
|
||||
|
@ -39,7 +39,7 @@
|
|||
</span>
|
||||
</label>
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<span class="${properties.kcInputClass!}">
|
||||
<span class="${properties.kcInputClass!}" dir="ltr">
|
||||
<input type="password" id="password-confirm" name="password-confirm"
|
||||
autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password-confirm')>true</#if>"
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
<span class="${properties.kcInputClass!} ${messagesPerField.existsError('username','password')?then('pf-m-error', '')}">
|
||||
<input tabindex="1" id="username" name="username" value="${(login.username!'')}" type="text" autofocus autocomplete="off"
|
||||
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
|
||||
dir="ltr"
|
||||
/>
|
||||
<#if messagesPerField.existsError('username','password')>
|
||||
<span class="pf-v5-c-form-control__utilities">
|
||||
|
@ -42,7 +43,7 @@
|
|||
<span class="pf-v5-c-form__label-text">${msg("password")}</span>
|
||||
</label>
|
||||
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<div class="${properties.kcInputGroup!}" dir="ltr">
|
||||
<span class="${properties.kcInputClass!}">
|
||||
<input tabindex="2" id="password" name="password" type="password" autocomplete="off"
|
||||
aria-invalid="<#if messagesPerField.existsError('username','password')>true</#if>"
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</span>
|
||||
</label>
|
||||
<span class="${properties.kcInputGroup!}">
|
||||
<span class="${properties.kcInputClass!}">
|
||||
<span class="${properties.kcInputClass!}" dir="ltr">
|
||||
<input type="password" id="password" name="password"
|
||||
autocomplete="new-password"
|
||||
aria-invalid="<#if messagesPerField.existsError('password','password-confirm')>true</#if>"
|
||||
|
@ -53,7 +53,7 @@
|
|||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="${properties.kcInputGroup!}">
|
||||
<div class="${properties.kcInputGroup!}" dir="ltr">
|
||||
<span class="${properties.kcInputClass!}">
|
||||
<input type="password" id="password-confirm"
|
||||
name="password-confirm"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false>
|
||||
<!DOCTYPE html>
|
||||
<html class="${properties.kcHtmlClass!}"<#if realm.internationalizationEnabled> lang="${locale.currentLanguageTag}"</#if>>
|
||||
<html class="${properties.kcHtmlClass!}"<#if realm.internationalizationEnabled> lang="${locale.currentLanguageTag}" dir="${(locale.rtl)?then('rtl','ltr')}"</#if>>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
Loading…
Reference in a new issue