diff --git a/app/eventyay/base/configurations/default_setting.py b/app/eventyay/base/configurations/default_setting.py index c3cb1be23e..297947095a 100644 --- a/app/eventyay/base/configurations/default_setting.py +++ b/app/eventyay/base/configurations/default_setting.py @@ -849,7 +849,24 @@ def primary_font_kwargs(): choices=settings.LANGUAGES, widget=MultipleLanguagesWidget, required=True, - label=_('Available languages'), + label=_('Active languages'), + ), + }, + 'content_locales': { + 'default': json.dumps([settings.LANGUAGE_CODE]), + 'type': list, + 'serializer_class': ListMultipleChoiceField, + 'serializer_kwargs': dict( + choices=settings.LANGUAGES, + required=True, + ), + 'form_class': forms.MultipleChoiceField, + 'form_kwargs': dict( + choices=settings.LANGUAGES, + widget=MultipleLanguagesWidget, + required=True, + label=_('Content languages'), + help_text=_('Languages that speakers can select for their submissions. Content languages should be a subset of active languages.'), ), }, 'locale': { diff --git a/app/eventyay/base/models/event.py b/app/eventyay/base/models/event.py index 58d13bde06..1ab227677a 100644 --- a/app/eventyay/base/models/event.py +++ b/app/eventyay/base/models/event.py @@ -2080,12 +2080,53 @@ def clean_presale(presale_start, presale_end): @cached_property def locales(self) -> list[str]: """Is a list of active event locales.""" - return self.locale_array.split(',') + if hasattr(self, 'settings') and 'locales' in self.settings._cache(): + if locales := self.settings.get('locales', as_type=list): + return locales + return [code for code in self.locale_array.split(',') if code] @cached_property def content_locales(self) -> list[str]: """Is a list of active content locales.""" - return self.content_locale_array.split(',') + if hasattr(self, 'settings') and 'content_locales' in self.settings._cache(): + if locales := self.settings.get('content_locales', as_type=list): + return locales + fallback = [code for code in self.content_locale_array.split(',') if code] + return fallback or self.locales + + def _clear_language_caches(self): + for attr in [ + 'locales', + 'content_locales', + 'is_multilingual', + 'named_locales', + 'available_content_locales', + 'named_content_locales', + 'named_plugin_locales', + 'plugin_locales', + ]: + self.__dict__.pop(attr, None) + + def update_language_configuration( + self, + *, + locales: list[str] | None = None, + content_locales: list[str] | None = None, + default_locale: str | None = None, + ) -> None: + locales_list = list(locales or []) + if content_locales is None: + content_locales_list = locales_list + else: + content_locales_list = list(content_locales) + if locales_list: + self.locale_array = ','.join(locales_list) + if content_locales_list: + self.content_locale_array = ','.join(content_locales_list) + if default_locale: + self.locale = default_locale + if locales_list or content_locales_list or default_locale: + self._clear_language_caches() @cached_property def is_multilingual(self) -> bool: diff --git a/app/eventyay/base/settings.py b/app/eventyay/base/settings.py index e7a14c00e1..0fb19379e6 100644 --- a/app/eventyay/base/settings.py +++ b/app/eventyay/base/settings.py @@ -113,8 +113,20 @@ def validate_event_settings(event, settings_dict): default_locale = settings_dict.get('locale') locales = settings_dict.get('locales', []) + if not isinstance(locales, list): + locales = list(locales) if default_locale and default_locale not in locales: raise ValidationError({'locale': _('Your default locale must also be enabled for your event (see box above).')}) + content_locales = settings_dict.get('content_locales') + if content_locales is None: + content_locales = locales + elif not isinstance(content_locales, list): + content_locales = list(content_locales) + if content_locales: + if invalid_content_locales := set(content_locales) - set(locales): + raise ValidationError( + {'content_locales': _('Content languages must be a subset of the active languages.')} + ) if settings_dict.get('attendee_names_required') and not settings_dict.get('attendee_names_asked'): raise ValidationError( {'attendee_names_required': _('You cannot require specifying attendee names if you do not ask for them.')} diff --git a/app/eventyay/config/settings.py b/app/eventyay/config/settings.py index e53cbf36b8..aec71f45f8 100644 --- a/app/eventyay/config/settings.py +++ b/app/eventyay/config/settings.py @@ -455,6 +455,8 @@ def instance_name(request): ('fi', _('Finnish')), ('el', _('Greek')), ('it', _('Italian')), + ('id', _('Indonesian')), + ('ko', _('Korean')), ('lv', _('Latvian')), ('pl', _('Polish')), ('pt-pt', _('Portuguese (Portugal)')), @@ -624,6 +626,12 @@ def instance_name(request): 'official': False, 'percentage': 95, }, + 'id': { + 'name': _('Indonesian'), + 'natural_name': 'Bahasa Indonesia', + 'official': False, + 'percentage': 90, + }, 'ja-jp': { 'name': _('Japanese'), 'natural_name': '日本語', @@ -631,6 +639,12 @@ def instance_name(request): 'percentage': 69, 'public_code': 'jp', }, + 'ko': { + 'name': _('Korean'), + 'natural_name': '한국어', + 'official': False, + 'percentage': 88, + }, 'nl': { 'name': _('Dutch'), 'natural_name': 'Nederlands', diff --git a/app/eventyay/control/views/event.py b/app/eventyay/control/views/event.py index 9ea8199502..34ad9e1c4f 100644 --- a/app/eventyay/control/views/event.py +++ b/app/eventyay/control/views/event.py @@ -191,6 +191,11 @@ def get_context_data(self, *args, **kwargs) -> dict: def form_valid(self, form): self._save_decoupled(self.sform) self.sform.save() + form.instance.update_language_configuration( + locales=self.sform.cleaned_data.get('locales'), + content_locales=self.sform.cleaned_data.get('content_locales'), + default_locale=self.sform.cleaned_data.get('locale'), + ) self.save_meta() self.save_product_meta_property_formset(self.object) self.save_confirm_texts_formset(self.object) diff --git a/app/eventyay/control/views/main.py b/app/eventyay/control/views/main.py index 4156ea78a0..ba7cc206b6 100644 --- a/app/eventyay/control/views/main.py +++ b/app/eventyay/control/views/main.py @@ -282,6 +282,7 @@ def done(self, form_list, form_dict, **kwargs): event.settings.set('timezone', basics_data['timezone']) event.settings.set('locale', basics_data['locale']) event.settings.set('locales', foundation_data['locales']) + event.settings.set('content_locales', foundation_data['locales']) if (copy_data and copy_data['copy_from_event']) or self.clone_from or event.has_subevents: return redirect( diff --git a/app/eventyay/eventyay_common/forms/event.py b/app/eventyay/eventyay_common/forms/event.py index 9dd432e31f..ad8d1de174 100644 --- a/app/eventyay/eventyay_common/forms/event.py +++ b/app/eventyay/eventyay_common/forms/event.py @@ -24,9 +24,10 @@ class EventCommonSettingsForm(SettingsForm): ) auto_fields = [ - "locales", - "locale", - "region", + 'locales', + 'content_locales', + 'locale', + 'region', "contact_mail", "imprint_url", 'logo_image', @@ -57,6 +58,8 @@ def clean(self): def __init__(self, *args, **kwargs): self.event = kwargs['obj'] super().__init__(*args, **kwargs) + if self.event and 'content_locales' in self.fields: + self.fields['content_locales'].initial = self.event.content_locales class EventUpdateForm(I18nModelForm): diff --git a/app/eventyay/eventyay_common/templates/eventyay_common/event/settings.html b/app/eventyay/eventyay_common/templates/eventyay_common/event/settings.html index 772c590a12..b1fa5959b9 100644 --- a/app/eventyay/eventyay_common/templates/eventyay_common/event/settings.html +++ b/app/eventyay/eventyay_common/templates/eventyay_common/event/settings.html @@ -129,6 +129,7 @@

{{ request.event.name }} {% trans "- Settings" %}

{% trans "Localization" %} {% bootstrap_field sform.locales layout="control" %} + {% if sform.content_locales %}{% bootstrap_field sform.content_locales layout="control" %}{% endif %} {% bootstrap_field sform.locale layout="control" %} {% bootstrap_field sform.timezone layout="control" %} {% bootstrap_field sform.region layout="control" %} diff --git a/app/eventyay/eventyay_common/views/event.py b/app/eventyay/eventyay_common/views/event.py index 09fe8d0d6e..8414edb42a 100644 --- a/app/eventyay/eventyay_common/views/event.py +++ b/app/eventyay/eventyay_common/views/event.py @@ -263,6 +263,7 @@ def done(self, form_list, form_dict, **kwargs): event.settings.set('timezone', basics_data['timezone']) event.settings.set('locale', basics_data['locale']) event.settings.set('locales', foundation_data['locales']) + event.settings.set('content_locales', foundation_data['locales']) # Use the selected create_for option, but ensure smart defaults work for all create_for = self.storage.extra_data.get('create_for', EventCreatedFor.BOTH) @@ -280,6 +281,7 @@ def done(self, form_list, form_dict, **kwargs): 'timezone': str(basics_data.get('timezone')), 'locale': event.settings.locale, 'locales': event.settings.locales, + 'content_locales': event.settings.get('content_locales', as_type=list), 'is_video_creation': final_is_video_creation, } @@ -354,6 +356,11 @@ def get_context_data(self, *args, **kwargs) -> dict: def form_valid(self, form): self._save_decoupled(self.sform) self.sform.save() + form.instance.update_language_configuration( + locales=self.sform.cleaned_data.get('locales'), + content_locales=self.sform.cleaned_data.get('content_locales'), + default_locale=self.sform.cleaned_data.get('locale'), + ) tickets.invalidate_cache.apply_async(kwargs={'event': self.request.event.pk}) diff --git a/talk/src/pretalx/eventyay_common/tasks.py b/talk/src/pretalx/eventyay_common/tasks.py index 4c5af78423..d3f776f477 100644 --- a/talk/src/pretalx/eventyay_common/tasks.py +++ b/talk/src/pretalx/eventyay_common/tasks.py @@ -127,12 +127,14 @@ def process_event_webhook(event_data): try: action = event_data.get("action") organiser = get_object_or_404(Organiser, slug=event_data.get("organiser_slug")) + locales = event_data.get("locales") or [] + content_locales = event_data.get("content_locales") or locales if action == Action.CREATE: with scopes_disabled(): event = Event.objects.create( organiser=organiser, - locale_array=",".join(event_data.get("locales")), - content_locale_array=",".join(event_data.get("locales")), + locale_array=",".join(locales), + content_locale_array=",".join(content_locales), name=event_data.get("name"), slug=event_data.get("slug"), timezone=event_data.get("timezone"), @@ -157,8 +159,8 @@ def process_event_webhook(event_data): event.name = event_data.get("name") event.date_from = datetime.fromisoformat(event_data["date_from"]) event.date_to = datetime.fromisoformat(event_data["date_to"]) - event.locale_array = ",".join(event_data.get("locales")) - event.content_locale_array = ",".join(event_data.get("locales")) + event.locale_array = ",".join(locales) + event.content_locale_array = ",".join(content_locales) event.timezone = event_data.get("timezone") event.locale = event_data.get("locale") event.plugins = ( diff --git a/talk/src/pretalx/settings.py b/talk/src/pretalx/settings.py index 8f1aede751..0bb1c1a34f 100644 --- a/talk/src/pretalx/settings.py +++ b/talk/src/pretalx/settings.py @@ -412,6 +412,12 @@ def merge_csp(*options, config=None): "official": False, "percentage": 96, }, + "id": { + "name": _("Indonesian"), + "natural_name": "Bahasa Indonesia", + "official": False, + "percentage": 90, + }, "ja-jp": { "name": _("Japanese"), "natural_name": "日本語", @@ -419,6 +425,12 @@ def merge_csp(*options, config=None): "percentage": 62, "public_code": "jp", }, + "ko": { + "name": _("Korean"), + "natural_name": "한국어", + "official": False, + "percentage": 88, + }, "nl": { "name": _("Dutch"), "natural_name": "Nederlands",