尝试从Django模板更改语言时出现问题

14

我需要包含两个按钮或链接,以允许用户在英语和西班牙语之间切换语言。我已经阅读了文档并尝试了以下内容:

<form action="/i18n/setlang/" method="post">{% csrf_token %}
    <input name="language" type="hidden" value="es" />
    <input type="submit" value="ES" />
</form>

然而,每次我点击按钮时,页面都会重新加载,但语言根本没有改变。我错过了什么吗?

注意:我没有设置next,因为我只想在所需的语言下重新加载当前页面。

如果我使用文档提供的默认表单,结果是相同的:页面重新加载但语言没有改变:

<form action="{% url 'set_language' %}" method="post">
    {% csrf_token %}
    <input name="next" type="hidden" value="{{ redirect_to }}" />
    <select name="language">
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
                {{ language.name_local }} ({{ language.code }})
            </option>
        {% endfor %}
    </select>
    <input type="submit" value="Go" />
</form>

更新:

经过进一步测试,我注意到在urls.py中同时使用i18n_patternspatterns存在问题。目前我的文件看起来像:

urlpatterns = i18n_patterns('',
    url(r'^contents/', include('contents.urls')),
    url(r'^events/', include('events.urls')),
    # ...
)
urlpatterns += patterns('',
    url(r'^i18n/', include('django.conf.urls.i18n')),
)

这似乎不起作用。但是,如果我删除 i18n_patterns 并将其更改为 patterns,那么它似乎可以工作:

urlpatterns = patterns('',
    url(r'^contents/', include('contents.urls')),
    url(r'^events/', include('events.urls')),
    # ...
)
urlpatterns += patterns('',
    url(r'^i18n/', include('django.conf.urls.i18n')),
)

文档中说您不必将其包含在内部,因此我认为这应该有效,但事实并非如此!无论您是在i18n_patterns之前还是之后包含django.conf.urls.i18n,它总是表现出相同的结果。

也许可以尝试查看Django是否成功设置了语言cookie,默认情况下为名为django_language的cookie,请使用浏览器的开发工具查看在之前和之后有哪些cookie。 - Ziyan
嗨@Ziyan!我刚刚发布了解决方案! :) - Caumons
10个回答

26
经过更多的测试和感谢@AronYsidoro提供的相关问题,我终于找到了问题,并且找到了一个非常简单的解决方案,可以解决这个问题。
首先,让我解释一下问题:当在你的urls.py中使用i18_patterns来在URL前添加语言代码时,如果你调用URL set_language来更改语言而没有指定next,它会默认为当前语言,但是带有之前添加的旧语言代码!所以,语言会回到原来的状态!而且,如果你明确指定了next,你必须确保不在开头包含语言代码。
如果你使用{{ request.path }}{{ request.get_full_path }}将当前页面指定为next,这样是行不通的,因为它也会返回语言代码。
那么,当使用i18n_patterns时,我们如何去除这个不需要的语言代码,以便在语言改变时重新加载当前页面呢?很简单,我们只需要切片前三个字符(斜杠和两个字符的语言代码)!
这里有两个例子。第一个是一个选择框(以语言作为选项),另一个是一个按钮(每种语言一个)。
我真的希望这对其他人有所帮助。你只需复制粘贴代码,它应该可以工作。然而,如果使用“按钮形式”,你只需设置所需的语言!
从列表中更改语言:
<form action="{% url 'set_language' %}" method="post">
  {% csrf_token %}
  <input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
  <select name="language">
    {% get_language_info_list for LANGUAGES as languages %}
      {% for language in languages %}
        <option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
          {{ language.name_local }} ({{ language.code }})
        </option>
      {% endfor %}
  </select>
  <input type="submit" value="Change" />
</form>

更改语言为按钮:

<form action="{% url 'set_language' %}" method="post">
  {% csrf_token %}
  <input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
  <input name="language" type="hidden" value="es" />
  <input type="submit" value="ES" />
</form>

编辑: 一些语言代码的长度超过2个字符(例如zh-hans,zh-hant),因此,请执行以下操作:
{% if get_current_language == 'zh-hans' or get_current_language == 'zh-hant' %}
    <li><a href="/{{ language.code }}{{ request.get_full_path|slice:'8:' }}">{{ language.name_local }} ({{ language.code }}) <b>{% if language.code == get_current_language %} ✓ {% endif %}</b></a></li>
{% else %}
    <li><a href="/{{ language.code }}{{ request.get_full_path|slice:'3:' }}">{{ language.name_local }} ({{ language.code }}) <b>{% if language.code == get_current_language %} ✓ {% endif %}</b></a></li>
{% endif %}

