home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 1999 August / SGI Freeware 1999 August.iso / dist / fw_gnome-python.idb / usr / freeware / lib / python1.5 / site-packages / gettext.py.z / gettext.py
Encoding:
Python Source  |  1999-07-16  |  8.2 KB  |  297 lines

  1. """This module allows python programs to use GNU gettext message catalogs.
  2.  
  3. Author: James Henstridge <james@daa.com.au>
  4. (This is loosely based on gettext.pl in the GNU gettext distribution)
  5.  
  6. The best way to use it is like so:
  7.     import gettext
  8.     gettext.bindtextdomain(PACKAGE, LOCALEDIR)
  9.     gettext.textdomain(PACKAGE)
  10.     _ = gettext.gettext
  11.     print _('Hello World')
  12.  
  13. where PACKAGE is the domain for this package, and LOCALEDIR is usually
  14. '$prefix/share/locale' where $prefix is the install prefix.
  15.  
  16. If you have more than one catalog to use, you can directly create catalog
  17. objects.  These objects are created as so:
  18.     import gettext
  19.     cat = gettext.Catalog(PACKAGE, localedir=LOCALEDIR)
  20.     _ = cat.gettext
  21.     print _('Hello World')
  22.  
  23. The catalog object can also be accessed as a dictionary (ie cat['hello']).
  24.  
  25. There are also some experimental features.  You can add to the catalog, just
  26. as you would with a normal dictionary.  When you are finished, you can call
  27. its save method, which will create a new .mo file containing all the
  28. translations:
  29.     import gettext
  30.     cat = Catalog()
  31.     cat['Hello'] = 'konichiwa'
  32.     cat.save('./tmp.mo')
  33.  
  34. Once you have written an internationalized program, you can create a .po file
  35. for it with "xgettext --keyword=_ fillename ...".  Then do the translation and
  36. compile it into a .mo file, ready for use with this module.  Note that you
  37. will have to use C style strings (ie. use double quotes) for proper string
  38. extraction.
  39. """
  40. import os, string
  41.  
  42. prefix = '/usr/local'
  43. localedir = prefix + '/share/locale'
  44.  
  45. lang = []
  46. for env in 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG':
  47.     if os.environ.has_key(env):
  48.         lang = string.split(os.environ[env], ':')
  49.         break
  50. if 'C' not in lang:
  51.     lang.append('C')
  52.  
  53. if os.environ.has_key('PY_XGETTEXT'):
  54.     xgettext = os.environ['PY_XGETTEXT']
  55. else:
  56.     xgettext = None
  57.  
  58. del os, string
  59.  
  60. error = 'gettext.error'
  61.  
  62. def _lsbStrToInt(str):
  63.     return ord(str[0]) + \
  64.            (ord(str[1]) << 8) + \
  65.            (ord(str[2]) << 16) + \
  66.            (ord(str[3]) << 24)
  67. def _intToLsbStr(int):
  68.     return chr(int         & 0xff) + \
  69.            chr((int >> 8)  & 0xff) + \
  70.            chr((int >> 16) & 0xff) + \
  71.            chr((int >> 24) & 0xff)
  72.  
  73. def _getpos(levels = 0):
  74.     """Returns the position in the code where the function was called.
  75.     The function uses some knowledge about python stack frames."""
  76.     import sys
  77.     # get access to the stack frame by generating an exception.
  78.     try:
  79.         raise RuntimeError
  80.     except RuntimeError:
  81.         frame = sys.exc_traceback.tb_frame
  82.     frame = frame.f_back # caller's frame
  83.     while levels > 0:
  84.         frame = frame.f_back
  85.         levels = levels - 1
  86.     return (frame.f_globals['__name__'],
  87.         frame.f_code.co_name,
  88.         frame.f_lineno)
  89.  
  90. class Catalog:
  91.     def __init__(self, domain=None, localedir=localedir):
  92.         self.domain = domain
  93.         self.localedir = localedir
  94.         self.cat = {}
  95.         if not domain: return
  96.         for self.lang in lang:
  97.             if self.lang == 'C':
  98.                 return
  99.             catalog = "%s//%s/LC_MESSAGES/%s.mo" % (
  100.                 localedir, self.lang, domain)
  101.             try:
  102.                 f = open(catalog, "rb")
  103.                 buffer = f.read()
  104.                 del f
  105.                 break
  106.             except IOError:
  107.                 pass
  108.         else:
  109.             return # assume C locale
  110.  
  111.         if _lsbStrToInt(buffer[:4]) != 0x950412de:
  112.             # magic number doesn't match
  113.             raise error, 'Bad magic number in %s' % (catalog,)
  114.  
  115.         self.revision = _lsbStrToInt(buffer[4:8])
  116.         nstrings = _lsbStrToInt(buffer[8:12])
  117.         origTabOffset  = _lsbStrToInt(buffer[12:16])
  118.         transTabOffset = _lsbStrToInt(buffer[16:20])
  119.         for i in range(nstrings):
  120.             origLength = _lsbStrToInt(buffer[origTabOffset:
  121.                              origTabOffset+4])
  122.             origOffset = _lsbStrToInt(buffer[origTabOffset+4:
  123.                              origTabOffset+8])
  124.             origTabOffset = origTabOffset + 8
  125.             origStr = buffer[origOffset:origOffset+origLength]
  126.         
  127.             transLength = _lsbStrToInt(buffer[transTabOffset:
  128.                               transTabOffset+4])
  129.             transOffset = _lsbStrToInt(buffer[transTabOffset+4:
  130.                               transTabOffset+8])
  131.             transTabOffset = transTabOffset + 8
  132.             transStr = buffer[transOffset:transOffset+transLength]
  133.             
  134.             self.cat[origStr] = transStr
  135.  
  136.     def gettext(self, string):
  137.         """Get the translation of a given string"""
  138.         if self.cat.has_key(string):
  139.             return self.cat[string]
  140.         else:
  141.             return string
  142.     # allow catalog access as cat(str) and cat[str] and cat.gettext(str)
  143.     __getitem__ = gettext
  144.     __call__ = gettext
  145.  
  146.     # this is experimental code for producing mo files from Catalog objects
  147.     def __setitem__(self, string, trans):
  148.         """Set the translation of a given string"""
  149.         self.cat[string] = trans
  150.     def save(self, file):
  151.         """Create a .mo file from a Catalog object"""
  152.         try:
  153.             f = open(file, "wb")
  154.         except IOError:
  155.             raise error, "can't open " + file + " for writing"
  156.         f.write(_intToLsbStr(0x950412de))    # magic number
  157.         f.write(_intToLsbStr(0))             # revision
  158.         f.write(_intToLsbStr(len(self.cat))) # nstrings
  159.  
  160.         oIndex = []; oData = ''
  161.         tIndex = []; tData = ''
  162.         for orig, trans in self.cat.items():
  163.             oIndex.append((len(orig), len(oData)))
  164.             oData = oData + orig + '\0'
  165.             tIndex.append((len(trans), len(tData)))
  166.             tData = tData + trans + '\0'
  167.         oIndexOfs = 20
  168.         tIndexOfs = oIndexOfs + 8 * len(oIndex)
  169.         oDataOfs = tIndexOfs + 8 * len(tIndex)
  170.         tDataOfs = oDataOfs + len(oData)
  171.         f.write(_intToLsbStr(oIndexOfs))
  172.         f.write(_intToLsbStr(tIndexOfs))
  173.         for length, offset in oIndex:
  174.             f.write(_intToLsbStr(length))
  175.             f.write(_intToLsbStr(offset + oDataOfs))
  176.         for length, offset in tIndex:
  177.             f.write(_intToLsbStr(length))
  178.             f.write(_intToLsbStr(offset + tDataOfs))
  179.         f.write(oData)
  180.         f.write(tData)
  181.  
  182. _cat = None
  183. _cats = {}
  184.  
  185. if xgettext:
  186.     class Catalog:
  187.         def __init__(self, domain, localedir):
  188.             self.domain = domain
  189.             self.localedir = localedir
  190.             self._strings = {}
  191.         def gettext(self, string):
  192.             # there is always one level of redirection for calls
  193.             # to this function
  194.             pos = _getpos(2) # get this function's caller
  195.             if self._strings.has_key(string):
  196.                 if pos not in self._strings[string]:
  197.                     self._strings[string].append(pos)
  198.             else:
  199.                 self._strings[string] = [pos]
  200.             return string
  201.         __getitem__ = gettext
  202.         __call__ = gettext
  203.         def __setitem__(self, item, data):
  204.             pass
  205.         def save(self, file):
  206.             pass
  207.         def output(self, fp):
  208.             import string
  209.             fp.write('# POT file for domain %s\n' % (self.domain,))
  210.             for str in self._strings.keys():
  211.                 pos = map(lambda x: "%s(%s):%d" % x,
  212.                       self._strings[str])
  213.                 pos.sort()
  214.                 length = 80
  215.                 for p in pos:
  216.                     if length + len(p) > 74:
  217.                         fp.write('\n#:')
  218.                         length = 2
  219.                     fp.write(' ')
  220.                     fp.write(p)
  221.                     length = length + 1 + len(p)
  222.                 fp.write('\n')
  223.                 if '\n' in str:
  224.                     fp.write('msgid ""\n')
  225.                     lines = string.split(str, '\n')
  226.                     lines = map(lambda x:
  227.                             '"%s\\n"\n' % (x,),
  228.                             lines[:-1]) + \
  229.                             ['"%s"\n' % (lines[-1],)]
  230.                     fp.writelines(lines)
  231.                 else:
  232.                     fp.write('msgid "%s"\n' % (str,))
  233.                 fp.write('msgstr ""\n')
  234.                 
  235.     import sys
  236.     if hasattr(sys, 'exitfunc'):
  237.         _exitchain = sys.exitfunc
  238.     else:
  239.         _exitchain = None
  240.     def exitfunc(dir=xgettext, _exitchain=_exitchain):
  241.         # actually output all the .pot files.
  242.         import os
  243.         for file in _cats.keys():
  244.             fp = open(os.path.join(dir, file + '.pot'), 'w')
  245.             cat = _cats[file]
  246.             cat.output(fp)
  247.             fp.close()
  248.         if _exitchain: _exitchain()
  249.     sys.exitfunc = exitfunc
  250.     del sys, exitfunc, _exitchain, xgettext
  251.  
  252. def bindtextdomain(domain, localedir=localedir):
  253.     global _cat
  254.     if not _cats.has_key(domain):
  255.         _cats[domain] = Catalog(domain, localedir)
  256.     if not _cat: _cat = _cats[domain]
  257.  
  258. def textdomain(domain):
  259.     global _cat
  260.     if not _cats.has_key(domain):
  261.         _cats[domain] = Catalog(domain)
  262.     _cat = _cats[domain]
  263.  
  264. def gettext(string):
  265.     if _cat == None: raise error, "No catalog loaded"
  266.     return _cat.gettext(string)
  267.  
  268. _ = gettext
  269.  
  270. def dgettext(domain, string):
  271.     if domain is None:
  272.         return gettext(string)
  273.     if not _cats.has_key(domain):
  274.         raise error, "Domain '" + domain + "' not loaded"
  275.     return _cats[domain].gettext(string)
  276.  
  277. def test():
  278.     import sys
  279.     global localedir
  280.     if len(sys.argv) not in (2, 3):
  281.         print "Usage: %s DOMAIN [LOCALEDIR]" % (sys.argv[0],)
  282.         sys.exit(1)
  283.     domain = sys.argv[1]
  284.     if len(sys.argv) == 3:
  285.         bindtextdomain(domain, sys.argv[2])
  286.     textdomain(domain)
  287.     info = gettext('')  # this is where special info is often stored
  288.     if info:
  289.         print "Info for domain %s, lang %s." % (domain, _cat.lang)
  290.         print info
  291.     else:
  292.         print "No info given in mo file."
  293.  
  294. if __name__ == '__main__':
  295.     test()
  296.  
  297.