Internationalization and Localization

来源:互联网 发布:psv重构数据库如何进入 编辑:程序博客网 时间:2024/05/22 02:12

Table of Contents

  • 1   Internationalization and Localization
    • 1.1   Getting Started
    • 1.2   Using Babel
    • 1.3   Back To Work
    • 1.4   Testing the Application
    • 1.5   Fallback Languages
    • 1.6   Translations Within Templates
    • 1.7   Lazy Translations
    • 1.8   Producing a Python Egg
    • 1.9   Plural Forms
  • 2   Summary
  • 3   Further Reading

Internationalization and localization are means of adapting software for non-native environments, especially for other nations and cultures.

Parts of an application which might need to be localized might include:

  • Language
  • Date/time format
  • Formatting of numbers e.g. decimal points, positioning of separators, character used as separator
  • Time zones (UTC in internationalized environments)
  • Currency
  • Weights and measures

The distinction between internationalization and localization is subtle but important. Internationalization is the adaptation of products for potential use virtually everywhere, while localization is the addition of special features for use in a specific locale.

For example, in terms of language used in software, internationalization is the process of marking up all strings that might need to be translated whilst localization is the process of producing translations for a particular locale.

Pylons provides built-in support to enable you to internationalize language but leaves you to handle any other aspects of internationalization which might be appropriate to your application.

Note

Internationalization is often abbreviated as I18N (or i18n or I18n) where the number 18 refers to the number of letters omitted. Localization is often abbreviated L10n or l10n in the same manner. These abbreviations also avoid picking one spelling (internationalisation vs. internationalization, etc.) over the other.

In order to represent characters from multiple languages, you will need to utilize Unicode. This document assumes you have read the Unicode document.

1   Internationalization and Localization

By now you should have a good idea of what Unicode is, how to use it in Python and which areas of you application need to pay specific attention to decoding and encoding Unicode data.

This final section will look at the issue of making your application work with multiple languages.

Pylons uses the Python gettext module for internationalization. It is based off the GNU gettext API.

1.1   Getting Started

Everywhere in your code where you want strings to be available in different languages you wrap them in the _() function. There are also a number of other translation functions which are documented in the API reference at http://pylonshq.com/docs/module-pylons.i18n.translation.html

Note

The _() function is a reference to the ugettext() function. _() is a convention for marking text to be translated and saves on keystrokes. ugettext() is the Unicode version of gettext(); it returns unicode strings.

In our example we want the string 'Hello' to appear in three different languages: English, French and Spanish. We also want to display the word 'Hello' in the default language. We'll then go on to use some pural words too.

Lets call our project translate_demo:

1
$ paster create -t pylons translate_demo

Now lets add a friendly controller that says hello:

12
$ cd translate_demo$ paster controller hello

Edit controllers/hello.py to make use of the _() function everywhere where the string Hello appears:

 1 2 3 4 5 6 7 8 9101112131415
import loggingfrom pylons.i18n import get_lang, set_langfrom translate_demo.lib.base import *log = logging.getLogger(__name__)class HelloController(BaseController):    def index(self):        response.write('Default: %s<br />' % _('Hello'))        for lang in ['fr','en','es']:            set_lang(lang)            response.write("%s: %s<br />" % (get_lang(), _('Hello')))

When writing wrapping strings in the gettext functions, it is important not to piece sentences together manually; certain languages might need to invert the grammars. Don't do this:

123
# BAD!msg = _("He told her ")msg += _("not to go outside.")

but this is perfectly acceptable:

12
# GOODmsg = _("He told her not to go outside")

The controller has now been internationalized, but it will raise a LanguageError until we have setup the alternative language catalogs.

GNU gettext use three types of files in the translation framework.

POT (Portable Object Template) files

The first step in the localization process. A program is used to search through your project's source code and pick out every string passed to one of the translation functions, such as _(). This list is put together in a specially-formatted template file that will form the basis of all translations. This is the .pot file.

PO (Portable Object) files

The second step in the localization process. Using the POT file as a template, the list of messages are translated and saved as a .po file.

MO (Machine Object) files

The final step in the localization process. The PO file is run through a program that turns it into an optimized machine-readable binary file, which is the .mo file. Compiling the translations to machine code makes the localized program much faster in retrieving the translations while it is running.

GNU gettext provides a suite of command line programs for extracting messages from source code and working with the associated gettext catalogs. The Babel project provides pure Python alternative versions of these tools. Unlike the GNU gettext tool xgettext', Babel supports extracting translatable strings from Python templating languages (currently Mako and Genshi).

