[TemplatesHSPI18n: fixed typos, added html/messages.zip Makefile target Jeremy Shaw **20120207211912 Ignore-this: 896ff9b0bd182ce139c696d81a6e6c3d ] hunk ./Makefile 27 -Templates.html : $(TEMPLATES_DEPS) $(TEMPLATES_DEMOS) factorial.tpl-inc +Templates.html : $(TEMPLATES_DEPS) $(TEMPLATES_DEMOS) factorial.tpl-inc html/messages.zip hunk ./Makefile 32 +html/messages.zip: messages/standard/en.msg messages/standard/en-GB.msg messages/standard/jbo.msg messages/thing/en.msg + zip $@ $^ + hunk ./TemplatesHSP.markdown.lhs 495 - +

[Source code for the app is here.]

hunk ./TemplatesHSPI18n.markdown.lhs 7 -
- -> {-# LANGUAGE FlexibleContexts, FlexibleInstances, TemplateHaskell, MultiParamTypeClasses, OverloadedStrings #-} -> {-# OPTIONS_GHC -F -pgmFtrhsx #-} -> module Main where -> -> import Control.Applicative ((<$>), optional) -> import Control.Arrow (first, second) -> import Control.Monad (MonadPlus, msum) -> import Control.Monad.Reader (ReaderT, ask, runReaderT) -> import Control.Monad.Trans (MonadIO(liftIO)) -> import Data.ByteString.Char8 (unpack) -> import Data.Function (on) -> import Data.List (sortBy) -> import Data.Maybe (fromMaybe) -> import Data.Map (Map, fromList) -> import qualified Data.Map as Map -> import Data.Text (Text) -> import qualified Data.Text as Text -> import qualified Data.Text.Lazy as LazyText -> import Happstack.Server ( Happstack, ServerPart, ServerPartT, dir, getHeaderM, lookTexts', mapServerPartT -> , nullConf, nullDir, queryString, simpleHTTP, acceptLanguage, bestLanguage) -> import Happstack.Server.HSP.HTML -> import qualified HSX.XMLGenerator as HSX -> import Text.Shakespeare.I18N (RenderMessage(..), Lang, ToMessage(..), mkMessage, mkMessageFor, mkMessageVariant) -> import System.Random (randomRIO) -> - -
- - hunk ./TemplatesHSPI18n.markdown.lhs 11 -application usuable by people that speak a variety of different +application usuable by people that speak different hunk ./TemplatesHSPI18n.markdown.lhs 13 -conventions for things like formatting times and dates and -currency. +conventions for things like formatting times and dates, currency, etc. hunk ./TemplatesHSPI18n.markdown.lhs 24 +> hunk ./TemplatesHSPI18n.markdown.lhs 35 -internationalization. +internationalization. We will build on top of the `shakespeare-i18n` library. + +As usual, we start off with a bunch of imports and pragmas: + +
+ +> {-# LANGUAGE FlexibleContexts, FlexibleInstances, TemplateHaskell, +> MultiParamTypeClasses, OverloadedStrings #-} +> {-# OPTIONS_GHC -F -pgmFtrhsx #-} +> module Main where +> +> import Control.Applicative ((<$>)) +> import Control.Monad (msum) +> import Control.Monad.Reader (ReaderT, ask, runReaderT) +> import Control.Monad.Trans (MonadIO(liftIO)) +> import Data.Map (Map, fromList) +> import qualified Data.Map as Map +> import Data.Text (Text) +> import qualified Data.Text as Text +> import Happstack.Server ( ServerPart, ServerPartT, dir, lookTexts', mapServerPartT +> , nullConf, nullDir, queryString, simpleHTTP, acceptLanguage, bestLanguage) +> import Happstack.Server.HSP.HTML +> import qualified HSX.XMLGenerator as HSX +> import Text.Shakespeare.I18N (RenderMessage(..), Lang, mkMessage, mkMessageFor, mkMessageVariant) +> import System.Random (randomRIO) +> + +
hunk ./TemplatesHSPI18n.markdown.lhs 82 -> translation_en Hello = "hello" +> translation_en Hello = Text.pack "hello" hunk ./TemplatesHSPI18n.markdown.lhs 129 - 2. having to call 'translate' explicitly is boring and tedious + 2. having to call 'translate' explicitly is boring, tedious, and error prone hunk ./TemplatesHSPI18n.markdown.lhs 131 - 3. having to pass around the desired 'lang' manually is also boring and tedious + 3. having to pass around the desired 'lang' manually is also boring, tedious, and error prone hunk ./TemplatesHSPI18n.markdown.lhs 137 -`shakespeare-i18n` provides one simple class for providing translations: +`shakespeare-i18n` provides a simple class for providing translations: hunk ./TemplatesHSPI18n.markdown.lhs 151 -`renderMessage` is pretty straight-forward. - -It takes a list of preferred languages a message datatype (such as `Message` type we defined above) and returns the best matching translation. The only mysterious part is the `master` argument. Personally, I think `variant` would be a better name for the argument. The argument exists so that you can provide more than one set of translations for the same message type. +`renderMessage` is pretty straight-forward. It takes a list of preferred languages and a message datatype (such as `Message` type we defined above) and returns the best matching translation. The only mysterious part is the `master` argument. (Personally, I think `variant` might be a better name for the argument). The argument exists so that you can provide more than one set of translations for the same message type. hunk ./TemplatesHSPI18n.markdown.lhs 184 + 4. the `.msg` files must be UTF-8 encoded hunk ./TemplatesHSPI18n.markdown.lhs 203 -
+
hunk ./TemplatesHSPI18n.markdown.lhs 212 -
+
hunk ./TemplatesHSPI18n.markdown.lhs 221 -
+
hunk ./TemplatesHSPI18n.markdown.lhs 253 -> + hunk ./TemplatesHSPI18n.markdown.lhs 260 -> mkMessageFor "DemoApp" "Thing" "messages/thing" "en" +> mkMessageFor "DemoApp" "Thing" "messages/thing" ("en") hunk ./TemplatesHSPI18n.markdown.lhs 281 -> -> mkMessage "DemoApp" "messages/standard" "en" +> mkMessage "DemoApp" "messages/standard" ("en") hunk ./TemplatesHSPI18n.markdown.lhs 320 -It has created a new type for us `DemoAppMessage` where each constructor is derived from the constructors found in the `en.msg` file. The construct names all have the prefix `Msg`. That is just to avoid name collisions with the other constructors in your application. +It has created a new type for us `DemoAppMessage` where each constructor is derived from the constructors found in the `en.msg` file. The constructor names all have the prefix `Msg`. That is just to avoid name collisions with the other constructors in your application. hunk ./TemplatesHSPI18n.markdown.lhs 335 -

shakespeare-i18n Variables and interpolation (#{ })

+

Constructor arguments, #{ }, and plurals

hunk ./TemplatesHSPI18n.markdown.lhs 337 -The `Problems` constructor in the `en.msg` file appears considerably more complicate than the `Hello` and `Goodbyte` cases: +The `Problems` constructor in the `en.msg` file appears considerably more complicate than the `Hello` and `Goodbye` cases: hunk ./TemplatesHSPI18n.markdown.lhs 339 -
+
hunk ./TemplatesHSPI18n.markdown.lhs 345 -There are two things going on here. +There are a few things going on here. hunk ./TemplatesHSPI18n.markdown.lhs 347 -

type annotations

+

Type Annotations

hunk ./TemplatesHSPI18n.markdown.lhs 369 -Will convert `n` to a `String` and splice the `String` into the message. The expression inside the `#{ }` must be a pure expression, and it must be a type that is an instance of the `ToMessage` class: +will convert `n` to a `String` and splice the `String` into the message. The expression inside the `#{ }` must be a pure expression and it must have a type that is an instance of the `ToMessage` class: hunk ./TemplatesHSPI18n.markdown.lhs 375 -> -> instance ToMessage String -> instance ToMessage Text hunk ./TemplatesHSPI18n.markdown.lhs 382 -

Handling Plurals and other language specifics

+

Handling plurals and other language specifics

hunk ./TemplatesHSPI18n.markdown.lhs 400 -Looking at `en.msg` you notice that we need to use `plural_en` twice to make the grammar sound natural. +Looking at `en.msg` you notice that we need to use `plural_en` twice to make the grammar sound natural. When creating messages is good to use whole phrases and sentences because changes in one part of a sentence can affect other parts of the sentence. Rules about plurals, word order, gender agreement, etc, vary widely from one language to the next. So it is best to assume as little as possible and give the translators as much flexibility as possible. hunk ./TemplatesHSPI18n.markdown.lhs 425 -> thing_tr "en" TypeError = "type error" -> thing_tr "en" SegFault = "segmentation fault" +> thing_tr lang TypeError | lang == "en" = "type error" +> thing_tr lang SegFault | lang == "en" = "segmentation fault" hunk ./TemplatesHSPI18n.markdown.lhs 433 -The `mkMessageFor` function allows use to create translations for an existing type: +The `mkMessageFor` function allows us to create translations for an existing type: hunk ./TemplatesHSPI18n.markdown.lhs 437 -mkMessageFor :: - String -- ^ master type - -> String -- ^ data to translate - -> FilePath -- ^ path to `.msg` files - -> Lang -- ^ default language - -> Q [Dec] +> mkMessageFor :: +> String -- ^ master type +> -> String -- ^ data to translate +> -> FilePath -- ^ path to `.msg` files +> -> Lang -- ^ default language +> -> Q [Dec] hunk ./TemplatesHSPI18n.markdown.lhs 446 -We can create a set of `.msg` files specific for the `Thing` type like this: +We can create a set of `.msg` files for the `Thing` type like this (note the file path): hunk ./TemplatesHSPI18n.markdown.lhs 469 +> -- autogenerated by `mkMessageFor` hunk ./TemplatesHSPI18n.markdown.lhs 482 +
hunk ./TemplatesHSPI18n.markdown.lhs 504 -The instance will need to know what the clients preferred languages +The instance will need to know what the client's preferred languages hunk ./TemplatesHSPI18n.markdown.lhs 511 +> hunk ./TemplatesHSPI18n.markdown.lhs 515 -Depending on your application, you might be using a different custom server monad. But, that is all we need for this demo. - hunk ./TemplatesHSPI18n.markdown.lhs 523 +> hunk ./TemplatesHSPI18n.markdown.lhs 531 +> pageTemplate :: (EmbedAsChild I18N body) => String -> body -> I18N XML +> pageTemplate title body = +> defaultTemplate title () +>
+> <% body %> +>
    +> <% mapM (\lang ->
  • <% lang %>
  • ) +> (["en", "en-GB", "jbo"] :: [String]) %> +>
+>
+> hunk ./TemplatesHSPI18n.markdown.lhs 544 -> defaultTemplate "home" ()

<% MsgHello %>

- +> pageTemplate "home" +>

<% MsgHello %>

+> hunk ./TemplatesHSPI18n.markdown.lhs 549 -> defaultTemplate "goodbye" ()

<% MsgGoodbye %>

- +> pageTemplate "goodbye" +>

<% MsgGoodbye %>

+> hunk ./TemplatesHSPI18n.markdown.lhs 554 -> defaultTemplate "problems" ()

<% MsgProblems n thing %>

+> pageTemplate "problems" +>

<% MsgProblems n thing %>

+> hunk ./TemplatesHSPI18n.markdown.lhs 562 +Getting the language preferences from `ReaderT [Lang]` is just one possibility. Your application may already have a place to store session data that you can get the preferences from, or you might just stick the preferences in a cookie. + hunk ./TemplatesHSPI18n.markdown.lhs 575 -You should not assume that the `Accept-Language` header is always correct. It is best to allow the user a way to override the `Accept-Language` header. That override could be stored in their user account or in session data. In this example we will just use a `QUERY_STRING` parameter `_LANG` to override the `Accept-Language` header. +You should not assume that the `Accept-Language` header is always correct. It is best to allow the user a way to override the `Accept-Language` header. That override could be stored in their user account, session data, a cookie, etc. In this example we will just use a `QUERY_STRING` parameter `_LANG` to override the `Accept-Language` header. hunk ./TemplatesHSPI18n.markdown.lhs 584 -> langs <- bestLanguage <$> acceptLanguage +> langs <- bestLanguage <$> acceptLanguage hunk ./TemplatesHSPI18n.markdown.lhs 586 +> hunk ./TemplatesHSPI18n.markdown.lhs 612 + +

[Source code for the app is here.]

+

[You will also need to download and unzip the message files here.]

+ +

Conclusions

+ +In this section we showed how to use `HSX` and `Happstack.Server.I18N`, and `shakespeare-i18n` together to provide an i18n solution. However, there are no dependencies between those libraries and modules. So, you can use other solutions to provide translations for `HSX`, or you can use `shakespeare-i18n` with other template systems. + +One thing that would make `shakespeare-i18n` better is a utility to help keep the `.msg` files up-to-date. I have describe my ideas for a tool here. We just need a volunteer to implement it. + hunk ./messages/standard/en-GB.msg 1 -Hello: greetings -Goodbye: seeya +Hello: all right? +Goodbye: cheerio hunk ./messages/standard/jbo.msg 1 -Hello: greetings -Goodbye: seeya +Hello: coi +Goodbye: co'o hunk ./theme.css 174 +.wide +{ + width: 90em; +} + +