《Django By Example》第九章 上 中文 翻譯 (個(gè)人學(xué)習(xí),渣翻)

全文鏈接

第一章 創(chuàng)建一個(gè)blog應(yīng)用
第二章 使用高級(jí)特性來(lái)增強(qiáng)你的blog
第三章 擴(kuò)展你的blog應(yīng)用
第四章上 創(chuàng)建一個(gè)社交網(wǎng)站
第四章下 創(chuàng)建一個(gè)社交網(wǎng)站
第五章 在你的網(wǎng)站中分享內(nèi)容
第六章 跟蹤用戶動(dòng)作
第七章 建立一個(gè)在線商店
第八章 管理付款和訂單
第九章上 擴(kuò)展你的商店
第九章下 擴(kuò)展你的商店
第十章上 創(chuàng)建一個(gè)在線學(xué)習(xí)平臺(tái)
第十章下 創(chuàng)建一個(gè)在線學(xué)習(xí)平臺(tái)
第十一章 緩存內(nèi)容
第十二章 構(gòu)建一個(gè)API

書(shū)籍出處:https://www.packtpub.com/web-development/django-example
原作者:Antonio Melé

(審校@夜夜月:本章分上下兩部分,這里是上半部。)

(譯者@ucag 注:哈哈哈,第九章終于來(lái)啦。這是在線商店的最后一章,下一章將會(huì)開(kāi)始一個(gè)新的項(xiàng)目。所以這一章的內(nèi)容會(huì)偏難,最難的是推薦引擎的編寫(xiě),其中的算法可能還需要各位好好的推敲,如果看了中文的看不懂,大家可以去看看英文原版的書(shū)以及相關(guān)的文檔。最近我也在研究機(jī)器學(xué)習(xí),有興趣的大家可以一起交流哈~)

(審校@夜夜月:大家好,我是來(lái)打醬油的~,粗校,主要更正了一些錯(cuò)字和格式,精校正進(jìn)行到四章樣子。)

第九章(上)

擴(kuò)展你的商店

在上一章中,你學(xué)習(xí)了如何把支付網(wǎng)關(guān)整合進(jìn)你的商店。你處理了支付通知,學(xué)會(huì)了如何生成 CSV 和 PDF 文件。在這一章中,你會(huì)把優(yōu)惠券添加進(jìn)你的商店中。你將學(xué)到國(guó)際化(internationalization)和本地化(localization)是如何工作的,你還會(huì)創(chuàng)建一個(gè)推薦引擎。
在這一章中將會(huì)包含一下知識(shí)點(diǎn):

  • 創(chuàng)建一個(gè)優(yōu)惠券系統(tǒng)來(lái)應(yīng)用折扣
  • 把國(guó)際化添加進(jìn)你的項(xiàng)目中
  • 使用 Rosetta 來(lái)管理翻譯
  • 使用 django-parler 來(lái)翻譯模型(model)
  • 建立一個(gè)產(chǎn)品推薦引擎

創(chuàng)建一個(gè)優(yōu)惠券系統(tǒng)

很多的在線商店會(huì)送出很多優(yōu)惠券,這些優(yōu)惠券可以在顧客的采購(gòu)中兌換為相應(yīng)的折扣。在線優(yōu)惠券通常是由一串給顧客的代碼構(gòu)成,這串代碼在一個(gè)特定的時(shí)間段內(nèi)是有效的。這串代碼可以被兌換一次或者多次。

我們將會(huì)為我們的商店創(chuàng)建一個(gè)優(yōu)惠券系統(tǒng)。優(yōu)惠券將會(huì)在顧客在某一個(gè)特定的時(shí)間段內(nèi)輸入時(shí)生效。優(yōu)惠券沒(méi)有任何兌換數(shù)的限制,他們也可用于購(gòu)物車(chē)的總金額中。對(duì)于這個(gè)功能,我們將會(huì)創(chuàng)建一個(gè)模型(model)來(lái)儲(chǔ)存優(yōu)惠券代碼,優(yōu)惠券有效的時(shí)間段,以及折扣的力度。

myshop 項(xiàng)目?jī)?nèi)使用如下命令創(chuàng)建一個(gè)新的應(yīng)用:

python manage.py startapp coupons

編輯 myshopsettings.py 文件,像下面這樣把把應(yīng)用添加到 INSTALLED_APPS 中:

INSTALLED_APPS = (
      # ...
      'coupons',
)

新的應(yīng)用已經(jīng)在我們的 Django 項(xiàng)目中激活了。

創(chuàng)建優(yōu)惠券模型(model)

讓我們開(kāi)始創(chuàng)建 Coupon 模型(model)。編輯 coupons 應(yīng)用中的 models.py 文件,添加以下代碼:

from django.db import models
from django.core.validators import MinValueValidator,\
                                    MaxValueValidator
class Coupon(models.Model):
    code = models.CharField(max_length=50,
                            unique=True)
    valid_from = models.DateTimeField()
    valid_to = models.DateTimeField()
    discount = models.IntegerField(
                validators=[MinValueValidator(0),
                            MaxValueValidator(100)])
    active = models.BooleanField()
    
    def __str__(self):
        return self.code

我們將會(huì)用這個(gè)模型(model)來(lái)儲(chǔ)存優(yōu)惠券。 Coupon 模型(model)包含以下幾個(gè)字段:

  • code:用戶必須要輸入的代碼來(lái)將優(yōu)惠券應(yīng)用到他們購(gòu)買(mǎi)的商品中
  • valid_from:表示優(yōu)惠券會(huì)在何時(shí)生效的時(shí)間和日期值
  • valid_to:表示優(yōu)惠券會(huì)在何時(shí)過(guò)期
  • discount:折扣率(這是一個(gè)百分比,所以它的值的范圍是 0 到 1000)。我們使用驗(yàn)證器來(lái)限制接收的最小值和最大值
  • active:表示優(yōu)惠券是否激活的布爾值

執(zhí)行下面的命令來(lái)為 coupons 生成首次遷移:

python manage.py makemigrations

輸出應(yīng)該包含以下這幾行:

Migrations for 'coupons':
    0001_initial.py:
        - Create model Coupon

之后我們執(zhí)行下面的命令來(lái)應(yīng)用遷移:

