diff --git a/bin/addons/base/module/module_wizard.xml b/bin/addons/base/module/module_wizard.xml
index 478dd781cff..cf131660622 100644
--- a/bin/addons/base/module/module_wizard.xml
+++ b/bin/addons/base/module/module_wizard.xml
@@ -33,8 +33,9 @@
+
-
+
diff --git a/bin/addons/base/module/wizard/wizard_export_lang.py b/bin/addons/base/module/wizard/wizard_export_lang.py
index 0bd589a3691..9571f7267cd 100644
--- a/bin/addons/base/module/wizard/wizard_export_lang.py
+++ b/bin/addons/base/module/wizard/wizard_export_lang.py
@@ -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
diff --git a/bin/addons/base/module/wizard/wizard_import_lang.py b/bin/addons/base/module/wizard/wizard_import_lang.py
index a6516bb24df..9134e64433a 100644
--- a/bin/addons/base/module/wizard/wizard_import_lang.py
+++ b/bin/addons/base/module/wizard/wizard_import_lang.py
@@ -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 {}
diff --git a/bin/tools/translate.py b/bin/tools/translate.py
index d2389dc37f5..71748e5060d 100644
--- a/bin/tools/translate.py
+++ b/bin/tools/translate.py
@@ -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