My tutorial about internationalization of Haskell programs will not be complete without explaining how to configure and install Haskell package with internationalization support. Who tried all steps of previous tutorial could admit, that installation of localized data is very long, complex and boring routine, so there must be a way to simplify it (a Haskell way).
Now it is. From the version 0.1.5 of hgettext package, there is included module, that teaches Cabal to install language files.
So, download new hgettext and create for our hello program real world installer.
Directory structure
Currently we have following files:
Main.hsThe `hello` program itself.messages.potTemplate file, which contain all strings to be translated. This file
should be included into the distribution to allow other users to
generate translation file for their language.en.po, de.poTranslations to the English and German
languages. These files should be installed to the `locale` folder and
our program has to be able to find them (has to know where they going
to be installed)
Any other files could be generated from the previous, so they shouldn't be included to the distribution package.
Let's create the directory structure for our project. This is simple project, so directory structure should be simple too. Here it is:
hello\
|
|-po\
| |
| |-messages.pot
| |-en.po
| |-de.po
|
|-src\
|
|-Main.hs
Create install script
In order to create a cabal package, we have to add only two files. The first is hello.cabal:
Name: hello
Version: 0.1.3
Cabal-Version: >= 1.6
License: BSD3
Author: James Bond
Maintainer: James.Bond@MI6.bi
Copyright: 2009 James Bond
Category: Hello
Synopsis: Internationalized Hello sample
Build-Type: Simple
Extra-Source-Files: po/*.po po/*.pot
x-gettext-po-files: po/*.po
x-gettext-domain-name: hs-hello
Executable hello
Main-Is: Main.hs
Hs-Source-Dirs: src
Build-Depends: base,hgettext >= 0.1.5, setlocale
This is standard .cabal file, but there we added two more lines:
x-gettext-po-filesTells cabal where ar PO files to installx-gettext-domain-nameSets the domain name, under which files will be installed
For other details see documentation for hgettext Distribution.Simple.I18N.GetText module.
Note that we also enumerated *.po files in the extra-source-files section to add them to the distribution package.
The second file to create --- Setup.hs:
import Distribution.Simple.I18N.GetText
main = gettextDefaultMain
The gettextDefaultMain function substitutes the defaultMain function, but also adds several install hooks to the cabal package, to handle internationalization stuff.
Update the program code
So our installer knows where to put the *.po files and the domain name for them. Our code should know it too --- to make proper initialization. It is not Haskell way to duplicate same information twice, so let's modify the code to get the initialization parameters directly from the installer:
module Main where
import Text.Printf
import Text.I18N.GetText
import System.Locale.SetLocale
import System.IO.Unsafe
__ :: String -> String
__ = unsafePerformIO . getText
main = do
setLocale LC_ALL (Just "")
bindTextDomain __MESSAGE_CATALOG_DOMAIN__ (Just __MESSAGE_CATALOG_DIR__)
textDomain __MESSAGE_CATALOG_DOMAIN__
putStrLn (__ "Please enter your name:")
name <- getLine
printf (__ "Hello, %s, how are you?\n") name
So, the only lines were changed are:
bindTextDomain __MESSAGE_CATALOG_DOMAIN__ (Just __MESSAGE_CATALOG_DIR__)
textDomain __MESSAGE_CATALOG_DOMAIN__
Nice. __MESSAGE_CATALOG_DOMAIN__ and __MESSAGE_CATALOG_DIR__ are macro definitions, whose hold configured strings from the Cabal.
That's all?
Actually, yes. Now you could configure, build and install newly created package by invoking commands:
runhaskell Setup.hs configure
runhaskell Setup.hs build
runhaskell Setup.hs install
And test it.
Have a nice weekend :)
PS: Complete project tarball you can find here.
Hi!
ReplyDeleteThanks a lot for plumbing this big hole in Haskell's eco-system. :-)
I asked about that some time ago (haskell-cafe) and received (almost) zero feedback.
However, having proper gettext-like support in Haskell is essential so one can write i18n apps.
Unfortunately, atm, I'm too busy with Pythobn/Django :-(
Sincerely,
Gour
Thank You for comment,
ReplyDeleteIt is very nice to see, that my work is useful for others :)
This is great work, but it seems that you're setting the locale at compile time rather than at run time. On Linux and other Unix-based systems, this won't work. The user can change the locale at any time (e.g. by logging out, choosing a new locale, and logging in again) and all installed programs must respect the new locale. That's why gettext determines the locale at run time.
ReplyDeletefreeourbooks: I think, you missed a point in my thoughts (yes, I explaining not so clear as I want :)). I implemented similar to GNU's gettext internationalization. Look at the my previous post where I wrote the minimal Hello World and tried dynamically change the locale (at the end of the text).
ReplyDeleteAnyway, following post shows more sophisticated example, where I change locale of the Gtk application on the fly, without program restarting :).