python manage.py migrate

你可以看見(jiàn)包含下面這一行的輸出:

Applying coupons.0001_initial... OK

遷移現(xiàn)在已經(jīng)被應(yīng)用到了數(shù)據(jù)庫(kù)中。讓我們把 Coupon 模型(model)添加到管理站點(diǎn)。編輯 coupons 應(yīng)用的 admin.py 文件,添加以下代碼:

from django.contrib import admin
from .models improt Coupon

class CouponAdmin(admin.ModelAdmin):
    list_display = ['code', 'valid_from', 'valid_to',
                    'discount', 'active']
    list_filter = ['active', 'valid_from', 'valid_to']
    search_fields = ['code']
admin.site.register(Coupon, CouponAdmin)

Coupon 模型(model)現(xiàn)在已經(jīng)注冊(cè)進(jìn)了管理站點(diǎn)中。確保你已經(jīng)用命令 python manage.py runserver 打開(kāi)了開(kāi)發(fā)服務(wù)器。訪問(wèn) http://127.0.0.1:8000/admin/coupons/add 。你可以看見(jiàn)下面的表單:

django-9-1

填寫(xiě)表單創(chuàng)建一個(gè)在當(dāng)前日期有效的新優(yōu)惠券,確保你點(diǎn)擊了 Active 復(fù)選框,然后點(diǎn)擊 Save按鈕。

把應(yīng)用優(yōu)惠券到購(gòu)物車(chē)中

我們可以保存新的優(yōu)惠券以及檢索目前的優(yōu)惠券?,F(xiàn)在我們需要一個(gè)方法來(lái)讓顧客可以應(yīng)用他們的優(yōu)惠券到他們購(gòu)買(mǎi)的產(chǎn)品中?;c(diǎn)時(shí)間來(lái)想想這個(gè)功能該如何實(shí)現(xiàn)。應(yīng)用一張優(yōu)惠券的流程如下:

1. 用戶將產(chǎn)品添加進(jìn)購(gòu)物車(chē)
2. 用戶在購(gòu)物車(chē)詳情頁(yè)的表單中輸入優(yōu)惠代碼
3. 當(dāng)用戶輸入優(yōu)惠代碼然后提交表單時(shí),我們查找一張和所給優(yōu)惠代碼相符的有效優(yōu)惠券。我們必須檢查用戶輸入的優(yōu)惠券代碼, `active` 屬性為 `True` ,當(dāng)前時(shí)間位于 `valid_from 和 `valid_to` 之間。
4. 如果查找到了相應(yīng)的優(yōu)惠券,我們把它保存在用戶會(huì)話中,然后展示包含折扣了的購(gòu)物車(chē)以及更新總價(jià)。
5. 當(dāng)用戶下單時(shí),我們把優(yōu)惠券保存到所給的訂單中。

coupons 應(yīng)用路徑下創(chuàng)建一個(gè)新的文件,命名為 forms.py 文件,添加以下代碼:

from django import forms

class CouponApplyForm(forms.Form):
    code = forms.CharField()

我們將會(huì)用這個(gè)表格來(lái)讓用戶輸入優(yōu)惠券代碼。編輯 coupons 應(yīng)用中的 views.py 文件,添加以下代碼:

from django.shortcuts import render, redirect
from django.utils import timezone
from django.views.decorators.http import require_POST
from .models import Coupon
from .forms import CouponApplyForm

@require_POST
def coupon_apply(request):
    now = timezone.now()
    form = CouponApplyForm(request.POST)
    if form.is_valid():
        code = form.cleaned_data['code']
        try:
            coupon = Coupon.objects.get(code__iexact=code,
                                    valid_from__lte=now,
                                    valid_to__gte=now,
                                    active=True)
            request.session['coupon_id'] = coupon.id
        except Coupon.DoesNotExist:
            request.session['coupon_id'] = None
    return redirect('cart:cart_detail')

coupon_apply 視圖(view)驗(yàn)證優(yōu)惠券然后把它保存在用戶會(huì)話(session)中。我們使用 require_POST 裝飾器來(lái)限制這個(gè)視圖(view)僅接受 POST 請(qǐng)求。在視圖(view)中,我們執(zhí)行了以下幾個(gè)任務(wù):

1.我們用上傳的數(shù)據(jù)實(shí)例化了 `CouponApplyForm` 然后檢查表單是否合法。
2. 如果表單是合法的,我們就從表單的 `cleaned_data` 字典中獲取 `code` 。我們嘗試用所給的代碼檢索 `Coupon` 對(duì)象。我們使用 `iexact` 字段來(lái)對(duì)照查找大小寫(xiě)不敏感的精確匹配項(xiàng)。優(yōu)惠券在當(dāng)前必須是激活的(`active=True`)以及必須在當(dāng)前日期內(nèi)是有效的。我們使用 Django 的 `timezone.now()` 函數(shù)來(lái)獲得當(dāng)前的時(shí)區(qū)識(shí)別時(shí)間和日期(time-zone-aware) 然后我們把它和 `valid_from` 和 `valid_to` 字段做比較,對(duì)這兩個(gè)日期分別執(zhí)行 `lte` (小于等于)運(yùn)算和 `gte` (大于等于)運(yùn)算來(lái)進(jìn)行字段查找。
3. 我們?cè)谟脩舻臅?huì)話中保存優(yōu)惠券的 `id``。
4. 我們把用戶重定向到 `cart_detail` URL 來(lái)展示應(yīng)用了優(yōu)惠券的購(gòu)物車(chē)。

我們需要一個(gè) coupon_apply 視圖(view)的 URL 模式。在 coupon 應(yīng)用路徑下創(chuàng)建一個(gè)新的文件,命名為 urls.py ,添加以下代碼:

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^apply/$', views.coupon_apply, name='apply'),
]

然后,編輯 myshop 項(xiàng)目的主 urls.py 文件,引入 coupons 的 URL 模式:

url(r'^coupons/', include('coupons.urls', namespace='coupons')),

記得把這個(gè)放在 shop.urls 模式之前。

現(xiàn)在編輯 cart 應(yīng)用的 cart.py,包含以下導(dǎo)入:

from coupons.models import Coupon

把下面這行代碼添加進(jìn) Cart 類(lèi)的 __init__() 方法中來(lái)從會(huì)話中初始化優(yōu)惠券:

