laurent@391: #! /usr/bin/env python laurent@391: # -*- coding: iso-8859-1 -*- laurent@391: # laurent@391: # PYTHON MODULE: MKI18N.PY laurent@391: # ========= laurent@391: # laurent@391: # Abstract: Make Internationalization (i18n) files for an application. laurent@391: # laurent@391: # Copyright Pierre Rouleau. 2003. Released to public domain. laurent@391: # laurent@391: # Last update: Saturday, November 8, 2003. @ 15:55:18. laurent@391: # laurent@391: # File: ROUP2003N01::C:/dev/python/mki18n.py laurent@391: # laurent@391: # RCS $Header: //software/official/MKS/MKS_SI/TV_NT/dev/Python/rcs/mki18n.py 1.5 2003/11/05 19:40:04 PRouleau Exp $ laurent@391: # laurent@391: # Update history: laurent@391: # laurent@391: # - File created: Saturday, June 7, 2003. by Pierre Rouleau laurent@391: # - 10/06/03 rcs : RCS Revision 1.1 2003/06/10 10:06:12 PRouleau laurent@391: # - 10/06/03 rcs : RCS Initial revision laurent@391: # - 23/08/03 rcs : RCS Revision 1.2 2003/06/10 10:54:27 PRouleau laurent@391: # - 23/08/03 P.R.: [code:fix] : The strings encoded in this file are encode in iso-8859-1 format. Added the encoding laurent@391: # notification to Python to comply with Python's 2.3 PEP 263. laurent@391: # - 23/08/03 P.R.: [feature:new] : Added the '-e' switch which is used to force the creation of the empty English .mo file. laurent@391: # - 22/10/03 P.R.: [code] : incorporated utility functions in here to make script self sufficient. laurent@391: # - 05/11/03 rcs : RCS Revision 1.4 2003/10/22 06:39:31 PRouleau laurent@391: # - 05/11/03 P.R.: [code:fix] : included the unixpath() in this file. laurent@391: # - 08/11/03 rcs : RCS Revision 1.5 2003/11/05 19:40:04 PRouleau laurent@391: # laurent@391: # RCS $Log: $ laurent@391: # laurent@391: # laurent@391: # ----------------------------------------------------------------------------- laurent@391: """ laurent@391: mki18n allows you to internationalize your software. You can use it to laurent@391: create the GNU .po files (Portable Object) and the compiled .mo files laurent@391: (Machine Object). laurent@391: laurent@391: mki18n module can be used from the command line or from within a script (see laurent@391: the Usage at the end of this page). laurent@391: laurent@391: Table of Contents laurent@391: ----------------- laurent@391: laurent@391: makePO() -- Build the Portable Object file for the application -- laurent@391: catPO() -- Concatenate one or several PO files with the application domain files. -- laurent@391: makeMO() -- Compile the Portable Object files into the Machine Object stored in the right location. -- laurent@391: printUsage -- Displays how to use this script from the command line -- laurent@391: laurent@391: Scriptexecution -- Runs when invoked from the command line -- laurent@391: laurent@391: laurent@391: NOTE: this module uses GNU gettext utilities. laurent@391: laurent@391: You can get the gettext tools from the following sites: laurent@391: laurent@391: - `GNU FTP site for gettetx`_ where several versions (0.10.40, 0.11.2, 0.11.5 and 0.12.1) are available. laurent@391: Note that you need to use `GNU libiconv`_ to use this. Get it from the `GNU laurent@391: libiconv ftp site`_ and get version 1.9.1 or later. Get the Windows .ZIP laurent@391: files and install the packages inside c:/gnu. All binaries will be stored laurent@391: inside c:/gnu/bin. Just put c:/gnu/bin inside your PATH. You will need laurent@391: the following files: laurent@391: laurent@391: - `gettext-runtime-0.12.1.bin.woe32.zip`_ laurent@391: - `gettext-tools-0.12.1.bin.woe32.zip`_ laurent@391: - `libiconv-1.9.1.bin.woe32.zip`_ laurent@391: laurent@391: laurent@391: .. _GNU libiconv: http://www.gnu.org/software/libiconv/ laurent@391: .. _GNU libiconv ftp site: http://www.ibiblio.org/pub/gnu/libiconv/ laurent@391: .. _gettext-runtime-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-runtime-0.12.1.bin.woe32.zip laurent@391: .. _gettext-tools-0.12.1.bin.woe32.zip: ftp://ftp.gnu.org/gnu/gettext/gettext-tools-0.12.1.bin.woe32.zip laurent@391: .. _libiconv-1.9.1.bin.woe32.zip: http://www.ibiblio.org/pub/gnu/libiconv/libiconv-1.9.1.bin.woe32.zip laurent@391: laurent@391: """ laurent@391: # ----------------------------------------------------------------------------- laurent@391: # Module Import laurent@391: # ------------- laurent@391: # laurent@391: import os laurent@391: import sys laurent@391: import wx laurent@391: # ----------------------------------------------------------------------------- laurent@391: # Global variables laurent@391: # ---------------- laurent@391: # laurent@391: laurent@391: __author__ = "Pierre Rouleau" laurent@391: __version__= "$Revision: 1.5 $" laurent@391: laurent@391: # ----------------------------------------------------------------------------- laurent@391: laurent@391: def getlanguageDict(): laurent@391: languageDict = {} laurent@391: laurent@391: for lang in [x for x in dir(wx) if x.startswith("LANGUAGE")]: laurent@391: i = wx.Locale(wx.LANGUAGE_DEFAULT).GetLanguageInfo(getattr(wx, lang)) laurent@391: if i: laurent@391: languageDict[i.CanonicalName] = i.Description laurent@391: laurent@391: return languageDict laurent@391: laurent@391: # ----------------------------------------------------------------------------- laurent@391: # m a k e P O ( ) -- Build the Portable Object file for the application -- laurent@391: # ^^^^^^^^^^^^^^^ laurent@391: # laurent@391: def makePO(applicationDirectoryPath, applicationDomain=None, verbose=0) : laurent@391: """Build the Portable Object Template file for the application. laurent@391: laurent@391: makePO builds the .pot file for the application stored inside laurent@391: a specified directory by running xgettext for all application source laurent@391: files. It finds the name of all files by looking for a file called 'app.fil'. laurent@391: If this file does not exists, makePo raises an IOError exception. laurent@391: By default the application domain (the application laurent@391: name) is the same as the directory name but it can be overridden by the laurent@391: 'applicationDomain' argument. laurent@391: laurent@391: makePO always creates a new file called messages.pot. If it finds files laurent@391: of the form app_xx.po where 'app' is the application name and 'xx' is one laurent@391: of the ISO 639 two-letter language codes, makePO resynchronizes those laurent@391: files with the latest extracted strings (now contained in messages.pot). laurent@391: This process updates all line location number in the language-specific laurent@391: .po files and may also create new entries for translation (or comment out laurent@391: some). The .po file is not changed, instead a new file is created with laurent@391: the .new extension appended to the name of the .po file. laurent@391: laurent@391: By default the function does not display what it is doing. Set the laurent@391: verbose argument to 1 to force it to print its commands. laurent@391: """ laurent@391: laurent@391: if applicationDomain is None: laurent@391: applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) laurent@391: else: laurent@391: applicationName = applicationDomain laurent@391: currentDir = os.getcwd() laurent@391: os.chdir(applicationDirectoryPath) laurent@391: if not os.path.exists('app.fil'): laurent@391: raise IOError(2,'No module file: app.fil') laurent@391: laurent@391: # Steps: laurent@391: # Use xgettext to parse all application modules laurent@391: # The following switches are used: laurent@391: # laurent@391: # -s : sort output by string content (easier to use when we need to merge several .po files) laurent@391: # --files-from=app.fil : The list of files is taken from the file: app.fil laurent@391: # --output= : specifies the name of the output file (using a .pot extension) laurent@391: cmd = 'xgettext -s --no-wrap --language=Python --files-from=app.fil --output=messages.pot' laurent@391: if verbose: print cmd laurent@391: os.system(cmd) laurent@391: laurent@391: languageDict = getlanguageDict() laurent@391: laurent@391: for langCode in languageDict.keys(): laurent@391: if langCode == 'en': laurent@391: pass laurent@391: else: laurent@391: langPOfileName = "%s_%s.po" % (applicationName , langCode) laurent@391: if os.path.exists(langPOfileName): laurent@391: cmd = 'msgmerge -s --no-wrap "%s" messages.pot > "%s.new"' % (langPOfileName, langPOfileName) laurent@391: if verbose: print cmd laurent@391: os.system(cmd) laurent@391: os.chdir(currentDir) laurent@391: laurent@391: # ----------------------------------------------------------------------------- laurent@391: # c a t P O ( ) -- Concatenate one or several PO files with the application domain files. -- laurent@391: # ^^^^^^^^^^^^^ laurent@391: # laurent@391: def catPO(applicationDirectoryPath, listOf_extraPo, applicationDomain=None, targetDir=None, verbose=0) : laurent@391: """Concatenate one or several PO files with the application domain files. laurent@391: """ laurent@391: laurent@391: if applicationDomain is None: laurent@391: applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) laurent@391: else: laurent@391: applicationName = applicationDomain laurent@391: currentDir = os.getcwd() laurent@391: os.chdir(applicationDirectoryPath) laurent@391: laurent@391: languageDict = getlanguageDict() laurent@391: laurent@391: for langCode in languageDict.keys(): laurent@391: if langCode == 'en': laurent@391: pass laurent@391: else: laurent@391: langPOfileName = "%s_%s.po" % (applicationName , langCode) laurent@391: if os.path.exists(langPOfileName): laurent@391: fileList = '' laurent@391: for fileName in listOf_extraPo: laurent@391: fileList += ("%s_%s.po " % (fileName,langCode)) laurent@391: cmd = "msgcat -s --no-wrap %s %s > %s.cat" % (langPOfileName, fileList, langPOfileName) laurent@391: if verbose: print cmd laurent@391: os.system(cmd) laurent@391: if targetDir is None: laurent@391: pass laurent@391: else: laurent@391: mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) laurent@391: cmd = "msgfmt --output-file=%s/%s.mo %s_%s.po.cat" % (mo_targetDir,applicationName,applicationName,langCode) laurent@391: if verbose: print cmd laurent@391: os.system(cmd) laurent@391: os.chdir(currentDir) laurent@391: laurent@391: # ----------------------------------------------------------------------------- laurent@391: # m a k e M O ( ) -- Compile the Portable Object files into the Machine Object stored in the right location. -- laurent@391: # ^^^^^^^^^^^^^^^ laurent@391: # laurent@391: def makeMO(applicationDirectoryPath,targetDir='./locale',applicationDomain=None, verbose=0, forceEnglish=0) : laurent@391: """Compile the Portable Object files into the Machine Object stored in the right location. laurent@391: laurent@391: makeMO converts all translated language-specific PO files located inside laurent@391: the application directory into the binary .MO files stored inside the laurent@391: LC_MESSAGES sub-directory for the found locale files. laurent@391: laurent@391: makeMO searches for all files that have a name of the form 'app_xx.po' laurent@391: inside the application directory specified by the first argument. The laurent@391: 'app' is the application domain name (that can be specified by the laurent@391: applicationDomain argument or is taken from the directory name). The 'xx' laurent@391: corresponds to one of the ISO 639 two-letter language codes. laurent@391: laurent@391: makeMo stores the resulting files inside a sub-directory of `targetDir` laurent@391: called xx/LC_MESSAGES where 'xx' corresponds to the 2-letter language laurent@391: code. laurent@391: """ laurent@391: if targetDir is None: laurent@391: targetDir = './locale' laurent@391: if verbose: laurent@391: print "Target directory for .mo files is: %s" % targetDir laurent@391: laurent@391: if applicationDomain is None: laurent@391: applicationName = fileBaseOf(applicationDirectoryPath,withPath=0) laurent@391: else: laurent@391: applicationName = applicationDomain laurent@391: currentDir = os.getcwd() laurent@391: os.chdir(applicationDirectoryPath) laurent@391: laurent@391: languageDict = getlanguageDict() laurent@391: laurent@391: for langCode in languageDict.keys(): laurent@391: if (langCode == 'en') and (forceEnglish==0): laurent@391: pass laurent@391: else: laurent@391: langPOfileName = "%s_%s.po" % (applicationName , langCode) laurent@391: if os.path.exists(langPOfileName): laurent@391: mo_targetDir = "%s/%s/LC_MESSAGES" % (targetDir,langCode) laurent@391: if not os.path.exists(mo_targetDir): laurent@391: mkdir(mo_targetDir) laurent@391: cmd = 'msgfmt --output-file="%s/%s.mo" "%s_%s.po"' % (mo_targetDir,applicationName,applicationName,langCode) laurent@391: if verbose: print cmd laurent@391: os.system(cmd) laurent@391: os.chdir(currentDir) laurent@391: laurent@391: # ----------------------------------------------------------------------------- laurent@391: # p r i n t U s a g e -- Displays how to use this script from the command line -- laurent@391: # ^^^^^^^^^^^^^^^^^^^ laurent@391: # laurent@391: def printUsage(errorMsg=None) : laurent@391: """Displays how to use this script from the command line.""" laurent@391: print """ laurent@391: ################################################################################## laurent@391: # mki18n : Make internationalization files. # laurent@391: # Uses the GNU gettext system to create PO (Portable Object) files # laurent@391: # from source code, coimpile PO into MO (Machine Object) files. # laurent@391: # Supports C,C++,Python source files. # laurent@391: # # laurent@391: # Usage: mki18n {OPTION} [appDirPath] # laurent@391: # # laurent@391: # Options: # laurent@391: # -e : When -m is used, forces English .mo file creation # laurent@391: # -h : prints this help # laurent@391: # -m : make MO from existing PO files # laurent@391: # -p : make PO, update PO files: Creates a new messages.pot # laurent@391: # file. Creates a dom_xx.po.new for every existing # laurent@391: # language specific .po file. ('xx' stands for the ISO639 # laurent@391: # two-letter language code and 'dom' stands for the # laurent@391: # application domain name). mki18n requires that you # laurent@391: # write a 'app.fil' file which contains the list of all # laurent@391: # source code to parse. # laurent@391: # -v : verbose (prints comments while running) # laurent@391: # --domain=appName : specifies the application domain name. By default # laurent@391: # the directory name is used. # laurent@391: # --moTarget=dir : specifies the directory where .mo files are stored. # laurent@391: # If not specified, the target is './locale' # laurent@391: # # laurent@391: # You must specify one of the -p or -m option to perform the work. You can # laurent@391: # specify the path of the target application. If you leave it out mki18n # laurent@391: # will use the current directory as the application main directory. # laurent@391: # # laurent@391: ##################################################################################""" laurent@391: if errorMsg: laurent@391: print "\n ERROR: %s" % errorMsg laurent@391: laurent@391: # ----------------------------------------------------------------------------- laurent@391: # f i l e B a s e O f ( ) -- Return base name of filename -- laurent@391: # ^^^^^^^^^^^^^^^^^^^^^^^ laurent@391: # laurent@391: def fileBaseOf(filename,withPath=0) : laurent@391: """fileBaseOf(filename,withPath) ---> string laurent@391: laurent@391: Return base name of filename. The returned string never includes the extension. laurent@391: Use os.path.basename() to return the basename with the extension. The laurent@391: second argument is optional. If specified and if set to 'true' (non zero) laurent@391: the string returned contains the full path of the file name. Otherwise the laurent@391: path is excluded. laurent@391: laurent@391: [Example] laurent@391: >>> fn = 'd:/dev/telepath/tvapp/code/test.html' laurent@391: >>> fileBaseOf(fn) laurent@391: 'test' laurent@391: >>> fileBaseOf(fn) laurent@391: 'test' laurent@391: >>> fileBaseOf(fn,1) laurent@391: 'd:/dev/telepath/tvapp/code/test' laurent@391: >>> fileBaseOf(fn,0) laurent@391: 'test' laurent@391: >>> fn = 'abcdef' laurent@391: >>> fileBaseOf(fn) laurent@391: 'abcdef' laurent@391: >>> fileBaseOf(fn,1) laurent@391: 'abcdef' laurent@391: >>> fn = "abcdef." laurent@391: >>> fileBaseOf(fn) laurent@391: 'abcdef' laurent@391: >>> fileBaseOf(fn,1) laurent@391: 'abcdef' laurent@391: """ laurent@391: pos = filename.rfind('.') laurent@391: if pos > 0: laurent@391: filename = filename[:pos] laurent@391: if withPath: laurent@391: return filename laurent@391: else: laurent@391: return os.path.basename(filename) laurent@391: # ----------------------------------------------------------------------------- laurent@391: # m k d i r ( ) -- Create a directory (and possibly the entire tree) -- laurent@391: # ^^^^^^^^^^^^^ laurent@391: # laurent@391: def mkdir(directory) : laurent@391: """Create a directory (and possibly the entire tree). laurent@391: laurent@391: The os.mkdir() will fail to create a directory if one of the laurent@391: directory in the specified path does not exist. mkdir() laurent@391: solves this problem. It creates every intermediate directory laurent@391: required to create the final path. Under Unix, the function laurent@391: only supports forward slash separator, but under Windows and MacOS laurent@391: the function supports the forward slash and the OS separator (backslash laurent@391: under windows). laurent@391: """ laurent@391: laurent@391: # translate the path separators laurent@391: directory = unixpath(directory) laurent@391: # build a list of all directory elements laurent@391: aList = filter(lambda x: len(x)>0, directory.split('/')) laurent@391: theLen = len(aList) laurent@391: # if the first element is a Windows-style disk drive laurent@391: # concatenate it with the first directory laurent@391: if aList[0].endswith(':'): laurent@391: if theLen > 1: laurent@391: aList[1] = aList[0] + '/' + aList[1] laurent@391: del aList[0] laurent@391: theLen -= 1 laurent@391: # if the original directory starts at root, laurent@391: # make sure the first element of the list laurent@391: # starts at root too laurent@391: if directory[0] == '/': laurent@391: aList[0] = '/' + aList[0] laurent@391: # Now iterate through the list, check if the laurent@391: # directory exists and if not create it laurent@391: theDir = '' laurent@391: for i in range(theLen): laurent@391: theDir += aList[i] laurent@391: if not os.path.exists(theDir): laurent@391: os.mkdir(theDir) laurent@391: theDir += '/' laurent@391: laurent@391: # ----------------------------------------------------------------------------- laurent@391: # u n i x p a t h ( ) -- Return a path name that contains Unix separator. -- laurent@391: # ^^^^^^^^^^^^^^^^^^^ laurent@391: # laurent@391: def unixpath(thePath) : laurent@391: r"""Return a path name that contains Unix separator. laurent@391: laurent@391: [Example] laurent@391: >>> unixpath(r"d:\test") laurent@391: 'd:/test' laurent@391: >>> unixpath("d:/test/file.txt") laurent@391: 'd:/test/file.txt' laurent@391: >>> laurent@391: """ laurent@391: thePath = os.path.normpath(thePath) laurent@391: if os.sep == '/': laurent@391: return thePath laurent@391: else: laurent@391: return thePath.replace(os.sep,'/') laurent@391: laurent@391: # ----------------------------------------------------------------------------- laurent@391: laurent@391: # S c r i p t e x e c u t i o n -- Runs when invoked from the command line -- laurent@391: # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ laurent@391: # laurent@391: if __name__ == "__main__": laurent@391: import getopt # command line parsing laurent@391: argc = len(sys.argv) laurent@391: if argc == 1: laurent@391: printUsage('Missing argument: specify at least one of -m or -p (or both).') laurent@391: sys.exit(1) laurent@391: # If there is some arguments, parse the command line laurent@391: validOptions = "ehmpv" laurent@391: validLongOptions = ['domain=', 'moTarget='] laurent@391: option = {} laurent@391: option['forceEnglish'] = 0 laurent@391: option['mo'] = 0 laurent@391: option['po'] = 0 laurent@391: option['verbose'] = 0 laurent@391: option['domain'] = None laurent@391: option['moTarget'] = None laurent@391: try: laurent@391: optionList,pargs = getopt.getopt(sys.argv[1:],validOptions,validLongOptions) laurent@391: except getopt.GetoptError, e: laurent@391: printUsage(e[0]) laurent@391: sys.exit(1) laurent@391: for (opt,val) in optionList: laurent@391: if (opt == '-h'): laurent@391: printUsage() laurent@391: sys.exit(0) laurent@391: elif (opt == '-e'): option['forceEnglish'] = 1 laurent@391: elif (opt == '-m'): option['mo'] = 1 laurent@391: elif (opt == '-p'): option['po'] = 1 laurent@391: elif (opt == '-v'): option['verbose'] = 1 laurent@391: elif (opt == '--domain'): option['domain'] = val laurent@391: elif (opt == '--moTarget'): option['moTarget'] = val laurent@391: if len(pargs) == 0: laurent@391: appDirPath = os.getcwd() laurent@391: if option['verbose']: laurent@391: print "No project directory given. Using current one: %s" % appDirPath laurent@391: elif len(pargs) == 1: laurent@391: appDirPath = pargs[0] laurent@391: else: laurent@391: printUsage('Too many arguments (%u). Use double quotes if you have space in directory name' % len(pargs)) laurent@391: sys.exit(1) laurent@391: if option['domain'] is None: laurent@391: # If no domain specified, use the name of the target directory laurent@391: option['domain'] = fileBaseOf(appDirPath) laurent@391: if option['verbose']: laurent@391: print "Application domain used is: '%s'" % option['domain'] laurent@391: if option['po']: laurent@391: try: laurent@391: makePO(appDirPath,option['domain'],option['verbose']) laurent@391: except IOError, e: laurent@391: printUsage(e[1] + '\n You must write a file app.fil that contains the list of all files to parse.') laurent@391: if option['mo']: laurent@391: makeMO(appDirPath,option['moTarget'],option['domain'],option['verbose'],option['forceEnglish']) laurent@391: sys.exit(1) laurent@391: laurent@391: laurent@391: # -----------------------------------------------------------------------------