Multilanguage in WebCenter Framework Portal

This is a solution for the following challenges in WebCenter Framework Portal:

  • Literals translation and Locale Handling.
  • Translation of the Titles associated to the Navigation Resources in the Navigation Models.

To solve those challenges it uses the following:

  • A User Preference Language solution based on a Java Filter and a Cookie.
  • An ADF Phase Listener for the Navigation Model Cache Invalidation. It is necessary if the Resource Bundle is in use for the Title attribute of the Navigation Resources

Java Filter and the Cookie storing the Locale User Preference

Java Filter and a RequestWrapper to overrided the Request Supported Locales sent to WebCenter Framework Portal. The default language is English in case of not being assigned a language preference yet.

public final class LocaleFilter implements Filter {
     
    /**
     * Filter Configuration
     */
    private FilterConfig _filterConfig = null;
 
    /**
     * Initializing the filter
     * @param filterConfig
     * @throws ServletException
     */
    public void init(FilterConfig filterConfig) throws ServletException {
        _filterConfig = filterConfig;
    }
 
    /**
     * Destroy the filter
     */
    public void destroy() {
        _filterConfig = null;
    }
 
    /**
     * Check the Cookie and overwrite the default language given by the browser
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException,
                                                   ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        String preferredLocale = this.getPreferredLocaleFromCookies(req);
        LocaleRequestWrapper localeReqWrapp = new LocaleRequestWrapper(req,preferredLocale);
        chain.doFilter(localeReqWrapp, response);
    }
 
    /**
     * Get the preferredLocale from the cookies
     * @param req
     * @return String format locale from the configured cookie
     */
    private String getPreferredLocaleFromCookies(HttpServletRequest req) {
        Cookie[] cookies = req.getCookies();
        boolean found = false;
        int i = 0;
        Cookie cookie = null;
        String prefLang = null;
        if (cookies != null) {
            while (i < cookies.length && !found) {
                cookie = cookies[i];;
                if (cookie.getName().equalsIgnoreCase(CustomLocaleUtils.LANG_COOKIE)) {
                    prefLang = cookie.getValue();
                    found = true;
                }
                ++i;
            }
        }
        return prefLang;
    }
}
public final class LocaleRequestWrapper extends HttpServletRequestWrapper{
     
    /**
     * Locale to apply
     */
    private Locale locale = null;
 
 
    /**
     * Initializes de wrapped request setting the language to be used
     * @param req
     * @param lang
     */
    public LocaleRequestWrapper(HttpServletRequest req, String lang) {
        super(req);
        if (StringUtils.isNotEmpty(lang)) {
            // Preferred locale by the user (Cookie)
            locale = LocaleUtils.toLocale(lang);
        } else {
            // Default locale, english hardcoded
            locale = new Locale("en");
        }
    }
 
    /**
     * Setting the language to be shown instead of the browser Accept-Languages
     * @return Enumeration with just the desired locale
     */
    @Override
    public Enumeration getLocales() {
        Vector locales = new Vector();
        locales.add(locale);
        return locales.elements();
    }

Helpful class to handle the Cookies:

public final class CustomLocaleUtils {
     
    /**
     * Name of the cookie storing the user preference
     */
    public static final String LANG_COOKIE = "merchan_lang";
     
    /**
     * Services class. Can not be instantiated
     */
    private CustomLocaleUtils() {
        super();
    }
     
     
    /**
     * Set a new preferred locale
     * @param lang
     */
    public static void setCookieLang(Locale lang) {
        HttpServletResponse response = (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse();
        // Get the context-root because is the same as the cookie-path in weblogic.xml
        ServletContext appContext = (ServletContext)ADFContext.getCurrent().getEnvironment().getContext();
        // Write the cookie in the cookie path /MyApp
        Cookie langCookie = new Cookie(LANG_COOKIE,lang.getLanguage());
        //langCookie.setPath(appContext.getContextPath());
        langCookie.setPath("/");
        response.addCookie(langCookie);
    }
}

Locale Handler used to manage the operation with the Locales.

public final class LocaleHandler {
 
    /**
     * List of the supported Locales by the WebCenter Portal Application
     */
    private List supportedLocales;
 
    /**
     * Holds the current locale
     */
    private Locale currentLocale;
     
