* the translations are graphically exportable by module

* allow importation of po files

bzr revid: christophe@tinyerp.com-20080718131350-tftqhvwyyaz8r1q9
This commit is contained in:
Christophe Simonis 2008-07-18 15:13:50 +02:00
parent 7cc947cb02
commit 2ccf0092a7
4 changed files with 119 additions and 54 deletions

View File

@ -33,8 +33,9 @@
<separator string="Export translation file" colspan="2"/>
<field name="lang" width="300"/>
<field name="format"/>
<field name="modules" width="500" height="200"/>
<field name="state" invisible="1"/>
<newline/><label/><newline/><label/>
<newline/><label/>
</group>
<group col="1" states="get" fill="0">
<separator string="Export done"/>

View File

@ -123,7 +123,9 @@ class wizard_export_lang(osv.osv_memory):
def act_getfile(self, cr, uid, ids, context=None):
this = self.browse(cr, uid, ids)[0]
file=tools.trans_generate(this.lang, 'all', dbname=cr.dbname)
mods = map(lambda m: m.name, this.modules)
mods.sort()
file=tools.trans_generate(this.lang, mods, dbname=cr.dbname)
buf=StringIO.StringIO()
if this.format == 'csv':
@ -135,8 +137,9 @@ class wizard_export_lang(osv.osv_memory):
this.advice = _("Save this document to a .po file and edit it with a specific software or a text editor. The file encoding is UTF-8.")
file.pop(0)
writer = tools.TinyPoFile(buf)
writer.write_infos(mods)
for module, type, name, res_id, src, trad in file:
writer.write(type, name, res_id, src, trad)
writer.write(module, type, name, res_id, src, trad)
else:
raise osv_except(_('Bad file format'))
@ -149,6 +152,7 @@ class wizard_export_lang(osv.osv_memory):
_columns = {
'lang': fields.selection(_get_languages, 'Language',required=True),
'format': fields.selection( ( ('csv','CSV File'), ('po','PO File') ), 'File Format', required=True),
'modules': fields.many2many('ir.module.module', 'rel_modules_langexport', 'wiz_id', 'module_id', 'Modules', domain=[('state','=','installed')]),
'data': fields.binary('File', readonly=True),
'advice': fields.text('', readonly=True),
'state': fields.selection( ( ('choose','choose'), # choose language

View File

@ -57,10 +57,16 @@ class wizard_import_lang(wizard.interface):
def _import_lang(self, cr, uid, data, context):
form=data['form']
#buf=base64.decodestring(form['data']).split('\n')
fileobj = TemporaryFile('w+')
fileobj.write( base64.decodestring(form['data']) )
tools.trans_load_data(cr.dbname, fileobj, form['code'], lang_name=form['name'])
# now we determine the file format
fileobj.seek(0)
first_line = fileobj.readline().strip()
fileformat = first_line.endswith("type,name,res_id,src,value") and 'csv' or 'po'
fileobj.seek(0)
tools.trans_load_data(cr.dbname, fileobj, fileformat, form['code'], lang_name=form['name'])
fileobj.close()
return {}

View File

@ -35,6 +35,7 @@ import ir
import netsvc
from tools.misc import UpdateableStr
import inspect
import mx.DateTime as mxdt
class UNIX_LINE_TERMINATOR(csv.excel):
lineterminator = '\n'
@ -75,45 +76,94 @@ class TinyPoFile(object):
def __iter__(self):
self.buffer.seek(0)
self.lines = self.buffer.readlines()
self.first = True
return self
def next(self):
def unquote(str):
return str[1:-1].replace("\\n", "\n") \
.replace('\\"', "\"")
type = name = res_id = source = trad = None
line = True
line = self.buffer.readline().strip()
while line.startswith('#') or line == '':
if line.startwith('#:'):
line = None
while not line:
if 0 == len(self.lines):
raise StopIteration()
line = self.lines.pop(0).strip()
while line.startswith('#'):
if line.startswith('#:'):
type, name, res_id = line[2:].strip().split(':')
line = self.buffer.readline().strip()
line = self.lines.pop(0).strip()
if not line.startswith('msgid'):
raise Exception("malformed file")
source = line[7:-1]
line = self.buffer.readline().strip()
while not line.startwith('msgstr'):
line = self.lines.pop(0).strip()
if not source and self.first:
# if the source is "" and it's the first msgid, it's the special
# msgstr with the informations about the traduction and the
# traductor; we skip it
while line:
line = self.lines.pop(0).strip()
return next()
while not line.startswith('msgstr'):
if not line:
raise Exception('malformed file')
source += line[1:-1]
line = self.buffer.readline().strip()
source += unquote(line)
line = self.lines.pop(0).strip()
trad = line[8:-1]
line = self.buffer.readline().strip()
line = self.lines.pop(0).strip()
while line:
trad += line[1:-1]
line = self.buffer.readline().strip()
trad += unquote(line)
line = self.lines.pop(0).strip()
self.first = False
return type, name, res_id, source, trad
def write(self, type, name, res_id, source, trad):
def quote(str):
return '"%s"' % str.replace('"','\\"')
def write_infos(self, modules):
import release
self.buffer.write("# Translation of %(project)s.\n" \
"# This file containt the translation of the following modules:\n" \
"%(modules)s" \
"#\n" \
"msgid \"\"\n" \
"msgstr \"\"\n" \
"\"Project-Id-Version: %(project)s %(version)s\"\n" \
"\"Report-Msgid-Bugs-To: %(bugmail)s\"\n" \
"\"POT-Creation-Date: %(now)s\"\n" \
"\"PO-Revision-Date: %(now)s\"\n" \
"\"Last-Translator: <>\"\n" \
"\"Language-Team: \"\n" \
"\"MIME-Version: 1.0\"\n" \
"\"Content-Type: text/plain; charset=UTF-8\"\n" \
"\"Content-Transfer-Encoding: \"\n" \
"\"Plural-Forms: \"\n" \
"\n"
self.buffer.write("#, python-format\n" \
"#: %s:%s:%s\n" \
"msgid %s\n" \
"msgstr %s\n\n" \
% (type, name, str(res_id), quote(source), quote(trad))
)
% { 'project': release.description,
'version': release.version,
'modules': reduce(lambda s, m: s + "#\t* %s\n" % m, modules, ""),
'bugmail': 'support@tinyerp.com', #TODO: use variable from release
'now': mxdt.ISO.strUTC(mxdt.ISO.DateTime.utc()),
}
)
def write(self, module, type, name, res_id, source, trad):
def quote(str):
return '"%s"' % str.replace('"','\\"') \
.replace('\n', '\\n"\n"')
self.buffer.write("#. module: %s\n" \
"#, python-format\n" \
"#: %s:%s:%s\n" \
"msgid %s\n" \
"msgstr %s\n\n" \
% (module, type, name, str(res_id), quote(source), quote(trad))
)
@ -297,7 +347,7 @@ def trans_generate(lang, modules, dbname=None):
if field_def.translate:
name = model + "," + field_name
trad = getattr(obj, field_name) or ''
push_translation(module, 'model', name, xml_name, trad.encode('utf8'))
push_translation(module, 'model', name, xml_name, trad)
# parse source code for _() calls
def get_module_from_path(path):
@ -305,14 +355,16 @@ def trans_generate(lang, modules, dbname=None):
if path.startswith(relative_addons_path):
path = path[len(relative_addons_path)+1:]
return path.split(os.path.sep)[0]
return None
def parse_py_files(path):
for root, dirs, files in os.walk(path):
for fname in fnmatch.filter(files, '*.py'):
fabsolutepath = join(root, fname)
frelativepath = fabsolutepath[len(tools.config['root_path'])+1:]
module = get_module_from_path(frelativepath)
return 'base' # files that are not in a module are considered as being in 'base' module
modobj = pool.get('ir.module.module')
for root, dirs, files in os.walk(tools.config['root_path']):
for fname in fnmatch.filter(files, '*.py'):
fabsolutepath = join(root, fname)
frelativepath = fabsolutepath[len(tools.config['root_path'])+1:]
module = get_module_from_path(frelativepath)
is_mod_installed = modobj.search(cr, uid, [('state', '=', 'installed'), ('name', '=', module)]) <> []
if (('all' in modules) or (module in modules)) and is_mod_installed:
code_string = tools.file_open(fabsolutepath, subdir='').read()
iter = re.finditer(
'[^a-zA-Z0-9_]_\([\s]*["\'](.+?)["\'][\s]*\)',
@ -321,13 +373,6 @@ def trans_generate(lang, modules, dbname=None):
push_translation(module, 'code', frelativepath, 0, i.group(1).encode('utf8'))
if 'all' in modules:
parse_py_files(tools.config['root_path'])
else:
for m in modules:
parse_py_files(join(tools.config['addons_path'], m))
out = [["module","type","name","res_id","src","value"]] # header
# translate strings marked as to be translated
for module, type, name, id, source in _to_translate:
@ -341,13 +386,15 @@ def trans_load(db_name, filename, lang, strict=False):
logger = netsvc.Logger()
try:
fileobj = open(filename,'r')
fileformat = os.path.splitext(filename)[-1][1:].lower()
r = trans_load_data(db_name, fileobj, fileformat, lang, strict=False)
fileobj.close()
return r
except IOError:
logger.notifyChannel("init", netsvc.LOG_ERROR, "couldn't read file")
r = trans_load_data(db_name, fileobj, lang, strict=False)
fileobj.close()
return r
return None
def trans_load_data(db_name, fileobj, lang, strict=False, lang_name=None):
def trans_load_data(db_name, fileobj, fileformat, lang, strict=False, lang_name=None):
logger = netsvc.Logger()
logger.notifyChannel("init", netsvc.LOG_INFO,
'loading translation file for language %s' % (lang))
@ -378,11 +425,18 @@ def trans_load_data(db_name, fileobj, lang, strict=False, lang_name=None):
ls = map(lambda x: (x['code'],x['name']), langs)
fileobj.seek(0)
reader = csv.reader(fileobj, quotechar='"', delimiter=',')
# read the first line of the file (it contains columns titles)
for row in reader:
f = row
break
if fileformat == 'csv':
reader = csv.reader(fileobj, quotechar='"', delimiter=',')
# read the first line of the file (it contains columns titles)
for row in reader:
f = row
break
elif fileformat == 'po':
reader = TinyPoFile(fileobj)
f = ['type', 'name', 'res_id', 'src', 'value']
else:
raise Exception(_('Bad file format'))
# read the rest of the file
line = 1
@ -390,8 +444,8 @@ def trans_load_data(db_name, fileobj, lang, strict=False, lang_name=None):
line += 1
#try:
# skip empty rows and rows where the translation field (=last fiefd) is empty
if (not row) or (not row[:-1]):
print "translate: skip %s" % repr(row)
if (not row) or (not row[-1]):
#print "translate: skip %s" % repr(row)
continue
# dictionary which holds values for this line of the csv file