[FIX] base: lang should fallback on english instead of arab

Install a database with many langs, arab, french, english, ... Keep
english as the default lang. Start a shell and validate a sale-order
using the superuser. On the web client, the sale order has been
validated in arab instead of in english.

In 16.0 the `context_get` method was changed to ensure there was always
a lang set in the returned context. It used the following fallback
order: context > request. The solution was partial because in case there
was no request to extract a lang from, no lang was set on the context.

In a recent 16.0 fix (f2523c4a), the mechanism was changed to fix the
previous problem. The fallback order became: context > request > first
installed lang. This solution is sub-optimal because the first installed
lang isn't always the best pick. e.g. when you have a mostly english
company but that arab is installed for some website pages, arab is
selected instead of english (the langs are alphabetically sorted)

In this work, the fallback order is changed once again:

  1. The lang set on the user's profile if activated
  2. The best lang extracted from the user's browser if activated
  3. (new) The lang of the user's current company if activated
  4. (new) English if activated
  5. The first lang (ordered by ISO code) if any
  6. English

The 3rd should cover most of ill-cases. For the 4th step, we assume that
english is prioritaty to other installed langs when no lang standout.

closes odoo/odoo#113186

X-original-commit: 03134bf7cb1e3d63f3be435fbc734e6198ca029b
Signed-off-by: Julien Castiaux (juc) <juc@odoo.com>
This commit is contained in:
Julien Castiaux
2023-02-21 17:02:06 +01:00
parent ae13a2f41e
commit 93b684d3c7
4 changed files with 74 additions and 30 deletions
@@ -52,7 +52,7 @@ class TestEventPerformance(EventPerformanceCase):
batch_size = 20
# simple without type involved + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=3236): # tef 3173 / com 3172
with freeze_time(self.reference_now), self.assertQueryCount(event_user=3377): # tef 3275 / com 3274
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -60,7 +60,7 @@ class TestEventPerformance(EventPerformanceCase):
)
for x in range(batch_size)
]
self.env['event.event'].create(event_values)
self.env['event.event'].with_context(lang='en_US').create(event_values)
@users('event_user')
@warmup
@@ -89,7 +89,7 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=3340): # tef 3277 / com 3276
with freeze_time(self.reference_now), self.assertQueryCount(event_user=3481): # tef 3379 / com 3378
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = [
dict(self.event_base_vals,
@@ -97,7 +97,7 @@ class TestEventPerformance(EventPerformanceCase):
)
for x in range(batch_size)
]
self.env['event.event'].create(event_values)
self.env['event.event'].with_context(lang='en_US').create(event_values)
@users('event_user')
@@ -128,7 +128,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# no type, website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=427): # tef 379 / com 380
with freeze_time(self.reference_now), self.assertQueryCount(event_user=447): # tef 379 / com 427
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
# <div name="event_menu_configuration" groups="base.group_no_one">
@@ -150,7 +150,7 @@ class TestEventPerformance(EventPerformanceCase):
has_social = 'social_menu' in self.env['event.event'] # otherwise view may crash in enterprise
# type and website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=450): # tef 402 / com 408
with freeze_time(self.reference_now), self.assertQueryCount(event_user=470): # tef 402 / com 450
self.env.cr._now = self.reference_now # force create_date to check schedulers
# Require for `website_menu` to be visible
# <div name="event_menu_configuration" groups="base.group_no_one">
@@ -181,13 +181,13 @@ class TestEventPerformance(EventPerformanceCase):
def test_event_create_single_notype_website(self):
""" Test a single event creation """
# simple without type involved + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=234): # tef 228 / com 227
with freeze_time(self.reference_now), self.assertQueryCount(event_user=242): # tef 228 / com 234
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
website_menu=True
)
self.env['event.event'].create([event_values])
self.env['event.event'].with_context(lang='en_US').create([event_values])
@users('event_user')
@warmup
@@ -212,13 +212,13 @@ class TestEventPerformance(EventPerformanceCase):
event_type = self.env['event.type'].browse(self.test_event_type.ids)
# complex with type + website
with freeze_time(self.reference_now), self.assertQueryCount(event_user=262): # tef 256 / com 255
with freeze_time(self.reference_now), self.assertQueryCount(event_user=271): # tef 256 / com 262
self.env.cr._now = self.reference_now # force create_date to check schedulers
event_values = dict(
self.event_base_vals,
event_type_id=event_type.id,
)
self.env['event.event'].create([event_values])
self.env['event.event'].with_context(lang='en_US').create([event_values])
@tagged('event_performance', 'registration_performance', 'post_install', '-at_install')
+13 -6
View File
@@ -693,12 +693,19 @@ class Users(models.Model):
for name, key in name_to_key.items()
}
# ensure the language is set and is compatible with the web client
lang = context.get('lang') or (request and request.default_lang()) or DEFAULT_LANG
if lang == 'ar_AR':
context['lang'] = 'ar'
if lang in babel.core.LOCALE_ALIASES:
context['lang'] = babel.core.LOCALE_ALIASES[lang]
# ensure lang is set and available
# context > request > company > english > any lang installed
langs = [code for code, _ in self.env['res.lang'].get_installed()]
lang = context.get('lang')
if lang not in langs:
lang = request.best_lang if request else None
if lang not in langs:
lang = self.env.user.company_id.partner_id.lang
if lang not in langs:
lang = DEFAULT_LANG
if lang not in langs:
lang = langs[0] if langs else DEFAULT_LANG
context['lang'] = lang
# ensure uid is set
context['uid'] = self.env.uid
+34 -1
View File
@@ -1,9 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from types import SimpleNamespace
from unittest.mock import patch
from odoo.addons.base.models.res_users import is_selection_groups, get_selection_groups, name_selection_groups
from odoo.exceptions import UserError
from odoo.tests.common import TransactionCase, Form, tagged
from odoo.tests.common import Form, TransactionCase, new_test_user, tagged
from odoo.tools import mute_logger
@@ -188,6 +190,37 @@ class TestUsers(TransactionCase):
self.assertTrue(portal_partner_2.exists(), 'Should have kept the partner')
self.assertEqual(asked_deletion_2.state, 'fail', 'Should have marked the deletion as failed')
def test_context_get_lang(self):
self.env['res.lang'].with_context(active_test=False).search([
('code', 'in', ['fr_FR', 'es_ES', 'de_DE', 'en_US'])
]).write({'active': True})
user = new_test_user(self.env, 'jackoneill')
user = user.with_user(user)
user.lang = 'fr_FR'
company = user.company_id.partner_id.sudo()
company.lang = 'de_DE'
request = SimpleNamespace()
request.best_lang = 'es_ES'
request_patch = patch('odoo.addons.base.models.res_users.request', request)
self.addCleanup(request_patch.stop)
request_patch.start()
self.assertEqual(user.context_get()['lang'], 'fr_FR')
self.env.registry.clear_caches()
user.lang = False
self.assertEqual(user.context_get()['lang'], 'es_ES')
self.env.registry.clear_caches()
request_patch.stop()
self.assertEqual(user.context_get()['lang'], 'de_DE')
self.env.registry.clear_caches()
company.lang = False
self.assertEqual(user.context_get()['lang'], 'en_US')
@tagged('post_install', '-at_install')
class TestUsers2(TransactionCase):
+17 -13
View File
@@ -1372,6 +1372,22 @@ class Request:
_cr = cr
@lazy_property
def best_lang(self):
lang = self.httprequest.accept_languages.best
if not lang:
return None
try:
code, territory, _, _ = babel.core.parse_locale(lang, sep='-')
if territory:
lang = f'{code}_{territory}'
else:
lang = babel.core.LOCALE_ALIASES[code]
return lang
except (ValueError, KeyError):
return None
# =====================================================
# Helpers
# =====================================================
@@ -1434,19 +1450,7 @@ class Request:
:returns: Preferred language if specified or 'en_US'
:rtype: str
"""
lang = self.httprequest.accept_languages.best
if not lang:
return DEFAULT_LANG
try:
code, territory, _, _ = babel.core.parse_locale(lang, sep='-')
if territory:
lang = f'{code}_{territory}'
else:
lang = babel.core.LOCALE_ALIASES[code]
return lang
except (ValueError, KeyError):
return DEFAULT_LANG
return self.best_lang or DEFAULT_LANG
def get_http_params(self):
"""