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