* the translations are graphically exportable by module
* allow importation of po files bzr revid: christophe@tinyerp.com-20080718131350-tftqhvwyyaz8r1q9
This commit is contained in:
parent
7cc947cb02
commit
2ccf0092a7
|
@ -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"/>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue