2009-10-20 10:52:23 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
2006-12-07 13:41:40 +00:00
|
|
|
##############################################################################
|
2010-05-11 14:01:11 +00:00
|
|
|
#
|
2009-11-20 11:57:21 +00:00
|
|
|
# OpenERP, Open Source Management Solution
|
2009-11-27 07:23:48 +00:00
|
|
|
# Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
|
2008-06-16 07:24:04 +00:00
|
|
|
#
|
2008-11-03 18:27:16 +00:00
|
|
|
# This program is free software: you can redistribute it and/or modify
|
2009-11-27 07:23:48 +00:00
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2008-11-03 18:27:16 +00:00
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2009-11-27 07:23:48 +00:00
|
|
|
# GNU Affero General Public License for more details.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2009-11-27 07:23:48 +00:00
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
2010-05-11 14:01:11 +00:00
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2008-11-03 18:27:16 +00:00
|
|
|
##############################################################################
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2010-02-02 07:39:28 +00:00
|
|
|
import codecs
|
|
|
|
import csv
|
|
|
|
import fnmatch
|
|
|
|
import inspect
|
2010-10-08 14:03:49 +00:00
|
|
|
import itertools
|
2010-02-02 07:39:28 +00:00
|
|
|
import locale
|
2006-12-07 13:41:40 +00:00
|
|
|
import os
|
2011-02-07 12:57:23 +00:00
|
|
|
import openerp.pooler as pooler
|
2011-04-20 14:14:07 +00:00
|
|
|
import openerp.sql_db as sql_db
|
2010-02-02 07:39:28 +00:00
|
|
|
import re
|
2010-11-23 13:58:35 +00:00
|
|
|
import logging
|
2010-02-02 07:39:28 +00:00
|
|
|
import tarfile
|
|
|
|
import tempfile
|
2010-12-15 11:40:19 +00:00
|
|
|
import threading
|
2006-12-07 13:41:40 +00:00
|
|
|
from os.path import join
|
2010-02-02 07:39:28 +00:00
|
|
|
|
|
|
|
from datetime import datetime
|
2009-09-17 07:27:12 +00:00
|
|
|
from lxml import etree
|
2010-02-02 07:39:28 +00:00
|
|
|
|
2011-02-09 09:02:31 +00:00
|
|
|
import config
|
2011-02-07 12:57:23 +00:00
|
|
|
import misc
|
|
|
|
from misc import UpdateableStr
|
|
|
|
from misc import SKIPPED_ELEMENT_TYPES
|
2011-02-09 09:02:31 +00:00
|
|
|
import osutil
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2009-04-07 07:18:45 +00:00
|
|
|
_LOCALE2WIN32 = {
|
|
|
|
'af_ZA': 'Afrikaans_South Africa',
|
|
|
|
'sq_AL': 'Albanian_Albania',
|
|
|
|
'ar_SA': 'Arabic_Saudi Arabia',
|
|
|
|
'eu_ES': 'Basque_Spain',
|
|
|
|
'be_BY': 'Belarusian_Belarus',
|
|
|
|
'bs_BA': 'Serbian (Latin)',
|
|
|
|
'bg_BG': 'Bulgarian_Bulgaria',
|
|
|
|
'ca_ES': 'Catalan_Spain',
|
|
|
|
'hr_HR': 'Croatian_Croatia',
|
|
|
|
'zh_CN': 'Chinese_China',
|
|
|
|
'zh_TW': 'Chinese_Taiwan',
|
|
|
|
'cs_CZ': 'Czech_Czech Republic',
|
|
|
|
'da_DK': 'Danish_Denmark',
|
|
|
|
'nl_NL': 'Dutch_Netherlands',
|
|
|
|
'et_EE': 'Estonian_Estonia',
|
|
|
|
'fa_IR': 'Farsi_Iran',
|
|
|
|
'ph_PH': 'Filipino_Philippines',
|
|
|
|
'fi_FI': 'Finnish_Finland',
|
|
|
|
'fr_FR': 'French_France',
|
|
|
|
'fr_BE': 'French_France',
|
|
|
|
'fr_CH': 'French_France',
|
|
|
|
'fr_CA': 'French_France',
|
|
|
|
'ga': 'Scottish Gaelic',
|
|
|
|
'gl_ES': 'Galician_Spain',
|
|
|
|
'ka_GE': 'Georgian_Georgia',
|
|
|
|
'de_DE': 'German_Germany',
|
|
|
|
'el_GR': 'Greek_Greece',
|
|
|
|
'gu': 'Gujarati_India',
|
|
|
|
'he_IL': 'Hebrew_Israel',
|
|
|
|
'hi_IN': 'Hindi',
|
|
|
|
'hu': 'Hungarian_Hungary',
|
|
|
|
'is_IS': 'Icelandic_Iceland',
|
|
|
|
'id_ID': 'Indonesian_indonesia',
|
|
|
|
'it_IT': 'Italian_Italy',
|
|
|
|
'ja_JP': 'Japanese_Japan',
|
|
|
|
'kn_IN': 'Kannada',
|
|
|
|
'km_KH': 'Khmer',
|
|
|
|
'ko_KR': 'Korean_Korea',
|
|
|
|
'lo_LA': 'Lao_Laos',
|
|
|
|
'lt_LT': 'Lithuanian_Lithuania',
|
|
|
|
'lat': 'Latvian_Latvia',
|
|
|
|
'ml_IN': 'Malayalam_India',
|
|
|
|
'id_ID': 'Indonesian_indonesia',
|
|
|
|
'mi_NZ': 'Maori',
|
|
|
|
'mn': 'Cyrillic_Mongolian',
|
|
|
|
'no_NO': 'Norwegian_Norway',
|
|
|
|
'nn_NO': 'Norwegian-Nynorsk_Norway',
|
|
|
|
'pl': 'Polish_Poland',
|
|
|
|
'pt_PT': 'Portuguese_Portugal',
|
|
|
|
'pt_BR': 'Portuguese_Brazil',
|
|
|
|
'ro_RO': 'Romanian_Romania',
|
|
|
|
'ru_RU': 'Russian_Russia',
|
|
|
|
'mi_NZ': 'Maori',
|
|
|
|
'sr_CS': 'Serbian (Cyrillic)_Serbia and Montenegro',
|
|
|
|
'sk_SK': 'Slovak_Slovakia',
|
|
|
|
'sl_SI': 'Slovenian_Slovenia',
|
2010-10-20 16:18:43 +00:00
|
|
|
#should find more specific locales for spanish countries,
|
|
|
|
#but better than nothing
|
|
|
|
'es_AR': 'Spanish_Spain',
|
|
|
|
'es_BO': 'Spanish_Spain',
|
|
|
|
'es_CL': 'Spanish_Spain',
|
|
|
|
'es_CO': 'Spanish_Spain',
|
|
|
|
'es_CR': 'Spanish_Spain',
|
|
|
|
'es_DO': 'Spanish_Spain',
|
|
|
|
'es_EC': 'Spanish_Spain',
|
2009-04-07 07:18:45 +00:00
|
|
|
'es_ES': 'Spanish_Spain',
|
2010-10-20 16:18:43 +00:00
|
|
|
'es_GT': 'Spanish_Spain',
|
|
|
|
'es_HN': 'Spanish_Spain',
|
|
|
|
'es_MX': 'Spanish_Spain',
|
|
|
|
'es_NI': 'Spanish_Spain',
|
|
|
|
'es_PA': 'Spanish_Spain',
|
|
|
|
'es_PE': 'Spanish_Spain',
|
|
|
|
'es_PR': 'Spanish_Spain',
|
|
|
|
'es_PY': 'Spanish_Spain',
|
|
|
|
'es_SV': 'Spanish_Spain',
|
|
|
|
'es_UY': 'Spanish_Spain',
|
|
|
|
'es_VE': 'Spanish_Spain',
|
2009-04-07 07:18:45 +00:00
|
|
|
'sv_SE': 'Swedish_Sweden',
|
|
|
|
'ta_IN': 'English_Australia',
|
|
|
|
'th_TH': 'Thai_Thailand',
|
|
|
|
'mi_NZ': 'Maori',
|
|
|
|
'tr_TR': 'Turkish_Turkey',
|
|
|
|
'uk_UA': 'Ukrainian_Ukraine',
|
|
|
|
'vi_VN': 'Vietnamese_Viet Nam',
|
2009-10-12 07:55:08 +00:00
|
|
|
'tlh_TLH': 'Klingon',
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2009-04-07 07:18:45 +00:00
|
|
|
}
|
|
|
|
|
2007-10-24 06:14:05 +00:00
|
|
|
|
2008-07-08 08:13:12 +00:00
|
|
|
class UNIX_LINE_TERMINATOR(csv.excel):
|
2008-07-22 14:24:36 +00:00
|
|
|
lineterminator = '\n'
|
2007-10-24 06:14:05 +00:00
|
|
|
|
2008-07-08 08:13:12 +00:00
|
|
|
csv.register_dialect("UNIX", UNIX_LINE_TERMINATOR)
|
2007-10-24 06:14:05 +00:00
|
|
|
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2008-12-13 06:01:18 +00:00
|
|
|
# Warning: better use self.pool.get('ir.translation')._get_source if you can
|
2006-12-07 13:41:40 +00:00
|
|
|
#
|
2008-07-08 08:13:12 +00:00
|
|
|
def translate(cr, name, source_type, lang, source=None):
|
2008-07-22 14:24:36 +00:00
|
|
|
if source and name:
|
|
|
|
cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s and src=%s', (lang, source_type, str(name), source))
|
|
|
|
elif name:
|
|
|
|
cr.execute('select value from ir_translation where lang=%s and type=%s and name=%s', (lang, source_type, str(name)))
|
|
|
|
elif source:
|
|
|
|
cr.execute('select value from ir_translation where lang=%s and type=%s and src=%s', (lang, source_type, source))
|
|
|
|
res_trans = cr.fetchone()
|
|
|
|
res = res_trans and res_trans[0] or False
|
|
|
|
return res
|
2008-06-17 20:17:23 +00:00
|
|
|
|
2010-09-07 08:49:02 +00:00
|
|
|
logger = logging.getLogger('translate')
|
|
|
|
|
2008-07-15 08:40:23 +00:00
|
|
|
class GettextAlias(object):
|
2009-08-13 11:06:43 +00:00
|
|
|
|
2010-12-15 13:21:28 +00:00
|
|
|
def _get_db(self):
|
2010-12-15 11:40:19 +00:00
|
|
|
# find current DB based on thread/worker db name (see netsvc)
|
|
|
|
db_name = getattr(threading.currentThread(), 'dbname', None)
|
|
|
|
if db_name:
|
2011-04-20 14:14:07 +00:00
|
|
|
return sql_db.db_connect(db_name)
|
2010-12-15 11:40:19 +00:00
|
|
|
|
2010-09-07 08:49:02 +00:00
|
|
|
def _get_cr(self, frame):
|
2010-12-15 13:21:28 +00:00
|
|
|
is_new_cr = False
|
2010-12-15 11:40:19 +00:00
|
|
|
cr = frame.f_locals.get('cr', frame.f_locals.get('cursor'))
|
2010-09-07 08:49:02 +00:00
|
|
|
if not cr:
|
|
|
|
s = frame.f_locals.get('self', {})
|
2010-12-15 11:40:19 +00:00
|
|
|
cr = getattr(s, 'cr', None)
|
|
|
|
if not cr:
|
2010-12-15 13:21:28 +00:00
|
|
|
db = self._get_db()
|
2010-12-15 11:40:19 +00:00
|
|
|
if db:
|
|
|
|
cr = db.cursor()
|
2010-12-15 13:21:28 +00:00
|
|
|
is_new_cr = True
|
|
|
|
return cr, is_new_cr
|
2010-11-24 09:36:47 +00:00
|
|
|
|
2010-09-07 08:49:02 +00:00
|
|
|
def _get_lang(self, frame):
|
2010-12-15 11:40:19 +00:00
|
|
|
lang = None
|
|
|
|
ctx = frame.f_locals.get('context')
|
|
|
|
if not ctx:
|
|
|
|
kwargs = frame.f_locals.get('kwargs')
|
|
|
|
if kwargs is None:
|
|
|
|
args = frame.f_locals.get('args')
|
|
|
|
if args and isinstance(args, (list, tuple)) \
|
|
|
|
and isinstance(args[-1], dict):
|
|
|
|
ctx = args[-1]
|
|
|
|
elif isinstance(kwargs, dict):
|
|
|
|
ctx = kwargs.get('context')
|
|
|
|
if ctx:
|
|
|
|
lang = ctx.get('lang')
|
2010-09-07 08:49:02 +00:00
|
|
|
if not lang:
|
2010-12-15 11:40:19 +00:00
|
|
|
s = frame.f_locals.get('self', {})
|
|
|
|
c = getattr(s, 'localcontext', None)
|
|
|
|
if c:
|
|
|
|
lang = c.get('lang')
|
2010-09-07 08:49:02 +00:00
|
|
|
return lang
|
2010-11-24 09:36:47 +00:00
|
|
|
|
2010-09-07 08:49:02 +00:00
|
|
|
def __call__(self, source):
|
|
|
|
res = source
|
2010-12-15 11:40:19 +00:00
|
|
|
cr = None
|
2010-12-15 13:21:28 +00:00
|
|
|
is_new_cr = False
|
2009-11-24 14:44:05 +00:00
|
|
|
try:
|
2010-12-15 11:40:19 +00:00
|
|
|
frame = inspect.currentframe()
|
|
|
|
if frame is None:
|
|
|
|
return source
|
|
|
|
frame = frame.f_back
|
|
|
|
if not frame:
|
|
|
|
return source
|
2010-09-07 08:49:02 +00:00
|
|
|
lang = self._get_lang(frame)
|
2010-12-15 11:40:19 +00:00
|
|
|
if lang:
|
2010-12-15 13:21:28 +00:00
|
|
|
cr, is_new_cr = self._get_cr(frame)
|
2010-12-15 11:40:19 +00:00
|
|
|
if cr:
|
|
|
|
# Try to use ir.translation to benefit from global cache if possible
|
2010-12-15 13:21:28 +00:00
|
|
|
pool = pooler.get_pool(cr.dbname)
|
|
|
|
res = pool.get('ir.translation')._get_source(cr, 1, None, ('code','sql_constraint'), lang, source)
|
2010-12-15 11:40:19 +00:00
|
|
|
else:
|
|
|
|
logger.debug('no context cursor detected, skipping translation for "%r"', source)
|
|
|
|
else:
|
|
|
|
logger.debug('no translation language detected, skipping translation for "%r" ', source)
|
2010-10-12 18:20:42 +00:00
|
|
|
except Exception:
|
2010-12-15 11:40:19 +00:00
|
|
|
logger.debug('translation went wrong for "%r", skipped', source)
|
2010-12-29 11:28:50 +00:00
|
|
|
# if so, double-check the root/base translations filenames
|
2010-09-07 08:49:02 +00:00
|
|
|
finally:
|
2010-12-15 13:21:28 +00:00
|
|
|
if cr and is_new_cr:
|
2010-09-07 08:49:02 +00:00
|
|
|
cr.close()
|
|
|
|
return res
|
2008-07-15 08:40:23 +00:00
|
|
|
|
|
|
|
_ = GettextAlias()
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2008-07-17 10:22:36 +00:00
|
|
|
|
2010-11-16 12:38:44 +00:00
|
|
|
def quote(s):
|
|
|
|
"""Returns quoted PO term string, with special PO characters escaped"""
|
2010-11-16 14:15:08 +00:00
|
|
|
assert r"\n" not in s, "Translation terms may not include escaped newlines ('\\n'), please use only literal newlines! (in '%s')" % s
|
2010-11-16 12:38:44 +00:00
|
|
|
return '"%s"' % s.replace('\\','\\\\') \
|
|
|
|
.replace('"','\\"') \
|
|
|
|
.replace('\n', '\\n"\n"')
|
|
|
|
|
|
|
|
re_escaped_char = re.compile(r"(\\.)")
|
|
|
|
re_escaped_replacements = {'n': '\n', }
|
|
|
|
|
|
|
|
def _sub_replacement(match_obj):
|
|
|
|
return re_escaped_replacements.get(match_obj.group(1)[1], match_obj.group(1)[1])
|
|
|
|
|
|
|
|
def unquote(str):
|
|
|
|
"""Returns unquoted PO term string, with special PO characters unescaped"""
|
|
|
|
return re_escaped_char.sub(_sub_replacement, str[1:-1])
|
|
|
|
|
2008-07-17 10:22:36 +00:00
|
|
|
# class to handle po files
|
|
|
|
class TinyPoFile(object):
|
2008-07-22 14:24:36 +00:00
|
|
|
def __init__(self, buffer):
|
2010-11-23 13:58:35 +00:00
|
|
|
self.logger = logging.getLogger('i18n')
|
2008-07-22 14:24:36 +00:00
|
|
|
self.buffer = buffer
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2010-12-21 14:46:46 +00:00
|
|
|
def warn(self, msg, *args):
|
|
|
|
self.logger.warning(msg, *args)
|
2009-12-10 11:08:26 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
def __iter__(self):
|
|
|
|
self.buffer.seek(0)
|
2008-10-08 15:28:05 +00:00
|
|
|
self.lines = self._get_lines()
|
2009-11-24 14:44:05 +00:00
|
|
|
self.lines_count = len(self.lines);
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
self.first = True
|
2008-08-22 09:39:14 +00:00
|
|
|
self.tnrs= []
|
2008-07-22 14:24:36 +00:00
|
|
|
return self
|
2008-10-08 15:28:05 +00:00
|
|
|
|
|
|
|
def _get_lines(self):
|
|
|
|
lines = self.buffer.readlines()
|
|
|
|
# remove the BOM (Byte Order Mark):
|
2008-10-09 08:52:45 +00:00
|
|
|
if len(lines):
|
2008-10-08 15:28:05 +00:00
|
|
|
lines[0] = unicode(lines[0], 'utf8').lstrip(unicode( codecs.BOM_UTF8, "utf8"))
|
|
|
|
|
|
|
|
lines.append('') # ensure that the file ends with at least an empty line
|
|
|
|
return lines
|
|
|
|
|
2008-12-11 17:59:36 +00:00
|
|
|
def cur_line(self):
|
2009-11-24 14:44:05 +00:00
|
|
|
return (self.lines_count - len(self.lines))
|
2008-12-11 17:59:36 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
def next(self):
|
|
|
|
type = name = res_id = source = trad = None
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2008-08-22 09:39:14 +00:00
|
|
|
if self.tnrs:
|
|
|
|
type, name, res_id, source, trad = self.tnrs.pop(0)
|
2010-11-26 08:20:48 +00:00
|
|
|
if not res_id:
|
|
|
|
res_id = '0'
|
2008-08-22 09:39:14 +00:00
|
|
|
else:
|
|
|
|
tmp_tnrs = []
|
|
|
|
line = None
|
2009-11-24 14:44:05 +00:00
|
|
|
fuzzy = False
|
2008-11-23 11:11:28 +00:00
|
|
|
while (not line):
|
2008-08-22 09:39:14 +00:00
|
|
|
if 0 == len(self.lines):
|
|
|
|
raise StopIteration()
|
2008-07-22 14:24:36 +00:00
|
|
|
line = self.lines.pop(0).strip()
|
2009-11-26 14:09:35 +00:00
|
|
|
while line.startswith('#'):
|
|
|
|
if line.startswith('#~ '):
|
|
|
|
break
|
2009-11-20 11:57:21 +00:00
|
|
|
if line.startswith('#:'):
|
2009-11-24 14:44:05 +00:00
|
|
|
if ' ' in line[2:].strip():
|
|
|
|
for lpart in line[2:].strip().split(' '):
|
|
|
|
tmp_tnrs.append(lpart.strip().split(':',2))
|
|
|
|
else:
|
2009-01-29 07:37:17 +00:00
|
|
|
tmp_tnrs.append( line[2:].strip().split(':',2) )
|
2009-11-24 14:44:05 +00:00
|
|
|
elif line.startswith('#,') and (line[2:].strip() == 'fuzzy'):
|
|
|
|
fuzzy = True
|
2008-08-22 09:39:14 +00:00
|
|
|
line = self.lines.pop(0).strip()
|
2008-10-15 07:49:53 +00:00
|
|
|
while not line:
|
|
|
|
# allow empty lines between comments and msgid
|
|
|
|
line = self.lines.pop(0).strip()
|
2009-11-24 14:44:05 +00:00
|
|
|
if line.startswith('#~ '):
|
|
|
|
while line.startswith('#~ ') or not line.strip():
|
|
|
|
if 0 == len(self.lines):
|
2009-05-16 21:20:29 +00:00
|
|
|
raise StopIteration()
|
2009-11-24 14:44:05 +00:00
|
|
|
line = self.lines.pop(0)
|
|
|
|
# This has been a deprecated entry, don't return anything
|
|
|
|
return self.next()
|
2009-05-16 21:20:29 +00:00
|
|
|
|
2008-08-22 09:39:14 +00:00
|
|
|
if not line.startswith('msgid'):
|
2008-10-08 15:28:05 +00:00
|
|
|
raise Exception("malformed file: bad line: %s" % line)
|
2008-09-11 15:15:44 +00:00
|
|
|
source = unquote(line[6:])
|
2008-07-22 14:24:36 +00:00
|
|
|
line = self.lines.pop(0).strip()
|
2008-08-22 09:39:14 +00:00
|
|
|
if not source and self.first:
|
2008-10-08 15:28:05 +00:00
|
|
|
# if the source is "" and it's the first msgid, it's the special
|
|
|
|
# msgstr with the informations about the traduction and the
|
2008-08-22 09:39:14 +00:00
|
|
|
# traductor; we skip it
|
|
|
|
self.tnrs = []
|
|
|
|
while line:
|
|
|
|
line = self.lines.pop(0).strip()
|
2008-10-08 15:28:05 +00:00
|
|
|
return self.next()
|
|
|
|
|
2008-08-22 09:39:14 +00:00
|
|
|
while not line.startswith('msgstr'):
|
|
|
|
if not line:
|
2008-12-11 17:59:36 +00:00
|
|
|
raise Exception('malformed file at %d'% self.cur_line())
|
2008-08-22 09:39:14 +00:00
|
|
|
source += unquote(line)
|
|
|
|
line = self.lines.pop(0).strip()
|
2008-07-22 14:24:36 +00:00
|
|
|
|
2008-09-11 15:15:44 +00:00
|
|
|
trad = unquote(line[7:])
|
2008-07-22 14:24:36 +00:00
|
|
|
line = self.lines.pop(0).strip()
|
2008-08-22 09:39:14 +00:00
|
|
|
while line:
|
|
|
|
trad += unquote(line)
|
|
|
|
line = self.lines.pop(0).strip()
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2008-09-29 20:41:15 +00:00
|
|
|
if tmp_tnrs and not fuzzy:
|
2008-08-22 09:39:14 +00:00
|
|
|
type, name, res_id = tmp_tnrs.pop(0)
|
|
|
|
for t, n, r in tmp_tnrs:
|
|
|
|
self.tnrs.append((t, n, r, source, trad))
|
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
self.first = False
|
2009-12-10 11:08:26 +00:00
|
|
|
|
|
|
|
if name is None:
|
2010-11-23 13:58:58 +00:00
|
|
|
if not fuzzy:
|
2010-12-30 09:17:34 +00:00
|
|
|
self.warn('Missing "#:" formated comment at line %d for the following source:\n\t%s',
|
2010-11-23 13:58:58 +00:00
|
|
|
self.cur_line(), source[:30])
|
2009-11-24 14:44:05 +00:00
|
|
|
return self.next()
|
2008-07-22 14:24:36 +00:00
|
|
|
return type, name, res_id, source, trad
|
|
|
|
|
|
|
|
def write_infos(self, modules):
|
2011-02-07 12:57:23 +00:00
|
|
|
import openerp.release as release
|
2008-07-22 14:24:36 +00:00
|
|
|
self.buffer.write("# Translation of %(project)s.\n" \
|
2009-01-20 13:54:09 +00:00
|
|
|
"# This file contains the translation of the following modules:\n" \
|
2008-07-22 14:24:36 +00:00
|
|
|
"%(modules)s" \
|
|
|
|
"#\n" \
|
|
|
|
"msgid \"\"\n" \
|
|
|
|
"msgstr \"\"\n" \
|
2008-10-09 08:24:16 +00:00
|
|
|
'''"Project-Id-Version: %(project)s %(version)s\\n"\n''' \
|
|
|
|
'''"Report-Msgid-Bugs-To: %(bugmail)s\\n"\n''' \
|
|
|
|
'''"POT-Creation-Date: %(now)s\\n"\n''' \
|
|
|
|
'''"PO-Revision-Date: %(now)s\\n"\n''' \
|
|
|
|
'''"Last-Translator: <>\\n"\n''' \
|
|
|
|
'''"Language-Team: \\n"\n''' \
|
|
|
|
'''"MIME-Version: 1.0\\n"\n''' \
|
|
|
|
'''"Content-Type: text/plain; charset=UTF-8\\n"\n''' \
|
|
|
|
'''"Content-Transfer-Encoding: \\n"\n''' \
|
|
|
|
'''"Plural-Forms: \\n"\n''' \
|
2008-07-22 14:24:36 +00:00
|
|
|
"\n"
|
|
|
|
|
|
|
|
% { 'project': release.description,
|
|
|
|
'version': release.version,
|
|
|
|
'modules': reduce(lambda s, m: s + "#\t* %s\n" % m, modules, ""),
|
2008-09-12 19:45:04 +00:00
|
|
|
'bugmail': release.support_email,
|
2011-01-12 14:31:41 +00:00
|
|
|
'now': datetime.utcnow().strftime('%Y-%m-%d %H:%M')+"+0000",
|
2008-07-22 14:24:36 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2008-08-22 09:39:14 +00:00
|
|
|
def write(self, modules, tnrs, source, trad):
|
2008-07-22 14:24:36 +00:00
|
|
|
|
2008-08-22 09:39:14 +00:00
|
|
|
plurial = len(modules) > 1 and 's' or ''
|
2008-09-02 15:01:44 +00:00
|
|
|
self.buffer.write("#. module%s: %s\n" % (plurial, ', '.join(modules)))
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2008-08-22 09:39:14 +00:00
|
|
|
|
2008-12-01 09:25:53 +00:00
|
|
|
code = False
|
2008-09-04 13:28:41 +00:00
|
|
|
for typy, name, res_id in tnrs:
|
|
|
|
self.buffer.write("#: %s:%s:%s\n" % (typy, name, res_id))
|
2008-12-01 09:25:53 +00:00
|
|
|
if typy == 'code':
|
|
|
|
code = True
|
|
|
|
|
|
|
|
if code:
|
|
|
|
# only strings in python code are python formated
|
|
|
|
self.buffer.write("#, python-format\n")
|
2008-08-22 09:39:14 +00:00
|
|
|
|
2008-09-04 13:28:41 +00:00
|
|
|
if not isinstance(trad, unicode):
|
|
|
|
trad = unicode(trad, 'utf8')
|
|
|
|
if not isinstance(source, unicode):
|
|
|
|
source = unicode(source, 'utf8')
|
|
|
|
|
|
|
|
msg = "msgid %s\n" \
|
|
|
|
"msgstr %s\n\n" \
|
|
|
|
% (quote(source), quote(trad))
|
|
|
|
self.buffer.write(msg.encode('utf8'))
|
2008-07-17 10:22:36 +00:00
|
|
|
|
|
|
|
|
2006-12-07 13:41:40 +00:00
|
|
|
# Methods to export the translation file
|
|
|
|
|
2011-01-05 14:07:58 +00:00
|
|
|
def trans_export(lang, modules, buffer, format, cr):
|
2008-08-19 13:10:16 +00:00
|
|
|
|
|
|
|
def _process(format, modules, rows, buffer, lang, newlang):
|
|
|
|
if format == 'csv':
|
|
|
|
writer=csv.writer(buffer, 'UNIX')
|
|
|
|
for row in rows:
|
|
|
|
writer.writerow(row)
|
|
|
|
elif format == 'po':
|
|
|
|
rows.pop(0)
|
2011-02-07 12:57:23 +00:00
|
|
|
writer = TinyPoFile(buffer)
|
2008-08-19 13:10:16 +00:00
|
|
|
writer.write_infos(modules)
|
2008-08-22 09:39:14 +00:00
|
|
|
|
|
|
|
# we now group the translations by source. That means one translation per source.
|
|
|
|
grouped_rows = {}
|
2008-08-19 13:10:16 +00:00
|
|
|
for module, type, name, res_id, src, trad in rows:
|
2008-08-22 09:39:14 +00:00
|
|
|
row = grouped_rows.setdefault(src, {})
|
|
|
|
row.setdefault('modules', set()).add(module)
|
2008-09-11 15:15:44 +00:00
|
|
|
if ('translation' not in row) or (not row['translation']):
|
|
|
|
row['translation'] = trad
|
2008-08-22 09:39:14 +00:00
|
|
|
row.setdefault('tnrs', []).append((type, name, res_id))
|
|
|
|
|
|
|
|
for src, row in grouped_rows.items():
|
|
|
|
writer.write(row['modules'], row['tnrs'], src, row['translation'])
|
|
|
|
|
2008-08-19 13:10:16 +00:00
|
|
|
elif format == 'tgz':
|
|
|
|
rows.pop(0)
|
|
|
|
rows_by_module = {}
|
|
|
|
for row in rows:
|
|
|
|
module = row[0]
|
2010-12-21 14:43:54 +00:00
|
|
|
# first row is the "header", as in csv, it will be popped
|
|
|
|
rows_by_module.setdefault(module, [['module', 'type', 'name', 'res_id', 'src', ''],])
|
|
|
|
rows_by_module[module].append(row)
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2008-08-19 13:10:16 +00:00
|
|
|
tmpdir = tempfile.mkdtemp()
|
|
|
|
for mod, modrows in rows_by_module.items():
|
|
|
|
tmpmoddir = join(tmpdir, mod, 'i18n')
|
|
|
|
os.makedirs(tmpmoddir)
|
|
|
|
pofilename = (newlang and mod or lang) + ".po" + (newlang and 't' or '')
|
2009-08-18 17:12:26 +00:00
|
|
|
buf = file(join(tmpmoddir, pofilename), 'w')
|
2008-08-19 13:10:16 +00:00
|
|
|
_process('po', [mod], modrows, buf, lang, newlang)
|
2009-08-18 17:12:26 +00:00
|
|
|
buf.close()
|
2008-08-19 13:10:16 +00:00
|
|
|
|
|
|
|
tar = tarfile.open(fileobj=buffer, mode='w|gz')
|
2008-09-02 15:13:05 +00:00
|
|
|
tar.add(tmpdir, '')
|
2008-08-19 13:10:16 +00:00
|
|
|
tar.close()
|
|
|
|
|
|
|
|
else:
|
|
|
|
raise Exception(_('Bad file format'))
|
|
|
|
|
|
|
|
newlang = not bool(lang)
|
|
|
|
if newlang:
|
|
|
|
lang = 'en_US'
|
2011-01-05 14:07:58 +00:00
|
|
|
trans = trans_generate(lang, modules, cr)
|
2010-09-01 07:52:44 +00:00
|
|
|
if newlang and format!='csv':
|
|
|
|
for trx in trans:
|
|
|
|
trx[-1] = ''
|
2008-08-22 09:39:14 +00:00
|
|
|
modules = set([t[0] for t in trans[1:]])
|
2008-08-19 13:10:16 +00:00
|
|
|
_process(format, modules, trans, buffer, lang, newlang)
|
2008-07-22 14:24:36 +00:00
|
|
|
del trans
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2006-12-07 13:41:40 +00:00
|
|
|
def trans_parse_xsl(de):
|
2008-07-22 14:24:36 +00:00
|
|
|
res = []
|
2009-11-28 11:58:09 +00:00
|
|
|
for n in de:
|
2009-09-17 07:27:12 +00:00
|
|
|
if n.get("t"):
|
2010-12-29 11:33:34 +00:00
|
|
|
for m in n:
|
|
|
|
if isinstance(m, SKIPPED_ELEMENT_TYPES) or not m.text:
|
|
|
|
continue
|
2009-09-17 07:27:12 +00:00
|
|
|
l = m.text.strip().replace('\n',' ')
|
2008-07-22 14:24:36 +00:00
|
|
|
if len(l):
|
|
|
|
res.append(l.encode("utf8"))
|
|
|
|
res.extend(trans_parse_xsl(n))
|
|
|
|
return res
|
2006-12-07 13:41:40 +00:00
|
|
|
|
|
|
|
def trans_parse_rml(de):
|
2008-07-22 14:24:36 +00:00
|
|
|
res = []
|
2009-11-28 11:58:09 +00:00
|
|
|
for n in de:
|
2010-12-29 11:33:34 +00:00
|
|
|
for m in n:
|
|
|
|
if isinstance(m, SKIPPED_ELEMENT_TYPES) or not m.text:
|
|
|
|
continue
|
2009-09-17 07:27:12 +00:00
|
|
|
string_list = [s.replace('\n', ' ').strip() for s in re.split('\[\[.+?\]\]', m.text)]
|
2008-07-22 14:24:36 +00:00
|
|
|
for s in string_list:
|
|
|
|
if s:
|
|
|
|
res.append(s.encode("utf8"))
|
|
|
|
res.extend(trans_parse_rml(n))
|
|
|
|
return res
|
2006-12-07 13:41:40 +00:00
|
|
|
|
|
|
|
def trans_parse_view(de):
|
2008-07-22 14:24:36 +00:00
|
|
|
res = []
|
2010-12-14 12:29:11 +00:00
|
|
|
if de.tag == 'attribute' and de.get("name") == 'string':
|
|
|
|
if de.text:
|
|
|
|
res.append(de.text.encode("utf8"))
|
2009-09-17 07:27:12 +00:00
|
|
|
if de.get("string"):
|
2009-11-28 11:58:09 +00:00
|
|
|
res.append(de.get('string').encode("utf8"))
|
2011-04-21 09:34:00 +00:00
|
|
|
if de.get("help"):
|
|
|
|
res.append(de.get('help').encode("utf8"))
|
2009-09-17 07:27:12 +00:00
|
|
|
if de.get("sum"):
|
2009-11-28 11:58:09 +00:00
|
|
|
res.append(de.get('sum').encode("utf8"))
|
2010-11-24 09:36:47 +00:00
|
|
|
if de.get("confirm"):
|
|
|
|
res.append(de.get('confirm').encode("utf8"))
|
2009-11-28 11:58:09 +00:00
|
|
|
for n in de:
|
2008-07-22 14:24:36 +00:00
|
|
|
res.extend(trans_parse_view(n))
|
|
|
|
return res
|
2006-12-07 13:41:40 +00:00
|
|
|
|
|
|
|
# tests whether an object is in a list of modules
|
|
|
|
def in_modules(object_name, modules):
|
2008-07-22 14:24:36 +00:00
|
|
|
if 'all' in modules:
|
|
|
|
return True
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
module_dict = {
|
|
|
|
'ir': 'base',
|
|
|
|
'res': 'base',
|
|
|
|
'workflow': 'base',
|
|
|
|
}
|
|
|
|
module = object_name.split('.')[0]
|
|
|
|
module = module_dict.get(module, module)
|
|
|
|
return module in modules
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2011-01-05 14:07:58 +00:00
|
|
|
def trans_generate(lang, modules, cr):
|
2010-11-23 13:58:35 +00:00
|
|
|
logger = logging.getLogger('i18n')
|
2011-01-05 14:07:58 +00:00
|
|
|
dbname = cr.dbname
|
2008-07-23 16:33:28 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
pool = pooler.get_pool(dbname)
|
|
|
|
trans_obj = pool.get('ir.translation')
|
|
|
|
model_data_obj = pool.get('ir.model.data')
|
|
|
|
uid = 1
|
|
|
|
l = pool.obj_pool.items()
|
|
|
|
l.sort()
|
|
|
|
|
|
|
|
query = 'SELECT name, model, res_id, module' \
|
|
|
|
' FROM ir_model_data'
|
2010-12-30 09:17:34 +00:00
|
|
|
|
|
|
|
query_models = """SELECT m.id, m.model, imd.module
|
|
|
|
FROM ir_model AS m, ir_model_data AS imd
|
2010-11-16 16:35:10 +00:00
|
|
|
WHERE m.id = imd.res_id AND imd.model = 'ir.model' """
|
|
|
|
|
2009-06-08 20:29:44 +00:00
|
|
|
if 'all_installed' in modules:
|
|
|
|
query += ' WHERE module IN ( SELECT name FROM ir_module_module WHERE state = \'installed\') '
|
2010-11-16 16:35:10 +00:00
|
|
|
query_models += " AND imd.module in ( SELECT name FROM ir_module_module WHERE state = 'installed') "
|
2010-06-15 13:27:22 +00:00
|
|
|
query_param = None
|
|
|
|
if 'all' not in modules:
|
|
|
|
query += ' WHERE module IN %s'
|
2010-11-16 16:35:10 +00:00
|
|
|
query_models += ' AND imd.module in %s'
|
2010-06-15 13:27:22 +00:00
|
|
|
query_param = (tuple(modules),)
|
2008-07-22 14:24:36 +00:00
|
|
|
query += ' ORDER BY module, model, name'
|
2010-11-16 16:35:10 +00:00
|
|
|
query_models += ' ORDER BY module, model'
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
cr.execute(query, query_param)
|
|
|
|
|
|
|
|
_to_translate = []
|
|
|
|
def push_translation(module, type, name, id, source):
|
2009-01-11 18:01:06 +00:00
|
|
|
tuple = (module, source, name, id, type)
|
2008-09-02 13:02:14 +00:00
|
|
|
if source and tuple not in _to_translate:
|
2008-07-22 14:24:36 +00:00
|
|
|
_to_translate.append(tuple)
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2008-12-16 16:21:12 +00:00
|
|
|
def encode(s):
|
|
|
|
if isinstance(s, unicode):
|
|
|
|
return s.encode('utf8')
|
|
|
|
return s
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
for (xml_name,model,res_id,module) in cr.fetchall():
|
2008-12-16 16:21:12 +00:00
|
|
|
module = encode(module)
|
|
|
|
model = encode(model)
|
|
|
|
xml_name = "%s.%s" % (module, encode(xml_name))
|
|
|
|
|
2008-10-20 13:34:57 +00:00
|
|
|
if not pool.get(model):
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.error("Unable to find object %r", model)
|
2008-10-20 13:34:57 +00:00
|
|
|
continue
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2009-09-29 12:39:23 +00:00
|
|
|
exists = pool.get(model).exists(cr, uid, res_id)
|
|
|
|
if not exists:
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.warning("Unable to find object %r with id %d", model, res_id)
|
2009-06-10 11:18:40 +00:00
|
|
|
continue
|
2009-09-29 12:39:23 +00:00
|
|
|
obj = pool.get(model).browse(cr, uid, res_id)
|
2009-06-10 11:18:40 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
if model=='ir.ui.view':
|
2009-09-17 07:27:12 +00:00
|
|
|
d = etree.XML(encode(obj.arch))
|
|
|
|
for t in trans_parse_view(d):
|
2008-12-16 16:21:12 +00:00
|
|
|
push_translation(module, 'view', encode(obj.model), 0, t)
|
2008-07-22 14:24:36 +00:00
|
|
|
elif model=='ir.actions.wizard':
|
2008-12-16 16:21:12 +00:00
|
|
|
service_name = 'wizard.'+encode(obj.wiz_name)
|
2011-02-07 12:57:23 +00:00
|
|
|
import openerp.netsvc as netsvc
|
2009-08-29 15:23:46 +00:00
|
|
|
if netsvc.Service._services.get(service_name):
|
|
|
|
obj2 = netsvc.Service._services[service_name]
|
2009-06-09 23:15:54 +00:00
|
|
|
for state_name, state_def in obj2.states.iteritems():
|
|
|
|
if 'result' in state_def:
|
|
|
|
result = state_def['result']
|
|
|
|
if result['type'] != 'form':
|
|
|
|
continue
|
|
|
|
name = "%s,%s" % (encode(obj.wiz_name), state_name)
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2009-06-09 23:15:54 +00:00
|
|
|
def_params = {
|
|
|
|
'string': ('wizard_field', lambda s: [encode(s)]),
|
|
|
|
'selection': ('selection', lambda s: [encode(e[1]) for e in ((not callable(s)) and s or [])]),
|
|
|
|
'help': ('help', lambda s: [encode(s)]),
|
|
|
|
}
|
|
|
|
|
|
|
|
# export fields
|
2009-11-24 14:44:05 +00:00
|
|
|
if not result.has_key('fields'):
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.warning("res has no fields: %r", result)
|
2009-11-24 14:44:05 +00:00
|
|
|
continue
|
2009-06-09 23:15:54 +00:00
|
|
|
for field_name, field_def in result['fields'].iteritems():
|
|
|
|
res_name = name + ',' + field_name
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2009-06-09 23:15:54 +00:00
|
|
|
for fn in def_params:
|
|
|
|
if fn in field_def:
|
|
|
|
transtype, modifier = def_params[fn]
|
|
|
|
for val in modifier(field_def[fn]):
|
|
|
|
push_translation(module, transtype, res_name, 0, val)
|
|
|
|
|
|
|
|
# export arch
|
|
|
|
arch = result['arch']
|
|
|
|
if arch and not isinstance(arch, UpdateableStr):
|
2009-11-17 12:45:48 +00:00
|
|
|
d = etree.XML(arch)
|
|
|
|
for t in trans_parse_view(d):
|
2009-06-09 23:15:54 +00:00
|
|
|
push_translation(module, 'wizard_view', name, 0, t)
|
|
|
|
|
|
|
|
# export button labels
|
|
|
|
for but_args in result['state']:
|
|
|
|
button_name = but_args[0]
|
|
|
|
button_label = but_args[1]
|
|
|
|
res_name = name + ',' + button_name
|
|
|
|
push_translation(module, 'wizard_button', res_name, 0, button_label)
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
elif model=='ir.model.fields':
|
2009-11-24 14:44:05 +00:00
|
|
|
try:
|
2008-12-18 16:49:25 +00:00
|
|
|
field_name = encode(obj.name)
|
2009-11-24 14:44:05 +00:00
|
|
|
except AttributeError, exc:
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.error("name error in %s: %s", xml_name, str(exc))
|
2009-11-24 14:44:05 +00:00
|
|
|
continue
|
2008-09-10 13:14:16 +00:00
|
|
|
objmodel = pool.get(obj.model)
|
|
|
|
if not objmodel or not field_name in objmodel._columns:
|
2008-09-01 19:29:02 +00:00
|
|
|
continue
|
2008-09-10 13:14:16 +00:00
|
|
|
field_def = objmodel._columns[field_name]
|
2008-07-22 14:24:36 +00:00
|
|
|
|
2008-12-16 16:21:12 +00:00
|
|
|
name = "%s,%s" % (encode(obj.model), field_name)
|
|
|
|
push_translation(module, 'field', name, 0, encode(field_def.string))
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
if field_def.help:
|
2008-12-16 16:21:12 +00:00
|
|
|
push_translation(module, 'help', name, 0, encode(field_def.help))
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
if field_def.translate:
|
2008-09-10 13:14:16 +00:00
|
|
|
ids = objmodel.search(cr, uid, [])
|
|
|
|
obj_values = objmodel.read(cr, uid, ids, [field_name])
|
2008-07-22 14:24:36 +00:00
|
|
|
for obj_value in obj_values:
|
|
|
|
res_id = obj_value['id']
|
|
|
|
if obj.name in ('ir.model', 'ir.ui.menu'):
|
|
|
|
res_id = 0
|
|
|
|
model_data_ids = model_data_obj.search(cr, uid, [
|
|
|
|
('model', '=', model),
|
|
|
|
('res_id', '=', res_id),
|
|
|
|
])
|
|
|
|
if not model_data_ids:
|
2008-12-16 16:21:12 +00:00
|
|
|
push_translation(module, 'model', name, 0, encode(obj_value[field_name]))
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
if hasattr(field_def, 'selection') and isinstance(field_def.selection, (list, tuple)):
|
2010-10-08 14:03:49 +00:00
|
|
|
for dummy, val in field_def.selection:
|
2008-12-16 16:21:12 +00:00
|
|
|
push_translation(module, 'selection', name, 0, encode(val))
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
elif model=='ir.actions.report.xml':
|
2008-12-16 16:21:12 +00:00
|
|
|
name = encode(obj.report_name)
|
2008-07-22 14:24:36 +00:00
|
|
|
fname = ""
|
|
|
|
if obj.report_rml:
|
|
|
|
fname = obj.report_rml
|
|
|
|
parse_func = trans_parse_rml
|
2010-10-08 14:03:49 +00:00
|
|
|
report_type = "report"
|
2008-07-22 14:24:36 +00:00
|
|
|
elif obj.report_xsl:
|
|
|
|
fname = obj.report_xsl
|
|
|
|
parse_func = trans_parse_xsl
|
|
|
|
report_type = "xsl"
|
2010-10-08 14:03:49 +00:00
|
|
|
if fname and obj.report_type in ('pdf', 'xsl'):
|
|
|
|
try:
|
2011-02-07 12:57:23 +00:00
|
|
|
report_file = misc.file_open(fname)
|
2011-01-04 10:13:35 +00:00
|
|
|
try:
|
|
|
|
d = etree.parse(report_file)
|
|
|
|
for t in parse_func(d.iter()):
|
|
|
|
push_translation(module, report_type, name, 0, t)
|
|
|
|
finally:
|
|
|
|
report_file.close()
|
2010-10-08 14:03:49 +00:00
|
|
|
except (IOError, etree.XMLSyntaxError):
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.exception("couldn't export translation for report %s %s %s", name, report_type, fname)
|
2010-10-20 16:38:22 +00:00
|
|
|
|
2010-11-16 16:35:10 +00:00
|
|
|
for field_name,field_def in obj._table._columns.items():
|
2008-07-22 14:24:36 +00:00
|
|
|
if field_def.translate:
|
|
|
|
name = model + "," + field_name
|
2009-11-24 14:44:05 +00:00
|
|
|
try:
|
2008-11-15 08:58:54 +00:00
|
|
|
trad = getattr(obj, field_name) or ''
|
2009-11-24 14:44:05 +00:00
|
|
|
except:
|
|
|
|
trad = ''
|
2009-06-10 11:18:40 +00:00
|
|
|
push_translation(module, 'model', name, xml_name, encode(trad))
|
2008-07-22 14:24:36 +00:00
|
|
|
|
2010-11-16 16:35:10 +00:00
|
|
|
# End of data for ir.model.data query results
|
|
|
|
|
|
|
|
cr.execute(query_models, query_param)
|
|
|
|
|
|
|
|
def push_constraint_msg(module, term_type, model, msg):
|
|
|
|
# Check presence of __call__ directly instead of using
|
|
|
|
# callable() because it will be deprecated as of Python 3.0
|
|
|
|
if not hasattr(msg, '__call__'):
|
|
|
|
push_translation(module, term_type, model, 0, encode(msg))
|
|
|
|
|
|
|
|
for (model_id, model, module) in cr.fetchall():
|
|
|
|
module = encode(module)
|
|
|
|
model = encode(model)
|
|
|
|
|
|
|
|
model_obj = pool.get(model)
|
|
|
|
|
|
|
|
if not model_obj:
|
|
|
|
logging.getLogger("i18n").error("Unable to find object %r", model)
|
|
|
|
continue
|
|
|
|
|
|
|
|
for constraint in getattr(model_obj, '_constraints', []):
|
|
|
|
push_constraint_msg(module, 'constraint', model, constraint[1])
|
|
|
|
|
|
|
|
for constraint in getattr(model_obj, '_sql_constraints', []):
|
|
|
|
push_constraint_msg(module, 'sql_constraint', model, constraint[2])
|
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
# parse source code for _() calls
|
2010-05-12 10:33:52 +00:00
|
|
|
def get_module_from_path(path, mod_paths=None):
|
2010-11-16 12:38:44 +00:00
|
|
|
if not mod_paths:
|
|
|
|
# First, construct a list of possible paths
|
2011-02-07 12:57:23 +00:00
|
|
|
def_path = os.path.abspath(os.path.join(config.config['root_path'], 'addons')) # default addons path (base)
|
|
|
|
ad_paths= map(lambda m: os.path.abspath(m.strip()),config.config['addons_path'].split(','))
|
2010-11-16 12:38:44 +00:00
|
|
|
mod_paths=[def_path]
|
|
|
|
for adp in ad_paths:
|
|
|
|
mod_paths.append(adp)
|
|
|
|
if not os.path.isabs(adp):
|
|
|
|
mod_paths.append(adp)
|
|
|
|
elif adp.startswith(def_path):
|
|
|
|
mod_paths.append(adp[len(def_path)+1:])
|
|
|
|
for mp in mod_paths:
|
|
|
|
if path.startswith(mp) and (os.path.dirname(path) != mp):
|
|
|
|
path = path[len(mp)+1:]
|
|
|
|
return path.split(os.path.sep)[0]
|
2008-07-22 14:24:36 +00:00
|
|
|
return 'base' # files that are not in a module are considered as being in 'base' module
|
2008-10-08 15:28:05 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
modobj = pool.get('ir.module.module')
|
2008-09-08 07:52:51 +00:00
|
|
|
installed_modids = modobj.search(cr, uid, [('state', '=', 'installed')])
|
2008-10-09 08:00:41 +00:00
|
|
|
installed_modules = map(lambda m: m['name'], modobj.read(cr, uid, installed_modids, ['name']))
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2011-02-07 12:57:23 +00:00
|
|
|
root_path = os.path.join(config.config['root_path'], 'addons')
|
2010-05-12 14:06:40 +00:00
|
|
|
|
2011-02-07 12:57:23 +00:00
|
|
|
apaths = map(os.path.abspath, map(str.strip, config.config['addons_path'].split(',')))
|
2010-11-16 12:38:44 +00:00
|
|
|
if root_path in apaths:
|
|
|
|
path_list = apaths
|
2009-07-28 07:45:36 +00:00
|
|
|
else :
|
2010-11-16 12:38:44 +00:00
|
|
|
path_list = [root_path,] + apaths
|
|
|
|
|
2010-12-29 11:28:50 +00:00
|
|
|
# Also scan these non-addon paths
|
|
|
|
for bin_path in ['osv', 'report' ]:
|
2011-02-07 12:57:23 +00:00
|
|
|
path_list.append(os.path.join(config.config['root_path'], bin_path))
|
2010-11-16 12:38:44 +00:00
|
|
|
|
2010-11-25 17:21:44 +00:00
|
|
|
logger.debug("Scanning modules at paths: ", path_list)
|
2010-11-16 12:38:44 +00:00
|
|
|
|
|
|
|
mod_paths = []
|
|
|
|
join_dquotes = re.compile(r'([^\\])"[\s\\]*"', re.DOTALL)
|
|
|
|
join_quotes = re.compile(r'([^\\])\'[\s\\]*\'', re.DOTALL)
|
|
|
|
re_dquotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*"(.+?)"[\s]*?\)', re.DOTALL)
|
|
|
|
re_quotes = re.compile(r'[^a-zA-Z0-9_]_\([\s]*\'(.+?)\'[\s]*?\)', re.DOTALL)
|
2009-07-28 07:45:36 +00:00
|
|
|
|
2010-10-08 14:03:49 +00:00
|
|
|
def export_code_terms_from_file(fname, path, root, terms_type):
|
|
|
|
fabsolutepath = join(root, fname)
|
|
|
|
frelativepath = fabsolutepath[len(path):]
|
2010-11-16 12:38:44 +00:00
|
|
|
module = get_module_from_path(fabsolutepath, mod_paths=mod_paths)
|
2010-10-08 14:03:49 +00:00
|
|
|
is_mod_installed = module in installed_modules
|
|
|
|
if (('all' in modules) or (module in modules)) and is_mod_installed:
|
2010-11-25 17:21:44 +00:00
|
|
|
logger.debug("Scanning code of %s at module: %s", frelativepath, module)
|
2011-02-07 12:57:23 +00:00
|
|
|
src_file = misc.file_open(fabsolutepath, subdir='')
|
2011-01-04 10:13:35 +00:00
|
|
|
try:
|
|
|
|
code_string = src_file.read()
|
|
|
|
finally:
|
|
|
|
src_file.close()
|
2010-10-08 14:03:49 +00:00
|
|
|
if module in installed_modules:
|
|
|
|
frelativepath = str("addons" + frelativepath)
|
2010-11-16 12:38:44 +00:00
|
|
|
ite = re_dquotes.finditer(code_string)
|
2010-12-29 11:33:06 +00:00
|
|
|
code_offset = 0
|
|
|
|
code_line = 1
|
2010-11-16 12:38:44 +00:00
|
|
|
for i in ite:
|
|
|
|
src = i.group(1)
|
|
|
|
if src.startswith('""'):
|
|
|
|
assert src.endswith('""'), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30])
|
|
|
|
src = src[2:-2]
|
|
|
|
else:
|
|
|
|
src = join_dquotes.sub(r'\1', src)
|
2010-12-29 11:33:06 +00:00
|
|
|
# try to count the lines from the last pos to our place:
|
|
|
|
code_line += code_string[code_offset:i.start(1)].count('\n')
|
2010-11-16 12:38:44 +00:00
|
|
|
# now, since we did a binary read of a python source file, we
|
|
|
|
# have to expand pythonic escapes like the interpreter does.
|
|
|
|
src = src.decode('string_escape')
|
2010-12-29 11:33:06 +00:00
|
|
|
push_translation(module, terms_type, frelativepath, code_line, encode(src))
|
|
|
|
code_line += i.group(1).count('\n')
|
|
|
|
code_offset = i.end() # we have counted newlines up to the match end
|
|
|
|
|
2010-11-16 12:38:44 +00:00
|
|
|
ite = re_quotes.finditer(code_string)
|
2010-12-29 11:33:06 +00:00
|
|
|
code_offset = 0 #reset counters
|
|
|
|
code_line = 1
|
2010-11-16 12:38:44 +00:00
|
|
|
for i in ite:
|
|
|
|
src = i.group(1)
|
|
|
|
if src.startswith("''"):
|
|
|
|
assert src.endswith("''"), "Incorrect usage of _(..) function (should contain only literal strings!) in file %s near: %s" % (frelativepath, src[:30])
|
|
|
|
src = src[2:-2]
|
|
|
|
else:
|
|
|
|
src = join_quotes.sub(r'\1', src)
|
2010-12-29 11:33:06 +00:00
|
|
|
code_line += code_string[code_offset:i.start(1)].count('\n')
|
2010-11-16 12:38:44 +00:00
|
|
|
src = src.decode('string_escape')
|
2010-12-29 11:33:06 +00:00
|
|
|
push_translation(module, terms_type, frelativepath, code_line, encode(src))
|
|
|
|
code_line += i.group(1).count('\n')
|
|
|
|
code_offset = i.end() # we have counted newlines up to the match end
|
2010-10-08 14:03:49 +00:00
|
|
|
|
2009-11-20 11:57:21 +00:00
|
|
|
for path in path_list:
|
2010-11-25 17:21:44 +00:00
|
|
|
logger.debug("Scanning files of modules at %s", path)
|
2011-02-07 12:57:23 +00:00
|
|
|
for root, dummy, files in osutil.walksymlinks(path):
|
2010-10-08 14:03:49 +00:00
|
|
|
for fname in itertools.chain(fnmatch.filter(files, '*.py')):
|
|
|
|
export_code_terms_from_file(fname, path, root, 'code')
|
|
|
|
for fname in itertools.chain(fnmatch.filter(files, '*.mako')):
|
|
|
|
export_code_terms_from_file(fname, path, root, 'report')
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
|
|
|
|
out = [["module","type","name","res_id","src","value"]] # header
|
2009-01-11 18:01:06 +00:00
|
|
|
_to_translate.sort()
|
2008-07-22 14:24:36 +00:00
|
|
|
# translate strings marked as to be translated
|
2009-01-11 18:01:06 +00:00
|
|
|
for module, source, name, id, type in _to_translate:
|
2008-07-22 14:24:36 +00:00
|
|
|
trans = trans_obj._get_source(cr, uid, name, type, lang, source)
|
2008-12-16 16:21:12 +00:00
|
|
|
out.append([module, type, name, id, source, encode(trans) or ''])
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
return out
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2011-01-05 14:07:58 +00:00
|
|
|
def trans_load(cr, filename, lang, verbose=True, context=None):
|
2010-11-23 13:58:35 +00:00
|
|
|
logger = logging.getLogger('i18n')
|
2008-07-22 14:24:36 +00:00
|
|
|
try:
|
|
|
|
fileobj = open(filename,'r')
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.info("loading %s", filename)
|
2008-07-22 14:24:36 +00:00
|
|
|
fileformat = os.path.splitext(filename)[-1][1:].lower()
|
2011-01-05 14:07:58 +00:00
|
|
|
r = trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, context=context)
|
2008-07-22 14:24:36 +00:00
|
|
|
fileobj.close()
|
|
|
|
return r
|
|
|
|
except IOError:
|
2008-09-04 13:28:41 +00:00
|
|
|
if verbose:
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.error("couldn't read translation file %s", filename)
|
2008-07-22 14:24:36 +00:00
|
|
|
return None
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2011-01-05 14:07:58 +00:00
|
|
|
def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, context=None):
|
2011-01-07 15:17:35 +00:00
|
|
|
"""Populates the ir_translation table. Fixing the res_ids so that they point
|
|
|
|
correctly to ir_model_data is done in a separate step, using the
|
|
|
|
'trans_update_res_ids' function below."""
|
2010-11-23 13:58:35 +00:00
|
|
|
logger = logging.getLogger('i18n')
|
2008-09-04 13:28:41 +00:00
|
|
|
if verbose:
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.info('loading translation file for language %s', lang)
|
2010-11-23 15:27:40 +00:00
|
|
|
if context is None:
|
|
|
|
context = {}
|
2011-01-05 14:07:58 +00:00
|
|
|
db_name = cr.dbname
|
2008-07-22 14:24:36 +00:00
|
|
|
pool = pooler.get_pool(db_name)
|
|
|
|
lang_obj = pool.get('res.lang')
|
|
|
|
trans_obj = pool.get('ir.translation')
|
|
|
|
model_data_obj = pool.get('ir.model.data')
|
2011-02-07 12:57:23 +00:00
|
|
|
iso_lang = misc.get_iso_codes(lang)
|
2008-07-22 14:24:36 +00:00
|
|
|
try:
|
|
|
|
uid = 1
|
2009-01-29 23:20:17 +00:00
|
|
|
ids = lang_obj.search(cr, uid, [('code','=', lang)])
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2008-07-22 14:24:36 +00:00
|
|
|
if not ids:
|
2009-01-29 23:20:17 +00:00
|
|
|
# lets create the language with locale information
|
2010-12-30 09:17:34 +00:00
|
|
|
lang_obj.load_lang(cr, 1, lang=lang, lang_name=lang_name)
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
|
2009-01-29 23:20:17 +00:00
|
|
|
# now, the serious things: we read the language file
|
|
|
|
fileobj.seek(0)
|
2008-07-22 14:24:36 +00:00
|
|
|
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:
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.error('Bad file format: %s', fileformat)
|
2008-07-22 14:24:36 +00:00
|
|
|
raise Exception(_('Bad file format'))
|
|
|
|
|
|
|
|
# read the rest of the file
|
|
|
|
line = 1
|
|
|
|
for row in reader:
|
|
|
|
line += 1
|
|
|
|
# skip empty rows and rows where the translation field (=last fiefd) is empty
|
2009-06-10 08:11:57 +00:00
|
|
|
#if (not row) or (not row[-1]):
|
|
|
|
# continue
|
2008-07-22 14:24:36 +00:00
|
|
|
|
|
|
|
# dictionary which holds values for this line of the csv file
|
|
|
|
# {'lang': ..., 'type': ..., 'name': ..., 'res_id': ...,
|
|
|
|
# 'src': ..., 'value': ...}
|
|
|
|
dic = {'lang': lang}
|
|
|
|
for i in range(len(f)):
|
|
|
|
if f[i] in ('module',):
|
|
|
|
continue
|
|
|
|
dic[f[i]] = row[i]
|
|
|
|
|
|
|
|
try:
|
2010-11-25 17:22:36 +00:00
|
|
|
dic['res_id'] = dic['res_id'] and int(dic['res_id']) or 0
|
2011-01-06 10:50:21 +00:00
|
|
|
dic['module'] = False
|
|
|
|
dic['xml_id'] = False
|
2008-07-22 14:24:36 +00:00
|
|
|
except:
|
2011-01-07 15:17:35 +00:00
|
|
|
split_id = dic['res_id'].split('.', 1)
|
|
|
|
dic['module'] = split_id[0]
|
|
|
|
dic['xml_id'] = split_id[1]
|
2011-01-06 10:50:21 +00:00
|
|
|
dic['res_id'] = False
|
2008-07-22 14:24:36 +00:00
|
|
|
|
2010-12-21 10:58:43 +00:00
|
|
|
args = [
|
|
|
|
('lang', '=', lang),
|
|
|
|
('type', '=', dic['type']),
|
|
|
|
('name', '=', dic['name']),
|
|
|
|
('src', '=', dic['src']),
|
|
|
|
]
|
|
|
|
if dic['type'] == 'model':
|
2011-01-17 14:05:04 +00:00
|
|
|
if dic['res_id'] is False:
|
|
|
|
args.append(('module', '=', dic['module']))
|
|
|
|
args.append(('xml_id', '=', dic['xml_id']))
|
|
|
|
else:
|
|
|
|
args.append(('res_id', '=', dic['res_id']))
|
2010-12-21 10:58:43 +00:00
|
|
|
ids = trans_obj.search(cr, uid, args)
|
|
|
|
if ids:
|
2010-12-27 13:58:00 +00:00
|
|
|
if context.get('overwrite') and dic['value']:
|
2010-12-21 10:58:43 +00:00
|
|
|
trans_obj.write(cr, uid, ids, {'value': dic['value']})
|
2008-07-22 14:24:36 +00:00
|
|
|
else:
|
2010-12-21 10:58:43 +00:00
|
|
|
trans_obj.create(cr, uid, dic)
|
2008-09-04 13:28:41 +00:00
|
|
|
if verbose:
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.info("translation file loaded succesfully")
|
2008-07-22 14:24:36 +00:00
|
|
|
except IOError:
|
2009-11-20 11:57:21 +00:00
|
|
|
filename = '[lang: %s][format: %s]' % (iso_lang or 'new', fileformat)
|
2010-11-23 13:58:35 +00:00
|
|
|
logger.exception("couldn't read translation file %s", filename)
|
2006-12-07 13:41:40 +00:00
|
|
|
|
2011-01-06 10:50:21 +00:00
|
|
|
def trans_update_res_ids(cr):
|
2011-01-07 15:17:35 +00:00
|
|
|
cr.execute("""
|
|
|
|
UPDATE ir_translation
|
2011-01-17 17:16:08 +00:00
|
|
|
SET res_id = COALESCE ((SELECT ir_model_data.res_id
|
2011-01-07 15:17:35 +00:00
|
|
|
FROM ir_model_data
|
|
|
|
WHERE ir_translation.module = ir_model_data.module
|
2011-01-17 17:16:08 +00:00
|
|
|
AND ir_translation.xml_id = ir_model_data.name), 0)
|
2011-01-07 15:17:35 +00:00
|
|
|
WHERE ir_translation.module is not null
|
|
|
|
AND ir_translation.xml_id is not null
|
|
|
|
AND ir_translation.res_id = 0;
|
2011-01-06 10:50:21 +00:00
|
|
|
""")
|
|
|
|
|
2009-02-05 12:51:17 +00:00
|
|
|
def get_locales(lang=None):
|
|
|
|
if lang is None:
|
|
|
|
lang = locale.getdefaultlocale()[0]
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2009-02-05 12:51:17 +00:00
|
|
|
if os.name == 'nt':
|
|
|
|
lang = _LOCALE2WIN32.get(lang, lang)
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2009-02-05 12:51:17 +00:00
|
|
|
def process(enc):
|
|
|
|
ln = locale._build_localename((lang, enc))
|
|
|
|
yield ln
|
|
|
|
nln = locale.normalize(ln)
|
|
|
|
if nln != ln:
|
|
|
|
yield nln
|
|
|
|
|
|
|
|
for x in process('utf8'): yield x
|
|
|
|
|
|
|
|
prefenc = locale.getpreferredencoding()
|
|
|
|
if prefenc:
|
|
|
|
for x in process(prefenc): yield x
|
2009-11-20 11:57:21 +00:00
|
|
|
|
2009-02-05 12:51:17 +00:00
|
|
|
prefenc = {
|
2009-11-20 11:57:21 +00:00
|
|
|
'latin1': 'latin9',
|
2009-02-05 12:51:17 +00:00
|
|
|
'iso-8859-1': 'iso8859-15',
|
|
|
|
'cp1252': '1252',
|
|
|
|
}.get(prefenc.lower())
|
|
|
|
if prefenc:
|
|
|
|
for x in process(prefenc): yield x
|
|
|
|
|
|
|
|
yield lang
|
|
|
|
|
|
|
|
|
|
|
|
|
2009-02-04 17:27:00 +00:00
|
|
|
def resetlocale():
|
2009-11-20 11:57:21 +00:00
|
|
|
# locale.resetlocale is bugged with some locales.
|
2009-02-05 12:51:17 +00:00
|
|
|
for ln in get_locales():
|
2009-02-05 10:02:29 +00:00
|
|
|
try:
|
2009-02-05 13:18:12 +00:00
|
|
|
return locale.setlocale(locale.LC_ALL, ln)
|
2009-02-05 10:02:29 +00:00
|
|
|
except locale.Error:
|
2009-02-05 12:51:17 +00:00
|
|
|
continue
|
2009-02-04 17:27:00 +00:00
|
|
|
|
2010-10-20 14:42:48 +00:00
|
|
|
def load_language(cr, lang):
|
|
|
|
"""Loads a translation terms for a language.
|
|
|
|
Used mainly to automate language loading at db initialization.
|
2011-04-21 09:34:00 +00:00
|
|
|
|
2010-10-20 14:42:48 +00:00
|
|
|
:param lang: language ISO code with optional _underscore_ and l10n flavor (ex: 'fr', 'fr_BE', but not 'fr-BE')
|
|
|
|
:type lang: str
|
|
|
|
"""
|
|
|
|
pool = pooler.get_pool(cr.dbname)
|
|
|
|
language_installer = pool.get('base.language.install')
|
|
|
|
uid = 1
|
|
|
|
oid = language_installer.create(cr, uid, {'lang': lang})
|
|
|
|
language_installer.lang_install(cr, uid, [oid], context=None)
|
|
|
|
|
2008-07-23 15:01:27 +00:00
|
|
|
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
|
|
|