Проблема обнаружения языка вне views.py в Django

У меня есть приложение Django, которое загружает список стран и штатов из json-файла и переводит их с помощью пользовательского диктонария перевода, который я создал для этой цели. Я не использую gettext для этой конкретной задачи, потому что gettext не работает с данными, поступающими из баз данных или файлов.

Сначала файл, который обрабатывает json-файл

from django.utils.translation import gettext_lazy as _, get_language
import importlib
current_lng = get_language()
# importing the appropriate translation file based on current_lng
imp = importlib.import_module("countries.translations.countries_%s" % current_lng)
import json

def readJson(filename):
    with open(filename, 'r', encoding="utf8") as fp:
        return json.load(fp)

def get_country():
    filepath = 'myproj/static/data/countries_states_cities.json'
    all_data = readJson(filepath)

    all_countries = [('----', _("--- Select a Country ---"))]

    for x in all_data:
        y = (x['name'], imp.t_countries(x['name']))
        all_countries.append(y)

    return all_countries
# the files continues with other function but they are not relevant
arr_country = get_country()

Я использую countries_hangler.py в forms.py

from django import forms
from .models import Address
from django.conf import settings
from .countries_handler import arr_country
from django.utils.translation import gettext_lazy as _


class AddressForm(forms.ModelForm):
    # data = []

    #def __init__(self, data):
    #    self.data = data

    country = forms.ChoiceField(
        choices = arr_country,
        required = False, 
        label=_('Company Country Location'), 
        widget=forms.Select(attrs={'class':'form-control', 'id': 'id_country'}),
    )

    def get_state_by_country(self, country):
        return return_state_by_country(country)

    def get_city_by_state(self, state):
        return return_city_by_state(state)

    class Meta:
        model = Address
        fields = ['country']

В файле views.py у меня есть такие строки

from django.shortcuts import render
from django.http import HttpResponseRedirect, JsonResponse
from .forms import AddressForm
import json
from django.utils import translation
from django.utils.translation import gettext_lazy as _, get_language

def load_form(request):
    form = AddressForm
    # the lang variable is only passed for testing
    return render(request, 'countries/country_form.html', {'form':form, 'lang':get_language()})

settings.py правильно настроен, вот соответствующие части

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# ...
LANGUAGE_CODE = 'en'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

LANGUAGES = [
  ('en', 'English'),
  ('fr', 'French'),
]

Также мой urls.py определен правильно

from django.contrib import admin
from django.urls import path, include
from django.conf.urls.i18n import i18n_patterns

urlpatterns = i18n_patterns(
    path('admin/', admin.site.urls),
    path('countries/', include('countries.urls')),
    prefix_default_language=False
)

Теперь, когда я меняю язык таким образом:

http://127.0.0.1:8000/fr/countries/

функция get_language в countries_handler.py по-прежнему возвращает 'en' вместо 'fr'. В результате файл перевода для французского языка никогда не загружается:

# countries/translations/countries_fr.py
# here's my little custom translation tool
def t_countries(c):
    country_dict = {
        "Albania" : "Albanie",
        "Algeria" : "Algérie",
        "Antigua and Barbuda" : "Antigua-et-Barbuda",
        "Argentina" : "Argentine",
        "Armenia" : "Arménie",
    # ...
    }

    if c in country_dict:
        return country_dict[c]
    
    return c

Кстати, если я помещу следующие строки в forms.py или countries_handler.py:

from django.utils import translation

и тогда

translation.activate('fr')

Тогда изменение языка будет очень хорошо заметно.

Теперь я действительно знаю причину проблемы, единственная проблема, что я не могу получить решение. ПРИЧИНА ПРОБЛЕМЫ ЗАКЛЮЧАЕТСЯ В СЛЕДУЮЩЕМ:

В forms.py (через languages_handler.py) get_language() не определяет язык, измененный из url, потому что forms.py создает форму во время запуска сервера разработки, а не после запроса url.

В результате get_language возвращает правильный язык только в views.py, но не в forms.py. Конечно, я попытался загрузить данные формы в представление, а не в форму следующим образом

# views.py
from .countries_handler import arr_country
# .....
def load_form(request):
    form = AddressForm(arr_country)
    return render(request, 'countries/country_form.html', {'form':form, 'lang':get_language()})

и затем в форме:

class AddressForm(forms.ModelForm):
    data = []

    def __init__(self, data):
        self.data = data

    country = forms.ChoiceField(
        choices = data,
        required = False, 
        label=_('Company Country Location'), 
        widget=forms.Select(attrs={'class':'form-control', 'id': 'id_country'}),
    )

Но это не работает. В этом случае форма даже не отображается!!!

Тогда, что я должен сделать, чтобы решить эту проблему? Есть ли способ определить текущий язык пользователя из url или сформировать cookie, а затем отправить данные на форму и построить ее динамически во время выполнения?

Мне нужно, чтобы список стран загружался с правильным переводом, когда я меняю язык в урле.

Вот как это сделать. Прежде всего, обратите внимание, что:

Forms.py и countries_handler.py запускаются при старте сервера, а не при возникновении http-запроса. Поэтому любое изменение, поступающее из браузера, например, изменение пользователем языка в url, должно быть обработано в views.py, в том самом месте, куда попадает маршрут в urls.py. Вот правильный views.py:

# ...
from .countries_handler import get_country, return_state_by_country
from django.utils.translation import gettext_lazy as _, get_language
# ... 

def load_form(request):
    # load_form is called by urls.py, therefore it respond to a change made in the url, at run time. 
    # get_language must be called here to get a correct result. 
    arr_country = get_country(get_language())
    form = AddressForm(arr_country)
    return render(request, 'countries/country_form.html', {'form':form, 'lang':get_language()})

Здесь вызывается get_country из countries_handler.py. get_language корректно передает ему текущий выбранный язык.

Вот forms.py:

class AddressForm(forms.ModelForm):
    """ Using an empty list as a default argument is a common error. 
    It may lead to unwanted behavior. The correct way to do it is to 
    initialize the list to None """
    def __init__(self, data=None, *args, **kwargs):
        super(AddressForm, self).__init__(*args, **kwargs)
        if data is not None:
            self.fields['country'].choices = data

    country = forms.ChoiceField(
        choices = (),
        required = False, 
        label=_('Company Country Location'), 
        widget=forms.Select(attrs={'class':'form-control', 'id': 'id_country'}),
    )

    def get_state_by_country(self, country):
        return return_state_by_country(country)

    def get_city_by_state(self, state):
        return return_city_by_state(state)

    class Meta:
        model = Address
        fields = ['country']

Обратите внимание, как я заполняю поля выбора из конструктора напрямую. данные объявлены как необязательные, потому что когда я использую тот же класс для вызова return_state_by_country(country), например, у меня нет никаких данных для передачи.

Так и есть. Я не выкладываю все файлы проекта, чтобы избежать многословия. Но основные находятся в этом ответе. В нем я узнал, как динамически передавать данные в конструктор формы во время выполнения. Остальное остается неизменным.

Вернуться на верх