diff --git a/froide/account/forms.py b/froide/account/forms.py index fa9f28382..812cba72c 100644 --- a/froide/account/forms.py +++ b/froide/account/forms.py @@ -17,6 +17,7 @@ from froide.helper.content_urls import get_content_url from froide.helper.form_utils import JSONMixin +from froide.helper.language import get_user_language_choices from froide.helper.spam import SpamProtectionMixin from froide.helper.widgets import ( BootstrapCheckboxInput, @@ -625,7 +626,11 @@ def save(self, commit=True): class AccountSettingsForm(forms.ModelForm): + language = forms.ChoiceField( + choices=get_user_language_choices, + widget=BootstrapSelect, + ) + class Meta: model = User fields = ["language"] - widgets = {"language": BootstrapSelect} diff --git a/froide/account/migrations/0042_alter_user_language.py b/froide/account/migrations/0042_alter_user_language.py new file mode 100644 index 000000000..6a041792d --- /dev/null +++ b/froide/account/migrations/0042_alter_user_language.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.12 on 2026-04-15 10:47 + +import froide.helper.language +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0041_alter_application_authorization_grant_type'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='language', + field=models.CharField(choices=froide.helper.language.get_language_choices, default=froide.helper.language.get_default_language, help_text='Determines the default language of communication with you.', max_length=10, verbose_name='Language'), + ), + ] diff --git a/froide/account/models.py b/froide/account/models.py index 805b88840..7361fab99 100644 --- a/froide/account/models.py +++ b/froide/account/models.py @@ -25,6 +25,7 @@ from taggit.models import TagBase, TaggedItemBase from froide.helper.csv_utils import export_csv, get_dict +from froide.helper.language import get_default_language, get_language_choices from froide.helper.storage import HashedFilenameStorage, delete_file_if_last_reference @@ -180,8 +181,8 @@ class User(AbstractBaseUser, PermissionsMixin): language = models.CharField( verbose_name=_("Language"), max_length=10, - default=settings.LANGUAGE_CODE, - choices=settings.LANGUAGES, + default=get_default_language, + choices=get_language_choices, help_text=_("Determines the default language of communication with you."), ) diff --git a/froide/document/migrations/0032_alter_document_language.py b/froide/document/migrations/0032_alter_document_language.py new file mode 100644 index 000000000..88da20219 --- /dev/null +++ b/froide/document/migrations/0032_alter_document_language.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.12 on 2026-04-20 10:05 + +import filingcabinet.language +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('document', '0031_alter_document_updated_at_alter_document_user_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='document', + name='language', + field=models.CharField(blank=True, choices=filingcabinet.language.get_language_choices, default=filingcabinet.language.get_default_language, max_length=10), + ), + ] diff --git a/froide/foirequest/forms/request.py b/froide/foirequest/forms/request.py index 5681f6a53..169c89405 100644 --- a/froide/foirequest/forms/request.py +++ b/froide/foirequest/forms/request.py @@ -18,6 +18,7 @@ from froide.helper.auth import get_read_queryset from froide.helper.form_utils import JSONMixin from froide.helper.forms import TagObjectForm +from froide.helper.language import get_user_language_choices from froide.helper.text_utils import apply_user_redaction, redact_plaintext, slugify from froide.helper.widgets import BootstrapRadioSelect, BootstrapSelect, PriceInput from froide.publicbody.models import Category, PublicBody @@ -114,7 +115,7 @@ class RequestForm(JSONMixin, forms.Form): ) tags = TagField(required=False, widget=forms.HiddenInput) language = forms.ChoiceField( - choices=settings.LANGUAGES, + choices=get_user_language_choices, initial=settings.LANGUAGE_CODE, label=_("Language"), widget=forms.HiddenInput, diff --git a/froide/foirequest/migrations/0075_alter_foiproject_language_alter_foirequest_language.py b/froide/foirequest/migrations/0075_alter_foiproject_language_alter_foirequest_language.py new file mode 100644 index 000000000..5e4a7de28 --- /dev/null +++ b/froide/foirequest/migrations/0075_alter_foiproject_language_alter_foirequest_language.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.12 on 2026-04-15 10:47 + +import froide.helper.language +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('foirequest', '0074_requestdraft_proof'), + ] + + operations = [ + migrations.AlterField( + model_name='foiproject', + name='language', + field=models.CharField(blank=True, choices=froide.helper.language.get_language_choices, default=froide.helper.language.get_default_language, max_length=10), + ), + migrations.AlterField( + model_name='foirequest', + name='language', + field=models.CharField(blank=True, choices=froide.helper.language.get_language_choices, default=froide.helper.language.get_default_language, max_length=10), + ), + ] diff --git a/froide/foirequest/models/project.py b/froide/foirequest/models/project.py index 77fecaf9f..71b975d08 100644 --- a/froide/foirequest/models/project.py +++ b/froide/foirequest/models/project.py @@ -9,6 +9,7 @@ from taggit.managers import TaggableManager from taggit.models import TaggedItemBase +from froide.helper.language import get_default_language, get_language_choices from froide.helper.text_utils import redact_plaintext from froide.publicbody.models import PublicBody from froide.team.models import Team @@ -77,8 +78,8 @@ class FoiProject(models.Model): language = models.CharField( max_length=10, blank=True, - default=settings.LANGUAGE_CODE, - choices=settings.LANGUAGES, + default=get_default_language, + choices=get_language_choices, ) site = models.ForeignKey( diff --git a/froide/foirequest/models/request.py b/froide/foirequest/models/request.py index d9f0fff6b..63cdfccc5 100644 --- a/froide/foirequest/models/request.py +++ b/froide/foirequest/models/request.py @@ -22,6 +22,7 @@ from froide.campaign.models import Campaign from froide.helper.email_utils import make_address +from froide.helper.language import get_default_language, get_language_choices from froide.helper.text_diff import CONTENT_CACHE_THRESHOLD, get_differences from froide.helper.text_utils import redact_plaintext from froide.publicbody.models import FoiLaw, Jurisdiction, PublicBody @@ -413,8 +414,8 @@ class FoiRequest(models.Model): language = models.CharField( max_length=10, blank=True, - default=settings.LANGUAGE_CODE, - choices=settings.LANGUAGES, + default=get_default_language, + choices=get_language_choices, ) site = models.ForeignKey( diff --git a/froide/helper/context_processors.py b/froide/helper/context_processors.py index 491ea4145..281b050d8 100644 --- a/froide/helper/context_processors.py +++ b/froide/helper/context_processors.py @@ -17,6 +17,7 @@ def site_settings(request): request, "LANGUAGE_CODE", settings.LANGUAGE_CODE ), "DEFAULT_LANGUAGE_CODE": settings.LANGUAGE_CODE, + "USER_LANGUAGES": settings.USER_LANGUAGES, } diff --git a/froide/helper/language.py b/froide/helper/language.py new file mode 100644 index 000000000..072a84570 --- /dev/null +++ b/froide/helper/language.py @@ -0,0 +1,13 @@ +from django.conf import settings + + +def get_default_language(): + return settings.LANGUAGE_CODE + + +def get_language_choices(): + return settings.LANGUAGES + + +def get_user_language_choices(): + return settings.USER_LANGUAGES diff --git a/froide/settings.py b/froide/settings.py index 3d27f34dd..3b367ed77 100644 --- a/froide/settings.py +++ b/froide/settings.py @@ -299,6 +299,10 @@ def STATICFILES_DIRS(self): ("zh-hk", _("Chinese (Traditional, Hong Kong)")), ) + # Languages available for user-facing selection (e.g. account settings, language picker). + # Override in downstream projects to exclude content-only languages like de-ls. + USER_LANGUAGES = LANGUAGES + # If you set this to False, Django will make some optimizations so as not # to load the internationalization machinery. USE_I18N = values.BooleanValue(True) diff --git a/froide/templates/header.html b/froide/templates/header.html index 445aca06e..99b31109d 100644 --- a/froide/templates/header.html +++ b/froide/templates/header.html @@ -112,9 +112,9 @@ {% endif %} {% endblock nav_account_login %} {% block language_select %} - {% if LANGUAGES|length > 1 %} + {% if USER_LANGUAGES|length > 1 %}