[web-devel] [Yesod] rendering different templates for different languages

Michael Snoyman michael at snoyman.com
Mon Feb 21 07:25:23 CET 2011

A proper i18n solution is high on my wish list right now, but I've
purposely avoided implementing one so far since I'd rather wait until
I think we have a good solution as opposed to implementing an
acceptable solution now. But let me share my ideas, it might help you
out here.

In general, it's very uncommon that you need a completely separate set
of templates for each language. Your markup, classes, styles, and
logic will likely be identical for each language, and creating a
separate template for each will just result in a lot of pain in the
long run. Instead, you're likely better off having a single template
and just translating strings.

I've blogged about this before[1]. My idea is to use a datatype for
your translatable strings, and then have a function that takes a
language and a value and returns the translated string. A simple

    data Strings = Hello | Person String Int
    toEnglish Hello = "Hello"
    toEnglish (Person name age) = name ++ " is " ++ show age ++ "
years old" -- obviously need to check if person is 1 year old and

    toHebrew Hello = "שלום"
    toHebrew (Person name age) = name ++ " הוא בן " ++ show age ++ " שנים"

The nice thing about this approach is you have the full power of
Haskell to address typical translation issues, such as pluralization,
word order and gender matching. (As a counter example, at work, we use
XSLT for this, and then you get the full power of XSLT for solving the
problem ::cringe::.)

You can then use the languages[2] function from Yesod to help you out:

    getRenderString = chooseFunc `fmap` languages
         chooseFunc [] = toEnglish -- default language
         chooseFunc ("en":_) = toEnglish
         chooseFunc ("he":_) = toHebrew
         chooseFunc (_:x) = chooseFunc x

Then you can write a handler function like:

getPersonR name age = do
    render <- getRenderString
    defaultLayout [$hamlet|
<h1>#{render Hello}
<p>#{render $ Person name age}

Which will work for English and Hebrew just fine. Ideally, I would
like to add support to Hamlet for this directly, involving a String
rendering function similar to the URL rendering function already in
place. But for the moment, this should work.

I'd love to hear peoples opinions about this.


[1] http://docs.yesodweb.com/blog/i18n-in-haskell
[2] http://hackage.haskell.org/packages/archive/yesod-core/

On Sun, Feb 20, 2011 at 11:19 PM, Dmitry Kurochkin
<dmitry.kurochkin at gmail.com> wrote:
> Hi all.
> I want a handler to render different templates for different languages.
> I have getCurrentLanguage function and now I try to do something like:
>    getRootR = do
>        currentLanguage <- getCurrentLanguage
>        defaultLayout $ do
>            addWidget $(widgetFile $ currentLanguage ++ "/homepage")
> This results in:
>    GHC stage restriction: `currentLanguage'
>      is used in a top-level splice or annotation,
>      and must be imported, not defined locally
> This makes sense to me, because TH is calculated at compile time. I
> would like to hear ideas how to work around this restriction. Perhaps
> there is an existing solution in Yesod?
> At the moment, the best I could think of is smth like this:
>    getRootR = do
>        currentLanguage <- getCurrentLanguage
>        defaultLayout $ do
>            case currentLanguage of
>                "en" -> addWidget $(widgetFile  "en/homepage")
>                ... and so on for each language ...
> Obviously, this is not a solution taking in account that there are many
> languages and many handlers.
> I was considering creating a global (template file name -> rendered
> template) map. But I am not sure this is really feasible.
> Regards,
>  Dmitry
> _______________________________________________
> web-devel mailing list
> web-devel at haskell.org
> http://www.haskell.org/mailman/listinfo/web-devel

More information about the web-devel mailing list