Below is my solution to your problem. It is bulky, but finished, informative and, as far as I can see, complete. With it you shall be able to incude the necessary view from a family of language-suffixed views, basing on the current language.
My assumptions about your setup
- You are dealing with locales that describe languages, i.e. are in
Locale.ENGLISH
format;
- You selected language is stored in a session scoped bean;
- You keep the internationalized pages in the following format:
page.xhtml
, page_en.xhtml
, page_fr.xhtml
, etc;
- Default language is English;
- Your
FacesServlet
is mapped to *.xhtml
.
Standard settings for my solution
Session scoped bean, holding available languages and user selection:
@ManagedBean
@SessionScoped
public class LanguageBean implements Serializable {
private List<Locale> languages;//getter
private Locale selectedLanguage;//getter + setter
public LanguageBean() {
languages = new ArrayList<Locale>();
languages.add(Locale.ENGLISH);
languages.add(Locale.FRENCH);
languages.add(Locale.GERMAN);
selectedLanguage = Locale.ENGLISH;
}
public Locale findLocale(String value) {
for(Locale locale : languages) {
if(locale.getLanguage().equals(new Locale(value).getLanguage())) {
return locale;
}
}
return null;
}
public void languageChanged(ValueChangeEvent e){
FacesContext.getCurrentInstance().getViewRoot().setLocale(selectedLanguage);
}
}
Converter for a locale:
@ManagedBean
@RequestScoped
public class LocaleConverter implements Converter {
@ManagedProperty("#{languageBean}")
private LanguageBean languageBean;//setter
public LocaleConverter() { }
public Object getAsObject(FacesContext context, UIComponent component, String value) {
if(value == null || value.equals("")) {
return null;
}
Locale locale = languageBean.findLocale(value);
if(locale == null) {
throw new ConverterException(new FacesMessage("Locale not supported: " + value));
}
return locale;
}
public String getAsString(FacesContext context, UIComponent component, Object value) {
if (!(value instanceof Locale) || (value == null)) {
return null;
}
return ((Locale)value).getLanguage();
}
}
Main view (main.xhtml
) with links to internationalized pages and with ability to change current language via a dropdown box:
<f:view locale="#{languageBean.selectedLanguage}">
<h:head>
<title>Links to internationalized pages</title>
</h:head>
<h:body>
<h:form>
<h:selectOneMenu converter="#{localeConverter}" value="#{languageBean.selectedLanguage}" valueChangeListener="#{languageBean.languageChanged}" onchange="submit()">
<f:selectItems value="#{languageBean.languages}"/>
</h:selectOneMenu>
</h:form>
<br/>
<h:link value="Show me internationalized page (single)" outcome="/international/page-single"/>
<br/>
<h:link value="Show me internationalized page (multiple)" outcome="/international/page-multiple"/>
</h:body>
</f:view>
Solution based on multiple pages - one per language
Base page that is internationalized by adding _lang suffixes (page-multiple.xhtml
)
<f:metadata>
<f:event type="preRenderView" listener="#{pageLoader.loadPage}"/>
</f:metadata>
Internationalized pages:
For English (page-multiple_en.xhtml
):
<h:head>
<title>Hello - English</title>
</h:head>
<h:body>
Internationalized page - English
</h:body>
For French (page-multiple_fr.xhtml
):
<h:head>
<title>Hello - Français</title>
</h:head>
<h:body>
Page internationalisé - Français
</h:body>
For German (no view, simulation of missing file).
Managed bean that performs redirection:
@ManagedBean
@RequestScoped
public class PageLoader {
@ManagedProperty("#{languageBean}")
private LanguageBean languageBean;//setter
public PageLoader() { }
public void loadPage() throws IOException {
Locale locale = languageBean.getSelectedLanguage();
FacesContext context = FacesContext.getCurrentInstance();
ExternalContext external = context.getExternalContext();
String currentPath = context.getViewRoot().getViewId();
String resource = currentPath.replace(".xhtml", "_" + locale.toString() + ".xhtml");
if(external.getResource(resource) == null) {
resource = currentPath.replace(".xhtml", "_en.xhtml");
}
String redirectedResource = external.getRequestContextPath() + resource.replace(".xhtml", ".jsf");
external.redirect(redirectedResource);
}
}
Every time view page-multiple.xhtml
is requested it is redirected to the language-suffixed views, or to the english view, if target language's view is not found. Current language is taken from session scoped bean, all views must be located in the same folder on server. Of course, that can be redone, basing on language defined in a view parameter instead. The target pages can use a composition. Default data can be served in a non-suffixed view with preRenderView
listener not performing redirection.
As a remark, my (three) views were stored in international/
folder of web pages.
Solution based on a single page for all languages
Though your problem should be covered by the former setup, another idea came to mind, that I will describe below.
Sometimes it might be easier not to create as many views (+1 for redirection) as there are supported languages, but instead create a single view that will conditionally render its output, basing on the currently selected language.
The view (page-single.xhtml
, located in the same folder on server as well) could look like:
<ui:param name="lang" value="#{languageBean.selectedLanguage}"/>
<ui:fragment rendered="#{lang == 'en'}">
<h:head>
<title>Hello - English</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF8" />
</h:head>
<h:body>
Internationalized page - English
</h:body>
</ui:fragment>
<ui:fragment rendered="#{lang == 'fr'}">
<h:head>
<title>Hello - Français</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF8" />
</h:head>
<h:body>
Page internationalisé - Français
</h:body>
</ui:fragment>
<ui:fragment rendered="#{(lang ne 'en') and (lang ne 'fr')}">
<h:head>
<title>Hello - Default</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF8" />
</h:head>
<h:body>
Internationalized page - Default
</h:body>
</ui:fragment>
With this view you specify all data inside, conditionally rendering only the data demanded by the needed language, or default data.
Providing for a custom resource resolver
Resourse resolver will include the needed file, basing on the current locale of the view.
Resource resolver:
public class InternalizationResourceResolver extends ResourceResolver {
private String baseLanguage;
private String delimiter;
private ResourceResolver parent;
public InternalizationResourceResolver(ResourceResolver parent) {
this.parent = parent;
this.baseLanguage = "en";
this.delimiter = "_";
}
@Override
public URL resolveUrl(String path) {
URL url = parent.resolveUrl(path);
if(url == null) {
if(path.startsWith("//ml")) {
path = path.substring(4);
Locale locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
URL urlInt = parent.resolveUrl(path.replace(".xhtml", delimiter + locale.toString() + ".xhtml"));
if(urlInt == null) {
URL urlBaseInt = parent.resolveUrl(path.replace(".xhtml", delimiter + baseLanguage + ".xhtml"));
if(urlBaseInt != null) {
url = urlBaseInt;
}
} else {
url = urlInt;
}
}
}
return url;
}
}
Enable the resolver in web.xml
:
<context-param>
<param-name>javax.faces.FACELETS_RESOURCE_RESOLVER</param-name>
<param-value>i18n.InternalizationResourceResolver</param-value>
</context-param>
With this setup it is possible to render the following view:
View which uses <ui:include>
, in which internatiaonalised includes will be defined with the created //ml/
prefix:
<f:view locale="#{languageBean.selectedLanguage}">
<h:head>
</h:head>
<h:body>
<ui:include src="//ml/international/page-include.xhtml" />
</h:body>
</f:view>
There will be no page-include.xhtml
, but there will be per language views, like:
page-include_en.xhtml
:
<h:outputText value="Welcome" />
page-include_fr.xhtml
:
<h:outputText value="Bienvenue" />
This way, the resolver will choose the right internationalized included view, basing on the current locale.
<ui:include>
. The OP do not want to redirect to a completely independent page on a per-request basis by e.g. a redirect. So a filter and event listener are completely out of question. – Villanovan