# store current applied coupon
self.coupon_id = self.session.get('coupon_id')

在這行代碼中,我們嘗試從當(dāng)前會(huì)話中得到 coupon_id 會(huì)話鍵,然后把它保存在 Cart 對(duì)象中。把以下方法添加進(jìn) Cart 對(duì)象中:

@property
def coupon(self):
    if self.coupon_id:
        return Coupon.objects.get(id=self.coupon_id)
    return None

def get_discount(self):
    if self.coupon:
        return (self.coupon.discount / Decimal('100')) \
                * self.get_total_price()
    return Decimal('0')

def get_total_price_after_discount(self):
    return self.get_total_price() - self.get_discount()

下面是這幾個(gè)方法:

  • coupon():我們定義這個(gè)方法作為 property 。如果購(gòu)物車(chē)包含 coupon_id 函數(shù),就會(huì)返回一個(gè)帶有給定 idCoupon 對(duì)象
  • get_discount():如果購(gòu)物車(chē)包含 coupon ,我們就檢索它的折扣比率,然后返回購(gòu)物車(chē)中被扣除折扣的總和。
  • get_total_price_after_discount():返回被減去折扣之后的總價(jià)。

Cart 類(lèi)現(xiàn)在已經(jīng)準(zhǔn)備好處理應(yīng)用于當(dāng)前會(huì)話的優(yōu)惠券了,然后將它應(yīng)用于相應(yīng)折扣中。

讓我們?cè)谫?gòu)物車(chē)詳情視圖(view)中引入優(yōu)惠券系統(tǒng)。編輯 cart 應(yīng)用的 views.py ,然后把下面這一行添加到頂部:

from coupons.forms import CouponApplyForm

之后,編輯 cart_detail 視圖(view),然后添加新的表單:

def cart_detail(request):
    cart = Cart(request)
    for item in cart:
        item['update_quantity_form'] = CartAddProductForm(
                    initial={'quantity': item['quantity'],
                    'update': True})
    coupon_apply_form = CouponApplyForm()
    return render(request,
            'cart/detail.html',
            {'cart': cart,
            'coupon_apply_form': coupon_apply_form})

編輯 cart 應(yīng)用的 acrt/detail.html 文件,找到下面這幾行:

<tr class="total">
    <td>Total</td>
    <td colspan="4"></td>
    <td class="num">${{ cart.get_total_price }}</td>
</tr>

把它們換成以下幾行:

{% if cart.coupon %}
<tr class="subtotal">
    <td>Subtotal</td>
    <td colspan="4"></td>
    <td class="num">${{ cart.get_total_price }}</td>
</tr>
<tr>
<td>
    "{{ cart.coupon.code }}" coupon
    ({{ cart.coupon.discount }}% off)
    </td>
    <td colspan="4"></td>
    <td class="num neg">
    - ${{ cart.get_discount|floatformat:"2" }}
    </td>
</tr>
{% endif %}
<tr class="total">
    <td>Total</td>
    <td colspan="4"></td>
    <td class="num">
    ${{ cart.get_total_price_after_discount|floatformat:"2" }}
    </td>
</tr>

這段代碼用于展示可選優(yōu)惠券以及折扣率。如果購(gòu)物車(chē)中有優(yōu)惠券,我們就在第一行展示購(gòu)物車(chē)的總價(jià)作為 小計(jì)。然后我們?cè)诘诙姓故井?dāng)前可應(yīng)用于購(gòu)物車(chē)的優(yōu)惠券。最后,我們調(diào)用 cart 對(duì)象的 get_total_price_after_discount() 方法來(lái)展示折扣了的總價(jià)格。

在同一個(gè)文件中,在 </table> 標(biāo)簽之后引入以下代碼:

<p>Apply a coupon:</p>
<form action="{% url "coupons:apply" %}" method="post">
    {{ coupon_apply_form }}
    <input type="submit" value="Apply">
    {% csrf_token %}
</form>

我們將會(huì)展示一個(gè)表單來(lái)讓用戶輸入優(yōu)惠券代碼,然后將它應(yīng)用于當(dāng)前的購(gòu)物車(chē)當(dāng)中。

訪問(wèn) http://127.0.0.1:8000 ,向購(gòu)物車(chē)當(dāng)中添加一個(gè)商品,然后在表單中輸入你創(chuàng)建的優(yōu)惠代碼來(lái)應(yīng)用你的優(yōu)惠券。你可以看到購(gòu)物車(chē)像下面這樣展示優(yōu)惠券折扣:

django-9-2

讓我們把優(yōu)惠券添加到購(gòu)物流程中的下一步。編輯 orders 應(yīng)用的 orders/order/create.html 模板(template),找到下面這幾行:

<ul>
{% for item in cart %}
<li>
    {{ item.quantity }}x {{ item.product.name }}
    <span>${{ item.total_price }}</span>
</li>
{% endfor %}
</ul>

把它替換為下面這幾行:

<ul>
{% for item in cart %}
    <li>
    {{ item.quantity }}x {{ item.product.name }}
    <span>${{ item.total_price }}</span>
    </li>
{% endfor %}
{% if cart.coupon %}
<li>
"{{ cart.coupon.code }}" ({{ cart.coupon.discount }}% off)
<span>- ${{ cart.get_discount|floatformat:"2" }}</span>
</li>
{% endif %}
</ul>

訂單匯總現(xiàn)在已經(jīng)包含使用了的優(yōu)惠券,如果有優(yōu)惠券的話?,F(xiàn)在找到下面這一行:

<p>Total: ${{ cart.get_total_price }}</p>

把他們換成以下一行:

<p>Total: ${{ cart.get_total_price_after_discount|floatformat:"2" }}</p>

這樣做之后,總價(jià)也將會(huì)在減去優(yōu)惠券折扣被計(jì)算出來(lái)。
訪問(wèn) http://127.0.0.1:8000/orders/create/ 。你會(huì)看到包含使用了優(yōu)惠券的訂單小計(jì):

django-9-3

用戶現(xiàn)在可以在購(gòu)物車(chē)當(dāng)中使用優(yōu)惠券了。盡管,當(dāng)用戶結(jié)賬時(shí),我們依然需要在訂單中儲(chǔ)存優(yōu)惠券信息。

