[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:
Fabio Barbero
2022-03-14 08:42:58 +00:00
parent f953e61ba6
commit 47041f2d45
18 changed files with 171 additions and 72 deletions
@@ -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"/>
+4 -4
View File
@@ -129,11 +129,11 @@
<field name="lang_code" invisible="1"/>
<label for="lang_id" attrs="{'invisible': [('lang_active_count', '&lt;=', 1)]}"/>
<div class="o_row" attrs="{'invisible': [('lang_active_count', '&lt;=', 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', '&lt;=', 1)]}"/>
<div class="o_row" attrs="{'invisible': [('lang_active_count', '&lt;=', 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"
+13 -9
View File
@@ -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
+14 -1
View File
@@ -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',
},
{
+18 -4
View File
@@ -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',
+4 -1
View File
@@ -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>
+16
View File
@@ -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"/>
+1 -1
View File
@@ -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"
+2 -2
View File
@@ -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;"/> &amp; 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"/> &amp; 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([
+1 -1
View File
@@ -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()