goa/tools/i18n.py
changeset 0 b97547f5f1fa
child 447 0e52d72104a6
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 #!/usr/bin/env python
       
     2 """This script is just a thin wrapper around ``msgcat`` and ``msgfmt``
       
     3 to generate ``.mo`` files
       
     4 """
       
     5 
       
     6 import sys
       
     7 import os
       
     8 import os.path as osp
       
     9 import shutil
       
    10 from tempfile import mktemp
       
    11 from glob import glob
       
    12 from mx.DateTime import now
       
    13 
       
    14 from logilab.common.fileutils import ensure_fs_mode
       
    15 from logilab.common.shellutils import find, rm
       
    16 
       
    17 from yams import BASE_TYPES
       
    18 
       
    19 from cubicweb import CW_SOFTWARE_ROOT
       
    20 # from cubicweb.__pkginfo__ import version as cubicwebversion
       
    21 cubicwebversion = '2.48.2'
       
    22 
       
    23 DEFAULT_POT_HEAD = r'''# LAX application po file
       
    24 
       
    25 msgid ""
       
    26 msgstr ""
       
    27 "Project-Id-Version: cubicweb %s\n"
       
    28 "PO-Revision-Date: 2008-03-28 18:14+0100\n"
       
    29 "Last-Translator: Logilab Team <contact@logilab.fr>\n"
       
    30 "Language-Team: fr <contact@logilab.fr>\n"
       
    31 "MIME-Version: 1.0\n"
       
    32 "Content-Type: text/plain; charset=UTF-8\n"
       
    33 "Content-Transfer-Encoding: 8bit\n"
       
    34 "Generated-By: cubicweb-devtools\n"
       
    35 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
       
    36 
       
    37 ''' % cubicwebversion
       
    38 
       
    39 
       
    40 STDLIB_ERTYPES = BASE_TYPES | set( ('EUser', 'EProperty', 'Card', 'identity', 'for_user') )
       
    41 
       
    42 def create_dir(directory):
       
    43     """create a directory if it doesn't exist yet"""
       
    44     try:
       
    45         os.makedirs(directory)
       
    46         print 'created directory', directory
       
    47     except OSError, ex:
       
    48         import errno
       
    49         if ex.errno != errno.EEXIST:
       
    50             raise
       
    51         print 'directory %s already exists' % directory
       
    52 
       
    53 def execute(cmd):
       
    54     """display the command, execute it and raise an Exception if returned
       
    55     status != 0
       
    56     """
       
    57     print cmd.replace(os.getcwd() + os.sep, '')
       
    58     status = os.system(cmd)
       
    59     if status != 0:
       
    60         raise Exception()
       
    61 
       
    62 def add_msg(w, msgid):
       
    63     """write an empty pot msgid definition"""
       
    64     if isinstance(msgid, unicode):
       
    65         msgid = msgid.encode('utf-8')
       
    66     msgid = msgid.replace('"', r'\"').splitlines()
       
    67     if len(msgid) > 1:
       
    68         w('msgid ""\n')
       
    69         for line in msgid:
       
    70             w('"%s"' % line.replace('"', r'\"'))
       
    71     else:
       
    72         w('msgid "%s"\n' % msgid[0])
       
    73     w('msgstr ""\n\n')
       
    74 
       
    75 
       
    76 def generate_schema_pot(w, vreg, tmpldir):
       
    77     """generate a pot file with schema specific i18n messages
       
    78 
       
    79     notice that relation definitions description and static vocabulary
       
    80     should be marked using '_' and extracted using xgettext
       
    81     """
       
    82     cube = tmpldir and osp.split(tmpldir)[-1]
       
    83     config = vreg.config
       
    84     vreg.register_objects(config.vregistry_path())
       
    85     w(DEFAULT_POT_HEAD)
       
    86     _generate_schema_pot(w, vreg, vreg.schema, libschema=None, # no libschema for now
       
    87                          cube=cube)
       
    88 
       
    89 
       
    90 def _generate_schema_pot(w, vreg, schema, libschema=None, cube=None):
       
    91     w('# schema pot file, generated on %s\n' % now().strftime('%Y-%m-%d %H:%M:%S'))
       
    92     w('# \n')
       
    93     w('# singular and plural forms for each entity type\n')
       
    94     w('\n')
       
    95     # XXX hard-coded list of stdlib's entity schemas
       
    96     libschema = libschema or STDLIB_ERTYPES
       
    97     entities = [e for e in schema.entities() if not e in libschema]
       
    98     done = set()
       
    99     for eschema in sorted(entities):
       
   100         etype = eschema.type
       
   101         add_msg(w, etype)
       
   102         add_msg(w, '%s_plural' % etype)
       
   103         if not eschema.is_final():
       
   104             add_msg(w, 'This %s' % etype)
       
   105             add_msg(w, 'New %s' % etype)
       
   106             add_msg(w, 'add a %s' % etype)
       
   107             add_msg(w, 'remove this %s' % etype)
       
   108         if eschema.description and not eschema.description in done:
       
   109             done.add(eschema.description)
       
   110             add_msg(w, eschema.description)
       
   111     w('# subject and object forms for each relation type\n')
       
   112     w('# (no object form for final relation types)\n')
       
   113     w('\n')
       
   114     if libschema is not None:
       
   115         relations = [r for r in schema.relations() if not r in libschema]
       
   116     else:
       
   117         relations = schema.relations()
       
   118     for rschema in sorted(set(relations)):
       
   119         rtype = rschema.type
       
   120         add_msg(w, rtype)
       
   121         if not (schema.rschema(rtype).is_final() or rschema.symetric):
       
   122             add_msg(w, '%s_object' % rtype)
       
   123         if rschema.description and rschema.description not in done:
       
   124             done.add(rschema.description)
       
   125             add_msg(w, rschema.description)
       
   126     w('# add related box generated message\n')
       
   127     w('\n')
       
   128     for eschema in schema.entities():
       
   129         if eschema.is_final():
       
   130             continue
       
   131         entity = vreg.etype_class(eschema)(None, None)
       
   132         for x, rschemas in (('subject', eschema.subject_relations()),
       
   133                             ('object', eschema.object_relations())):
       
   134             for rschema in rschemas:
       
   135                 if rschema.is_final():
       
   136                     continue
       
   137                 for teschema in rschema.targets(eschema, x):
       
   138                     if defined_in_library(libschema, eschema, rschema, teschema, x):
       
   139                         continue
       
   140                     if entity.relation_mode(rschema.type, teschema.type, x) == 'create':
       
   141                         if x == 'subject':
       
   142                             label = 'add %s %s %s %s' % (eschema, rschema, teschema, x)
       
   143                             label2 = "creating %s (%s %%(linkto)s %s %s)" % (teschema, eschema, rschema, teschema)
       
   144                         else:
       
   145                             label = 'add %s %s %s %s' % (teschema, rschema, eschema, x)
       
   146                             label2 = "creating %s (%s %s %s %%(linkto)s)" % (teschema, teschema, rschema, eschema)
       
   147                         add_msg(w, label)
       
   148                         add_msg(w, label2)
       
   149     cube = (cube or 'cubicweb') + '.'
       
   150     done = set()
       
   151     for reg, objdict in vreg.items():
       
   152         for objects in objdict.values():
       
   153             for obj in objects:
       
   154                 objid = '%s_%s' % (reg, obj.id)
       
   155                 if objid in done:
       
   156                     continue
       
   157                 if obj.__module__.startswith(cube) and obj.property_defs:
       
   158                     add_msg(w, '%s_description' % objid)
       
   159                     add_msg(w, objid)
       
   160                     done.add(objid)
       
   161                     
       
   162 def defined_in_library(libschema, etype, rtype, tetype, x):
       
   163     """return true if the given relation definition exists in cubicweb's library"""
       
   164     if libschema is None:
       
   165         return False
       
   166     if x == 'subject':
       
   167         subjtype, objtype = etype, tetype
       
   168     else:
       
   169         subjtype, objtype = tetype, etype
       
   170     try:
       
   171         return libschema.rschema(rtype).has_rdef(subjtype, objtype)
       
   172     except (KeyError, AttributeError):
       
   173         # if libschema is a simple list of entity types (lax specific)
       
   174         # or if the relation could not be found
       
   175         return False
       
   176 
       
   177 
       
   178 
       
   179 # XXX check if this is a pure duplication of the original
       
   180 # `cubicweb.common.i18n` function
       
   181 def compile_i18n_catalogs(sourcedirs, destdir, langs):
       
   182     """generate .mo files for a set of languages into the `destdir` i18n directory
       
   183     """
       
   184     print 'compiling %s catalogs...' % destdir
       
   185     errors = []
       
   186     for lang in langs:
       
   187         langdir = osp.join(destdir, lang, 'LC_MESSAGES')
       
   188         if not osp.exists(langdir):
       
   189             create_dir(langdir)
       
   190         pofiles = [osp.join(path, '%s.po' % lang) for path in sourcedirs]
       
   191         pofiles = [pof for pof in pofiles if osp.exists(pof)]
       
   192         mergedpo = osp.join(destdir, '%s_merged.po' % lang)
       
   193         try:
       
   194             # merge application messages' catalog with the stdlib's one
       
   195             execute('msgcat --use-first --sort-output --strict %s > %s'
       
   196                     % (' '.join(pofiles), mergedpo))
       
   197             # make sure the .mo file is writeable and compile with *msgfmt*
       
   198             applmo = osp.join(destdir, lang, 'LC_MESSAGES', 'cubicweb.mo')
       
   199             try:
       
   200                 ensure_fs_mode(applmo)
       
   201             except OSError:
       
   202                 pass # suppose not osp.exists
       
   203             execute('msgfmt %s -o %s' % (mergedpo, applmo))
       
   204         except Exception, ex:
       
   205             errors.append('while handling language %s: %s' % (lang, ex))
       
   206         try:
       
   207             # clean everything
       
   208             os.unlink(mergedpo)
       
   209         except Exception:
       
   210             continue
       
   211     return errors
       
   212 
       
   213 
       
   214 def update_cubes_catalog(vreg, appdirectory, langs):
       
   215     toedit = []
       
   216     tmpl = osp.basename(osp.normpath(appdirectory))
       
   217     tempdir = mktemp()
       
   218     os.mkdir(tempdir)
       
   219     print '*' * 72
       
   220     print 'updating %s cube...' % tmpl
       
   221     os.chdir(appdirectory)
       
   222     potfiles = []
       
   223     if osp.exists(osp.join('i18n', 'entities.pot')):
       
   224         potfiles = potfiles.append( osp.join('i18n', scfile) )
       
   225     print '******** extract schema messages'
       
   226     schemapot = osp.join(tempdir, 'schema.pot')
       
   227     potfiles.append(schemapot)
       
   228     # XXX
       
   229     generate_schema_pot(open(schemapot, 'w').write, vreg, appdirectory)
       
   230     print '******** extract Javascript messages'
       
   231     jsfiles =  find('.', '.js')
       
   232     if jsfiles:
       
   233         tmppotfile = osp.join(tempdir, 'js.pot')
       
   234         execute('xgettext --no-location --omit-header -k_ -L java --from-code=utf-8 -o %s %s'
       
   235                 % (tmppotfile, ' '.join(jsfiles)))
       
   236         # no pot file created if there are no string to translate
       
   237         if osp.exists(tmppotfile): 
       
   238             potfiles.append(tmppotfile)
       
   239     print '******** create cube specific catalog'
       
   240     tmppotfile = osp.join(tempdir, 'generated.pot')
       
   241     execute('xgettext --no-location --omit-header -k_ -o %s %s'
       
   242             % (tmppotfile, ' '.join(glob('*.py'))))
       
   243     if osp.exists(tmppotfile): # doesn't exists of no translation string found
       
   244         potfiles.append(tmppotfile)
       
   245     potfile = osp.join(tempdir, 'cube.pot')
       
   246     print '******** merging .pot files'
       
   247     execute('msgcat %s > %s' % (' '.join(potfiles), potfile))
       
   248     print '******** merging main pot file with existing translations'
       
   249     os.chdir('i18n')
       
   250     for lang in langs:
       
   251         print '****', lang
       
   252         tmplpo = '%s.po' % lang
       
   253         if not osp.exists(tmplpo):
       
   254             shutil.copy(potfile, tmplpo)
       
   255         else:
       
   256             execute('msgmerge -N -s %s %s > %snew' % (tmplpo, potfile, tmplpo))
       
   257             ensure_fs_mode(tmplpo)
       
   258             shutil.move('%snew' % tmplpo, tmplpo)
       
   259         toedit.append(osp.abspath(tmplpo))
       
   260     # cleanup
       
   261     rm(tempdir)
       
   262     # instructions pour la suite
       
   263     print '*' * 72
       
   264     print 'you can now edit the following files:'
       
   265     print '* ' + '\n* '.join(toedit)
       
   266              
       
   267 
       
   268 def getlangs(i18ndir):
       
   269     return [fname[:-3] for fname in os.listdir(i18ndir)
       
   270             if fname.endswith('.po')]
       
   271 
       
   272 
       
   273 def get_i18n_directory(appdirectory):
       
   274     if not osp.isdir(appdirectory):
       
   275         print '%s is not an application directory' % appdirectory
       
   276         sys.exit(2)
       
   277     i18ndir = osp.join(appdirectory, 'i18n')
       
   278     if not osp.isdir(i18ndir):
       
   279         print '%s is not an application directory ' \
       
   280             '(i18n subdirectory missing)' % appdirectory
       
   281         sys.exit(2)
       
   282     return i18ndir