在 Django 中检测 views.py 之外的语言时出现问题

Problem detecting a language outside views.py in 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()

我在forms.py

中使用countries_hangler.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/

countries_handler.py 中的 get_language 函数仍然是 returns '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 只有 returns 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,然后将数据发送到表单并在运行时动态构建它?

我需要在 url 中更改语言时正确加载国家列表。

终于自己找到了解决办法,分享一下。首先注意:

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()})

这里调用了countries_handler.py中的get_country。 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']

请注意我是如何直接从构造函数中填充选择字段的。数据被声明为可选的,因为当我使用相同的 class 调用 return_state_by_country(country) 时,例如,我没有任何数据要传递。

也就是。我没有 post 项目的所有文件以避免冗长。但最重要的是在这个答案中。我在那里学习了如何在 运行 时间将数据动态传递给表单构造函数。其余不变