[Haskell-cafe] haskell i18n best practices
Felipe Almeida Lessa
felipe.lessa at gmail.com
Fri Sep 30 12:29:35 CEST 2011
On Thu, Sep 29, 2011 at 7:54 PM, Paulo Pocinho <pocinho at gmail.com> wrote:
> Still uncomfortable with i18n, I learned about the article "I18N in
> Haskell" in yesod blog . I'd like to hear more about it.
Yesod's approach is pretty nice . The idea is to have a data type
with all your messages, like
data Message =
MyNameIs String |
For each of your supported languages, you provide a rendering function
(they may be in separate source files)
render_en_US :: Message -> String
render_en_US Hello = "Hello!"
render_en_US WhatsYourName = "What's your name?"
render_en_US (MyNameIs name) = "My name is " ++ name ++ "."
render_en_US (Ihave_apples 0) = "I don't have any apples."
render_en_US (Ihave_apples 1) = "I have one apple."
render_en_US (Ihave_apples n) = "I have " ++ n ++ " apples."
render_en_US GoodBye = "Good bye!"
render_pt_BR :: Message -> String
render_pt_BR Hello = "Olá!"
render_pt_BR WhatsYourName = "Como você se chama?"
render_pt_BR (MyNameIs name) = "Eu me chamo " ++ name ++ "."
render_pt_BR (Ihave_apples 0) = "Não tenho nenhuma maçã."
render_pt_BR (Ihave_apples 1) = "Tenho uma maçã."
render_pt_BR (Ihave_apples 2) = "Tenho uma maçã."
render_pt_BR (Ihave_apples n) = "Tenho " ++ show n ++ " maçãs."
render_pt_BR GoodBye = "Tchau!"
Given those functions, you can construct something like
type Lang = String
render :: [Lang] -> Message -> String
render ("pt" :_) = render_pt_BR
render ("pt_BR":_) = render_pt_BR
render ("en" :_) = render_en_US
render ("en_US":_) = render_en_US
render (_:xs) = render xs
render _ = render_en_US
So 'r = render ["fr", "pt"]' will do the right thing. You just need
to pass this 'r' around in your code. Using is easy and clear:
putStrLn $ r Hello
putStrLn $ r WhatsYourName
name <- getLine
putStrLn $ r MyNameIs "Alice"
putStrLn $ r (Ihave_apples $ length name `mod` 4)
putStrLn $ r GoodBye
This approach is nice for several reasons:
- Builtin support for complicated messages. Making something like
Ihave_apples in gettext would be hard. Each language has its own
rules, and you need to encode all of them in your code. On this
example, my render_pt_BR recognizes and treats differently the 2
apples case. If you didn't think about it when you wrote your code
(using gettext), you'd need to change your code for pt_BR.
- Fast processing. "render" as I've coded above looks at the
language list just once. After that, it's just GHC's pattern
- Fast startup. No need to look for strings on the hard drive.
- Flexible. You may try several extensions, depending on your needs
(a) Using a type class (like Yesod) if you don't want one big data type.
(b) Using Text instead of String. Or even Builder.
The biggest drawback is lack of tool support and lack of "translators'
expertise". gettext has a lot of inertia and is used everywhere on a
FLOSS system. But as Ertugrul Soeylemez said, if you're targeting
Windows, _not_ using gettext should be an advantage (less pain while
More information about the Haskell-Cafe