Internationalization of Haskell programs using gettext: Difference between revisions
m (correct errors (thanks to Michael Thompson)) |
(Updated code, used recomendations from GNU gettext manual) |
||
Line 14: | Line 14: | ||
putStrLn $ "Hello, " ++ name ++ ", how are you?" | putStrLn $ "Hello, " ++ name ++ ", how are you?" | ||
</haskell> | </haskell> | ||
Using these | |||
[http://www.gnu.org/software/gettext/manual/gettext.html#Preparing-Strings recomendations], | |||
prepare strings and wrap them to some 'translation' function '__': | |||
<haskell>module Main where | <haskell>module Main where | ||
import IO | import IO | ||
import Text.Printf | |||
__ = id | __ = id | ||
Line 25: | Line 29: | ||
putStrLn (__ "Please enter your name:") | putStrLn (__ "Please enter your name:") | ||
name <- getLine | name <- getLine | ||
printf (__ "Hello, %s, how are you?") name | |||
</haskell> | </haskell> | ||
We will return to the definition of '__' a bit later; for now we will leave the function empty (<hask>id</hask>). | We will return to the definition of '__' a bit later; for now we will leave the function empty (<hask>id</hask>). | ||
Line 60: | Line 64: | ||
#: Main.hs:0 | #: Main.hs:0 | ||
msgid "Hello, " | msgid "Hello, %s, how are you?\n" | ||
msgstr "" | msgstr "" | ||
</pre> | |||
We are interested in the last part of this file -- the parts beginning with <tt>#: Main.hs:...</tt>. Each is followed by a pair of lines beginning with <tt>msgid</tt> and <tt>msgstr</tt>. <tt>msgid</tt> is the original text from the code, and <tt>msgstr</tt> is the translated string. Each language should have its own translation file. I will create two translations: German and English. | We are interested in the last part of this file -- the parts beginning with <tt>#: Main.hs:...</tt>. Each is followed by a pair of lines beginning with <tt>msgid</tt> and <tt>msgstr</tt>. <tt>msgid</tt> is the original text from the code, and <tt>msgstr</tt> is the translated string. Each language should have its own translation file. I will create two translations: German and English. | ||
Line 83: | Line 84: | ||
#: Main.hs:0 | #: Main.hs:0 | ||
msgid "Hello, | msgid "Hello, %s, how are you?\n" | ||
msgstr "Hallo, %s, wie geht es Ihnen?\n" | |||
</pre> | |||
msgstr ", wie geht es Ihnen?"</pre> | |||
=== Install translation files === | === Install translation files === | ||
Line 103: | Line 101: | ||
<haskell>module Main where | <haskell>module Main where | ||
import IO | import IO | ||
import Text.I18N.GetText | import Text.I18N.GetText | ||
Line 119: | Line 117: | ||
putStrLn (__ "Please enter your name:") | putStrLn (__ "Please enter your name:") | ||
name <- getLine | name <- getLine | ||
printf (__ "Hello, %s, how are you?\n") name | |||
</haskell> | </haskell> | ||
Here we added three initialization strings: | Here we added three initialization strings: |
Revision as of 10:32, 2 April 2009
The approach I'll talk about is based on GNU gettext utility. All my experience on this utility is taken from internationalizing Python applications. So I adapted this experience to the Haskell world.
Prepare program for internationalization
Let's start with an example. Suppose that we want to make the following program multilingual:
module Main where
import IO
main = do
putStrLn "Please enter your name:"
name <- getLine
putStrLn $ "Hello, " ++ name ++ ", how are you?"
Using these recomendations, prepare strings and wrap them to some 'translation' function '__':
module Main where
import IO
import Text.Printf
__ = id
main = do
putStrLn (__ "Please enter your name:")
name <- getLine
printf (__ "Hello, %s, how are you?") name
We will return to the definition of '__' a bit later; for now we will leave the function empty (id
).
Translate
The next step is to generate a POT file (a template which contains all strings to needed to be translated). For Python, C, C++ and Scheme there is the xgettext utility, but it doesn't support Haskell. So I created simple utility, that does the same thing for haskell files --- hgettext. You could find it on Hackage.
Now, from the directory that contains your project, run this command:
hgettext -k __ -o messages.pot Main.hs
It will gather all strings containing the function '__' from the Main.hs and write everything to messages.pot.
Now look at the resulting pot file:
# Translation file msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2009-01-13 06:05-0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" #: Main.hs:0 msgid "Please enter your name:" msgstr "" #: Main.hs:0 msgid "Hello, %s, how are you?\n" msgstr ""
We are interested in the last part of this file -- the parts beginning with #: Main.hs:.... Each is followed by a pair of lines beginning with msgid and msgstr. msgid is the original text from the code, and msgstr is the translated string. Each language should have its own translation file. I will create two translations: German and English.
To create a PO file for specific locale we should use the msginit utility.
To generate the German translation template run:
msginit --input=messages.pot --locale=de.UTF-8
And for English translations run:
msginit --input=messages.pot --locale=en.UTF-8
If we look at the generated files (en.po and de.po), we will see that English translation is completely filled, only the German PO file needs to be edited. So we fill it with following strings:
#: Main.hs:0 msgid "Please enter your name:" msgstr "Wie heißen Sie?" #: Main.hs:0 msgid "Hello, %s, how are you?\n" msgstr "Hallo, %s, wie geht es Ihnen?\n"
Install translation files
Now we have to create directories where these translations should be placed. Originally all translation files are placed in the folder /usr/share/locale/ , but you are free to select a different place. Run:
mkdir -p {de,en}/LC_MESSAGES
This will create two sub-directories 'de' and 'en', each containing LC_MESSAGES, in the current directory. Now we use the msgfmt tool to encode our po files to mo files (binary translation files):
msgfmt --output-file=en/LC_MESSAGES/hello.mo en.po msgfmt --output-file=de/LC_MESSAGES/hello.mo de.po
Turn on internationalization in the code
Ok, now the preparatory tasks are done. The final step is to modify the code to support the internationalization:
module Main where
import IO
import Text.I18N.GetText
import System.Locale.SetLocale
import System.IO.Unsafe
__ :: String -> String
__ = unsafePerformIO . getText
main = do
setLocale LC_ALL (Just "")
bindTextDomain "hello" "."
textDomain "hello"
putStrLn (__ "Please enter your name:")
name <- getLine
printf (__ "Hello, %s, how are you?\n") name
Here we added three initialization strings:
setLocale LC_ALL (Just "")
bindTextDomain "hello" "."
textDomain "hello"
You'll have to download the setlocale package to enable the first function: it sets the current locale to the default value. The next two functions tell gettext to take the "hello.mo" message file from the locale directory (I set it to ".", but in general case, this directory should be passed from the package configuration).
The final step is to define the function '__'. It simply calls getText
from the module Text.I18N.GetText
. Its type is String -> IO String
so I used unsafePerformIO
to make it simpler the.
Run and test the program
Now you can build and try the program in different locales:
user> ghc --make Main.hs [1 of 1] Compiling Main ( Main.hs, Main.o ) Linking Main ... user> LOCALE=en_US.UTF-8 ./Main Please enter your name: Bond Hello, Bond, how are you? user> LOCALE=de_DE.UTF-8 ./Main Wie heißen Sie? Bond Hallo, Bond, wie geht es Ihnen? user>
Distribute internationalized cabal package
TBD