在訂單中使用優(yōu)惠券

我們會(huì)儲(chǔ)存每張訂單中使用的優(yōu)惠券。首先,我們需要修改 Order 模型(model)來(lái)儲(chǔ)存相關(guān)聯(lián)的 Coupon 對(duì)象,如果有這個(gè)對(duì)象的話。

編輯 orders 應(yīng)用的 models.py 文件,添加以下代碼:

from decimal import Decimal
from django.core.validators import MinValueValidator, \
                                    MaxValueValidator
from coupons.models import Coupon

然后,把下列字段添加進(jìn) Order 模型(model)中:

coupon = models.ForeignKey(Coupon,
                            related_name='orders',
                            null=True,
                            blank=True)
discount = models.IntegerField(default=0,
                        validators=[MinValueValidator(0),
                                MaxValueValidator(100)])

這些字段讓用戶可以在訂單中儲(chǔ)存可選的優(yōu)惠券信息,以及優(yōu)惠券的相應(yīng)折扣。折扣被存在關(guān)聯(lián)的 Coupon 對(duì)象中,但是我們?cè)?Order 模型(model)中引入它以便我們?cè)趦?yōu)惠券被更改或者刪除時(shí)好保存它。

因?yàn)?Order 模型(model)已經(jīng)被改變了,我們需要?jiǎng)?chuàng)建遷移。執(zhí)行下面的命令:

python manage.py makemigrations

你可以看到如下輸出:

Migrations for 'orders':
    0002_auto_20150606_1735.py:
        - Add field coupon to order
        - Add field discount to order

用下面的命令來(lái)執(zhí)行遷移:

python manage.py migrate orders

你必須要確保新的遷移已經(jīng)被應(yīng)用了。 Order 模型(model)的字段變更現(xiàn)在已經(jīng)同步到了數(shù)據(jù)庫(kù)中。

回到 models.py 文件中,按照如下更改 Order 模型(model)的 get_total_cost() 方法:

def get_total_cost(self):
    total_cost = sum(item.get_cost() for item in self.items.all())
    return total_cost - total_cost * \
        (self.discount / Decimal('100'))

Order 模型(model)的 get_total_cost() 現(xiàn)在已經(jīng)把使用了的折扣包含在內(nèi)了,如果有折扣的話。

編輯 orders 應(yīng)用的 views.py 文件,更改 order_create 視圖(view)以便在創(chuàng)建新的訂單時(shí)保存相關(guān)聯(lián)的優(yōu)惠券和折扣。找到下面這一行:

order = form.save()

把它替換為下面這幾行:

order = form.save(commit=False)
if cart.coupon:
    order.coupon = cart.coupon
    order.discount = cart.coupon.discount
order.save()

在新的代碼中,我們使用 OrderCrateForm 表單的 save() 方法創(chuàng)建了一個(gè) Order 對(duì)象。我們使用 commit=False 來(lái)避免將它保存在數(shù)據(jù)庫(kù)中。如果購(gòu)物車(chē)當(dāng)中有優(yōu)惠券,我們就會(huì)保存相關(guān)聯(lián)的優(yōu)惠券和折扣。然后才把 order 對(duì)象保存到數(shù)據(jù)庫(kù)中。

確保用 python manage.py runserver 運(yùn)行了開(kāi)發(fā)服務(wù)器。使用 ./ngrok http:8000 命令來(lái)啟動(dòng) Ngrok 。

在你的瀏覽器中打開(kāi) Ngrok 提供的 URL , 然后用用你創(chuàng)建的優(yōu)惠券完成一次購(gòu)物。當(dāng)你完成一次成功的支付后,有可以訪問(wèn) http://127.0.0.1:8000/admin/orders/order/ ,檢查 order 對(duì)象是否包含了優(yōu)惠券和折扣,如下:

django-9-4

你也可以更改管理界面的訂單詳情模板(template)和訂單的 PDF 賬單,以便用展示購(gòu)物車(chē)的方式來(lái)展示使用了的優(yōu)惠券。

下面。我們將會(huì)為我們的項(xiàng)目添加國(guó)際化(internationalization)。

添加國(guó)際化(internationalization)和本地化(localization)

Django 提供了完整的國(guó)際化和本地化的支持。這使得你可以把你的項(xiàng)目翻譯為多種語(yǔ)言以及處理地區(qū)特性的 日期,時(shí)間,數(shù)字,和時(shí)區(qū) 的格式。讓我們一起來(lái)搞清楚國(guó)際化和本地化的區(qū)別。國(guó)際化(通常簡(jiǎn)寫(xiě)為:i18n)是為讓軟件適應(yīng)潛在的不同語(yǔ)言和多種語(yǔ)言的使用做的處理,這樣它就不是以某種特定語(yǔ)言或某幾種語(yǔ)言為硬編碼的軟件了。本地化(簡(jiǎn)寫(xiě)為:l10n)是對(duì)軟件的翻譯以及使軟件適應(yīng)某種特定語(yǔ)言的處理。Django 自身已經(jīng)使用自帶的國(guó)際化框架被翻譯為了50多種語(yǔ)言。

使用 Django 國(guó)際化##

國(guó)際化框架讓你可以容易的在 Python 代碼和模板(template)中標(biāo)記需要翻譯的字符串。它依賴于 GNU 文本獲取集來(lái)生成和管理消息文件。消息文件是一個(gè)表示一種語(yǔ)言的純文本文件。它包含某一語(yǔ)言的一部分或者所有的翻譯字符串。消息文件有 .po 擴(kuò)展名。

一旦翻譯完成,信息文件就會(huì)被編譯一遍快速的連接到被翻譯的字符串。編譯后的翻譯文件的擴(kuò)展名為 .mo 。

國(guó)際化和本地化設(shè)置

