I18n approach: Language in URL

Splash Forums Rewrite Users I18n approach: Language in URL

This topic contains 4 replies, has 2 voices, and was last updated by  zebhed 7 hours, 51 minutes ago.

Viewing 5 posts - 1 through 5 (of 5 total)
  • Author
    Posts
  • #27470

    zebhed
    Participant

    Hello everybody 🙂

    First off, I have a rather simple requirement:

    I want to rewrite the URL so that it contains the language that is set in
    FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();

    So as a result I want something like this:

    /my-context-path/de/imprint
    /my-context-path/en/imprint
    etc.

    ————————————–

    My approach follows the explanations I found in other postings in this forum:

    @RewriteConfiguration
    public class UrlRewriteRules extends HttpConfigurationProvider {
    
        private ConfigurationBuilder cb;
    
        @Override
        public int priority() {
            return 0;
        }
    
        @Override
        public Configuration getConfiguration(ServletContext context) {
            cb = ConfigurationBuilder.begin();
    
            joinPathTo("/imprint", "/web/page/imprint.xhtml");
            joinPathTo("/start", "/web/page/start.xhtml");
            //etc.
            
            return cb;
        }
    
        private Configuration joinPathTo(String path, String to) {
            //without "language" -> this works! 
            //return cb.addRule(Join.path(path).to(to).withInboundCorrection());
    
            //with "language" -> this does NOT work! 
            return cb.addRule(Join.path("/{lang}" + path).to(to).withInboundCorrection()).where("lang").bindsTo(PhaseBinding.to(El.property("currentViewLanguage.language")).after(PhaseId.RESTORE_VIEW));
            
        }
    }

    Since facesContext.viewRoot.locale.language does provoke an exception when used directly (because it lacks “setLanguage()”) I have encapsulated that into a request scoped bean:

    @ManagedBean
    @RequestScoped
    public class CurrentViewLanguage {
    
        String language;
    
        @PostConstruct
        public void postConstruct() {
            language = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
        }
    
        public String getLanguage() {
            return language;
        }
    
        public void setLanguage(String language) {
            this.language = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
        }
    
    }

    This bean is triggered on EVERY page via
    <h:outputText value="#{currentViewLanguage.language}" />
    which, of course, prints the expected language.

    ————————————–

    If I type e.g. the following into the address bar of the browser, then it works:
    http://localhost:8080/my-context-path/de/imprint

    But all links on this page are not rewritten, so e.g. I see:
    <a href="/my-context-path/web/page/imprint.xhtml">Impressum</a>

    So inbound rewriting does seem to work, but how can I manage to get outbound rewriting also to work?

    Any help is greatly appreciated.

    Btw, I have tested this with Rewrite 3.4.1. and 2.0.12. which both behave the same in this case as far as I can tell.

    • This topic was modified 1 week, 2 days ago by  zebhed.
    • This topic was modified 1 week, 2 days ago by  zebhed.
    • This topic was modified 1 week, 2 days ago by  zebhed.
    #27474

    I guess outbound rewriting will work if you add an explicit lang param to the link. Something like:

    <h:link outcome="/web/page/imprint.xhtml">
      <f:param name="lang" value="#{currentViewLanguage.language}" />
      Imprint
    </h:link>
    

    Another thing? What’s the reason for the implementation of CurrentViewLanguage.setLanguage()? That looks strange. The argument of the method is ignored and you read the language from the ViewRoot??

    #27475

    zebhed
    Participant

    Hi Christian,

    thank you very much for your response.

    ———————–

    You are absolutely right. Adding the f:param and everything works. Sometimes the obvious is before us and we can´t see it 😉 Thank you.

    Nonetheless this will result in lots of work and even more duplicate code if I have to add f:param to every h:link. I wonder if there is a centralized programmtic approach:

    a) Normally I would use a servlet filter to enrich the request parameters via “lang”. But FacesContext is not available in a filter.

    b) Of course, I can build my own JSF custom component that extends h:link and already includes f:param “lang”. Maybe this is the best approach.

    c) Any other ideas?

    ———————–

    The reason why I am using “FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage()” is because API says “it returns the language of the Locale used in localizing the response being created for this view”. That´s exactly what I want.

    Sadly, if I am doing

    return cb.addRule(Join.path("/{lang}" + path).to(to).withInboundCorrection()).where("lang").bindsTo(PhaseBinding.to(El.property("facesContext.viewRoot.locale.language")).after(PhaseId.RESTORE_VIEW));

    it results in the following exception:

    javax.el.PropertyNotWritableException: The class 'java.util.Locale' does not have a writable property 'language'.
    	at javax.el.BeanELResolver.setValue(BeanELResolver.java:434)

    That´s why I wrapped the call into “CurrentViewLanguage”. setLanguage() must be there and is read-only. So, yes, it´s weird, but a working solution without side effects ….. Hmm, maybe I should leave the method body empty. Good point, thx.

    ———————–

    Greetings from Munich
    Martin

    • This reply was modified 1 week ago by  zebhed.
    #27478

    Hi there.

    Sorry for the very late reply. Regarding your CurrentViewLanguage implementation: I typically do it differently. I basically create a request scoped bean which get the value of the path parameter and then provides a Locale which I use to configure the ViewRoot for that locale. So basically something like this:

    @RequestScoped
    public class LocaleBean {
    
      private String lang;
    
      public String getLang() {
        return lang;
      }
    
      public void setLang(String lang) {
        this.lang = lang;
      }
    
      public Locale getLocale() {
        return new Locale(lang);
      }
    
    }
    

    And then in the main template:

    <f:view locale=#{localeBean.locale}>
    
      ...
      
    </f:view>
    

    That works pretty well. Rewrite assigns the lang parameter BEFORE the JSF lifecycle starts and when JSF kicks in, it gets the correct locale from the LocaleBean.

    #27479

    zebhed
    Participant

    Very true.

    After a few refactorings on the way I came to a similar solution.

    Following solution has the advantage that browser language/locale is used as an initial value which may be manually overridden by the user (via a form whose action calls “setLanguage()” ):

    @ManagedBean
    @SessionScoped
    public class RequestorLanguage implements Serializable {
    
        public static String GET_PARAM_LANGUAGE = "language";
        //data
        private String language;
        private Locale locale;
    
        @PostConstruct
        public void postConstruct() {
            this.locale = FacesContext.getCurrentInstance().getViewRoot().getLocale();
            this.language = locale.getLanguage();
        }
    
        public String getLanguage() {
            return language;
        }
    
        public void setLanguage(String language) {
            this.locale = new Locale(language);
            this.language = locale.getLanguage();
    
            FacesContext.getCurrentInstance().getViewRoot().setLocale(this.locale); //HINT: this alone is not sufficient and not remembered by JSF! Locale has to be set for every page request via "f:view"!
        }
    
        public Locale getLocale() {
            return locale;
        }
    
    }
Viewing 5 posts - 1 through 5 (of 5 total)

You must be logged in to reply to this topic.

Comments are closed.