diff --git a/src/pretix/control/forms/__init__.py b/src/pretix/control/forms/__init__.py index a4a85aae9e..dff88182be 100644 --- a/src/pretix/control/forms/__init__.py +++ b/src/pretix/control/forms/__init__.py @@ -44,6 +44,7 @@ from django.forms.utils import from_current_timezone from django.urls import reverse from django.utils.html import escape from django.utils.safestring import mark_safe +from django.utils.text import format_lazy from django.utils.timezone import now from django.utils.translation import gettext_lazy as _ from django_scopes.forms import SafeModelMultipleChoiceField @@ -51,6 +52,7 @@ from django_scopes.forms import SafeModelMultipleChoiceField from pretix.helpers.hierarkey import clean_filename from ...base.forms import I18nModelForm +from ...helpers.i18n import get_language_score from ...helpers.images import ( IMAGE_EXTS, validate_uploaded_file_for_valid_image, ) @@ -300,18 +302,43 @@ class SlugWidget(forms.TextInput): class MultipleLanguagesWidget(forms.CheckboxSelectMultiple): + template_name = 'pretixcontrol/multi_languages_select.html' option_template_name = 'pretixcontrol/multi_languages_widget.html' def sort(self): - self.choices = sorted(self.choices, key=lambda l: ( + def filter_and_sort(choices, languages, cond=True): + return sorted( + [c for c in choices if (c[0] in languages) == cond], + key=lambda c: str(c[1]) + ) + self.choices = ( ( - 0 if l[0] in settings.LANGUAGES_OFFICIAL - else ( - 1 if l[0] not in settings.LANGUAGES_INCUBATING - else 2 - ) - ), str(l[1]) - )) + '', + filter_and_sort(self.choices, settings.LANGUAGES_OFFICIAL) + ), + ( + ( + _('Community translations'), + format_lazy( + _('These translations are not maintained by the pretix team. We cannot vouch for their correctness ' + 'and new or recently changed features might not be translated and will show in English instead. ' + 'You can help translating.'), + translate_url='https://translate.pretix.eu' + ), + 'fa fa-group' + ), + filter_and_sort(self.choices, settings.LANGUAGES_OFFICIAL.union(settings.LANGUAGES_INCUBATING), False) + ), + ( + ( + _('Development only'), + _('These translations are still in progress. These languages can currently only be selected on development ' + 'installations of pretix, not in production.'), + 'fa fa-flask text-danger' + ), + filter_and_sort(self.choices, settings.LANGUAGES_INCUBATING) + ) + ) def options(self, name, value, attrs=None): self.sort() @@ -325,6 +352,8 @@ class MultipleLanguagesWidget(forms.CheckboxSelectMultiple): opt = super().create_option(name, value, label, selected, index, subindex, attrs) opt['official'] = value in settings.LANGUAGES_OFFICIAL opt['incubating'] = value in settings.LANGUAGES_INCUBATING + base_score = get_language_score("de") + opt['score'] = round(get_language_score(value) / base_score * 100) return opt diff --git a/src/pretix/control/templates/pretixcontrol/multi_languages_select.html b/src/pretix/control/templates/pretixcontrol/multi_languages_select.html new file mode 100644 index 0000000000..6e61fb5512 --- /dev/null +++ b/src/pretix/control/templates/pretixcontrol/multi_languages_select.html @@ -0,0 +1,5 @@ +{% with id=widget.attrs.id %}{% for group, options, index in widget.optgroups %}{% if group %} +
{% if group.2 %} {% endif %}{{ group.0 }}

{{ group.1|safe }}

