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', 'entities.pot') ) |
|
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 |
|