Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion froide/account/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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}
19 changes: 19 additions & 0 deletions froide/account/migrations/0042_alter_user_language.py
Original file line number Diff line number Diff line change
@@ -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'),
),
]
5 changes: 3 additions & 2 deletions froide/account/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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."),
)

Expand Down
19 changes: 19 additions & 0 deletions froide/document/migrations/0032_alter_document_language.py
Original file line number Diff line number Diff line change
@@ -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),
),
]
3 changes: 2 additions & 1 deletion froide/foirequest/forms/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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),
),
]
5 changes: 3 additions & 2 deletions froide/foirequest/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
5 changes: 3 additions & 2 deletions froide/foirequest/models/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions froide/helper/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}


Expand Down
13 changes: 13 additions & 0 deletions froide/helper/language.py
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions froide/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions froide/templates/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@
{% endif %}
{% endblock nav_account_login %}
{% block language_select %}
{% if LANGUAGES|length > 1 %}
{% if USER_LANGUAGES|length > 1 %}
<li class="nav-item dropdown">
{% for lang_code, language in LANGUAGES %}
{% for lang_code, language in USER_LANGUAGES %}
{% if lang_code == CURRENT_LANGUAGE_CODE %}
<a class="nav-link dropdown-toggle"
href="#"
Expand All @@ -126,7 +126,7 @@
{% endfor %}
<div class="dropdown-menu dropdown-menu-right"
aria-labelledby="navbarlanguage-link">
{% for lang_code, language in LANGUAGES %}
{% for lang_code, language in USER_LANGUAGES %}
<form class="dropdown-item d-grid"
action="{% url 'set_language' %}"
method="post">
Expand Down
38 changes: 38 additions & 0 deletions froide/tests/test_migration_stability.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from io import StringIO

from django.core.management import call_command
from django.test import override_settings

import pytest

# LANGUAGES / LANGUAGE_CODE values that differ from the test settings to
# provoke a diff if any model field bakes the resolved settings value into
# its state (via choices=settings.LANGUAGES or default=settings.LANGUAGE_CODE).
DIFFERENT_LANGUAGES = (
("fr", "French"),
("ja", "Japanese"),
("pt-br", "Portuguese (Brazil)"),
)


@pytest.mark.django_db
@override_settings(LANGUAGES=DIFFERENT_LANGUAGES, LANGUAGE_CODE="ja")
def test_no_migration_needed_when_languages_change():
"""Changing LANGUAGES or LANGUAGE_CODE must not produce new migrations in froide apps."""
out = StringIO()
try:
call_command(
"makemigrations",
"--check",
"--dry-run",
stdout=out,
)
except SystemExit as e:
# Exit code 1 means migrations would be created
if e.code == 1:
pytest.fail(
"Changing settings.LANGUAGES or settings.LANGUAGE_CODE triggered "
"migration changes.\n"
f"Output: {out.getvalue()}"
)
raise
Loading