mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-10 15:32:05 -03:30
Use default locale from realm an intermediate fallback
closes #40990 Signed-off-by: Christian Janker <christian.janker@gmx.at> Signed-off-by: Alexander Schwartz <alexander.schwartz@ibm.com> Co-authored-by: Alexander Schwartz <alexander.schwartz@ibm.com>
This commit is contained in:
parent
35ee49b5d4
commit
374e45b883
@ -59,6 +59,10 @@ Additionally, both `IdentityProviderModel` and `IdentityProviderRepresentation`
|
||||
configuration like `isHideOnLogin` to be null in order to not include these in Identity Provider types that do
|
||||
not need these configurations.
|
||||
|
||||
=== Realm default locale attempted for translation fallbacks
|
||||
|
||||
When localization for a realm is enabled and a translation for a message key is unavailable for the language the user selected, {project_name} now attempts to find a matching message key with the realm's default locale before defaulting to English.
|
||||
|
||||
=== SPIFFE Identity Provider configuration changed
|
||||
|
||||
The SPIFFE Identity Provider preview feature now uses the `trustDomain` configuration instead of `issuer`. This change
|
||||
|
||||
@ -221,7 +221,7 @@ public class ThemeResource {
|
||||
new KeySource((String) e.getKey(), (String) e.getValue(), Source.THEME)).collect(toSet());
|
||||
|
||||
Map<Locale, Properties> realmLocalizationMessages = LocaleUtil.getRealmLocalizationTexts(realm, locale);
|
||||
for (Locale currentLocale = locale; currentLocale != null; currentLocale = LocaleUtil.getParentLocale(currentLocale)) {
|
||||
for (Locale currentLocale = locale; currentLocale != null; currentLocale = LocaleUtil.getParentLocale(currentLocale, realm)) {
|
||||
final List<KeySource> realmOverride = realmLocalizationMessages.get(currentLocale).entrySet().stream().map(e ->
|
||||
new KeySource((String) e.getKey(), (String) e.getValue(), Source.REALM)).collect(toList());
|
||||
resultSet.addAll(realmOverride);
|
||||
|
||||
@ -62,11 +62,28 @@ public class LocaleUtil {
|
||||
/**
|
||||
* Returns the parent locale of the given {@code locale}. If the locale just contains a language (e.g. "de"),
|
||||
* returns the fallback locale "en". For "en" no parent exists, {@code null} is returned.
|
||||
*
|
||||
*
|
||||
* @param locale the locale
|
||||
* @return the parent locale, may be {@code null}
|
||||
* @deprecated use {@link LocaleUtil#getParentLocale(Locale, RealmModel)} instead.
|
||||
*/
|
||||
@Deprecated(since = "26.5", forRemoval = true)
|
||||
public static Locale getParentLocale(Locale locale) {
|
||||
return getParentLocale(locale, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parent locale of the given {@code locale}. If the locale just contains a language (e.g. "de"),
|
||||
* returns the fallback default locale of the realm or if that does not exist "en".
|
||||
* For "en" no parent exists, {@code null} is returned.
|
||||
*
|
||||
* @return the parent locale, may be {@code null}
|
||||
*/
|
||||
public static Locale getParentLocale(Locale locale, RealmModel realm) {
|
||||
if (Locale.ENGLISH.equals(locale)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (locale.getVariant() != null && !locale.getVariant().isEmpty()) {
|
||||
return new Locale(locale.getLanguage(), locale.getCountry());
|
||||
}
|
||||
@ -75,11 +92,20 @@ public class LocaleUtil {
|
||||
return new Locale(locale.getLanguage());
|
||||
}
|
||||
|
||||
if (!Locale.ENGLISH.equals(locale)) {
|
||||
if (realm != null
|
||||
&& realm.isInternationalizationEnabled()
|
||||
&& realm.getDefaultLocale() != null
|
||||
&& Locale.forLanguageTag(realm.getDefaultLocale()).getLanguage().equals(locale.getLanguage())) {
|
||||
return Locale.ENGLISH;
|
||||
}
|
||||
|
||||
return null;
|
||||
if (realm != null
|
||||
&& realm.isInternationalizationEnabled()
|
||||
&& realm.getDefaultLocale() != null) {
|
||||
return Locale.forLanguageTag(realm.getDefaultLocale());
|
||||
}
|
||||
|
||||
return Locale.ENGLISH;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -90,10 +116,10 @@ public class LocaleUtil {
|
||||
* @param locale the locale
|
||||
* @return the applicable locales
|
||||
*/
|
||||
static List<Locale> getApplicableLocales(Locale locale) {
|
||||
static List<Locale> getApplicableLocales(Locale locale, RealmModel realm) {
|
||||
List<Locale> applicableLocales = new ArrayList<>();
|
||||
|
||||
for (Locale currentLocale = locale; currentLocale != null; currentLocale = getParentLocale(currentLocale)) {
|
||||
for (Locale currentLocale = locale; currentLocale != null; currentLocale = getParentLocale(currentLocale, realm)) {
|
||||
applicableLocales.add(currentLocale);
|
||||
}
|
||||
|
||||
@ -107,10 +133,10 @@ public class LocaleUtil {
|
||||
* @param locale the locale
|
||||
* @param messages the (locale-)grouped messages
|
||||
* @return the merged properties
|
||||
* @see #mergeGroupedMessages(Locale, Map, Map)
|
||||
* @see #mergeGroupedMessages(RealmModel, Locale, Map, Map)
|
||||
*/
|
||||
public static Properties mergeGroupedMessages(Locale locale, Map<Locale, Properties> messages) {
|
||||
return mergeGroupedMessages(locale, messages, null);
|
||||
public static Properties mergeGroupedMessages(RealmModel realm, Locale locale, Map<Locale, Properties> messages) {
|
||||
return mergeGroupedMessages(realm, locale, messages, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -147,11 +173,11 @@ public class LocaleUtil {
|
||||
* @param secondMessages may be {@code null}, the second (locale-)grouped messages, having lower priority (per
|
||||
* locale) than {@code firstMessages}
|
||||
* @return the merged properties
|
||||
* @see #mergeGroupedMessages(Locale, Map)
|
||||
* @see #mergeGroupedMessages(RealmModel, Locale, Map)
|
||||
*/
|
||||
public static Properties mergeGroupedMessages(Locale locale, Map<Locale, Properties> firstMessages,
|
||||
public static Properties mergeGroupedMessages(RealmModel realm, Locale locale, Map<Locale, Properties> firstMessages,
|
||||
Map<Locale, Properties> secondMessages) {
|
||||
List<Locale> applicableLocales = getApplicableLocales(locale);
|
||||
List<Locale> applicableLocales = getApplicableLocales(locale, realm);
|
||||
|
||||
Properties mergedProperties = new Properties();
|
||||
|
||||
@ -186,7 +212,7 @@ public class LocaleUtil {
|
||||
* the theme properties, but only when defined for the same locale. In general, texts for a more specific locale
|
||||
* take precedence over texts for a less specific locale.
|
||||
* <p>
|
||||
* For implementation details, see {@link #mergeGroupedMessages(Locale, Map, Map)}.
|
||||
* For implementation details, see {@link #mergeGroupedMessages(RealmModel, Locale, Map, Map)}.
|
||||
*
|
||||
* @param realm the realm from which the localization texts should be used
|
||||
* @param locale the locale for which the relevant texts should be retrieved
|
||||
@ -197,13 +223,13 @@ public class LocaleUtil {
|
||||
Map<Locale, Properties> themeMessages) {
|
||||
Map<Locale, Properties> realmLocalizationMessages = getRealmLocalizationTexts(realm, locale);
|
||||
|
||||
return mergeGroupedMessages(locale, realmLocalizationMessages, themeMessages);
|
||||
return mergeGroupedMessages(realm, locale, realmLocalizationMessages, themeMessages);
|
||||
}
|
||||
|
||||
public static Map<Locale, Properties> getRealmLocalizationTexts(RealmModel realm, Locale locale) {
|
||||
LinkedHashMap<Locale, Properties> groupedMessages = new LinkedHashMap<>();
|
||||
|
||||
List<Locale> applicableLocales = getApplicableLocales(locale);
|
||||
List<Locale> applicableLocales = getApplicableLocales(locale, realm);
|
||||
for (Locale applicableLocale : applicableLocales) {
|
||||
Map<String, String> currentRealmLocalizationTexts =
|
||||
realm.getRealmLocalizationTextsByLocale(applicableLocale.toLanguageTag());
|
||||
|
||||
@ -70,10 +70,11 @@ public class DefaultThemeManager implements ThemeManager {
|
||||
public Theme getTheme(String name, Theme.Type type) {
|
||||
Theme theme = factory.getCachedTheme(name, type);
|
||||
if (theme == null) {
|
||||
theme = loadTheme(name, type);
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
theme = loadTheme(name, type, realm);
|
||||
if (theme == null) {
|
||||
String defaultThemeName = session.getProvider(ThemeSelectorProvider.class).getDefaultThemeName(type);
|
||||
theme = loadTheme(defaultThemeName, type);
|
||||
theme = loadTheme(defaultThemeName, type, realm);
|
||||
log.errorv("Failed to find {0} theme {1}, using built-in themes", type, name);
|
||||
} else {
|
||||
theme = factory.addCachedTheme(name, type, theme);
|
||||
@ -106,7 +107,7 @@ public class DefaultThemeManager implements ThemeManager {
|
||||
public void close() {
|
||||
}
|
||||
|
||||
private Theme loadTheme(String name, Theme.Type type) {
|
||||
private Theme loadTheme(String name, Theme.Type type, RealmModel realm) {
|
||||
Theme theme = findTheme(name, type);
|
||||
if (theme == null) {
|
||||
return null;
|
||||
@ -131,7 +132,7 @@ public class DefaultThemeManager implements ThemeManager {
|
||||
}
|
||||
}
|
||||
|
||||
return new ExtendingTheme(themes, session.getAllProviders(ThemeResourceProvider.class));
|
||||
return new ExtendingTheme(realm, themes, session.getAllProviders(ThemeResourceProvider.class));
|
||||
}
|
||||
|
||||
private Theme findTheme(String name, Theme.Type type) {
|
||||
@ -162,6 +163,7 @@ public class DefaultThemeManager implements ThemeManager {
|
||||
|
||||
private static class ExtendingTheme implements Theme {
|
||||
|
||||
private final RealmModel realm;
|
||||
private final List<Theme> themes;
|
||||
private final Set<ThemeResourceProvider> themeResourceProviders;
|
||||
|
||||
@ -172,7 +174,8 @@ public class DefaultThemeManager implements ThemeManager {
|
||||
|
||||
private Pattern compiledContentHashPattern;
|
||||
|
||||
public ExtendingTheme(List<Theme> themes, Set<ThemeResourceProvider> themeResourceProviders) {
|
||||
public ExtendingTheme(RealmModel realm, List<Theme> themes, Set<ThemeResourceProvider> themeResourceProviders) {
|
||||
this.realm = realm;
|
||||
this.themes = themes;
|
||||
this.themeResourceProviders = themeResourceProviders;
|
||||
try {
|
||||
@ -251,7 +254,7 @@ public class DefaultThemeManager implements ThemeManager {
|
||||
@Override
|
||||
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
||||
Map<Locale, Properties> messagesByLocale = getMessagesByLocale(baseBundlename, locale);
|
||||
return LocaleUtil.mergeGroupedMessages(locale, messagesByLocale);
|
||||
return LocaleUtil.mergeGroupedMessages(realm, locale, messagesByLocale);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -267,7 +270,7 @@ public class DefaultThemeManager implements ThemeManager {
|
||||
|
||||
private Map<Locale, Properties> getMessagesByLocale(String baseBundlename, Locale locale) throws IOException {
|
||||
if (messages.get(baseBundlename) == null || messages.get(baseBundlename).get(locale) == null) {
|
||||
Locale parent = getParent(locale);
|
||||
Locale parent = LocaleUtil.getParentLocale(locale, realm);
|
||||
|
||||
Map<Locale, Properties> parentMessages =
|
||||
parent == null ? Collections.emptyMap() : getMessagesByLocale(baseBundlename, parent);
|
||||
@ -376,9 +379,6 @@ public class DefaultThemeManager implements ThemeManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static Locale getParent(Locale locale) {
|
||||
return LocaleUtil.getParentLocale(locale);
|
||||
}
|
||||
|
||||
private List<ThemeProvider> getProviders() {
|
||||
if (providers == null) {
|
||||
|
||||
@ -8,6 +8,9 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.utils.RealmModelDelegate;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
@ -18,29 +21,79 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
* @author <a href="mailto:daniel.fesenmeyer@bosch.com">Daniel Fesenmeyer</a>
|
||||
*/
|
||||
public class LocaleUtilTest {
|
||||
RealmModel REALM_ITALIAN = new RealmModelDelegate(null) {
|
||||
@Override
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultLocale() {
|
||||
return "it";
|
||||
}
|
||||
};
|
||||
|
||||
RealmModel REALM_ITALIAN_SWITZERLAND = new RealmModelDelegate(null) {
|
||||
@Override
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultLocale() {
|
||||
return "it-CH";
|
||||
}
|
||||
};
|
||||
|
||||
RealmModel REALM_ENGLISH = new RealmModelDelegate(null) {
|
||||
@Override
|
||||
public boolean isInternationalizationEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDefaultLocale() {
|
||||
return "en";
|
||||
}
|
||||
};
|
||||
|
||||
private static final Locale LOCALE_DE_CH = Locale.forLanguageTag("de-CH");
|
||||
private static final Locale LOCALE_DE_CH_1996 = Locale.forLanguageTag("de-CH-1996");
|
||||
|
||||
@Test
|
||||
public void getParentLocale() {
|
||||
assertThat(LocaleUtil.getParentLocale(LOCALE_DE_CH_1996), equalTo(LOCALE_DE_CH));
|
||||
assertThat(LocaleUtil.getParentLocale(LOCALE_DE_CH), equalTo(Locale.GERMAN));
|
||||
assertThat(LocaleUtil.getParentLocale(Locale.GERMAN), equalTo(Locale.ENGLISH));
|
||||
assertThat(LocaleUtil.getParentLocale(LOCALE_DE_CH_1996, null), equalTo(LOCALE_DE_CH));
|
||||
assertThat(LocaleUtil.getParentLocale(LOCALE_DE_CH, null), equalTo(Locale.GERMAN));
|
||||
assertThat(LocaleUtil.getParentLocale(Locale.GERMAN, null), equalTo(Locale.ENGLISH));
|
||||
|
||||
assertThat(LocaleUtil.getParentLocale(Locale.ENGLISH), nullValue());
|
||||
assertThat(LocaleUtil.getParentLocale(Locale.ENGLISH, null), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getParentLocaleWithRealmDefaultLocaleFallback() {
|
||||
assertThat(LocaleUtil.getParentLocale(LOCALE_DE_CH_1996, REALM_ITALIAN), equalTo(LOCALE_DE_CH));
|
||||
assertThat(LocaleUtil.getParentLocale(LOCALE_DE_CH, REALM_ITALIAN), equalTo(Locale.GERMAN));
|
||||
assertThat(LocaleUtil.getParentLocale(Locale.GERMAN, REALM_ITALIAN), equalTo(Locale.ITALIAN));
|
||||
assertThat(LocaleUtil.getParentLocale(Locale.ITALIAN, REALM_ITALIAN), equalTo(Locale.ENGLISH));
|
||||
assertThat(LocaleUtil.getParentLocale(Locale.ENGLISH, REALM_ITALIAN), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getApplicableLocales() {
|
||||
assertThat(LocaleUtil.getApplicableLocales(LOCALE_DE_CH_1996),
|
||||
assertThat(LocaleUtil.getApplicableLocales(LOCALE_DE_CH_1996, null),
|
||||
equalTo(Arrays.asList(LOCALE_DE_CH_1996, LOCALE_DE_CH, Locale.GERMAN, Locale.ENGLISH)));
|
||||
assertThat(LocaleUtil.getApplicableLocales(LOCALE_DE_CH),
|
||||
assertThat(LocaleUtil.getApplicableLocales(LOCALE_DE_CH, null),
|
||||
equalTo(Arrays.asList(LOCALE_DE_CH, Locale.GERMAN, Locale.ENGLISH)));
|
||||
assertThat(LocaleUtil.getApplicableLocales(Locale.GERMAN),
|
||||
assertThat(LocaleUtil.getApplicableLocales(Locale.GERMAN, null),
|
||||
equalTo(Arrays.asList(Locale.GERMAN, Locale.ENGLISH)));
|
||||
assertThat(LocaleUtil.getApplicableLocales(Locale.GERMAN, REALM_ITALIAN),
|
||||
equalTo(Arrays.asList(Locale.GERMAN, Locale.ITALIAN, Locale.ENGLISH)));
|
||||
assertThat(LocaleUtil.getApplicableLocales(Locale.GERMAN, REALM_ITALIAN_SWITZERLAND),
|
||||
equalTo(Arrays.asList(Locale.GERMAN, Locale.forLanguageTag("it-CH"), Locale.ITALIAN, Locale.ENGLISH)));
|
||||
assertThat(LocaleUtil.getApplicableLocales(Locale.GERMAN, REALM_ENGLISH),
|
||||
equalTo(Arrays.asList(Locale.GERMAN, Locale.ENGLISH)));
|
||||
|
||||
assertThat(LocaleUtil.getApplicableLocales(Locale.ENGLISH), equalTo(Collections.singletonList(Locale.ENGLISH)));
|
||||
assertThat(LocaleUtil.getApplicableLocales(Locale.ENGLISH, null), equalTo(Collections.singletonList(Locale.ENGLISH)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -75,7 +128,7 @@ public class LocaleUtilTest {
|
||||
keyDefinedForLanguageAndParents, keyDefinedForEnglishOnly), Locale.ENGLISH);
|
||||
groupedMessages.put(Locale.ENGLISH, englishMessages);
|
||||
|
||||
Properties mergedMessages = LocaleUtil.mergeGroupedMessages(LOCALE_DE_CH_1996, groupedMessages);
|
||||
Properties mergedMessages = LocaleUtil.mergeGroupedMessages(null, LOCALE_DE_CH_1996, groupedMessages);
|
||||
|
||||
Properties expectedMergedMessages = new Properties();
|
||||
addTestValue(expectedMergedMessages, keyDefinedEverywhere, LOCALE_DE_CH_1996);
|
||||
@ -164,7 +217,7 @@ public class LocaleUtilTest {
|
||||
groupedMessages2.put(Locale.ENGLISH, english2Messages);
|
||||
|
||||
Properties mergedMessages =
|
||||
LocaleUtil.mergeGroupedMessages(LOCALE_DE_CH_1996, groupedMessages1, groupedMessages2);
|
||||
LocaleUtil.mergeGroupedMessages(null, LOCALE_DE_CH_1996, groupedMessages1, groupedMessages2);
|
||||
|
||||
Properties expectedMergedMessages = new Properties();
|
||||
addTestValue(expectedMergedMessages, keyDefinedForVariantFromMessages1AndFallbacks, LOCALE_DE_CH_1996,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user