6
请注意,如果prefix_default_language=False,则{{ request.get_full_path|slice:'3:' }}可能会失败。最好进行检查,否则有可能截取URL的有效部分。 - Dário
3
除了上面的评论,大多数语言代码都有两个字母,例如de_DE、zh-Hans,因此依赖这一点是很危险的。在Python中使用'/'.join(request.get_full_path.split('/')[1:]),比如模板标签,会更好。 - Jens-Erik Weber
请注意,使用这种方法切片的部分将不会被翻译,并将按原样返回。 - ImportError
不,不要这样!你想改变语言并重新加载页面吗?只需设置 <input name="next" type="hidden" value=""> 就可以了。Django文档缺乏对 set_language 的详细解释。 - VengaVenga
请注意,有些语言代码的长度超过2个字符(例如zh-hans,zh-hant)。 - undefined

21

可能选项的总结:

使用 select 更改用户会话语言

Django文档中有一个很好的详细描述和示例。

使用按钮更改用户会话语言

无需像@Caumons建议的那样为每个按钮重复一个表单,而是可以在表单中包含与语言数量相同的按钮。

<form action="{% url 'set_language' %}" method="post">
    {% csrf_token %}
    <input name="next" type="hidden" value="{{ request.get_full_path|slice:'3:' }}" />
    <ul class="nav navbar-nav navbar-right language menu">
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
            <li>
                <button type="submit"
                        name="language"
                        value="{{ language.code }}"
                        class="{% if language.code == LANGUAGE_CODE %}selected{% endif %}">
                    {{ language.name_local }}
                </button>
            </li>
        {% endfor %}
    </ul>
</form>

您可以轻松地将按钮样式设置为链接或其他样式。

使用链接更改显示语言

如果不需要更改默认用户会话语言,则可以使用简单的链接来更改内容:

<ul class="nav navbar-nav navbar-right language menu">
    {% get_current_language as LANGUAGE_CODE %}
    {% get_available_languages as LANGUAGES %}
    {% get_language_info_list for LANGUAGES as languages %}
    {% for language in languages %}
        <li>
            <a href="/{{ language.code }}{{ request.get_full_path|slice:'3:' }}"
               class="{% if language.code == LANGUAGE_CODE %}selected{% endif %}"
               lang="{{ language.code }}">
                {{ language.name_local }}
            </a>
        </li>
    {% endfor %}
</ul>

搜索引擎优化 (SEO)

如果使用表单更改会话语言,按照Django的建议,我不确定内容是否完全符合SEO友好。因此,可能需要将链接<a>标记添加为隐藏在<button>元素下方。


2

我知道这不是一个稳定的解决方案,但我需要一个开关按钮(不是下拉列表,因为我想在两种语言之间切换)。 所以我想到了这个:

{% get_language_info_list for LANGUAGES as languages %}
{% if LANGUAGE_CODE == languages.0.code %}
    <form action="{% url 'set_language' %}" method="post">
    {% csrf_token %}
    <div class="lang-btn">
        <input name="next" type="hidden" value="{{ redirect_to }}" />
        <input name="language" type="hidden" value="{{ languages.1.code }}" />
        <button type="submit"><img width="30" src="{% static 'united-kingdom.png' %}" alt=""></button>
        </div>
        </form>
{% else %}
    <form action="{% url 'set_language' %}" method="post">
    {% csrf_token %}
        <div class="lang-btn">
            <input name="next" type="hidden" value="{{ redirect_to }}" />
            <input name="language" type="hidden" value="{{ languages.0.code }}" />
            <button type="submit"><img width="30" src="{% static 'turkey.png' %}" alt=""></button>
        </div>
    </form>
{% endif %}

2
如果你的系统只支持两种语言,那么可以按照以下方式使用:
{% ifequal LANGUAGE_CODE "en" %}
       <a href="/es{{ request.get_full_path }}">Spanish</a>
{% else %}
       <a href="/en{{ request.get_full_path }}">English</a>
{% endifequal %}

没有必要使用表单、URL和提交等,这对我有效。

3
request.get_full_path 返回的内容中包含语言组件,因此需要使用 |slice:'3:' 进行截取。 - Wtower
1
这是一个糟糕的答案和错误的方法,首先您正在硬编码URL,其次,这在文档中不被推荐。第三,get_full_path也返回语言代码。 - Angel F