1.2   Using Babel

http://www.edgewall.org/gfx/babel_logo.png

To use Babel, you must first install it via easy_install. Run the command:

1
$ easy_install Babel

Pylons (as of 0.9.6) includes some sane defaults for Babel's distutils commands in the setup.cfg file.

It also includes an extraction method mapping in the setup.py file. It is commented out by default, to avoid distutils warning about it being an unrecognized option when Babel is not installed. These lines should be uncommented before proceeding with the rest of this walk through:

1234
message_extractors = {'translate_demo': [        ('**.py', 'python', None),        ('templates/**.mako', 'mako', None),        ('public/**', 'ignore', None)]},

We'll use Babel to extract messages to a .pot file in your project's i18n directory. First, the directory needs to be created. Don't forget to add it to your revision control system if one is in use:

123
$ cd translate_demo$ mkdir translate_demo/i18n$ svn add translate_demo/i18n

Next we can extract all messages from the project with the following command:

1234567
$ python setup.py extract_messagesrunning extract_messagesextracting messages from translate_demo/__init__.pyextracting messages from translate_demo/websetup.py...extracting messages from translate_demo/tests/functional/test_hello.pywriting PO template file to translate_demo/i18n/translate_demo.pot

This will create a .pot file in the i18n directory that looks something like this:

 1 2 3 4 5 6 7 8 910111213141516171819202122
# Translations template for translate_demo.# Copyright (C) 2007 ORGANIZATION# This file is distributed under the same license as the translate_demo project.# FIRST AUTHOR <EMAIL@ADDRESS>, 2007.##, fuzzymsgid ""msgstr """Project-Id-Version: translate_demo 0.0.0/n""Report-Msgid-Bugs-To: EMAIL@ADDRESS/n""POT-Creation-Date: 2007-08-02 18:01-0700/n""PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE/n""Last-Translator: FULL NAME <EMAIL@ADDRESS>/n""Language-Team: LANGUAGE <LL@li.org>/n""MIME-Version: 1.0/n""Content-Type: text/plain; charset=utf-8/n""Content-Transfer-Encoding: 8bit/n""Generated-By: Babel 0.9dev-r215/n"#: translate_demo/controllers/hello.py:10 translate_demo/controllers/hello.py:13msgid "Hello"msgstr ""

The .pot details that appear here can be customized via the extract_messages configuration in your project's setup.cfg (See the Babel Command-Line Interface Documentation for all configuration options).

Next, we'll initialize a catalog (.po file) for the Spanish language:

1234
$ python setup.py init_catalog -l esrunning init_catalogcreating catalog 'translate_demo/i18n/es/LC_MESSAGES/translate_demo.po' based on    'translate_demo/i18n/translate_demo.pot'

Then we can edit the last line of the new Spanish .po file to add a translation of "Hello":

12
msgid "Hello"msgstr "¡Hola!"

Finally, to utilize these translations in our application, we need to compile the .po file to a .mo file:

12345
$ python setup.py compile_catalogrunning compile_catalog1 of 1 messages (100%) translated in 'translate_demo/i18n/es/LC_MESSAGES/translate_demo.po'compiling catalog 'translate_demo/i18n/es/LC_MESSAGES/translate_demo.po' to    'translate_demo/i18n/es/LC_MESSAGES/translate_demo.mo'

We can also use the update_catalog command to merge new messages from the .pot to the .po files. For example, if we later added the following line of code to the end of HelloController's index method:

1
response.write('Goodbye: %s' % _('Goodbye'))

We'd then need to re-extract the messages from the project, then run the update_catalog command:

 1 2 3 4 5 6 7 8 91011
$ python setup.py extract_messagesrunning extract_messagesextracting messages from translate_demo/__init__.pyextracting messages from translate_demo/websetup.py...extracting messages from translate_demo/tests/functional/test_hello.pywriting PO template file to translate_demo/i18n/translate_demo.pot$ python setup.py update_catalogrunning update_catalogupdating catalog 'translate_demo/i18n/es/LC_MESSAGES/translate_demo.po' based on    'translate_demo/i18n/translate_demo.pot'

We'd then edit our catalog to add a translation for "Goodbye", and recompile the .po file as we did above.

For more information, see the Babel documentation and the GNU Gettext Manual.

1.3   Back To Work

Next we'll need to repeat the process of creating a .mo file for the en and fr locales:

