[IMP] base: allow user to activate multiple languages at once
Purpose ======= Add "Activate" button when selecting multiple languages, select multiple languages when clicking on "Add languages" in settings. Specifications ============= `lang` variable in `base.language.install` changed from Selection to Many2many to allow multiple languages being activated at once. Hide globe icon for language fields from view mode (only visible when editing). Remove state in base.language.install since it's no longer need to keep track of the installation step. Task-2662548 closes odoo/odoo#78287 Related: odoo/upgrade#2921 Signed-off-by: Thibault Delavallee (tde) <tde@openerp.com>
This commit is contained in:
@@ -51,7 +51,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt8">
|
||||
<button name="%(base.action_view_base_language_install)d" icon="fa-arrow-right" type="action" string="Add Language" class="btn-link"/>
|
||||
<button name="%(base.action_view_base_language_install)d" icon="fa-arrow-right" type="action" string="Add Languages" class="btn-link"/>
|
||||
</div>
|
||||
<div class="mt8" groups="base.group_no_one">
|
||||
<button name="%(base.res_lang_act_window)d" icon="fa-arrow-right" type="action" string="Manage Languages" class="btn-link"/>
|
||||
|
||||
@@ -129,11 +129,11 @@
|
||||
<field name="lang_code" invisible="1"/>
|
||||
<label for="lang_id" attrs="{'invisible': [('lang_active_count', '<=', 1)]}"/>
|
||||
<div class="o_row" attrs="{'invisible': [('lang_active_count', '<=', 1)]}">
|
||||
<field name="lang_id" options="{'no_quick_create':True,'no_create_edit':True,'no_open': True,}"/>
|
||||
<field name="lang_id" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||
<button
|
||||
type="action"
|
||||
name="%(base.res_lang_act_window)d"
|
||||
class="btn-sm btn-link mb4 fa fa-globe"
|
||||
class="oe_edit_only btn-sm btn-link mb4 fa fa-globe"
|
||||
aria-label="More languages"
|
||||
groups="base.group_system"
|
||||
title="More languages"
|
||||
@@ -309,11 +309,11 @@
|
||||
<field name="lang_active_count" invisible="1"/>
|
||||
<label for="lang_id" attrs="{'invisible': [('lang_active_count', '<=', 1)]}"/>
|
||||
<div class="o_row" attrs="{'invisible': [('lang_active_count', '<=', 1)]}">
|
||||
<field name="lang_id" options="{'no_quick_create':True,'no_create_edit':True,'no_open': True,}"/>
|
||||
<field name="lang_id" options="{'no_quick_create': True, 'no_create_edit': True, 'no_open': True}"/>
|
||||
<button
|
||||
type="action"
|
||||
name="%(base.res_lang_act_window)d"
|
||||
class="btn-sm btn-link mb4 fa fa-globe"
|
||||
class="btn-sm btn-link mb4 oe_edit_only fa fa-globe"
|
||||
aria-label="More languages"
|
||||
groups="base.group_system"
|
||||
title="More languages"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
from collections import defaultdict
|
||||
import logging
|
||||
|
||||
from odoo import api, models
|
||||
from odoo import models
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -151,26 +151,30 @@ class BaseLanguageInstall(models.TransientModel):
|
||||
|
||||
def lang_install(self):
|
||||
self.ensure_one()
|
||||
already_installed = self.lang in [code for code, _ in self.env['res.lang'].get_installed()]
|
||||
res = super(BaseLanguageInstall, self).lang_install()
|
||||
if already_installed:
|
||||
lang_codes = set(self.lang_ids.mapped('code'))
|
||||
installed = {code for code, __ in self.env['res.lang'].get_installed()}
|
||||
to_install = lang_codes - installed
|
||||
if not to_install:
|
||||
# update of translations instead of new installation
|
||||
# skip to avoid duplicating the translations
|
||||
return res
|
||||
|
||||
# CoA in multilang mode
|
||||
for coa in self.env['account.chart.template'].search([('spoken_languages', '!=', False)]):
|
||||
if self.lang in coa.spoken_languages.split(';'):
|
||||
coa_langs_codes = to_install & set(coa.spoken_languages.split(';'))
|
||||
if coa_langs_codes:
|
||||
# companies on which it is installed
|
||||
for company in self.env['res.company'].search([('chart_template_id', '=', coa.id)]):
|
||||
# write account.account translations in the real COA
|
||||
coa._process_accounts_translations(company.id, [self.lang], 'name')
|
||||
coa._process_accounts_translations(company.id, coa_langs_codes, 'name')
|
||||
# write account.group translations
|
||||
coa._process_account_group_translations(company.id, [self.lang], 'name')
|
||||
coa._process_account_group_translations(company.id, coa_langs_codes, 'name')
|
||||
# copy account.tax name translations
|
||||
coa._process_taxes_translations(company.id, [self.lang], 'name')
|
||||
coa._process_taxes_translations(company.id, coa_langs_codes, 'name')
|
||||
# copy account.tax description translations
|
||||
coa._process_taxes_translations(company.id, [self.lang], 'description')
|
||||
coa._process_taxes_translations(company.id, coa_langs_codes, 'description')
|
||||
# copy account.fiscal.position translations
|
||||
coa._process_fiscal_pos_translations(company.id, [self.lang], 'name')
|
||||
coa._process_fiscal_pos_translations(company.id, coa_langs_codes, 'name')
|
||||
|
||||
return res
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import api, models, tools, _
|
||||
from odoo.addons.website.models import ir_http
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.http import request
|
||||
|
||||
@@ -21,3 +20,17 @@ class Lang(models.Model):
|
||||
if request and getattr(request, 'is_frontend', True):
|
||||
return self.env['website'].get_current_website().language_ids.get_sorted()
|
||||
return super().get_available()
|
||||
|
||||
def action_activate_langs(self):
|
||||
"""
|
||||
Open wizard to install language(s), so user can select the website(s)
|
||||
to translate in that language.
|
||||
"""
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Add languages'),
|
||||
'view_mode': 'form',
|
||||
'res_model': 'base.language.install',
|
||||
'views': [[False, 'form']],
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
@@ -17,15 +17,28 @@ tour.register('automatic_editor_on_new_website', {
|
||||
trigger: 'a.o_add_language',
|
||||
},
|
||||
{
|
||||
content: "Select dropdown",
|
||||
trigger: 'select[name=lang]',
|
||||
run: () => {
|
||||
$('select[name="lang"]').val('"pa_GB"').change();
|
||||
}
|
||||
content: "type Parseltongue",
|
||||
trigger: 'div[name="lang_ids"] .o_input_dropdown input',
|
||||
run: 'text Parseltongue',
|
||||
},
|
||||
{
|
||||
content: 'select Parseltongue',
|
||||
trigger: 'div[name="lang_ids"] .o_input_dropdown input',
|
||||
extra_trigger: '.dropdown-item:contains(Parseltongue)',
|
||||
run: function () {
|
||||
// The dropdown element is outside the action manager, and can therefor not
|
||||
// be selected directly by "trigger".
|
||||
// That's why we need to simulate the click in JS
|
||||
const element = $('.dropdown-item:contains(Parseltongue)');
|
||||
if (!element.length) {
|
||||
console.error('Lang not found');
|
||||
}
|
||||
element.click();
|
||||
},
|
||||
},
|
||||
{
|
||||
content: "load parseltongue",
|
||||
extra_trigger: '.modal select[name="lang"]:propValueContains(pa_GB)',
|
||||
extra_trigger: '.modal div[name="lang_ids"] .badge-pill .o_tag_badge_text:contains(Parseltongue)',
|
||||
trigger: '.modal-footer button:first',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -21,13 +21,27 @@ tour.register('rte_translator', {
|
||||
content: "click on Add a language",
|
||||
trigger: 'a.o_add_language',
|
||||
}, {
|
||||
content: "select Parseltongue",
|
||||
trigger: 'select[name="lang"]',
|
||||
run: 'text "pa_GB"',
|
||||
content: "type Parseltongue",
|
||||
trigger: 'div[name="lang_ids"] .o_input_dropdown input',
|
||||
run: 'text Parseltongue',
|
||||
}, {
|
||||
content: 'select Parseltongue',
|
||||
trigger: 'div[name="lang_ids"] .o_input_dropdown input',
|
||||
extra_trigger: '.dropdown-item:contains(Parseltongue)',
|
||||
run: function () {
|
||||
// The dropdown element is outside the action manager, and can therefor not
|
||||
// be selected directly by "trigger".
|
||||
// That's why we need to simulate the click in JS
|
||||
const element = $('.dropdown-item:contains(Parseltongue)');
|
||||
if (!element.length) {
|
||||
console.error('Lang not found');
|
||||
}
|
||||
element.click();
|
||||
},
|
||||
}, {
|
||||
content: "load Parseltongue",
|
||||
trigger: '.modal-footer button:first',
|
||||
extra_trigger: '.modal select[name="lang"]:propValueContains(pa_GB)',
|
||||
extra_trigger: '.modal div[name="lang_ids"] .badge-pill .o_tag_badge_text:contains(Parseltongue)',
|
||||
}, {
|
||||
content: "click language dropdown (2)",
|
||||
trigger: '.js_language_selector .dropdown-toggle',
|
||||
|
||||
@@ -57,7 +57,10 @@ class TestConfiguratorTranslation(TestConfiguratorCommon):
|
||||
|
||||
def test_01_configurator_translation(self):
|
||||
with mute_logger('odoo.addons.base.models.ir_translation'):
|
||||
self.env["base.language.install"].create({'lang': 'fr_FR', 'overwrite': True}).lang_install()
|
||||
self.env["base.language.install"].create({
|
||||
'overwrite': True,
|
||||
'lang_ids': [(6, 0, [self.env.ref('base.lang_fr').id])],
|
||||
}).lang_install()
|
||||
feature = self.env['website.configurator.feature'].search([('name', '=', 'Privacy Policy')])
|
||||
feature.with_context(lang='fr_FR').write({'name': 'Politique de confidentialité'})
|
||||
website_fr = self.env['website'].create({
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<field name="website_default_lang_id" options="{'no_open': True, 'no_create': True}" attrs="{'required': [('website_id', '!=', False)]}"/>
|
||||
</div>
|
||||
<div class="mt8">
|
||||
<button type="action" name="%(base.action_view_base_language_install)d" string="Install a language" class="btn-link" icon="fa-arrow-right"/>
|
||||
<button type="action" name="%(base.action_view_base_language_install)d" string="Install languages" class="btn-link" icon="fa-arrow-right"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,13 +22,12 @@ class BaseLanguageInstall(models.TransientModel):
|
||||
|
||||
def lang_install(self):
|
||||
action = super(BaseLanguageInstall, self).lang_install()
|
||||
lang = self.env['res.lang']._lang_get(self.lang)
|
||||
if self.website_ids and lang:
|
||||
self.website_ids.write({'language_ids': [(4, lang.id)]})
|
||||
if self.website_ids and self.lang_ids:
|
||||
self.website_ids.language_ids |= self.lang_ids
|
||||
params = self._context.get('params', {})
|
||||
if 'url_return' in params:
|
||||
return {
|
||||
'url': params['url_return'].replace('[lang]', self.lang),
|
||||
'url': params['url_return'].replace('[lang]', self.first_lang_id.code),
|
||||
'type': 'ir.actions.act_url',
|
||||
'target': 'self'
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<field name="model">base.language.install</field>
|
||||
<field name="inherit_id" ref="base.view_base_language_install"/>
|
||||
<field name="arch" type="xml">
|
||||
<group states="init" position="inside">
|
||||
<group position="inside">
|
||||
<field name="website_ids" widget="many2many_checkboxes" groups="website.group_multi_website"/>
|
||||
</group>
|
||||
</field>
|
||||
|
||||
@@ -334,6 +334,22 @@ class Lang(models.Model):
|
||||
|
||||
return formatted
|
||||
|
||||
def action_activate_langs(self):
|
||||
""" Activate the selected languages """
|
||||
for lang in self.filtered(lambda l: not l.active):
|
||||
lang.toggle_active()
|
||||
message = _("The languages that you selected have been successfully installed. Users can choose their favorite language in their preferences.")
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'target': 'new',
|
||||
'params': {
|
||||
'message': message,
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
|
||||
def split(l, counts):
|
||||
"""
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<field name="model">res.lang</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Languages" limit="200">
|
||||
<header>
|
||||
<button name="action_activate_langs" type="object" string="Activate"/>
|
||||
</header>
|
||||
<field name="name"/>
|
||||
<field name="code" groups="base.group_no_one"/>
|
||||
<field name="iso_code" groups="base.group_no_one"/>
|
||||
|
||||
@@ -223,7 +223,7 @@
|
||||
<button
|
||||
type="action"
|
||||
name="%(base.res_lang_act_window)d"
|
||||
class="btn-sm btn-link mb4 fa fa-globe"
|
||||
class="oe_edit_only btn-sm btn-link mb4 fa fa-globe"
|
||||
aria-label="More languages"
|
||||
groups="base.group_system"
|
||||
title="More languages"
|
||||
|
||||
@@ -245,7 +245,7 @@
|
||||
<button
|
||||
type="action"
|
||||
name="%(base.res_lang_act_window)d"
|
||||
class="btn-sm btn-link mb4 fa fa-globe"
|
||||
class="oe_edit_only btn-sm btn-link mb4 fa fa-globe"
|
||||
aria-label="More languages"
|
||||
title="More languages"/>
|
||||
</div>
|
||||
@@ -447,7 +447,7 @@
|
||||
<button
|
||||
type="action"
|
||||
name="%(base.res_lang_act_window)d"
|
||||
class="btn-sm btn-link mb4 fa fa-globe"
|
||||
class="oe_edit_only btn-sm btn-link mb4 fa fa-globe"
|
||||
aria-label="More languages"
|
||||
groups="base.group_system"
|
||||
title="More languages"
|
||||
|
||||
@@ -9,45 +9,61 @@ class BaseLanguageInstall(models.TransientModel):
|
||||
_description = "Install Language"
|
||||
|
||||
@api.model
|
||||
def _default_language(self):
|
||||
def _default_lang_ids(self):
|
||||
""" Display the selected language when using the 'Update Terms' action
|
||||
from the language list view
|
||||
"""
|
||||
if self._context.get('active_model') == 'res.lang':
|
||||
lang = self.env['res.lang'].browse(self._context.get('active_id'))
|
||||
return lang.code
|
||||
return self._context.get('active_ids') or [self._context.get('active_id')]
|
||||
return False
|
||||
|
||||
@api.model
|
||||
def _get_languages(self):
|
||||
return [[code, name] for code, _, name, *_ in self.env['res.lang'].get_available()]
|
||||
|
||||
lang = fields.Selection(_get_languages, string='Language', required=True,
|
||||
default=_default_language)
|
||||
# add a context on the field itself, to be sure even inactive langs are displayed
|
||||
lang_ids = fields.Many2many('res.lang', 'res_lang_install_rel',
|
||||
'language_wizard_id', 'lang_id', 'Languages',
|
||||
default=_default_lang_ids, context={'active_test': False})
|
||||
overwrite = fields.Boolean('Overwrite Existing Terms',
|
||||
default=True,
|
||||
help="If you check this box, your customized translations will be overwritten and replaced by the official ones.")
|
||||
state = fields.Selection([('init', 'init'), ('done', 'done')],
|
||||
string='Status', readonly=True, default='init')
|
||||
first_lang_id = fields.Many2one('res.lang',
|
||||
compute='_compute_first_lang_id',
|
||||
help="Used when the user only selects one language and is given the option to switch to it")
|
||||
|
||||
def _compute_first_lang_id(self):
|
||||
self.first_lang_id = False
|
||||
for lang_installer in self.filtered('lang_ids'):
|
||||
lang_installer.first_lang_id = lang_installer.lang_ids[0]
|
||||
|
||||
def lang_install(self):
|
||||
self.ensure_one()
|
||||
mods = self.env['ir.module.module'].search([('state', '=', 'installed')])
|
||||
self.env['res.lang']._activate_lang(self.lang)
|
||||
mods._update_translations(self.lang, self.overwrite)
|
||||
self.state = 'done'
|
||||
lang_ids = self.lang_ids
|
||||
langs_to_activate = lang_ids.filtered(lambda l: not l.active)
|
||||
langs_to_activate.toggle_active()
|
||||
mods._update_translations(lang_ids.mapped('code'), self.overwrite)
|
||||
self.env.cr.execute('ANALYZE ir_translation')
|
||||
|
||||
if len(lang_ids) == 1:
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'base.language.install',
|
||||
'res_id': self.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'views': [[self.env.ref('base.language_install_view_form_lang_switch').id, 'form']],
|
||||
}
|
||||
|
||||
return {
|
||||
'name': _('Language Pack'),
|
||||
'view_mode': 'form',
|
||||
'view_id': False,
|
||||
'res_model': 'base.language.install',
|
||||
'domain': [],
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'display_notification',
|
||||
'context': dict(self._context, active_ids=self.ids),
|
||||
'type': 'ir.actions.act_window',
|
||||
'target': 'new',
|
||||
'res_id': self.id,
|
||||
'params': {
|
||||
'message': _("The languages that you selected have been successfully installed.\
|
||||
Users can choose their favorite language in their preferences."),
|
||||
'type': 'success',
|
||||
'sticky': False,
|
||||
'next': {'type': 'ir.actions.act_window_close'},
|
||||
}
|
||||
}
|
||||
|
||||
def reload(self):
|
||||
@@ -57,7 +73,7 @@ class BaseLanguageInstall(models.TransientModel):
|
||||
}
|
||||
|
||||
def switch_lang(self):
|
||||
self.env.user.lang = self.lang
|
||||
self.env.user.lang = self.first_lang_id.code
|
||||
return {
|
||||
'type': 'ir.actions.client',
|
||||
'tag': 'reload_context',
|
||||
|
||||
@@ -2,34 +2,48 @@
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="base.language_install_view_form_lang_switch" model="ir.ui.view">
|
||||
<field name="name">Switch to language</field>
|
||||
<field name="model">base.language.install</field>
|
||||
<field name="priority">100</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Switch to language">
|
||||
<group>
|
||||
<span class="o_form_label">
|
||||
<strong><field name="first_lang_id" readonly="True" options="{'no_open': True}"/></strong> has been successfully installed.
|
||||
Users can choose their favorite language in their preferences.
|
||||
</span>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="reload" string="Close" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button name="switch_lang" type="object" class="btn-primary ml-1" data-hotkey="w">
|
||||
Switch to <field name="first_lang_id" class="text-white" style="pointer-events: none;"/> & Close
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_base_language_install" model="ir.ui.view">
|
||||
<field name="name">Load a Translation</field>
|
||||
<field name="model">base.language.install</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Load a Translation">
|
||||
<field name="state" invisible="1"/>
|
||||
<group states="init">
|
||||
<field name="lang"/>
|
||||
<group>
|
||||
<field name="lang_ids" widget="many2many_tags"
|
||||
context="{'active_test': False}" options="{'no_quick_create': True, 'no_create_edit': True}"/>
|
||||
<field name="overwrite" groups="base.group_no_one"/>
|
||||
</group>
|
||||
<group states="done" colspan="4">
|
||||
<span class="o_form_label"><strong><field name="lang" readonly="True" force_save="1"/></strong> has been successfully installed.
|
||||
Users can choose his favorite language in their preferences.</span>
|
||||
</group>
|
||||
<footer states="init">
|
||||
<footer>
|
||||
<button name="lang_install" string="Add" type="object" class="btn-primary"/>
|
||||
<button special="cancel" data-hotkey="z" string="Cancel" class="btn-secondary"/>
|
||||
</footer>
|
||||
<footer states="done">
|
||||
<button name="reload" string="Close" type="object" class="btn-primary" data-hotkey="q"/>
|
||||
<button name="switch_lang" type="object" class="btn-primary" data-hotkey="w">Switch to <field name="lang" readonly="True" force_save="1"/> & Close</button>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_base_language_install" model="ir.actions.act_window">
|
||||
<field name="name">Add Language</field>
|
||||
<field name="name">Add Languages</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">base.language.install</field>
|
||||
<field name="view_mode">form</field>
|
||||
|
||||
@@ -348,7 +348,11 @@ class TestTranslationFlow(common.TransactionCase):
|
||||
""" Ensure export+import gives the same result as loading a language """
|
||||
# load language and generate missing terms to create missing empty terms
|
||||
with mute_logger('odoo.addons.base.models.ir_translation'):
|
||||
self.env["base.language.install"].create({'lang': 'fr_FR', 'overwrite': True}).lang_install()
|
||||
self.env["base.language.install"].create({
|
||||
'overwrite': True,
|
||||
'lang_ids': [(6, 0, [self.env.ref('base.lang_fr').id])],
|
||||
}).lang_install()
|
||||
|
||||
self.env["base.update.translations"].create({'lang': 'fr_FR'}).act_update()
|
||||
|
||||
translations = self.env["ir.translation"].search([
|
||||
|
||||
@@ -1255,5 +1255,5 @@ def load_language(cr, lang):
|
||||
l10n flavor (ex: 'fr', 'fr_BE', but not 'fr-BE')
|
||||
"""
|
||||
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
|
||||
installer = env['base.language.install'].create({'lang': lang})
|
||||
installer = env['base.language.install'].create({'lang_ids': [(6, 0, [env['res.lang'].search([('code', '=', lang)]).id])]})
|
||||
installer.lang_install()
|
||||
|
||||
Reference in New Issue
Block a user