    /**
     * Flag to indicate if the language was changed
     */
    private boolean changed;
 
    /**
     * Default constructor
     */
    public LocaleHandler() {
        super();
        // Initialize the supportedLocales list
        Iterator iteratorSupportedLocales = FacesContext.getCurrentInstance().getApplication().getSupportedLocales();
        supportedLocales = new ArrayList();
        SelectItem itemLocale = null;
        while (iteratorSupportedLocales.hasNext()) {
            Locale locale = iteratorSupportedLocales.next();
            itemLocale = new SelectItem(locale, locale.getDisplayName(), locale.getDisplayName());
            supportedLocales.add(itemLocale);
        }
        currentLocale = ADFContext.getCurrent().getLocale();
        changed = false;
    }
 
    /**
     * Change the language from a given action of an JSF ActionListener
     * @param ae
     */
    public void changeLanguageEvent(ActionEvent ae) {
        CustomLocaleUtils.setCookieLang(currentLocale);
        NavigationModel navModel = SiteStructureContext.getInstance().getCurrentNavigationModel();
        FacesContext fctx = FacesContext.getCurrentInstance();
        ExternalContext ectx = fctx.getExternalContext();
        try {
            NavigationResource navResource = navModel.getCurrentSelection();
            String prettyUrl = ectx.getRequestContextPath() + navResource.getGoLinkPrettyUrl();
            changed = true;
            ectx.redirect(prettyUrl);
         } catch (IOException e) {
            e.printStackTrace();
        }
    }
     
    /**
     * Set changed
     * @param changed
     */
    public void setChanged(boolean changed) {
        this.changed = changed;
    }
 
    /**
     * Get changed flag
     * @return boolean
     */
    public boolean isChanged() {
        return changed;
    }
 
    /**
     * Get the supported locales by the WebCenter Application
     * @return List of locales
     */
    public List getSupportedLocales() {
        return supportedLocales;
    }
 
    /**
     * Set the current locale in a variable to easier access
     * @param currentLocale
     */
    public void setCurrentLocale(Locale currentLocale) {
        this.currentLocale = currentLocale;
    }
 
    /**
     * Get the current locale
     * @return
     */
    public Locale getCurrentLocale() {
        return currentLocale;
    }
}

Snippet used in the Page Template to change the language / locale:

<!-- Language selector -->
<af:panelgrouplayout id="pt_pgl5" layout="horizontal">
   <af:outputlabel for="pt_smc1" id="pt_plam3" inlinestyle="color:White;" value="#{portalBundle.LANGUAGE_SELECTOR}">
      <af:selectonechoice id="pt_smc1" value="#{localeHandler.currentLocale}">
        <f:selectitems id="pt_si1" value="#{localeHandler.supportedLocales}">
      </f:selectitems></af:selectonechoice>
      <af:spacer height="10" id="pt_s1" width="10">
      <af:commandbutton actionlistener="#{localeHandler.changeLanguageEvent}" id="pt_cb1" text="#{portalBundle.CHANGE}">
</af:commandbutton></af:spacer></af:outputlabel></af:panelgrouplayout>

Is it sufficient to achieve multilanguage?. In case of using the Title attribute of the Nagigation Resources get the value from a Resource Bundle then it is necessary to implement an ADF Phase Listener who will be the responsible of flusing the Navigation Model Cache.

How to invalidate the Navigation Model Cache

Implementation of the ADF Phase Listener executing the invalidation in case of changing the language and executed before of the PREAPRE_MODEL Phase (Before of evaluating # EL Expressions).

public class LocalePhaseListener implements PagePhaseListener {
    public LocalePhaseListener() {
        super();
    }
 
    public void afterPhase(PagePhaseEvent pagePhaseEvent) {
    }
 
    public void beforePhase(PagePhaseEvent pagePhaseEvent) {
        if (Lifecycle.PREPARE_MODEL_ID == pagePhaseEvent.getPhaseId()) {
            LocaleHandler localeHandler = (LocaleHandler)ADFContext.getCurrent().getSessionScope().get("localeHandler");
            if (localeHandler != null && localeHandler.isChanged()) {
                SiteStructureUtils.invalidateDefaultNavigationModelCache();
                localeHandler.setChanged(false);
            }
        }
    }
}

Sample in the GitHub Repository.

Choose the right CX technology for your business