2
除了这里建议添加的表单之外:

Original Answer

翻译成"最初的回答"
<form action="{% url 'set_language' %}" method="post">
  {% csrf_token %}
  {{ request.get_full_path_info|slice:'3:'}}
  <input name="next" type="hidden" value="{{ languageless_url }}" />
  <ul class="nav navbar-nav navbar-right language menu">
    {% get_current_language as LANGUAGE_CODE %}
    {% get_available_languages as LANGUAGES %}
    {% get_language_info_list for LANGUAGES as languages %}
    {% for language in languages %}
    <li>
      <button type="submit" name="language" value="{{ language.code }}"
        class="{% if language.code == LANGUAGE_CODE %}selected{% endif %}">
        {{ language.code }}
      </button>
    </li>
    {% endfor %}
  </ul>
</form>

我建议添加一个上下文处理器(app.context_processors.py):
最初的回答
def language_processor(request):
"""
This is needed for language picker to work
"""
return {
    'languageless_url':
    '/' + '/'.join(request.get_full_path().split('/')[2:])
}

这样可以将逻辑从模板中分离出来。 还要记得在模板设置中添加处理器:
            'context_processors': [
            'app.context_processors.language_processor',

2
如果你只需要两种语言,例如英语和法语,并且在settings.py文件中已经进行了定义,在你的主应用程序中正确配置了默认语言和urls.py。那么,只需在你的模板(或部分、顶部栏等)中使用以下代码:btn-kinito "btn-header 这些只是样式类,你可以使用CSS或JS来操作它们。
循环或迭代内部只是遍历LANGUAGES[]列表,这个列表在settings.py中定义,然后创建一个带有字符“|”和空格&nbsp;的按钮,以使其看起来可爱,因为我们只有两种语言。 {% url 'set_language' %} 是Django的重定向视图,称为set_language,它将重定向到URL。这就是为什么在你的主应用程序的urls.py文件中需要放置path('i18n/', include('django.conf.urls.i18n')),。因此,在为列表中的每种语言创建按钮之后,你将能够被重定向到该URL。
  <div class="btn-header">
    <form action="{% url 'set_language' %}" method="post">
      {% csrf_token %}
      <input name="next" type="hidden" value="{{ redirect_to }}" />
        {% get_current_language as LANGUAGE_CODE %}
        {% get_available_languages as LANGUAGES %}
        {% get_language_info_list for LANGUAGES as languages %}
        {% for language in languages %}
          <button type="submit" name="language" value="{{ language.code }}"
            class="btn-kinito">
            {{ language.code }}
          </button>|&nbsp;
        {% endfor %}
    </form>
  </div>

对于urls.py,我认为它可能看起来像这样:

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


# I don't want my admin translated
urlpatterns = [
    path('admin/', admin.site.urls),
]

urlpatterns += i18n_patterns (
    path('i18n/', include('django.conf.urls.i18n')),
    path('', include('pages.urls')),
    path('cats', include('cats.urls')),
    path('dogs', include('dogs.urls')),
    prefix_default_language=False,
 ) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
prefix_default_language=False,是可选的,它从url中删除默认语言前缀,如果你只有两种或三种语言,这是有意义的。虽然我以前遇到过问题,其中prefix_default_language=False,没有起作用。
如何解决prefix_default_language=False,无法工作或无法从url中删除默认语言前缀的问题:
在我的settings.py中,我将:LANGUAGE_CODE = 'en-us'改为LANGUAGE_CODE = 'en'
(似乎已经解决了问题)

你好Elias @elias-glyptis,感谢您提供的好建议。我已经按照您的建议进行了操作,一切都很顺利。但是我遇到了一个问题,就是当我选择语言时,URL模式看起来是正确的,页面也会刷新,但网页仍然以英文呈现。它没有使用我所做的翻译。您对此有什么建议吗?谢谢! - Michel Mesquita
1
嗨,Michel @MichelMesquita,欢迎您的到来 :) 我认为您可能在编译.po文件中遇到了问题。我在另一个问题上回答了这个问题,这是那个答案的链接 https://dev59.com/62855IYBdhLWcg3wYjSF#59937217 如果那个答案解决了编译问题,请告诉我。还有一个答案是关于您的env文件夹的位置,因为这也可能会导致问题,这是另一个链接,可以帮助您进行故障排除。https://dev59.com/62855IYBdhLWcg3wYjSF#59937217 - Elias Glyptis
抱歉,这是第二个链接 https://dev59.com/t14d5IYBdhLWcg3wDvC0#55717573 - Elias Glyptis
嗨@elias-glyptis,你发给我的链接非常有用。非常感谢!你详细的解释帮助我彻底检查了我的代码!非常感激!我进行了许多测试:本地和在Heroku上。但是翻译仍然没有生效。我想知道这是否与我Mac上的gettext有关?我已经使用brew安装了它。这是我正在构建的页面。单词“PL8 Shop”,“Products”和“Your cart is empty”应该被翻译。谢谢!http://pl8.herokuapp.com/ - Michel Mesquita