{% endif %}{% for option in options %}
+ {% include option.template_name with widget=option %}
{% endfor %}{% if group %} +
{% endif %}{% endfor %} +{% endwith %} diff --git a/src/pretix/control/templates/pretixcontrol/multi_languages_widget.html b/src/pretix/control/templates/pretixcontrol/multi_languages_widget.html index 5649ad5224..3f8cc16e9d 100644 --- a/src/pretix/control/templates/pretixcontrol/multi_languages_widget.html +++ b/src/pretix/control/templates/pretixcontrol/multi_languages_widget.html @@ -5,13 +5,12 @@ {% include "django/forms/widgets/input.html" %} {% if widget.wrap_label %} {{ widget.label }} - {% if widget.incubating %} - - {% trans "Translation in development" %} - - {% elif not widget.official %} - - {% trans "Unofficial translation" %} + {% if widget.incubating or not widget.official %} +   + + {{ widget.score }} % {% endif %} diff --git a/src/pretix/helpers/i18n.py b/src/pretix/helpers/i18n.py index 48ab13e98c..9f4c1071a5 100644 --- a/src/pretix/helpers/i18n.py +++ b/src/pretix/helpers/i18n.py @@ -19,12 +19,18 @@ # You should have received a copy of the GNU Affero General Public License along with this program. If not, see # . # +import gettext as gettext_module import json +import os import re +from functools import lru_cache +from django.apps import apps from django.conf import settings from django.utils import translation from django.utils.formats import get_format +from django.utils.translation import to_locale +from django.utils.translation.trans_real import TranslationCatalog date_conversion_to_moment = { '%a': 'ddd', @@ -156,3 +162,63 @@ def get_moment_locale(locale=None): def i18ncomp(query): return json.dumps(str(query))[1:-1] + + +@lru_cache +def get_language_score(locale): + """ + For a given language code, return a numeric score on how well-translated the language is. The score + is an integer greater than 1 and can be arbitrarily high, so it's only useful for comparing with + other languages. + + Note that there is no valid score for "en", since it's technically not "translated". + """ + catalog = None + app_configs = reversed(apps.get_app_configs()) + + for app in app_configs: + # Filter out all third-party apps by looking for the pretix name and for valid pretix plugins + if not app.name.startswith("pretix") and not hasattr(app, 'PretixPluginMeta'): + continue + if hasattr(app, 'PretixPluginMeta'): + # Filter out invisible plugins and plugins only available to some users + p = app.PretixPluginMeta + if not getattr(p, 'visible', True) or hasattr(app, 'is_available'): + continue + localedir = os.path.join(app.path, "locale") + if os.path.exists(localedir): + try: + translation = gettext_module.translation( + domain="django", + localedir=localedir, + languages=[to_locale(locale)], + fallback=False, + ) + except: + continue + if catalog is None: + catalog = TranslationCatalog(translation) + else: + catalog.update(translation) + + # Add pretix' main translation folder as well as installation-specific translation folders + for localedir in reversed(settings.LOCALE_PATHS): + try: + translation = gettext_module.translation( + domain="django", + localedir=localedir, + languages=[to_locale(locale)], + fallback=False, + ) + except: + continue + if catalog is None: + catalog = TranslationCatalog(translation) + else: + catalog.update(translation) + + if not catalog: + score = 1 + else: + score = len(list(catalog.items())) or 1 + return score diff --git a/src/pretix/static/pretixbase/scss/_theme.scss b/src/pretix/static/pretixbase/scss/_theme.scss index 2ac15521b8..f8d723992c 100644 --- a/src/pretix/static/pretixbase/scss/_theme.scss +++ b/src/pretix/static/pretixbase/scss/_theme.scss @@ -116,6 +116,22 @@ input[type=number]::-webkit-outer-spin-button { font-weight: normal; } +.checkbox-group { + margin-top: 1.25em; + margin-bottom: 1.25em; +} +.checkbox-group-legend { + margin-bottom: .65em; + border-bottom: 1px solid #ccc; + font-weight: bold; +} +.checkbox-group-legend > span { + background: white; + padding: .5em; + padding-left: 0; + position: relative; + top: .35em; +} .alert { text-align: left;