12345678
$ python setup.py init_catalog -l enrunning init_catalogcreating catalog 'translate_demo/i18n/en/LC_MESSAGES/translate_demo.po' based on    'translate_demo/i18n/translate_demo.pot'$ python setup.py init_catalog -l frrunning init_catalogcreating catalog 'translate_demo/i18n/fr/LC_MESSAGES/translate_demo.po' based on    'translate_demo/i18n/translate_demo.pot'

Modify the last line of the fr catalog to look like this:

123
#: translate_demo/controllers/hello.py:10 translate_demo/controllers/hello.py:13msgid "Hello"msgstr "Bonjour"

Since our original messages are already in English, the en catalog can stay blank; gettext will fallback to the original.

Once you've edited these new .po files and compiled them to .mo files, you'll end up with an i18n directory containing:

1234567
i18n/translate_demo.poti18n/en/LC_MESSAGES/translate_demo.poi18n/en/LC_MESSAGES/translate_demo.moi18n/es/LC_MESSAGES/translate_demo.poi18n/es/LC_MESSAGES/translate_demo.moi18n/fr/LC_MESSAGES/translate_demo.poi18n/fr/LC_MESSAGES/translate_demo.mo

1.4   Testing the Application

Start the server with the following command:

1
$ paster serve --reload development.ini

Test your controller by visiting http://localhost:5000/hello. You should see the following output:

1234
Default: Hellofr: Bonjouren: Helloes: ¡Hola!

You can now set the language used in a controller on the fly.

For example this could be used to allow a user to set which language they wanted your application to work in. You could save the value to the session object:

12
session['lang'] = 'en'session.save()

then on each controller call the language to be used could be read from the session and set in your controller's __before__() method so that the pages remained in the same language that was previously set:

123
def __before__(self):    if 'lang' in session:        set_lang(session['lang'])

Pylons also supports defining the default language to be used in the configuration file. Set a lang variable to the desired default language in your development.ini file, and Pylons will automatically call set_lang with that language at the beginning of every request.

E.g. to set the default language to Spanish, you would add lang = es to your development.ini:

123
[app:main]use = egg:translate_demolang = es

If you are running the server with the --reload option the server will automatically restart if you change the development.ini file. Otherwise restart the server manually and the output would this time be as follows:

1234
Default: ¡Hola!fr: Bonjouren: Helloes: ¡Hola!

1.5   Fallback Languages

If your code calls _() with a string that doesn't exist at all in your language catalog, the string passed to _() is returned instead.

Modify the last line of the hello controller to look like this:

1
response.write("%s %s, %s" % (_('Hello'), _('World'), _('Hi!')))

Warning

Of course, in real life breaking up sentences in this way is very dangerous because some grammars might require the order of the words to be different.

If you run the example again the output will be:

1234
Default: ¡Hola!fr: Bonjour World!en: Hello World!es: ¡Hola! World!

This is because we never provided a translation for the string 'World!' so the string itself is used.

Pylons also provides a mechanism for fallback languages, so that you can specify other languages to be used if the word is ommitted from the main language's catalog.

In this example we choose fr as the main language but es as a fallback:

 1 2 3 4 5 6 7 8 91011121314
import loggingfrom pylons.i18n import add_fallback, set_langfrom translate_demo.lib.base import *log = logging.getLogger(__name__)class HelloController(BaseController):     def index(self):         set_lang('fr')         add_fallback('es')         return "%s %s, %s" % (_('Hello'), _('World'), _('Hi!'))

If Hello is in the fr .mo file as Bonjour, World is only in es as Mundo and none of the catalogs contain Hi!, you'll get the multilingual message: Bonjour Mundo, Hi!. This is a combination of the French, Spanish and original (English in this case, as defined in our source code) words.

You can add as many fallback languages with the add_fallback() function as you like and they will be tested in the order you add them.

One case where using fallbacks in this way is particularly useful is when you wish to display content based on the languages requested by the browser in the HTTP_ACCEPT_LANGUAGE header. Typically the browser may submit a number of languages so it is useful to be add fallbacks in the order specified by the browser so that you always try to display words in the language of preference and search the other languages in order if a translation cannot be found. The languages defined in the HTTP_ACCEPT_LANGAUGE header are available in Pylons as request.languages and can be used like this:

12
for lang in request.languages:    add_fallback(lang)

1.6   Translations Within Templates

You can also use the _() function within templates in exactly the same way you do in code. For example, in a Mako template:

1
${_('Hello')}

would produce the string 'Hello' in the language you had set.

Babel currently supports extracting gettext messages from Mako and Genshi templates. The Mako extractor also provides support for translator comments. Babel can be extended to extract messages from other sources via a custom extraction method plugin.

