Attachment 'mki18n.py'

Download

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

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2009-10-04 09:16:03, 28.7 KB) [[attachment:i18n.html]]
  • [get | view] (2009-10-04 09:16:03, 19.8 KB) [[attachment:mki18n.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.