Django 提供了幾種國(guó)際化的設(shè)置。下面是幾種最有關(guān)聯(lián)的設(shè)置:

  • USE_I18N:布爾值。用于設(shè)定 Django 翻譯系統(tǒng)是否啟動(dòng)。默認(rèn)值為 True。
    -USE_L10N:布爾值。表示本地格式化是否啟動(dòng)。當(dāng)被激活式,本地格式化被用于展示日期和數(shù)字。默認(rèn)為 False 。
  • USE_TZ:布爾值。用于指定日期和時(shí)間是否是時(shí)區(qū)別(timezone-aware)。
  • LANGUAGE_CODE:項(xiàng)目的默認(rèn)語(yǔ)言。在標(biāo)準(zhǔn)的語(yǔ)言 ID 格式中,比如,en-us 是美式英語(yǔ),en-gb 是英式英語(yǔ)。這個(gè)設(shè)置要 USE_I18NTrue 才能生效。你可以在這個(gè)網(wǎng)站找到一個(gè)合法的語(yǔ)言 ID 表:http://www.i18nguy.com/unicode/language-identifiers.html 。
  • LANGUAGES:包含項(xiàng)目可用語(yǔ)言的元組。它們由包含 語(yǔ)言代碼語(yǔ)言名字 的雙元組構(gòu)成的。你可以在 django.conf.global_settions 里看到可用語(yǔ)言的列表。當(dāng)你選擇你的網(wǎng)站將會(huì)使用哪一種語(yǔ)言時(shí),你就把 LANGUAGES 設(shè)置為那個(gè)列表的子列表。
  • LOCAL_PATHS:Django 尋找包含翻譯的信息文件的路徑列表。
  • TIME_ZONE:代表項(xiàng)目時(shí)區(qū)的字符串。當(dāng)你使用 startproject 命令創(chuàng)建新項(xiàng)目時(shí)它被設(shè)置為 UTC 。你也可以把它設(shè)置為其他的時(shí)區(qū),比如 Europe/Madrid 。

這是一些可用的國(guó)際化和本地化的設(shè)置。你可以在這個(gè)網(wǎng)站找到全部的(設(shè)置)列表: https://docs.djangoproject.com/en/1.8/ref/settings/#globalization-i18n-l10n

國(guó)際化管理命令

Django 使用 manage.py 或者 django-admin.py 工具包管理翻譯,包含以下命令:

  • makemessages:運(yùn)行于源代碼樹(shù)中,尋找所有被用于翻譯的字符串,然后在 locale 路徑中創(chuàng)建或更新 .po 信息文件。每一種語(yǔ)言創(chuàng)建一個(gè)單一的 .po 文件。
  • compilemessages: 把擴(kuò)展名為 .po 的信息文件編譯為用于檢索翻譯的 .mo 文件。

你需要文本獲取工具集來(lái)創(chuàng)建,更新,以及編譯信息文件。大部分的 Linux 發(fā)行版都包含文本獲取工具集。如果你在使用 Mac OS X,用 Honebrew (http://brew.sh)是最簡(jiǎn)單的安裝它的方法,使用以下命令 :brew install gettext。你或許也需要將它和命令行強(qiáng)制連接 brew link gettext --force 。對(duì)于 Windows ,安裝步驟如下 : https://docs.djangoproject.com/en/1.8/topics/i18n/translation/#gettext-on-windows 。

怎么把翻譯添加到 Django 項(xiàng)目中

讓我們看看國(guó)際化項(xiàng)目的過(guò)程。我們需要像下面這樣做:

1. 我們?cè)?Python 代碼和模板(template)中中標(biāo)記需要翻譯的字符串。
2. 我們運(yùn)行 `makemessages` 命令來(lái)創(chuàng)建或者更新包含所有翻譯字符串的信息文件。
3. 我們翻譯包含在信息文件中的字符串,然后使用  `compilemessages` 編譯他們。

Django 如何決定當(dāng)前語(yǔ)言

Django 配備有一個(gè)基于請(qǐng)求數(shù)據(jù)的中間件,這個(gè)中間件用于決定當(dāng)前語(yǔ)言是什么。這個(gè)中間件是位于 django.middleware.locale.localMiddlewareLocaleMiddleware ,它執(zhí)行下面的任務(wù):

1. 如果使用 `i18_patterns` —— 這是你使用的被翻譯的 URL 模式,它在被請(qǐng)求的 URL 中尋找一個(gè)語(yǔ)言前綴來(lái)決定當(dāng)前語(yǔ)言。
2. 如果沒(méi)有找到語(yǔ)言前綴,就會(huì)在當(dāng)前用戶會(huì)話中尋找當(dāng)前的 `LANGUAGE_SESSION_KEY` 。
3. 如果在會(huì)話中沒(méi)有設(shè)置語(yǔ)言,就會(huì)尋找?guī)в挟?dāng)前語(yǔ)言的 cookie 。這個(gè) cookie 的定制名可在 `LANGUAGE_COOKIE_NAME` 中設(shè)置。默認(rèn)地,這個(gè) cookie 的名字是 `django_language` 。
4. 如果沒(méi)有找到 cookie,就會(huì)在請(qǐng)求 HTTP 頭中尋找 `Accept-Language` 。
5. 如果 `Accept-Language` 頭中沒(méi)有指定語(yǔ)言,Django 就使用在 `LANGUAGE_CODE` 設(shè)置中定義的語(yǔ)言。

默認(rèn)的,Django 會(huì)使用 LANGUAGE_OCDE 中設(shè)置的語(yǔ)言,除非你正在使用 LocaleMiddleware 。上述操作進(jìn)會(huì)在使用這個(gè)中間件時(shí)生效。

為我們的項(xiàng)目準(zhǔn)備國(guó)際化

讓我們的項(xiàng)目準(zhǔn)備好使用不同的語(yǔ)言吧。我們將要為我們的商店創(chuàng)建英文版和西班牙語(yǔ)版。編輯項(xiàng)目中的 settings.py 文件,添加下列 LANGUAGES 設(shè)置。把它放在 LANGUAGE_OCDE 旁邊:

LANGUAGES = (
        ('en', 'English'),
        ('es', 'Spanish'),
)

LANGUAGES 設(shè)置包含兩個(gè)由語(yǔ)言代碼和語(yǔ)言名的元組構(gòu)成,比如 en-usen-gb ,或者一般的設(shè)置為 en 。這樣設(shè)置之后,我們指定我們的應(yīng)用只會(huì)對(duì)英語(yǔ)和西班牙語(yǔ)可用。如果我們不指定 LANGUAGES 設(shè)置,網(wǎng)站將會(huì)對(duì)所有 Django 的翻譯語(yǔ)言有效。

