keycloak-aplcache

add localization support to realm (cherry picked from commit

2/21/2015 2:36:47 PM

Changes

forms/common-themes/src/main/resources/theme/account/base/messages/messages.properties 48(+0 -48)

forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages.properties 2(+0 -2)

forms/common-themes/src/main/resources/theme/login/base/messages/messages.properties 126(+0 -126)

Details

diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
index a723490..b545aa3 100755
--- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
+++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.2.0.Beta1.xml
@@ -73,6 +73,12 @@
             </column>
             <column name="RETRIEVE_TOKEN" type="BOOLEAN" defaultValueBoolean="false"/>
         </createTable>
+        <createTable tableName="REALM_SUPPORTED_LOCALES">
+            <column name="REALM_ID" type="VARCHAR(36)">
+                <constraints nullable="false"/>
+            </column>
+            <column name="VALUE" type="VARCHAR(255)"/>
+        </createTable>
         <addColumn tableName="CLIENT">
             <column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false"/>
         </addColumn>
@@ -88,11 +94,14 @@
         <addForeignKeyConstraint baseColumnNames="PROTOCOL_MAPPER_ID" baseTableName="PROTOCOL_MAPPER_CONFIG" constraintName="FK_PMConfig" deferrable="false" initiallyDeferred="false" onDelete="RESTRICT" onUpdate="RESTRICT" referencedColumnNames="ID" referencedTableName="PROTOCOL_MAPPER"/>
         <addForeignKeyConstraint baseColumnNames="IDENTITY_PROVIDER_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_7CELWNIBJI49AVXSRTUF6XJ12" referencedColumnNames="INTERNAL_ID" referencedTableName="IDENTITY_PROVIDER"/>
         <addForeignKeyConstraint baseColumnNames="CLIENT_ID" baseTableName="CLIENT_IDENTITY_PROVIDER_MAPPING" constraintName="FK_56ELWNIBJI49AVXSRTUF6XJ23" referencedColumnNames="ID" referencedTableName="CLIENT"/>
+        <addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_SUPPORTED_LOCALES" constraintName="FK_SUPPORTED_LOCALES_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
         <addUniqueConstraint columnNames="PROVIDER_NONIMAL_ID" constraintName="UK_2DAELWNIBJI49AVXSRTUF6XJ33" tableName="IDENTITY_PROVIDER"/>
         <addUniqueConstraint columnNames="IDENTITY_PROVIDER_ID,CLIENT_ID" constraintName="UK_7CAELWNIBJI49AVXSRTUF6XJ12" tableName="CLIENT_IDENTITY_PROVIDER_MAPPING"/>
 
         <addColumn tableName="REALM">
             <column name="LOGIN_LIFESPAN" type="INT"/>
+            <column name="INTERNATIONALIZATION_ENABLED" type="BOOLEAN" defaultValueBoolean="false"/>
+            <column name="DEFAULT_LOCALE" type="VARCHAR(255)" />
             <column name="REGISTRATION_EMAIL_AS_USERNAME" type="BOOLEAN" defaultValueBoolean="false"/>
         </addColumn>
     </changeSet>
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 023172f..532807e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -1,10 +1,6 @@
 package org.keycloak.representations.idm;
 
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -68,6 +64,10 @@ public class RealmRepresentation {
     private List<IdentityProviderRepresentation> identityProviders;
     private List<ProtocolMapperRepresentation> protocolMappers;
     private Boolean identityFederationEnabled;
+    protected Boolean internationalizationEnabled;
+    protected Set<String> supportedLocales;
+    protected String defaultLocale;
+
 
     public String getId() {
         return id;
@@ -513,4 +513,31 @@ public class RealmRepresentation {
     public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
         this.protocolMappers = protocolMappers;
     }
+
+    public Boolean isInternationalizationEnabled() {
+        return internationalizationEnabled;
+    }
+
+    public void setInternationalizationEnabled(Boolean internationalizationEnabled) {
+        this.internationalizationEnabled = internationalizationEnabled;
+    }
+
+    public Set<String> getSupportedLocales() {
+        if(supportedLocales == null){
+            supportedLocales = new HashSet<String>();
+        }
+        return supportedLocales;
+    }
+
+    public void setSupportedLocales(Set<String> supportedLocales) {
+        this.supportedLocales = supportedLocales;
+    }
+
+    public String getDefaultLocale() {
+        return defaultLocale;
+    }
+
+    public void setDefaultLocale(String defaultLocale) {
+        this.defaultLocale = defaultLocale;
+    }
 }
diff --git a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
index 7677a20..bc6920b 100755
--- a/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
+++ b/forms/account-api/src/main/java/org/keycloak/account/AccountProvider.java
@@ -1,11 +1,13 @@
 package org.keycloak.account;
 
+import org.apache.http.client.methods.HttpHead;
 import org.keycloak.events.Event;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.provider.Provider;
 
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -18,13 +20,15 @@ public interface AccountProvider extends Provider {
 
     AccountProvider setUriInfo(UriInfo uriInfo);
 
+    AccountProvider setHttpHeaders(HttpHeaders httpHeaders);
+
     Response createResponse(AccountPages page);
 
-    AccountProvider setError(String message);
+    AccountProvider setError(String message, Object ... parameters);
 
-    AccountProvider setSuccess(String message);
+    AccountProvider setSuccess(String message, Object ... parameters);
 
-    AccountProvider setWarning(String message);
+    AccountProvider setWarning(String message, Object ... parameters);
 
     AccountProvider setUser(UserModel user);
 
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
index 368b459..881606f 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/FreeMarkerAccountProvider.java
@@ -3,38 +3,21 @@ package org.keycloak.account.freemarker;
 import org.jboss.logging.Logger;
 import org.keycloak.account.AccountPages;
 import org.keycloak.account.AccountProvider;
-import org.keycloak.account.freemarker.model.AccountBean;
-import org.keycloak.account.freemarker.model.AccountFederatedIdentityBean;
-import org.keycloak.account.freemarker.model.FeaturesBean;
-import org.keycloak.account.freemarker.model.LogBean;
-import org.keycloak.account.freemarker.model.MessageBean;
-import org.keycloak.account.freemarker.model.PasswordBean;
-import org.keycloak.account.freemarker.model.ReferrerBean;
-import org.keycloak.account.freemarker.model.SessionsBean;
-import org.keycloak.account.freemarker.model.TotpBean;
-import org.keycloak.account.freemarker.model.UrlBean;
+import org.keycloak.account.freemarker.model.*;
 import org.keycloak.events.Event;
-import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
-import org.keycloak.freemarker.FreeMarkerException;
-import org.keycloak.freemarker.FreeMarkerUtil;
-import org.keycloak.freemarker.Theme;
-import org.keycloak.freemarker.ThemeProvider;
+import org.keycloak.freemarker.*;
+import org.keycloak.freemarker.beans.TextFormatterBean;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionModel;
+import org.keycloak.services.resources.flows.Urls;
 
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.core.*;
 import java.io.IOException;
 import java.net.URI;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
+import java.text.MessageFormat;
+import java.util.*;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -57,12 +40,14 @@ public class FreeMarkerAccountProvider implements AccountProvider {
     private boolean passwordSet;
     private KeycloakSession session;
     private FreeMarkerUtil freeMarker;
+    private HttpHeaders headers;
 
     public static enum MessageType {SUCCESS, WARNING, ERROR}
 
     private UriInfo uriInfo;
 
     private String message;
+    private Object[] parameters;
     private MessageType messageType;
 
     public FreeMarkerAccountProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
@@ -76,6 +61,12 @@ public class FreeMarkerAccountProvider implements AccountProvider {
     }
 
     @Override
+    public AccountProvider setHttpHeaders(HttpHeaders httpHeaders) {
+        this.headers = httpHeaders;
+        return this;
+    }
+
+    @Override
     public Response createResponse(AccountPages page) {
         Map<String, Object> attributes = new HashMap<String, Object>();
 
@@ -94,9 +85,14 @@ public class FreeMarkerAccountProvider implements AccountProvider {
             logger.warn("Failed to load properties", e);
         }
 
+        Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, headers);
+        if(locale != null){
+            attributes.put("locale", locale);
+            attributes.put("formatter", new TextFormatterBean(locale));
+        }
         Properties messages;
         try {
-            messages = theme.getMessages();
+            messages = theme.getMessages(locale);
             attributes.put("rb", messages);
         } catch (IOException e) {
             logger.warn("Failed to load messages", e);
@@ -115,13 +111,23 @@ public class FreeMarkerAccountProvider implements AccountProvider {
         }
 
         if (message != null) {
-            attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
+            String formattedMessage;
+            if(messages.containsKey(message)){
+                formattedMessage = new MessageFormat(messages.getProperty(message).replace("'","''"),locale).format(parameters);
+            }else{
+                formattedMessage = message;
+            }
+            attributes.put("message", new MessageBean(formattedMessage, messageType));
         }
 
         if (referrer != null) {
             attributes.put("referrer", new ReferrerBean(referrer));
         }
 
+        if(realm != null){
+            attributes.put("realm", new RealmBean(realm));
+        }
+
         attributes.put("url", new UrlBean(realm, theme, baseUri, baseQueryUri, uriInfo.getRequestUri(), stateChecker));
 
         attributes.put("features", new FeaturesBean(identityProviderEnabled, eventsEnabled, passwordUpdateSupported));
@@ -150,6 +156,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
             String result = freeMarker.processTemplate(attributes, Templates.getTemplate(page), theme);
             Response.ResponseBuilder builder = Response.status(status).type(MediaType.TEXT_HTML).entity(result);
             BrowserSecurityHeaderSetup.headers(builder, realm);
+            LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri,realm.getName()));
             return builder.build();
         } catch (FreeMarkerException e) {
             logger.error("Failed to process template", e);
@@ -163,22 +170,25 @@ public class FreeMarkerAccountProvider implements AccountProvider {
     }
 
     @Override
-    public AccountProvider setError(String message) {
+    public AccountProvider setError(String message, Object ... parameters) {
         this.message = message;
+        this.parameters = parameters;
         this.messageType = MessageType.ERROR;
         return this;
     }
 
     @Override
-    public AccountProvider setSuccess(String message) {
+    public AccountProvider setSuccess(String message, Object ... parameters) {
         this.message = message;
+        this.parameters = parameters;
         this.messageType = MessageType.SUCCESS;
         return this;
     }
 
     @Override
-    public AccountProvider setWarning(String message) {
+    public AccountProvider setWarning(String message, Object ... parameters) {
         this.message = message;
+        this.parameters = parameters;
         this.messageType = MessageType.WARNING;
         return this;
     }
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java
new file mode 100755
index 0000000..b0a5eb4
--- /dev/null
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/RealmBean.java
@@ -0,0 +1,49 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2012, Red Hat, Inc., and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.keycloak.account.freemarker.model;
+
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.representations.idm.CredentialRepresentation;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
+ */
+public class RealmBean {
+
+    private RealmModel realm;
+
+    public RealmBean(RealmModel realmModel) {
+        realm = realmModel;
+    }
+
+    public boolean isInternationalizationEnabled() {
+        return realm.isInternationalizationEnabled();
+    }
+
+    public Set<String> getSupportedLocales(){
+        return realm.getSupportedLocales();
+    }
+
+}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
index de7e432..a357aad 100755
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/UrlBean.java
@@ -59,6 +59,10 @@ public class UrlBean {
         return Urls.accountSessionsLogoutPage(baseQueryURI, realm, stateChecker).toString();
     }
 
+    public String getLocaleCookiePath(){
+        return Urls.localeCookiePath(baseURI, realm);
+    }
+
     public String getTotpRemoveUrl() {
         return Urls.accountTotpRemove(baseQueryURI, realm, stateChecker).toString();
     }
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java
new file mode 100644
index 0000000..5243d56
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/beans/TextFormatterBean.java
@@ -0,0 +1,19 @@
+package org.keycloak.freemarker.beans;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+
+/**
+ * @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
+ */
+public class TextFormatterBean {
+    private Locale locale;
+
+    public TextFormatterBean(Locale locale) {
+        this.locale = locale;
+    }
+
+    public String format(String pattern, Object ... parameters){
+        return new MessageFormat(pattern.replace("'","''"),locale).format(parameters);
+    }
+}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
index 533b631..1fefce1 100644
--- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/ExtendingThemeManager.java
@@ -7,14 +7,7 @@ import org.keycloak.models.KeycloakSession;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Properties;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
 /**
@@ -228,11 +221,11 @@ public class ExtendingThemeManager implements ThemeProvider {
         }
 
         @Override
-        public Properties getMessages() throws IOException {
+        public Properties getMessages(Locale locale) throws IOException {
             Properties messages = new Properties();
             ListIterator<Theme> itr = themes.listIterator(themes.size());
             while (itr.hasPrevious()) {
-                Properties m = itr.previous().getMessages();
+                Properties m = itr.previous().getMessages(locale);
                 if (m != null) {
                     messages.putAll(m);
                 }
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java
new file mode 100644
index 0000000..e153fcf
--- /dev/null
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/LocaleHelper.java
@@ -0,0 +1,113 @@
+package org.keycloak.freemarker;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.NewCookie;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+import java.util.*;
+
+/**
+ * @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
+ */
+public class LocaleHelper {
+    public final static String LOCALE_COOKIE = "KEYCLOAK_LOCALE";
+    public static final String LOCALE_PARAM = "ui_locale";
+
+    private final static Logger LOGGER = Logger.getLogger(LocaleHelper.class);
+
+    public static Locale getLocale(RealmModel realm, UserModel user) {
+        return getLocale(realm, user, null, null);
+    }
+
+    public static Locale getLocale(RealmModel realm, UserModel user, UriInfo uriInfo, HttpHeaders httpHeaders) {
+        if(!realm.isInternationalizationEnabled()){
+            return Locale.ENGLISH;
+        }
+
+        //1. Locale cookie
+        if(httpHeaders != null && httpHeaders.getCookies().containsKey(LOCALE_COOKIE)){
+            String localeString = httpHeaders.getCookies().get(LOCALE_COOKIE).getValue();
+            Locale locale =  findLocale(localeString, realm.getSupportedLocales());
+            if(locale != null){
+                if(user != null){
+                    user.setAttribute(UserModel.LOCALE, locale.toLanguageTag());
+                }
+                return locale;
+            }else{
+                LOGGER.infof("Locale %s is not supported.", localeString);
+            }
+        }
+
+        //2. User profile
+        if(user != null && user.getAttributes().containsKey(UserModel.LOCALE)){
+            String localeString = user.getAttribute(UserModel.LOCALE);
+            Locale locale =  findLocale(localeString, realm.getSupportedLocales());
+            if(locale != null){
+
+                return locale;
+            }else{
+                LOGGER.infof("Locale %s is not supported.", localeString);
+            }
+        }
+
+        //3. ui_locales query parameter
+        if(uriInfo != null && uriInfo.getQueryParameters().containsKey(LOCALE_PARAM)){
+            String localeString = uriInfo.getQueryParameters().getFirst(LOCALE_PARAM);
+            Locale locale =  findLocale(localeString, realm.getSupportedLocales());
+            if(locale != null){
+                return locale;
+            }else{
+                LOGGER.infof("Locale %s is not supported.", localeString);
+            }
+        }
+
+        //4. Accept-Language http header
+        if(httpHeaders !=null && httpHeaders.getAcceptableLanguages() != null && !httpHeaders.getAcceptableLanguages().isEmpty()){
+            for(Locale l : httpHeaders.getAcceptableLanguages()){
+                String localeString = l.toLanguageTag();
+                Locale locale =  findLocale(localeString, realm.getSupportedLocales());
+                if(locale != null){
+                    return locale;
+                }else{
+                    LOGGER.infof("Locale %s is not supported.", localeString);
+                }
+            }
+        }
+
+        //5. Default realm locale
+        if(realm.getDefaultLocale() != null){
+            return Locale.forLanguageTag(realm.getDefaultLocale());
+        }
+
+        return Locale.ENGLISH;
+    }
+
+    public static void updateLocaleCookie(Response.ResponseBuilder builder, Locale locale, RealmModel realm, UriInfo uriInfo, String path) {
+        if (locale == null) {
+            return;
+        }
+        boolean secure = realm.getSslRequired().isRequired(uriInfo.getRequestUri().getHost());
+        builder.cookie(new NewCookie(LocaleHelper.LOCALE_COOKIE, locale.toLanguageTag(), path, null, null, 31536000, secure));
+    }
+
+    public static Locale findLocale(String localeString, Set<String> supportedLocales) {
+        Locale result = null;
+        Locale search = Locale.forLanguageTag(localeString);
+        for(String languageTag : supportedLocales) {
+            Locale locale = Locale.forLanguageTag(languageTag);
+            if(locale.getLanguage().equals(search.getLanguage())){
+                if(locale.getCountry().equals("") && result == null){
+                    result = locale;
+                }
+                if(locale.getCountry().equals(search.getCountry())){
+                    return locale;
+                }
+            }
+        }
+        return result;
+    }
+}
diff --git a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java
index dff92ee..6a12a49 100644
--- a/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java
+++ b/forms/common-freemarker/src/main/java/org/keycloak/freemarker/Theme.java
@@ -3,6 +3,7 @@ package org.keycloak.freemarker;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.Locale;
 import java.util.Properties;
 
 /**
@@ -28,7 +29,7 @@ public interface Theme {
 
     public InputStream getResourceAsStream(String path) throws IOException;
 
-    public Properties getMessages() throws IOException;
+    public Properties getMessages(Locale locale) throws IOException;
 
     public Properties getProperties() throws IOException;
 
diff --git a/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java b/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java
new file mode 100644
index 0000000..01bb998
--- /dev/null
+++ b/forms/common-freemarker/src/test/java/org/keycloak/freemarke/LocaleHelperTest.java
@@ -0,0 +1,24 @@
+package org.keycloak.freemarke;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.keycloak.freemarker.LocaleHelper;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+/**
+ * @author <a href="mailto:gerbermichi@me.com">Michael Gerber</a>
+ */
+public class LocaleHelperTest {
+    @Test
+    public void findLocaleTest(){
+        Assert.assertEquals("de", LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag());
+        Assert.assertEquals("en", LocaleHelper.findLocale("en", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag());
+        Assert.assertEquals("de", LocaleHelper.findLocale("de-CH", new HashSet<>(Arrays.asList("de","en"))).toLanguageTag());
+        Assert.assertEquals("de-CH", LocaleHelper.findLocale("de-CH", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag());
+        Assert.assertEquals("de-DE", LocaleHelper.findLocale("de-DE", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag());
+        Assert.assertEquals("de", LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de","de-CH","de-DE"))).toLanguageTag());
+        Assert.assertNull(LocaleHelper.findLocale("de", new HashSet<>(Arrays.asList("de-CH","de-DE"))));
+    }
+}
diff --git a/forms/common-themes/pom.xml b/forms/common-themes/pom.xml
index 8ad524f..04ea6bc 100755
--- a/forms/common-themes/pom.xml
+++ b/forms/common-themes/pom.xml
@@ -42,6 +42,11 @@
             <artifactId>resteasy-jaxrs</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
 	</dependencies>
 
 	<build>
diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
index 1a793ee..c95edd2 100755
--- a/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
+++ b/forms/common-themes/src/main/java/org/keycloak/theme/ClassLoaderTheme.java
@@ -2,10 +2,13 @@ package org.keycloak.theme;
 
 import org.keycloak.freemarker.Theme;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.Locale;
 import java.util.Properties;
+import java.util.ResourceBundle;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -26,7 +29,7 @@ public class ClassLoaderTheme implements Theme {
 
     private String resourceRoot;
 
-    private String messages;
+    private String messageRoot;
 
     private Properties properties;
 
@@ -43,7 +46,7 @@ public class ClassLoaderTheme implements Theme {
 
         this.templateRoot = themeRoot;
         this.resourceRoot = themeRoot + "resources/";
-        this.messages = themeRoot + "messages/messages.properties";
+        this.messageRoot = themeRoot + "messages/";
         this.properties = new Properties();
 
         URL p = classLoader.getResource(themeRoot + "theme.properties");
@@ -102,9 +105,13 @@ public class ClassLoaderTheme implements Theme {
     }
 
     @Override
-    public Properties getMessages() throws IOException {
+    public Properties getMessages(Locale locale) throws IOException {
+        if(locale == null){
+            return null;
+        }
         Properties m = new Properties();
-        URL url = classLoader.getResource(this.messages);
+
+        URL url = classLoader.getResource(this.messageRoot + "messages_" + locale.toString() + ".properties");
         if (url != null) {
             m.load(url.openStream());
         }
diff --git a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java
index aa2312e..0edc92e 100644
--- a/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java
+++ b/forms/common-themes/src/main/java/org/keycloak/theme/FolderTheme.java
@@ -7,6 +7,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.Locale;
 import java.util.Properties;
 
 /**
@@ -81,9 +82,14 @@ public class FolderTheme implements Theme {
     }
 
     @Override
-    public Properties getMessages() throws IOException {
+    public Properties getMessages(Locale locale) throws IOException {
+        if(locale == null){
+            return null;
+        }
+
         Properties m = new Properties();
-        File file = new File(themeDir, "messages" + File.separator + "messages.properties");
+
+        File file = new File(themeDir, "messages" + File.separator + "messages_" + locale.toString() + ".properties");
         if (file.isFile()) {
             m.load(new FileInputStream(file));
         }
diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties
new file mode 100644
index 0000000..4d3e4b3
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_de.properties
@@ -0,0 +1,57 @@
+authenticatorCode=One-time code
+email=E-Mail
+firstName=Vorname
+lastName=Nachname
+password=Passwort
+passwordConfirm=Passwort best�tigung
+passwordNew=Neues Passwort
+username=Benutzernamen
+street=Strasse
+region=Staat, Provinz, Region
+postal_code=PLZ
+locality=Stadt oder Ortschaft
+country=Land
+
+missingFirstNameMessage=Bitte geben Sie einen Vornamen ein.
+missingEmailMessage=Bitte geben Sie eine E-Mail Adresse ein.
+missingLastNameMessage=Bitte geben Sie einen Nachnamen ein.
+missingPasswordMessage=Bitte geben Sie ein Passwort ein.
+notMatchPasswordMessage=Passw�rter sind nicht identisch.
+
+missingTotpMessage=Bitte geben Sie den One-time Code ein.
+invalidPasswordExistingMessage=Das aktuelle Passwort is ung�ltig.
+invalidPasswordConfirmMessage=Die Passwort best�tigung ist nicht identisch.
+invalidTotpMessage=Ung�ltiger One-time Code.
+invalidEmailMessage=Ung�ltige E-Mail Adresse.
+
+readOnlyUserMessage=Sie k�nnen dieses Benutzerkonto nicht �ndern, da es schreibgesch�tzt ist.
+readOnlyPasswordMessage=Sie k�nnen dieses Passwort nicht �ndern, da es schreibgesch�tzt ist.
+
+successTotpMessage=Mobile Authentifizierung eingerichtet.
+successTotpRemovedMessage=Mobile Authentifizierung entfernt.
+
+accountUpdatedMessage=Ihr Benutzerkonto wurde aktualisiert.
+accountPasswordUpdatedMessage=Ihr Passwort wurde aktualisiert.
+
+missingIdentityProviderMessage=Identity Provider nicht angegeben.
+invalidFederatedIdentityActionMessage=Ung�ltige oder fehlende Aktion.
+identityProviderNotFoundMessage=Angegebener Identity Provider nicht gefunden.
+federatedIdentityLinkNotActiveMessage=Diese Identit�t ist nicht mehr aktiv.
+federatedIdentityRemovingLastProviderMessage=Sie k�nnen den letzen Eintrag nicht enfernen, da Sie kein Passwort haben.
+identityProviderRedirectErrorMessage=Fehler bei der Weiterleitung zum Identity Proivder.
+identityProviderRemovedMessage=Identity Provider erfolgreich entfernt.
+
+accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin.
+
+doLogOutAllSessions=Alle Sessionen abmelden
+
+accountTemporarilyDisabledMessage=Benutzerkonto ist tempor�r gespert, bitte kontaktieren Sie den Admin oder versuchen Sie es sp�ter nocheinmal.
+invalidPasswordMinLengthMessage=Ung�ltiges Passwort: minimum l�nge {0}.
+invalidPasswordMinDigitsMessage=Ung�ltiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
+invalidPasswordMinLowerCaseCharsMessage=Ung�ltiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
+invalidPasswordMinUpperCaseCharsMessage=Ung�ltiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
+invalidPasswordMinSpecialCharsMessage=Ung�ltiges Passwort\: muss mindestens {0} Spezialzeichen beinhalten.
+invalidPasswordNotUsernameMessage=Ung�ltiges Passwort\: darf nicht gleich sein wie Benutzername.
+
+locale_de=Deutsch
+locale_en=Englisch
diff --git a/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties
new file mode 100755
index 0000000..41072c8
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/account/base/messages/messages_en.properties
@@ -0,0 +1,57 @@
+authenticatorCode=One-time code
+email=Email
+firstName=First name
+lastName=Last name
+password=Password
+passwordConfirm=Confirmation
+passwordNew=New Password
+username=Username
+street=Street
+locality=City or Locality
+region=State, Province, or Region
+postal_code=Zip or Postal code
+country=Country
+
+missingFirstNameMessage=Please specify first name.
+invalidEmailMessage=Invalid email address.
+missingLastNameMessage=Please specify last name.
+missingEmailMessage=Please specify email.
+missingPasswordMessage=Please specify password.
+notMatchPasswordMessage=Passwords don't match.
+
+missingTotpMessage=Please specify authenticator code
+invalidPasswordExistingMessage=Invalid existing password
+invalidPasswordConfirmMessage=Password confirmation doesn't match
+invalidTotpMessage=Invalid authenticator code
+
+readOnlyUserMessage=You can't update your account as it is read only
+readOnlyPasswordMessage=You can't update your password as your account is read only
+
+successTotpMessage=Mobile authenticator configured.
+successTotpRemovedMessage=Mobile authenticator removed.
+
+accountUpdatedMessage=Your account has been updated
+accountPasswordUpdatedMessage=Your password has been updated
+
+missingIdentityProviderMessage=Identity provider not specified
+invalidFederatedIdentityActionMessage=Invalid or missing action
+identityProviderNotFoundMessage=Specified identity provider not found
+federatedIdentityLinkNotActiveMessage=This identity is not active anymore
+federatedIdentityRemovingLastProviderMessage=You can't remove last federated identity as you don't have password
+identityProviderRedirectErrorMessage=Failed to redirect to identity provider
+identityProviderRemovedMessage=Identity provider removed successfully
+
+accountDisabledMessage=Account is disabled, contact admin
+
+doLogOutAllSessions=Log out all sessions
+
+accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later
+invalidPasswordMinLengthMessage=Invalid password: minimum length {0}
+invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters
+invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits
+invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters
+invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters
+invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username
+
+locale_de=German
+locale_en=English
diff --git a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl
index 85c7a16..435f188 100755
--- a/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/sessions.ftl
@@ -42,6 +42,6 @@
 
     </table>
 
-    <a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">${rb.logOutAllSessions}</a>
+    <a id="logout-all-sessions" href="${url.sessionsLogoutUrl}">${rb.doLogOutAllSessions}</a>
 
 </@layout.mainLayout>
diff --git a/forms/common-themes/src/main/resources/theme/account/base/template.ftl b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
index 11dc877..0d862c3 100644
--- a/forms/common-themes/src/main/resources/theme/account/base/template.ftl
+++ b/forms/common-themes/src/main/resources/theme/account/base/template.ftl
@@ -15,6 +15,19 @@
             <script type="text/javascript" src="${url.resourcesPath}/${script}"></script>
         </#list>
     </#if>
+    <#if realm.internationalizationEnabled>
+        <script type="text/javascript">
+            window.onload = function () {
+                var select = document.querySelector(".kc-locale-select");
+                select.onchange = function (event) {
+                    document.cookie = "KEYCLOAK_LOCALE=" + select.value+"; path=${url.localeCookiePath}";
+                    setTimeout(function () {
+                        window.location.reload();
+                    }, 0);
+                }
+            }
+        </script>
+    </#if>
 </head>
 <body class="admin-console user ${bodyClass}">
 
@@ -28,6 +41,15 @@
             <div class="navbar-collapse navbar-collapse-1">
                 <div class="container">
                     <ul class="nav navbar-nav navbar-utility">
+                        <#if realm.internationalizationEnabled>
+                            <li>
+                                <select class="kc-locale-select">
+                                    <#list realm.supportedLocales as l>
+                                        <option value="${l}" <#if locale.toLanguageTag()==l>selected="selected"</#if>>${rb["locale_" + l]}</option>
+                                    </#list>
+                                </select>
+                            <li>
+                        </#if>
                         <#if referrer?has_content && referrer.url?has_content><li><a href="${referrer.url}" id="referrer">Back to ${referrer.name}</a></li></#if>
                         <li><a href="${url.logoutUrl}">Sign Out</a></li>
                     </ul>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
index e132cb4..3873df1 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
@@ -403,6 +403,18 @@ module.controller('RealmLoginSettingsCtrl', function($scope, Current, Realm, rea
 
 module.controller('RealmThemeCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
     genericRealmUpdate($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications, "/realms/" + realm.realm + "/theme-settings");
+
+    $scope.supportedLocalesOptions = {
+        'multiple' : true,
+        'simple_tags' : true,
+        'tags' : ['en', 'de']
+    };
+
+    $scope.$watch('realm.supportedLocales', function(oldVal, newVal) {
+        if(angular.isUndefined(newVal) || (angular.isArray(newVal) && newVal.length == 0)){
+            $scope.realm.defaultLocale = undefined;
+        }
+    }, true);
 });
 
 module.controller('RealmCacheCtrl', function($scope, Current, Realm, realm, serverInfo, $http, $location, Dialog, Notifications) {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-theme-settings.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-theme-settings.html
index c08d5f9..5d5a927 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-theme-settings.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-theme-settings.html
@@ -59,6 +59,33 @@
                         </div>
                         <span tooltip-placement="right" tooltip="Select theme for emails that are sent by the server." class="fa fa-info-circle"></span>
                     </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="internationalizationEnabled">Internationalization Enabled</label>
+                        <div class="col-sm-4">
+                            <input ng-model="realm.internationalizationEnabled" name="internationalizationEnabled" id="internationalizationEnabled" onoffswitch />
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="supportedLocales" class="control-label two-lines">Supported Locales</label>
+
+                        <div class="col-sm-4">
+                            <input id="supportedLocales" type="text" ui-select2="supportedLocalesOptions" ng-model="realm.supportedLocales" placeholder="Type a locale and enter" ng-required="realm.internationalizationEnabled" ng-disabled="!realm.internationalizationEnabled">
+                        </div>
+                    </div>
+                    <div class="form-group">
+                        <label class="col-sm-2 control-label" for="defaultLocale">Default Locale</label>
+                        <div class="col-sm-4">
+                            <div class="select-kc">
+                                <select id="defaultLocale"
+                                        ng-model="realm.defaultLocale"
+                                        ng-options="o as o for o in realm.supportedLocales"
+                                        ng-required="realm.internationalizationEnabled"
+                                        ng-disabled="!realm.internationalizationEnabled">
+                                    <option value="" disabled selected>Select one...</option>
+                                </select>
+                            </div>
+                        </div>
+                    </div>
                 </fieldset>
                 <div class="pull-right form-actions" data-ng-show="access.manageRealm">
                     <button kc-reset data-ng-show="changed">Clear changes</button>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html
index 59fa3b2..5007f12 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/user-detail.html
@@ -94,12 +94,24 @@
                     <label class="col-sm-2 control-label" for="reqActions">Required User Actions</label>
 
                     <div class="col-sm-5">
-                        <select ui-select2 ng-model="user.requiredActions" data-placeholder="Select an action..." multiple>
+                        <select ui-select2 id="reqActions" ng-model="user.requiredActions" data-placeholder="Select an action..." multiple>
                             <option ng-repeat="action in userReqActionList" value="{{action.id}}">{{action.text}}</option>
                         </select>
                     </div>
                     <span tooltip-placement="right" tooltip="Require an action when the user logs in. 'Verify email' sends an email to the user to verify their email address.  'Update profile' requires user to enter in new personal information.  'Update password' requires user to enter in a new password.  'Configure TOTP' requires setup of a mobile password generator." class="fa fa-info-circle"></span>
                 </div>
+                <div class="form-group clearfix" data-ng-if="realm.internationalizationEnabled">
+                    <label class="col-sm-2 control-label" for="locale">Locale</label>
+                    <div class="col-sm-5">
+                        <div class="select-kc">
+                            <select id="locale"
+                                    ng-model="user.attributes.locale"
+                                    ng-options="o as o for o in realm.supportedLocales">
+                                <option value="" disabled selected>Select one...</option>
+                            </select>
+                        </div>
+                    </div>
+                </div>
             </fieldset>
             <div data-ng-include data-src="resourceUrl + '/partials/user-attribute-entry.html'"></div>
             <div class="pull-right form-actions" data-ng-show="create && access.manageUsers">
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl
index 38d150f..301212f 100644
--- a/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl
+++ b/forms/common-themes/src/main/resources/theme/email/keycloak/email-verification.ftl
@@ -1,5 +1 @@
-Someone has created a Keycloak account with this email address. If this was you, click the link below to verify your email address:
-${link}
-This link will expire within ${linkExpiration} minutes.
-
-If you didn't create this account, just ignore this message.
\ No newline at end of file
+${formatter.format(rb.emailVerificationBody,link, linkExpiration)}
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl
index c1227aa..cd9d247 100644
--- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl
+++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-login_error.ftl
@@ -1 +1 @@
-A failed login attempt was dettected to your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin.
\ No newline at end of file
+${formatter.format(rb.eventLoginErrorBody,event.date,event.ipAddress)}
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl
index c62e174..37ae2f7 100644
--- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl
+++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-remove_totp.ftl
@@ -1 +1 @@
-TOTP was removed from your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin.
\ No newline at end of file
+${formatter.format(rb.eventRemoveTotpBody,event.date, event.ipAddress)}
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl
index 696a6e6..2c88214 100644
--- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl
+++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_password.ftl
@@ -1 +1 @@
-Your password was changed on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin.
\ No newline at end of file
+${formatter.format(rb.eventUpdatePasswordBody,event.date, event.ipAddress)}
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl
index 531ae66..b34a898 100644
--- a/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl
+++ b/forms/common-themes/src/main/resources/theme/email/keycloak/event-update_totp.ftl
@@ -1 +1 @@
-TOTP was updated for your account on ${event.date?datetime} from ${event.ipAddress}. If this was not you, please contact an admin.
\ No newline at end of file
+${formatter.format(rb.eventUpdateTotpBody,event.date, event.ipAddress)}
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties
new file mode 100644
index 0000000..a5a85f9
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_de.properties
@@ -0,0 +1,12 @@
+emailVerificationSubject=E-Mail verifizieren
+passwordResetSubject=Passwort zur�ckzusetzen
+passwordResetBody=Jemand hat angeforder Ihr Keycloak Passwort zur�ckzusetzen. Falls das Sie waren, dann klicken Sie auf den folgenden Link um das Passwort zur�ckzusetzen.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie das Passwort nicht zur�cksetzen m�chten, dann k�nnen Sie diese E-Mail ignorieren.
+emailVerificationBody=Jemand hat ein Keycloak Konto mit dieser E-Mail Adresse erstellt. Fall das Sie waren, dann klicken Sie auf den Link um die E-Mail Adresse zu verifizieren.\n\n{0}\n\nDieser Link wird in {1} Minuten ablaufen.\n\nFalls Sie dieses Konto nicht erstellt haben, dann k�nnen sie diese Nachricht ignorieren.
+eventLoginErrorSubject=Fehlgeschlagene Anmeldung
+eventLoginErrorBody=Jemand hat um {0} von {1} versucht sich mit ihrem Konto anzumelden. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
+eventRemoveTotpSubject=TOTP Entfernt
+eventRemoveTotpBody=TOTP wurde von ihrem Konto am {0} von {1} entfernt. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
+eventUpdatePasswordSubject=Passwort Aktualisiert
+eventUpdatePasswordBody=Ihr Passwort wurde am {0} von {1} ge�ndert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
+eventUpdateTotpSubject=TOTP Aktualisiert
+eventUpdateTotpBody=TOTP wurde am {0} von {1} ge�ndert. Falls das nicht Sie waren, dann kontaktieren Sie bitte Ihren Admin.
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties
new file mode 100755
index 0000000..e64a00f
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/email/keycloak/messages/messages_en.properties
@@ -0,0 +1,12 @@
+emailVerificationSubject=Verify email
+emailVerificationBody=Someone has created a Keycloak account with this email address. If this was you, click the link below to verify your email address\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you didn't create this account, just ignore this message.
+passwordResetSubject=Reset password
+passwordResetBody=Someone just requested to change your Keycloak account's password. If this was you, click on the link below to set a new password\n\n{0}\n\nThis link will expire within {1} minutes.\n\nIf you don't want to reset your password, just ignore this message and nothing will be changed.
+eventLoginErrorSubject=Login error
+eventLoginErrorBody=A failed login attempt was dettected to your account on {0} from {1}. If this was not you, please contact an admin.
+eventRemoveTotpSubject=Remove TOTP
+eventRemoveTotpBody=TOTP was removed from your account on {0} from {1}. If this was not you, please contact an admin.
+eventUpdatePasswordSubject=Update password
+eventUpdatePasswordBody=Your password was changed on {0} from {1}. If this was not you, please contact an admin.
+eventUpdateTotpSubject=Update TOTP
+eventUpdateTotpBody=TOTP was updated for your account on {0} from {1}. If this was not you, please contact an admin.
diff --git a/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl b/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl
index 5d277e5..55f0138 100644
--- a/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl
+++ b/forms/common-themes/src/main/resources/theme/email/keycloak/password-reset.ftl
@@ -1,5 +1 @@
-Someone just requested to change your Keycloak account's password. If this was you, click on the link below to set a new password:
-${link}
-This link will expire within ${linkExpiration} minutes.
-
-If you don't want to reset your password, just ignore this message and nothing will be changed.
\ No newline at end of file
+${formatter.format(rb.passwordResetBody,link, linkExpiration)}
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login.ftl b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
index e46cf95..e514b9e 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login.ftl
@@ -2,15 +2,15 @@
 <@layout.registrationLayout displayInfo=social.displayInfo; section>
     <#if section = "title">
         <#if client.application>
-             ${rb.loginTitle} ${realm.name}
+            ${formatter.format(rb.loginTitle,(realm.name!''))}
         <#elseif client.oauthClient>
-             ${realm.name} ${rb.loginOauthTitle}
+            ${formatter.format(rb.loginOauthTitle,(realm.name!''))}
         </#if>
     <#elseif section = "header">
         <#if client.application>
-             ${rb.loginTitle} <strong>${(realm.name)!''}</strong>
+            ${formatter.format(rb.loginTitleHtml,(realm.name!''))}
         <#elseif client.oauthClient>
-             Temporary access for <strong>${(realm.name)!''}</strong> requested by <strong>${(client.clientId)!''}</strong>.
+            ${formatter.format(rb.loginOauthTitleHtml,(realm.name!''), (client.clientId!''))}
         </#if>
     <#elseif section = "form">
         <#if realm.password>
@@ -41,24 +41,24 @@
                             <div class="checkbox">
                                 <label>
                                     <#if login.rememberMe??>
-                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> Remember Me
+                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3" checked> ${rb.rememberMe}
                                     <#else>
-                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> Remember Me
+                                        <input id="rememberMe" name="rememberMe" type="checkbox" tabindex="3"> ${rb.rememberMe}
                                     </#if>
                                 </label>
                             </div>
                         </#if>
                         <div class="${properties.kcFormOptionsWrapperClass!}">
                             <#if realm.resetPasswordAllowed>
-                                <span>${rb.loginForgot} <a href="${url.loginPasswordResetUrl}">${rb.password}</a>?</span>
+                                <span><a href="${url.loginPasswordResetUrl}">${rb.doForgotPassword}</a></span>
                             </#if>
                         </div>
                     </div>
 
                     <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
                         <div class="${properties.kcFormButtonsWrapperClass!}">
-                            <input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.logIn}"/>
-                            <input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.cancel}"/>
+                            <input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.doLogIn}"/>
+                            <input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.doCancel}"/>
                         </div>
                      </div>
                 </div>
@@ -75,7 +75,7 @@
     <#elseif section = "info" >
         <#if realm.password && realm.registrationAllowed>
             <div id="kc-registration">
-                <span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.register}</a></span>
+                <span>${rb.noAccount} <a href="${url.registrationUrl}">${rb.doRegister}</a></span>
             </div>
         </#if>
 
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl
index d71536c..c884ad4 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-config-totp.ftl
@@ -23,7 +23,7 @@
                 </div>
 
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
-                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}"/>
+                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.doSubmit}"/>
                 </div>
             </div>
         </form>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
index c881e9c..797b62d 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-oauth-grant.ftl
@@ -4,7 +4,7 @@
     <#if section = "title">
         ${rb.oauthGrantTitle}
     <#elseif section = "header">
-        Temporary access for <strong>${(realm.name)!''}</strong> requested by <strong>${(client.clientId)!''}</strong>.
+        ${formatter.format(rb.oauthGrantTitleHtml,(realm.name!''), (client.clientId!''))}
     <#elseif section = "form">
         <div id="kc-oauth" class="content-area">
             <h3>${rb.oauthGrantRequest}</h3>
@@ -55,8 +55,8 @@
 
                     <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
                         <div class="${properties.kcFormButtonsWrapperClass!}">
-                            <input class="btn btn-primary btn-lg" name="accept" id="kc-login" type="submit" value="${rb.yes}"/>
-                            <input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.no}"/>
+                            <input class="btn btn-primary btn-lg" name="accept" id="kc-login" type="submit" value="${rb.doYes}"/>
+                            <input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.doNo}"/>
                         </div>
                     </div>
                 </div>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl
index aca7d76..9fe7a15 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-reset-password.ftl
@@ -1,9 +1,9 @@
 <#import "template.ftl" as layout>
 <@layout.registrationLayout displayInfo=true; section>
     <#if section = "title">
-        ${rb.emailForgotHeader}
+        ${rb.emailForgotTitle}
     <#elseif section = "header">
-        ${rb.emailForgotHeader}
+        ${rb.emailForgotTitle}
     <#elseif section = "form">
         <form id="kc-reset-password-form" class="${properties.kcFormClass!}" action="${url.loginPasswordResetUrl}" method="post">
             <div class="${properties.kcFormGroupClass!}">
@@ -23,7 +23,7 @@
                 </div>
 
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
-                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}"/>
+                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.doSubmit}"/>
                 </div>
             </div>
         </form>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl
index 613e8f2..8661fa0 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-totp.ftl
@@ -1,9 +1,9 @@
 <#import "template.ftl" as layout>
 <@layout.registrationLayout; section>
     <#if section = "title">
-        ${rb.loginTitle} ${realm.name}
+        ${formatter.format(rb.loginTitle,realm.name)}
     <#elseif section = "header">
-        ${rb.loginTitle} <strong>${realm.name}</strong>
+        ${formatter.format(rb.loginTitleHtml,realm.name)}
     <#elseif section = "form">
         <form id="kc-totp-login-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
             <input id="username" name="username" value="${login.username!''}" type="hidden" />
@@ -27,8 +27,8 @@
 
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
                     <div class="${properties.kcFormButtonsWrapperClass!}">
-                        <input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.logIn}"/>
-                        <input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.cancel}"/>
+                        <input class="btn btn-primary btn-lg" name="login" id="kc-login" type="submit" value="${rb.doLogIn}"/>
+                        <input class="btn btn-default btn-lg" name="cancel" id="kc-cancel" type="submit" value="${rb.doCancel}"/>
                     </div>
                 </div>
             </div>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl
index 826ef31..bbc69a2 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-update-password.ftl
@@ -1,9 +1,9 @@
 <#import "template.ftl" as layout>
 <@layout.registrationLayout displayInfo=true; section>
     <#if section = "title">
-        ${rb.emailUpdateHeader}
+        ${rb.updatePasswordTitle}
     <#elseif section = "header">
-        ${rb.emailUpdateHeader}
+        ${rb.updatePasswordTitle}
     <#elseif section = "form">
         <form id="kc-passwd-update-form" class="${properties.kcFormClass!}" action="${url.loginUpdatePasswordUrl}" method="post">
             <div class="${properties.kcFormGroupClass!}">
@@ -31,7 +31,7 @@
                 </div>
 
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
-                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}"/>
+                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.doSubmit}"/>
                 </div>
             </div>
         </form>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl
index 812e29e..c3e1d9b 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-update-profile.ftl
@@ -40,7 +40,7 @@
                 </div>
 
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
-                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.submit}" />
+                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.doSubmit}" />
                 </div>
             </div>
         </form>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl b/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl
index 19e9bee..ac48f95 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/login-verify-email.ftl
@@ -6,10 +6,10 @@
         ${rb.emailVerifyTitle}
     <#elseif section = "form">
         <p class="instruction">
-            ${rb.emailVerifyInstr}
+            ${rb.emailVerifyInstruction1}
         </p>
-        <p class="instruction">${rb.emailVerifyInstrQ}
-            <a href="${url.loginEmailVerificationUrl}">${rb.emailVerifyClick}</a> ${rb.emailVerifyResend}
+        <p class="instruction">
+            ${rb.emailVerifyInstruction2} <a href="${url.loginEmailVerificationUrl}">${rb.doClickHere}</a> ${rb.emailVerifyInstruction3}
         </p>
     </#if>
 </@layout.registrationLayout>
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties
new file mode 100644
index 0000000..70d8aba
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_de.properties
@@ -0,0 +1,148 @@
+doLogIn=Anmelden
+doRegister=Registrieren
+doCancel=Abbrechen
+doSubmit=Absenden
+doYes=Ja
+doNo=Nein
+doForgotPassword=Passwort vergessen?
+doClickHere=hier klicken
+
+registerWithTitle=Registrierung bei {0}
+registerWithTitleHtml=Registrierung bei <strong>{0}</strong>
+loginTitle=Anmeldung bei {0}
+loginTitleHtml=Anmeldung bei <strong>{0}</strong>
+loginOauthTitle=Tempor�rer zugriff auf {0}
+loginOauthTitleHtml=Tempor�rer zugriff auf <strong>{0}<strong> angefordert von <strong>{1}</strong>.
+loginTotpTitle=Mobile Authentifizierung Einrichten
+loginProfileTitle=Benutzer Konto Informatinen aktualisieren
+oauthGrantTitle=OAuth gew�hren
+oauthGrantTitleHtml=Tempor�rer zugriff auf <strong>{0}<strong> angefordert von <strong>{1}</strong>.
+errorTitle=Es tut uns leid...
+errorTitleHtml=Es tut uns leid...
+emailVerifyTitle=E-Mail verifizieren
+emailForgotTitle=Passwort vergessen?
+updatePasswordTitle=Passwort aktualisieren
+
+noAccount=Neuer Benutzer?
+username=Benutzername
+usernameOrEmail=Benutzername oder E-Mail
+firstName=Vorname
+fullName=Name
+lastName=Nachname
+email=E-Mail
+password=Passwort
+passwordConfirm=Passwort best�tigen
+passwordNew=Neues Passwort
+passwordNewConfirm=Neues Passwort best�tigen
+rememberMe=Angemeldet bleiben
+authenticatorCode=One-time Code
+street=Strasse
+region=Staat, Provinz, Region
+postal_code=PLZ
+locality=Stadt oder Ortschaft
+country=Land
+
+loginTotpStep1=Installieren Sie <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> oder <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> auf Ihrem Smartphone.
+loginTotpStep2=�ffnen Sie die Applikation und scannen Sie den Barcode oder geben sie den Code ein.
+loginTotpStep3=Geben Sie den One-time Code welcher die Applikation generiert hat ein und klicken Sie auf Absenden.
+loginTotpOneTime=One-time Code
+
+oauthGrantRequest=Do you grant these access privileges?
+
+emailVerifyInstruction1=Ein E-Mail mit weitern Anweisungen wurde an Sie versendet.
+emailVerifyInstruction2=Falls Sie kein E-Mail erhalten haben, dann k�nnen Sie 
+emailVerifyInstruction3=um ein neues E-Mail zu verschicken.
+
+backToLogin=&laquo; Zur�ck zur Anmeldung
+backToApplication=&laquo; Zur�ck zur Applikation
+
+emailInstruction=Geben Sie ihren Benutzernamen oder E-Mail Adresse ein und klicken Sie auf Absenden. Danach werden wir ihnen ein E-Mail mit weiteren Instruktionen zusenden.
+
+invalidUserMessage=Ung�ltiger Benutzername oder Passwort.
+invalidEmailMessage=Ung�ltige E-Mail Adresse.
+accountDisabledMessage=Benutzerkonto ist gesperrt, bitte kontaktieren Sie den Admin.
+accountTemporarilyDisabledMessage=Benutzerkonto ist tempor�r gespert, bitte kontaktieren Sie den Admin oder versuchen Sie es sp�ter nocheinmal.
+expiredCodeMessage=Zeit�berschreitung bei der Anmeldung. Bitter melden Sie sich erneut an.
+
+missingFirstNameMessage=Bitte geben Sie einen Vornamen ein.
+missingLastNameMessage=Bitte geben Sie einen Nachnamen ein.
+missingEmailMessage=Bitte geben Sie eine E-Mail Adresse ein.
+missingUsernameMessage=Bitte geben Sie einen Benutzernamen ein.
+missingPasswordMessage=Bitte geben Sie ein Passwort ein.
+missingTotpMessage=Bitte geben Sie den One-time Code ein.
+notMatchPasswordMessage=Passw�rter sind nicht identisch.
+
+invalidPasswordExistingMessage=Das aktuelle Passwort is ung�ltig.
+invalidPasswordConfirmMessage=Die Passwort best�tigung ist nicht identisch.
+invalidTotpMessage=Ung�ltiger One-time Code.
+
+usernameExistsMessage=Benutzermane exisitert bereits.
+emailExistsMessage=E-Mail existiert bereits.
+
+federatedIdentityRegistrationEmailMissingMessage=Die E-Mail Adresse ist nicht vorhanden. Bitte verwenden Sie einen anderen Provider um das Benutzerkonto zu erstellen.
+federatedIdentityEmailExistsMessage=Es exisitert bereits ein Benutzer mit dieser E-Mail Adresse. Bitte melden Sie sich bei der Benutzerverwaltung an um das Benutzerkonto zu verkn�pfen.
+federatedIdentityUsernameExistsMessage=Es exisitert bereits ein Benutzer mit diesem Benutzernamen. Bitte melden Sie sich bei der Benutzerverwaltung an um das Benutzerkonto zu verkn�pfen.
+
+configureTotpMessage=Sie m�ssen eine Mobile Authentifizierung einrichten um das Benutzerkonto zu aktivieren.
+updateProfileMessage=Sie m�ssen ihr Benutzerkonto aktualisieren um das Benutzerkonto zu aktivieren.
+updatePasswordMessage=Sie m�ssen ihr Passwort �ndern um das Benutzerkonto zu aktivieren.
+verifyEmailMessage=Sie m�ssen ihre E-Mail Adresse verifizieren um das Benutzerkonto zu aktivieren.
+
+emailSentMessage=Sie sollten in k�rze ein E-Mail mit weiteren Instruktionen erhalten.
+emailSendErrorMessage=Das E-Mail konnte nicht versendet werden, bitte versuchen Sie es sp�ter nochmals.
+
+accountUpdatedMessage=Ihr Benutzerkonto wurde aktualisiert.
+accountPasswordUpdatedMessage=Ihr Passwort wurde aktualisiert.
+
+noAccessMessage=Kein Zugriff
+
+invalidPasswordMinLengthMessage=Ung�ltiges Passwort: minimum l�nge {0}.
+invalidPasswordMinDigitsMessage=Ung�ltiges Passwort: muss mindestens {0} Zahl(en) beinhalten.
+invalidPasswordMinLowerCaseCharsMessage=Ung�ltiges Passwort: muss mindestens {0} Kleinbuchstaben beinhalten.
+invalidPasswordMinUpperCaseCharsMessage=Ung�ltiges Passwort: muss mindestens {0} Grossbuchstaben beinhalten.
+invalidPasswordMinSpecialCharsMessage=Ung�ltiges Passwort: muss mindestens {0} Spezialzeichen beinhalten.
+invalidPasswordNotUsernameMessage=Ung�ltiges Passwort\: darf nicht gleich sein wie Benutzername.
+
+failedToProcessResponseMessage=Konnte Response nicht verarbeiten.
+httpsRequiredMessage=HTTPS erforderlich.
+realmNotEnabledMessage=Realm nicht aktiviert.
+invalidRequestMessage=Ung�ltiger Request.
+unknownLoginRequesterMessage=Ung�ltiger login requester
+loginRequesterNotEnabledMessage=Login requester nicht aktiviert.
+bearerOnlyMessage=Bearer-only Applikationen k�nne sich nicht via Browser anmelden.
+directGrantsOnlyMessage=Direct-grants-only Clients k�nne sich nicht via Browser anmelden.
+invalidRedirectUriMessage=Ung�ltige redirect uri.
+unsupportedNameIdFormatMessage=Nicht unterst�tztes NameIDFormat.
+invlidRequesterMessage=Ung�ltiger requester.
+registrationNotAllowedMessage=Registrierung nicht erlaubt.
+
+permissionNotApprovedMessage=Berechtigung nicht best�tigt.
+noRelayStateInResponseMessage=Kein relay state in der Antwort von dem Identity Provider [{0}].
+identityProviderAlreadyLinkedMessage=Die Identit�t welche von dem Identity Provider [{0}] zur�ckgegeben wurde, ist bereits mit einem anderen Benutzer verkn�pft.
+insufficientPermissionMessage=Nicht gen�gtend Rechte um die Identit�t zu verkn�pfen.
+couldNotProceedWithAuthenticationRequestMessage=Konnte den Authentifizierungs Request nicht weiter verarbeiten.
+couldNotObtainTokenMessage=Konnte kein token vom Identity Provider [{0}] entnehmen.
+unexpectedErrorRetrievingTokenMessage=Unerwarteter Fehler w�hrend dem Empfang des Token von dem Identity Provider [{0}].
+unexpectedErrorHandlingResponseMessage=Unerwarteter Fehler w�hrend der bearbeitung des Respons vom Identity Provider [{0}].
+identityProviderAuthenticationFailedMessage=Authentifizierung Fehlgeschlagen. Konnte sich mit dem Identity Provider [{0}] nicht authentifizieren.
+couldNotSendAuthenticationRequestMessage=Konnte Authentifizierungs Request nicht an den Identity Provider [{0}] schiken.
+unexpectedErrorHandlingRequestMessage=Unerwarteter Fehler w�hrend der bearbeitung des Requests zum Identity Provider [{0}].
+invalidAccessCodeMessage=Ung�ltiger Access-Code.
+sessionNotActiveMessage=Session nicht aktiv.
+unknownCodeMessage=Unbekannter Code, bitte melden Sie sich erneut �ber die Applikation an.
+invalidCodeMessage=Ung�ltiger Code, bitte melden Sie sich erneut �ber die Applikation an.
+identityProviderUnexpectedErrorMessage=Unerwarteter Fehler w�hrend der Authentifizierung mit dem Identity Provider.
+identityProviderNotFoundMessage=Konnte kein Identity Provider mit der Idenit�t [{0}] finden.
+realmSupportsNoCredentialsMessage=Realm [{0}] unterst�tzt keine Credential Typen..
+identityProviderNotUniqueMessage=Realm [{0}] unterst�tz mehrere Identity Providers.
+
+invalidParameterMessage=Invalid parameter\: {0}
+missingParameterMessage=Missing parameter\: {0}
+clientNotFoundMessage=Client not found.
+
+emailVerifiedMessage=Ihr E-Mail Addresse wurde erfolgreich verifiziert.
+
+locale_de=Deutsch
+locale_en=Englisch
+
+poweredByKeycloak=Powered by Keycloak
\ No newline at end of file
diff --git a/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties
new file mode 100755
index 0000000..6f48c59
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/login/base/messages/messages_en.properties
@@ -0,0 +1,146 @@
+doLogIn=Log in
+doRegister=Register
+doCancel=Cancel
+doSubmit=Submit
+doYes=Yes
+doNo=No
+doForgotPassword=Forgot Password?
+doClickHere=Click here
+
+registerWithTitle=Register with {0}
+registerWithTitleHtml=Register with <strong>{0}</strong>
+loginTitle=Log in to {0}
+loginTitleHtml=Log in to <strong>{0}</strong>
+loginOauthTitle=Temporary access for {0}
+loginOauthTitleHtml=Temporary access for <strong>{0}<strong> requested by <strong>{1}</strong>.
+loginTotpTitle=Mobile Authenticator Setup
+loginProfileTitle=Update Account Information
+oauthGrantTitle=OAuth Grant
+oauthGrantTitleHtml=Temporary access for <strong>{0}</strong> requested by <strong>{1}</strong>.
+errorTitle=We're sorry...
+errorTitleHtml=We're <strong>sorry</strong> ...
+emailVerifyTitle=Email verification
+emailForgotTitle=Forgot Your Password?
+updatePasswordTitle=Update password
+
+noAccount=New user?
+username=Username
+usernameOrEmail=Username or email
+firstName=First name
+fullName=Full name
+lastName=Last name
+email=Email
+password=Password
+passwordConfirm=Confirm password
+passwordNew=New Password
+passwordNewConfirm=New Password confirmation
+rememberMe=Remember me
+authenticatorCode=One-time code
+street=Street
+locality=City or Locality
+region=State, Province, or Region
+postal_code=Zip or Postal code
+country=Country
+
+loginTotpStep1=Install <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or <a href="http://code.google.com/p/google-authenticator/" target="_blank">Google Authenticator</a> on your mobile
+loginTotpStep2=Open the application and scan the barcode or enter the key
+loginTotpStep3=Enter the one-time code provided by the application and click Submit to finish the setup
+loginTotpOneTime=One-time code
+
+oauthGrantRequest=Do you grant these access privileges?
+
+emailVerifyInstruction1=An email with instructions to verify your email address has been sent to you.
+emailVerifyInstruction2=Haven't received a verification code in your email?
+emailVerifyInstruction3=to re-send the email.
+
+backToLogin=&laquo; Back to Login
+
+emailInstruction=Enter your username or email address and we will send you instructions on how to create a new password.
+
+invalidUserMessage=Invalid username or password.
+invalidEmailMessage=Invalid email address.
+accountDisabledMessage=Account is disabled, contact admin.
+accountTemporarilyDisabledMessage=Account is temporarily disabled, contact admin or try again later.
+expiredCodeMessage=Login timeout. Please login again.
+
+missingFirstNameMessage=Please specify first name.
+missingLastNameMessage=Please specify last name.
+missingEmailMessage=Please specify email.
+missingUsernameMessage=Please specify username.
+missingPasswordMessage=Please specify password.
+missingTotpMessage=Please specify authenticator code.
+notMatchPasswordMessage=Passwords don't match.
+
+invalidPasswordExistingMessage=Invalid existing password.
+invalidPasswordConfirmMessage=Password confirmation doesn't match.
+invalidTotpMessage=Invalid authenticator code.
+
+usernameExistsMessage=Username already exists.
+emailExistsMessage=Email already exists.
+
+federatedIdentityEmailExistsMessage=User with email already exists. Please login to account management to link the account.
+federatedIdentityUsernameExistsMessage=User with username already exists. Please login to account management to link the account.
+
+configureTotpMessage=You need to set up Mobile Authenticator to activate your account.
+updateProfileMessage=You need to update your user profile to activate your account.
+updatePasswordMessage=You need to change your password to activate your account.
+verifyEmailMessage=You need to verify your email address to activate your account.
+
+emailSentMessage=You should receive an email shortly with further instructions.
+emailSendErrorMessage=Failed to send email, please try again later
+
+accountUpdatedMessage=Your account has been updated
+accountPasswordUpdatedMessage=Your password has been updated
+
+noAccessMessage=No access
+
+invalidPasswordMinLengthMessage=Invalid password: minimum length {0}
+invalidPasswordMinDigitsMessage=Invalid password: must contain at least {0} numerical digits
+invalidPasswordMinLowerCaseCharsMessage=Invalid password: must contain at least {0} lower case characters
+invalidPasswordMinUpperCaseCharsMessage=Invalid password: must contain at least {0} upper case characters
+invalidPasswordMinSpecialCharsMessage=Invalid password: must contain at least {0} special characters
+invalidPasswordNotUsernameMessage=Invalid password\: must not be equal to the username
+
+failedToProcessResponseMessage=Failed to process response
+httpsRequiredMessage=HTTPS required
+realmNotEnabledMessage=Realm not enabled
+invalidRequestMessage=Invalid Request
+unknownLoginRequesterMessage=Unknown login requester
+loginRequesterNotEnabledMessage=Login requester not enabled
+bearerOnlyMessage=Bearer-only applications are not allowed to initiate browser login
+directGrantsOnlyMessage=Direct-grants-only clients are not allowed to initiate browser login
+invalidRedirectUriMessage=Invalid redirect uri
+unsupportedNameIdFormatMessage=Unsupported NameIDFormat
+invlidRequesterMessage=Invalid requester
+registrationNotAllowedMessage=Registration not allowed
+
+permissionNotApprovedMessage=Permission not approved.
+noRelayStateInResponseMessage=No relay state in response from identity provider [{0}].
+identityProviderAlreadyLinkedMessage=The identity returned by the identity provider [{0}] is already linked to another user.
+insufficientPermissionMessage=Insufficient permissions to link identities.
+couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
+couldNotObtainTokenMessage=Could not obtain token from identity provider [{0}].
+unexpectedErrorRetrievingTokenMessage=Unexpected error when retrieving token from identity provider [{0}].
+unexpectedErrorHandlingResponseMessage=Unexpected error when handling response from identity provider [{0}].
+identityProviderAuthenticationFailedMessage=Authentication failed. Could not authenticate with identity provider [{0}].
+couldNotSendAuthenticationRequestMessage=Could not send authentication request to identity provider [{0}].
+unexpectedErrorHandlingRequestMessage=Unexpected error when handling authentication request to identity provider [{0}].
+invalidAccessCodeMessage=Invalid access code.
+sessionNotActiveMessage=Session not active.
+unknownCodeMessage=Unknown code, please login again through your application.
+invalidCodeMessage=Invalid code, please login again through your application.
+identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
+identityProviderNotFoundMessage=Could not find an identity provider with the identifier [{0}].
+realmSupportsNoCredentialsMessage=Realm [{0}] does not support any credential type.
+identityProviderNotUniqueMessage=Realm [{0}] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
+emailVerifiedMessage=Your email address has been verified.
+
+locale_de=German
+locale_en=English
+
+poweredByKeycloak=Powered by Keycloak
+backToApplication=&laquo; Back to Application
+missingParameterMessage=Missing parameters\: {0}
+clientNotFoundMessage=Client not found.
+invalidParameterMessage=Invalid parameter\: {0}
+federatedIdentityRegistrationEmailMissingMessage=Email is not provided. Use another provider to create account please.
diff --git a/forms/common-themes/src/main/resources/theme/login/base/register.ftl b/forms/common-themes/src/main/resources/theme/login/base/register.ftl
index 492fbab..8dcdcea 100755
--- a/forms/common-themes/src/main/resources/theme/login/base/register.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/register.ftl
@@ -1,9 +1,9 @@
 <#import "template.ftl" as layout>
 <@layout.registrationLayout; section>
     <#if section = "title">
-        ${rb.registerWith} ${realm.name}
+        ${formatter.format(rb.registerWithTitle,(realm.name!''))}
     <#elseif section = "header">
-        ${rb.registerWith} <strong>${realm.name}</strong>
+         ${formatter.format(rb.registerWithTitleHtml,(realm.name!''))}
     <#elseif section = "form">
         <form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.registrationAction}" method="post">
           <#if !realm.registrationEmailAsUsername>
@@ -116,7 +116,7 @@
                 </div>
 
                 <div id="kc-form-buttons" class="${properties.kcFormButtonsClass!}">
-                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.register}"/>
+                    <input class="btn btn-primary btn-lg" type="submit" value="${rb.doRegister}"/>
                 </div>
             </div>
         </form>
diff --git a/forms/common-themes/src/main/resources/theme/login/base/template.ftl b/forms/common-themes/src/main/resources/theme/login/base/template.ftl
index 49c0246..c09271d 100644
--- a/forms/common-themes/src/main/resources/theme/login/base/template.ftl
+++ b/forms/common-themes/src/main/resources/theme/login/base/template.ftl
@@ -21,6 +21,19 @@
             <script src="${url.resourcesPath}/${script}" type="text/javascript"></script>
         </#list>
     </#if>
+    <#if realm.internationalizationEnabled>
+        <script type="text/javascript">
+            window.onload = function () {
+                var select = document.querySelector(".kc-locale-select");
+                select.onchange = function (event) {
+                    document.cookie = "KEYCLOAK_LOCALE=" + select.value+"; path=${url.localeCookiePath}";
+                    setTimeout(function () {
+                        window.location.reload();
+                    }, 0);
+                }
+            }
+        </script>
+    </#if>
 </head>
 
 <body class="${properties.kcBodyClass!}">
@@ -31,6 +44,15 @@
 
             <div id="kc-header" class="${properties.kcHeaderClass!}">
                 <div id="kc-header-wrapper" class="${properties.kcHeaderWrapperClass!}"><#nested "header"></div>
+                <#if realm.internationalizationEnabled>
+                    <div id="kc-locale-wrapper" class="${properties.kcLocaleWrapperClass!}">
+                        <select class="kc-locale-select">
+                            <#list realm.supportedLocales as l>
+                                <option value="${l}" <#if locale.toLanguageTag()==l>selected="selected"</#if>>${rb["locale_" + l]}</option>
+                            </#list>
+                        </select>
+                    </div>
+                </#if>
             </div>
 
             <#if displayMessage && message?has_content>
diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
index df43d16..21972b2 100755
--- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
+++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java
@@ -5,9 +5,12 @@ import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
 import org.keycloak.email.freemarker.beans.EventBean;
 import org.keycloak.events.Event;
+import org.keycloak.events.EventType;
 import org.keycloak.freemarker.FreeMarkerUtil;
+import org.keycloak.freemarker.LocaleHelper;
 import org.keycloak.freemarker.Theme;
 import org.keycloak.freemarker.ThemeProvider;
+import org.keycloak.freemarker.beans.TextFormatterBean;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
@@ -17,10 +20,7 @@ import javax.mail.Session;
 import javax.mail.Transport;
 import javax.mail.internet.InternetAddress;
 import javax.mail.internet.MimeMessage;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
+import java.util.*;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -56,7 +56,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
         Map<String, Object> attributes = new HashMap<String, Object>();
         attributes.put("event", new EventBean(event));
 
-        send("passwordResetSubject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes);
+        send(toCamelCase(event.getType()) + "Subject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes);
     }
 
     @Override
@@ -81,8 +81,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
         try {
             ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
             Theme theme = themeProvider.getTheme(realm.getEmailTheme(), Theme.Type.EMAIL);
-
-            String subject =  theme.getMessages().getProperty(subjectKey);
+            Locale locale = LocaleHelper.getLocale(realm, user);
+            attributes.put("locale", locale);
+            Properties rb = theme.getMessages(locale);
+            attributes.put("rb", rb);
+            attributes.put("formatter", new TextFormatterBean(locale));
+            String subject =  rb.getProperty(subjectKey);
             String body = freeMarker.processTemplate(attributes, template, theme);
 
             send(subject, body);
@@ -150,4 +154,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
     public void close() {
     }
 
+    private String toCamelCase(EventType event){
+        StringBuilder sb = new StringBuilder("event");
+        for(String s : event.name().toString().toLowerCase().split("_")){
+            sb.append(s.substring(0,1).toUpperCase()).append(s.substring(1));
+        }
+        return sb.toString();
+    }
+
 }
diff --git a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
index 11c26fe..36a5252 100755
--- a/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
+++ b/forms/login-api/src/main/java/org/keycloak/login/LoginFormsProvider.java
@@ -7,6 +7,7 @@ import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.provider.Provider;
 
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
@@ -22,6 +23,8 @@ public interface LoginFormsProvider extends Provider {
 
     public LoginFormsProvider setUriInfo(UriInfo uriInfo);
 
+    public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders);
+
     public Response createResponse(UserModel.RequiredAction action);
 
     public Response createLogin();
@@ -45,11 +48,11 @@ public interface LoginFormsProvider extends Provider {
     public LoginFormsProvider setAccessRequest(List<RoleModel> realmRolesRequested, MultivaluedMap<String,RoleModel> resourceRolesRequested);
     public LoginFormsProvider setAccessRequest(String message);
 
-    public LoginFormsProvider setError(String message);
+    public LoginFormsProvider setError(String message, Object ... parameters);
 
-    public LoginFormsProvider setSuccess(String message);
+    public LoginFormsProvider setSuccess(String message, Object ... parameters);
 
-    public LoginFormsProvider setWarning(String message);
+    public LoginFormsProvider setWarning(String message, Object ... parameters);
 
     public LoginFormsProvider setUser(UserModel user);
 
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
index be59af0..14579b9 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -5,11 +5,8 @@ import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
 import org.keycloak.OAuth2Constants;
 import org.keycloak.email.EmailException;
 import org.keycloak.email.EmailProvider;
-import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
-import org.keycloak.freemarker.FreeMarkerException;
-import org.keycloak.freemarker.FreeMarkerUtil;
-import org.keycloak.freemarker.Theme;
-import org.keycloak.freemarker.ThemeProvider;
+import org.keycloak.freemarker.*;
+import org.keycloak.freemarker.beans.TextFormatterBean;
 import org.keycloak.login.LoginFormsPages;
 import org.keycloak.login.LoginFormsProvider;
 import org.keycloak.login.freemarker.model.ClientBean;
@@ -33,23 +30,17 @@ import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.LoginActionsService;
 import org.keycloak.services.resources.flows.Urls;
 
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.core.*;
 import java.io.IOException;
 import java.net.URI;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
+import java.text.MessageFormat;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
-public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
+    public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
     private static final Logger logger = Logger.getLogger(FreeMarkerLoginFormsProvider.class);
 
@@ -63,6 +54,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
     private Map<String, String> httpResponseHeaders = new HashMap<String, String>();
     private String accessRequestMessage;
     private URI actionUri;
+    private Object[] parameters;
 
     private String message;
     private MessageType messageType = MessageType.ERROR;
@@ -80,6 +72,8 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
 
     private UriInfo uriInfo;
 
+    private HttpHeaders httpHeaders;
+
     public FreeMarkerLoginFormsProvider(KeycloakSession session, FreeMarkerUtil freeMarker) {
         this.session = session;
         this.freeMarker = freeMarker;
@@ -95,21 +89,27 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         return this;
     }
 
+    @Override
+    public LoginFormsProvider setHttpHeaders(HttpHeaders httpHeaders) {
+        this.httpHeaders = httpHeaders;
+        return this;
+    }
+
     public Response createResponse(UserModel.RequiredAction action) {
         String actionMessage;
         LoginFormsPages page;
 
         switch (action) {
             case CONFIGURE_TOTP:
-                actionMessage = Messages.ACTION_WARN_TOTP;
+                actionMessage = Messages.CONFIGURE_TOTP;
                 page = LoginFormsPages.LOGIN_CONFIG_TOTP;
                 break;
             case UPDATE_PROFILE:
-                actionMessage = Messages.ACTION_WARN_PROFILE;
+                actionMessage = Messages.UPDATE_PROFILE;
                 page = LoginFormsPages.LOGIN_UPDATE_PROFILE;
                 break;
             case UPDATE_PASSWORD:
-                actionMessage = Messages.ACTION_WARN_PASSWD;
+                actionMessage = Messages.UPDATE_PASSWORD;
                 page = LoginFormsPages.LOGIN_UPDATE_PASSWORD;
                 break;
             case VERIFY_EMAIL:
@@ -123,10 +123,10 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
                     session.getProvider(EmailProvider.class).setRealm(realm).setUser(user).sendVerifyEmail(link, expiration);
                 } catch (EmailException e) {
                     logger.error("Failed to send verification email", e);
-                    return setError("emailSendError").createErrorPage();
+                    return setError(Messages.EMAIL_SENT_ERROR).createErrorPage();
                 }
 
-                actionMessage = Messages.ACTION_WARN_EMAIL;
+                actionMessage = Messages.VERIFY_EMAIL;
                 page = LoginFormsPages.LOGIN_VERIFY_EMAIL;
                 break;
             default:
@@ -175,8 +175,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         }
 
         Properties messages;
+        Locale locale = LocaleHelper.getLocale(realm, user, uriInfo, httpHeaders);
+        if(locale != null){
+            attributes.put("locale", locale);
+            attributes.put("formatter", new TextFormatterBean(locale));
+        }
         try {
-            messages = theme.getMessages();
+            messages = theme.getMessages(locale);
             attributes.put("rb", messages);
         } catch (IOException e) {
             logger.warn("Failed to load messages", e);
@@ -184,7 +189,13 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         }
 
         if (message != null) {
-            attributes.put("message", new MessageBean(messages.containsKey(message) ? messages.getProperty(message) : message, messageType));
+            String formattedMessage;
+            if(messages.containsKey(message)){
+                formattedMessage = new MessageFormat(messages.getProperty(message).replace("'","''"),locale).format(parameters);
+            }else{
+                formattedMessage = message;
+            }
+            attributes.put("message", new MessageBean(formattedMessage, messageType));
         }
         if (page == LoginFormsPages.OAUTH_GRANT) {
             // for some reason Resteasy 2.3.7 doesn't like query params and form params with the same name and will null out the code form param
@@ -233,6 +244,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
             for (Map.Entry<String, String> entry : httpResponseHeaders.entrySet()) {
                 builder.header(entry.getKey(), entry.getValue());
             }
+            LocaleHelper.updateLocaleCookie(builder, locale, realm, uriInfo, Urls.localeCookiePath(baseUri, realm.getName()));
             return builder.build();
         } catch (FreeMarkerException e) {
             logger.error("Failed to process template", e);
@@ -277,21 +289,24 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
         return createResponse(LoginFormsPages.CODE);
     }
 
-    public FreeMarkerLoginFormsProvider setError(String message) {
+    public FreeMarkerLoginFormsProvider setError(String message, Object ... parameters) {
         this.message = message;
         this.messageType = MessageType.ERROR;
+        this.parameters = parameters;
         return this;
     }
 
-    public FreeMarkerLoginFormsProvider setSuccess(String message) {
+    public FreeMarkerLoginFormsProvider setSuccess(String message, Object ... parameters) {
         this.message = message;
         this.messageType = MessageType.SUCCESS;
+        this.parameters = parameters;
         return this;
     }
 
-    public FreeMarkerLoginFormsProvider setWarning(String message) {
+    public FreeMarkerLoginFormsProvider setWarning(String message, Object ... parameters) {
         this.message = message;
         this.messageType = MessageType.WARNING;
+        this.parameters = parameters;
         return this;
     }
 
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
index e4ac27f..3e36d08 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/RealmBean.java
@@ -25,6 +25,8 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.models.RequiredCredentialModel;
 import org.keycloak.representations.idm.CredentialRepresentation;
 
+import java.util.Set;
+
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
  */
@@ -60,6 +62,14 @@ public class RealmBean {
         return realm.isRememberMe();
     }
 
+    public boolean isInternationalizationEnabled() {
+        return realm.isInternationalizationEnabled();
+    }
+
+    public Set<String> getSupportedLocales(){
+        return realm.getSupportedLocales();
+    }
+
     public boolean isPassword() {
         for (RequiredCredentialModel r : realm.getRequiredCredentials()) {
             if (r.getType().equals(CredentialRepresentation.PASSWORD)) {
diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
index 26d67a3..259b098 100755
--- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
+++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/UrlBean.java
@@ -86,6 +86,10 @@ public class UrlBean {
         return Urls.loginActionEmailVerification(baseURI, realm).toString();
     }
 
+    public String getLocaleCookiePath(){
+        return Urls.localeCookiePath(baseURI, realm);
+    }
+
     public String getOauthAction() {
         if (this.actionuri != null) {
             return this.actionuri.getPath();
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 38baa89..678fb33 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -65,6 +65,10 @@ public class RealmEntity extends AbstractIdentifiableEntity {
 
     private String adminAppId;
 
+    private boolean internationalizationEnabled;
+    private List<String> supportedLocales = new ArrayList<String>();
+    private String defaultLocale;
+
     public String getName() {
         return name;
     }
@@ -407,6 +411,30 @@ public class RealmEntity extends AbstractIdentifiableEntity {
     public void setCertificatePem(String certificatePem) {
         this.certificatePem = certificatePem;
     }
+
+    public boolean isInternationalizationEnabled() {
+        return internationalizationEnabled;
+    }
+
+    public void setInternationalizationEnabled(boolean internationalizationEnabled) {
+        this.internationalizationEnabled = internationalizationEnabled;
+    }
+
+    public List<String> getSupportedLocales() {
+        return supportedLocales;
+    }
+
+    public void setSupportedLocales(List<String> supportedLocales) {
+        this.supportedLocales = supportedLocales;
+    }
+
+    public String getDefaultLocale() {
+        return defaultLocale;
+    }
+
+    public void setDefaultLocale(String defaultLocale) {
+        this.defaultLocale = defaultLocale;
+    }
 }
 
 
diff --git a/model/api/src/main/java/org/keycloak/models/ModelException.java b/model/api/src/main/java/org/keycloak/models/ModelException.java
index 1c2f068..d3431b0 100644
--- a/model/api/src/main/java/org/keycloak/models/ModelException.java
+++ b/model/api/src/main/java/org/keycloak/models/ModelException.java
@@ -5,6 +5,8 @@ package org.keycloak.models;
  */
 public class ModelException extends RuntimeException {
 
+    private Object[] parameters;
+
     public ModelException() {
     }
 
@@ -12,6 +14,11 @@ public class ModelException extends RuntimeException {
         super(message);
     }
 
+    public ModelException(String message, Object ... parameters) {
+        super(message);
+        this.parameters = parameters;
+    }
+
     public ModelException(String message, Throwable cause) {
         super(message, cause);
     }
@@ -20,4 +27,11 @@ public class ModelException extends RuntimeException {
         super(cause);
     }
 
+    public Object[] getParameters() {
+        return parameters;
+    }
+
+    public void setParameters(Object[] parameters) {
+        this.parameters = parameters;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
index 3651e9d..cfcc108 100755
--- a/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
+++ b/model/api/src/main/java/org/keycloak/models/PasswordPolicy.java
@@ -1,5 +1,6 @@
 package org.keycloak.models;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -9,6 +10,13 @@ import java.util.List;
  */
 public class PasswordPolicy {
 
+    public static final String INVALID_PASSWORD_MIN_LENGTH_MESSAGE = "invalidPasswordMinLengthMessage";
+    public static final String INVALID_PASSWORD_MIN_DIGITS_MESSAGE = "invalidPasswordMinDigitsMessage";
+    public static final String INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE = "invalidPasswordMinLowerCaseCharsMessage";
+    public static final String INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE = "invalidPasswordMinUpperCaseCharsMessage";
+    public static final String INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE = "invalidPasswordMinSpecialCharsMessage";
+    public static final String INVALID_PASSWORD_NOT_USERNAME = "invalidPasswordNotUsernameMessage";
+    
     private List<Policy> policies;
     private String policyString;
 
@@ -76,9 +84,9 @@ public class PasswordPolicy {
         return -1;
     }
 
-    public String validate(String username, String password) {
+    public Error validate(String username, String password) {
         for (Policy p : policies) {
-            String error = p.validate(username, password);
+            Error error = p.validate(username, password);
             if (error != null) {
                 return error;
             }
@@ -87,7 +95,25 @@ public class PasswordPolicy {
     }
 
     private static interface Policy {
-        public String validate(String username, String password);
+        public Error validate(String username, String password);
+    }
+
+    public static class Error{
+        private String message;
+        private Object[] parameters;
+
+        private Error(String message, Object ... parameters){
+            this.message = message;
+            this.parameters = parameters;
+        }
+
+        public String getMessage() {
+            return message;
+        }
+
+        public Object[] getParameters() {
+            return parameters;
+        }
     }
 
     private static class HashIterations implements Policy {
@@ -99,7 +125,7 @@ public class PasswordPolicy {
         }
 
         @Override
-        public String validate(String username, String password) {
+        public Error validate(String username, String password) {
             return null;
         }
     }
@@ -111,8 +137,8 @@ public class PasswordPolicy {
         }
 
         @Override
-        public String validate(String username, String password) {
-            return username.equals(password) ? "Invalid password: must not be equal to the username" : null;
+        public Error validate(String username, String password) {
+            return username.equals(password) ? new Error(INVALID_PASSWORD_NOT_USERNAME) : null;
         }
     }
 
@@ -125,8 +151,8 @@ public class PasswordPolicy {
         }
 
         @Override
-        public String validate(String username, String password) {
-            return password.length() < min ? "Invalid password: minimum length " + min : null;
+        public Error validate(String username, String password) {
+            return password.length() < min ? new Error(INVALID_PASSWORD_MIN_LENGTH_MESSAGE, min) : null;
         }
     }
 
@@ -139,14 +165,14 @@ public class PasswordPolicy {
         }
 
         @Override
-        public String validate(String username, String password) {
+        public Error validate(String username, String password) {
             int count = 0;
             for (char c : password.toCharArray()) {
                 if (Character.isDigit(c)) {
                     count++;
                 }
             }
-            return count < min ? "Invalid password: must contain at least " + min + " numerical digits" : null;
+            return count < min ? new Error(INVALID_PASSWORD_MIN_DIGITS_MESSAGE, min) : null;
         }
     }
 
@@ -159,14 +185,14 @@ public class PasswordPolicy {
         }
 
         @Override
-        public String validate(String username, String password) {
+        public Error validate(String username, String password) {
             int count = 0;
             for (char c : password.toCharArray()) {
                 if (Character.isLowerCase(c)) {
                     count++;
                 }
             }
-            return count < min ? "Invalid password: must contain at least " + min + " lower case characters": null;
+            return count < min ? new Error(INVALID_PASSWORD_MIN_LOWER_CASE_CHARS_MESSAGE, min): null;
         }
     }
 
@@ -179,14 +205,14 @@ public class PasswordPolicy {
         }
 
         @Override
-        public String validate(String username, String password) {
+        public Error validate(String username, String password) {
             int count = 0;
             for (char c : password.toCharArray()) {
                 if (Character.isUpperCase(c)) {
                     count++;
                 }
             }
-            return count < min ? "Invalid password: must contain at least " + min + " upper case characters" : null;
+            return count < min ? new Error(INVALID_PASSWORD_MIN_UPPER_CASE_CHARS_MESSAGE, min) : null;
         }
     }
 
@@ -199,14 +225,14 @@ public class PasswordPolicy {
         }
 
         @Override
-        public String validate(String username, String password) {
+        public Error validate(String username, String password) {
             int count = 0;
             for (char c : password.toCharArray()) {
                 if (!Character.isLetterOrDigit(c)) {
                     count++;
                 }
             }
-            return count < min ? "Invalid password: must contain at least " + min + " special characters" : null;
+            return count < min ? new Error(INVALID_PASSWORD_MIN_SPECIAL_CHARS_MESSAGE, min) : null;
         }
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index ab260a1..7308565 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -244,4 +244,11 @@ public interface RealmModel extends RoleContainerModel {
     ClientModel findClientById(String id);
 
     boolean isIdentityFederationEnabled();
+
+    boolean isInternationalizationEnabled();
+    void setInternationalizationEnabled(boolean enabled);
+    Set<String> getSupportedLocales();
+    void setSupportedLocales(Set<String> locales);
+    String getDefaultLocale();
+    void setDefaultLocale(String locale);
 }
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
index a360f95..1016341 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -323,8 +323,8 @@ public class UserFederationManager implements UserProvider {
     public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) {
         if (credential.getType().equals(UserCredentialModel.PASSWORD)) {
             if (realm.getPasswordPolicy() != null) {
-                String error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue());
-                if (error != null) throw new ModelException(error);
+                PasswordPolicy.Error error = realm.getPasswordPolicy().validate(user.getUsername(), credential.getValue());
+                if (error != null) throw new ModelException(error.getMessage(), error.getParameters());
             }
         }
         user.updateCredential(credential);
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 1047ad2..ee8ce80 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -13,6 +13,7 @@ public interface UserModel {
     public static final String LAST_NAME = "lastName";
     public static final String FIRST_NAME = "firstName";
     public static final String EMAIL = "email";
+    public static final String LOCALE = "locale";
 
     String getId();
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 686eede..736aff8 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -155,6 +155,10 @@ public class ModelToRepresentation {
             rep.addIdentityProvider(toRepresentation(provider));
         }
 
+        rep.setInternationalizationEnabled(realm.isInternationalizationEnabled());
+        rep.getSupportedLocales().addAll(realm.getSupportedLocales());
+        rep.setDefaultLocale(realm.getDefaultLocale());
+
         return rep;
     }
 
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index ce87ada..c2090d3 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -243,6 +243,16 @@ public class RepresentationToModel {
                 UserModel user = createUser(session, newRealm, userRep, appMap);
             }
         }
+
+        if(rep.isInternationalizationEnabled() != null){
+            newRealm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
+        }
+        if(rep.getSupportedLocales() != null){
+            newRealm.setSupportedLocales(new HashSet<String>(rep.getSupportedLocales()));
+        }
+        if(rep.getDefaultLocale() != null){
+            newRealm.setDefaultLocale(rep.getDefaultLocale());
+        }
     }
 
     public static void updateRealm(RealmRepresentation rep, RealmModel realm) {
@@ -304,6 +314,16 @@ public class RepresentationToModel {
         if ("GENERATE".equals(rep.getPublicKey())) {
             KeycloakModelUtils.generateRealmKeys(realm);
         }
+
+        if(rep.isInternationalizationEnabled() != null){
+            realm.setInternationalizationEnabled(rep.isInternationalizationEnabled());
+        }
+        if(rep.getSupportedLocales() != null){
+            realm.setSupportedLocales(new HashSet<String>(rep.getSupportedLocales()));
+        }
+        if(rep.getDefaultLocale() != null){
+            realm.setDefaultLocale(rep.getDefaultLocale());
+        }
     }
 
     // Basic realm stuff
diff --git a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
index 7242c55..3bde361 100644
--- a/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
+++ b/model/api/src/test/java/org/keycloak/models/PasswordPolicyTest.java
@@ -11,62 +11,72 @@ public class PasswordPolicyTest {
     @Test
     public void testLength() {
         PasswordPolicy policy = new PasswordPolicy("length");
-        Assert.assertEquals("Invalid password: minimum length 8", policy.validate("jdoe", "1234567"));
+        Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "1234567").getMessage());
+        Assert.assertArrayEquals(new Object[]{8}, policy.validate("jdoe", "1234567").getParameters());
         Assert.assertNull(policy.validate("jdoe", "12345678"));
 
         policy = new PasswordPolicy("length(4)");
-        Assert.assertEquals("Invalid password: minimum length 4", policy.validate("jdoe", "123"));
+        Assert.assertEquals("invalidPasswordMinLengthMessage", policy.validate("jdoe", "123").getMessage());
+        Assert.assertArrayEquals(new Object[]{4}, policy.validate("jdoe", "123").getParameters());
         Assert.assertNull(policy.validate("jdoe", "1234"));
     }
 
     @Test
     public void testDigits() {
         PasswordPolicy policy = new PasswordPolicy("digits");
-        Assert.assertEquals("Invalid password: must contain at least 1 numerical digits", policy.validate("jdoe", "abcd"));
+        Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd").getParameters());
         Assert.assertNull(policy.validate("jdoe", "abcd1"));
 
         policy = new PasswordPolicy("digits(2)");
-        Assert.assertEquals("Invalid password: must contain at least 2 numerical digits", policy.validate("jdoe", "abcd1"));
+        Assert.assertEquals("invalidPasswordMinDigitsMessage", policy.validate("jdoe", "abcd1").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abcd1").getParameters());
         Assert.assertNull(policy.validate("jdoe", "abcd12"));
     }
 
     @Test
     public void testLowerCase() {
         PasswordPolicy policy = new PasswordPolicy("lowerCase");
-        Assert.assertEquals("Invalid password: must contain at least 1 lower case characters", policy.validate("jdoe", "ABCD1234"));
+        Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABCD1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "ABCD1234").getParameters());
         Assert.assertNull(policy.validate("jdoe", "ABcD1234"));
 
         policy = new PasswordPolicy("lowerCase(2)");
-        Assert.assertEquals("Invalid password: must contain at least 2 lower case characters", policy.validate("jdoe", "ABcD1234"));
+        Assert.assertEquals("invalidPasswordMinLowerCaseCharsMessage", policy.validate("jdoe", "ABcD1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ABcD1234").getParameters());
         Assert.assertNull(policy.validate("jdoe", "aBcD1234"));
     }
 
     @Test
     public void testUpperCase() {
         PasswordPolicy policy = new PasswordPolicy("upperCase");
-        Assert.assertEquals("Invalid password: must contain at least 1 upper case characters", policy.validate("jdoe", "abcd1234"));
+        Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abcd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters());
         Assert.assertNull(policy.validate("jdoe", "abCd1234"));
 
         policy = new PasswordPolicy("upperCase(2)");
-        Assert.assertEquals("Invalid password: must contain at least 2 upper case characters", policy.validate("jdoe", "abCd1234"));
+        Assert.assertEquals("invalidPasswordMinUpperCaseCharsMessage", policy.validate("jdoe", "abCd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "abCd1234").getParameters());
         Assert.assertNull(policy.validate("jdoe", "AbCd1234"));
     }
 
     @Test
     public void testSpecialChars() {
         PasswordPolicy policy = new PasswordPolicy("specialChars");
-        Assert.assertEquals("Invalid password: must contain at least 1 special characters", policy.validate("jdoe", "abcd1234"));
+        Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "abcd1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{1}, policy.validate("jdoe", "abcd1234").getParameters());
         Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
 
         policy = new PasswordPolicy("specialChars(2)");
-        Assert.assertEquals("Invalid password: must contain at least 2 special characters", policy.validate("jdoe", "ab&d1234"));
+        Assert.assertEquals("invalidPasswordMinSpecialCharsMessage", policy.validate("jdoe", "ab&d1234").getMessage());
+        Assert.assertArrayEquals(new Object[]{2}, policy.validate("jdoe", "ab&d1234").getParameters());
         Assert.assertNull(policy.validate("jdoe", "ab&d-234"));
     }
 
     @Test
     public void testNotUsername() {
         PasswordPolicy policy = new PasswordPolicy("notUsername");
-        Assert.assertEquals("Invalid password: must not be equal to the username", policy.validate("jdoe", "jdoe"));
+        Assert.assertEquals("invalidPasswordNotUsernameMessage", policy.validate("jdoe", "jdoe").getMessage());
         Assert.assertNull(policy.validate("jdoe", "ab&d1234"));
     }
 
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
index 5183b74..99ab488 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java
@@ -1068,6 +1068,36 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
+    public boolean isInternationalizationEnabled() {
+        return realm.isInternationalizationEnabled();
+    }
+
+    @Override
+    public void setInternationalizationEnabled(boolean enabled) {
+        realm.setInternationalizationEnabled(enabled);
+    }
+
+    @Override
+    public Set<String> getSupportedLocales() {
+        return new HashSet<>(realm.getSupportedLocales());
+    }
+
+    @Override
+    public void setSupportedLocales(Set<String> locales) {
+        realm.setSupportedLocales(new ArrayList<>(locales));
+    }
+
+    @Override
+    public String getDefaultLocale() {
+        return realm.getDefaultLocale();
+    }
+
+    @Override
+    public void setDefaultLocale(String locale) {
+        realm.setDefaultLocale(locale);
+    }
+
+    @Override
     public boolean equals(Object o) {
         if (this == o) return true;
         if (o == null || !(o instanceof RealmModel)) return false;
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index dc06240..a4ce5bb 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -83,6 +83,9 @@ public class CachedRealm {
     private Map<String, String> realmRoles = new HashMap<String, String>();
     private Map<String, String> applications = new HashMap<String, String>();
     private Map<String, String> clients = new HashMap<String, String>();
+    private boolean internationalizationEnabled;
+    private Set<String> supportedLocales = new HashSet<String>();
+    private String defaultLocale;
 
     public CachedRealm() {
     }
@@ -164,6 +167,10 @@ public class CachedRealm {
             cache.addCachedOAuthClient(cachedApp);
         }
 
+        internationalizationEnabled = model.isInternationalizationEnabled();
+        supportedLocales.addAll(model.getSupportedLocales());
+        defaultLocale = model.getDefaultLocale();
+
     }
 
 
@@ -353,4 +360,16 @@ public class CachedRealm {
     public List<IdentityProviderModel> getIdentityProviders() {
         return identityProviders;
     }
+
+    public boolean isInternationalizationEnabled() {
+        return internationalizationEnabled;
+    }
+
+    public Set<String> getSupportedLocales() {
+        return supportedLocales;
+    }
+
+    public String getDefaultLocale() {
+        return defaultLocale;
+    }
 }
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index a58774d..77b84e6 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -888,4 +888,39 @@ public class RealmAdapter implements RealmModel {
     public int hashCode() {
         return getId().hashCode();
     }
+
+    @Override
+    public boolean isInternationalizationEnabled() {
+        if (updated != null) return updated.isInternationalizationEnabled();
+        return cached.isInternationalizationEnabled();
+    }
+
+    @Override
+    public void setInternationalizationEnabled(boolean enabled) {
+        getDelegateForUpdate();
+        updated.setInternationalizationEnabled(enabled);
+    }
+
+    @Override
+    public Set<String> getSupportedLocales() {
+        if (updated != null) return updated.getSupportedLocales();
+        return cached.getSupportedLocales();
+    }
+
+    @Override
+    public void setSupportedLocales(Set<String> locales) {
+        getDelegateForUpdate();
+        updated.setSupportedLocales(locales);
+    }
+
+    @Override
+    public String getDefaultLocale() {
+        if (updated != null) return updated.getDefaultLocale();
+        return cached.getDefaultLocale();
+    }
+
+    @Override
+    public void setDefaultLocale(String locale) {
+        updated.setDefaultLocale(locale);
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 1ba7a55..563791e 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -137,6 +137,18 @@ public class RealmEntity {
     @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
     protected List<IdentityProviderEntity> identityProviders = new ArrayList<IdentityProviderEntity>();
 
+    @Column(name="INTERNATIONALIZATION_ENABLED")
+    protected boolean internationalizationEnabled;
+
+    @ElementCollection
+    @Column(name="VALUE")
+    @CollectionTable(name="REALM_SUPPORTED_LOCALES", joinColumns={ @JoinColumn(name="REALM_ID") })
+    protected Set<String> supportedLocales = new HashSet<String>();
+
+    @Column(name="DEFAULT_LOCALE")
+    protected String defaultLocale;
+
+
     public String getId() {
         return id;
     }
@@ -452,5 +464,28 @@ public class RealmEntity {
         getIdentityProviders().add(entity);
     }
 
+    public boolean isInternationalizationEnabled() {
+        return internationalizationEnabled;
+    }
+
+    public void setInternationalizationEnabled(boolean internationalizationEnabled) {
+        this.internationalizationEnabled = internationalizationEnabled;
+    }
+
+    public Set<String> getSupportedLocales() {
+        return supportedLocales;
+    }
+
+    public void setSupportedLocales(Set<String> supportedLocales) {
+        this.supportedLocales = supportedLocales;
+    }
+
+    public String getDefaultLocale() {
+        return defaultLocale;
+    }
+
+    public void setDefaultLocale(String defaultLocale) {
+        this.defaultLocale = defaultLocale;
+    }
 }
 
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index a17dd17..73add51 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -1238,4 +1238,36 @@ public class RealmAdapter implements RealmModel {
         return !this.realm.getIdentityProviders().isEmpty();
     }
 
+    @Override
+    public boolean isInternationalizationEnabled() {
+        return realm.isInternationalizationEnabled();
+    }
+
+    @Override
+    public void setInternationalizationEnabled(boolean enabled) {
+        realm.setInternationalizationEnabled(enabled);
+        em.flush();
+    }
+
+    @Override
+    public Set<String> getSupportedLocales() {
+        return realm.getSupportedLocales();
+    }
+
+    @Override
+    public void setSupportedLocales(Set<String> locales) {
+        realm.setSupportedLocales(locales);
+        em.flush();
+    }
+
+    @Override
+    public String getDefaultLocale() {
+        return realm.getDefaultLocale();
+    }
+
+    @Override
+    public void setDefaultLocale(String locale) {
+        realm.setDefaultLocale(locale);
+        em.flush();
+    }
 }
\ No newline at end of file
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 9bee5a5..655a452 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -1085,5 +1085,40 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         return getId().hashCode();
     }
 
+    @Override
+    public boolean isInternationalizationEnabled() {
+        return realm.isInternationalizationEnabled();
+    }
+
+    @Override
+    public void setInternationalizationEnabled(boolean enabled) {
+        realm.setInternationalizationEnabled(enabled);
+        updateRealm();
+    }
+
+    @Override
+    public Set<String> getSupportedLocales() {
+        return new HashSet<String>(realm.getSupportedLocales());
+    }
+
+    @Override
+    public void setSupportedLocales(Set<String> locales) {
+        if (locales != null) {
+            realm.setEventsListeners(new ArrayList<String>(locales));
+        } else {
+            realm.setEventsListeners(Collections.EMPTY_LIST);
+        }
+        updateRealm();
+    }
 
+    @Override
+    public String getDefaultLocale() {
+        return realm.getDefaultLocale();
+    }
+
+    @Override
+    public void setDefaultLocale(String locale) {
+        realm.setDefaultLocale(locale);
+        updateRealm();
+    }
 }
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
index 183ebd5..0e4b089 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java
@@ -22,6 +22,7 @@ import org.keycloak.protocol.saml.mappers.SAMLLoginResponseMapper;
 import org.keycloak.protocol.saml.mappers.SAMLRoleListMapper;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.ResourceAdminManager;
+import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.admin.ClientAttributeCertificateResource;
 import org.keycloak.services.resources.flows.Flows;
@@ -94,6 +95,7 @@ public class SamlProtocol implements LoginProtocol {
 
     protected UriInfo uriInfo;
 
+    protected HttpHeaders headers;
 
 
     @Override
@@ -115,6 +117,12 @@ public class SamlProtocol implements LoginProtocol {
     }
 
     @Override
+    public SamlProtocol setHttpHeaders(HttpHeaders headers){
+        this.headers = headers;
+        return this;
+    }
+
+    @Override
     public Response cancelLogin(ClientSessionModel clientSession) {
         return getErrorResponse(clientSession, JBossSAMLURIConstants.STATUS_REQUEST_DENIED.get());
     }
@@ -141,7 +149,7 @@ public class SamlProtocol implements LoginProtocol {
               return builder.redirectBinding().response();
           }
         } catch (Exception e) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE );
         }
     }
 
@@ -295,7 +303,7 @@ public class SamlProtocol implements LoginProtocol {
             samlDocument = builder.buildDocument(samlModel);
         } catch (Exception e) {
             logger.error("failed", e);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.FAILED_TO_PROCESS_RESPONSE);
         }
 
         SAML2BindingBuilder2 bindingBuilder = new SAML2BindingBuilder2();
@@ -317,7 +325,7 @@ public class SamlProtocol implements LoginProtocol {
                 publicKey = SamlProtocolUtils.getEncryptionValidationKey(client);
             } catch (Exception e) {
                 logger.error("failed", e);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE);
             }
             bindingBuilder.encrypt(publicKey);
         }
@@ -329,7 +337,7 @@ public class SamlProtocol implements LoginProtocol {
             }
         } catch (Exception e) {
             logger.error("failed", e);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Failed to process response");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.FAILED_TO_PROCESS_RESPONSE );
         }
     }
 
diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
index 8042031..3e7cd2f 100755
--- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
+++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlService.java
@@ -23,6 +23,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.HttpAuthenticationManager;
+import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.RealmsResource;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.util.StreamUtil;
@@ -106,18 +107,18 @@ public class SamlService {
             if (!checkSsl()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.SSL_REQUIRED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED );
             }
             if (!realm.isEnabled()) {
                 event.event(EventType.LOGIN_ERROR);
                 event.error(Errors.REALM_DISABLED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
             }
 
             if (samlRequest == null && samlResponse == null) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_TOKEN);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST );
 
             }
             return null;
@@ -131,7 +132,7 @@ public class SamlService {
             if (!uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
                 event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
                 event.detail(Details.REASON, "invalid_destination");
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request.");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
             }
 
             AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm, uriInfo, clientConnection, headers, false);
@@ -139,7 +140,7 @@ public class SamlService {
                 logger.warn("Unknown saml response.");
                 event.event(EventType.LOGOUT);
                 event.error(Errors.INVALID_TOKEN);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
             }
             // assume this is a logout response
             UserSessionModel userSession = authResult.getSession();
@@ -148,10 +149,10 @@ public class SamlService {
                 logger.warn("UserSession is not tagged as logging out.");
                 event.event(EventType.LOGOUT);
                 event.error(Errors.INVALID_SAML_LOGOUT_RESPONSE);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
             }
             logger.debug("logout response");
-            Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
+            Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
             event.success();
             return response;
         }
@@ -161,7 +162,7 @@ public class SamlService {
             if (documentHolder == null) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_TOKEN);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
             }
 
             SAML2Object samlObject = documentHolder.getSamlObject();
@@ -173,23 +174,23 @@ public class SamlService {
             if (client == null) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.CLIENT_NOT_FOUND);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
             }
 
             if (!client.isEnabled()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.CLIENT_DISABLED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
             }
             if ((client instanceof ApplicationModel) && ((ApplicationModel)client).isBearerOnly()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.NOT_ALLOWED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Bearer-only applications are not allowed to initiate browser login");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.BEARER_ONLY);
             }
             if (client.isDirectGrantsOnly()) {
                 event.event(EventType.LOGIN);
                 event.error(Errors.NOT_ALLOWED);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "direct-grants-only clients are not allowed to initiate browser login");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.DIRECT_GRANTS_ONLY );
             }
 
             try {
@@ -198,7 +199,7 @@ public class SamlService {
                 SamlService.logger.error("request validation failed", e);
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_SIGNATURE);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid requester.");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUESTER);
             }
             logger.debug("verified request");
             if (samlObject instanceof AuthnRequestType) {
@@ -216,7 +217,7 @@ public class SamlService {
             } else {
                 event.event(EventType.LOGIN);
                 event.error(Errors.INVALID_TOKEN);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid Request");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
             }
         }
 
@@ -230,7 +231,7 @@ public class SamlService {
             if (!uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
                 event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
                 event.detail(Details.REASON, "invalid_destination");
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request.");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
             }
             String bindingType = getBindingType(requestAbstractType);
             if ("true".equals(client.getAttribute(SamlProtocol.SAML_FORCE_POST_BINDING))) bindingType = SamlProtocol.SAML_POST_BINDING;
@@ -252,7 +253,7 @@ public class SamlService {
 
             if (redirect == null) {
                 event.error(Errors.INVALID_REDIRECT_URI);
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect_uri.");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI );
             }
 
 
@@ -275,7 +276,7 @@ public class SamlService {
                 } else {
                     event.error(Errors.INVALID_SAML_AUTHN_REQUEST);
                     event.detail(Details.REASON, "unsupported_nameid_format");
-                    return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unsupported NameIDFormat.");
+                    return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNSUPPORTED_NAME_ID_FORMAT);
                 }
             }
 
@@ -284,10 +285,10 @@ public class SamlService {
 
             // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?)
             HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event);
-            HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate();
+            HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers);
             if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
 
-            LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
+            LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
                     .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode());
 
             // Attach state from SPNEGO authentication
@@ -339,7 +340,7 @@ public class SamlService {
             if (!uriInfo.getAbsolutePath().equals(logoutRequest.getDestination())) {
                 event.error(Errors.INVALID_SAML_LOGOUT_REQUEST);
                 event.detail(Details.REASON, "invalid_destination");
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid request.");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REQUEST);
             }
 
             // authenticate identity cookie, but ignore an access token timeout as we're logging out anyways.
@@ -366,7 +367,7 @@ public class SamlService {
                     }
                 }
                 logger.debug("browser Logout");
-                return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
+                return authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
             }
 
 
@@ -379,7 +380,7 @@ public class SamlService {
             if (redirectUri != null) {
                 redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUri, realm, client);
                 if (redirectUri == null) {
-                    return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
+                    return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI );
                 }
             }
             if (redirectUri != null) {
@@ -391,7 +392,7 @@ public class SamlService {
         }
 
         private Response logout(UserSessionModel userSession) {
-            Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection);
+            Response response = authManager.browserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
             if (response == null) event.user(userSession.getUser()).session(userSession).success();
             return response;
         }
diff --git a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
index 7bd4d03..0290771 100755
--- a/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/LoginProtocol.java
@@ -7,6 +7,7 @@ import org.keycloak.models.UserSessionModel;
 import org.keycloak.provider.Provider;
 import org.keycloak.services.managers.ClientSessionCode;
 
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
@@ -21,6 +22,8 @@ public interface LoginProtocol extends Provider {
 
     LoginProtocol setUriInfo(UriInfo uriInfo);
 
+    LoginProtocol setHttpHeaders(HttpHeaders headers);
+
     Response cancelLogin(ClientSessionModel clientSession);
     Response invalidSessionError(ClientSessionModel clientSession);
     Response authenticated(UserSessionModel userSession, ClientSessionCode accessCode);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
index 6f6441f..708fb21 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/AuthorizationEndpoint.java
@@ -24,6 +24,7 @@ import org.keycloak.services.ErrorPageException;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.HttpAuthenticationManager;
+import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.Urls;
 
@@ -116,7 +117,7 @@ public class AuthorizationEndpoint {
         action = Action.REGISTER;
 
         if (!realm.isRegistrationAllowed()) {
-            throw new ErrorPageException(session, realm, uriInfo, "Registration not allowed");
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
         }
 
         return this;
@@ -148,21 +149,21 @@ public class AuthorizationEndpoint {
     private void checkSsl() {
         if (!uriInfo.getBaseUri().getScheme().equals("https") && realm.getSslRequired().isRequired(clientConnection)) {
             event.error(Errors.SSL_REQUIRED);
-            throw new ErrorPageException(session, realm, uriInfo, "HTTPS required");
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
         }
     }
 
     private void checkRealm() {
         if (!realm.isEnabled()) {
             event.error(Errors.REALM_DISABLED);
-            throw new ErrorPageException(session, realm, uriInfo, "Realm not enabled");
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
         }
     }
 
     private void checkClient() {
         if (clientId == null) {
             event.error(Errors.INVALID_REQUEST);
-            throw new ErrorPageException(session, realm, uriInfo, "Missing paramater: " + OIDCLoginProtocol.CLIENT_ID_PARAM);
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.MISSING_PARAMETER, OIDCLoginProtocol.CLIENT_ID_PARAM );
         }
 
         event.client(clientId);
@@ -170,17 +171,17 @@ public class AuthorizationEndpoint {
         client = realm.findClient(clientId);
         if (client == null) {
             event.error(Errors.CLIENT_NOT_FOUND);
-            throw new ErrorPageException(session, realm, uriInfo, "Client not found");
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.CLIENT_NOT_FOUND );
         }
 
         if ((client instanceof ApplicationModel) && ((ApplicationModel) client).isBearerOnly()) {
             event.error(Errors.NOT_ALLOWED);
-            throw new ErrorPageException(session, realm, uriInfo, "Bearer only clients are not allowed to initiate browser login");
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.BEARER_ONLY );
         }
 
         if (client.isDirectGrantsOnly()) {
             event.error(Errors.NOT_ALLOWED);
-            throw new ErrorPageException(session, realm, uriInfo, "Direct grants only clients are not allowed to initiate browser login");
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.DIRECT_GRANTS_ONLY);
         }
     }
 
@@ -190,7 +191,7 @@ public class AuthorizationEndpoint {
                 responseType = legacyResponseType;
             } else {
                 event.error(Errors.INVALID_REQUEST);
-                throw new ErrorPageException(session, realm, uriInfo, "Missing query parameter: " + OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+                throw new ErrorPageException(session, realm, uriInfo, headers, Messages.MISSING_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
             }
         }
 
@@ -200,7 +201,7 @@ public class AuthorizationEndpoint {
             action = Action.CODE;
         } else {
             event.error(Errors.INVALID_REQUEST);
-            throw new ErrorPageException(session, realm, uriInfo, "Invalid " + OIDCLoginProtocol.RESPONSE_TYPE_PARAM);
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.INVALID_PARAMETER, OIDCLoginProtocol.RESPONSE_TYPE_PARAM );
         }
     }
 
@@ -210,7 +211,7 @@ public class AuthorizationEndpoint {
         redirectUri = RedirectUtils.verifyRedirectUri(uriInfo, redirectUriParam, realm, client);
         if (redirectUri == null) {
             event.error(Errors.INVALID_REDIRECT_URI);
-            throw new ErrorPageException(session, realm, uriInfo, "Invalid " + OIDCLoginProtocol.REDIRECT_URI_PARAM);
+            throw new ErrorPageException(session, realm, uriInfo, headers, Messages.INVALID_PARAMETER, OIDCLoginProtocol.REDIRECT_URI_PARAM);
         }
     }
 
@@ -237,8 +238,8 @@ public class AuthorizationEndpoint {
             IdentityProviderModel identityProviderModel = realm.getIdentityProviderById(idpHint);
 
             if (identityProviderModel == null) {
-                return Flows.forms(session, realm, null, uriInfo)
-                        .setError("Could not find an identity provider with the identifier [" + idpHint + "].")
+                return Flows.forms(session, realm, null, uriInfo, headers)
+                        .setError(Messages.IDENTITY_PROVIDER_NOT_FOUND, idpHint)
                         .createErrorPage();
             }
             return buildRedirectToIdentityProvider(idpHint, accessCode);
@@ -249,11 +250,11 @@ public class AuthorizationEndpoint {
 
         // SPNEGO/Kerberos authentication TODO: This should be somehow pluggable instead of hardcoded this way (Authentication interceptors?)
         HttpAuthenticationManager httpAuthManager = new HttpAuthenticationManager(session, clientSession, realm, uriInfo, request, clientConnection, event);
-        HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate();
+        HttpAuthenticationManager.HttpAuthOutput httpAuthOutput = httpAuthManager.spnegoAuthenticate(headers);
         if (httpAuthOutput.getResponse() != null) return httpAuthOutput.getResponse();
 
         if (prompt != null && prompt.equals("none")) {
-            OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo);
+            OIDCLoginProtocol oauth = new OIDCLoginProtocol(session, realm, uriInfo, headers);
             return oauth.cancelLogin(clientSession);
         }
 
@@ -271,13 +272,13 @@ public class AuthorizationEndpoint {
                     return buildRedirectToIdentityProvider(identityProviders.get(0).getId(), accessCode);
                 }
 
-                return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + realm.getName() + "] supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.").createErrorPage();
+                return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.IDENTITY_PROVIDER_NOT_UNIQUE, realm.getName()).createErrorPage();
             }
 
-            return Flows.forms(session, realm, null, uriInfo).setError("Realm [" + realm.getName() + "] does not support any credential type.").createErrorPage();
+            return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.REALM_SUPPORTS_NO_CREDENTIALS, realm.getName()).createErrorPage();
         }
 
-        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
+        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
                 .setClientSessionCode(accessCode);
 
         // Attach state from SPNEGO authentication
@@ -306,7 +307,7 @@ public class AuthorizationEndpoint {
     private Response buildRegister() {
         authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
 
-        return Flows.forms(session, realm, client, uriInfo)
+        return Flows.forms(session, realm, client, uriInfo, headers)
                 .setClientSessionCode(new ClientSessionCode(realm, clientSession).getCode())
                 .createRegistration();
     }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
index 16ab80c..a47aa6a 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java
@@ -21,6 +21,7 @@ import org.keycloak.protocol.oidc.utils.RedirectUtils;
 import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.ErrorResponseException;
 import org.keycloak.services.managers.AuthenticationManager;
+import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.Cors;
 import org.keycloak.services.resources.flows.Flows;
 
@@ -91,7 +92,7 @@ public class LogoutEndpoint {
         if (redirectUri != null) {
             String validatedRedirect = RedirectUtils.verifyRealmRedirectUri(uriInfo, redirectUri, realm);
             if (validatedRedirect == null) {
-                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid redirect uri.");
+                return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_REDIRECT_URI);
             }
             return Response.status(302).location(UriBuilder.fromUri(validatedRedirect).build()).build();
         } else {
@@ -143,7 +144,7 @@ public class LogoutEndpoint {
     }
 
     private void logout(UserSessionModel userSession) {
-        authManager.logout(session, realm, userSession, uriInfo, clientConnection);
+        authManager.logout(session, realm, userSession, uriInfo, clientConnection, headers);
         event.user(userSession.getUser()).session(userSession).success();
     }
 
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
index ab78e6c..730e9be 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java
@@ -276,7 +276,7 @@ public class TokenEndpoint {
 
         AccessTokenResponse res;
         try {
-            res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event);
+            res = tokenManager.refreshAccessToken(session, uriInfo, clientConnection, realm, client, refreshToken, event, headers);
         } catch (OAuthErrorException e) {
             event.error(Errors.INVALID_TOKEN);
             throw new ErrorResponseException(e.getError(), e.getDescription(), Response.Status.BAD_REQUEST);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java
index caef436..8dad6fd 100644
--- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/ValidateTokenEndpoint.java
@@ -20,10 +20,7 @@ import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.core.*;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -43,6 +40,9 @@ public class ValidateTokenEndpoint {
     @Context
     private UriInfo uriInfo;
 
+    @Context
+    private HttpHeaders headers;
+
     private TokenManager tokenManager;
     private RealmModel realm;
     private EventBuilder event;
@@ -81,7 +81,7 @@ public class ValidateTokenEndpoint {
         event.user(token.getSubject()).session(token.getSessionState()).detail(Details.VALIDATE_ACCESS_TOKEN, token.getId());
 
         try {
-            tokenManager.validateToken(session, uriInfo, clientConnection, realm, token);
+            tokenManager.validateToken(session, uriInfo, clientConnection, realm, token, headers);
         } catch (OAuthErrorException e) {
             Map<String, String> error = new HashMap<String, String>();
             error.put(OAuth2Constants.ERROR, e.getError());
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
index 3900f1a..433acd7 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocol.java
@@ -33,6 +33,7 @@ import org.keycloak.protocol.LoginProtocol;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.ResourceAdminManager;
 
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriBuilder;
 import javax.ws.rs.core.UriInfo;
@@ -63,13 +64,17 @@ public class OIDCLoginProtocol implements LoginProtocol {
 
     protected UriInfo uriInfo;
 
-    public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo) {
+    protected HttpHeaders headers;
+
+    public OIDCLoginProtocol(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
         this.session = session;
         this.realm = realm;
         this.uriInfo = uriInfo;
+        this.headers = headers;
     }
 
-    public OIDCLoginProtocol() {
+    public OIDCLoginProtocol(){
+
     }
 
     @Override
@@ -91,6 +96,12 @@ public class OIDCLoginProtocol implements LoginProtocol {
     }
 
     @Override
+    public OIDCLoginProtocol setHttpHeaders(HttpHeaders headers){
+        this.headers = headers;
+        return this;
+    }
+
+    @Override
     public Response cancelLogin(ClientSessionModel clientSession) {
         String redirect = clientSession.getRedirectUri();
         String state = clientSession.getNote(OIDCLoginProtocol.STATE_PARAM);
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
index c2ec74b..fd4b3b1 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCLoginProtocolService.java
@@ -67,6 +67,9 @@ public class OIDCLoginProtocolService {
     @Context
     private KeycloakSession session;
 
+    @Context
+    private HttpHeaders headers;
+
     public OIDCLoginProtocolService(RealmModel realm, EventBuilder event, AuthenticationManager authManager) {
         this.realm = realm;
         this.tokenManager = new TokenManager();
@@ -226,7 +229,7 @@ public class OIDCLoginProtocolService {
     @Path("oauth/oob")
     @GET
     public Response installedAppUrnCallback(final @QueryParam("code") String code, final @QueryParam("error") String error, final @QueryParam("error_description") String errorDescription) {
-        LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo);
+        LoginFormsProvider forms = Flows.forms(session, realm, null, uriInfo, headers);
         if (code != null) {
             return forms.setClientSessionCode(code).createCode();
         } else {
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 07fd828..da9fdd8 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -30,6 +30,7 @@ import org.keycloak.representations.RefreshToken;
 import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.util.Time;
 
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.UriInfo;
 import java.io.IOException;
 import java.util.HashSet;
@@ -73,7 +74,7 @@ public class TokenManager {
         }
     }
 
-    public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken) throws OAuthErrorException {
+    public TokenValidation validateToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, AccessToken oldToken, HttpHeaders headers) throws OAuthErrorException {
         UserModel user = session.users().getUserById(oldToken.getSubject(), realm);
         if (user == null) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token", "Unknown user");
@@ -85,7 +86,7 @@ public class TokenManager {
 
         UserSessionModel userSession = session.sessions().getUserSession(realm, oldToken.getSessionState());
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.logout(session, realm, userSession, uriInfo, connection);
+            AuthenticationManager.logout(session, realm, userSession, uriInfo, connection, headers);
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Session not active", "Session not active");
         }
         ClientSessionModel clientSession = null;
@@ -124,12 +125,12 @@ public class TokenManager {
 
     }
 
-    public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event) throws OAuthErrorException {
+    public AccessTokenResponse refreshAccessToken(KeycloakSession session, UriInfo uriInfo, ClientConnection connection, RealmModel realm, ClientModel authorizedClient, String encodedRefreshToken, EventBuilder event, HttpHeaders headers) throws OAuthErrorException {
         RefreshToken refreshToken = verifyRefreshToken(realm, encodedRefreshToken);
 
         event.user(refreshToken.getSubject()).session(refreshToken.getSessionState()).detail(Details.REFRESH_TOKEN_ID, refreshToken.getId());
 
-        TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken);
+        TokenValidation validation = validateToken(session, uriInfo, connection, realm, refreshToken, headers);
         // validate authorizedClient is same as validated client
         if (!validation.clientSession.getClient().getId().equals(authorizedClient.getId())) {
             throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Invalid refresh token. Token client and authorized client don't match");
diff --git a/services/src/main/java/org/keycloak/services/ErrorPageException.java b/services/src/main/java/org/keycloak/services/ErrorPageException.java
index 3f8d435..634ea7a 100644
--- a/services/src/main/java/org/keycloak/services/ErrorPageException.java
+++ b/services/src/main/java/org/keycloak/services/ErrorPageException.java
@@ -5,6 +5,7 @@ import org.keycloak.models.RealmModel;
 import org.keycloak.services.resources.flows.Flows;
 
 import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
@@ -16,18 +17,22 @@ public class ErrorPageException extends WebApplicationException {
     private final KeycloakSession session;
     private final RealmModel realm;
     private final UriInfo uriInfo;
+    private final HttpHeaders httpHeaders;
     private final String errorMessage;
+    private final Object[] parameters;
 
-    public ErrorPageException(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String errorMessage) {
+    public ErrorPageException(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String errorMessage, Object ... parameters) {
         this.session = session;
         this.realm = realm;
         this.uriInfo = uriInfo;
+        this.httpHeaders = headers;
         this.errorMessage = errorMessage;
+        this.parameters = parameters;
     }
 
     @Override
     public Response getResponse() {
-        return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, errorMessage);
+        return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, httpHeaders, errorMessage, parameters);
     }
 
 }
diff --git a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
index e3bb569..9e0e4ab 100755
--- a/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AppAuthManager.java
@@ -42,7 +42,7 @@ public class AppAuthManager extends AuthenticationManager {
     public AuthResult authenticateBearerToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
         String tokenString = extractAuthorizationHeaderToken(headers);
         if (tokenString == null) return null;
-        AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString);
+        AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, true, tokenString, headers);
         return authResult;
     }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index aa27f17..f37b837 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -79,7 +79,7 @@ public class AuthenticationManager {
         return userSession != null && userSession.getLastSessionRefresh() + realm.getSsoSessionIdleTimeout() > currentTime && max > currentTime;
     }
 
-    public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) {
+    public static void logout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
         if (userSession == null) return;
         UserModel user = userSession.getUser();
         userSession.setState(UserSessionModel.State.LOGGING_OUT);
@@ -95,6 +95,7 @@ public class AuthenticationManager {
                 if (authMethod == null) continue; // must be a keycloak service like account
                 LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
                 protocol.setRealm(realm)
+                        .setHttpHeaders(headers)
                         .setUriInfo(uriInfo);
                 protocol.backchannelLogout(userSession, clientSession);
                 clientSession.setAction(ClientSessionModel.Action.LOGGED_OUT);
@@ -105,7 +106,7 @@ public class AuthenticationManager {
     }
 
 
-    public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) {
+    public static Response browserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
         if (userSession == null) return null;
         UserModel user = userSession.getUser();
 
@@ -128,6 +129,7 @@ public class AuthenticationManager {
                 if (authMethod == null) continue; // must be a keycloak service like account
                 LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
                 protocol.setRealm(realm)
+                        .setHttpHeaders(headers)
                         .setUriInfo(uriInfo);
                 try {
                     logger.debugv("backchannel logout to: {0}", client.getClientId());
@@ -140,12 +142,13 @@ public class AuthenticationManager {
         }
 
         if (redirectClients.size() == 0) {
-            return finishBrowserLogout(session, realm, userSession, uriInfo, connection);
+            return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers);
         }
         for (ClientSessionModel nextRedirectClient : redirectClients) {
             String authMethod = nextRedirectClient.getAuthMethod();
             LoginProtocol protocol = session.getProvider(LoginProtocol.class, authMethod);
             protocol.setRealm(realm)
+                    .setHttpHeaders(headers)
                     .setUriInfo(uriInfo);
             // setting this to logged out cuz I"m not sure protocols can always verify that the client was logged out or not
             nextRedirectClient.setAction(ClientSessionModel.Action.LOGGED_OUT);
@@ -161,16 +164,17 @@ public class AuthenticationManager {
             }
 
         }
-        return finishBrowserLogout(session, realm, userSession, uriInfo, connection);
+        return finishBrowserLogout(session, realm, userSession, uriInfo, connection, headers);
     }
 
-    protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection) {
+    protected static Response finishBrowserLogout(KeycloakSession session, RealmModel realm, UserSessionModel userSession, UriInfo uriInfo, ClientConnection connection, HttpHeaders headers) {
         expireIdentityCookie(realm, uriInfo, connection);
         expireRememberMeCookie(realm, uriInfo, connection);
         userSession.setState(UserSessionModel.State.LOGGED_OUT);
         String method = userSession.getNote(KEYCLOAK_LOGOUT_PROTOCOL);
         LoginProtocol protocol = session.getProvider(LoginProtocol.class, method);
         protocol.setRealm(realm)
+                .setHttpHeaders(headers)
                 .setUriInfo(uriInfo);
         Response response = protocol.finishLogout(userSession);
         session.sessions().removeUserSession(realm, userSession);
@@ -285,7 +289,7 @@ public class AuthenticationManager {
         }
 
         String tokenString = cookie.getValue();
-        AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString);
+        AuthResult authResult = verifyIdentityToken(session, realm, uriInfo, connection, checkActive, tokenString, headers);
         if (authResult == null) {
             expireIdentityCookie(realm, uriInfo, connection);
             return null;
@@ -335,6 +339,7 @@ public class AuthenticationManager {
         if (userSession.isRememberMe()) createRememberMeCookie(realm, userSession.getUser().getUsername(), uriInfo, clientConnection);
         LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
         protocol.setRealm(realm)
+                .setHttpHeaders(request.getHttpHeaders())
                 .setUriInfo(uriInfo);
         return protocol.authenticated(userSession, new ClientSessionCode(realm, clientSession));
 
@@ -364,7 +369,7 @@ public class AuthenticationManager {
             UserModel.RequiredAction action = user.getRequiredActions().iterator().next();
             accessCode.setRequiredAction(action);
 
-            LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo).setClientSessionCode(accessCode.getCode()).setUser(user);
+            LoginFormsProvider loginFormsProvider = Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders()).setClientSessionCode(accessCode.getCode()).setUser(user);
             if (action.equals(UserModel.RequiredAction.VERIFY_EMAIL)) {
                 event.clone().event(EventType.SEND_VERIFY_EMAIL).detail(Details.EMAIL, user.getEmail()).success();
                 LoginActionsService.createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
@@ -387,7 +392,7 @@ public class AuthenticationManager {
                 }
             }
 
-            return Flows.forms(session, realm, client, uriInfo)
+            return Flows.forms(session, realm, client, uriInfo, request.getHttpHeaders())
                     .setClientSessionCode(accessCode.getCode())
                     .setAccessRequest(realmRoles, resourceRoles)
                     .setClient(client)
@@ -415,7 +420,7 @@ public class AuthenticationManager {
         }
     }
 
-    protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString) {
+    protected AuthResult verifyIdentityToken(KeycloakSession session, RealmModel realm, UriInfo uriInfo, ClientConnection connection, boolean checkActive, String tokenString, HttpHeaders headers) {
         try {
             AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive);
             if (checkActive) {
@@ -435,7 +440,7 @@ public class AuthenticationManager {
 
             UserSessionModel userSession = session.sessions().getUserSession(realm, token.getSessionState());
             if (!isSessionValid(realm, userSession)) {
-                if (userSession != null) logout(session, realm, userSession, uriInfo, connection);
+                if (userSession != null) logout(session, realm, userSession, uriInfo, connection, headers);
                 logger.debug("User session not active");
                 return null;
             }
diff --git a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
index 7dca9c1..702b684 100644
--- a/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/HttpAuthenticationManager.java
@@ -58,7 +58,7 @@ public class HttpAuthenticationManager {
     }
 
 
-    public HttpAuthOutput spnegoAuthenticate() {
+    public HttpAuthOutput spnegoAuthenticate(HttpHeaders headers) {
         boolean kerberosSupported = false;
         for (RequiredCredentialModel c : realm.getRequiredCredentials()) {
             if (c.getType().equals(CredentialRepresentation.KERBEROS)) {
@@ -96,7 +96,7 @@ public class HttpAuthenticationManager {
             CredentialValidationOutput output = session.users().validCredentials(realm, spnegoCredential);
 
             if (output.getAuthStatus() == CredentialValidationOutput.Status.AUTHENTICATED) {
-                return sendResponse(output.getAuthenticatedUser(), output.getState(), "spnego");
+                return sendResponse(output.getAuthenticatedUser(), output.getState(), "spnego", headers);
             }  else {
                 String spnegoResponseToken = (String) output.getState().get(KerberosConstants.RESPONSE_TOKEN);
                 return challengeNegotiation(spnegoResponseToken);
@@ -106,7 +106,7 @@ public class HttpAuthenticationManager {
 
 
     // Send response after successful authentication
-    private HttpAuthOutput sendResponse(UserModel user, Map<String, String> authState, String authMethod) {
+    private HttpAuthOutput sendResponse(UserModel user, Map<String, String> authState, String authMethod, HttpHeaders headers) {
         if (logger.isTraceEnabled()) {
             logger.trace("User " + user.getUsername() + " authenticated with " + authMethod);
         }
@@ -114,7 +114,7 @@ public class HttpAuthenticationManager {
         Response response;
         if (!user.isEnabled()) {
             event.error(Errors.USER_DISABLED);
-            response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, Messages.ACCOUNT_DISABLED);
+            response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.ACCOUNT_DISABLED);
         } else {
             UserSessionModel userSession = session.sessions().createUserSession(realm, user, user.getUsername(), clientConnection.getRemoteAddr(), authMethod, false);
 
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index 0ad0352..a1cc072 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -26,67 +26,151 @@ package org.keycloak.services.messages;
  */
 public class Messages {
 
-    public static final String ACCOUNT_DISABLED = "accountDisabled";
-    public static final String ACCOUNT_TEMPORARILY_DISABLED = "accountTemporarilyDisabled";
+    public static final String INVALID_USER = "invalidUserMessage";
 
-    public static final String INVALID_PASSWORD = "invalidPassword";
+    public static final String INVALID_EMAIL = "invalidEmailMessage";
 
-    public static final String INVALID_PASSWORD_EXISTING = "invalidPasswordExisting";
+    public static final String ACCOUNT_DISABLED = "accountDisabledMessage";
 
-    public static final String INVALID_PASSWORD_CONFIRM = "invalidPasswordConfirm";
+    public static final String ACCOUNT_TEMPORARILY_DISABLED = "accountTemporarilyDisabledMessage";
 
-    public static final String INVALID_EMAIL = "invalidEmail";
+    public static final String EXPIRED_CODE = "expiredCodeMessage";
 
-    public static final String INVALID_USER = "invalidUser";
+    public static final String MISSING_FIRST_NAME = "missingFirstNameMessage";
 
-    public static final String EXPIRED_CODE = "expiredCode";
+    public static final String MISSING_LAST_NAME = "missingLastNameMessage";
 
-    public static final String READ_ONLY_USER = "readOnlyUser";
+    public static final String MISSING_EMAIL = "missingEmailMessage";
 
-    public static final String READ_ONLY_PASSWORD = "readOnlyPassword";
+    public static final String MISSING_USERNAME = "missingUsernameMessage";
 
-    public static final String MISSING_EMAIL = "missingEmail";
+    public static final String MISSING_PASSWORD = "missingPasswordMessage";
 
-    public static final String MISSING_FIRST_NAME = "missingFirstName";
+    public static final String MISSING_TOTP = "missingTotpMessage";
 
-    public static final String MISSING_LAST_NAME = "missingLastName";
+    public static final String NOTMATCH_PASSWORD = "notMatchPasswordMessage";
 
-    public static final String MISSING_PASSWORD = "missingPassword";
+    public static final String INVALID_PASSWORD_EXISTING = "invalidPasswordExistingMessage";
 
-    public static final String NOTMATCH_PASSWORD = "notMatchPassword";
+    public static final String INVALID_PASSWORD_CONFIRM = "invalidPasswordConfirmMessage";
 
-    public static final String MISSING_USERNAME = "missingUsername";
+    public static final String INVALID_TOTP = "invalidTotpMessage";
 
-    public static final String MISSING_TOTP = "missingTotp";
+    public static final String USERNAME_EXISTS = "usernameExistsMessage";
 
-    public static final String INVALID_TOTP = "invalidTotp";
+    public static final String EMAIL_EXISTS = "emailExistsMessage";
 
-    public static final String USERNAME_EXISTS = "usernameExists";
+    public static final String FEDERATED_IDENTITY_EMAIL_EXISTS = "federatedIdentityEmailExistsMessage";
 
-    public static final String EMAIL_EXISTS = "emailExists";
+    public static final String FEDERATED_IDENTITY_USERNAME_EXISTS = "federatedIdentityUsernameExistsMessage";
 
-    public static final String ACTION_WARN_TOTP = "actionTotpWarning";
+    public static final String CONFIGURE_TOTP = "configureTotpMessage";
 
-    public static final String ACTION_WARN_PROFILE = "actionProfileWarning";
+    public static final String UPDATE_PROFILE = "updateProfileMessage";
 
-    public static final String ACTION_WARN_PASSWD = "actionPasswordWarning";
+    public static final String UPDATE_PASSWORD = "updatePasswordMessage";
 
-    public static final String ACTION_WARN_EMAIL = "actionEmailWarning";
+    public static final String VERIFY_EMAIL = "verifyEmailMessage";
 
-    public static final String MISSING_IDENTITY_PROVIDER = "missingIdentityProvider";
+    public static final String EMAIL_VERIFIED = "emailVerifiedMessage";
 
-    public static final String INVALID_FEDERATED_IDENTITY_ACTION = "invalidFederatedIdentityAction";
+    public static final String EMAIL_SENT = "emailSentMessage";
 
-    public static final String IDENTITY_PROVIDER_NOT_FOUND = "identityProviderNotFound";
+    public static final String EMAIL_SENT_ERROR = "emailSendErrorMessage";
 
-    public static final String FEDERATED_IDENTITY_NOT_ACTIVE = "federatedIdentityLinkNotActive";
+    public static final String ACCOUNT_UPDATED = "accountUpdatedMessage";
 
-    public static final String FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER = "federatedIdentityRemovingLastProvider";
+    public static final String ACCOUNT_PASSWORD_UPDATED = "accountPasswordUpdatedMessage";
 
-    public static final String IDENTITY_PROVIDER_REDIRECT_ERROR = "identityProviderRedirectError";
+    public static final String NO_ACCESS = "noAccessMessage";
 
-    public static final String IDENTITY_PROVIDER_REMOVED = "identityProviderRemoved";
+    public static final String FAILED_TO_PROCESS_RESPONSE = "failedToProcessResponseMessage";
 
-    public static final String ERROR = "error";
+    public static final String HTTPS_REQUIRED = "httpsRequiredMessage";
 
+    public static final String REALM_NOT_ENABLED = "realmNotEnabledMessage";
+
+    public static final String INVALID_REQUEST = "invalidRequestMessage";
+
+    public static final String INVALID_REQUESTER = "invalidRequesterMessage";
+
+    public static final String UNKNOWN_LOGIN_REQUESTER = "unknownLoginRequesterMessage";
+
+    public static final String LOGIN_REQUESTER_NOT_ENABLED = "loginRequesterNotEnabledMessage";
+
+    public static final String BEARER_ONLY = "bearerOnlyMessage";
+
+    public static final String DIRECT_GRANTS_ONLY = "directGrantsOnlyMessage";
+
+    public static final String INVALID_REDIRECT_URI = "invalidRedirectUriMessage";
+
+    public static final String UNSUPPORTED_NAME_ID_FORMAT = "unsupportedNameIdFormatMessage";
+
+    public static final String REGISTRATION_NOT_ALLOWED = "registrationNotAllowedMessage";
+
+    public static final String PERMISSION_NOT_APPROVED = "permissionNotApprovedMessage";
+
+    public static final String NO_RELAY_STATE_IN_RESPONSE = "noRelayStateInResponseMessage";
+
+    public static final String IDENTITY_PROVIDER_ALREADY_LINKED = "identityProviderAlreadyLinkedMessage";
+
+    public static final String INSUFFICIENT_PERMISSION = "insufficientPermissionMessage";
+
+    public static final String COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST = "couldNotProceedWithAuthenticationRequestMessage";
+
+    public static final String COULD_NOT_OBTAIN_TOKEN = "couldNotObtainTokenMessage";
+
+    public static final String UNEXPECTED_ERROR_RETRIEVING_TOKEN = "unexpectedErrorRetrievingTokenMessage";
+
+    public static final String IDENTITY_PROVIDER_AUTHENTICATION_FAILED = "identityProviderAuthenticationFailedMessage";
+
+    public static final String UNEXPECTED_ERROR_HANDLING_RESPONSE = "unexpectedErrorHandlingResponseMessage";
+
+    public static final String COULD_NOT_SEND_AUTHENTICATION_REQUEST = "couldNotSendAuthenticationRequestMessage";
+
+    public static final String UNEXPECTED_ERROR_HANDLING_REQUEST = "unexpectedErrorHandlingRequestMessage";
+
+    public static final String INVALID_ACCESS_CODE = "invalidAccessCodeMessage";
+
+    public static final String SESSION_NOT_ACTIVE = "sessionNotActiveMessage";
+
+    public static final String UNKNOWN_CODE = "unknownCodeMessage";
+
+    public static final String INVALID_CODE = "invalidCodeMessage";
+
+    public static final String IDENTITY_PROVIDER_UNEXPECTED_ERROR = "identityProviderUnexpectedErrorMessage";
+
+    public static final String IDENTITY_PROVIDER_NOT_FOUND = "identityProviderNotFoundMessage";
+
+    public static final String IDENTITY_PROVIDER_NOT_UNIQUE = "identityProviderNotUniqueMessage";
+
+    public static final String REALM_SUPPORTS_NO_CREDENTIALS = "realmSupportsNoCredentialsMessage";
+
+    public static final String READ_ONLY_USER = "readOnlyUserMessage";
+
+    public static final String READ_ONLY_PASSWORD = "readOnlyPasswordMessage";
+
+    public static final String SUCCESS_TOTP_REMOVED = "successTotpRemovedMessage";
+
+    public static final String SUCCESS_TOTP = "successTotpMessage";
+
+    public static final String MISSING_IDENTITY_PROVIDER = "missingIdentityProviderMessage";
+
+    public static final String INVALID_FEDERATED_IDENTITY_ACTION = "invalidFederatedIdentityActionMessage";
+
+    public static final String FEDERATED_IDENTITY_NOT_ACTIVE = "federatedIdentityLinkNotActiveMessage";
+
+    public static final String FEDERATED_IDENTITY_REMOVING_LAST_PROVIDER = "federatedIdentityRemovingLastProviderMessage";
+
+    public static final String IDENTITY_PROVIDER_REDIRECT_ERROR = "identityProviderRedirectErrorMessage";
+
+    public static final String IDENTITY_PROVIDER_REMOVED = "identityProviderRemovedMessage";
+
+    public static final String MISSING_PARAMETER = "missingParameterMessage";
+
+    public static final String CLIENT_NOT_FOUND = "clientNotFoundMessage";
+
+    public static final String INVALID_PARAMETER = "invalidParameterMessage";
+
+    public static final String FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING = "federatedIdentityRegistrationEmailMissingMessage";
 }
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 8b5297b..1efc33a 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -32,20 +32,7 @@ import org.keycloak.events.Event;
 import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventStoreProvider;
 import org.keycloak.events.EventType;
-import org.keycloak.models.AccountRoles;
-import org.keycloak.models.ApplicationModel;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.Constants;
-import org.keycloak.models.FederatedIdentityModel;
-import org.keycloak.models.IdentityProviderModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.ModelReadOnlyException;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserCredentialValueModel;
-import org.keycloak.models.UserModel;
-import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.*;
 import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.protocol.oidc.OIDCLoginProtocol;
@@ -160,7 +147,7 @@ public class AccountService {
     public void init() {
         eventStore = session.getProvider(EventStoreProvider.class);
 
-        account = session.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo);
+        account = session.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo).setHttpHeaders(headers);
 
         AuthenticationManager.AuthResult authResult = authManager.authenticateBearerToken(session, realm, uriInfo, clientConnection, headers);
         if (authResult != null) {
@@ -242,7 +229,7 @@ public class AccountService {
             try {
                 require(AccountRoles.MANAGE_ACCOUNT);
             } catch (ForbiddenException e) {
-                return Flows.forms(session, realm, null, uriInfo).setError("No access").createErrorPage();
+                return Flows.forms(session, realm, null, uriInfo, headers).setError(Messages.NO_ACCESS).createErrorPage();
             }
 
             setReferrerOnPage();
@@ -442,7 +429,7 @@ public class AccountService {
                 event.clone().event(EventType.UPDATE_EMAIL).detail(Details.PREVIOUS_EMAIL, oldEmail).detail(Details.UPDATED_EMAIL, email).success();
             }
             setReferrerOnPage();
-            return account.setSuccess("accountUpdated").createResponse(AccountPages.ACCOUNT);
+            return account.setSuccess(Messages.ACCOUNT_UPDATED).createResponse(AccountPages.ACCOUNT);
         } catch (ModelReadOnlyException roe) {
             setReferrerOnPage();
             return account.setError(Messages.READ_ONLY_USER).setProfileFormData(formData).createResponse(AccountPages.ACCOUNT);
@@ -466,7 +453,7 @@ public class AccountService {
         event.event(EventType.REMOVE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
 
         setReferrerOnPage();
-        return account.setSuccess("successTotpRemoved").createResponse(AccountPages.TOTP);
+        return account.setSuccess(Messages.SUCCESS_TOTP_REMOVED).createResponse(AccountPages.TOTP);
     }
 
 
@@ -545,7 +532,7 @@ public class AccountService {
         event.event(EventType.UPDATE_TOTP).client(auth.getClient()).user(auth.getUser()).success();
 
         setReferrerOnPage();
-        return account.setSuccess("successTotp").createResponse(AccountPages.TOTP);
+        return account.setSuccess(Messages.SUCCESS_TOTP).createResponse(AccountPages.TOTP);
     }
 
     /**
@@ -614,7 +601,11 @@ public class AccountService {
         } catch (ModelReadOnlyException mre) {
             setReferrerOnPage();
             return account.setError(Messages.READ_ONLY_PASSWORD).createResponse(AccountPages.PASSWORD);
-        } catch (Exception ape) {
+        }catch (ModelException me) {
+            logger.error("Failed to update password", me);
+            setReferrerOnPage();
+            return account.setError(me.getMessage(), me.getParameters()).createResponse(AccountPages.PASSWORD);
+        }catch (Exception ape) {
             logger.error("Failed to update password", ape);
             setReferrerOnPage();
             return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
@@ -631,7 +622,7 @@ public class AccountService {
         event.event(EventType.UPDATE_PASSWORD).client(auth.getClient()).user(auth.getUser()).success();
 
         setReferrerOnPage();
-        return account.setPasswordSet(true).setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
+        return account.setPasswordSet(true).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createResponse(AccountPages.PASSWORD);
     }
 
     @Path("federated-identity-update")
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
index 8c9e4d4..3d0697d 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Flows.java
@@ -26,6 +26,7 @@ import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 
+import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
 
@@ -37,16 +38,16 @@ public class Flows {
     private Flows() {
     }
 
-    public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo) {
-        return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client);
+    public static LoginFormsProvider forms(KeycloakSession session, RealmModel realm, ClientModel client, UriInfo uriInfo, HttpHeaders headers) {
+        return session.getProvider(LoginFormsProvider.class).setRealm(realm).setUriInfo(uriInfo).setClient(client).setHttpHeaders(headers);
     }
 
     public static ErrorFlows errors() {
         return new ErrorFlows();
     }
 
-    public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, String message) {
-        return Flows.forms(session, realm, null, uriInfo).setError(message).createErrorPage();
+    public static Response forwardToSecurityFailurePage(KeycloakSession session, RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String message, Object ... parameters) {
+        return Flows.forms(session, realm, null, uriInfo, headers).setError(message,parameters).createErrorPage();
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
index 7889388..ab56f4d 100755
--- a/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
+++ b/services/src/main/java/org/keycloak/services/resources/flows/Urls.java
@@ -190,6 +190,10 @@ public class Urls {
         return requiredActionsBase(baseUri).path(LoginActionsService.class, "processConsent").build(realmId);
     }
 
+    public static String localeCookiePath(URI baseUri, String realmName){
+        return realmBase(baseUri).path(realmName).build().getRawPath();
+    }
+
     public static URI themeRoot(URI baseUri) {
         return themeBase(baseUri).path(Version.RESOURCES_VERSION).build();
     }
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index c95b618..4666ff4 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -46,6 +46,7 @@ import org.keycloak.services.managers.AuthenticationManager;
 import org.keycloak.services.managers.AuthenticationManager.AuthResult;
 import org.keycloak.services.managers.ClientSessionCode;
 import org.keycloak.services.managers.EventsManager;
+import org.keycloak.services.messages.Messages;
 import org.keycloak.services.resources.flows.Flows;
 import org.keycloak.services.resources.flows.Urls;
 import org.keycloak.social.SocialIdentityProvider;
@@ -57,13 +58,8 @@ import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
+import javax.ws.rs.core.*;
 import javax.ws.rs.core.Response.Status;
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriInfo;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -97,6 +93,10 @@ public class IdentityBrokerService {
 
     @Context
     private HttpRequest request;
+
+    @Context
+    private HttpHeaders headers;
+
     private EventBuilder event;
 
     public IdentityBrokerService(RealmModel realmModel) {
@@ -135,12 +135,12 @@ public class IdentityBrokerService {
                 return response;
             }
         } catch (IdentityBrokerException e) {
-            return redirectToErrorPage("Could not send authentication request to identity provider [" + providerId + "].", e);
+            return redirectToErrorPage(Messages.COULD_NOT_SEND_AUTHENTICATION_REQUEST, e, providerId);
         } catch (Exception e) {
-            return redirectToErrorPage("Unexpected error when handling authentication request to identity provider [" + providerId + "].", e);
+            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_REQUEST, e, providerId);
         }
 
-        return redirectToErrorPage("Could not proceed with authentication request to identity provider.");
+        return redirectToErrorPage(Messages.COULD_NOT_PROCEED_WITH_AUTHENTICATION_REQUEST);
     }
 
     @GET
@@ -191,11 +191,10 @@ public class IdentityBrokerService {
                 }
 
                 if (OAuthClientModel.class.isInstance(clientModel) && !forceRetrieval) {
-                    return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo)
+                    return corsResponse(Flows.forms(this.session, this.realmModel, clientModel, this.uriInfo, headers)
                             .setClientSessionCode(authManager.extractAuthorizationHeaderToken(this.request.getHttpHeaders()))
                             .setAccessRequest("Your information from " + providerId + " identity provider.")
                             .setClient(clientModel)
-                            .setUriInfo(this.uriInfo)
                             .setActionUri(this.uriInfo.getRequestUri())
                             .createOAuthGrant(null), clientModel);
                 }
@@ -220,9 +219,9 @@ public class IdentityBrokerService {
 
             return badRequest("Invalid token.");
         } catch (IdentityBrokerException e) {
-            return redirectToErrorPage("Could not obtain token fron identity provider [" + providerId + "].", e);
+            return redirectToErrorPage(Messages.COULD_NOT_OBTAIN_TOKEN, e, providerId);
         }  catch (Exception e) {
-            return redirectToErrorPage("Unexpected error when retrieving token from identity provider [" + providerId + "].", e);
+            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_RETRIEVING_TOKEN, e, providerId);
         }
     }
 
@@ -232,7 +231,7 @@ public class IdentityBrokerService {
     public Response consentTokenRetrieval(@PathParam("provider_id") String providerId,
                                           MultivaluedMap<String, String> formData) {
         if (formData.containsKey("cancel")) {
-            return redirectToErrorPage("Permission not approved.");
+            return redirectToErrorPage(Messages.PERMISSION_NOT_APPROVED);
         }
 
         return getToken(providerId, true);
@@ -251,7 +250,7 @@ public class IdentityBrokerService {
             String relayState = identityProvider.getRelayState(createAuthenticationRequest(providerId, null));
 
             if (relayState == null) {
-                return redirectToErrorPage("No relay state in response from identity identity [" + providerId + ".");
+                return redirectToErrorPage(Messages.NO_RELAY_STATE_IN_RESPONSE, providerId);
             }
 
             if (isDebugEnabled()) {
@@ -287,10 +286,10 @@ public class IdentityBrokerService {
             return performLocalAuthentication(identity, clientSessionCode);
         } catch (IdentityBrokerException e) {
             rollback();
-            return redirectToErrorPage("Authentication failed. Could not authenticate with identity provider [" + providerId + "].", e);
+            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_AUTHENTICATION_FAILED, e, providerId);
         } catch (Exception e) {
             rollback();
-            return redirectToErrorPage("Unexpected error when handling response from identity provider [" + providerId + "].", e);
+            return redirectToErrorPage(Messages.UNEXPECTED_ERROR_HANDLING_RESPONSE, e, providerId);
         } finally {
             if (this.session.getTransaction().isActive()) {
                 this.session.getTransaction().commit();
@@ -353,7 +352,7 @@ public class IdentityBrokerService {
         this.event.event(EventType.IDENTITY_PROVIDER_ACCCOUNT_LINKING);
 
         if (federatedUser != null) {
-            return redirectToErrorPage("The identity returned by the identity provider [" + providerId + "] is already linked to other user.");
+            return redirectToErrorPage(Messages.IDENTITY_PROVIDER_ALREADY_LINKED, providerId);
         }
 
         UserModel authenticatedUser = clientSession.getUserSession().getUser();
@@ -364,12 +363,12 @@ public class IdentityBrokerService {
 
         if (!authenticatedUser.isEnabled()) {
             fireErrorEvent(Errors.USER_DISABLED);
-            return redirectToErrorPage("User is disabled.");
+            return redirectToErrorPage(Messages.ACCOUNT_DISABLED);
         }
 
         if (!authenticatedUser.hasRole(this.realmModel.getApplicationByName(ACCOUNT_MANAGEMENT_APP).getRole(MANAGE_ACCOUNT))) {
             fireErrorEvent(Errors.NOT_ALLOWED);
-            return redirectToErrorPage("Insufficient permissions to link identities.");
+            return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION);
         }
 
         this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, federatedIdentityModel);
@@ -438,28 +437,28 @@ public class IdentityBrokerService {
         return Urls.identityProviderAuthnResponse(this.uriInfo.getBaseUri(), providerId, this.realmModel.getName()).toString();
     }
 
-    private Response redirectToErrorPage(String message) {
-        return redirectToErrorPage(message, null);
+    private Response redirectToErrorPage(String message, Object ... parameters) {
+        return redirectToErrorPage(message, null, parameters);
     }
 
-    private Response redirectToErrorPage(String message, Throwable throwable) {
+    private Response redirectToErrorPage(String message, Throwable throwable, Object ... parameters) {
         if (message == null) {
-            message = "Unexpected error when authenticating with identity provider";
+            message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
         }
 
         fireErrorEvent(message, throwable);
-        return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, message);
+        return Flows.forwardToSecurityFailurePage(this.session, this.realmModel, this.uriInfo, headers, message, parameters);
     }
 
     private Response redirectToLoginPage(Throwable t, ClientSessionCode clientCode) {
         String message = t.getMessage();
 
         if (message == null) {
-            message = "Unexpected error when authenticating with identity provider";
+            message = Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR;
         }
 
         fireErrorEvent(message);
-        return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo)
+        return Flows.forms(this.session, this.realmModel, clientCode.getClientSession().getClient(), this.uriInfo, headers)
                 .setClientSessionCode(clientCode.getCode())
                 .setError(message)
                 .createLogin();
@@ -535,7 +534,7 @@ public class IdentityBrokerService {
 
         if (existingUser != null) {
             fireErrorEvent(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS);
-            throw new IdentityBrokerException("federatedIdentityEmailExists");
+            throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_EMAIL_EXISTS);
         }
 
         String username = updatedIdentity.getUsername();
@@ -543,7 +542,7 @@ public class IdentityBrokerService {
             username = updatedIdentity.getEmail();
             if (username == null || username.trim().length() == 0) {
                 fireErrorEvent(Errors.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING);
-                throw new IdentityBrokerException("federatedIdentityRegistrationEmailMissing");
+                throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_REGISTRATION_EMAIL_MISSING);
                 // TODO KEYCLOAK-1053 (ask user to enter email address) should be implemented instead of plain exception as better solution for this case
             }
             username = username.trim();
@@ -555,7 +554,7 @@ public class IdentityBrokerService {
 
         if (existingUser != null) {
             fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS);
-            throw new IdentityBrokerException("federatedIdentityUsernameExists");
+            throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_USERNAME_EXISTS);
         }
 
         if (isDebugEnabled()) {
diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
index 0198644..9e9282a 100755
--- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
+++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java
@@ -32,15 +32,8 @@ import org.keycloak.events.EventBuilder;
 import org.keycloak.events.EventType;
 import org.keycloak.jose.jws.JWSBuilder;
 import org.keycloak.login.LoginFormsProvider;
-import org.keycloak.models.ClientModel;
-import org.keycloak.models.ClientSessionModel;
-import org.keycloak.models.KeycloakSession;
-import org.keycloak.models.RealmModel;
-import org.keycloak.models.RequiredCredentialModel;
-import org.keycloak.models.UserCredentialModel;
-import org.keycloak.models.UserModel;
+import org.keycloak.models.*;
 import org.keycloak.models.UserModel.RequiredAction;
-import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.TimeBasedOTP;
 import org.keycloak.protocol.LoginProtocol;
@@ -160,7 +153,7 @@ public class LoginActionsService {
                 return false;
             } else if (!clientCode.isValid(requiredAction)) {
                 event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
                 return false;
             } else {
                 return true;
@@ -172,7 +165,7 @@ public class LoginActionsService {
                 return false;
             } else if (!(clientCode.isValid(requiredAction) || clientCode.isValid(alternativeRequiredAction))) {
                 event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo,headers, Messages.INVALID_CODE);
                 return false;
             } else {
                 return true;
@@ -182,18 +175,18 @@ public class LoginActionsService {
         public boolean check(String code) {
             if (!checkSsl()) {
                 event.error(Errors.SSL_REQUIRED);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
                 return false;
             }
             if (!realm.isEnabled()) {
                 event.error(Errors.REALM_DISABLED);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
                 return false;
             }
             clientCode = ClientSessionCode.parse(code, session, realm);
             if (clientCode == null) {
                 event.error(Errors.INVALID_CODE);
-                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
+                response = Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
                 return false;
             }
             return true;
@@ -224,7 +217,7 @@ public class LoginActionsService {
             clientSession.setAction(ClientSessionModel.Action.AUTHENTICATE);
         }
 
-        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo)
+        LoginFormsProvider forms = Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
                 .setClientSessionCode(clientSessionCode.getCode());
 
         return forms.createLogin();
@@ -242,7 +235,7 @@ public class LoginActionsService {
         event.event(EventType.REGISTER);
         if (!realm.isRegistrationAllowed()) {
             event.error(Errors.REGISTRATION_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
         }
 
         Checks checks = new Checks();
@@ -256,7 +249,7 @@ public class LoginActionsService {
 
         authManager.expireIdentityCookie(realm, uriInfo, clientConnection);
 
-        return Flows.forms(session, realm, clientSession.getClient(), uriInfo)
+        return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers)
                 .setClientSessionCode(clientSessionCode.getCode())
                 .createRegistration();
     }
@@ -276,17 +269,17 @@ public class LoginActionsService {
         event.event(EventType.LOGIN);
         if (!checkSsl()) {
             event.error(Errors.SSL_REQUIRED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
         }
 
         if (!realm.isEnabled()) {
             event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
         }
         ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
         if (clientCode == null) {
             event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
         }
 
         ClientSessionModel clientSession = clientCode.getClientSession();
@@ -295,7 +288,7 @@ public class LoginActionsService {
         if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE) || clientSession.getUserSession() != null) {
             clientCode.setAction(ClientSessionModel.Action.AUTHENTICATE);
             event.client(clientSession.getClient()).error(Errors.EXPIRED_CODE);
-            return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo).setError(Messages.EXPIRED_CODE)
+            return Flows.forms(this.session, realm, clientSession.getClient(), uriInfo, headers).setError(Messages.EXPIRED_CODE)
                     .setClientSessionCode(clientCode.getCode())
                     .createLogin();
         }
@@ -319,17 +312,18 @@ public class LoginActionsService {
         ClientModel client = clientSession.getClient();
         if (client == null) {
             event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
         }
         if (!client.isEnabled()) {
             event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
         }
 
         if (formData.containsKey("cancel")) {
             event.error(Errors.REJECTED_BY_USER);
             LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
             protocol.setRealm(realm)
+                    .setHttpHeaders(headers)
                     .setUriInfo(uriInfo);
             return protocol.cancelLogin(clientSession);
         }
@@ -356,14 +350,14 @@ public class LoginActionsService {
                 return authManager.nextActionAfterAuthentication(session, userSession, clientSession, clientConnection, request, uriInfo, event);
             case ACCOUNT_TEMPORARILY_DISABLED:
                 event.error(Errors.USER_TEMPORARILY_DISABLED);
-                return Flows.forms(this.session, realm, client, uriInfo)
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
                         .setError(Messages.ACCOUNT_TEMPORARILY_DISABLED)
                         .setFormData(formData)
                         .setClientSessionCode(clientCode.getCode())
                         .createLogin();
             case ACCOUNT_DISABLED:
                 event.error(Errors.USER_DISABLED);
-                return Flows.forms(this.session, realm, client, uriInfo)
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
                         .setError(Messages.ACCOUNT_DISABLED)
                         .setClientSessionCode(clientCode.getCode())
                         .setFormData(formData).createLogin();
@@ -373,19 +367,19 @@ public class LoginActionsService {
                 String passwordToken = new JWSBuilder().jsonContent(new PasswordToken(realm.getName(), user.getId())).rsa256(realm.getPrivateKey());
                 formData.add(CredentialRepresentation.PASSWORD_TOKEN, passwordToken);
 
-                return Flows.forms(this.session, realm, client, uriInfo)
+                return Flows.forms(this.session, realm, client, uriInfo, headers)
                         .setFormData(formData)
                         .setClientSessionCode(clientCode.getCode())
                         .createLoginTotp();
             case INVALID_USER:
                 event.error(Errors.USER_NOT_FOUND);
-                return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
                         .setFormData(formData)
                         .setClientSessionCode(clientCode.getCode())
                         .createLogin();
             default:
                 event.error(Errors.INVALID_USER_CREDENTIALS);
-                return Flows.forms(this.session, realm, client, uriInfo).setError(Messages.INVALID_USER)
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.INVALID_USER)
                         .setFormData(formData)
                         .setClientSessionCode(clientCode.getCode())
                         .createLogin();
@@ -407,25 +401,25 @@ public class LoginActionsService {
         event.event(EventType.REGISTER);
         if (!checkSsl()) {
             event.error(Errors.SSL_REQUIRED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
         }
 
         if (!realm.isEnabled()) {
             event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
         }
         if (!realm.isRegistrationAllowed()) {
             event.error(Errors.REGISTRATION_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Registration not allowed");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REGISTRATION_NOT_ALLOWED);
         }
         ClientSessionCode clientCode = ClientSessionCode.parse(code, session, realm);
         if (clientCode == null) {
             event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
         }
         if (!clientCode.isValid(ClientSessionModel.Action.AUTHENTICATE)) {
             event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid code, please login again through your application.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_CODE);
         }
 
         String username = formData.getFirst("username");
@@ -444,17 +438,17 @@ public class LoginActionsService {
 
         if (!realm.isEnabled()) {
             event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
         }
         ClientModel client = clientSession.getClient();
         if (client == null) {
             event.error(Errors.CLIENT_NOT_FOUND);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown login requester.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
         }
 
         if (!client.isEnabled()) {
             event.error(Errors.CLIENT_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Login requester not enabled.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
         }
 
 
@@ -464,15 +458,20 @@ public class LoginActionsService {
         }
 
         // Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
-        String error = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes);
-        if (error == null) {
-            error = Validation.validatePassword(formData, realm.getPasswordPolicy());
+        String errorMessage = Validation.validateRegistrationForm(realm, formData, requiredCredentialTypes);
+        Object[] parameters = new Object[0];
+        if (errorMessage == null) {
+            PasswordPolicy.Error error = Validation.validatePassword(formData, realm.getPasswordPolicy());
+            if(error != null){
+                errorMessage = error.getMessage();
+                parameters = error.getParameters();
+            }
         }
 
-        if (error != null) {
+        if (errorMessage != null) {
             event.error(Errors.INVALID_REGISTRATION);
-            return Flows.forms(session, realm, client, uriInfo)
-                    .setError(error)
+            return Flows.forms(session, realm, client, uriInfo, headers)
+                    .setError(errorMessage, parameters)
                     .setFormData(formData)
                     .setClientSessionCode(clientCode.getCode())
                     .createRegistration();
@@ -481,7 +480,7 @@ public class LoginActionsService {
         // Validate that user with this username doesn't exist in realm or any federation provider
         if (session.users().getUserByUsername(username, realm) != null) {
             event.error(Errors.USERNAME_IN_USE);
-            return Flows.forms(session, realm, client, uriInfo)
+            return Flows.forms(session, realm, client, uriInfo, headers)
                     .setError(Messages.USERNAME_EXISTS)
                     .setFormData(formData)
                     .setClientSessionCode(clientCode.getCode())
@@ -491,7 +490,7 @@ public class LoginActionsService {
         // Validate that user with this email doesn't exist in realm or any federation provider
         if (session.users().getUserByEmail(email, realm) != null) {
             event.error(Errors.EMAIL_IN_USE);
-            return Flows.forms(session, realm, client, uriInfo)
+            return Flows.forms(session, realm, client, uriInfo, headers)
                     .setError(Messages.EMAIL_EXISTS)
                     .setFormData(formData)
                     .setClientSessionCode(clientCode.getCode())
@@ -512,9 +511,14 @@ public class LoginActionsService {
 
             boolean passwordUpdateSuccessful;
             String passwordUpdateError = null;
+            Object[] passwordUpdateErrorParameters = null;
             try {
                 session.users().updateCredential(realm, user, UserCredentialModel.password(formData.getFirst("password")));
                 passwordUpdateSuccessful = true;
+            } catch (ModelException me) {
+                passwordUpdateSuccessful = false;
+                passwordUpdateError = me.getMessage();
+                passwordUpdateErrorParameters = me.getParameters();
             } catch (Exception ape) {
                 passwordUpdateSuccessful = false;
                 passwordUpdateError = ape.getMessage();
@@ -523,8 +527,8 @@ public class LoginActionsService {
             // User already registered, but force him to update password
             if (!passwordUpdateSuccessful) {
                 user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
-                return Flows.forms(session, realm, client, uriInfo)
-                        .setError(passwordUpdateError)
+                return Flows.forms(session, realm, client, uriInfo, headers)
+                        .setError(passwordUpdateError, passwordUpdateErrorParameters)
                         .setClientSessionCode(clientCode.getCode())
                         .createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
             }
@@ -552,7 +556,7 @@ public class LoginActionsService {
 
 
         if (!checkSsl()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
         }
 
         String code = formData.getFirst("code");
@@ -560,7 +564,7 @@ public class LoginActionsService {
         ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
         if (accessCode == null || !accessCode.isValid(ClientSessionModel.Action.OAUTH_GRANT)) {
             event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Invalid access code.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.INVALID_ACCESS_CODE);
         }
         ClientSessionModel clientSession = accessCode.getClientSession();
         event.detail(Details.CODE_ID, clientSession.getId());
@@ -582,14 +586,15 @@ public class LoginActionsService {
         }
 
         if (!AuthenticationManager.isSessionValid(realm, userSession)) {
-            AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection);
+            AuthenticationManager.logout(session, realm, userSession, uriInfo, clientConnection, headers);
             event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Session not active");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.SESSION_NOT_ACTIVE);
         }
         event.session(userSession);
 
         LoginProtocol protocol = session.getProvider(LoginProtocol.class, clientSession.getAuthMethod());
         protocol.setRealm(realm)
+                .setHttpHeaders(headers)
                 .setUriInfo(uriInfo);
         if (formData.containsKey("cancel")) {
             event.error(Errors.REJECTED_BY_USER);
@@ -623,7 +628,7 @@ public class LoginActionsService {
 
         String error = Validation.validateUpdateProfileForm(formData);
         if (error != null) {
-            return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(error)
+            return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(error)
                     .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.UPDATE_PROFILE);
         }
@@ -641,7 +646,7 @@ public class LoginActionsService {
 
             // check for duplicated email
             if (userByEmail != null && !userByEmail.getId().equals(user.getId())) {
-                return Flows.forms(session, realm, null, uriInfo).setUser(user).setError(Messages.EMAIL_EXISTS)
+                return Flows.forms(session, realm, null, uriInfo, headers).setUser(user).setError(Messages.EMAIL_EXISTS)
                         .setClientSessionCode(accessCode.getCode())
                         .createResponse(RequiredAction.UPDATE_PROFILE);
             }
@@ -680,7 +685,7 @@ public class LoginActionsService {
         String totp = formData.getFirst("totp");
         String totpSecret = formData.getFirst("totpSecret");
 
-        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
+        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
         if (Validation.isEmpty(totp)) {
             return loginForms.setError(Messages.MISSING_TOTP)
                     .setClientSessionCode(accessCode.getCode())
@@ -725,7 +730,7 @@ public class LoginActionsService {
         String passwordNew = formData.getFirst("password-new");
         String passwordConfirm = formData.getFirst("password-confirm");
 
-        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo).setUser(user);
+        LoginFormsProvider loginForms = Flows.forms(session, realm, null, uriInfo, headers).setUser(user);
         if (Validation.isEmpty(passwordNew)) {
             return loginForms.setError(Messages.MISSING_PASSWORD)
                     .setClientSessionCode(accessCode.getCode())
@@ -738,6 +743,10 @@ public class LoginActionsService {
 
         try {
             session.users().updateCredential(realm, user, UserCredentialModel.password(passwordNew));
+        } catch (ModelException me) {
+            return loginForms.setError(me.getMessage(), me.getParameters())
+                    .setClientSessionCode(accessCode.getCode())
+                    .createResponse(RequiredAction.UPDATE_PASSWORD);
         } catch (Exception ape) {
             return loginForms.setError(ape.getMessage())
                     .setClientSessionCode(accessCode.getCode())
@@ -751,7 +760,7 @@ public class LoginActionsService {
         if (clientSession.getAction().equals(ClientSessionModel.Action.RECOVER_PASSWORD)) {
             String actionCookieValue = getActionCookie();
             if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
-                return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setSuccess("passwordUpdated").createInfoPage();
+                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.ACCOUNT_PASSWORD_UPDATED).createInfoPage();
             }
         }
 
@@ -783,7 +792,7 @@ public class LoginActionsService {
 
             String actionCookieValue = getActionCookie();
             if (actionCookieValue == null || !actionCookieValue.equals(userSession.getId())) {
-                return Flows.forms(session, realm, clientSession.getClient(), uriInfo).setSuccess("emailVerified").createInfoPage();
+                return Flows.forms(session, realm, clientSession.getClient(), uriInfo, headers).setSuccess(Messages.EMAIL_VERIFIED).createInfoPage();
             }
 
             event = event.clone().removeDetail(Details.EMAIL).event(EventType.LOGIN);
@@ -801,7 +810,7 @@ public class LoginActionsService {
 
             createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
 
-            return Flows.forms(session, realm, null, uriInfo)
+            return Flows.forms(session, realm, null, uriInfo, headers)
                     .setClientSessionCode(accessCode.getCode())
                     .setUser(userSession.getUser())
                     .createResponse(RequiredAction.VERIFY_EMAIL);
@@ -818,11 +827,11 @@ public class LoginActionsService {
                 return checks.response;
             }
             ClientSessionCode accessCode = checks.clientCode;
-            return Flows.forms(session, realm, null, uriInfo)
+            return Flows.forms(session, realm, null, uriInfo, headers)
                     .setClientSessionCode(accessCode.getCode())
                     .createResponse(RequiredAction.UPDATE_PASSWORD);
         } else {
-            return Flows.forms(session, realm, null, uriInfo)
+            return Flows.forms(session, realm, null, uriInfo, headers)
                     .setClientSessionCode(code)
                     .createPasswordReset();
         }
@@ -835,16 +844,16 @@ public class LoginActionsService {
                                       final MultivaluedMap<String, String> formData) {
         event.event(EventType.SEND_RESET_PASSWORD);
         if (!checkSsl()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "HTTPS required");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.HTTPS_REQUIRED);
         }
         if (!realm.isEnabled()) {
             event.error(Errors.REALM_DISABLED);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Realm not enabled.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.REALM_NOT_ENABLED);
         }
         ClientSessionCode accessCode = ClientSessionCode.parse(code, session, realm);
         if (accessCode == null) {
             event.error(Errors.INVALID_CODE);
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, "Unknown code, please login again through your application.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_CODE);
         }
         ClientSessionModel clientSession = accessCode.getClientSession();
 
@@ -852,12 +861,10 @@ public class LoginActionsService {
 
         ClientModel client = clientSession.getClient();
         if (client == null) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,
-                    "Unknown login requester.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.UNKNOWN_LOGIN_REQUESTER);
         }
         if (!client.isEnabled()) {
-            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo,
-                    "Login requester not enabled.");
+            return Flows.forwardToSecurityFailurePage(session, realm, uriInfo, headers, Messages.LOGIN_REQUESTER_NOT_ENABLED);
         }
 
         event.client(client.getClientId())
@@ -900,7 +907,7 @@ public class LoginActionsService {
             } catch (EmailException e) {
                 event.error(Errors.EMAIL_SEND_FAILED);
                 logger.error("Failed to send password reset email", e);
-                return Flows.forms(this.session, realm, client, uriInfo).setError("emailSendError")
+                return Flows.forms(this.session, realm, client, uriInfo, headers).setError(Messages.EMAIL_SENT_ERROR)
                         .setClientSessionCode(accessCode.getCode())
                         .createErrorPage();
             }
@@ -908,7 +915,7 @@ public class LoginActionsService {
             createActionCookie(realm, uriInfo, clientConnection, userSession.getId());
         }
 
-        return Flows.forms(session, realm, client,  uriInfo).setSuccess("emailSent").setClientSessionCode(accessCode.getCode()).createPasswordReset();
+        return Flows.forms(session, realm, client,  uriInfo, headers).setSuccess(Messages.EMAIL_SENT).setClientSessionCode(accessCode.getCode()).createPasswordReset();
     }
 
     private String getActionCookie() {
diff --git a/services/src/main/java/org/keycloak/services/validation/Validation.java b/services/src/main/java/org/keycloak/services/validation/Validation.java
index d306dc0..f72a630 100755
--- a/services/src/main/java/org/keycloak/services/validation/Validation.java
+++ b/services/src/main/java/org/keycloak/services/validation/Validation.java
@@ -48,7 +48,7 @@ public class Validation {
         return null;
     }
 
-    public static String validatePassword(MultivaluedMap<String, String> formData, PasswordPolicy policy) {
+    public static PasswordPolicy.Error validatePassword(MultivaluedMap<String, String> formData, PasswordPolicy policy) {
         return policy.validate(formData.getFirst("username"), formData.getFirst("password"));
     }
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 6497cb5..4743826 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -283,7 +283,7 @@ public class AccountTest {
         // All fields are required, so there should be an error when something is missing.
         profilePage.updateProfile("", "New last", "new@email.com");
 
-        Assert.assertEquals("Please specify first name", profilePage.getError());
+        Assert.assertEquals("Please specify first name.", profilePage.getError());
         Assert.assertEquals("", profilePage.getFirstName());
         Assert.assertEquals("New last", profilePage.getLastName());
         Assert.assertEquals("new@email.com", profilePage.getEmail());
@@ -292,7 +292,7 @@ public class AccountTest {
 
         profilePage.updateProfile("New first", "", "new@email.com");
 
-        Assert.assertEquals("Please specify last name", profilePage.getError());
+        Assert.assertEquals("Please specify last name.", profilePage.getError());
         Assert.assertEquals("New first", profilePage.getFirstName());
         Assert.assertEquals("", profilePage.getLastName());
         Assert.assertEquals("new@email.com", profilePage.getEmail());
@@ -301,7 +301,7 @@ public class AccountTest {
 
         profilePage.updateProfile("New first", "New last", "");
 
-        Assert.assertEquals("Please specify email", profilePage.getError());
+        Assert.assertEquals("Please specify email.", profilePage.getError());
         Assert.assertEquals("New first", profilePage.getFirstName());
         Assert.assertEquals("New last", profilePage.getLastName());
         Assert.assertEquals("", profilePage.getEmail());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
index 1d66651..0d6b4cf 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionEmailVerificationTest.java
@@ -236,7 +236,7 @@ public class RequiredActionEmailVerificationTest {
         events.expectRequiredAction(EventType.VERIFY_EMAIL).session(sessionId).detail("email", "test-user@localhost").detail(Details.CODE_ID, mailCodeId).assertEvent();
 
         assertTrue(infoPage.isCurrent());
-        assertEquals("Email verified", infoPage.getInfo());
+        assertEquals("Your email address has been verified.", infoPage.getInfo());
 
         loginPage.open();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
index d832f96..e5e4c3c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/actions/RequiredActionUpdateProfileTest.java
@@ -108,7 +108,7 @@ public class RequiredActionUpdateProfileTest {
 
         updateProfilePage.assertCurrent();
 
-        Assert.assertEquals("Please specify first name", updateProfilePage.getError());
+        Assert.assertEquals("Please specify first name.", updateProfilePage.getError());
 
         events.assertEmpty();
     }
@@ -125,7 +125,7 @@ public class RequiredActionUpdateProfileTest {
 
         updateProfilePage.assertCurrent();
 
-        Assert.assertEquals("Please specify last name", updateProfilePage.getError());
+        Assert.assertEquals("Please specify last name.", updateProfilePage.getError());
 
         events.assertEmpty();
     }
@@ -142,7 +142,7 @@ public class RequiredActionUpdateProfileTest {
 
         updateProfilePage.assertCurrent();
 
-        Assert.assertEquals("Please specify email", updateProfilePage.getError());
+        Assert.assertEquals("Please specify email.", updateProfilePage.getError());
 
         events.assertEmpty();
     }
@@ -159,7 +159,7 @@ public class RequiredActionUpdateProfileTest {
 
         updateProfilePage.assertCurrent();
 
-        Assert.assertEquals("Email already exists", updateProfilePage.getError());
+        Assert.assertEquals("Email already exists.", updateProfilePage.getError());
 
         events.assertEmpty();
     }
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index b1ae4cb..6579d4e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -286,7 +286,7 @@ public abstract class AbstractIdentityProviderTest {
 
         assertNotNull(element);
 
-        assertEquals("Email already exists", element.getText());
+        assertEquals("Email already exists.", element.getText());
 
         this.updateProfilePage.assertCurrent();
         this.updateProfilePage.update("Test", "User", "test-user@redhat.com");
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
index 611c6bd..5f484dc 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java
@@ -236,12 +236,12 @@ public class FederationProvidersIntegrationTest {
         // check existing username
         registerPage.register("firstName", "lastName", "email@mail.cz", "existing", "Password1", "Password1");
         registerPage.assertCurrent();
-        Assert.assertEquals("Username already exists", registerPage.getError());
+        Assert.assertEquals("Username already exists.", registerPage.getError());
 
         // Check existing email
         registerPage.register("firstName", "lastName", "existing@email.org", "nonExisting", "Password1", "Password1");
         registerPage.assertCurrent();
-        Assert.assertEquals("Email already exists", registerPage.getError());
+        Assert.assertEquals("Email already exists.", registerPage.getError());
     }
 
     @Test
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
index 75a83f4..8c5a2f8 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java
@@ -162,7 +162,7 @@ public class LoginTest {
 
             loginPage.assertCurrent();
 
-            Assert.assertEquals("Account is disabled, contact admin", loginPage.getError());
+            Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError());
 
             events.expectLogin().user(userId).session((String) null).error("user_disabled").detail(Details.USERNAME, "login-test").assertEvent();
         } finally {
@@ -316,7 +316,7 @@ public class LoginTest {
             loginPage.login("login@test.com", "password");
 
             loginPage.assertCurrent();
-            Assert.assertEquals("Login timeout. Please login again", loginPage.getError());
+            Assert.assertEquals("Login timeout. Please login again.", loginPage.getError());
 
             events.expectLogin().user((String) null).session((String) null).error("expired_code").clearDetails().detail(Details.CODE_ID, AssertEvents.isCodeId()).assertEvent();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
index 2d0657a..b2ac90e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/RegisterTest.java
@@ -78,7 +78,7 @@ public class RegisterTest {
         registerPage.register("firstName", "lastName", "registerExistingUser@email", "test-user@localhost", "password", "password");
 
         registerPage.assertCurrent();
-        Assert.assertEquals("Username already exists", registerPage.getError());
+        Assert.assertEquals("Username already exists.", registerPage.getError());
 
         events.expectRegister("test-user@localhost", "registerExistingUser@email").user((String) null).error("username_in_use").assertEvent();
     }
@@ -92,7 +92,7 @@ public class RegisterTest {
         registerPage.register("firstName", "lastName", "registerUserInvalidPasswordConfirm@email", "registerUserInvalidPasswordConfirm", "password", "invalid");
 
         registerPage.assertCurrent();
-        Assert.assertEquals("Password confirmation doesn't match", registerPage.getError());
+        Assert.assertEquals("Password confirmation doesn't match.", registerPage.getError());
 
         events.expectRegister("registerUserInvalidPasswordConfirm", "registerUserInvalidPasswordConfirm@email").user((String) null).error("invalid_registration").assertEvent();
     }
@@ -157,7 +157,7 @@ public class RegisterTest {
         registerPage.register("firstName", "lastName", "registerUserMissingUsername@email", null, "password", "password");
 
         registerPage.assertCurrent();
-        Assert.assertEquals("Please specify username", registerPage.getError());
+        Assert.assertEquals("Please specify username.", registerPage.getError());
 
         events.expectRegister(null, "registerUserMissingUsername@email").removeDetail("username").error("invalid_registration").assertEvent();
     }
@@ -170,12 +170,12 @@ public class RegisterTest {
 
         registerPage.register("firstName", "lastName", null, "registerUserMissingEmail", "password", "password");
         registerPage.assertCurrent();
-        Assert.assertEquals("Please specify email", registerPage.getError());
+        Assert.assertEquals("Please specify email.", registerPage.getError());
         events.expectRegister("registerUserMissingEmail", null).removeDetail("email").error("invalid_registration").assertEvent();
 
         registerPage.register("firstName", "lastName", "registerUserInvalidEmailemail", "registerUserInvalidEmail", "password", "password");
         registerPage.assertCurrent();
-        Assert.assertEquals("Invalid email address", registerPage.getError());
+        Assert.assertEquals("Invalid email address.", registerPage.getError());
         events.expectRegister("registerUserInvalidEmail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent();
     }
 
@@ -205,7 +205,7 @@ public class RegisterTest {
             registerPage.registerWithEmailAsUsername("firstName", "lastName", "test-user@localhost", "password", "password");
 
             registerPage.assertCurrent();
-            Assert.assertEquals("Username already exists", registerPage.getError());
+            Assert.assertEquals("Username already exists.", registerPage.getError());
 
             events.expectRegister("test-user@localhost", "test-user@localhost").user((String) null).error("username_in_use").assertEvent();
         } finally {
@@ -224,12 +224,12 @@ public class RegisterTest {
 
             registerPage.registerWithEmailAsUsername("firstName", "lastName", null, "password", "password");
             registerPage.assertCurrent();
-            Assert.assertEquals("Please specify email", registerPage.getError());
+            Assert.assertEquals("Please specify email.", registerPage.getError());
             events.expectRegister(null, null).removeDetail("username").removeDetail("email").error("invalid_registration").assertEvent();
 
             registerPage.registerWithEmailAsUsername("firstName", "lastName", "registerUserInvalidEmailemail", "password", "password");
             registerPage.assertCurrent();
-            Assert.assertEquals("Invalid email address", registerPage.getError());
+            Assert.assertEquals("Invalid email address.", registerPage.getError());
             events.expectRegister("registerUserInvalidEmailemail", "registerUserInvalidEmailemail").error("invalid_registration").assertEvent();
         } finally {
             configureRelamRegistrationEmailAsUsername(false);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
index 7582e20..a96b660 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java
@@ -504,7 +504,7 @@ public class ResetPasswordTest {
         events.expectRequiredAction(EventType.UPDATE_PASSWORD).user(userId).session(sessionId).detail(Details.USERNAME, username).assertEvent();
 
         assertTrue(infoPage.isCurrent());
-        assertEquals("Password updated", infoPage.getInfo());
+        assertEquals("Your password has been updated", infoPage.getInfo());
 
         loginPage.open();
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
index c75a030..9aa884f 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthRedirectUriTest.java
@@ -108,7 +108,7 @@ public class OAuthRedirectUriTest {
             oauth.openLoginForm();
 
             Assert.assertTrue(errorPage.isCurrent());
-            Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
+            Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
         } finally {
             keycloakRule.update(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -133,7 +133,7 @@ public class OAuthRedirectUriTest {
             oauth.openLoginForm();
 
             Assert.assertTrue(errorPage.isCurrent());
-            Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
+            Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
         } finally {
             keycloakRule.update(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -158,7 +158,7 @@ public class OAuthRedirectUriTest {
             oauth.openLoginForm();
 
             Assert.assertTrue(errorPage.isCurrent());
-            Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
+            Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
         } finally {
             keycloakRule.update(new KeycloakRule.KeycloakSetup() {
                 @Override
@@ -184,7 +184,7 @@ public class OAuthRedirectUriTest {
         oauth.openLoginForm();
 
         Assert.assertTrue(errorPage.isCurrent());
-        Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
+        Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
     }
 
     @Test
@@ -244,7 +244,7 @@ public class OAuthRedirectUriTest {
             Assert.assertTrue(loginPage.isCurrent());
         } else {
             Assert.assertTrue(errorPage.isCurrent());
-            Assert.assertEquals("Invalid redirect_uri", errorPage.getError());
+            Assert.assertEquals("Invalid parameter: redirect_uri", errorPage.getError());
         }
 
         if (expectValid) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
index 1766a87..fc83347 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java
@@ -56,7 +56,7 @@ public class LoginPage extends AbstractPage {
     @FindBy(linkText = "Register")
     private WebElement registerLink;
 
-    @FindBy(linkText = "Password")
+    @FindBy(linkText = "Forgot Password?")
     private WebElement resetPasswordLink;
 
     @FindBy(linkText = "Username")