1
{% load i18n %}

{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}

 <div class="btn-header">
    <form action="{% url 'set_language' %}" method="post">
      {% csrf_token %}
      <input name="next" type="hidden" value="{{ redirect_to }}" />
        {% for language in languages %}
            {% if language.code != LANGUAGE_CODE %}
                <button type="submit" name="language" value="{{ language.code }}">{{ language.name }}</button>
            {% endif %}
        {% endfor %}
    </form>
  </div>

如果你有两种语言,它只会显示关闭状态的那一种。

https://www.loom.com/share/9319c0e9204f417a8eec897965ce3a96


0

似乎很多人(包括我自己)花了很多时间寻找适用于所有情况的正确解决方案。"诀窍"是如果您想在不同的语言中重新加载现有页面,则将next =''设置为。引用自Django文档:

在设置语言选择后,Django会查找POST或GET数据中的下一个参数。如果找到并且Django认为它是安全的URL(即它不指向不同的主机并使用安全的方案),则将重定向到该URL。否则,Django可能会回退到将用户重定向到Referer标头中的URL

最后,我想要一个可以通过链接、按钮、下拉菜单或其他方式触发任何语言更改的函数。我最终得到了这个小js函数:

django_language_set(language_code){
  url = "{% url 'set_language' %}";
  data = {
    language: language_code,
    next: '',
    csrfmiddlewaretoken: '{{ csrf_token }}'
  };
  this.form_post(url, data)
}

form_post(path, params, method='post') {
  /* simulates a post submit, call like:
    form_post('/home', {language: 'de', next: ''})"
  */
  const form = document.createElement('form');
  form.method = method;
  form.action = path;

  for (const key in params) {
    if (params.hasOwnProperty(key)) {
      const hiddenField = document.createElement('input');
      hiddenField.type = 'hidden';
      hiddenField.name = key;
      hiddenField.value = params[key];

      form.appendChild(hiddenField);
    }
  }

  document.body.appendChild(form);
  form.submit();
}

调用函数,例如:

<a @click="django_language_set('fr')">French</a>

0

Django 3.02

<div class="uk-flex">

    {% get_current_language as LANGUAGE_CODE %}
    {% get_available_languages as LANGUAGES %}
    {% get_language_info_list for LANGUAGES as languages %}
    <div class="languages">
        <p>{% trans "Language" %}:</p>
        <ul class="languages">
        {% for language in languages %}
            <li>
                <a href="/{{ language.code }}/{{ request.get_full_path |slice:'4:'}}" {% if language.code == LANGUAGE_CODE %} class="selected"{% endif %}>
                {{ language.name_local }}
                </a>
            </li>
        {% endfor %}
        </ul>
    </div>
</div>

0
我添加了一个带有按钮和国旗图标的解决方案。
 <form action="{% url 'set_language' %}" method="post">
    {% csrf_token %}
    {% get_current_language as LANGUAGE_CODE %}
    <input name="next" type="hidden" value="{{ languageless_url }}" />
    <a class="btn  dropdown-sm-toggle" role="button" data-bs-toggle="dropdown" aria-expanded="false">
      <img src="/static/img/flags/{{LANGUAGE_CODE}}.svg" width="20" height="30"></a>
    </a>
    <ul class="dropdown-menu dropdown-menu-end" id="language-list">
      {% get_available_languages as LANGUAGES %}
      {% get_language_info_list for LANGUAGES as languages %}
      {% for language in languages %}
      <li>
        <button class="dropdown-item" type="submit" name="language" value="{{ language.code }}"
          class="{% if language.code == LANGUAGE_CODE %}selected{% endif %}">
          <img src="/static/img/flags/{{ language.code }}.svg" width="20" height="30">&nbsp;&nbsp;
          {#{{ language.code }}#}{{ language.name_local }}
        </button>
      </li>
      {% endfor %}
    </ul>
  </form>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接