Allow "" as a valid value in a OptionsBox menu (reported by Guido Schimmels).
[rox-lib.git] / python / rox / i18n.py
blob4527787683f271982dedded8addbb36fd97995ac
1 """If you want your program to be translated into multiple languages you need
2 to do the following:
4 - Pass all strings that should be translated through the '_' function, eg:
5 print _('Hello World!')
7 - Create a Messages subdirectory in your application.
9 - Run 'pygettext *.py' to extract all the marked strings.
11 - Copy messages.pot as Messages/<lang>.po and edit (see ROX-Lib2's README).
13 - Use msgfmt to convert the .po files to .gmo files.
15 - In your application, use the rox.i18n.translation() function to set the _ function:
16 __builtins__._ = rox.i18n.translation(os.path.join(rox.app_dir, 'Messages'))
17 (for libraries, just do '_ ='; don't mess up the builtins)
19 Note that the marked strings must be fixed. If you're using formats, mark up the
20 format, eg:
22 print _('You have %d lives remaining') % lives
24 You might like to look at the scripts in ROX-Lib2's Messages directory for
25 more help.
26 """
28 import os
30 def _expand_lang(locale):
31 from locale import normalize
32 locale = normalize(locale)
33 COMPONENT_CODESET = 1 << 0
34 COMPONENT_TERRITORY = 1 << 1
35 COMPONENT_MODIFIER = 1 << 2
36 # split up the locale into its base components
37 mask = 0
38 pos = locale.find('@')
39 if pos >= 0:
40 modifier = locale[pos:]
41 locale = locale[:pos]
42 mask |= COMPONENT_MODIFIER
43 else:
44 modifier = ''
45 pos = locale.find('.')
46 if pos >= 0:
47 codeset = locale[pos:]
48 locale = locale[:pos]
49 mask |= COMPONENT_CODESET
50 else:
51 codeset = ''
52 pos = locale.find('_')
53 if pos >= 0:
54 territory = locale[pos:]
55 locale = locale[:pos]
56 mask |= COMPONENT_TERRITORY
57 else:
58 territory = ''
59 language = locale
60 ret = []
61 for i in range(mask+1):
62 if not (i & ~mask): # if all components for this combo exist ...
63 val = language
64 if i & COMPONENT_TERRITORY: val += territory
65 if i & COMPONENT_CODESET: val += codeset
66 if i & COMPONENT_MODIFIER: val += modifier
67 ret.append(val)
68 ret.reverse()
69 return ret
71 def expand_languages(languages = None):
72 # Get some reasonable defaults for arguments that were not supplied
73 if languages is None:
74 languages = []
75 for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
76 val = os.environ.get(envar)
77 if val:
78 languages = val.split(':')
79 break
80 if 'C' not in languages:
81 languages.append('C')
83 # now normalize and expand the languages
84 nelangs = []
85 for lang in languages:
86 for nelang in _expand_lang(lang):
87 if nelang not in nelangs:
88 nelangs.append(nelang)
89 return nelangs
91 # Locate a .mo file using the ROX strategy
92 def find(messages_dir, languages = None):
93 """Look in messages_dir for a .gmo file for the user's preferred language
94 (or override this with the 'languages' argument). Returns the filename, or
95 None if there was no translation."""
96 # select a language
97 for lang in expand_languages(languages):
98 if lang == 'C':
99 break
100 mofile = os.path.join(messages_dir, '%s.gmo' % lang)
101 if os.path.exists(mofile):
102 return mofile
103 return None
105 def translation(messages_dir, languages = None):
106 """Load the translation for the user's language and return a function
107 which translates a string into its unicode equivalent."""
108 mofile = find(messages_dir, languages)
109 if not mofile:
110 return lambda x: x
111 import gettext
112 return gettext.GNUTranslations(file(mofile)).ugettext
114 langs = expand_languages()