確保你的 LANGUAGE_OCDE 像如下設(shè)置:

LANGUAGE_OCDE = 'en'

django.middleware.locale.LocaleMiddleware 添加到 MIDDLEWARE_CLASSES 設(shè)置中。確保這個(gè)設(shè)置在 SessionsMiddleware 之后,因?yàn)?LocaleMiddleware 需要使用會(huì)話數(shù)據(jù)。它同樣必須放在 CommonMiddleware 之前,因?yàn)楹笳咝枰环N激活了的語(yǔ)言來(lái)解析請(qǐng)求 URL 。MIDDLEWARE_CLASSES 設(shè)置看起來(lái)應(yīng)該如下:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ...
)

中間件的順序非常重要,因?yàn)槊總€(gè)中間件所依賴的數(shù)據(jù)可能是由其他中間件處理之后的才獲得的。中間件按照 MIDDLEWARE_CLASSES 的順序應(yīng)用在每個(gè)請(qǐng)求上,以及反序應(yīng)用于響應(yīng)中。

在主項(xiàng)目路徑下,在 manage.py 文件同級(jí),創(chuàng)建以下文件結(jié)構(gòu):

locale/
    en/
    es/

locale 路徑是放置應(yīng)用信息文件的地方。再次編輯 settings.py ,然后添加以下設(shè)置:

LOCALE_PATHS = (
    os.path.join(BASE_DIR, 'locale/'),
)

LOCALE_PATHS 設(shè)置指定了 Django 尋找翻譯文件的路徑。第一個(gè)路徑有最高優(yōu)先權(quán)。

當(dāng)你在你的項(xiàng)目路徑下使用 makemessages 命令時(shí),信息文件將會(huì)在 locale 路徑下生成。盡管,對(duì)于包含 locale 路徑的應(yīng)用,信息文件就會(huì)保存在這個(gè)應(yīng)用的 locale 路徑中。

翻譯 Python 代碼

我們翻譯在 Python 代碼中的字母,你可以使用 django.utils.translation 中的 gettext() 函數(shù)來(lái)標(biāo)記需要翻譯的字符串。這個(gè)函數(shù)翻譯信息然后返回一個(gè)字符串。約定俗成的用法是導(dǎo)入這個(gè)函數(shù)后把它命名為 _ (下劃線)。

你可以在這個(gè)網(wǎng)站找到所有關(guān)于翻譯的文檔 : https://docs.djangoproject.com/en/1.8/topics/i18n/translation/ 。

標(biāo)準(zhǔn)翻譯

下面的代碼展示了如何標(biāo)記一個(gè)翻譯字符串:

from django.utils.translation import gettext as _
output = _('Text to be translated.')

惰性翻譯(Lazy translation)

Django 對(duì)所有的翻譯函數(shù)引入了惰性(lazy)版,這些惰性函數(shù)都帶有后綴 _lazy() 。當(dāng)使用惰性函數(shù)時(shí),字符串在值被連接時(shí)就會(huì)被翻譯,而不是在被調(diào)用時(shí)翻譯(這也是它為什么被惰性翻譯的原因)。惰性函數(shù)遲早會(huì)派上用場(chǎng),特別是當(dāng)標(biāo)記字符串在模型(model)加載的執(zhí)行路徑中時(shí)。

使用 gettext_lazy() 而不是 gettext() ,字符串就會(huì)在連接到值的時(shí)候被翻譯而不會(huì)在函數(shù)調(diào)用的時(shí)候被翻譯。Django 為所有的翻譯都提供了惰性版本。

翻譯引入的變量

標(biāo)記的翻譯字符串可以包含占位符來(lái)引入翻譯中的變量。下面就是一個(gè)帶有占位符的翻譯字字符串的例子:

from django.utils.translation import gettext as _
month = _('April')
day = '14'
output = _('Today is %(month)s %(day)s') % {'month': month,
                                            'day': day}

通過(guò)使用占位符,你可以重新排序文字變量。舉個(gè)例子,以前的英文版本是 'Today is April 14' ,但是西班牙的版本是這樣的 'Hoy es 14 de Abril' 。當(dāng)你的翻譯字符串有多于一個(gè)參數(shù)時(shí),我們總是使用字符串插值來(lái)代替位置插值

翻譯中的復(fù)數(shù)形式

對(duì)于復(fù)數(shù)形式,你可以使用 gettext()gettext_lazy() 。這些函數(shù)基于一個(gè)可以表示對(duì)象數(shù)量的參數(shù)來(lái)翻譯單數(shù)和復(fù)數(shù)形式。下面這個(gè)例子展示了如何使用它們:

output = ngettext('there is %(count)d product',
                'there are %(count)d products',
                count) % {'count': count}

現(xiàn)在你已經(jīng)基本知道了如何在你的 Python 代碼中翻譯字符了,現(xiàn)在是把翻譯應(yīng)用到項(xiàng)目中的時(shí)候了。

翻譯你的代碼

編輯項(xiàng)目中的 settings.py ,導(dǎo)入 gettext_lazy() 函數(shù),然后像下面這樣修改 LANGUAGES 的設(shè)置:

from django.utils.translation import gettext_lazy as _

LANGUAGES = (
    ('en', _('English')),
    ('es', _('Spanish')),
)

我們使用 gettext_lazy() 而不是 gettext() 來(lái)避免循環(huán)導(dǎo)入,這樣就可以在語(yǔ)言名被連接時(shí)就翻譯它們。

在你的項(xiàng)目路徑下執(zhí)行下面的命令:

django-admin makemessages --all

你可以看到如下輸出:

processing locale es
processing locale en

看下 locale/ 路徑。你可以看到如下文件結(jié)構(gòu):

en/
    LC_MESSAGES/
        django.po
es/
    LC_MESSAGES/
        django.po

.po 文件已經(jīng)為每一個(gè)語(yǔ)言創(chuàng)建好了。用文本編輯器打開(kāi) es/LC_MESSAGES/django.po 。在文件的末尾,你可以看到如下幾行:

#: settings.py:104
msgid "English"
msgstr ""

#: settings.py:105
msgid "Spanish"
msgstr ""

