Consistent message resolving regarding language fallbacks for all themes
- the prio of messages is now as follows for all themes (RL = realm localization, T = Theme i18n files): RL <variant> > T <variant> > RL <region> > T <region> > RL <language> > T <language> > RL en > T en - centralize the message resolving logic in helper methods in LocaleUtil and use it for all themes, add unit tests in LocaleUtilTest - add basic integration tests to check whether realm localization can be used in all supported contexts: - Account UI V2: org.keycloak.testsuite.ui.account2.InternationalizationTest - Login theme: LoginPageTest - Email theme: EmailTest - deprecate the param useRealmDefaultLocaleFallback=true of endpoint /admin/realms/{realm}/localization/{locale}, because it does not resolve fallbacks as expected and is no longer used in admin-ui v2 - fix locale selection in DefaultLocaleSelectorProvider that a supported region (like "de-CH") will no longer selected instead of a supported language (like "de"), when just the language is requested, add corresponding unit tests - improvements regarding message resolving in Admin UI V2: - add cypress test i18n_test.spec.ts, which checks the fallback implementation - log a warning instead of an error, when messages for some languages/namespaces cannot be loaded (the page will probably work with fallbacks in that case) Closes #15845
This commit is contained in:
parent
74dd370906
commit
d543ba5b56
26 changed files with 1030 additions and 276 deletions
|
@ -185,3 +185,13 @@ Keycloak's proxy configuration setting for mode *passthrough* no longer parses H
|
||||||
Installations that want the HTTP headers in the client's request to be parsed should use the **edge** or **reencrypt** setting.
|
Installations that want the HTTP headers in the client's request to be parsed should use the **edge** or **reencrypt** setting.
|
||||||
|
|
||||||
See https://www.keycloak.org/server/reverseproxy[Using a reverse proxy] for details.
|
See https://www.keycloak.org/server/reverseproxy[Using a reverse proxy] for details.
|
||||||
|
|
||||||
|
= Consistent fallback message resolving for all themes
|
||||||
|
|
||||||
|
This change only may affect you when you are using realm localization messages.
|
||||||
|
|
||||||
|
Up to this version, the resolving of fallback messages was inconsistent across themes, when realm localization messages were used. More information can be found in the following https://github.com/keycloak/keycloak/issues/15845[issue].
|
||||||
|
|
||||||
|
The implementation has now been unified for all themes. In general, the message for the most specific matching language tag has the highest priority. If there are both a realm localization message and a Theme 18n message, the realm localization message has the higher priority. Summarized, the priority of the messages is as follows (RL = realm localization, T = Theme i18n files): `RL <variant> > T <variant> > RL <region> > T <region> > RL <language> > T <language> > RL en > T en`.
|
||||||
|
|
||||||
|
Probably this can be better explained with an example: When the variant `de-CH-1996` is requested and there is a realm localization message for the variant, this message will be used. If such a realm localization message does not exist, the Theme i18n files are searched for a corresponding message for that variant. If such a message does not exist, a realm localization message for the region (`de-CH`) will be searched. If such a realm localization message does not exist, the Theme i18n files are searched for a message for that region. If still no message is found, a realm localization message for the language (`de`) will be searched. If there is no matching realm localization message, the Theme i18n files are be searched for a message for that language. As last fallback, the English (`en`) translation is used: First, an English realm localization will be searched - if not found, the Theme 18n files are searched for an English message.
|
||||||
|
|
|
@ -37,10 +37,35 @@ public interface RealmLocalizationResource {
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
List<String> getRealmSpecificLocales();
|
List<String> getRealmSpecificLocales();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the localization texts for the given locale.
|
||||||
|
*
|
||||||
|
* @param locale the locale
|
||||||
|
* @return the localization texts
|
||||||
|
*/
|
||||||
@Path("{locale}")
|
@Path("{locale}")
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
Map<String, String> getRealmLocalizationTexts(final @PathParam("locale") String locale, @QueryParam("useRealmDefaultLocaleFallback") Boolean useRealmDefaultLocaleFallback);
|
Map<String, String> getRealmLocalizationTexts(final @PathParam("locale") String locale);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DEPRECATED - Get the localization texts for the given locale.
|
||||||
|
*
|
||||||
|
* @param locale the locale
|
||||||
|
* @param useRealmDefaultLocaleFallback whether the localization texts for the realm default locale should be used
|
||||||
|
* as fallbacks in the result
|
||||||
|
* @return the localization texts
|
||||||
|
* @deprecated use {@link #getRealmLocalizationTexts(String)}, in order to retrieve localization texts without
|
||||||
|
* fallbacks. If you need fallbacks, call the endpoint multiple time with all the relevant locales (e.g.
|
||||||
|
* "de" in case of "de-CH") - the realm default locale is NOT the only fallback to be considered.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Path("{locale}")
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
Map<String, String> getRealmLocalizationTexts(final @PathParam("locale") String locale,
|
||||||
|
@QueryParam("useRealmDefaultLocaleFallback") Boolean useRealmDefaultLocaleFallback);
|
||||||
|
|
||||||
|
|
||||||
@Path("{locale}/{key}")
|
@Path("{locale}/{key}")
|
||||||
|
|
178
js/apps/admin-ui/cypress/e2e/i18n_test.spec.ts
Normal file
178
js/apps/admin-ui/cypress/e2e/i18n_test.spec.ts
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
import LoginPage from "../support/pages/LoginPage";
|
||||||
|
import SidebarPage from "../support/pages/admin-ui/SidebarPage";
|
||||||
|
import adminClient from "../support/util/AdminClient";
|
||||||
|
import { keycloakBefore } from "../support/util/keycloak_hooks";
|
||||||
|
import ProviderPage from "../support/pages/admin-ui/manage/providers/ProviderPage";
|
||||||
|
import RealmRepresentation from "libs/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
|
|
||||||
|
const loginPage = new LoginPage();
|
||||||
|
const sidebarPage = new SidebarPage();
|
||||||
|
|
||||||
|
const providersPage = new ProviderPage();
|
||||||
|
|
||||||
|
const usernameI18nTest = "user_i18n_test";
|
||||||
|
let usernameI18nId: string;
|
||||||
|
|
||||||
|
let originalMasterRealm: RealmRepresentation;
|
||||||
|
|
||||||
|
describe("i18n tests", () => {
|
||||||
|
before(() => {
|
||||||
|
cy.wrap(null).then(async () => {
|
||||||
|
const realm = (await adminClient.getRealm("master"))!;
|
||||||
|
originalMasterRealm = realm;
|
||||||
|
realm.supportedLocales = ["en", "de", "de-CH", "fo"];
|
||||||
|
realm.internationalizationEnabled = true;
|
||||||
|
await adminClient.updateRealm("master", realm);
|
||||||
|
|
||||||
|
const { id: userId } = await adminClient.createUser({
|
||||||
|
username: usernameI18nTest,
|
||||||
|
enabled: true,
|
||||||
|
credentials: [
|
||||||
|
{ type: "password", temporary: false, value: usernameI18nTest },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
usernameI18nId = userId!;
|
||||||
|
|
||||||
|
await adminClient.addRealmRoleToUser(usernameI18nId, "admin");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await adminClient.deleteUser(usernameI18nTest);
|
||||||
|
|
||||||
|
if (originalMasterRealm) {
|
||||||
|
await adminClient.updateRealm("master", originalMasterRealm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await adminClient.removeAllLocalizationTexts();
|
||||||
|
});
|
||||||
|
|
||||||
|
const realmLocalizationEn = "realmSettings en";
|
||||||
|
const themeLocalizationEn = "Realm settings";
|
||||||
|
const realmLocalizationDe = "realmSettings de";
|
||||||
|
const themeLocalizationDe = "Realm-Einstellungen";
|
||||||
|
const realmLocalizationDeCh = "realmSettings de-CH";
|
||||||
|
|
||||||
|
it("should use THEME localization for fallback (en) when language without theme localization is requested and no realm localization exists", () => {
|
||||||
|
updateUserLocale("fo");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
sidebarPage.checkRealmSettingsLinkContainsText(themeLocalizationEn);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use THEME localization for language when language with theme localization is requested and no realm localization exists", () => {
|
||||||
|
updateUserLocale("de");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
sidebarPage.checkRealmSettingsLinkContainsText(themeLocalizationDe);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use REALM localization for fallback (en) when language without theme localization is requested and realm localization exists for fallback (en)", () => {
|
||||||
|
addCommonRealmSettingsLocalizationText("en", realmLocalizationEn);
|
||||||
|
updateUserLocale("fo");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
sidebarPage.checkRealmSettingsLinkContainsText(realmLocalizationEn);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use THEME localization for language when language with theme localization is requested and realm localization exists for fallback (en) only", () => {
|
||||||
|
addCommonRealmSettingsLocalizationText("en", realmLocalizationEn);
|
||||||
|
updateUserLocale("de");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
sidebarPage.checkRealmSettingsLinkContainsText(themeLocalizationDe);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use REALM localization for language when language is requested and realm localization exists for language", () => {
|
||||||
|
addCommonRealmSettingsLocalizationText("de", realmLocalizationDe);
|
||||||
|
updateUserLocale("de");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
sidebarPage.checkRealmSettingsLinkContainsText(realmLocalizationDe);
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: currently skipped due to https://github.com/keycloak/keycloak/issues/20412
|
||||||
|
it.skip("should use REALM localization for region when region is requested and realm localization exists for region", () => {
|
||||||
|
addCommonRealmSettingsLocalizationText("de-CH", realmLocalizationDeCh);
|
||||||
|
updateUserLocale("de-CH");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
sidebarPage.checkRealmSettingsLinkContainsText(realmLocalizationDeCh);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use REALM localization for language when language is requested and realm localization exists for fallback (en), language, region", () => {
|
||||||
|
addCommonRealmSettingsLocalizationText("en", realmLocalizationEn);
|
||||||
|
addCommonRealmSettingsLocalizationText("de", realmLocalizationDe);
|
||||||
|
addCommonRealmSettingsLocalizationText("de-CH", realmLocalizationDeCh);
|
||||||
|
updateUserLocale("de");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
sidebarPage.checkRealmSettingsLinkContainsText(realmLocalizationDe);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use REALM localization for language when region is requested and realm localization exists for fallback (en), language", () => {
|
||||||
|
addCommonRealmSettingsLocalizationText("en", realmLocalizationEn);
|
||||||
|
addCommonRealmSettingsLocalizationText("de", realmLocalizationDe);
|
||||||
|
updateUserLocale("de-CH");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
sidebarPage.checkRealmSettingsLinkContainsText(realmLocalizationDe);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should apply plurals and interpolation for THEME localization", () => {
|
||||||
|
updateUserLocale("en");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
// check key "user-federation:addProvider_other"
|
||||||
|
providersPage.assertCardContainsText("ldap", "Add Ldap providers");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should apply plurals and interpolation for REALM localization", () => {
|
||||||
|
addLocalization(
|
||||||
|
"en",
|
||||||
|
"user-federation:addProvider_other",
|
||||||
|
"addProvider_other en: {{provider}}"
|
||||||
|
);
|
||||||
|
updateUserLocale("en");
|
||||||
|
|
||||||
|
goToUserFederationPage();
|
||||||
|
|
||||||
|
providersPage.assertCardContainsText("ldap", "addProvider_other en: Ldap");
|
||||||
|
});
|
||||||
|
|
||||||
|
function goToUserFederationPage() {
|
||||||
|
loginPage.logIn(usernameI18nTest, usernameI18nTest);
|
||||||
|
keycloakBefore();
|
||||||
|
sidebarPage.goToUserFederation();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateUserLocale(locale: string) {
|
||||||
|
cy.wrap(null).then(() =>
|
||||||
|
adminClient.updateUser(usernameI18nId, { attributes: { locale: locale } })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCommonRealmSettingsLocalizationText(
|
||||||
|
locale: string,
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
addLocalization(locale, "common:realmSettings", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLocalization(locale: string, key: string, value: string) {
|
||||||
|
cy.wrap(null).then(() =>
|
||||||
|
adminClient.addLocalizationText(locale, key, value)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
|
@ -142,4 +142,8 @@ export default class SidebarPage extends CommonElements {
|
||||||
cy.get('[role="progressbar"]').should("not.exist");
|
cy.get('[role="progressbar"]').should("not.exist");
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkRealmSettingsLinkContainsText(expectedText: string) {
|
||||||
|
cy.get(this.realmSettingsBtn).should("contain", expectedText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -430,6 +430,11 @@ export default class ProviderPage {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertCardContainsText(providerType: string, expectedText: string) {
|
||||||
|
cy.findByTestId(`${providerType}-card`).should("contain", expectedText);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
disableEnabledSwitch(providerType: string) {
|
disableEnabledSwitch(providerType: string) {
|
||||||
cy.get(`#${providerType}-switch`).uncheck({ force: true });
|
cy.get(`#${providerType}-switch`).uncheck({ force: true });
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import type ClientScopeRepresentation from "@keycloak/keycloak-admin-client/lib/
|
||||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||||
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||||
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||||
|
import type { RoleMappingPayload } from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
|
||||||
import { merge } from "lodash-es";
|
import { merge } from "lodash-es";
|
||||||
|
|
||||||
class AdminClient {
|
class AdminClient {
|
||||||
|
@ -41,6 +42,11 @@ class AdminClient {
|
||||||
await this.client.realms.update({ realm }, payload);
|
await this.client.realms.update({ realm }, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRealm(realm: string) {
|
||||||
|
await this.login();
|
||||||
|
return await this.client.realms.findOne({ realm });
|
||||||
|
}
|
||||||
|
|
||||||
async deleteRealm(realm: string) {
|
async deleteRealm(realm: string) {
|
||||||
await this.login();
|
await this.login();
|
||||||
await this.client.realms.del({ realm });
|
await this.client.realms.del({ realm });
|
||||||
|
@ -130,6 +136,17 @@ class AdminClient {
|
||||||
await this.client.users.addToGroup({ id: user.id!, groupId });
|
await this.client.users.addToGroup({ id: user.id!, groupId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addRealmRoleToUser(userId: string, roleName: string) {
|
||||||
|
await this.login();
|
||||||
|
|
||||||
|
const realmRole = await this.client.roles.findOneByName({ name: roleName });
|
||||||
|
|
||||||
|
await this.client.users.addRealmRoleMappings({
|
||||||
|
id: userId,
|
||||||
|
roles: [realmRole as RoleMappingPayload],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async deleteUser(username: string) {
|
async deleteUser(username: string) {
|
||||||
await this.login();
|
await this.login();
|
||||||
const user = await this.client.users.find({ username });
|
const user = await this.client.users.find({ username });
|
||||||
|
@ -260,6 +277,29 @@ class AdminClient {
|
||||||
federatedIdentity: fedIdentity,
|
federatedIdentity: fedIdentity,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addLocalizationText(locale: string, key: string, value: string) {
|
||||||
|
await this.login();
|
||||||
|
await this.client.realms.addLocalization(
|
||||||
|
{ realm: this.client.realmName, selectedLocale: locale, key: key },
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeAllLocalizationTexts() {
|
||||||
|
await this.login();
|
||||||
|
const localesWithTexts = await this.client.realms.getRealmSpecificLocales({
|
||||||
|
realm: this.client.realmName,
|
||||||
|
});
|
||||||
|
await Promise.all(
|
||||||
|
localesWithTexts.map((locale) =>
|
||||||
|
this.client.realms.deleteRealmLocalizationTexts({
|
||||||
|
realm: this.client.realmName,
|
||||||
|
selectedLocale: locale,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const adminClient = new AdminClient();
|
const adminClient = new AdminClient();
|
||||||
|
|
|
@ -12,7 +12,9 @@ export class WhoAmI {
|
||||||
constructor(private me?: WhoAmIRepresentation) {
|
constructor(private me?: WhoAmIRepresentation) {
|
||||||
if (this.me?.locale) {
|
if (this.me?.locale) {
|
||||||
i18n.changeLanguage(this.me.locale, (error) => {
|
i18n.changeLanguage(this.me.locale, (error) => {
|
||||||
if (error) console.error("Unable to set locale to", this.me?.locale);
|
if (error) {
|
||||||
|
console.warn("Error(s) loading locale", this.me?.locale, error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,8 @@
|
||||||
|
|
||||||
package org.keycloak.theme;
|
package org.keycloak.theme;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -63,6 +65,20 @@ public interface Theme {
|
||||||
*/
|
*/
|
||||||
Properties getMessages(String baseBundlename, Locale locale) throws IOException;
|
Properties getMessages(String baseBundlename, Locale locale) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve localized messages from a message bundle named "messages" and enhance those messages with messages from
|
||||||
|
* realm localization.
|
||||||
|
* <p>
|
||||||
|
* In general, the translation for the most specific applicable language is used. If a translation exists both in the message bundle and realm localization, the realm localization translation is used.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param realm The realm from which the localization should be retrieved
|
||||||
|
* @param locale The locale of the desired message bundle.
|
||||||
|
* @return The localized messages from the bundle, enhanced with realm localization
|
||||||
|
* @throws IOException If bundle can not be read.
|
||||||
|
*/
|
||||||
|
Properties getEnhancedMessages(RealmModel realm, Locale locale) throws IOException;
|
||||||
|
|
||||||
Properties getProperties() throws IOException;
|
Properties getProperties() throws IOException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,6 @@ import org.keycloak.theme.Theme;
|
||||||
import org.keycloak.theme.beans.LinkExpirationFormatterMethod;
|
import org.keycloak.theme.beans.LinkExpirationFormatterMethod;
|
||||||
import org.keycloak.theme.beans.MessageFormatterMethod;
|
import org.keycloak.theme.beans.MessageFormatterMethod;
|
||||||
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||||
import org.keycloak.utils.StringUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -212,20 +211,17 @@ public class FreeMarkerEmailTemplateProvider implements EmailTemplateProvider {
|
||||||
Theme theme = getTheme();
|
Theme theme = getTheme();
|
||||||
Locale locale = session.getContext().resolveLocale(user);
|
Locale locale = session.getContext().resolveLocale(user);
|
||||||
attributes.put("locale", locale);
|
attributes.put("locale", locale);
|
||||||
KeycloakUriInfo uriInfo = session.getContext().getUri();
|
|
||||||
Properties rb = new Properties();
|
Properties messages = theme.getEnhancedMessages(realm, locale);
|
||||||
if(!StringUtil.isNotBlank(realm.getDefaultLocale()))
|
attributes.put("msg", new MessageFormatterMethod(locale, messages));
|
||||||
{
|
|
||||||
rb.putAll(realm.getRealmLocalizationTextsByLocale(realm.getDefaultLocale()));
|
|
||||||
}
|
|
||||||
rb.putAll(theme.getMessages(locale));
|
|
||||||
rb.putAll(realm.getRealmLocalizationTextsByLocale(locale.toLanguageTag()));
|
|
||||||
attributes.put("msg", new MessageFormatterMethod(locale, rb));
|
|
||||||
attributes.put("properties", theme.getProperties());
|
attributes.put("properties", theme.getProperties());
|
||||||
attributes.put("realmName", getRealmName());
|
attributes.put("realmName", getRealmName());
|
||||||
attributes.put("user", new ProfileBean(user));
|
attributes.put("user", new ProfileBean(user));
|
||||||
|
KeycloakUriInfo uriInfo = session.getContext().getUri();
|
||||||
attributes.put("url", new UrlBean(realm, theme, uriInfo.getBaseUri(), null));
|
attributes.put("url", new UrlBean(realm, theme, uriInfo.getBaseUri(), null));
|
||||||
String subject = new MessageFormat(rb.getProperty(subjectKey, subjectKey), locale).format(subjectAttributes.toArray());
|
|
||||||
|
String subject = new MessageFormat(messages.getProperty(subjectKey, subjectKey), locale).format(subjectAttributes.toArray());
|
||||||
String textTemplate = String.format("text/%s", template);
|
String textTemplate = String.format("text/%s", template);
|
||||||
String textBody;
|
String textBody;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -48,7 +48,6 @@ import org.keycloak.theme.beans.MessageType;
|
||||||
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
import org.keycloak.theme.beans.MessagesPerFieldBean;
|
||||||
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||||
import org.keycloak.utils.MediaType;
|
import org.keycloak.utils.MediaType;
|
||||||
import org.keycloak.utils.StringUtil;
|
|
||||||
|
|
||||||
import jakarta.ws.rs.core.HttpHeaders;
|
import jakarta.ws.rs.core.HttpHeaders;
|
||||||
import jakarta.ws.rs.core.MultivaluedMap;
|
import jakarta.ws.rs.core.MultivaluedMap;
|
||||||
|
@ -217,14 +216,9 @@ public class FreeMarkerAccountProvider implements AccountProvider {
|
||||||
* @return message bundle for other use
|
* @return message bundle for other use
|
||||||
*/
|
*/
|
||||||
protected Properties handleThemeResources(Theme theme, Locale locale, Map<String, Object> attributes) {
|
protected Properties handleThemeResources(Theme theme, Locale locale, Map<String, Object> attributes) {
|
||||||
Properties messagesBundle = new Properties();
|
Properties messagesBundle;
|
||||||
try {
|
try {
|
||||||
if(!StringUtil.isNotBlank(realm.getDefaultLocale()))
|
messagesBundle = theme.getEnhancedMessages(realm, locale);
|
||||||
{
|
|
||||||
messagesBundle.putAll(realm.getRealmLocalizationTextsByLocale(realm.getDefaultLocale()));
|
|
||||||
}
|
|
||||||
messagesBundle.putAll(theme.getMessages(locale));
|
|
||||||
messagesBundle.putAll(realm.getRealmLocalizationTextsByLocale(locale.toLanguageTag()));
|
|
||||||
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("Failed to load messages", e);
|
logger.warn("Failed to load messages", e);
|
||||||
|
|
|
@ -92,7 +92,6 @@ import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||||
import org.keycloak.userprofile.UserProfileContext;
|
import org.keycloak.userprofile.UserProfileContext;
|
||||||
import org.keycloak.userprofile.UserProfileProvider;
|
import org.keycloak.userprofile.UserProfileProvider;
|
||||||
import org.keycloak.utils.MediaType;
|
import org.keycloak.utils.MediaType;
|
||||||
import org.keycloak.utils.StringUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -379,14 +378,9 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
* @return message bundle for other use
|
* @return message bundle for other use
|
||||||
*/
|
*/
|
||||||
protected Properties handleThemeResources(Theme theme, Locale locale) {
|
protected Properties handleThemeResources(Theme theme, Locale locale) {
|
||||||
Properties messagesBundle = new Properties();
|
Properties messagesBundle;
|
||||||
try {
|
try {
|
||||||
if(!StringUtil.isNotBlank(realm.getDefaultLocale()))
|
messagesBundle = theme.getEnhancedMessages(realm, locale);
|
||||||
{
|
|
||||||
messagesBundle.putAll(realm.getRealmLocalizationTextsByLocale(realm.getDefaultLocale()));
|
|
||||||
}
|
|
||||||
messagesBundle.putAll(theme.getMessages(locale));
|
|
||||||
messagesBundle.putAll(realm.getRealmLocalizationTextsByLocale(locale.toLanguageTag()));
|
|
||||||
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
attributes.put("msg", new MessageFormatterMethod(locale, messagesBundle));
|
||||||
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
|
attributes.put("advancedMsg", new AdvancedMessageFormatterMethod(locale, messagesBundle));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -441,8 +435,6 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
||||||
|
|
||||||
Locale locale = session.getContext().resolveLocale(user);
|
Locale locale = session.getContext().resolveLocale(user);
|
||||||
Properties messagesBundle = handleThemeResources(theme, locale);
|
Properties messagesBundle = handleThemeResources(theme, locale);
|
||||||
Map<String, String> localizationTexts = realm.getRealmLocalizationTextsByLocale(locale.getCountry());
|
|
||||||
messagesBundle.putAll(localizationTexts);
|
|
||||||
FormMessage msg = new FormMessage(null, message);
|
FormMessage msg = new FormMessage(null, message);
|
||||||
return formatMessage(msg, messagesBundle, locale);
|
return formatMessage(msg, messagesBundle, locale);
|
||||||
}
|
}
|
||||||
|
|
|
@ -166,18 +166,19 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||||
private Locale findLocale(RealmModel realm, String... localeStrings) {
|
private Locale findLocale(RealmModel realm, String... localeStrings) {
|
||||||
List<Locale> supportedLocales = realm.getSupportedLocalesStream()
|
List<Locale> supportedLocales = realm.getSupportedLocalesStream()
|
||||||
.map(Locale::forLanguageTag).collect(Collectors.toList());
|
.map(Locale::forLanguageTag).collect(Collectors.toList());
|
||||||
|
|
||||||
|
return findBestMatchingLocale(supportedLocales, localeStrings);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Locale findBestMatchingLocale(List<Locale> supportedLocales, String... localeStrings) {
|
||||||
for (String localeString : localeStrings) {
|
for (String localeString : localeStrings) {
|
||||||
if (localeString != null) {
|
if (localeString != null) {
|
||||||
Locale result = null;
|
Locale result = null;
|
||||||
Locale search = Locale.forLanguageTag(localeString);
|
Locale search = Locale.forLanguageTag(localeString);
|
||||||
for (Locale supportedLocale : supportedLocales) {
|
for (Locale supportedLocale : supportedLocales) {
|
||||||
if (supportedLocale.getLanguage().equals(search.getLanguage())) {
|
if (doesLocaleMatch(search, supportedLocale) && (result == null
|
||||||
if (search.getCountry().equals("") ^ supportedLocale.getCountry().equals("") && result == null) {
|
|| doesFirstLocaleBetterMatchThanSecondLocale(supportedLocale, result, search))) {
|
||||||
result = supportedLocale;
|
result = supportedLocale;
|
||||||
}
|
|
||||||
if (supportedLocale.getCountry().equals(search.getCountry())) {
|
|
||||||
return supportedLocale;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
@ -188,6 +189,28 @@ public class DefaultLocaleSelectorProvider implements LocaleSelectorProvider {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean doesLocaleMatch(Locale candidate, Locale supportedLocale) {
|
||||||
|
return candidate.getLanguage().equals(supportedLocale.getLanguage())
|
||||||
|
&& ((candidate.getCountry().equals("") ^ supportedLocale.getCountry().equals(""))
|
||||||
|
|| candidate.getCountry().equals(supportedLocale.getCountry()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean doesFirstLocaleBetterMatchThanSecondLocale(Locale firstLocale, Locale secondLocale,
|
||||||
|
Locale supportedLocale) {
|
||||||
|
if (firstLocale.getLanguage().equals(supportedLocale.getLanguage())
|
||||||
|
&& !secondLocale.getLanguage().equals(supportedLocale.getLanguage())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstLocale.getCountry().equals(supportedLocale.getCountry())
|
||||||
|
&& !secondLocale.getCountry().equals(supportedLocale.getCountry())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstLocale.getVariant().equals(supportedLocale.getVariant())
|
||||||
|
&& !secondLocale.getVariant().equals(supportedLocale.getVariant());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ import org.keycloak.theme.freemarker.FreeMarkerProvider;
|
||||||
import org.keycloak.urls.UrlType;
|
import org.keycloak.urls.UrlType;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.utils.MediaType;
|
import org.keycloak.utils.MediaType;
|
||||||
import org.keycloak.utils.StringUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by st on 29/03/17.
|
* Created by st on 29/03/17.
|
||||||
|
@ -111,12 +110,7 @@ public class AccountConsole {
|
||||||
if (auth != null) user = auth.getUser();
|
if (auth != null) user = auth.getUser();
|
||||||
Locale locale = session.getContext().resolveLocale(user);
|
Locale locale = session.getContext().resolveLocale(user);
|
||||||
map.put("locale", locale.toLanguageTag());
|
map.put("locale", locale.toLanguageTag());
|
||||||
Properties messages = new Properties();
|
Properties messages = theme.getEnhancedMessages(realm, locale);
|
||||||
messages.putAll(theme.getMessages(locale));
|
|
||||||
if(StringUtil.isNotBlank(realm.getDefaultLocale())) {
|
|
||||||
messages.putAll(realm.getRealmLocalizationTextsByLocale(realm.getDefaultLocale()));
|
|
||||||
}
|
|
||||||
messages.putAll(realm.getRealmLocalizationTextsByLocale(locale.toLanguageTag()));
|
|
||||||
map.put("msg", new MessageFormatterMethod(locale, messages));
|
map.put("msg", new MessageFormatterMethod(locale, messages));
|
||||||
map.put("msgJSON", messagesToJsonString(messages));
|
map.put("msgJSON", messagesToJsonString(messages));
|
||||||
map.put("supportedLocales", supportedLocales(messages));
|
map.put("supportedLocales", supportedLocales(messages));
|
||||||
|
|
|
@ -27,7 +27,6 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.theme.Theme;
|
import org.keycloak.theme.Theme;
|
||||||
import org.keycloak.utils.StringUtil;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Message formatter for Admin GUI/API messages.
|
* Message formatter for Admin GUI/API messages.
|
||||||
|
@ -48,13 +47,8 @@ public class AdminMessageFormatter implements BiFunction<String, Object[], Strin
|
||||||
try {
|
try {
|
||||||
KeycloakContext context = session.getContext();
|
KeycloakContext context = session.getContext();
|
||||||
locale = context.resolveLocale(user);
|
locale = context.resolveLocale(user);
|
||||||
messages = new Properties();
|
|
||||||
messages.putAll(getTheme(session).getMessages(locale));
|
|
||||||
RealmModel realm = context.getRealm();
|
RealmModel realm = context.getRealm();
|
||||||
if(StringUtil.isNotBlank(realm.getDefaultLocale())) {
|
messages = getTheme(session).getEnhancedMessages(realm, locale);
|
||||||
messages.putAll(realm.getRealmLocalizationTextsByLocale(realm.getDefaultLocale()));
|
|
||||||
}
|
|
||||||
messages.putAll(realm.getRealmLocalizationTextsByLocale(locale.toLanguageTag()));
|
|
||||||
} catch (IOException cause) {
|
} catch (IOException cause) {
|
||||||
throw new RuntimeException("Failed to configure error messages", cause);
|
throw new RuntimeException("Failed to configure error messages", cause);
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,18 +136,23 @@ public class RealmLocalizationResource {
|
||||||
@Path("{locale}")
|
@Path("{locale}")
|
||||||
@GET
|
@GET
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Map<String, String> getRealmLocalizationTexts(@PathParam("locale") String locale, @QueryParam("useRealmDefaultLocaleFallback") Boolean useFallback) {
|
public Map<String, String> getRealmLocalizationTexts(@PathParam("locale") String locale,
|
||||||
|
@Deprecated @QueryParam("useRealmDefaultLocaleFallback") Boolean useFallback) {
|
||||||
auth.requireAnyAdminRole();
|
auth.requireAnyAdminRole();
|
||||||
|
|
||||||
Map<String, String> realmLocalizationTexts = new HashMap<>();
|
// this fallback is no longer needed since the fix for #15845, don't forget to remove it from the API
|
||||||
if(useFallback != null && useFallback && StringUtil.isNotBlank(realm.getDefaultLocale())) {
|
if (useFallback != null && useFallback) {
|
||||||
realmLocalizationTexts.putAll(realm.getRealmLocalizationTextsByLocale(realm.getDefaultLocale()));
|
Map<String, String> realmLocalizationTexts = new HashMap<>();
|
||||||
|
if (StringUtil.isNotBlank(realm.getDefaultLocale())) {
|
||||||
|
realmLocalizationTexts.putAll(realm.getRealmLocalizationTextsByLocale(realm.getDefaultLocale()));
|
||||||
|
}
|
||||||
|
|
||||||
|
realmLocalizationTexts.putAll(realm.getRealmLocalizationTextsByLocale(locale));
|
||||||
|
|
||||||
|
return realmLocalizationTexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
realmLocalizationTexts.putAll(realm.getRealmLocalizationTextsByLocale(locale));
|
return realm.getRealmLocalizationTextsByLocale(locale);
|
||||||
|
|
||||||
return realmLocalizationTexts;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{locale}/{key}")
|
@Path("{locale}/{key}")
|
||||||
|
|
|
@ -24,11 +24,24 @@ import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.sessions.AuthenticationSessionModel;
|
import org.keycloak.sessions.AuthenticationSessionModel;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ListIterator;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
* @author <a href="mailto:daniel.fesenmeyer@bosch.com">Daniel Fesenmeyer</a>
|
||||||
*/
|
*/
|
||||||
public class LocaleUtil {
|
public class LocaleUtil {
|
||||||
|
|
||||||
|
private LocaleUtil() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
public static void processLocaleParam(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession) {
|
public static void processLocaleParam(KeycloakSession session, RealmModel realm, AuthenticationSessionModel authSession) {
|
||||||
if (authSession != null && realm.isInternationalizationEnabled()) {
|
if (authSession != null && realm.isInternationalizationEnabled()) {
|
||||||
String locale = session.getContext().getUri().getQueryParameters().getFirst(LocaleSelectorProvider.KC_LOCALE_PARAM);
|
String locale = session.getContext().getUri().getQueryParameters().getFirst(LocaleSelectorProvider.KC_LOCALE_PARAM);
|
||||||
|
@ -40,4 +53,162 @@ 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}
|
||||||
|
*/
|
||||||
|
public static Locale getParentLocale(Locale locale) {
|
||||||
|
if (locale.getVariant() != null && !locale.getVariant().isEmpty()) {
|
||||||
|
return new Locale(locale.getLanguage(), locale.getCountry());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (locale.getCountry() != null && !locale.getCountry().isEmpty()) {
|
||||||
|
return new Locale(locale.getLanguage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Locale.ENGLISH.equals(locale)) {
|
||||||
|
return Locale.ENGLISH;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the applicable locales for the given locale.
|
||||||
|
* <p>
|
||||||
|
* Example: Locale "de-CH" has the applicable locales "de-CH", "de" and "en" (in exactly that order).
|
||||||
|
*
|
||||||
|
* @param locale the locale
|
||||||
|
* @return the applicable locales
|
||||||
|
*/
|
||||||
|
static List<Locale> getApplicableLocales(Locale locale) {
|
||||||
|
List<Locale> applicableLocales = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Locale currentLocale = locale; currentLocale != null; currentLocale = getParentLocale(currentLocale)) {
|
||||||
|
applicableLocales.add(currentLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return applicableLocales;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the given (locale-)grouped messages into one instance of {@link Properties}, applicable for the given
|
||||||
|
* {@code locale}.
|
||||||
|
*
|
||||||
|
* @param locale the locale
|
||||||
|
* @param messages the (locale-)grouped messages
|
||||||
|
* @return the merged properties
|
||||||
|
* @see #mergeGroupedMessages(Locale, Map, Map)
|
||||||
|
*/
|
||||||
|
public static Properties mergeGroupedMessages(Locale locale, Map<Locale, Properties> messages) {
|
||||||
|
return mergeGroupedMessages(locale, messages, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the given (locale-)grouped messages into one instance of {@link Properties}, applicable for the given
|
||||||
|
* {@code locale}.
|
||||||
|
* <p>
|
||||||
|
* The priority of the messages is as follows (abbreviations: F = firstMessages, S = secondMessages):
|
||||||
|
* <ol>
|
||||||
|
* <li>F <language-region-variant></li>
|
||||||
|
* <li>S <language-region-variant></li>
|
||||||
|
* <li>F <language-region></li>
|
||||||
|
* <li>S <language-region></li>
|
||||||
|
* <li>F <language></li>
|
||||||
|
* <li>S <language></li>
|
||||||
|
* <li>F en</li>
|
||||||
|
* <li>S en</li>
|
||||||
|
* </ol>
|
||||||
|
* <p>
|
||||||
|
* Example for the message priority for locale "de-CH-1996" (language "de", region "CH", variant "1996):
|
||||||
|
* <ol>
|
||||||
|
* <li>F de-CH-1996</li>
|
||||||
|
* <li>S de-CH-1996</li>
|
||||||
|
* <li>F de-CH</li>
|
||||||
|
* <li>S de-CH</li>
|
||||||
|
* <li>F de</li>
|
||||||
|
* <li>S de</li>
|
||||||
|
* <li>F en</li>
|
||||||
|
* <li>S en</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* @param locale the locale
|
||||||
|
* @param firstMessages the first (locale-)grouped messages, having higher priority (per locale) than
|
||||||
|
* {@code secondMessages}
|
||||||
|
* @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)
|
||||||
|
*/
|
||||||
|
public static Properties mergeGroupedMessages(Locale locale, Map<Locale, Properties> firstMessages,
|
||||||
|
Map<Locale, Properties> secondMessages) {
|
||||||
|
List<Locale> applicableLocales = getApplicableLocales(locale);
|
||||||
|
|
||||||
|
Properties mergedProperties = new Properties();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* iterate starting from the end of the list in order to add the least relevant messages first (in order to be
|
||||||
|
* overwritten by more relevant messages)
|
||||||
|
*/
|
||||||
|
ListIterator<Locale> itr = applicableLocales.listIterator(applicableLocales.size());
|
||||||
|
while (itr.hasPrevious()) {
|
||||||
|
Locale currentLocale = itr.previous();
|
||||||
|
|
||||||
|
// add secondMessages first, if specified (to be overwritten by firstMessages)
|
||||||
|
if (secondMessages != null) {
|
||||||
|
Properties currentLocaleSecondMessages = secondMessages.get(currentLocale);
|
||||||
|
if (currentLocaleSecondMessages != null) {
|
||||||
|
mergedProperties.putAll(currentLocaleSecondMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add firstMessages, overwriting secondMessages (if specified)
|
||||||
|
Properties currentLocaleFirstMessages = firstMessages.get(currentLocale);
|
||||||
|
if (currentLocaleFirstMessages != null) {
|
||||||
|
mergedProperties.putAll(currentLocaleFirstMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enhance the properties from a theme with realm localization texts. Realm localization texts take precedence over
|
||||||
|
* 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)}.
|
||||||
|
*
|
||||||
|
* @param realm the realm from which the localization texts should be used
|
||||||
|
* @param locale the locale for which the relevant texts should be retrieved
|
||||||
|
* @param themeMessages the theme messages, which should be enhanced and maybe overwritten
|
||||||
|
* @return the enhanced properties
|
||||||
|
*/
|
||||||
|
public static Properties enhancePropertiesWithRealmLocalizationTexts(RealmModel realm, Locale locale,
|
||||||
|
Map<Locale, Properties> themeMessages) {
|
||||||
|
Map<Locale, Properties> realmLocalizationMessages = getRealmLocalizationTexts(realm, locale);
|
||||||
|
|
||||||
|
return mergeGroupedMessages(locale, realmLocalizationMessages, themeMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<Locale, Properties> getRealmLocalizationTexts(RealmModel realm, Locale locale) {
|
||||||
|
LinkedHashMap<Locale, Properties> groupedMessages = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
List<Locale> applicableLocales = getApplicableLocales(locale);
|
||||||
|
for (Locale applicableLocale : applicableLocales) {
|
||||||
|
Map<String, String> currentRealmLocalizationTexts =
|
||||||
|
realm.getRealmLocalizationTextsByLocale(applicableLocale.toLanguageTag());
|
||||||
|
Properties currentMessages = new Properties();
|
||||||
|
currentMessages.putAll(currentRealmLocalizationTexts);
|
||||||
|
|
||||||
|
groupedMessages.put(applicableLocale, currentMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,18 @@
|
||||||
|
|
||||||
package org.keycloak.theme;
|
package org.keycloak.theme;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.services.util.LocaleUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -148,6 +153,16 @@ public class ClassLoaderTheme implements Theme {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getEnhancedMessages(RealmModel realm, Locale locale) throws IOException {
|
||||||
|
if (locale == null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Locale, Properties> localeMessages = Collections.singletonMap(locale, getMessages(locale));
|
||||||
|
return LocaleUtil.enhancePropertiesWithRealmLocalizationTexts(realm, locale, localeMessages);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getProperties() {
|
public Properties getProperties() {
|
||||||
return properties;
|
return properties;
|
||||||
|
|
|
@ -18,26 +18,28 @@
|
||||||
package org.keycloak.theme;
|
package org.keycloak.theme;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.common.Version;
|
|
||||||
import org.keycloak.common.util.StringPropertyReplacer;
|
import org.keycloak.common.util.StringPropertyReplacer;
|
||||||
import org.keycloak.common.util.SystemEnvProperties;
|
import org.keycloak.common.util.SystemEnvProperties;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.ThemeManager;
|
import org.keycloak.models.ThemeManager;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import org.keycloak.common.Profile;
|
import org.keycloak.common.Profile;
|
||||||
|
import org.keycloak.services.util.LocaleUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
@ -158,7 +160,8 @@ public class DefaultThemeManager implements ThemeManager {
|
||||||
|
|
||||||
private Properties properties;
|
private Properties properties;
|
||||||
|
|
||||||
private ConcurrentHashMap<String, ConcurrentHashMap<Locale, Properties>> messages = new ConcurrentHashMap<>();
|
private ConcurrentHashMap<String, ConcurrentHashMap<Locale, Map<Locale, Properties>>> messages =
|
||||||
|
new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public ExtendingTheme(List<Theme> themes, Set<ThemeResourceProvider> themeResourceProviders) {
|
public ExtendingTheme(List<Theme> themes, Set<ThemeResourceProvider> themeResourceProviders) {
|
||||||
this.themes = themes;
|
this.themes = themes;
|
||||||
|
@ -230,31 +233,44 @@ public class DefaultThemeManager implements ThemeManager {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
||||||
if (messages.get(baseBundlename) == null || messages.get(baseBundlename).get(locale) == null) {
|
Map<Locale, Properties> messagesByLocale = getMessagesByLocale(baseBundlename, locale);
|
||||||
Properties messages = new Properties();
|
return LocaleUtil.mergeGroupedMessages(locale, messagesByLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getEnhancedMessages(RealmModel realm, Locale locale) throws IOException {
|
||||||
|
Map<Locale, Properties> messagesByLocale = getMessagesByLocale("messages", locale);
|
||||||
|
return LocaleUtil.enhancePropertiesWithRealmLocalizationTexts(realm, locale, messagesByLocale);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = getParent(locale);
|
||||||
|
|
||||||
if (parent != null) {
|
Map<Locale, Properties> parentMessages =
|
||||||
messages.putAll(getMessages(baseBundlename, parent));
|
parent == null ? Collections.emptyMap() : getMessagesByLocale(baseBundlename, parent);
|
||||||
}
|
|
||||||
|
|
||||||
for (ThemeResourceProvider t : themeResourceProviders ){
|
Properties currentMessages = new Properties();
|
||||||
messages.putAll(t.getMessages(baseBundlename, locale));
|
Map<Locale, Properties> groupedMessages = new HashMap<>(parentMessages);
|
||||||
|
groupedMessages.put(locale, currentMessages);
|
||||||
|
|
||||||
|
for (ThemeResourceProvider t : themeResourceProviders) {
|
||||||
|
currentMessages.putAll(t.getMessages(baseBundlename, locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
ListIterator<Theme> itr = themes.listIterator(themes.size());
|
||||||
while (itr.hasPrevious()) {
|
while (itr.hasPrevious()) {
|
||||||
Properties m = itr.previous().getMessages(baseBundlename, locale);
|
Properties m = itr.previous().getMessages(baseBundlename, locale);
|
||||||
if (m != null) {
|
if (m != null) {
|
||||||
messages.putAll(m);
|
currentMessages.putAll(m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.messages.putIfAbsent(baseBundlename, new ConcurrentHashMap<Locale, Properties>());
|
|
||||||
this.messages.get(baseBundlename).putIfAbsent(locale, messages);
|
|
||||||
|
|
||||||
return messages;
|
|
||||||
|
this.messages.putIfAbsent(baseBundlename, new ConcurrentHashMap<>());
|
||||||
|
this.messages.get(baseBundlename).putIfAbsent(locale, groupedMessages);
|
||||||
|
|
||||||
|
return groupedMessages;
|
||||||
} else {
|
} else {
|
||||||
return messages.get(baseBundlename).get(locale);
|
return messages.get(baseBundlename).get(locale);
|
||||||
}
|
}
|
||||||
|
@ -291,19 +307,7 @@ public class DefaultThemeManager implements ThemeManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Locale getParent(Locale locale) {
|
private static Locale getParent(Locale locale) {
|
||||||
if (Locale.ENGLISH.equals(locale)) {
|
return LocaleUtil.getParentLocale(locale);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locale.getVariant() != null && !locale.getVariant().isEmpty()) {
|
|
||||||
return new Locale(locale.getLanguage(), locale.getCountry());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (locale.getCountry() != null && !locale.getCountry().isEmpty()) {
|
|
||||||
return new Locale(locale.getLanguage());
|
|
||||||
}
|
|
||||||
|
|
||||||
return Locale.ENGLISH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<ThemeProvider> getProviders() {
|
private List<ThemeProvider> getProviders() {
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
|
|
||||||
package org.keycloak.theme;
|
package org.keycloak.theme;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.services.util.LocaleUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -25,7 +28,9 @@ import java.io.InputStreamReader;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,7 +112,7 @@ public class FolderTheme implements Theme {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
public Properties getMessages(String baseBundlename, Locale locale) throws IOException {
|
||||||
if(locale == null){
|
if (locale == null){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +128,15 @@ public class FolderTheme implements Theme {
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Properties getEnhancedMessages(RealmModel realm, Locale locale) throws IOException {
|
||||||
|
if (locale == null){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Locale, Properties> localeMessages = Collections.singletonMap(locale, getMessages(locale));
|
||||||
|
return LocaleUtil.enhancePropertiesWithRealmLocalizationTexts(realm, locale, localeMessages);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Properties getProperties() {
|
public Properties getProperties() {
|
||||||
return properties;
|
return properties;
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package org.keycloak.locale;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.nullValue;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class DefaultLocaleSelectorProviderTest {
|
||||||
|
|
||||||
|
private static final Locale LOCALE_DE_CH = Locale.forLanguageTag("de-CH");
|
||||||
|
private static final Locale LOCALE_DE_CH_1996 = Locale.forLanguageTag("de-CH-1996");
|
||||||
|
private static final Locale LOCALE_DE_AT = Locale.forLanguageTag("de-AT");
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBestMatchingLocaleReturnsExactLocaleInCaseOfExactMatch() {
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(Arrays.asList(Locale.FRENCH, Locale.GERMAN),
|
||||||
|
"de-CH-1996", "de-CH", "de"), equalTo(Locale.GERMAN));
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(Arrays.asList(Locale.FRENCH, LOCALE_DE_CH),
|
||||||
|
"de-CH-1996", "de-CH", "de"), equalTo(LOCALE_DE_CH));
|
||||||
|
assertThat(
|
||||||
|
DefaultLocaleSelectorProvider.findBestMatchingLocale(Arrays.asList(Locale.FRENCH, LOCALE_DE_CH_1996),
|
||||||
|
"de-CH-1996", "de-CH", "de"),
|
||||||
|
equalTo(LOCALE_DE_CH_1996));
|
||||||
|
|
||||||
|
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(
|
||||||
|
Arrays.asList(Locale.FRENCH, LOCALE_DE_CH_1996, LOCALE_DE_CH, Locale.GERMAN),
|
||||||
|
"de"), equalTo(Locale.GERMAN));
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(
|
||||||
|
Arrays.asList(Locale.FRENCH, LOCALE_DE_CH_1996, LOCALE_DE_CH, Locale.GERMAN),
|
||||||
|
"de-CH"), equalTo(LOCALE_DE_CH));
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(
|
||||||
|
Arrays.asList(Locale.FRENCH, LOCALE_DE_CH_1996, LOCALE_DE_CH, Locale.GERMAN),
|
||||||
|
"de-CH-1996"), equalTo(LOCALE_DE_CH_1996));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBestMatchingLocaleForRegionReturnsLanguageWhenNoLocaleForRegionDefined() {
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(
|
||||||
|
Arrays.asList(Locale.FRENCH, Locale.GERMAN),
|
||||||
|
"de-CH"), equalTo(Locale.GERMAN));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBestMatchingLocaleForLanguageReturnsLanguageWhenLocalesForBothLanguageAndRegionDefined() {
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(
|
||||||
|
Arrays.asList(Locale.FRENCH, LOCALE_DE_CH, Locale.GERMAN),
|
||||||
|
"de"), equalTo(Locale.GERMAN));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO:
|
||||||
|
* Unclear whether this is really expected behavior: when just a language is requested ("de"), and the language
|
||||||
|
* is not in the supported locales, but a language with region is specified ("de-CH"), the language with region
|
||||||
|
* will be used. The other option would be to return null which would fall back to english.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void findBestMatchingLocaleForLanguageReturnsRegionWhenNoLocaleForLanguageDefined() {
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(
|
||||||
|
Arrays.asList(Locale.FRENCH, LOCALE_DE_CH),
|
||||||
|
"de"), equalTo(LOCALE_DE_CH));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBestMatchingLocaleReturnsNullWhenNoMatchingLanguageIsFound() {
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(Arrays.asList(Locale.FRENCH, Locale.GERMAN),
|
||||||
|
"cs"), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void findBestMatchingLocaleForRegionReturnsNullInCaseOfDifferentRegionsForSameLanguage() {
|
||||||
|
assertThat(DefaultLocaleSelectorProvider.findBestMatchingLocale(
|
||||||
|
Arrays.asList(Locale.FRENCH, LOCALE_DE_AT),
|
||||||
|
"de-CH"), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,208 @@
|
||||||
|
package org.keycloak.services.util;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
|
import static org.hamcrest.CoreMatchers.nullValue;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:daniel.fesenmeyer@bosch.com">Daniel Fesenmeyer</a>
|
||||||
|
*/
|
||||||
|
public class LocaleUtilTest {
|
||||||
|
|
||||||
|
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.ENGLISH), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getApplicableLocales() {
|
||||||
|
assertThat(LocaleUtil.getApplicableLocales(LOCALE_DE_CH_1996),
|
||||||
|
equalTo(Arrays.asList(LOCALE_DE_CH_1996, LOCALE_DE_CH, Locale.GERMAN, Locale.ENGLISH)));
|
||||||
|
assertThat(LocaleUtil.getApplicableLocales(LOCALE_DE_CH),
|
||||||
|
equalTo(Arrays.asList(LOCALE_DE_CH, Locale.GERMAN, Locale.ENGLISH)));
|
||||||
|
assertThat(LocaleUtil.getApplicableLocales(Locale.GERMAN),
|
||||||
|
equalTo(Arrays.asList(Locale.GERMAN, Locale.ENGLISH)));
|
||||||
|
|
||||||
|
assertThat(LocaleUtil.getApplicableLocales(Locale.ENGLISH), equalTo(Collections.singletonList(Locale.ENGLISH)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mergeGroupedMessages() {
|
||||||
|
Map<Locale, Properties> groupedMessages = new HashMap<>();
|
||||||
|
|
||||||
|
String keyDefinedEverywhere = "everywhere";
|
||||||
|
String keyDefinedForRegionAndParents = "region-and-parents";
|
||||||
|
String keyDefinedForLanguageAndParents = "language-and-parents";
|
||||||
|
String keyDefinedForEnglishOnly = "english-only";
|
||||||
|
|
||||||
|
// add messages for an irrelevant locale, in order to check that such messages are not in the merged result
|
||||||
|
Properties irrelevantMessages = new Properties();
|
||||||
|
addTestValue(irrelevantMessages, "french-only", Locale.FRENCH);
|
||||||
|
groupedMessages.put(Locale.FRENCH, irrelevantMessages);
|
||||||
|
|
||||||
|
Properties variantMessages = new Properties();
|
||||||
|
addTestValue(variantMessages, keyDefinedEverywhere, LOCALE_DE_CH_1996);
|
||||||
|
groupedMessages.put(LOCALE_DE_CH_1996, variantMessages);
|
||||||
|
|
||||||
|
Properties regionMessages = new Properties();
|
||||||
|
addTestValues(regionMessages, Arrays.asList(keyDefinedEverywhere, keyDefinedForRegionAndParents), LOCALE_DE_CH);
|
||||||
|
groupedMessages.put(LOCALE_DE_CH, regionMessages);
|
||||||
|
|
||||||
|
Properties languageMessages = new Properties();
|
||||||
|
addTestValues(languageMessages, Arrays.asList(keyDefinedEverywhere, keyDefinedForRegionAndParents,
|
||||||
|
keyDefinedForLanguageAndParents), Locale.GERMAN);
|
||||||
|
groupedMessages.put(Locale.GERMAN, languageMessages);
|
||||||
|
|
||||||
|
Properties englishMessages = new Properties();
|
||||||
|
addTestValues(englishMessages, Arrays.asList(keyDefinedEverywhere, keyDefinedForRegionAndParents,
|
||||||
|
keyDefinedForLanguageAndParents, keyDefinedForEnglishOnly), Locale.ENGLISH);
|
||||||
|
groupedMessages.put(Locale.ENGLISH, englishMessages);
|
||||||
|
|
||||||
|
Properties mergedMessages = LocaleUtil.mergeGroupedMessages(LOCALE_DE_CH_1996, groupedMessages);
|
||||||
|
|
||||||
|
Properties expectedMergedMessages = new Properties();
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedEverywhere, LOCALE_DE_CH_1996);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForRegionAndParents, LOCALE_DE_CH);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForLanguageAndParents, Locale.GERMAN);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForEnglishOnly, Locale.ENGLISH);
|
||||||
|
|
||||||
|
assertThat(mergedMessages, equalTo(expectedMergedMessages));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mergeGroupedMessagesFromTwoSources() {
|
||||||
|
// messages with priority 1
|
||||||
|
Map<Locale, Properties> groupedMessages1 = new HashMap<>();
|
||||||
|
// messages with priority 2
|
||||||
|
Map<Locale, Properties> groupedMessages2 = new HashMap<>();
|
||||||
|
|
||||||
|
String messages1Prefix = "msg1";
|
||||||
|
String messages2Prefix = "msg2";
|
||||||
|
|
||||||
|
String keyDefinedForVariantFromMessages1AndFallbacks = "variant1-and-fallbacks";
|
||||||
|
String keyDefinedForVariantFromMessages2AndFallbacks = "variant2-and-fallbacks";
|
||||||
|
String keyDefinedForRegionFromMessages1AndFallbacks = "region1-and-fallbacks";
|
||||||
|
String keyDefinedForRegionFromMessages2AndFallbacks = "region2-and-fallbacks";
|
||||||
|
String keyDefinedForLanguageFromMessages1AndFallbacks = "language1-and-fallbacks";
|
||||||
|
String keyDefinedForLanguageFromMessages2AndFallbacks = "language2-and-fallbacks";
|
||||||
|
String keyDefinedForEnglishFromMessages1AndFallback = "english1-and-fallback";
|
||||||
|
String keyDefinedForEnglishFromMessages2only = "english2-only";
|
||||||
|
|
||||||
|
// add messages for an irrelevant locale, in order to check that such messages are not in the merged result
|
||||||
|
Properties irrelevantMessages = new Properties();
|
||||||
|
addTestValue(irrelevantMessages, "french-only", Locale.FRENCH);
|
||||||
|
groupedMessages1.put(Locale.FRENCH, irrelevantMessages);
|
||||||
|
groupedMessages2.put(Locale.FRENCH, irrelevantMessages);
|
||||||
|
|
||||||
|
Properties variant1Messages = new Properties();
|
||||||
|
addTestValue(variant1Messages, keyDefinedForVariantFromMessages1AndFallbacks, LOCALE_DE_CH_1996,
|
||||||
|
messages1Prefix);
|
||||||
|
groupedMessages1.put(LOCALE_DE_CH_1996, variant1Messages);
|
||||||
|
|
||||||
|
Properties variant2Messages = new Properties();
|
||||||
|
addTestValues(variant2Messages, Arrays.asList(keyDefinedForVariantFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForVariantFromMessages2AndFallbacks), LOCALE_DE_CH_1996, messages2Prefix);
|
||||||
|
groupedMessages2.put(LOCALE_DE_CH_1996, variant2Messages);
|
||||||
|
|
||||||
|
Properties region1Messages = new Properties();
|
||||||
|
addTestValues(region1Messages, Arrays.asList(keyDefinedForVariantFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForVariantFromMessages2AndFallbacks, keyDefinedForRegionFromMessages1AndFallbacks),
|
||||||
|
LOCALE_DE_CH, messages1Prefix);
|
||||||
|
groupedMessages1.put(LOCALE_DE_CH, region1Messages);
|
||||||
|
|
||||||
|
Properties region2Messages = new Properties();
|
||||||
|
addTestValues(region2Messages, Arrays.asList(keyDefinedForVariantFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForVariantFromMessages2AndFallbacks, keyDefinedForRegionFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForRegionFromMessages2AndFallbacks), LOCALE_DE_CH, messages2Prefix);
|
||||||
|
groupedMessages2.put(LOCALE_DE_CH, region2Messages);
|
||||||
|
|
||||||
|
Properties language1Messages = new Properties();
|
||||||
|
addTestValues(language1Messages, Arrays.asList(keyDefinedForVariantFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForVariantFromMessages2AndFallbacks, keyDefinedForRegionFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForRegionFromMessages2AndFallbacks, keyDefinedForLanguageFromMessages1AndFallbacks),
|
||||||
|
Locale.GERMAN, messages1Prefix);
|
||||||
|
groupedMessages1.put(Locale.GERMAN, language1Messages);
|
||||||
|
|
||||||
|
Properties language2Messages = new Properties();
|
||||||
|
addTestValues(language2Messages, Arrays.asList(keyDefinedForVariantFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForVariantFromMessages2AndFallbacks, keyDefinedForRegionFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForRegionFromMessages2AndFallbacks, keyDefinedForLanguageFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForLanguageFromMessages2AndFallbacks), Locale.GERMAN, messages2Prefix);
|
||||||
|
groupedMessages2.put(Locale.GERMAN, language2Messages);
|
||||||
|
|
||||||
|
Properties english1Messages = new Properties();
|
||||||
|
addTestValues(english1Messages, Arrays.asList(keyDefinedForVariantFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForVariantFromMessages2AndFallbacks, keyDefinedForRegionFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForRegionFromMessages2AndFallbacks, keyDefinedForLanguageFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForLanguageFromMessages2AndFallbacks, keyDefinedForEnglishFromMessages1AndFallback),
|
||||||
|
Locale.ENGLISH, messages1Prefix);
|
||||||
|
groupedMessages1.put(Locale.ENGLISH, english1Messages);
|
||||||
|
|
||||||
|
Properties english2Messages = new Properties();
|
||||||
|
addTestValues(english2Messages, Arrays.asList(keyDefinedForVariantFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForVariantFromMessages2AndFallbacks, keyDefinedForRegionFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForRegionFromMessages2AndFallbacks, keyDefinedForLanguageFromMessages1AndFallbacks,
|
||||||
|
keyDefinedForLanguageFromMessages2AndFallbacks, keyDefinedForEnglishFromMessages1AndFallback,
|
||||||
|
keyDefinedForEnglishFromMessages2only), Locale.ENGLISH, messages2Prefix);
|
||||||
|
groupedMessages2.put(Locale.ENGLISH, english2Messages);
|
||||||
|
|
||||||
|
Properties mergedMessages =
|
||||||
|
LocaleUtil.mergeGroupedMessages(LOCALE_DE_CH_1996, groupedMessages1, groupedMessages2);
|
||||||
|
|
||||||
|
Properties expectedMergedMessages = new Properties();
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForVariantFromMessages1AndFallbacks, LOCALE_DE_CH_1996,
|
||||||
|
messages1Prefix);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForVariantFromMessages2AndFallbacks, LOCALE_DE_CH_1996,
|
||||||
|
messages2Prefix);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForRegionFromMessages1AndFallbacks, LOCALE_DE_CH,
|
||||||
|
messages1Prefix);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForRegionFromMessages2AndFallbacks, LOCALE_DE_CH,
|
||||||
|
messages2Prefix);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForLanguageFromMessages1AndFallbacks, Locale.GERMAN,
|
||||||
|
messages1Prefix);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForLanguageFromMessages2AndFallbacks, Locale.GERMAN,
|
||||||
|
messages2Prefix);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForEnglishFromMessages1AndFallback, Locale.ENGLISH,
|
||||||
|
messages1Prefix);
|
||||||
|
addTestValue(expectedMergedMessages, keyDefinedForEnglishFromMessages2only, Locale.ENGLISH, messages2Prefix);
|
||||||
|
|
||||||
|
assertThat(mergedMessages, equalTo(expectedMergedMessages));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addTestValues(Properties messages, List<String> keys, Locale locale) {
|
||||||
|
keys.forEach(k -> addTestValue(messages, k, locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addTestValue(Properties messages, String key, Locale locale) {
|
||||||
|
messages.put(key, createTestValue(key, locale, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addTestValues(Properties messages, List<String> keys, Locale locale, String prefix) {
|
||||||
|
keys.forEach(k -> addTestValue(messages, k, locale, prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addTestValue(Properties messages, String key, Locale locale, String prefix) {
|
||||||
|
messages.put(key, createTestValue(key, locale, prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String createTestValue(String key, Locale locale, String prefix) {
|
||||||
|
return (prefix != null ? prefix + ":" : "") + locale.toLanguageTag() + ":" + key;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1813,99 +1813,13 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void localizations() {
|
public void localizations() {
|
||||||
invoke(new Invocation() {
|
verifyAnyAdminRoleReqired(realm -> realm.localization().getRealmSpecificLocales());
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmSpecificLocales();
|
|
||||||
}
|
|
||||||
}, clients.get("view-realm"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmSpecificLocales();
|
|
||||||
}
|
|
||||||
}, clients.get("manage-realm"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmSpecificLocales();
|
|
||||||
}
|
|
||||||
}, clients.get("multi"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmSpecificLocales();
|
|
||||||
}
|
|
||||||
}, clients.get("master-admin"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmSpecificLocales();
|
|
||||||
}
|
|
||||||
}, clients.get("none"), false);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmSpecificLocales();
|
|
||||||
}
|
|
||||||
}, clients.get("REALM2"), false);
|
|
||||||
|
|
||||||
invoke(new Invocation() {
|
verifyAnyAdminRoleReqired(realm -> realm.localization().getRealmLocalizationText("en", "test"));
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationText("en", "test");
|
|
||||||
}
|
|
||||||
}, clients.get("view-realm"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationText("en", "test");
|
|
||||||
}
|
|
||||||
}, clients.get("manage-realm"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationText("en", "test");
|
|
||||||
}
|
|
||||||
}, clients.get("multi"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationText("en", "test");
|
|
||||||
}
|
|
||||||
}, clients.get("master-admin"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationText("en", "test");
|
|
||||||
}
|
|
||||||
}, clients.get("none"), false);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationText("en", "test");
|
|
||||||
}
|
|
||||||
}, clients.get("REALM2"), false);
|
|
||||||
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationTexts("en", false);
|
|
||||||
}
|
|
||||||
}, clients.get("view-realm"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationTexts("en", false);
|
|
||||||
}
|
|
||||||
}, clients.get("manage-realm"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationTexts("en", false);
|
|
||||||
}
|
|
||||||
}, clients.get("multi"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationTexts("en", false);
|
|
||||||
}
|
|
||||||
}, clients.get("master-admin"), true);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationTexts("en", false);
|
|
||||||
}
|
|
||||||
}, clients.get("none"), false);
|
|
||||||
invoke(new Invocation() {
|
|
||||||
public void invoke(RealmResource realm) {
|
|
||||||
realm.localization().getRealmLocalizationTexts("en", false);
|
|
||||||
}
|
|
||||||
}, clients.get("REALM2"), false);
|
|
||||||
|
|
||||||
|
verifyAnyAdminRoleReqired(realm -> realm.localization().getRealmLocalizationTexts("en"));
|
||||||
|
verifyAnyAdminRoleReqired(realm -> realm.localization().getRealmLocalizationTexts("en", false));
|
||||||
|
|
||||||
invoke(new Invocation() {
|
invoke(new Invocation() {
|
||||||
public void invoke(RealmResource realm) {
|
public void invoke(RealmResource realm) {
|
||||||
realm.localization().createOrUpdateRealmLocalizationTexts("en", Collections.<String, String>emptyMap());
|
realm.localization().createOrUpdateRealmLocalizationTexts("en", Collections.<String, String>emptyMap());
|
||||||
|
@ -1985,6 +1899,15 @@ public class PermissionsTest extends AbstractKeycloakTest {
|
||||||
}, clients.get("REALM2"), false);
|
}, clients.get("REALM2"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifyAnyAdminRoleReqired(Invocation invocation) {
|
||||||
|
invoke(invocation, clients.get("view-realm"), true);
|
||||||
|
invoke(invocation, clients.get("manage-realm"), true);
|
||||||
|
invoke(invocation, clients.get("multi"), true);
|
||||||
|
invoke(invocation, clients.get("master-admin"), true);
|
||||||
|
invoke(invocation, clients.get("none"), false);
|
||||||
|
invoke(invocation, clients.get("REALM2"), false);
|
||||||
|
}
|
||||||
|
|
||||||
private void invoke(final Invocation invocation, Resource resource, boolean manage) {
|
private void invoke(final Invocation invocation, Resource resource, boolean manage) {
|
||||||
invoke(new InvocationWithResponse() {
|
invoke(new InvocationWithResponse() {
|
||||||
public void invoke(RealmResource realm, AtomicReference<Response> response) {
|
public void invoke(RealmResource realm, AtomicReference<Response> response) {
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class RealmLocalizationResourceTest extends AbstractAdminTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getRealmLocalizationTexts() {
|
public void getRealmLocalizationTexts() {
|
||||||
Map<String, String> localizations = resource.getRealmLocalizationTexts("en", false);
|
Map<String, String> localizations = resource.getRealmLocalizationTexts("en");
|
||||||
assertNotNull(localizations);
|
assertNotNull(localizations);
|
||||||
assertEquals(2, localizations.size());
|
assertEquals(2, localizations.size());
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ public class RealmLocalizationResourceTest extends AbstractAdminTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getRealmLocalizationsNotExists() {
|
public void getRealmLocalizationsNotExists() {
|
||||||
Map<String, String> localizations = resource.getRealmLocalizationTexts("zz", false);
|
Map<String, String> localizations = resource.getRealmLocalizationTexts("zz");
|
||||||
assertNotNull(localizations);
|
assertNotNull(localizations);
|
||||||
assertEquals(0, localizations.size());
|
assertEquals(0, localizations.size());
|
||||||
}
|
}
|
||||||
|
@ -125,7 +125,7 @@ public class RealmLocalizationResourceTest extends AbstractAdminTest {
|
||||||
public void deleteRealmLocalizationText() {
|
public void deleteRealmLocalizationText() {
|
||||||
resource.deleteRealmLocalizationText("en", "key-a");
|
resource.deleteRealmLocalizationText("en", "key-a");
|
||||||
|
|
||||||
Map<String, String> localizations = resource.getRealmLocalizationTexts("en", false);
|
Map<String, String> localizations = resource.getRealmLocalizationTexts("en");
|
||||||
assertEquals(1, localizations.size());
|
assertEquals(1, localizations.size());
|
||||||
assertEquals("text-b_en", localizations.get("key-b"));
|
assertEquals("text-b_en", localizations.get("key-b"));
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ public class RealmLocalizationResourceTest extends AbstractAdminTest {
|
||||||
|
|
||||||
resource.createOrUpdateRealmLocalizationTexts("es", newLocalizationTexts);
|
resource.createOrUpdateRealmLocalizationTexts("es", newLocalizationTexts);
|
||||||
|
|
||||||
final Map<String, String> persistedLocalizationTexts = resource.getRealmLocalizationTexts("es", false);
|
final Map<String, String> persistedLocalizationTexts = resource.getRealmLocalizationTexts("es");
|
||||||
assertEquals(newLocalizationTexts, persistedLocalizationTexts);
|
assertEquals(newLocalizationTexts, persistedLocalizationTexts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +168,7 @@ public class RealmLocalizationResourceTest extends AbstractAdminTest {
|
||||||
final Map<String, String> expectedLocalizationTexts = new HashMap<>();
|
final Map<String, String> expectedLocalizationTexts = new HashMap<>();
|
||||||
expectedLocalizationTexts.put("key-a", "text-a_en");
|
expectedLocalizationTexts.put("key-a", "text-a_en");
|
||||||
expectedLocalizationTexts.putAll(newLocalizationTexts);
|
expectedLocalizationTexts.putAll(newLocalizationTexts);
|
||||||
final Map<String, String> persistedLocalizationTexts = resource.getRealmLocalizationTexts("en", false);
|
final Map<String, String> persistedLocalizationTexts = resource.getRealmLocalizationTexts("en");
|
||||||
assertEquals(expectedLocalizationTexts, persistedLocalizationTexts);
|
assertEquals(expectedLocalizationTexts, persistedLocalizationTexts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,19 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.testsuite.i18n;
|
package org.keycloak.testsuite.i18n;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
import jakarta.mail.MessagingException;
|
import jakarta.mail.MessagingException;
|
||||||
import jakarta.mail.internet.MimeMessage;
|
import jakarta.mail.internet.MimeMessage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
|
@ -31,7 +36,6 @@ import org.junit.Test;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.UserRepresentation;
|
import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.testsuite.ProfileAssume;
|
|
||||||
import org.keycloak.testsuite.admin.ApiUtil;
|
import org.keycloak.testsuite.admin.ApiUtil;
|
||||||
import org.keycloak.testsuite.pages.InfoPage;
|
import org.keycloak.testsuite.pages.InfoPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
@ -70,52 +74,80 @@ public class EmailTest extends AbstractI18NTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void restPasswordEmail() throws IOException, MessagingException {
|
public void restPasswordEmail() throws MessagingException, IOException {
|
||||||
loginPage.open();
|
String expectedBodyContent = "Someone just requested to change";
|
||||||
loginPage.resetPassword();
|
verifyResetPassword("Reset password", expectedBodyContent, 1);
|
||||||
resetPasswordPage.changePassword("login-test");
|
|
||||||
|
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
|
||||||
|
|
||||||
Assert.assertEquals("Reset password", message.getSubject());
|
|
||||||
|
|
||||||
changeUserLocale("en");
|
changeUserLocale("en");
|
||||||
|
|
||||||
loginPage.open();
|
verifyResetPassword("Reset password", expectedBodyContent, 2);
|
||||||
loginPage.resetPassword();
|
|
||||||
|
|
||||||
resetPasswordPage.changePassword("login-test");
|
|
||||||
|
|
||||||
Assert.assertEquals(2, greenMail.getReceivedMessages().length);
|
|
||||||
|
|
||||||
message = greenMail.getReceivedMessages()[1];
|
|
||||||
|
|
||||||
Assert.assertEquals("Reset password", message.getSubject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void restPasswordEmailGerman() throws IOException, MessagingException {
|
public void realmLocalizationMessagesAreApplied() throws MessagingException, IOException {
|
||||||
changeUserLocale("de");
|
String subjectMessageKey = "passwordResetSubject";
|
||||||
|
String bodyMessageKey = "passwordResetBody";
|
||||||
|
String placeholders = "{0} {1} {2}";
|
||||||
|
|
||||||
|
String subjectEn = "Subject EN";
|
||||||
|
String expectedBodyContentEn = "Body EN";
|
||||||
|
String bodyMessageEn = expectedBodyContentEn + placeholders;
|
||||||
|
testRealm().localization().saveRealmLocalizationText(Locale.ENGLISH.toLanguageTag(), subjectMessageKey, subjectEn);
|
||||||
|
testRealm().localization().saveRealmLocalizationText(Locale.ENGLISH.toLanguageTag(), bodyMessageKey, bodyMessageEn);
|
||||||
|
getCleanup().addLocalization(Locale.ENGLISH.toLanguageTag());
|
||||||
|
|
||||||
|
String subjectDe = "Subject DE";
|
||||||
|
String expectedBodyContentDe = "Body DE";
|
||||||
|
String bodyMessageDe = expectedBodyContentDe + placeholders;
|
||||||
|
testRealm().localization().saveRealmLocalizationText(Locale.GERMAN.toLanguageTag(), subjectMessageKey, subjectDe);
|
||||||
|
testRealm().localization().saveRealmLocalizationText(Locale.GERMAN.toLanguageTag(), bodyMessageKey, bodyMessageDe);
|
||||||
|
getCleanup().addLocalization(Locale.GERMAN.toLanguageTag());
|
||||||
|
|
||||||
|
try {
|
||||||
|
verifyResetPassword(subjectEn, expectedBodyContentEn, 1);
|
||||||
|
|
||||||
|
changeUserLocale("de");
|
||||||
|
|
||||||
|
verifyResetPassword(subjectDe, expectedBodyContentDe, 2);
|
||||||
|
} finally {
|
||||||
|
// Revert
|
||||||
|
changeUserLocale("en");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void restPasswordEmailGerman() throws MessagingException, IOException {
|
||||||
|
changeUserLocale("de");
|
||||||
|
try {
|
||||||
|
verifyResetPassword("Passwort zurücksetzen", "Es wurde eine Änderung", 1);
|
||||||
|
} finally {
|
||||||
|
// Revert
|
||||||
|
changeUserLocale("en");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyResetPassword(String expectedSubject, String expectedTextBodyContent, int expectedMsgCount)
|
||||||
|
throws MessagingException, IOException {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.resetPassword();
|
loginPage.resetPassword();
|
||||||
resetPasswordPage.changePassword("login-test");
|
resetPasswordPage.changePassword("login-test");
|
||||||
|
|
||||||
Assert.assertEquals(1, greenMail.getReceivedMessages().length);
|
assertEquals(expectedMsgCount, greenMail.getReceivedMessages().length);
|
||||||
|
|
||||||
MimeMessage message = greenMail.getReceivedMessages()[0];
|
MimeMessage message = greenMail.getReceivedMessages()[expectedMsgCount - 1];
|
||||||
|
|
||||||
Assert.assertEquals("Passwort zurücksetzen", message.getSubject());
|
assertEquals(expectedSubject, message.getSubject());
|
||||||
|
|
||||||
// Revert
|
String textBody = MailUtils.getBody(message).getText();
|
||||||
changeUserLocale("en");
|
assertThat(textBody, containsString(expectedTextBodyContent));
|
||||||
|
// make sure all placeholders have been replaced
|
||||||
|
assertThat(textBody, not(containsString("{")));
|
||||||
|
assertThat(textBody, not(containsString("}")));
|
||||||
}
|
}
|
||||||
|
|
||||||
//KEYCLOAK-7478
|
//KEYCLOAK-7478
|
||||||
@Test
|
@Test
|
||||||
public void changeLocaleOnInfoPage() throws InterruptedException, IOException, MessagingException {
|
public void changeLocaleOnInfoPage() throws InterruptedException, IOException {
|
||||||
UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test");
|
UserResource testUser = ApiUtil.findUserByUsernameId(testRealm(), "login-test");
|
||||||
testUser.executeActionsEmail(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
|
testUser.executeActionsEmail(Arrays.asList(UserModel.RequiredAction.UPDATE_PASSWORD.toString()));
|
||||||
|
|
||||||
|
@ -132,11 +164,11 @@ public class EmailTest extends AbstractI18NTest {
|
||||||
WaitUtils.waitForPageToLoad();
|
WaitUtils.waitForPageToLoad();
|
||||||
|
|
||||||
Assert.assertTrue("Expected to be on InfoPage, but it was on " + DroneUtils.getCurrentDriver().getTitle(), infoPage.isCurrent());
|
Assert.assertTrue("Expected to be on InfoPage, but it was on " + DroneUtils.getCurrentDriver().getTitle(), infoPage.isCurrent());
|
||||||
Assert.assertThat(infoPage.getLanguageDropdownText(), is(equalTo("English")));
|
assertThat(infoPage.getLanguageDropdownText(), is(equalTo("English")));
|
||||||
|
|
||||||
infoPage.openLanguage("Deutsch");
|
infoPage.openLanguage("Deutsch");
|
||||||
|
|
||||||
Assert.assertThat(DroneUtils.getCurrentDriver().getPageSource(), containsString("Passwort aktualisieren"));
|
assertThat(DroneUtils.getCurrentDriver().getPageSource(), containsString("Passwort aktualisieren"));
|
||||||
|
|
||||||
infoPage.clickToContinueDe();
|
infoPage.clickToContinueDe();
|
||||||
|
|
||||||
|
@ -145,6 +177,6 @@ public class EmailTest extends AbstractI18NTest {
|
||||||
WaitUtils.waitForPageToLoad();
|
WaitUtils.waitForPageToLoad();
|
||||||
|
|
||||||
Assert.assertTrue("Expected to be on InfoPage, but it was on " + DroneUtils.getCurrentDriver().getTitle(), infoPage.isCurrent());
|
Assert.assertTrue("Expected to be on InfoPage, but it was on " + DroneUtils.getCurrentDriver().getTitle(), infoPage.isCurrent());
|
||||||
Assert.assertThat(infoPage.getInfo(), containsString("Your account has been updated."));
|
assertThat(infoPage.getInfo(), containsString("Your account has been updated."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import java.util.Arrays;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.hamcrest.Matchers;
|
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||||
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
|
||||||
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient43Engine;
|
import org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient43Engine;
|
||||||
|
@ -41,13 +40,15 @@ import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
|
||||||
import jakarta.ws.rs.core.Response;
|
import jakarta.ws.rs.core.Response;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.keycloak.testsuite.ProfileAssume;
|
|
||||||
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
import org.keycloak.testsuite.pages.LoginPasswordUpdatePage;
|
||||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||||
import org.keycloak.testsuite.util.IdentityProviderBuilder;
|
import org.keycloak.testsuite.util.IdentityProviderBuilder;
|
||||||
import org.openqa.selenium.Cookie;
|
import org.openqa.selenium.Cookie;
|
||||||
|
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
* @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
|
||||||
|
@ -89,7 +90,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
@Test
|
@Test
|
||||||
public void languageDropdown() {
|
public void languageDropdown() {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
Assert.assertEquals("English", loginPage.getLanguageDropdownText());
|
assertEquals("English", loginPage.getLanguageDropdownText());
|
||||||
|
|
||||||
switchLanguageToGermanAndBack("Username or email", "Benutzername oder E-Mail", loginPage);
|
switchLanguageToGermanAndBack("Username or email", "Benutzername oder E-Mail", loginPage);
|
||||||
}
|
}
|
||||||
|
@ -97,26 +98,26 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
@Test
|
@Test
|
||||||
public void uiLocalesParameter() {
|
public void uiLocalesParameter() {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
Assert.assertEquals("English", loginPage.getLanguageDropdownText());
|
assertEquals("English", loginPage.getLanguageDropdownText());
|
||||||
|
|
||||||
//test if cookie works
|
//test if cookie works
|
||||||
oauth.uiLocales("de");
|
oauth.uiLocales("de");
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
||||||
|
|
||||||
driver.manage().deleteAllCookies();
|
driver.manage().deleteAllCookies();
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
||||||
|
|
||||||
oauth.uiLocales("en de");
|
oauth.uiLocales("en de");
|
||||||
driver.manage().deleteAllCookies();
|
driver.manage().deleteAllCookies();
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
Assert.assertEquals("English", loginPage.getLanguageDropdownText());
|
assertEquals("English", loginPage.getLanguageDropdownText());
|
||||||
|
|
||||||
oauth.uiLocales("fr de");
|
oauth.uiLocales("fr de");
|
||||||
driver.manage().deleteAllCookies();
|
driver.manage().deleteAllCookies();
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -142,10 +143,9 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
@Test
|
@Test
|
||||||
public void testIdentityProviderCapitalization(){
|
public void testIdentityProviderCapitalization(){
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
Assert.assertEquals("GitHub", loginPage.findSocialButton("github").getText());
|
assertEquals("GitHub", loginPage.findSocialButton("github").getText());
|
||||||
Assert.assertEquals("mysaml", loginPage.findSocialButton("mysaml").getText());
|
assertEquals("mysaml", loginPage.findSocialButton("mysaml").getText());
|
||||||
Assert.assertEquals("MyOIDC", loginPage.findSocialButton("myoidc").getText());
|
assertEquals("MyOIDC", loginPage.findSocialButton("myoidc").getText());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
changePasswordPage.assertCurrent();
|
changePasswordPage.assertCurrent();
|
||||||
Assert.assertEquals("English", changePasswordPage.getLanguageDropdownText());
|
assertEquals("English", changePasswordPage.getLanguageDropdownText());
|
||||||
|
|
||||||
// Switch language
|
// Switch language
|
||||||
switchLanguageToGermanAndBack("Update password", "Passwort aktualisieren", changePasswordPage);
|
switchLanguageToGermanAndBack("Update password", "Passwort aktualisieren", changePasswordPage);
|
||||||
|
@ -169,7 +169,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
// Update password
|
// Update password
|
||||||
changePasswordPage.changePassword("password", "password");
|
changePasswordPage.changePassword("password", "password");
|
||||||
|
|
||||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
grantPage.assertCurrent();
|
grantPage.assertCurrent();
|
||||||
Assert.assertEquals("English", grantPage.getLanguageDropdownText());
|
assertEquals("English", grantPage.getLanguageDropdownText());
|
||||||
|
|
||||||
// Switch language
|
// Switch language
|
||||||
switchLanguageToGermanAndBack("Do you grant these access privileges?", "Wollen Sie diese Zugriffsrechte", changePasswordPage);
|
switchLanguageToGermanAndBack("Do you grant these access privileges?", "Wollen Sie diese Zugriffsrechte", changePasswordPage);
|
||||||
|
@ -193,7 +193,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
// Confirm grant
|
// Confirm grant
|
||||||
grantPage.accept();
|
grantPage.accept();
|
||||||
|
|
||||||
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||||
|
|
||||||
// Revert client
|
// Revert client
|
||||||
|
@ -205,16 +205,16 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
loginPage.openLanguage("Deutsch");
|
loginPage.openLanguage("Deutsch");
|
||||||
|
|
||||||
Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
||||||
|
|
||||||
Cookie localeCookie = driver.manage().getCookieNamed(LocaleSelectorProvider.LOCALE_COOKIE);
|
Cookie localeCookie = driver.manage().getCookieNamed(LocaleSelectorProvider.LOCALE_COOKIE);
|
||||||
Assert.assertEquals("de", localeCookie.getValue());
|
assertEquals("de", localeCookie.getValue());
|
||||||
|
|
||||||
loginPage.login("test-user@localhost", "password");
|
loginPage.login("test-user@localhost", "password");
|
||||||
|
|
||||||
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
|
UserResource user = ApiUtil.findUserByUsernameId(testRealm(), "test-user@localhost");
|
||||||
UserRepresentation userRep = user.toRepresentation();
|
UserRepresentation userRep = user.toRepresentation();
|
||||||
Assert.assertEquals("de", userRep.getAttributes().get("locale").get(0));
|
assertEquals("de", userRep.getAttributes().get("locale").get(0));
|
||||||
|
|
||||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
||||||
String idTokenHint = oauth.doAccessTokenRequest(code, "password").getIdToken();
|
String idTokenHint = oauth.doAccessTokenRequest(code, "password").getIdToken();
|
||||||
|
@ -222,7 +222,7 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
|
|
||||||
loginPage.open();
|
loginPage.open();
|
||||||
|
|
||||||
Assert.assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
assertEquals("Deutsch", loginPage.getLanguageDropdownText());
|
||||||
|
|
||||||
userRep.getAttributes().remove("locale");
|
userRep.getAttributes().remove("locale");
|
||||||
user.update(userRep);
|
user.update(userRep);
|
||||||
|
@ -245,59 +245,56 @@ public class LoginPageTest extends AbstractI18NTest {
|
||||||
Assert.assertNull(localeCookie);
|
Assert.assertNull(localeCookie);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void realmLocalizationMessagesAreApplied() {
|
||||||
|
String realmLocalizationMessageKey = "loginAccountTitle";
|
||||||
|
|
||||||
|
String realmLocalizationMessageValueEn = "Localization Test EN";
|
||||||
|
saveLocalizationText(Locale.ENGLISH.toLanguageTag(), realmLocalizationMessageKey,
|
||||||
|
realmLocalizationMessageValueEn);
|
||||||
|
String realmLocalizationMessageValueDe = "Localization Test DE";
|
||||||
|
saveLocalizationText(Locale.GERMAN.toLanguageTag(), realmLocalizationMessageKey,
|
||||||
|
realmLocalizationMessageValueDe);
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
switchLanguageToGermanAndBack(realmLocalizationMessageValueEn, realmLocalizationMessageValueDe, loginPage);
|
||||||
|
}
|
||||||
|
|
||||||
// KEYCLOAK-18590
|
// KEYCLOAK-18590
|
||||||
@Test
|
@Test
|
||||||
public void realmLocalizationMessagesAreNotCachedWithinTheTheme() throws IOException {
|
public void realmLocalizationMessagesAreNotCachedWithinTheTheme() {
|
||||||
final String locale = Locale.ENGLISH.toLanguageTag();
|
final String locale = Locale.ENGLISH.toLanguageTag();
|
||||||
|
|
||||||
final String realmLocalizationMessageKey = "loginAccountTitle";
|
final String realmLocalizationMessageKey = "loginAccountTitle";
|
||||||
final String realmLocalizationMessageValue = "Localization Test";
|
final String realmLocalizationMessageValue = "Localization Test";
|
||||||
|
|
||||||
|
saveLocalizationText(locale, realmLocalizationMessageKey, realmLocalizationMessageValue);
|
||||||
|
loginPage.open();
|
||||||
|
assertThat(driver.getPageSource(), containsString(realmLocalizationMessageValue));
|
||||||
|
|
||||||
try(CloseableHttpClient httpClient = (CloseableHttpClient) new HttpClientBuilder().build()) {
|
testRealm().localization().deleteRealmLocalizationText(locale, realmLocalizationMessageKey);
|
||||||
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient);
|
loginPage.open();
|
||||||
|
assertThat(driver.getPageSource(), not(containsString(realmLocalizationMessageValue)));
|
||||||
|
}
|
||||||
|
|
||||||
testRealm().localization().saveRealmLocalizationText(locale, realmLocalizationMessageKey,
|
private void saveLocalizationText(String locale, String key, String value) {
|
||||||
realmLocalizationMessageValue);
|
testRealm().localization().saveRealmLocalizationText(locale, key, value);
|
||||||
|
getCleanup().addLocalization(locale);
|
||||||
ResteasyClient client = ((ResteasyClientBuilder) ResteasyClientBuilder.newBuilder()).httpEngine(engine).build();
|
|
||||||
|
|
||||||
loginPage.open();
|
|
||||||
|
|
||||||
try(Response responseWithLocalization =
|
|
||||||
client.target(driver.getCurrentUrl()).request().acceptLanguage(locale).get()) {
|
|
||||||
|
|
||||||
assertThat(responseWithLocalization.readEntity(String.class),
|
|
||||||
Matchers.containsString(realmLocalizationMessageValue));
|
|
||||||
|
|
||||||
testRealm().localization().deleteRealmLocalizationText(locale, realmLocalizationMessageKey);
|
|
||||||
|
|
||||||
loginPage.open();
|
|
||||||
|
|
||||||
try(Response responseWithoutLocalization =
|
|
||||||
client.target(driver.getCurrentUrl()).request().acceptLanguage(locale).get()) {
|
|
||||||
|
|
||||||
assertThat(responseWithoutLocalization.readEntity(String.class),
|
|
||||||
Matchers.not(Matchers.containsString(realmLocalizationMessageValue)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchLanguageToGermanAndBack(String expectedEnglishMessage, String expectedGermanMessage, LanguageComboboxAwarePage page) {
|
private void switchLanguageToGermanAndBack(String expectedEnglishMessage, String expectedGermanMessage, LanguageComboboxAwarePage page) {
|
||||||
// Switch language to Deutsch
|
// Switch language to Deutsch
|
||||||
page.openLanguage("Deutsch");
|
page.openLanguage("Deutsch");
|
||||||
Assert.assertEquals("Deutsch", page.getLanguageDropdownText());
|
assertEquals("Deutsch", page.getLanguageDropdownText());
|
||||||
String pageSource = driver.getPageSource();
|
String pageSource = driver.getPageSource();
|
||||||
Assert.assertFalse(pageSource.contains(expectedEnglishMessage));
|
assertThat(pageSource, not(containsString(expectedEnglishMessage)));
|
||||||
Assert.assertTrue(pageSource.contains(expectedGermanMessage));
|
assertThat(pageSource, containsString(expectedGermanMessage));
|
||||||
|
|
||||||
// Revert language
|
// Revert language
|
||||||
page.openLanguage("English");
|
page.openLanguage("English");
|
||||||
Assert.assertEquals("English", page.getLanguageDropdownText());
|
assertEquals("English", page.getLanguageDropdownText());
|
||||||
pageSource = driver.getPageSource();
|
pageSource = driver.getPageSource();
|
||||||
Assert.assertTrue(pageSource.contains(expectedEnglishMessage));
|
assertThat(pageSource, containsString(expectedEnglishMessage));
|
||||||
Assert.assertFalse(pageSource.contains(expectedGermanMessage));
|
assertThat(pageSource, not(containsString(expectedGermanMessage)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,37 @@ public class InternationalizationTest extends AbstractAccountTest {
|
||||||
assertCustomLocaleWelcomeScreen();
|
assertCustomLocaleWelcomeScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void realmLocalizationMessagesAreApplied() {
|
||||||
|
String messageKey = "personalInfoHtmlTitle";
|
||||||
|
|
||||||
|
String localeEn = Locale.ENGLISH.toLanguageTag();
|
||||||
|
String messageEn = "personalInfoHtmlTitle EN";
|
||||||
|
testRealmResource().localization().saveRealmLocalizationText(localeEn,
|
||||||
|
messageKey, messageEn);
|
||||||
|
getCleanup().addLocalization(localeEn);
|
||||||
|
|
||||||
|
String localeDe = Locale.GERMAN.toLanguageTag();
|
||||||
|
String messageDe = "personalInfoHtmlTitle DE";
|
||||||
|
testRealmResource().localization().saveRealmLocalizationText(localeDe,
|
||||||
|
messageKey, messageDe);
|
||||||
|
getCleanup().addLocalization(localeDe);
|
||||||
|
|
||||||
|
// default locale should be "en"
|
||||||
|
personalInfoPage.navigateTo();
|
||||||
|
loginToAccount();
|
||||||
|
assertTestUserLocale(null);
|
||||||
|
assertPersonalInfo(messageEn);
|
||||||
|
|
||||||
|
// switch to locale "de"
|
||||||
|
personalInfoPage.selectLocale(localeDe);
|
||||||
|
personalInfoPage.clickSave(false);
|
||||||
|
WaitUtils.waitForPageToLoad();
|
||||||
|
|
||||||
|
assertTestUserLocale(localeDe);
|
||||||
|
assertPersonalInfo(messageDe);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Ignore
|
@Ignore
|
||||||
public void loginFormTest() {
|
public void loginFormTest() {
|
||||||
|
@ -141,11 +172,11 @@ public class InternationalizationTest extends AbstractAccountTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertCustomLocalePersonalInfo() {
|
private void assertCustomLocalePersonalInfo() {
|
||||||
assertEquals("Osobní údaje", personalInfoPage.getPageTitle());
|
assertPersonalInfo("Osobní údaje");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertCustomLocaleLoginPage() {
|
private void assertPersonalInfo(String expectedText) {
|
||||||
assertEquals(CUSTOM_LOCALE_NAME, loginPage.localeDropdown().getSelected());
|
assertEquals(expectedText, personalInfoPage.getPageTitle());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertTestUserLocale(String expectedLocale) {
|
private void assertTestUserLocale(String expectedLocale) {
|
||||||
|
|
Loading…
Reference in a new issue