Following up to the part 3 of my series about implementing a multilingual interface for this blog, I now present the things to make the language-magic happen: Middleware and context processors.

Disclaimer

When I wrote this code, I wasn't aware of the threadlocals pattern. In the next iteration of the code, I'll make use of it. For now, it works good enough so it stays.

Views, part 2

Those who read carefully or tried to use the code I posted on my previous post, would notice that I was referencing request.url_lang, which of course would lead to an error, since it's not obvious on when and where it's set. So let's present this:

Middleware

Here's a little helper function that does this:

re_language = re.compile(r'^/(?P<lang>[a-z]{2})/.*')
def put_language(request):
    m = re_language.match(request.path)
    if m:
        lang = m.group('lang')
        request.url_lang = lang
        #print request.url_lang
    else: raise Exception()
    return None

You can see that it uses a regular exception, so it's trivial to set it up to suit different needs.

Now we have another problem, that our URLconfs and views would have to be aware of this URL scheme, and it would make it tedious. So, we go and rewrite request.path like this:

class LanguageMiddleware:
    def process_request(self, request):
        for key in exceptions:
             if request.path.startswith(key): 
                 request.url_lang = request.LANGUAGE_CODE
                 return exceptions[key](request)
        try:
            put_language(request)
            request.oldpath = request.path
            request.path = request.path[3:]
        except Exception:
            if request.POST: return None
            return HttpResponseRedirect("/"+request.LANGUAGE_CODE+request.path)          
        if request.url_lang != request.LANGUAGE_CODE:
            request.different_lang = True
        return None

Warning

Django documentation states clearly that I shouldn't treat request.path as a writable property. Nevertheless I do. So this may break in the future.

This code does many little things:

  1. It sets the request.url_path attribute

  2. It removes the language part of the URL that django will use.

  3. It stores the request.path into request.oldpath. I use this in my 404 template to show the URL a visitor will actually see. Try to put a random address and see that the path displayed is the correct one.

  4. It redirects requests that don't have any language information set, so that there are no links and searches directly. Try to delete the language from the URL, and see what happens.

  5. It sets a flag if the URL language is different from the detected language, so we can present a message to the visitor. Try replacing the en part of the url for this post with el.

  6. Finally a wart. For some views, I don't want all this to happen, so I've setup an exceptions table:

    exceptions = {
                     '/i18n/': ret_none,
                     '/static/': ret_none,
                     '/feeds/': ret_none,  
                     '/accounts/': ret_none,          
                     '/en/about/': put_language,                
                     '/el/about/': put_language,                
                     '/google': ret_none,                
                     '/robots.txt': ret_none,                
                     }
       ret_none = lambda x: (None)
       
    This isn't so good, since if I want to add a new flatpage (like /el/about/) I'll have to add another entry. I should move this to the database somehow, but I haven't had the time to figure it out. If you have an idea, please tell :)

Context processors

Nothing big, really:

def language_note(request):
    try:
        if request.url_lang != request.LANGUAGE_CODE:
            if request.LANGUAGE_CODE == 'el':
                note = 'Βλέπετε αυτή τη σελίδα στα Αγγλικά. Είναι επίσης διαθέσιμη στα <a href="/el%s">Ελληνικά</a>'%(request.path,)
            else:
                note = 'You are viewing this page in Greek. It is also available in <a href="/en%s">English</a>'%(request.path,)        

            return {'language_note':note}
    except AttributeError:
        pass
    return {}

I should make this handle more than one available language, but I have no need right now, so I won't ;)

Epilogue

That's all for now. This series is over. I have some ideas about making this a bit simpler, but I have no time to fool around right now. In fact, if I have to make another multilingual site, I'll probably use django-multilingual and put some effort in improving that.

It was good scratching the itch, though :)

June 3, 2007, 4:39 p.m. More (630 words) 2 comments Feed
Previous entry: Ημέρα Αμαλίας Καλυβινού
Next entry: Why aren't there any good RAD frameworks for rich clients?

Comments

1

Comment by wathi , 2 years, 8 months ago :

Hi Orestis,

when switching the language onpage i recommend to also switch the document language in document header.

(<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">)

I noticed that In Firefox the code-parts are moving out of bounds. I guess it is the same in different browser. You can fix this with setting overflow:auto and a width in your stylesheet.

Ps: Why are the links for comment authors not followed? You delete spam thats goes through anyway. Dont you? :)

Have a nice day, wathi.

2

Comment by Orestis Markou , 2 years, 8 months ago :

Regarding the xml header: Good point, I've overlooked that.

Regarding the code blocks: I have in mind to use a code highlighter, so I didn't put much into the current setup.

Regarding the links in comments, I think it's good practice to have rel=nofollow in all content submitted by 3rd-parties, no matter what is spam and what isn't...

Thanks for the comment!


This post is older than 30 days and comments have been turned off.