forked from CGM_Public/pretix_original
* Allow to combine language variant with region (fixes #3947, Z#23220951) This only affects babel-based formatting (currently: currencies and phone numbers), **not** Django-based formatting (currently: date and time formats). * Remove tests where I don'T actually know whats right * Fix lookup order
This commit is contained in:
@@ -34,14 +34,13 @@
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from asgiref.local import Local
|
||||
from babel import localedata
|
||||
from django.conf import settings
|
||||
from django.utils import translation
|
||||
from django.utils.formats import date_format, number_format
|
||||
from django.utils.translation import gettext
|
||||
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
|
||||
from i18nfield.fields import ( # noqa
|
||||
I18nCharField, I18nTextarea, I18nTextField, I18nTextInput,
|
||||
)
|
||||
@@ -51,6 +50,9 @@ from i18nfield.strings import LazyI18nString # noqa
|
||||
from i18nfield.utils import I18nJSONEncoder # noqa
|
||||
|
||||
|
||||
_active_region = Local()
|
||||
|
||||
|
||||
class LazyDate:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
@@ -86,6 +88,8 @@ class LazyCurrencyNumber:
|
||||
return self.__str__()
|
||||
|
||||
def __str__(self):
|
||||
from pretix.base.templatetags.money import money_filter
|
||||
|
||||
return money_filter(self.value, self.currency)
|
||||
|
||||
|
||||
@@ -105,14 +109,40 @@ ALLOWED_LANGUAGES = dict(settings.LANGUAGES)
|
||||
|
||||
|
||||
def get_babel_locale():
|
||||
babel_locale = 'en'
|
||||
# Babel, and therefore django-phonenumberfield, do not support our custom locales such das de_Informal
|
||||
if translation.get_language():
|
||||
if localedata.exists(translation.get_language()):
|
||||
babel_locale = translation.get_language()
|
||||
elif localedata.exists(translation.get_language()[:2]):
|
||||
babel_locale = translation.get_language()[:2]
|
||||
return babel_locale
|
||||
# Babel, and therefore also django-phonenumberfield, do not support our custom locales such das de_Informal
|
||||
# Also, this returns best-effort region information for number formatting etc
|
||||
current_language = translation.get_language()
|
||||
current_region = getattr(_active_region, "value", None)
|
||||
|
||||
# Babel only accepts locales that exist on the system. We try combinations in the following order:
|
||||
# language-languageversion-region
|
||||
# language-region
|
||||
# language-languageversion
|
||||
# language
|
||||
# fallback to system default
|
||||
# fallback to english
|
||||
|
||||
try_locales = []
|
||||
if "-" in current_language:
|
||||
lng_parts = current_language.split("-")
|
||||
if current_region:
|
||||
try_locales.append(f"{lng_parts[0]}_{lng_parts[1].title()}_{current_region.upper()}")
|
||||
try_locales.append(f"{lng_parts[0]}_{current_region.upper()}")
|
||||
try_locales.append(f"{lng_parts[0]}_{lng_parts[1].upper()}")
|
||||
try_locales.append(f"{lng_parts[0]}_{lng_parts[1].title()}")
|
||||
try_locales.append(f"{lng_parts[0]}")
|
||||
else:
|
||||
if current_region:
|
||||
try_locales.append(f"{current_language}_{current_region.upper()}")
|
||||
try_locales.append(f"{current_language}")
|
||||
|
||||
try_locales.append(settings.LANGUAGE_CODE)
|
||||
|
||||
for locale in try_locales:
|
||||
if localedata.exists(locale):
|
||||
return locale
|
||||
|
||||
return "en"
|
||||
|
||||
|
||||
def get_language_without_region(lng=None):
|
||||
@@ -132,6 +162,10 @@ def get_language_without_region(lng=None):
|
||||
return lng
|
||||
|
||||
|
||||
def set_region(region):
|
||||
_active_region.value = region
|
||||
|
||||
|
||||
@contextmanager
|
||||
def language(lng, region=None):
|
||||
"""
|
||||
@@ -143,15 +177,18 @@ def language(lng, region=None):
|
||||
formatting. If you pass a ``lng`` that already contains a region, e.g. ``pt-br``, the ``region``
|
||||
attribute will be ignored.
|
||||
"""
|
||||
_lng = translation.get_language()
|
||||
lng_before = translation.get_language()
|
||||
region_before = getattr(_active_region, "value", None)
|
||||
lng = lng or settings.LANGUAGE_CODE
|
||||
if '-' not in lng and region:
|
||||
lng += '-' + region.lower()
|
||||
translation.activate(lng)
|
||||
_active_region.value = region
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
translation.activate(_lng)
|
||||
translation.activate(lng_before)
|
||||
_active_region.value = region_before
|
||||
|
||||
|
||||
class LazyLocaleException(Exception):
|
||||
|
||||
@@ -35,7 +35,7 @@ from django.utils.translation.trans_real import (
|
||||
parse_accept_lang_header,
|
||||
)
|
||||
|
||||
from pretix.base.i18n import get_language_without_region
|
||||
from pretix.base.i18n import get_language_without_region, set_region
|
||||
from pretix.base.settings import global_settings_object
|
||||
from pretix.multidomain.urlreverse import (
|
||||
get_event_domain, get_organizer_domain,
|
||||
@@ -92,10 +92,14 @@ class LocaleMiddleware(MiddlewareMixin):
|
||||
)
|
||||
if '-' not in language and settings_holder.settings.region:
|
||||
language += '-' + settings_holder.settings.region
|
||||
if settings_holder.settings.region:
|
||||
set_region(settings_holder.settings.region)
|
||||
else:
|
||||
gs = global_settings_object(request)
|
||||
if '-' not in language and gs.settings.region:
|
||||
language += '-' + gs.settings.region
|
||||
if gs.settings.region:
|
||||
set_region(gs.settings.region)
|
||||
|
||||
translation.activate(language)
|
||||
request.LANGUAGE_CODE = get_language_without_region()
|
||||
|
||||
@@ -26,7 +26,8 @@ from babel.numbers import format_currency
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.template.defaultfilters import floatformat
|
||||
from django.utils import translation
|
||||
|
||||
from pretix.base.i18n import get_babel_locale
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@@ -59,13 +60,10 @@ def money_filter(value: Decimal, arg='', hide_currency=False):
|
||||
if hide_currency:
|
||||
return floatformat(value, f"{places}g")
|
||||
|
||||
locale_parts = translation.get_language().split("-", 1)
|
||||
locale = locale_parts[0]
|
||||
if len(locale_parts) > 1 and len(locale_parts[1]) == 2:
|
||||
try:
|
||||
locale = Locale(locale_parts[0], locale_parts[1].upper())
|
||||
except UnknownLocaleError:
|
||||
pass
|
||||
try:
|
||||
locale = Locale(get_babel_locale())
|
||||
except UnknownLocaleError:
|
||||
locale = "en"
|
||||
|
||||
try:
|
||||
return format_currency(value, arg, locale=locale)
|
||||
|
||||
@@ -48,7 +48,7 @@ from django.utils.timezone import now
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from pretix.base.i18n import get_language_without_region
|
||||
from pretix.base.i18n import get_language_without_region, set_region
|
||||
from pretix.base.middleware import get_supported_language
|
||||
from pretix.base.models import (
|
||||
CartPosition, Customer, InvoiceAddress, ItemAddOn, OrderFee, Question,
|
||||
@@ -544,6 +544,7 @@ def iframe_entry_view_wrapper(view_func):
|
||||
region = request.event.settings.region
|
||||
if '-' not in lng and region:
|
||||
lng += '-' + region.lower()
|
||||
set_region(region)
|
||||
|
||||
# with language() is not good enough here – we really need to take the role of LocaleMiddleware and modify
|
||||
# global state, because template rendering might be happening lazily.
|
||||
|
||||
@@ -68,9 +68,6 @@ def test_urlreplace_replace_parameter():
|
||||
|
||||
("en", Decimal("1023"), "JPY", "¥1,023"),
|
||||
|
||||
("pt-pt", Decimal("10.00"), "EUR", "10,00" + NBSP + "€"),
|
||||
("pt-br", Decimal("10.00"), "EUR", "€" + NBSP + "10,00"),
|
||||
|
||||
# unknown currency
|
||||
("de", Decimal("1234.56"), "FOO", "1.234,56" + NBSP + "FOO"),
|
||||
("de", Decimal("1234.567"), "FOO", "1.234,57" + NBSP + "FOO"),
|
||||
|
||||
@@ -19,9 +19,12 @@
|
||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||
# <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
import pytest
|
||||
from django.utils.translation import get_language
|
||||
|
||||
from pretix.base.i18n import get_language_without_region, language
|
||||
from pretix.base.i18n import (
|
||||
get_babel_locale, get_language_without_region, language,
|
||||
)
|
||||
from pretix.helpers.i18n import get_javascript_format, get_moment_locale
|
||||
|
||||
|
||||
@@ -42,22 +45,26 @@ def test_get_locale():
|
||||
assert get_moment_locale('en-CA') == 'en-ca'
|
||||
|
||||
|
||||
def test_set_region():
|
||||
with language('de'):
|
||||
assert get_language() == 'de'
|
||||
assert get_language_without_region() == 'de'
|
||||
with language('de', 'US'):
|
||||
assert get_language() == 'de-us'
|
||||
assert get_language_without_region() == 'de'
|
||||
with language('de', 'DE'):
|
||||
assert get_language() == 'de-de'
|
||||
assert get_language_without_region() == 'de'
|
||||
with language('de-informal', 'DE'):
|
||||
assert get_language() == 'de-informal'
|
||||
assert get_language_without_region() == 'de-informal'
|
||||
with language('pt', 'PT'):
|
||||
assert get_language() == 'pt-pt'
|
||||
assert get_language_without_region() == 'pt-pt'
|
||||
with language('pt-pt', 'BR'):
|
||||
assert get_language() == 'pt-pt'
|
||||
assert get_language_without_region() == 'pt-pt'
|
||||
@pytest.mark.parametrize(
|
||||
["lng_in", "region_in", "lng_out", "lng_without_region_out", "babel_out"],
|
||||
[
|
||||
("en", None, "en", "en", "en"),
|
||||
("en-us", None, "en-us", "en", "en_US"),
|
||||
("en", "US", "en-us", "en", "en_US"),
|
||||
("de", None, "de", "de", "de"),
|
||||
("de", "US", "de-us", "de", "de"),
|
||||
("de", "DE", "de-de", "de", "de_DE"),
|
||||
("de-informal", "DE", "de-informal", "de-informal", "de_DE"),
|
||||
("de-informal", "CH", "de-informal", "de-informal", "de_CH"),
|
||||
("pt-pt", "PT", "pt-pt", "pt-pt", "pt_PT"),
|
||||
("es", "MX", "es-mx", "es", "es_MX"),
|
||||
("es-419", "MX", "es-419", "es-419", "es_MX"),
|
||||
("zh-hans", "CN", "zh-hans", "zh-hans", "zh_Hans_CN"),
|
||||
("zh-hant", "TW", "zh-hant", "zh-hant", "zh_Hant_TW"),
|
||||
],
|
||||
)
|
||||
def test_set_region(lng_in, region_in, lng_out, lng_without_region_out, babel_out):
|
||||
with language(lng_in, region_in):
|
||||
assert get_language() == lng_out
|
||||
assert get_language_without_region() == lng_without_region_out
|
||||
assert str(get_babel_locale()) == babel_out
|
||||
|
||||
Reference in New Issue
Block a user