每一個(gè)翻譯字符串前都有一個(gè)顯示該文件詳情的注釋以及它在哪一行被找到。每個(gè)翻譯都包含兩個(gè)字符串:

  • msgid:在源代碼中的翻譯字符串
  • msgstr:對(duì)應(yīng)語(yǔ)言的翻譯,默認(rèn)為空。這是你輸入所給字符串翻譯的地方。

按照如下,根據(jù)所給的 msgidmsgtsr 中填寫(xiě)翻譯:

#: settings.py:104
msgid "English"
msgstr "Inglés"

#: settings.py:105
msgid "Spanish"
msgstr "Espa?ol"

保存修改后的信息文件,打開(kāi) shell ,運(yùn)行下面的命令:

django-admin compilemessages

如果一切順利,你可以看到像如下的輸出:

processing file django.po in myshop/locale/en/LC_MESSAGES
processing file django.po in myshop/locale/es/LC_MESSAGES

輸出給出了被編譯的信息文件的相關(guān)信息。再看一眼 locale 路徑的 myshop 。你可以看到下面的文件:

en/
    LC_MESSAGES/
        django.mo
        django.po
es/
    LC_MESSAGES/
        django.mo
        django.po

你可以看到每個(gè)語(yǔ)言的 .mo 的編譯文件已經(jīng)生成了。

我們已經(jīng)翻譯了語(yǔ)言本身的名字。現(xiàn)在讓我們翻譯展示在網(wǎng)站中模型(model)字段的名字。編輯 orders 應(yīng)用的 models.py ,為 Order 模型(model)添加翻譯的被標(biāo)記名:

from django.utils.translation import gettext_lazy as _

class Order(models.Model):
    first_name = models.CharField(_('first name'),
                                max_length=50)
    last_name = models.CharField(_('last name'),
                                max_length=50)
    email = models.EmailField(_('e-mail'),)
    address = models.CharField(_('address'),
                                max_length=250)
    postal_code = models.CharField(_('postal code'),
                                max_length=20)
    city = models.CharField(_('city'),
                        max_length=100)
#...

我們添加為當(dāng)用戶下一個(gè)新訂單時(shí)展示的字段添加了名字,它們分別是 first_name ,last_name, email, address, postal_code ,city。記住,你也可以使用每個(gè)字段的 verbose_name 屬性。

orders 應(yīng)用路徑內(nèi)創(chuàng)建以下路徑:

locale/
    en/
    es/

通過(guò)創(chuàng)建 locale 路徑,這個(gè)應(yīng)用的翻譯字符串就會(huì)儲(chǔ)存在這個(gè)路徑下的信息文件里,而不是在主信息文件里。這樣做之后,你就可以為每個(gè)應(yīng)用生成獨(dú)自的翻譯文件。

在項(xiàng)目路徑下打開(kāi) shell ,運(yùn)行下面的命令:

django-admin makemessages --all

你可以看到如下輸出:

processing locale es
processing locale en

用文本編輯器打開(kāi) es/LC_MESSAGES/django.po 文件。你將會(huì)看到每一個(gè)模型(model)的翻譯字符串。為每一個(gè)所給的 msgid 字符串填寫(xiě) msgstr 的翻譯:

#: orders/models.py:10
msgid "first name"
msgstr "nombre"

#: orders/models.py:12
msgid "last name"
msgstr "apellidos"

#: orders/models.py:14
msgid "e-mail"
msgstr "e-mail"

#: orders/models.py:15
msgid "address"
msgstr "dirección"

#: orders/models.py:17
msgid "postal code"
msgstr "código postal"

#: orders/models.py:19
msgid "city"
msgstr "ciudad"

在你添加完翻譯之后,保存文件。

在文本編輯器內(nèi),你可以使用 Poedit 來(lái)編輯翻譯。 Poedit 是一個(gè)用來(lái)編輯翻譯的軟件,它使用 gettext 。它有 Linux ,Windows,Mac OS X 版,你可以在這個(gè)網(wǎng)站下載 Poedit : http://poedit.net/ 。

讓我們來(lái)翻譯項(xiàng)目中的表單吧。 orders 應(yīng)用的 OrderCrateForm 還沒(méi)有被翻譯,因?yàn)樗且粋€(gè) ModelForm ,使用 Order 模型(model)的 verbose_name 屬性作為每個(gè)字段的標(biāo)簽。我們將會(huì)翻譯 cartcoupons 應(yīng)用的表單。

編輯 cart 應(yīng)用路徑下的 forms.py 文件,給 CartAddProductFormquantity 字段添加一個(gè) label 屬性,然后按照如下標(biāo)記這個(gè)需要翻譯的字段:

from django import forms
from django.utils.translation import gettext_lazy as _

PRODUCT_QUANTITY_CHOICES = [(i, str(i)) for i in range(1, 21)]

class CartAddProductForm(forms.Form):
    quantity = forms.TypedChoiceField(
                        choices=PRODUCT_QUANTITY_CHOICES,
                        coerce=int,
                        label=_('Quantity'))
    update = forms.BooleanField(required=False,
                        initial=False,
                        widget=forms.HiddenInput)

編輯 coupons 應(yīng)用的 forms.py ,按照如下翻譯 CouponApplyForm

from django import forms
from django.utils.translation import gettext_lazy as _

class CouponApplyForm(forms.Form):
    code = forms.CharField(label=_('Coupon'))

翻譯模板(templates)

Django 提供了 {% trans %}{% blocktrans %} 模板(template)標(biāo)簽來(lái)翻譯模板(template)中的字符串。為了使用翻譯模板(template)標(biāo)簽,你需要在你的模板(template)頂部添加 {% load i18n %} 來(lái)載入她們。

{% trans %}模板(template)標(biāo)簽

{% trans %}模板(template)標(biāo)簽讓你可以標(biāo)記需要翻譯的字符串,常量,或者是參數(shù)。在內(nèi)部,Django 對(duì)所給文本執(zhí)行 gettext() 。這是如何標(biāo)記模板(template)中的翻譯字符串:

{% trans "Text to be translated" %}

你可使用 as 來(lái)儲(chǔ)存你在模板(template)內(nèi)使用的全局變量里的被翻譯內(nèi)容。下面這個(gè)例子保存了一個(gè)變量中叫做 greeting 的被翻譯文本:

{% trans "Hello!" as greeting %}
<h1>{{ greeting }}</h1>

{% trans %} 標(biāo)簽對(duì)于簡(jiǎn)單的翻譯字符串是很有用的,但是它不能包含變量的翻譯內(nèi)容。

{% blocktrans %}模板(template)標(biāo)簽

{% blocktrans %} 模板(template)標(biāo)簽讓你可以標(biāo)記含有占位符的變量和字符的內(nèi)容。線面這個(gè)例子展示了如何使用 {% blocktrans %} 標(biāo)簽來(lái)標(biāo)記包含一個(gè) name 變量的翻譯內(nèi)容:

{% blocktrans %}Hello {{ name }}!{% endblocktrans %}

你可以用 with 來(lái)引入模板(template)描述,比如連接對(duì)象的屬性或者應(yīng)用模板(template)的變量過(guò)濾器。你必須為他們用占位符。你不能在 blocktrans 內(nèi)連接任何描述或者對(duì)象屬性。下面的例子展示了如何使用 with 來(lái)引入一個(gè)應(yīng)用了 capfirst 過(guò)濾器的對(duì)象屬性:

{% blocktrans with name=user.name|capfirst %}
Hello {{ name }}!
{% endblocktrans %}

當(dāng)你的字符串中含有變量時(shí)使用 {% blocktrans %} 代替 {% trans %} 。

翻譯商店模板(template)

編輯 shop 應(yīng)用的 shop/base.html 。確保你已經(jīng)在頂部載入了 i18n 標(biāo)簽,然后按照如下標(biāo)記翻譯字符串:

{% load i18n %}
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>
{% block title %}{% trans "My shop" %}{% endblock %}
</title>
<link href="{% static "css/base.css" %}" rel="stylesheet">
</head>
<body>
<div id="header">
<a href="/" class="logo">{% trans "My shop" %}</a>
</div>
<div id="subheader">
<div class="cart">
{% with total_items=cart|length %}
{% if cart|length > 0 %}
{% trans "Your cart" %}:
<a href="{% url "cart:cart_detail" %}">
{% blocktrans with total_items_plural=total_
items|pluralize
total_price=cart.get_total_price %}
{{ total_items }} item{{ total_items_plural }},
${{ total_price }}
{% endblocktrans %}
</a>
{% else %}
{% trans "Your cart is empty." %}
{% endif %}
{% endwith %}
</div>
</div>
<div id="content">
{% block content %}
{% endblock %}
</div>
</body>
</html>

注意展示在購(gòu)物車(chē)小計(jì)的 {% blocktrans %} 標(biāo)簽。購(gòu)物車(chē)小計(jì)在之前是這樣的:

{{ total_items }} item{{ total_items|pluralize }},
${{ cart.get_total_price }}

我們使用 {% blocktrans with ... %} 來(lái)使用 total_ items|pluralize (模板(template)標(biāo)簽生效的地方)和 cart_total_price (連接對(duì)象方法的地方)的占位符:

{% blocktrans with total_items_plural=total_items|pluralize
total_price=cart.get_total_price %}
{{ total_items }} item{{ total_items_plural }},
${{ total_price }}
{% endblocktrans %}

下面,編輯 shop 應(yīng)用的 shop/product/detail.html 模板(template)然后在頂部載入 i18n 標(biāo)簽,但是它必須位于 {% extends %} 標(biāo)簽的下面:

{% load i18n %}

找到下面這一行:

<input type="submit" value="Add to cart">

把它替換為下面這一行:

<input type="submit" value="{% trans "Add to cart" %}">

現(xiàn)在翻譯 orders 應(yīng)用模板(template)。編輯 orders 應(yīng)用的 orders/order/create.html 模板(template),然后標(biāo)記翻譯文本:

{% extends "shop/base.html" %}
{% load i18n %}
{% block title %}
{% trans "Checkout" %}
{% endblock %}
{% block content %}
<h1>{% trans "Checkout" %}</h1>
<div class="order-info">
<h3>{% trans "Your order" %}</h3>
<ul>
{% for item in cart %}
<li>
{{ item.quantity }}x {{ item.product.name }}
<span>${{ item.total_price }}</span>
</li>
{% endfor %}
{% if cart.coupon %}
<li>
{% blocktrans with code=cart.coupon.code
discount=cart.coupon.discount %}
"{{ code }}" ({{ discount }}% off)
{% endblocktrans %}
<span>- ${{ cart.get_discount|floatformat:"2" }}</span>
</li>
{% endif %}
</ul>
<p>{% trans "Total" %}: ${{
cart.get_total_price_after_discount|floatformat:"2" }}</p>
</div>
<form action="." method="post" class="order-form">
{{ form.as_p }}
<p><input type="submit" value="{% trans "Place order" %}"></p>
{% csrf_token %}
</form>
{% endblock %}

看看本章中下列文件中的代碼,看看字符串是如何被標(biāo)記的:

  • shop 應(yīng)用:shop/product/list.html
  • orders 應(yīng)用:orders/order/created.html
  • cart 應(yīng)用:cart/detail.html

讓位我們更新信息文件來(lái)引入新的翻譯字符串。打開(kāi) shell ,運(yùn)行下面的命令:

django-admin makemessages --all

.po 文件已經(jīng)在 myshop 項(xiàng)目的 locale 路徑下,你將看到 orders 應(yīng)用現(xiàn)在已經(jīng)包含我們標(biāo)記的所有需要翻譯的字符串。

編輯項(xiàng)目和orderes 應(yīng)用中的 .po 翻譯文件,然后引入西班牙語(yǔ)翻譯。你可以參考本章中翻譯了的 .po 文件:

在項(xiàng)目路徑下打開(kāi) shell ,然后運(yùn)行下面的命令:

cd orders/
django-admin compilemessages
cd ../

我們已經(jīng)編譯了 orders 應(yīng)用的翻譯。

運(yùn)行下面的命令,這樣應(yīng)用中不包含 locale 路徑的翻譯就被包含進(jìn)了項(xiàng)目的信息文件中:

django-admin compilemessages

(審校@夜夜月:因?yàn)榈诰耪逻^(guò)長(zhǎng),所以分成上下兩章,目前上半章結(jié)束。)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容