Pylons (as of 0.9.6) automatically configures a Babel extraction mapping for your Python source code and Mako templates. This is defined in your project's setup.py file:

12345
message_extractors = {'translate_demo': [        ('public/**', 'ignore', None),        ('**.py', 'python', None),        ('templates/**.mako', 'mako', None),        ]},

For a project using Genshi instead of Mako, the Mako line might be replaced with:

1
('templates/**.html, 'genshi, None),

See Babel's documentation on Message Extraction for more information.

1.7   Lazy Translations

Occasionally you might come across a situation when you need to translate a string when it is accessed, not when the _() or other functions are called.

Consider this example:

 1 2 3 4 5 6 7 8 9101112131415161718
import loggingfrom pylons.i18n import get_lang, set_langfrom translate_demo.lib.base import *log = logging.getLogger(__name__)text = _('Hello')class HelloController(BaseController):    def index(self):        response.write('Default: %s<br />' % _('Hello'))        for lang in ['fr','en','es']:            set_lang(lang)            response.write("%s: %s<br />" % (get_lang(), _('Hello')))        response.write('Text: %s<br />' % text)

If we run this we get the following output:

12345
Default: Hello['fr']: Bonjour['en']: Good morning['es']: HolaText: Hello

This is because the function _('Hello') just after the imports is called when the default language is en so the variable text gets the value of the English translation even though when the string was used the default language was Spanish.

The rule of thumb in these situations is to try to avoid using the translation functions in situations where they are not executed on each request. For situations where this isn't possible, perhaps because you are working with legacy code or with a library which doesn't support internationalization, you need to use lazy translations.

If we modify the above example so that the import statements and assignment to text look like this:

1234567
from pylons.i18n import get_lang, lazy_gettext, set_langfrom helloworld.lib.base import *log = logging.getLogger(__name__)text = lazy_gettext('Hello')

then we get the output we expected:

12345
Default: Hello['fr']: Bonjour['en']: Good morning['es']: HolaText: Hola

There are lazy versions of all the standard Pylons translation functions.

There is one drawback to be aware of when using the lazy translation functions: they are not actually strings. This means that if our example had used the following code it would have failed with an error cannot concatenate 'str' and 'LazyString' objects:

1
response.write('Text: ' + text + '<br />')

For this reason you should only use the lazy translations where absolutely necessary and should always ensure they are converted to strings by calling str() or repr() before they are used in operations with real strings.

1.8   Producing a Python Egg

Finally you can produce an egg of your project which includes the translation files like this:

1
$ python setup.py bdist_egg

The setup.py automatically includes the .mo language catalogs your application needs so that your application can be distributed as an egg. This is done with the following line in your setup.py file:

1
package_data={'translate_demo': ['i18n/*/LC_MESSAGES/*.mo']},

1.9   Plural Forms

Pylons also provides the ungettext() function. It's designed for internationalizing plural words, and can be used as follows:

12
ungettext('There is %(num)d file here', 'There are %(num)d files here',          n) % {'num': n}

Plural forms have a different type of entry in .pot/.po files, as described in The Format of PO Files in GNU Gettext's Manual:

123456
#: translate_demo/controllers/hello.py:12#, python-formatmsgid "There is %(num)d file here"msgid_plural "There are %(num)d files here"msgstr[0] ""msgstr[1] ""

One thing to keep in mind is that other languages don't have the same plural forms as English. While English only has 2 plural forms, singular and plural, Slovenian has 4! That means that you must use ugettext for proper pluralization. Specifically, the following will not work:

12345
# BAD!if n == 1:    msg = _("There was no dog.")else:    msg = _("There were no dogs.")

2   Summary

This document only covers the basics of internationalizing and localizing a web application.

GNU Gettext is an extensive library, and the GNU Gettext Manual is highly recommended for more information.

Babel also provides support for interfacing to the CLDR (Common Locale Data Repository), providing access to various locale display names, localized number and date formatting, etc.

You should also be able to internationalize and then localize your application using Pylons' support for GNU gettext.

3   Further Reading

http://en.wikipedia.org/wiki/Internationalization

Please feel free to report any mistakes to the Pylons mailing list or to the author. Any corrections or clarifications would be gratefully received.

Comments  (Hide)

in para "For a project using Genshi..." there is a typo. it should read:

.syntax { background: #f8f8f8; }.syntax .c { color: #008800; font-style: italic } /* Comment */.syntax .err { border: 1px solid #FF0000 } /* Error */.syntax .k { color: #AA22FF; font-weight: bold } /* Keyword */.syntax .o { color: #666666 } /* Operator */.syntax .cm { color: #008800; font-style: italic } /* Comment.Multiline */.syntax .cp { color: #008800 } /* Comment.Preproc */.syntax .c1 { color: #008800; font-style: italic } /* Comment.Single */.syntax .cs { color: #008800; font-weight: bold } /* Comment.Special */.syntax .gd { color: #A00000 } /* Generic.Deleted */.syntax .ge { font-style: italic } /* Generic.Emph */.syntax .gr { color: #FF0000 } /* Generic.Error */.syntax .gh { color: #000080; font-weight: bold } /* Generic.Heading */.syntax .gi { color: #00A000 } /* Generic.Inserted */.syntax .go { color: #808080 } /* Generic.Output */.syntax .gp { color: #000080; font-weight: bold } /* Generic.Prompt */.syntax .gs { font-weight: bold } /* Generic.Strong */.syntax .gu { color: #800080; font-weight: bold } /* Generic.Subheading */.syntax .gt { color: #0040D0 } /* Generic.Traceback */.syntax .kc { color: #AA22FF; font-weight: bold } /* Keyword.Constant */.syntax .kd { color: #AA22FF; font-weight: bold } /* Keyword.Declaration */.syntax .kp { color: #AA22FF } /* Keyword.Pseudo */.syntax .kr { color: #AA22FF; font-weight: bold } /* Keyword.Reserved */.syntax .kt { color: #AA22FF; font-weight: bold } /* Keyword.Type */.syntax .m { color: #666666 } /* Literal.Number */.syntax .s { color: #BB4444 } /* Literal.String */.syntax .na { color: #BB4444 } /* Name.Attribute */.syntax .nb { color: #AA22FF } /* Name.Builtin */.syntax .nc { color: #0000FF } /* Name.Class */.syntax .no { color: #880000 } /* Name.Constant */.syntax .nd { color: #AA22FF } /* Name.Decorator */.syntax .ni { color: #999999; font-weight: bold } /* Name.Entity */.syntax .ne { color: #D2413A; font-weight: bold } /* Name.Exception */.syntax .nf { color: #00A000 } /* Name.Function */.syntax .nl { color: #A0A000 } /* Name.Label */.syntax .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */.syntax .nt { color: #008000; font-weight: bold } /* Name.Tag */.syntax .nv { color: #B8860B } /* Name.Variable */.syntax .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */.syntax .mf { color: #666666 } /* Literal.Number.Float */.syntax .mh { color: #666666 } /* Literal.Number.Hex */.syntax .mi { color: #666666 } /* Literal.Number.Integer */.syntax .mo { color: #666666 } /* Literal.Number.Oct */.syntax .sb { color: #BB4444 } /* Literal.String.Backtick */.syntax .sc { color: #BB4444 } /* Literal.String.Char */.syntax .sd { color: #BB4444; font-style: italic } /* Literal.String.Doc */.syntax .s2 { color: #BB4444 } /* Literal.String.Double */.syntax .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */.syntax .sh { color: #BB4444 } /* Literal.String.Heredoc */.syntax .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */.syntax .sx { color: #008000 } /* Literal.String.Other */.syntax .sr { color: #BB6688 } /* Literal.String.Regex */.syntax .s1 { color: #BB4444 } /* Literal.String.Single */.syntax .ss { color: #B8860B } /* Literal.String.Symbol */.syntax .bp { color: #AA22FF } /* Name.Builtin.Pseudo */.syntax .vc { color: #B8860B } /* Name.Variable.Class */.syntax .vg { color: #B8860B } /* Name.Variable.Global */.syntax .vi { color: #B8860B } /* Name.Variable.Instance */.syntax .il { color: #666666 } /* Literal.Number.Integer.Long */td.linenos { margin: 0; line-height: 1.25em; font-size: 0.95em; }

1
('templates/**.html', 'genshi', None),

Posted by Max Ischenko at Jan 12, 2008 14:26 | Permalink

In my 0.9.6.1 template the "message_extractors" in my setup.py are by default commented out. So to use gettext in templates you need to remove the "#" there.

Posted by Christoph Haas at Jan 17, 2008 14:21 | Permalink

Another comment on my i18n researches regarding formencode. If you write your own validators and want to localize the error messages then the best way to do that appears to be:

 

from pylons.i18n import _import formencodeclass MyValidator(formencode.FancyValidator):    messages = { 'myerror' : _('Something crazy is wrong.') }    ...

 

And when using the @validate decorator you have to pass in a state argument that sets "_" to Pylons' own gettext function. A simple sample class called PylonsFormEncodeState is described in http://pylonshq.com/project/pylonshq/ticket/296. All you have to do then is call @validate like this:

 

@validate(MyValidationSchema, form='myform', state=PylonsFormEncodeState())

 

Posted by Christoph Haas at Jan 19, 2008 20:44 | Permalink


Just another note (sorry for spamming): if you use "self.message()" in your own validators then formencode can just use its builtin localisation. So usually you either have the localized error message for built-in validators XOR for your own validators. Actualls self.message() doesn't do much so it should be safe to use this instead:

 

raise formencode.Invalid( _('Your input is just pure garbage.'), value, state

 

That way "python setup.py extract_messages" will find your _() string to create a POT file and you can still use the built-in validators with proper l18n.

Posted by Christoph Haas at Jan 19, 2008 21:57 | Permalink

Even if you use _('some string') and try to set_lang to a language that you don't have a PO/MO file for then you need to create a dummy english ('en') PO file to avoid getting an

 

LanguageError: IOError: Errno 2 No translation file found for domain

 

error. So just

 

python setup.py init_catalog -l en

 

and you can use "add_fallback('en')". No need to change anything in the en/LC_MESSAGES/foobar.po file. It just has to be there. (So it seems.)

Posted by Christoph Haas at Jan 21, 2008 22:25 | Permalink

After doing everything as explained above I get the following error trace,

 

Traceback (most recent call last):  File "setup.py", line 31, in     """,  File "/sw/lib/python2.5/distutils/core.py", line 112, in setup    _setup_distribution = dist = klass(attrs)  File "/sw/lib/python2.5/site-packages/setuptools/dist.py", line 223, in __init__    _Distribution.__init__(self,attrs)  File "/sw/lib/python2.5/distutils/dist.py", line 267, in __init__    self.finalize_options()  File "/sw/lib/python2.5/site-packages/setuptools/dist.py", line 256, in finalize_options    ep.load()(self, ep.name, value)  File "/sw/lib/python2.5/site-packages/pkg_resources.py", line 1912, in load    entry = __import__(self.module_name, globals(),globals(), ['__name__'])  File "/sw/lib/python2.5/site-packages/Babel-0.9.2-py2.5.egg/babel/messages/__init__.py", line 16, in     from babel.messages.catalog import *  File "/sw/lib/python2.5/site-packages/Babel-0.9.2-py2.5.egg/babel/messages/catalog.py", line 29, in     from babel.dates import format_datetime  File "/sw/lib/python2.5/site-packages/Babel-0.9.2-py2.5.egg/babel/dates.py", line 34, in     LC_TIME = default_locale('LC_TIME')  File "/sw/lib/python2.5/site-packages/Babel-0.9.2-py2.5.egg/babel/core.py", line 629, in default_locale    return '_'.join(filter(None, parse_locale(locale)))  File "/sw/lib/python2.5/site-packages/Babel-0.9.2-py2.5.egg/babel/core.py", line 737, in parse_locale    raise ValueError('expected only letters, got %r' % lang)ValueError: expected only letters, got 'utf-8'mykola-paliyenkos-macbook-pro:uaprom mykola$

 

What am I doing wrong?

Posted by Mykola Paliyenko at Feb 21, 2008 11:57 | Permalink

Just some hints for people using genshi. I spend some hours to figure out how to do this, so it might be helpful to you:

some useful links:
  • http://groups.google.com/group/pylons-discuss/msg/fad2dd21e8f5d9c7?hl=en pylons-discuss post about this issue
  • http://my.opera.com/WebApplications/blog/2008/03/17/search-engine-friendly Blog post on opera.com about the same issue
  • http://genshi.edgewall.org/wiki/Documentation/0.4.x/i18n.html#translation Genshi doc about internationalization and translation
quick solution:

in order to add Genshi Translator filter to the templates edit the file config/environment.py, add the following lines (thanks to Daniel Haus for pointing me to right solution): 

 

from genshi.filters.i18n import Translatorfrom pylons.i18n.translation import ugettext...  def load_environment(global_conf, app_conf): ...    # Translator    translator = Translator(ugettext)    def template_loaded(template):        template.filters.insert(0, translator)    tmpl_options["genshi.loader_callback"] = template_loaded 

 

Posted by Daniel Clerc at Jul 08, 2008 12:37 | Permalink