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