[IMP] *: translation methods cleaning
Extract some cleaning wanted in #30759 but not related to language packs - `amount_to_text` should work only on activated languages - deprecate `load_lang` and rely on `_create_lang` or `_activate_lang` - `trans_load_data` and `_load_module_terms` no longer silently activate languages - pass explicitly parameters instead of relying on context content - replace some `IrTranslation._load_module_terms(['base'], ['fr_FR'])` by `BaseModule._update_translations(['fr_FR'])` for higher level methods - create `TranslationExporter` class to clarify the `trans_export` method (and clean dead code) closes odoo/odoo#38859 Task-id: 2088290 Pad: https://pad.odoo.com/p/r.f6f789f8711d21314bd902972153ea9d Signed-off-by: Martin Trigaux (mat) <mat@odoo.com>
This commit is contained in:
@@ -368,7 +368,7 @@ class TestSMSComposerMass(test_mail_full_common.BaseFunctionalTest, sms_common.M
|
||||
self.assertSMSOutgoing(record.customer_id, None, 'Dear %s this is an SMS.' % record.display_name)
|
||||
|
||||
def test_composer_mass_active_ids_w_template_and_lang(self):
|
||||
self.env.ref('base.lang_fr').write({'active': True})
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.env['ir.translation'].create({
|
||||
'type': 'model',
|
||||
'name': 'sms.template,body',
|
||||
@@ -425,7 +425,7 @@ class TestSMSComposerMass(test_mail_full_common.BaseFunctionalTest, sms_common.M
|
||||
""" Test the context action from a SMS template (Add context action button)
|
||||
and the usage with the sms composer """
|
||||
# Create the lang info
|
||||
self.env.ref('base.lang_fr').write({'active': True})
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.env['ir.translation'].create({
|
||||
'type': 'model',
|
||||
'name': 'sms.template,body',
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestSmsTemplate(test_mail_full_common.BaseFunctionalTest, sms_common.MockS
|
||||
self.assertEqual(rendered_body[self.test_record.id], 'Dear %s this is an SMS.' % self.test_record.display_name)
|
||||
|
||||
def test_sms_template_lang(self):
|
||||
self.env.ref('base.lang_fr').write({'active': True})
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.user_admin.write({'lang': 'en_US'})
|
||||
self.env['ir.translation'].create({
|
||||
'type': 'model',
|
||||
|
||||
@@ -15,8 +15,7 @@ class TestUrlCommon(odoo.tests.HttpCase):
|
||||
'domain': self.domain,
|
||||
})
|
||||
|
||||
lang_fr = self.env.ref('base.lang_fr')
|
||||
lang_fr.write({'active': True})
|
||||
lang_fr = self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.website.language_ids = self.env.ref('base.lang_en') + lang_fr
|
||||
self.website.default_lang_id = self.env.ref('base.lang_en')
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ class TestLangUrl(HttpCase):
|
||||
|
||||
# Simulate multi lang without loading translations
|
||||
self.website = self.env.ref('website.default_website')
|
||||
lang_fr = self.env.ref('base.lang_fr')
|
||||
lang_fr.write({'active': True, 'url_code': 'fr'})
|
||||
lang_fr = self.env['res.lang']._activate_lang('fr_FR')
|
||||
lang_fr.write({'url_code': 'fr'})
|
||||
self.website.language_ids = self.env.ref('base.lang_en') + lang_fr
|
||||
self.website.default_lang_id = self.env.ref('base.lang_en')
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ class TestQwebProcessAtt(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestQwebProcessAtt, self).setUp()
|
||||
self.website = self.env.ref('website.default_website')
|
||||
self.env.ref('base.lang_fr').active = True
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.website.language_ids = self.env.ref('base.lang_en') + self.env.ref('base.lang_fr')
|
||||
self.website.default_lang_id = self.env.ref('base.lang_en')
|
||||
self.website.cdn_activated = True
|
||||
|
||||
@@ -873,7 +873,7 @@ class Module(models.Model):
|
||||
cat_id = modules.db.create_categories(self._cr, categs)
|
||||
self.write({'category_id': cat_id})
|
||||
|
||||
def _update_translations(self, filter_lang=None):
|
||||
def _update_translations(self, filter_lang=None, overwrite=False):
|
||||
if not filter_lang:
|
||||
langs = self.env['res.lang'].get_installed()
|
||||
filter_lang = [code for code, _ in langs]
|
||||
@@ -886,7 +886,7 @@ class Module(models.Model):
|
||||
for mod in update_mods
|
||||
}
|
||||
mod_names = topological_sort(mod_dict)
|
||||
self.env['ir.translation']._load_module_terms(mod_names, filter_lang)
|
||||
self.env['ir.translation']._load_module_terms(mod_names, filter_lang, overwrite)
|
||||
|
||||
def _check(self):
|
||||
for module in self:
|
||||
|
||||
@@ -27,15 +27,15 @@ class IrTranslationImport(object):
|
||||
"""
|
||||
_table = 'tmp_ir_translation_import'
|
||||
|
||||
def __init__(self, model):
|
||||
def __init__(self, cr, overwrite=False):
|
||||
""" Store some values, and also create a temporary SQL table to accept
|
||||
the data.
|
||||
|
||||
:param model: the model to insert the data into (as a recordset)
|
||||
"""
|
||||
self._cr = model._cr
|
||||
self._model_table = model._table
|
||||
self._overwrite = model._context.get('overwrite', False)
|
||||
self._cr = cr
|
||||
self._model_table = "ir_translation"
|
||||
self._overwrite = overwrite
|
||||
self._debug = False
|
||||
self._rows = []
|
||||
|
||||
@@ -774,24 +774,18 @@ class IrTranslation(models.Model):
|
||||
|
||||
return action
|
||||
|
||||
@api.model
|
||||
def _get_import_cursor(self):
|
||||
def _get_import_cursor(self, overwrite):
|
||||
""" Return a cursor-like object for fast inserting translations """
|
||||
return IrTranslationImport(self)
|
||||
return IrTranslationImport(self._cr, overwrite)
|
||||
|
||||
def _load_module_terms(self, modules, langs):
|
||||
def _load_module_terms(self, modules, langs, overwrite=False):
|
||||
""" Load PO files of the given modules for the given languages. """
|
||||
# make sure the given languages are active
|
||||
res_lang = self.env['res.lang'].sudo()
|
||||
for lang in langs:
|
||||
res_lang.load_lang(lang)
|
||||
# load i18n files
|
||||
for module_name in modules:
|
||||
modpath = get_module_path(module_name)
|
||||
if not modpath:
|
||||
continue
|
||||
for lang in langs:
|
||||
context = dict(self._context)
|
||||
lang_code = tools.get_iso_codes(lang)
|
||||
base_lang_code = None
|
||||
if '_' in lang_code:
|
||||
@@ -802,28 +796,28 @@ class IrTranslation(models.Model):
|
||||
base_trans_file = get_module_resource(module_name, 'i18n', base_lang_code + '.po')
|
||||
if base_trans_file:
|
||||
_logger.info('module %s: loading base translation file %s for language %s', module_name, base_lang_code, lang)
|
||||
tools.trans_load(self._cr, base_trans_file, lang, verbose=False, module_name=module_name, context=context)
|
||||
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
||||
tools.trans_load(self._cr, base_trans_file, lang, verbose=False, overwrite=overwrite)
|
||||
overwrite = True # make sure the requested translation will override the base terms later
|
||||
|
||||
# i18n_extra folder is for additional translations handle manually (eg: for l10n_be)
|
||||
base_trans_extra_file = get_module_resource(module_name, 'i18n_extra', base_lang_code + '.po')
|
||||
if base_trans_extra_file:
|
||||
_logger.info('module %s: loading extra base translation file %s for language %s', module_name, base_lang_code, lang)
|
||||
tools.trans_load(self._cr, base_trans_extra_file, lang, verbose=False, module_name=module_name, context=context)
|
||||
context['overwrite'] = True # make sure the requested translation will override the base terms later
|
||||
tools.trans_load(self._cr, base_trans_extra_file, lang, verbose=False, overwrite=overwrite)
|
||||
overwrite = True # make sure the requested translation will override the base terms later
|
||||
|
||||
# Step 2: then load the main translation file, possibly overriding the terms coming from the base language
|
||||
trans_file = get_module_resource(module_name, 'i18n', lang_code + '.po')
|
||||
if trans_file:
|
||||
_logger.info('module %s: loading translation file (%s) for language %s', module_name, lang_code, lang)
|
||||
tools.trans_load(self._cr, trans_file, lang, verbose=False, module_name=module_name, context=context)
|
||||
tools.trans_load(self._cr, trans_file, lang, verbose=False, overwrite=overwrite)
|
||||
elif lang_code != 'en_US':
|
||||
_logger.info('module %s: no translation for language %s', module_name, lang_code)
|
||||
|
||||
trans_extra_file = get_module_resource(module_name, 'i18n_extra', lang_code + '.po')
|
||||
if trans_extra_file:
|
||||
_logger.info('module %s: loading extra translation file (%s) for language %s', module_name, lang_code, lang)
|
||||
tools.trans_load(self._cr, trans_extra_file, lang, verbose=False, module_name=module_name, context=context)
|
||||
tools.trans_load(self._cr, trans_extra_file, lang, verbose=False, overwrite=overwrite)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -110,8 +110,7 @@ class Currency(models.Model):
|
||||
integer_value = int(parts[0])
|
||||
fractional_value = int(parts[2] or 0)
|
||||
|
||||
lang_code = self.env.context.get('lang') or self.env.user.lang
|
||||
lang = self.env['res.lang'].with_context(active_test=False).search([('code', '=', lang_code)])
|
||||
lang = tools.get_lang(self.env)
|
||||
amount_words = tools.ustr('{amt_value} {amt_word}').format(
|
||||
amt_value=_num2words(integer_value, lang=lang.iso_code),
|
||||
amt_word=self.currency_unit_label,
|
||||
|
||||
@@ -88,15 +88,24 @@ class Lang(models.Model):
|
||||
if not self.search_count([]):
|
||||
_logger.error("No language is active.")
|
||||
|
||||
@api.model
|
||||
# TODO remove me after v14
|
||||
def load_lang(self, lang, lang_name=None):
|
||||
""" Create the given language if necessary, and make it active. """
|
||||
# if the language exists, simply make it active
|
||||
language = self.with_context(active_test=False).search([('code', '=', lang)], limit=1)
|
||||
if language:
|
||||
language.write({'active': True})
|
||||
return language.id
|
||||
_logger.warning("Call to deprecated method load_lang, use _create_lang or _activate_lang instead")
|
||||
language = self._activate_lang(lang) or self._create_lang(lang, lang_name)
|
||||
return language.id
|
||||
|
||||
def _activate_lang(self, code):
|
||||
""" Activate languages
|
||||
:param code: code of the language to activate
|
||||
:return: the language matching 'code' activated
|
||||
"""
|
||||
lang = self.with_context(active_test=False).search([('code', '=', code)])
|
||||
if lang and not lang.active:
|
||||
lang.active = True
|
||||
return lang
|
||||
|
||||
def _create_lang(self, lang, lang_name=None):
|
||||
""" Create the given language and make it active. """
|
||||
# create the language with locale information
|
||||
fail = True
|
||||
iso_lang = tools.get_iso_codes(lang)
|
||||
@@ -149,7 +158,7 @@ class Lang(models.Model):
|
||||
'grouping' : str(conv.get('grouping', [])),
|
||||
}
|
||||
try:
|
||||
return self.create(lang_info).id
|
||||
return self.create(lang_info)
|
||||
finally:
|
||||
tools.resetlocale()
|
||||
|
||||
@@ -157,18 +166,16 @@ class Lang(models.Model):
|
||||
def install_lang(self):
|
||||
"""
|
||||
|
||||
This method is called from odoo/addons/base/base_data.xml to load
|
||||
This method is called from odoo/addons/data/res_lang_data.xml to load
|
||||
some language and set it as the default for every partners. The
|
||||
language is set via tools.config by the RPC 'create' method on the
|
||||
language is set via tools.config by the '_initialize_db' method on the
|
||||
'db' object. This is a fragile solution and something else should be
|
||||
found.
|
||||
|
||||
"""
|
||||
# config['load_language'] is a comma-separated list or None
|
||||
lang_code = (tools.config.get('load_language') or 'en_US').split(',')[0]
|
||||
lang = self._lang_get(lang_code)
|
||||
if not lang:
|
||||
self.load_lang(lang_code)
|
||||
lang = self._activate_lang(lang_code) or self._create_lang(lang_code)
|
||||
IrDefault = self.env['ir.default']
|
||||
default_value = IrDefault.get('res.partner', 'lang')
|
||||
if default_value is None:
|
||||
@@ -219,7 +226,7 @@ class Lang(models.Model):
|
||||
active_lang = [lang.code for lang in self.filtered(lambda l: l.active)]
|
||||
if active_lang:
|
||||
mods = self.env['ir.module.module'].search([('state', '=', 'installed')])
|
||||
mods._update_translations(filter_lang=active_lang)
|
||||
mods._update_translations(active_lang)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
|
||||
@@ -672,11 +672,13 @@ class TestExpression(SavepointCaseWithUserDemo):
|
||||
def test_lp1071710(self):
|
||||
""" Check that we can exclude translated fields (bug lp:1071710) """
|
||||
# first install french language
|
||||
self.env['ir.translation']._load_module_terms(['base'], ['fr_FR'])
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.env['res.partner'].search([('name', '=', 'Pepper Street')]).country_id = self.env.ref('base.be')
|
||||
# actual test
|
||||
Country = self.env['res.country'].with_context(lang='fr_FR')
|
||||
be = self.env.ref('base.be')
|
||||
be.with_context(lang='fr_FR').name = "Belgique"
|
||||
self.assertNotEqual(be.name, "Belgique", "Setting a translation should not impact other languages")
|
||||
not_be = self._search(Country, [('name', '!=', 'Belgique')])
|
||||
self.assertNotIn(be, not_be)
|
||||
|
||||
|
||||
@@ -220,7 +220,8 @@ class TestFormatLangDate(TransactionCase):
|
||||
lang = self.env['res.lang']
|
||||
|
||||
# Activate French and Simplified Chinese (test with non-ASCII characters)
|
||||
lang.search([('active', '=', False), ('code', 'in', ['fr_FR', 'zh_CN'])]).write({'active': True})
|
||||
lang._activate_lang('fr_FR')
|
||||
lang._activate_lang('zh_CN')
|
||||
|
||||
# -- test `date`
|
||||
# Change a single parameter
|
||||
|
||||
@@ -522,10 +522,8 @@ class TestQWebNS(TransactionCase):
|
||||
current_lang = 'en_US'
|
||||
other_lang = 'fr_FR'
|
||||
|
||||
self.env['res.lang'].load_lang(lang=other_lang)
|
||||
|
||||
self.env['res.lang']._lang_get(other_lang).write({
|
||||
'active': True,
|
||||
lang = self.env['res.lang']._activate_lang(other_lang)
|
||||
lang.write({
|
||||
'decimal_point': '*',
|
||||
'thousands_sep': '/'
|
||||
})
|
||||
|
||||
@@ -247,7 +247,8 @@ class TestTranslation(TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestTranslation, self).setUp()
|
||||
self.env['ir.translation']._load_module_terms(['base'], ['fr_FR'])
|
||||
lang = self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.env.ref('base.module_base')._update_translations(['fr_FR'])
|
||||
self.customers = self.env['res.partner.category'].create({'name': 'Customers'})
|
||||
self.env['ir.translation'].create({
|
||||
'type': 'model',
|
||||
@@ -436,7 +437,7 @@ class TestTranslationWrite(TransactionCase):
|
||||
self.assertEqual(source_name[0]['name'], "English Name", "Reference field not updated")
|
||||
|
||||
def test_03_fr_single(self):
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.env['res.users'].with_context(active_test=False).search([]).write({'lang': 'fr_FR'})
|
||||
self.env.ref('base.lang_en').active = False
|
||||
|
||||
@@ -454,7 +455,7 @@ class TestTranslationWrite(TransactionCase):
|
||||
self.assertEqual(len(translations), 0, "No French translation should be created when writing in French")
|
||||
|
||||
def test_04_fr_multi(self):
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
|
||||
langs = self.env['res.lang'].get_installed()
|
||||
self.assertEqual([('en_US', 'English (US)'), ('fr_FR', 'French / Français')], langs,
|
||||
@@ -481,9 +482,9 @@ class TestTranslationWrite(TransactionCase):
|
||||
])
|
||||
|
||||
def test_04_fr_multi_no_en(self):
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('es_ES')
|
||||
self.env['res.users'].with_context(active_test=False).search([]).write({'lang': 'fr_FR'})
|
||||
self.env['res.lang'].load_lang('es_ES')
|
||||
self.env.ref('base.lang_en').active = False
|
||||
|
||||
langs = self.env['res.lang'].get_installed()
|
||||
@@ -504,7 +505,7 @@ class TestTranslationWrite(TransactionCase):
|
||||
])
|
||||
|
||||
def test_05_remove_multi(self):
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
|
||||
langs = self.env['res.lang'].get_installed()
|
||||
self.assertEqual([('en_US', 'English (US)'), ('fr_FR', 'French / Français')], langs,
|
||||
@@ -576,7 +577,9 @@ class TestTranslationWrite(TransactionCase):
|
||||
class TestXMLTranslation(TransactionCase):
|
||||
def setUp(self):
|
||||
super(TestXMLTranslation, self).setUp()
|
||||
self.env['ir.translation']._load_module_terms(['base'], ['fr_FR', 'nl_NL'])
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('nl_NL')
|
||||
self.env.ref('base.module_base')._update_translations(['fr_FR', 'nl_NL'])
|
||||
|
||||
def create_view(self, archf, terms, **kwargs):
|
||||
view = self.env['ir.ui.view'].create({
|
||||
|
||||
@@ -606,7 +606,7 @@ class TestNoModel(ViewCase):
|
||||
"""
|
||||
Test if translations work correctly without a model
|
||||
"""
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
ARCH = '<template name="foo">%s</template>'
|
||||
TEXT_EN = "Copyright copyrighter"
|
||||
TEXT_FR = u"Copyrighter, tous droits réservés"
|
||||
|
||||
@@ -28,7 +28,6 @@ class BaseLanguageImport(models.TransientModel):
|
||||
|
||||
def import_lang(self):
|
||||
this = self[0]
|
||||
this = this.with_context(overwrite=this.overwrite)
|
||||
with TemporaryFile('wb+') as buf:
|
||||
try:
|
||||
buf.write(base64.decodebytes(this.data))
|
||||
@@ -37,8 +36,14 @@ class BaseLanguageImport(models.TransientModel):
|
||||
buf.seek(0)
|
||||
fileformat = os.path.splitext(this.filename)[-1][1:].lower()
|
||||
|
||||
tools.trans_load_data(this._cr, buf, fileformat, this.code,
|
||||
lang_name=this.name, context=this._context)
|
||||
Lang = self.env["res.lang"]
|
||||
lang = Lang._activate_lang(self.code) or Lang._create_lang(
|
||||
self.code, lang_name=self.name
|
||||
)
|
||||
|
||||
tools.trans_load_data(
|
||||
this._cr, buf, fileformat, this.code, overwrite=self.overwrite
|
||||
)
|
||||
except Exception as e:
|
||||
_logger.exception('File unsuccessfully imported, due to format mismatch.')
|
||||
raise UserError(
|
||||
|
||||
@@ -33,7 +33,8 @@ class BaseLanguageInstall(models.TransientModel):
|
||||
def lang_install(self):
|
||||
self.ensure_one()
|
||||
mods = self.env['ir.module.module'].search([('state', '=', 'installed')])
|
||||
mods.with_context(overwrite=self.overwrite)._update_translations(self.lang)
|
||||
self.env['res.lang']._activate_lang(self.lang)
|
||||
mods._update_translations(self.lang, self.overwrite)
|
||||
self.state = 'done'
|
||||
self.env.cr.execute('ANALYZE ir_translation')
|
||||
|
||||
|
||||
@@ -26,10 +26,7 @@ class BaseUpdateTranslations(models.TransientModel):
|
||||
return lang.name
|
||||
|
||||
def act_update(self):
|
||||
this = self[0]
|
||||
lang_name = self._get_lang_name(this.lang)
|
||||
with tempfile.NamedTemporaryFile() as buf:
|
||||
tools.trans_export(this.lang, ['all'], buf, 'po', self._cr)
|
||||
context = {'create_empty_translation': True}
|
||||
tools.trans_load_data(self._cr, buf, 'po', this.lang, lang_name=lang_name, context=context)
|
||||
tools.trans_export(self.lang, ['all'], buf, 'po', self._cr)
|
||||
tools.trans_load_data(self._cr, buf, 'po', self.lang, create_empty_translation=True)
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
@@ -33,7 +33,7 @@ class TestJavascriptAssetsBundle(FileTouchable):
|
||||
super(TestJavascriptAssetsBundle, self).setUp()
|
||||
self.jsbundle_xmlid = 'test_assetsbundle.bundle1'
|
||||
self.cssbundle_xmlid = 'test_assetsbundle.bundle2'
|
||||
self.env['res.lang'].load_lang('ar_SY')
|
||||
self.env['res.lang']._activate_lang('ar_SY')
|
||||
|
||||
|
||||
def _get_asset(self, xmlid, env=None):
|
||||
|
||||
@@ -291,7 +291,7 @@ class TestDurationExport(TestBasicExport):
|
||||
def setUp(self):
|
||||
super(TestDurationExport, self).setUp()
|
||||
# needs to have lang installed otherwise falls back on en_US
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
|
||||
def test_default_unit(self):
|
||||
converter = self.get_converter('float', 'duration')
|
||||
@@ -332,7 +332,7 @@ class TestRelativeDatetime(TestBasicExport):
|
||||
def setUp(self):
|
||||
super(TestRelativeDatetime, self).setUp()
|
||||
# needs to have lang installed otherwise falls back on en_US
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
|
||||
def test_basic(self):
|
||||
converter = self.get_converter('datetime', 'relative')
|
||||
|
||||
@@ -285,7 +285,7 @@ class test_selection(CreatorCase):
|
||||
[[u"Bar"]])
|
||||
|
||||
def test_localized_export(self):
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
ir_field = self.env['ir.model.fields']._get('export.selection', 'value')
|
||||
selection = ir_field.selection_ids
|
||||
translations = dict(self.translations_fr)
|
||||
|
||||
@@ -73,7 +73,7 @@ class ImporterCase(common.TransactionCase):
|
||||
return '__test__.' + name
|
||||
|
||||
def add_translations(self, name, type, code, *tnx):
|
||||
self.env['res.lang'].load_lang(code)
|
||||
self.env['res.lang']._activate_lang(code)
|
||||
Translations = self.env['ir.translation']
|
||||
for source, value in tnx:
|
||||
Translations.create({
|
||||
|
||||
@@ -1412,7 +1412,7 @@ class TestFields(TransactionCaseWithUserDemo):
|
||||
email = self.env.ref('test_new_api.emailmessage_0_0')
|
||||
self.assertEqual(email.message, message)
|
||||
|
||||
self.env['res.lang'].load_lang('fr_FR')
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
|
||||
def count(msg):
|
||||
# return the number of translations of msg.label
|
||||
|
||||
@@ -27,7 +27,8 @@ class TestUiTranslation(odoo.tests.HttpCase):
|
||||
@mute_logger('odoo.sql_db', 'odoo.http')
|
||||
def test_01_sql_constraints(self):
|
||||
# Raise an SQL constraint and test the message
|
||||
self.env['ir.translation']._load_module_terms(['test_new_api'], ['fr_FR'])
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
self.env.ref('base.module_test_new_api')._update_translations(['fr_FR'])
|
||||
constraint = self.env.ref('test_new_api.constraint_test_new_api_category_positive_color')
|
||||
message = constraint.with_context(lang='fr_FR').message
|
||||
self.assertEqual(message, "La couleur doit être une valeur positive !")
|
||||
|
||||
@@ -18,7 +18,8 @@ class TestTermCount(common.TransactionCase):
|
||||
"""
|
||||
Just make sure we have as many translation entries as we wanted.
|
||||
"""
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', module_name='test_translation_import', verbose=False)
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
|
||||
translations = self.env['ir.translation'].search([
|
||||
('lang', '=', 'fr_FR'),
|
||||
('src', '=', '1XBUO5PUYH2RYZSA1FTLRYS8SPCNU1UYXMEYMM25ASV7JC2KTJZQESZYRV9L8CGB'),
|
||||
@@ -40,6 +41,7 @@ class TestTermCount(common.TransactionCase):
|
||||
"""
|
||||
Just make sure we have as many translation entries as we wanted and module deducted from file content
|
||||
"""
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
|
||||
translations = self.env['ir.translation'].search([
|
||||
('lang', '=', 'fr_FR'),
|
||||
@@ -56,10 +58,11 @@ class TestTermCount(common.TransactionCase):
|
||||
menu = self.env.ref('test_translation_import.menu_test_translation_import')
|
||||
menu.name = "New Name"
|
||||
# install french and change translation content
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', module_name='test_translation_import', verbose=False)
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
|
||||
menu.with_context(lang='fr_FR').name = "Nouveau nom"
|
||||
# reload with overwrite
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', module_name='test_translation_import', verbose=False, context={'overwrite': True})
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False, overwrite=True)
|
||||
|
||||
# trans_load invalidates ormcache but not record cache
|
||||
menu.env.cache.invalidate()
|
||||
@@ -67,8 +70,9 @@ class TestTermCount(common.TransactionCase):
|
||||
self.assertEqual(menu.with_context(lang='fr_FR').name, "Nouveau nom")
|
||||
|
||||
def test_lang_with_base(self):
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_BE', module_name='test_translation_import', verbose=False)
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr_BE.po', 'fr_BE', module_name='test_translation_import', verbose=False, context={'overwrite': True})
|
||||
self.env['res.lang']._activate_lang('fr_BE')
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_BE', verbose=False)
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr_BE.po', 'fr_BE', verbose=False, overwrite=True)
|
||||
|
||||
# language override base language
|
||||
translations = self.env['ir.translation'].search([
|
||||
@@ -95,7 +99,8 @@ class TestTermCount(common.TransactionCase):
|
||||
"""
|
||||
Just make sure we do not create duplicated translation with 'code' type
|
||||
"""
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', module_name='test_translation_import', verbose=False)
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
odoo.tools.trans_load(self.cr, 'test_translation_import/i18n/fr.po', 'fr_FR', verbose=False)
|
||||
ids = self.env['ir.translation'].search([
|
||||
('lang', '=', 'fr_FR'),
|
||||
('src', '=', 'Test translation with two code lines'),
|
||||
@@ -118,12 +123,15 @@ class TestTermCount(common.TransactionCase):
|
||||
def test_export_empty_string(self):
|
||||
"""When the string and the translation is equal the translation is empty"""
|
||||
# Export the translations
|
||||
def update_translations(context=None):
|
||||
context = dict(context or {}, overwrite=True)
|
||||
def update_translations(create_empty_translation=False):
|
||||
self.env['res.lang']._activate_lang('fr_FR')
|
||||
with closing(io.BytesIO()) as bufferobj:
|
||||
odoo.tools.trans_export('fr_FR', ['test_translation_import'], bufferobj, 'po', self.cr)
|
||||
bufferobj.name = 'test_translation_import/i18n/fr.po'
|
||||
odoo.tools.trans_load_data(self.cr, bufferobj, 'po', 'fr_FR', verbose=False, context=context)
|
||||
odoo.tools.trans_load_data(self.cr, bufferobj, 'po', 'fr_FR',
|
||||
verbose=False,
|
||||
create_empty_translation=create_empty_translation,
|
||||
overwrite=True)
|
||||
|
||||
# Check that the not translated key is not created
|
||||
update_translations()
|
||||
@@ -131,7 +139,7 @@ class TestTermCount(common.TransactionCase):
|
||||
self.assertFalse(translation, 'An empty translation is not imported')
|
||||
|
||||
# Check that "Generate Missing Terms" create empty string for not translated key
|
||||
update_translations({'create_empty_translation': True})
|
||||
update_translations(create_empty_translation=True)
|
||||
translation = self.env['ir.translation'].search_count([('src', '=', 'Efgh'), ('value', '=', '')])
|
||||
self.assertTrue(translation, 'The translation of "Efgh" should be empty')
|
||||
|
||||
@@ -232,7 +240,8 @@ class TestTranslationFlow(common.TransactionCase):
|
||||
|
||||
translations = self.env["ir.translation"].search([
|
||||
('lang', '=', 'fr_FR'),
|
||||
('module', '=', 'test_translation_import')
|
||||
('module', '=', 'test_translation_import'),
|
||||
('value', '!=', ''),
|
||||
])
|
||||
|
||||
# minus 3 as the original fr.po contains 3 fake code translations (cf
|
||||
@@ -259,10 +268,12 @@ class TestTranslationFlow(common.TransactionCase):
|
||||
'overwrite': False,
|
||||
})
|
||||
with mute_logger('odoo.addons.base.models.res_lang'):
|
||||
import_fr.with_context(create_empty_translation=True).import_lang()
|
||||
import_fr.import_lang()
|
||||
|
||||
import_translation = self.env["ir.translation"].search([
|
||||
('lang', '=', 'fr_FR'),
|
||||
('module', '=', 'test_translation_import')
|
||||
('module', '=', 'test_translation_import'),
|
||||
('value', '!=', ''),
|
||||
])
|
||||
|
||||
self.assertEqual(init_translation_count, len(import_translation))
|
||||
|
||||
+2
-2
@@ -111,14 +111,14 @@ def export_translation():
|
||||
|
||||
def import_translation():
|
||||
config = odoo.tools.config
|
||||
context = {'overwrite': config["overwrite_existing_translations"]}
|
||||
overwrite = config["overwrite_existing_translations"]
|
||||
dbname = config['db_name']
|
||||
|
||||
registry = odoo.modules.registry.Registry.new(dbname)
|
||||
with odoo.api.Environment.manage():
|
||||
with registry.cursor() as cr:
|
||||
odoo.tools.trans_load(
|
||||
cr, config["translate_in"], config["language"], context=context,
|
||||
cr, config["translate_in"], config["language"], overwrite=overwrite,
|
||||
)
|
||||
|
||||
def main(args):
|
||||
|
||||
+214
-149
@@ -13,7 +13,7 @@ import re
|
||||
import tarfile
|
||||
import tempfile
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
from collections import defaultdict, namedtuple
|
||||
from datetime import datetime
|
||||
from os.path import join
|
||||
|
||||
@@ -647,13 +647,13 @@ class PoFileReader:
|
||||
continue
|
||||
_logger.error("malformed po file: unknown occurrence: %s", occurrence)
|
||||
|
||||
def TranslationFileWriter(target, fileformat='po', lang=None, modules=None):
|
||||
def TranslationFileWriter(target, fileformat='po', lang=None):
|
||||
""" Iterate over translation file to return Odoo translation entries """
|
||||
if fileformat == 'csv':
|
||||
return CSVFileWriter(target)
|
||||
|
||||
if fileformat == 'po':
|
||||
return PoFileWriter(target, modules=modules, lang=lang)
|
||||
return PoFileWriter(target, lang=lang)
|
||||
|
||||
if fileformat == 'tgz':
|
||||
return TarFileWriter(target, lang=lang)
|
||||
@@ -677,13 +677,34 @@ class CSVFileWriter:
|
||||
|
||||
class PoFileWriter:
|
||||
""" Iterate over po file to return Odoo translation entries """
|
||||
def __init__(self, target, modules, lang):
|
||||
import odoo.release as release
|
||||
def __init__(self, target, lang):
|
||||
|
||||
self.buffer = target
|
||||
self.lang = lang
|
||||
self.po = polib.POFile()
|
||||
|
||||
def write_rows(self, rows):
|
||||
# we now group the translations by source. That means one translation per source.
|
||||
grouped_rows = {}
|
||||
modules = set([])
|
||||
for module, type, name, res_id, src, trad, comments in rows:
|
||||
row = grouped_rows.setdefault(src, {})
|
||||
row.setdefault('modules', set()).add(module)
|
||||
if not row.get('translation') and trad != src:
|
||||
row['translation'] = trad
|
||||
row.setdefault('tnrs', []).append((type, name, res_id))
|
||||
row.setdefault('comments', set()).update(comments)
|
||||
modules.add(module)
|
||||
|
||||
for src, row in sorted(grouped_rows.items()):
|
||||
if not self.lang:
|
||||
# translation template, so no translation value
|
||||
row['translation'] = ''
|
||||
elif not row.get('translation'):
|
||||
row['translation'] = ''
|
||||
self.add_entry(row['modules'], sorted(row['tnrs']), src, row['translation'], row['comments'])
|
||||
|
||||
import odoo.release as release
|
||||
self.po.header = "Translation of %s.\n" \
|
||||
"This file contains the translation of the following modules:\n" \
|
||||
"%s" % (release.description, ''.join("\t* %s\n" % m for m in modules))
|
||||
@@ -701,25 +722,6 @@ class PoFileWriter:
|
||||
'Plural-Forms': '',
|
||||
}
|
||||
|
||||
def write_rows(self, rows):
|
||||
# we now group the translations by source. That means one translation per source.
|
||||
grouped_rows = {}
|
||||
for module, type, name, res_id, src, trad, comments in rows:
|
||||
row = grouped_rows.setdefault(src, {})
|
||||
row.setdefault('modules', set()).add(module)
|
||||
if not row.get('translation') and trad != src:
|
||||
row['translation'] = trad
|
||||
row.setdefault('tnrs', []).append((type, name, res_id))
|
||||
row.setdefault('comments', set()).update(comments)
|
||||
|
||||
for src, row in sorted(grouped_rows.items()):
|
||||
if not self.lang:
|
||||
# translation template, so no translation value
|
||||
row['translation'] = ''
|
||||
elif not row.get('translation'):
|
||||
row['translation'] = ''
|
||||
self.add_entry(row['modules'], row['tnrs'], src, row['translation'], row['comments'])
|
||||
|
||||
# buffer expects bytes
|
||||
self.buffer.write(str(self.po).encode())
|
||||
|
||||
@@ -763,7 +765,7 @@ class TarFileWriter:
|
||||
|
||||
for mod, modrows in rows_by_module.items():
|
||||
with io.BytesIO() as buf:
|
||||
po = PoFileWriter(buf, modules=[mod], lang=self.lang)
|
||||
po = PoFileWriter(buf, lang=self.lang)
|
||||
po.write_rows(modrows)
|
||||
buf.seek(0)
|
||||
|
||||
@@ -781,14 +783,10 @@ class TarFileWriter:
|
||||
self.tar.close()
|
||||
|
||||
# Methods to export the translation file
|
||||
|
||||
def trans_export(lang, modules, buffer, format, cr):
|
||||
|
||||
translations = trans_generate(lang, modules, cr)
|
||||
modules = set(t[0] for t in translations)
|
||||
writer = TranslationFileWriter(buffer, fileformat=format, lang=lang, modules=modules)
|
||||
writer.write_rows(translations)
|
||||
del translations
|
||||
reader = TranslationModuleReader(cr, modules=modules, lang=lang)
|
||||
writer = TranslationFileWriter(buffer, fileformat=format, lang=lang)
|
||||
writer.write_rows(reader)
|
||||
|
||||
|
||||
def trans_parse_rml(de):
|
||||
@@ -872,12 +870,44 @@ def babel_extract_qweb(fileobj, keywords, comment_tags, options):
|
||||
_extract_translatable_qweb_terms(tree.getroot(), handle_text)
|
||||
return result
|
||||
|
||||
ImdInfo = namedtuple('ExternalId', ['name', 'model', 'res_id', 'module'])
|
||||
|
||||
def trans_generate(lang, modules, cr):
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
to_translate = set()
|
||||
|
||||
def push_translation(module, type, name, id, source, comments=None):
|
||||
class TranslationModuleReader:
|
||||
""" Retrieve translated records per module
|
||||
|
||||
:param cr: cursor to database to export
|
||||
:param modules: list of modules to filter the exported terms, can be ['all']
|
||||
records with no external id are always ignored
|
||||
:param lang: language code to retrieve the translations
|
||||
retrieve source terms only if not set
|
||||
"""
|
||||
|
||||
def __init__(self, cr, modules=None, lang=None):
|
||||
self._cr = cr
|
||||
self._modules = modules or ['all']
|
||||
self._lang = lang
|
||||
self.env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
self._to_translate = []
|
||||
self._path_list = [(path, True) for path in odoo.addons.__path__]
|
||||
self._installed_modules = [
|
||||
m['name']
|
||||
for m in self.env['ir.module.module'].search_read([('state', '=', 'installed')], fields=['name'])
|
||||
]
|
||||
|
||||
self._export_translatable_records()
|
||||
self._export_translatable_resources()
|
||||
|
||||
|
||||
def __iter__(self):
|
||||
""" Export ir.translation values for all retrieved records """
|
||||
|
||||
IrTranslation = self.env['ir.translation']
|
||||
for module, source, name, res_id, ttype, comments in self._to_translate:
|
||||
trans = IrTranslation._get_source(name, ttype, self._lang, source) if self._lang else ""
|
||||
yield (module, ttype, name, res_id, source, encode(trans) or '', comments)
|
||||
|
||||
def _push_translation(self, module, ttype, name, res_id, source, comments=None):
|
||||
# empty and one-letter terms are ignored, they probably are not meant to be
|
||||
# translated, and would be very hard to translate anyway.
|
||||
sanitized_term = (source or '').strip()
|
||||
@@ -886,84 +916,98 @@ def trans_generate(lang, modules, cr):
|
||||
if not sanitized_term or len(sanitized_term) <= 1:
|
||||
return
|
||||
|
||||
tnx = (module, source, name, id, type, tuple(comments or ()))
|
||||
to_translate.add(tnx)
|
||||
self._to_translate.append((module, source, name, res_id, ttype, tuple(comments or ())))
|
||||
|
||||
def translatable_model(record):
|
||||
if not record._translate:
|
||||
return False
|
||||
def _get_translatable_records(self, records):
|
||||
""" Filter the records that are translatable
|
||||
|
||||
if record._name == 'ir.model.fields.selection':
|
||||
record = record.field_id
|
||||
if record._name == 'ir.model.fields':
|
||||
field_name = record.name
|
||||
field_model = env.get(record.model)
|
||||
if (field_model is None or not field_model._translate or
|
||||
field_name not in field_model._fields):
|
||||
return False
|
||||
A record is considered as untranslatable if:
|
||||
- it does not exist
|
||||
- the model is flagged with _translate=False
|
||||
- it is a field of a model flagged with _translate=False
|
||||
- it is a selection of a field of a model flagged with _translate=False
|
||||
|
||||
return True
|
||||
:param records: a list of namedtuple ImdInfo belonging to the same model
|
||||
"""
|
||||
model = next(iter(records)).model
|
||||
if model not in self.env:
|
||||
_logger.error("Unable to find object %r", model)
|
||||
return self.browse()
|
||||
|
||||
query = 'SELECT min(name), model, res_id, module FROM ir_model_data'
|
||||
if not self.env[model]._translate:
|
||||
return self.env[model].browse()
|
||||
|
||||
if 'all_installed' in modules:
|
||||
query += ' WHERE module IN ( SELECT name FROM ir_module_module WHERE state = \'installed\') '
|
||||
res_ids = [r.res_id for r in records]
|
||||
records = self.env[model].browse(res_ids).exists()
|
||||
if len(records) < len(res_ids):
|
||||
missing_ids = set(records.ids) - set(res_ids)
|
||||
_logger.warning("Unable to find objects %r with id %d", model, ', '.join(missing_ids))
|
||||
if not records:
|
||||
return records
|
||||
|
||||
if 'all' not in modules:
|
||||
query += ' WHERE module IN %s'
|
||||
query_param = (tuple(modules),)
|
||||
else:
|
||||
query += ' WHERE module != %s'
|
||||
query_param = ('__export__',)
|
||||
if model == 'ir.model.fields.selection':
|
||||
fields = defaultdict(list)
|
||||
for selection in records:
|
||||
fields[selection.field_id] = selection
|
||||
for field, selection in fields.items():
|
||||
field_name = field.name
|
||||
field_model = self.env.get(field.model)
|
||||
if (field_model is None or not field_model._translate or
|
||||
field_name not in field_model._fields):
|
||||
# the selection is linked to a model with _translate=False, remove it
|
||||
records -= selection
|
||||
elif model == 'ir.model.fields':
|
||||
for field in records:
|
||||
field_name = field.name
|
||||
field_model = self.env.get(field.model)
|
||||
if (field_model is None or not field_model._translate or
|
||||
field_name not in field_model._fields):
|
||||
# the field is linked to a model with _translate=False, remove it
|
||||
records -= field
|
||||
|
||||
query += ' GROUP BY model, res_id, module ORDER BY module, model, min(name)'
|
||||
return records
|
||||
|
||||
cr.execute(query, query_param)
|
||||
|
||||
for (xml_name, model, res_id, module) in cr.fetchall():
|
||||
xml_name = "%s.%s" % (module, xml_name)
|
||||
def _export_translatable_records(self):
|
||||
""" Export translations of all translated records having an external id """
|
||||
|
||||
if model not in env:
|
||||
_logger.error(u"Unable to find object %r", model)
|
||||
continue
|
||||
query = """SELECT min(name), model, res_id, module
|
||||
FROM ir_model_data
|
||||
WHERE module = ANY(%s)
|
||||
GROUP BY model, res_id, module
|
||||
ORDER BY module, model, min(name)"""
|
||||
|
||||
record = env[model].browse(res_id)
|
||||
if not record.exists():
|
||||
_logger.warning(u"Unable to find object %r with id %d", model, res_id)
|
||||
continue
|
||||
if 'all' not in self._modules:
|
||||
query_param = list(self._modules)
|
||||
else:
|
||||
query_param = self._installed_modules
|
||||
|
||||
if not translatable_model(record):
|
||||
continue
|
||||
self._cr.execute(query, (query_param,))
|
||||
|
||||
for field_name, field in record._fields.items():
|
||||
if field.translate:
|
||||
name = model + "," + field_name
|
||||
try:
|
||||
value = record[field_name] or ''
|
||||
except Exception:
|
||||
continue
|
||||
for term in set(field.get_trans_terms(value)):
|
||||
trans_type = 'model_terms' if callable(field.translate) else 'model'
|
||||
push_translation(module, trans_type, name, xml_name, term)
|
||||
records_per_model = defaultdict(dict)
|
||||
for (xml_name, model, res_id, module) in self._cr.fetchall():
|
||||
records_per_model[model][res_id] = ImdInfo(xml_name, model, res_id, module)
|
||||
|
||||
# End of data for ir.model.data query results
|
||||
for model, imd_per_id in records_per_model.items():
|
||||
records = self._get_translatable_records(imd_per_id.values())
|
||||
if not records:
|
||||
continue
|
||||
|
||||
installed_modules = [
|
||||
m['name']
|
||||
for m in env['ir.module.module'].search_read([('state', '=', 'installed')], fields=['name'])
|
||||
]
|
||||
for record in records:
|
||||
xml_name = "%s.%s" % (imd_per_id[record.id].module, imd_per_id[record.id].name)
|
||||
for field_name, field in record._fields.items():
|
||||
if field.translate:
|
||||
name = model + "," + field_name
|
||||
try:
|
||||
value = record[field_name] or ''
|
||||
except Exception:
|
||||
continue
|
||||
for term in set(field.get_trans_terms(value)):
|
||||
trans_type = 'model_terms' if callable(field.translate) else 'model'
|
||||
self._push_translation(module, trans_type, name, xml_name, term)
|
||||
|
||||
path_list = [(path, True) for path in odoo.addons.__path__]
|
||||
# Also scan these non-addon paths
|
||||
for bin_path in ['osv', 'report', 'modules', 'service', 'tools']:
|
||||
path_list.append((os.path.join(config['root_path'], bin_path), True))
|
||||
# non-recursive scan for individual files in root directory but without
|
||||
# scanning subdirectories that may contain addons
|
||||
path_list.append((config['root_path'], False))
|
||||
_logger.debug("Scanning modules at paths: %s", path_list)
|
||||
|
||||
def get_module_from_path(path):
|
||||
for (mp, rec) in path_list:
|
||||
def _get_module_from_path(self, path):
|
||||
for (mp, rec) in self._path_list:
|
||||
mp = os.path.join(mp, '')
|
||||
dirname = os.path.join(os.path.dirname(path), '')
|
||||
if rec and path.startswith(mp) and dirname != mp:
|
||||
@@ -971,22 +1015,24 @@ def trans_generate(lang, modules, cr):
|
||||
return path.split(os.path.sep)[0]
|
||||
return 'base' # files that are not in a module are considered as being in 'base' module
|
||||
|
||||
def verified_module_filepaths(fname, path, root):
|
||||
def _verified_module_filepaths(self, fname, path, root):
|
||||
fabsolutepath = join(root, fname)
|
||||
frelativepath = fabsolutepath[len(path):]
|
||||
display_path = "addons%s" % frelativepath
|
||||
module = get_module_from_path(fabsolutepath)
|
||||
if ('all' in modules or module in modules) and module in installed_modules:
|
||||
module = self._get_module_from_path(fabsolutepath)
|
||||
if ('all' in self._modules or module in self._modules) and module in self._installed_modules:
|
||||
if os.path.sep != '/':
|
||||
display_path = display_path.replace(os.path.sep, '/')
|
||||
return module, fabsolutepath, frelativepath, display_path
|
||||
return None, None, None, None
|
||||
|
||||
def babel_extract_terms(fname, path, root, extract_method="python", trans_type='code',
|
||||
def _babel_extract_terms(self, fname, path, root, extract_method="python", trans_type='code',
|
||||
extra_comments=None, extract_keywords={'_': None}):
|
||||
module, fabsolutepath, _, display_path = verified_module_filepaths(fname, path, root)
|
||||
|
||||
module, fabsolutepath, _, display_path = self._verified_module_filepaths(fname, path, root)
|
||||
if not module:
|
||||
return
|
||||
extra_comments = extra_comments or []
|
||||
if not module: return
|
||||
src_file = open(fabsolutepath, 'rb')
|
||||
options = {}
|
||||
if extract_method == 'python':
|
||||
@@ -996,76 +1042,98 @@ def trans_generate(lang, modules, cr):
|
||||
# Babel 0.9.6 yields lineno, message, comments
|
||||
# Babel 1.3 yields lineno, message, comments, context
|
||||
lineno, message, comments = extracted[:3]
|
||||
push_translation(module, trans_type, display_path, lineno,
|
||||
self._push_translation(module, trans_type, display_path, lineno,
|
||||
encode(message), comments + extra_comments)
|
||||
except Exception:
|
||||
_logger.exception("Failed to extract terms from %s", fabsolutepath)
|
||||
finally:
|
||||
src_file.close()
|
||||
|
||||
for (path, recursive) in path_list:
|
||||
_logger.debug("Scanning files of modules at %s", path)
|
||||
for root, dummy, files in os.walk(path, followlinks=True):
|
||||
for fname in fnmatch.filter(files, '*.py'):
|
||||
babel_extract_terms(fname, path, root,
|
||||
extract_keywords={'_': None, '_lt': None})
|
||||
# Javascript source files in the static/src/js directory, rest is ignored (libs)
|
||||
if fnmatch.fnmatch(root, '*/static/src/js*'):
|
||||
for fname in fnmatch.filter(files, '*.js'):
|
||||
babel_extract_terms(fname, path, root, 'javascript',
|
||||
extra_comments=[WEB_TRANSLATION_COMMENT],
|
||||
extract_keywords={'_t': None, '_lt': None})
|
||||
# QWeb template files
|
||||
if fnmatch.fnmatch(root, '*/static/src/xml*'):
|
||||
for fname in fnmatch.filter(files, '*.xml'):
|
||||
babel_extract_terms(fname, path, root, 'odoo.tools.translate:babel_extract_qweb',
|
||||
extra_comments=[WEB_TRANSLATION_COMMENT])
|
||||
if not recursive:
|
||||
# due to topdown, first iteration is in first level
|
||||
break
|
||||
def _export_translatable_resources(self):
|
||||
""" Export translations for static terms
|
||||
|
||||
This will include:
|
||||
- the python strings marked with _() or _lt()
|
||||
- the javascript strings marked with _t() or _lt() inside static/src/js/
|
||||
- the strings inside Qweb files inside static/src/xml/
|
||||
"""
|
||||
|
||||
out = []
|
||||
# translate strings marked as to be translated
|
||||
Translation = env['ir.translation']
|
||||
for module, source, name, id, type, comments in sorted(to_translate):
|
||||
trans = Translation._get_source(name, type, lang, source) if lang else ""
|
||||
out.append((module, type, name, id, source, encode(trans) or '', comments))
|
||||
return out
|
||||
# Also scan these non-addon paths
|
||||
for bin_path in ['osv', 'report', 'modules', 'service', 'tools']:
|
||||
self._path_list.append((os.path.join(config['root_path'], bin_path), True))
|
||||
# non-recursive scan for individual files in root directory but without
|
||||
# scanning subdirectories that may contain addons
|
||||
self._path_list.append((config['root_path'], False))
|
||||
_logger.debug("Scanning modules at paths: %s", self._path_list)
|
||||
|
||||
for (path, recursive) in self._path_list:
|
||||
_logger.debug("Scanning files of modules at %s", path)
|
||||
for root, dummy, files in os.walk(path, followlinks=True):
|
||||
for fname in fnmatch.filter(files, '*.py'):
|
||||
self._babel_extract_terms(fname, path, root,
|
||||
extract_keywords={'_': None, '_lt': None})
|
||||
# Javascript source files in the static/src/js directory, rest is ignored (libs)
|
||||
if fnmatch.fnmatch(root, '*/static/src/js*'):
|
||||
for fname in fnmatch.filter(files, '*.js'):
|
||||
self._babel_extract_terms(fname, path, root, 'javascript',
|
||||
extra_comments=[WEB_TRANSLATION_COMMENT],
|
||||
extract_keywords={'_t': None, '_lt': None})
|
||||
# QWeb template files
|
||||
if fnmatch.fnmatch(root, '*/static/src/xml*'):
|
||||
for fname in fnmatch.filter(files, '*.xml'):
|
||||
self._babel_extract_terms(fname, path, root, 'odoo.tools.translate:babel_extract_qweb',
|
||||
extra_comments=[WEB_TRANSLATION_COMMENT])
|
||||
if not recursive:
|
||||
# due to topdown, first iteration is in first level
|
||||
break
|
||||
|
||||
|
||||
def trans_load(cr, filename, lang, verbose=True, module_name=None, context=None):
|
||||
def trans_load(cr, filename, lang, verbose=True, create_empty_translation=False, overwrite=False):
|
||||
try:
|
||||
with file_open(filename, mode='rb') as fileobj:
|
||||
_logger.info("loading %s", filename)
|
||||
fileformat = os.path.splitext(filename)[-1][1:].lower()
|
||||
result = trans_load_data(cr, fileobj, fileformat, lang, verbose=verbose, module_name=module_name, context=context)
|
||||
return result
|
||||
return trans_load_data(cr, fileobj, fileformat, lang,
|
||||
verbose=verbose,
|
||||
create_empty_translation=create_empty_translation,
|
||||
overwrite=overwrite)
|
||||
except IOError:
|
||||
if verbose:
|
||||
_logger.error("couldn't read translation file %s", filename)
|
||||
return None
|
||||
|
||||
|
||||
def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True, module_name=None, context=None):
|
||||
"""Populates the ir_translation table."""
|
||||
def trans_load_data(cr, fileobj, fileformat, lang,
|
||||
verbose=True, create_empty_translation=False, overwrite=False):
|
||||
"""Populates the ir_translation table.
|
||||
|
||||
:param fileobj: buffer open to a translation file
|
||||
:param fileformat: format of the `fielobj` file, one of 'po' or 'csv'
|
||||
:param lang: language code of the translations contained in `fileobj`
|
||||
language must be present and activated in the database
|
||||
:param verbose: increase log output
|
||||
:param create_empty_translation: create an ir.translation record, even if no value
|
||||
is provided in the translation entry
|
||||
:param overwrite: if an ir.translation already exists for a term, replace it with
|
||||
the one in `fileobj`
|
||||
"""
|
||||
if verbose:
|
||||
_logger.info('loading translation file for language %s', lang)
|
||||
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, context or {})
|
||||
Lang = env['res.lang']
|
||||
Translation = env['ir.translation']
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
|
||||
try:
|
||||
if not Lang.search_count([('code', '=', lang)]):
|
||||
# lets create the language with locale information
|
||||
Lang.load_lang(lang=lang, lang_name=lang_name)
|
||||
if not env['res.lang']._lang_get(lang):
|
||||
_logger.error("Couldn't read translation for lang '%s', language not found", lang)
|
||||
return None
|
||||
|
||||
# now, the serious things: we read the language file
|
||||
fileobj.seek(0)
|
||||
reader = TranslationFileReader(fileobj, fileformat=fileformat)
|
||||
|
||||
# read the rest of the file
|
||||
irt_cursor = Translation._get_import_cursor()
|
||||
# read the rest of the file with a cursor-like object for fast inserting translations"
|
||||
Translation = env['ir.translation']
|
||||
irt_cursor = Translation._get_import_cursor(overwrite)
|
||||
|
||||
def process_row(row):
|
||||
"""Process a single PO (or POT) entry."""
|
||||
@@ -1078,12 +1146,9 @@ def trans_load_data(cr, fileobj, fileformat, lang, lang_name=None, verbose=True,
|
||||
dic.update(row)
|
||||
|
||||
# do not import empty values
|
||||
if not env.context.get('create_empty_translation', False) and not dic['value']:
|
||||
if not create_empty_translation and not dic['value']:
|
||||
return
|
||||
|
||||
if dic['type'] == 'code' and module_name:
|
||||
dic['module'] = module_name
|
||||
|
||||
irt_cursor.push(dic)
|
||||
|
||||
# First process the entries from the PO file (doing so also fills/removes
|
||||
|
||||
Reference in New Issue
Block a user