Problem with changing language while using language base in SEO urls

Splash Forums PrettyFaces Users Problem with changing language while using language base in SEO urls

This topic contains 7 replies, has 3 voices, and was last updated by  xmapact 3 years, 1 month ago.

Viewing 8 posts - 1 through 8 (of 8 total)
  • Author
    Posts
  • #24305

    xmapact
    Participant

    Hello,

    First of all I want to say thank’s for such a handy instrument that you provide. Everything was pretty nice and intuitive until the day I got a request to add language code base to all web-site URLs. E.g:

    As I learned from documentation I added lang parameter to all my <h:link> tags.
    And added such rules to my pretty-config.xml:

        <url-mapping id="base">
            <pattern value="/#{ lang : localeChanger.language }/" />
        </url-mapping>
    
        <url-mapping id="home" parentId="base">
            <pattern value="/" />
            <view-id value="/faces/index.xhtml" />
        </url-mapping>
        
        <url-mapping id="login"  parentId="base">
            <pattern value="/login" />
            <view-id value="/faces/login.xhtml" />
        </url-mapping>

    Problem is that I can pass language code to managed bean @ManagedBean (name = "localeChanger") but I can’t get current language code from it. So after deployment I see www.example.com/login but not www.example.com/en/login If I manually enter URL with language code – locale changes without problems.

    My second attempt was to use more primitive config, which works nice for me until I try to switch language with my <h:commandLink>. Than it looks like it switches to needed language and immediately back =) Here is my second config variant:

        <url-mapping id="home">
            <pattern value="/#{ lang : localeChanger.language }/" />
            <view-id value="/faces/index.xhtml" />
        </url-mapping>
        
        <url-mapping id="login">
            <pattern value="/#{ lang : localeChanger.language }/login" />
            <view-id value="/faces/login.xhtml" />
        </url-mapping>

    Everything works fine, but languages switch.

    Here is my @ManagedBean that controls i18n:

    package com.example.managedbean;
    
    import javax.faces.bean.ManagedBean;
    import javax.faces.bean.ViewScoped;
    import javax.faces.context.FacesContext;
    import java.io.Serializable;
    import java.util.Locale;
    
    /**
     *
     * @author xmapact
     */
    @ManagedBean(name = "localeChanger")
    @ViewScoped
    public class LocaleChangerBean implements Serializable {
    
        private String language = FacesContext.getCurrentInstance().getViewRoot().getLocale().getLanguage();
    
        public String englishAction() {
            FacesContext.getCurrentInstance().getViewRoot().setLocale(Locale.ENGLISH);
            this.language = "en";
            String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
    
            if (viewId.equals("/productCat.xhtml") || viewId.equals("/productDetails.xhtml")) {
                return null;
            } else {
                return FacesContext.getCurrentInstance().getViewRoot().getViewId() + "?faces-redirect=true";
            }
    
        }
    
        public String russianAction() {
            FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale("ru"));
            this.language = "ru";
            String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
    
            if (viewId.equals("/productCat.xhtml") || viewId.equals("/productDetails.xhtml")) {
                return null;
            } else {
                return FacesContext.getCurrentInstance().getViewRoot().getViewId() + "?faces-redirect=true";
            }
        }
    
        public String ukrainianAction() {
            FacesContext.getCurrentInstance().getViewRoot().setLocale(new Locale("uk"));
            this.language = "uk";
            String viewId = FacesContext.getCurrentInstance().getViewRoot().getViewId();
            
            if (viewId.equals("/productCat.xhtml") || viewId.equals("/productDetails.xhtml")) {
                return null;
            } else {
                return FacesContext.getCurrentInstance().getViewRoot().getViewId() + "?faces-redirect=true";
            }
        }
    
        public String getLanguage() {
            return this.language;
        }
    
        public void setLanguage(String language) {
            if (language.equals("ru")) {
                this.russianAction();
            } else if (language.equals("uk")) {
                this.ukrainianAction();
            } else {
                 this.englishAction();
            }
        }
        
        public Locale getLocale() {
            return new Locale(this.language);
        }
    
        public String getLinkClass(String lang) {
            String className = "inactive";
    
            if (this.language.equals(lang)) {
                className = "active";
            }
    
            return className;
        }
    }

    Maybe the problem is that my <h:commandLink> tags have a target that already includes language code? www.example.com/ru/#.

    Here is a part of my template:

    <div class="header_language">
                    <h:form>
                        <ul class="header_lang" style="display: inline-block;">
                            <li>
                                <h:commandLink action="#{localeChanger.englishAction}" class="#{localeChanger.getLinkClass('en')}">
                                    EN
                                </h:commandLink>
                            </li>
                            <li>
                                <h:commandLink target="/" action="#{localeChanger.russianAction()}" class="#{localeChanger.getLinkClass('ru')}">
                                    RU
                                </h:commandLink>
                            </li>
                            <li>
                                <h:commandLink target="/" action="#{localeChanger.ukrainianAction}" class="#{localeChanger.getLinkClass('uk')}">
                                    UA
                                </h:commandLink>
                            </li>
                        </ul>
                    </h:form>
                </div>

    Please help me to find a way to SEO + i18n + JSF + PrettyFaces paradise =)

    • This topic was modified 3 years, 1 month ago by  xmapact. Reason: improved formatting
    #24306

    Hey, let’s see if I can help.

    First of all: the two pretty-config.xml variants you posted are equivalent, with one minor difference. If you specify a parent mapping, PrettyFaces will concatenate both patterns to a single one. In your example this leads to /#{ lang : localeChanger.language }//login. The problem here is the //. So I guess you should remove the trailing slash from the parent mapping to get the expected result.

    Regarding your LocaleChangerBean I have a few comments:

    (1) It’s weird that this bean is view scoped. As it manages the language parameter which is part of the current URL, it should be request scoped.

    (2) The URL in your address bar will only change if you send a redirect from your action method (which is the “else” part of your code). So returning null doesn’t make any sense because the URL will be the same as before.

    (3) If your bean returns something like /someview.xhtml?faces-redirect=true, you will also have to set the language to the desired value for the outbound URL rewriting to work correctly. Something like /someview.xhtml?lang=en&faces-redirect=true.

    (4) I think it won’t work very good if you just change the language in the view root. Instead you should use f:view and use the locale attribute to specify the locale you want to use for the view. You can simply reference your bean for that. See the example below.

    (5) To send the correct redirect, it would be easier to return something like pretty:someId. To redirect the current mapping you can use:

    return "pretty:"+PrettyContext.getCurrentInstance().getCurrentMapping().getId();
    

    So your class could look like this:

    @ManagedBean(name = "localeChanger")
    @RequestScoped
    public class LocaleChangerBean implements Serializable {
    
        private String language = "en";   // the default language
    
        public String englishAction() {
    
            /* set the language to the new value */
            this.language = "en";
    
            /* either this way */
            return "pretty:" + PrettyContext.getCurrentInstance().getCurrentMapping().getId();
    
            /* or this way */
            return FacesContext.getCurrentInstance().getViewRoot().getViewId() + "?lang=" + language + 
                        "&faces-redirect=true";
    
        }
    
        public Locale getLocale() {
            return new Locale(language);
        }
    
        ....
    }

    And use this in your view:

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

    This way JSF will _always_ use the language that your LocaleChangerBean has. So you don’t need to change the locale in the view any more. Just change the property in your bean and redirect, so that the URL changes.

    I hope this helps a bit. I think there are many ways to implement such a requirement. And that’s the way I would implement it. 🙂

    #24332

    xmapact
    Participant

    Hello Christian,

    Thank’s for your detailed answer. I tried to follow all your instructions and now I have code that works and shows language base. It also changes locale if URL contains right language code: http://www.example.com/en/…/...
    So it is something that I wanted.

    Now I’m using ordinary links to change my locales and they work just fine. Example:

    <h:link>
        <f:param name="lang" value="en"/>
        EN
    </h:link>
    <h:link>
        <f:param name="lang" value="ru"/>
        RU
    </h:link>

    For some reason I have a problem with jsf.js accessibility.
    According to Chrome console error I get is: GET http://localhost:8080/My_Project/faces/javax.faces.resource/jsf.js?ln=javax.faces&stage=Development 500 (OK) This GET request returns my custom error page.

    My guess is that pretty filter blocks access to this file. (Maybe not 🙂 )
    Is there a way to check this?
    Thank’s a lot for your answers.

    #24334

    A 500 result code represents an internal server error. Mostly this happens because there is some kind of exception thrown during request processing. So you don’t see any errors in the logs?

    #24335

    Yeah, I have to agree with Christian. There is probably an internal server error occurring somewhere, so finding that will most likely show you what is wrong.

    It *is* possible that you have some rules / mappings that are causing problems with this, but if all you’ve done is the i18n stuff, then it’s probably less likely.

    #24340

    xmapact
    Participant

    Christian, Lincoln, thanks for your answers.
    Fixed a problem with 500 error. There was a big mess with viewParams. I was trying to use them from template but not from template client. And at the same time I was injecting values using PrettyFaces 0_o.

    Now when I got it working fine with PrettyFaces disabled I’m trying to configure friendly URLs again.
    What I have is this config:

        <url-mapping id="base">
            <pattern value="/#{lang}" />
        </url-mapping>
    
        <url-mapping id="cat" parentId="base">
            <pattern value="/#{cat}" />
            <view-id value="/faces/productCat.xhtml" />
        </url-mapping>
    
        <url-mapping id="details" parentId="base">
            <pattern value="/#{category}/#{OSG}" />
            <view-id value="/faces/productDetails.xhtml" />
        </url-mapping>

    And I got problems with jsf.js access with this parameters. But it works just fine with this conf:

        <url-mapping id="details" parentId="base">
            <pattern value="/anyTextHere/#{category}/#{OSG}" />
            <view-id value="/faces/productDetails.xhtml" />
        </url-mapping>

    Problem is that I’m getting content of /faces/productDetails.xhtml instead of jsf.js JavaScript. I’m sure that I’m doing something wrong because Google doesn’t help =) Maybe you have any idea how to solve this problem. I’ll provide any other information if it is needed.

    Thank’s in advance,

    Andrei.

    #24344

    Yeah, this happens because your mapping is matching the URL of JSF resources. The pattern for the details mapping is /#{lang}/#{category}/#{OSG}. And requests like /javax.faces.resources/something/somefile.js are matching this pattern and are therefore rewritten.

    So common best practice to fix this issue is to restrict the regular expression for the path parameters. By default the pattern [^/]+ is used for path parameters which actually means “everything except /”. I guess restricting the pattern for the language parameter makes sense and should fix this issue. Something like this:

    
    <url-mapping id="base">
      <pattern value="/#{ /[a-z]{2}/ lang }" />
    </url-mapping>
    

    See this page for details:

    http://ocpsoft.org/docs/prettyfaces/3.3.3/en-US/html/Configuration.html#config.pathparams.regex

    #24346

    xmapact
    Participant

    Christian! Thanks a lot.
    Problem is solved. Lesson learned.

    I’m in PrettyParadise =)

Viewing 8 posts - 1 through 8 (of 8 total)

You must be logged in to reply to this topic.

Comments are closed.