test
[iramuteq] / mki18n.py
1 #! /usr/bin/env python\r
2 # -*- coding: iso-8859-1 -*-\r
3\r
4 #   PYTHON MODULE:     MKI18N.PY\r
5 #                      =========\r
6\r
7 #   Abstract:         Make Internationalization (i18n) files for an application.\r
8\r
9 #   Copyright Pierre Rouleau. 2003. Released to public domain.\r
10\r
11 #   Last update: Saturday, November 8, 2003. @ 15:55:18.\r
12\r
13 #   File: ROUP2003N01::C:/dev/python/mki18n.py\r
14\r
15 #   RCS $Header: //software/official/MKS/MKS_SI/TV_NT/dev/Python/rcs/mki18n.py 1.5 2003/11/05 19:40:04 PRouleau Exp $\r
16\r
17 #   Update history:\r
18\r
19 #   - File created: Saturday, June 7, 2003. by Pierre Rouleau\r
20 #   - 10/06/03 rcs : RCS Revision 1.1  2003/06/10 10:06:12  PRouleau\r
21 #   - 10/06/03 rcs : RCS Initial revision\r
22 #   - 23/08/03 rcs : RCS Revision 1.2  2003/06/10 10:54:27  PRouleau\r
23 #   - 23/08/03 P.R.: [code:fix] : The strings encoded in this file are encode in iso-8859-1 format.  Added the encoding\r
24 #                    notification to Python to comply with Python's 2.3 PEP 263.\r
25 #   - 23/08/03 P.R.: [feature:new] : Added the '-e' switch which is used to force the creation of the empty English .mo file.\r
26 #   - 22/10/03 P.R.: [code] : incorporated utility functions in here to make script self sufficient.\r
27 #   - 05/11/03 rcs : RCS Revision 1.4  2003/10/22 06:39:31  PRouleau\r
28 #   - 05/11/03 P.R.: [code:fix] : included the unixpath() in this file.\r
29 #   - 08/11/03 rcs : RCS Revision 1.5  2003/11/05 19:40:04  PRouleau\r
30\r
31 #   RCS $Log: $\r
32\r
33\r
34 # -----------------------------------------------------------------------------\r
35 """                                \r
36 mki18n allows you to internationalize your software.  You can use it to \r
37 create the GNU .po files (Portable Object) and the compiled .mo files\r
38 (Machine Object).\r
39 \r
40 mki18n module can be used from the command line or from within a script (see \r
41 the Usage at the end of this page).\r
42 \r
43     Table of Contents\r
44     -----------------\r
45     \r
46     makePO()             -- Build the Portable Object file for the application --\r
47     catPO()              -- Concatenate one or several PO files with the application domain files. --\r
48     makeMO()             -- Compile the Portable Object files into the Machine Object stored in the right location. --\r
49     printUsage           -- Displays how to use this script from the command line --\r
50 \r
51     Scriptexecution      -- Runs when invoked from the command line --\r
52 \r
53 \r
54 NOTE:  this module uses GNU gettext utilities.\r
55 \r
56 You can get the gettext tools from the following sites:\r
57 \r
58    - `GNU FTP site for gettetx`_ where several versions (0.10.40, 0.11.2, 0.11.5 and 0.12.1) are available.\r
59      Note  that you need to use `GNU libiconv`_ to use this. Get it from the `GNU\r
60      libiconv  ftp site`_ and get version 1.9.1 or later. Get the Windows .ZIP\r
61      files and install the packages inside c:/gnu. All binaries will be stored\r
62      inside  c:/gnu/bin.  Just  put c:/gnu/bin inside your PATH. You will need\r
63      the following files: \r
64 \r
65       - `gettext-runtime-0.12.1.bin.woe32.zip`_ \r
66       - `gettext-tools-0.12.1.bin.woe32.zip`_\r
67       - `libiconv-1.9.1.bin.woe32.zip`_ \r
68 \r
69 \r
70 .. _GNU libiconv:                            http://www.gnu.org/software/libiconv/\r
71 .. _GNU libiconv ftp site:                   http://www.ibiblio.org/pub/gnu/libiconv/\r
72 .. _gettext-runtime-0.12.1.bin.woe32.zip:    ftp://ftp.gnu.org/gnu/gettext/gettext-runtime-0.12.1.bin.woe32.zip           \r
73 .. _gettext-tools-0.12.1.bin.woe32.zip:      ftp://ftp.gnu.org/gnu/gettext/gettext-tools-0.12.1.bin.woe32.zip \r
74 .. _libiconv-1.9.1.bin.woe32.zip:            http://www.ibiblio.org/pub/gnu/libiconv/libiconv-1.9.1.bin.woe32.zip\r
75 \r
76 """\r
77 # -----------------------------------------------------------------------------\r
78 # Module Import\r
79 # -------------\r
80\r
81 import os\r
82 import sys\r
83 import wx\r
84 # -----------------------------------------------------------------------------\r
85 # Global variables\r
86 # ----------------\r
87 #\r
88 \r
89 __author__ = "Pierre Rouleau"\r
90 __version__= "$Revision: 1.5 $"\r
91 \r
92 # -----------------------------------------------------------------------------\r
93 \r
94 def getlanguageDict():\r
95     languageDict = {}\r
96     \r
97     for lang in [x for x in dir(wx) if x.startswith("LANGUAGE")]:\r
98         i = wx.Locale(wx.LANGUAGE_DEFAULT).GetLanguageInfo(getattr(wx, lang))\r
99         if i:\r
100             languageDict[i.CanonicalName] = i.Description\r
101 \r
102     return languageDict\r
103 \r
104 # -----------------------------------------------------------------------------\r
105 # m a k e P O ( )         -- Build the Portable Object file for the application --\r
106 # ^^^^^^^^^^^^^^^\r
107 #\r
108 def makePO(applicationDirectoryPath,  applicationDomain=None, verbose=0) :\r
109     """Build the Portable Object Template file for the application.\r
110 \r
111     makePO builds the .pot file for the application stored inside \r
112     a specified directory by running xgettext for all application source \r
113     files.  It finds the name of all files by looking for a file called 'app.fil'. \r
114     If this file does not exists, makePo raises an IOError exception.\r
115     By default the application domain (the application\r
116     name) is the same as the directory name but it can be overridden by the\r
117     'applicationDomain' argument.\r
118 \r
119     makePO always creates a new file called messages.pot.  If it finds files \r
120     of the form app_xx.po where 'app' is the application name and 'xx' is one \r
121     of the ISO 639 two-letter language codes, makePO resynchronizes those \r
122     files with the latest extracted strings (now contained in messages.pot). \r
123     This process updates all line location number in the language-specific\r
124     .po files and may also create new entries for translation (or comment out \r
125     some).  The .po file is not changed, instead a new file is created with \r
126     the .new extension appended to the name of the .po file.\r
127 \r
128     By default the function does not display what it is doing.  Set the \r
129     verbose argument to 1 to force it to print its commands.\r
130     """\r
131 \r
132     if applicationDomain is None:\r
133         applicationName = fileBaseOf(applicationDirectoryPath,withPath=0)\r
134     else:\r
135         applicationName = applicationDomain\r
136     currentDir = os.getcwd()\r
137     os.chdir(applicationDirectoryPath)                    \r
138     if not os.path.exists('app.fil'):\r
139         raise IOError(2,'No module file: app.fil')\r
140 \r
141     # Steps:                                  \r
142     #  Use xgettext to parse all application modules\r
143     #  The following switches are used:\r
144     #  \r
145     #   -s                          : sort output by string content (easier to use when we need to merge several .po files)\r
146     #   --files-from=app.fil        : The list of files is taken from the file: app.fil\r
147     #   --output=                   : specifies the name of the output file (using a .pot extension)\r
148     cmd = 'xgettext -s --no-wrap --files-from=app.fil --output=messages.pot'\r
149     if verbose: print cmd\r
150     os.system(cmd)                                                \r
151 \r
152     languageDict = getlanguageDict()\r
153 \r
154     for langCode in languageDict.keys():\r
155         if langCode == 'en':\r
156             pass\r
157         else:\r
158             langPOfileName = "%s_%s.po" % (applicationName , langCode)\r
159             if os.path.exists(langPOfileName):\r
160                 cmd = 'msgmerge -s --no-wrap "%s" messages.pot > "%s.new"' % (langPOfileName, langPOfileName)\r
161                 if verbose: print cmd\r
162                 os.system(cmd)\r
163     os.chdir(currentDir)\r
164 \r
165 # -----------------------------------------------------------------------------\r
166 # c a t P O ( )         -- Concatenate one or several PO files with the application domain files. --\r
167 # ^^^^^^^^^^^^^\r
168 #\r
169 def catPO(applicationDirectoryPath, listOf_extraPo, applicationDomain=None, targetDir=None, verbose=0) :\r
170     """Concatenate one or several PO files with the application domain files.\r
171     """\r
172 \r
173     if applicationDomain is None:\r
174         applicationName = fileBaseOf(applicationDirectoryPath,withPath=0)\r
175     else:\r
176         applicationName = applicationDomain\r
177     currentDir = os.getcwd()\r
178     os.chdir(applicationDirectoryPath)\r
179 \r
180     languageDict = getlanguageDict()\r
181 \r
182     for langCode in languageDict.keys():\r
183         if langCode == 'en':\r
184             pass\r
185         else:\r
186             langPOfileName = "%s_%s.po" % (applicationName , langCode)\r
187             if os.path.exists(langPOfileName):\r
188                 fileList = ''\r
189                 for fileName in listOf_extraPo:\r
190                     fileList += ("%s_%s.po " % (fileName,langCode))\r
191                 cmd = "msgcat -s --no-wrap %s %s > %s.cat" % (langPOfileName, fileList, langPOfileName)\r
192                 if verbose: print cmd\r
193                 os.system(cmd)\r
194                 if targetDir is None:\r
195                     pass\r
196                 else:\r
197                     mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode)\r
198                     cmd = "msgfmt --output-file=%s/%s.mo %s_%s.po.cat" % (mo_targetDir,applicationName,applicationName,langCode)\r
199                     if verbose: print cmd\r
200                     os.system(cmd)\r
201     os.chdir(currentDir)\r
202 \r
203 # -----------------------------------------------------------------------------\r
204 # m a k e M O ( )         -- Compile the Portable Object files into the Machine Object stored in the right location. --\r
205 # ^^^^^^^^^^^^^^^\r
206\r
207 def makeMO(applicationDirectoryPath,targetDir='./locale',applicationDomain=None, verbose=0, forceEnglish=0) :\r
208     """Compile the Portable Object files into the Machine Object stored in the right location.\r
209 \r
210     makeMO converts all translated language-specific PO files located inside \r
211     the  application directory into the binary .MO files stored inside the \r
212     LC_MESSAGES sub-directory for the found locale files.\r
213 \r
214     makeMO searches for all files that have a name of the form 'app_xx.po' \r
215     inside the application directory specified by the first argument.  The \r
216     'app' is the application domain name (that can be specified by the \r
217     applicationDomain argument or is taken from the directory name). The 'xx' \r
218     corresponds to one of the ISO 639 two-letter language codes.\r
219 \r
220     makeMo stores the resulting files inside a sub-directory of `targetDir`\r
221     called xx/LC_MESSAGES where 'xx' corresponds to the 2-letter language\r
222     code.\r
223     """\r
224     if targetDir is None:\r
225         targetDir = './locale'\r
226     if verbose:\r
227         print "Target directory for .mo files is: %s" % targetDir\r
228 \r
229     if applicationDomain is None:\r
230         applicationName = fileBaseOf(applicationDirectoryPath,withPath=0)\r
231     else:\r
232         applicationName = applicationDomain\r
233     currentDir = os.getcwd()\r
234     os.chdir(applicationDirectoryPath)\r
235 \r
236     languageDict = getlanguageDict()\r
237     languageDict['en'] = 'zerzer'\r
238     for langCode in languageDict.keys():\r
239         print langCode\r
240         if (langCode == 'en') and (forceEnglish==0):\r
241             pass\r
242         else:\r
243             langPOfileName = "%s_%s.po" % (applicationName , langCode)\r
244             if os.path.exists(langPOfileName):\r
245                 print "%s/%s/LC_MESSAGES" % (targetDir,langCode)\r
246                 mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) \r
247                 if not os.path.exists(mo_targetDir):\r
248                     mkdir(mo_targetDir)\r
249                 cmd = 'msgfmt --output-file="%s/%s.mo" "%s_%s.po"' % (mo_targetDir,applicationName,applicationName,langCode)\r
250                 if verbose: print cmd\r
251                 os.system(cmd)\r
252     os.chdir(currentDir)\r
253    \r
254 # -----------------------------------------------------------------------------\r
255 # p r i n t U s a g e         -- Displays how to use this script from the command line --\r
256 # ^^^^^^^^^^^^^^^^^^^\r
257 #\r
258 def printUsage(errorMsg=None) :\r
259     """Displays how to use this script from the command line."""\r
260     print """\r
261     ##################################################################################\r
262     #   mki18n :   Make internationalization files.                                  #\r
263     #              Uses the GNU gettext system to create PO (Portable Object) files  #\r
264     #              from source code, coimpile PO into MO (Machine Object) files.     #\r
265     #              Supports C,C++,Python source files.                               #\r
266     #                                                                                #\r
267     #   Usage: mki18n {OPTION} [appDirPath]                                          #\r
268     #                                                                                #\r
269     #   Options:                                                                     #\r
270     #     -e               : When -m is used, forces English .mo file creation       #\r
271     #     -h               : prints this help                                        #\r
272     #     -m               : make MO from existing PO files                          #\r
273     #     -p               : make PO, update PO files: Creates a new messages.pot    #\r
274     #                        file. Creates a dom_xx.po.new for every existing        #\r
275     #                        language specific .po file. ('xx' stands for the ISO639 #\r
276     #                        two-letter language code and 'dom' stands for the       #\r
277     #                        application domain name).  mki18n requires that you     #\r
278     #                        write a 'app.fil' file  which contains the list of all  #\r
279     #                        source code to parse.                                   #\r
280     #     -v               : verbose (prints comments while running)                 #\r
281     #     --domain=appName : specifies the application domain name.  By default      #\r
282     #                        the directory name is used.                             #\r
283     #     --moTarget=dir : specifies the directory where .mo files are stored.       #\r
284     #                      If not specified, the target is './locale'                #\r
285     #                                                                                #\r
286     #   You must specify one of the -p or -m option to perform the work.  You can    #\r
287     #   specify the path of the target application.  If you leave it out mki18n      #\r
288     #   will use the current directory as the application main directory.            #        \r
289     #                                                                                #\r
290     ##################################################################################"""\r
291     if errorMsg:\r
292         print "\n   ERROR: %s" % errorMsg\r
293 \r
294 # -----------------------------------------------------------------------------\r
295 # f i l e B a s e O f ( )         -- Return base name of filename --\r
296 # ^^^^^^^^^^^^^^^^^^^^^^^\r
297\r
298 def fileBaseOf(filename,withPath=0) :\r
299    """fileBaseOf(filename,withPath) ---> string\r
300 \r
301    Return base name of filename.  The returned string never includes the extension.\r
302    Use os.path.basename() to return the basename with the extension.  The \r
303    second argument is optional.  If specified and if set to 'true' (non zero) \r
304    the string returned contains the full path of the file name.  Otherwise the \r
305    path is excluded.\r
306 \r
307    [Example]\r
308    >>> fn = 'd:/dev/telepath/tvapp/code/test.html'\r
309    >>> fileBaseOf(fn)\r
310    'test'\r
311    >>> fileBaseOf(fn)\r
312    'test'\r
313    >>> fileBaseOf(fn,1)\r
314    'd:/dev/telepath/tvapp/code/test'\r
315    >>> fileBaseOf(fn,0)\r
316    'test'\r
317    >>> fn = 'abcdef'\r
318    >>> fileBaseOf(fn)\r
319    'abcdef'\r
320    >>> fileBaseOf(fn,1)\r
321    'abcdef'\r
322    >>> fn = "abcdef."\r
323    >>> fileBaseOf(fn)\r
324    'abcdef'\r
325    >>> fileBaseOf(fn,1)\r
326    'abcdef'\r
327    """            \r
328    pos = filename.rfind('.')             \r
329    if pos > 0:\r
330       filename = filename[:pos]\r
331    if withPath:\r
332       return filename\r
333    else:\r
334       return os.path.basename(filename)\r
335 # -----------------------------------------------------------------------------\r
336 # m k d i r ( )         -- Create a directory (and possibly the entire tree) --\r
337 # ^^^^^^^^^^^^^\r
338\r
339 def mkdir(directory) :\r
340    """Create a directory (and possibly the entire tree).\r
341 \r
342    The os.mkdir() will fail to create a directory if one of the\r
343    directory in the specified path does not exist.  mkdir()\r
344    solves this problem.  It creates every intermediate directory\r
345    required to create the final path. Under Unix, the function \r
346    only supports forward slash separator, but under Windows and MacOS\r
347    the function supports the forward slash and the OS separator (backslash\r
348    under windows).\r
349    """ \r
350 \r
351    # translate the path separators\r
352    directory = unixpath(directory)\r
353    # build a list of all directory elements\r
354    aList = filter(lambda x: len(x)>0, directory.split('/'))\r
355    theLen = len(aList)                     \r
356    # if the first element is a Windows-style disk drive\r
357    # concatenate it with the first directory\r
358    if aList[0].endswith(':'):\r
359       if theLen > 1:\r
360          aList[1] = aList[0] + '/' + aList[1]\r
361          del aList[0]      \r
362          theLen -= 1         \r
363    # if the original directory starts at root,\r
364    # make sure the first element of the list \r
365    # starts at root too\r
366    if directory[0] == '/':     \r
367       aList[0] = '/' + aList[0]\r
368    # Now iterate through the list, check if the \r
369    # directory exists and if not create it\r
370    theDir = ''\r
371    for i in range(theLen):\r
372       theDir += aList[i]\r
373       if not os.path.exists(theDir):\r
374          os.mkdir(theDir)\r
375       theDir += '/'   \r
376       \r
377 # -----------------------------------------------------------------------------\r
378 # u n i x p a t h ( )         -- Return a path name that contains Unix separator. --\r
379 # ^^^^^^^^^^^^^^^^^^^\r
380\r
381 def unixpath(thePath) :\r
382    r"""Return a path name that contains Unix separator.\r
383 \r
384    [Example]\r
385    >>> unixpath(r"d:\test")\r
386    'd:/test'\r
387    >>> unixpath("d:/test/file.txt")\r
388    'd:/test/file.txt'\r
389    >>> \r
390    """\r
391    thePath = os.path.normpath(thePath)\r
392    if os.sep == '/':\r
393       return thePath\r
394    else:\r
395       return thePath.replace(os.sep,'/')\r
396 \r
397 # ----------------------------------------------------------------------------- \r
398 \r
399 # S c r i p t   e x e c u t i o n               -- Runs when invoked from the command line --\r
400 # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r
401\r
402 if __name__ == "__main__":\r
403     import getopt     # command line parsing\r
404     argc = len(sys.argv)\r
405     if argc == 1:\r
406         printUsage('Missing argument: specify at least one of -m or -p (or both).')\r
407         sys.exit(1)\r
408     # If there is some arguments, parse the command line\r
409     validOptions     = "ehmpv"\r
410     validLongOptions = ['domain=', 'moTarget=']             \r
411     option = {}\r
412     option['forceEnglish'] = 0\r
413     option['mo'] = 0\r
414     option['po'] = 0        \r
415     option['verbose'] = 0\r
416     option['domain'] = None\r
417     option['moTarget'] = None\r
418     try:\r
419         optionList,pargs = getopt.getopt(sys.argv[1:],validOptions,validLongOptions)\r
420     except getopt.GetoptError, e:\r
421         printUsage(e[0])\r
422         sys.exit(1)       \r
423     for (opt,val) in optionList:\r
424         if  (opt == '-h'):    \r
425             printUsage()\r
426             sys.exit(0) \r
427         elif (opt == '-e'):         option['forceEnglish'] = 1\r
428         elif (opt == '-m'):         option['mo'] = 1\r
429         elif (opt == '-p'):         option['po'] = 1\r
430         elif (opt == '-v'):         option['verbose'] = 1\r
431         elif (opt == '--domain'):   option['domain'] = val\r
432         elif (opt == '--moTarget'): option['moTarget'] = val\r
433     if len(pargs) == 0:\r
434         appDirPath = os.getcwd()\r
435         if option['verbose']:\r
436             print "No project directory given. Using current one:  %s" % appDirPath\r
437     elif len(pargs) == 1:\r
438         appDirPath = pargs[0]\r
439     else:\r
440         printUsage('Too many arguments (%u).  Use double quotes if you have space in directory name' % len(pargs))\r
441         sys.exit(1)\r
442     if option['domain'] is None:\r
443         # If no domain specified, use the name of the target directory\r
444         option['domain'] = fileBaseOf(appDirPath)\r
445     if option['verbose']:\r
446         print "Application domain used is: '%s'" % option['domain']\r
447     if option['po']:\r
448         try:\r
449             makePO(appDirPath,option['domain'],option['verbose'])\r
450         except IOError, e:\r
451             printUsage(e[1] + '\n   You must write a file app.fil that contains the list of all files to parse.')\r
452     if option['mo']:\r
453         makeMO(appDirPath,option['moTarget'],option['domain'],option['verbose'],option['forceEnglish'])\r
454     sys.exit(1)            \r
455             \r
456 \r
457 # -----------------------------------------------------------------------------\r