第九章 擴(kuò)展你的商店(下)

9.2 添加國際化和本地化

Django提供了完整的國際化和本地化支持。它允許你把應(yīng)用翻譯為多種語言频祝,它會處理特定區(qū)域日期沧踏,時間,數(shù)字和時區(qū)欣簇。讓我們弄清楚國際化和本地化的區(qū)別规脸。國際化(通常縮寫為i18n)是讓軟件適用于潛在的不同語言和地區(qū)的過程熊咽,讓軟件不會硬編碼為特定語言和地區(qū)莫鸭。本地化(縮寫為l10n)是實際翻譯軟件和適應(yīng)特定地區(qū)的過程。使用Django自己的國際化框架横殴,它本身被翻譯為超過50中語言被因。

9.2.1 使用Django國際化

國際化框架讓你很容易的在Python代碼和模板中編輯需要翻譯的字符串。它依賴GNU gettext工具集生成和管理信息文件。一個信息文件是一個代表一種語言的普通文本文件梨与。它包括你應(yīng)用中的部分或全部需要翻譯的字符串堕花,以及相應(yīng)的單種語言的翻譯。信息文件的擴(kuò)展名是.po粥鞋。

一旦完成翻譯航徙,信息文件就會被編譯,用來快速訪問翻譯后的字符串陷虎。被編譯的翻譯文件擴(kuò)展名是.mo到踏。

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

Django為國際化提供了一些設(shè)置。以下是最相關(guān)的設(shè)置:

  • USE_I18N:指定Django的翻譯系統(tǒng)是否可用的布爾值尚猿。默認(rèn)為True窝稿。
  • USE_L10N:表示本地格式是否可用的布爾值≡涞啵可用時伴榔,用本地格式表示日期和數(shù)字。默認(rèn)為False庄萎。
  • USE_TZ:指定日期和時間是否時區(qū)感知的布爾值踪少。當(dāng)你用startproject創(chuàng)建項目時,該值設(shè)置為True糠涛。
  • LANGUAGE_CODE:項目的默認(rèn)語言代碼援奢。它使用標(biāo)準(zhǔn)的語言ID格式,比如en-us表示美式英語忍捡,en-gb表示英式英語集漾。這個設(shè)置需要USE_I18N設(shè)為True才生效。你可以在這里查看有效地語言ID列表砸脊。
  • LANGUAGES:一個包括項目可用語言的元組具篇。它由包括語言代碼和語言名稱的雙元組構(gòu)成。你可以在django.conf.global_settings中查看可用語言列表凌埂。當(dāng)你選擇你的網(wǎng)站將使用哪些語言時驱显,你可以設(shè)置LANGUAGES為這個列表的一個子集。
  • LOCALE_PATHS:Django查找項目中包括翻譯的信息文件的目錄列表瞳抓。
  • TIME_ZONE:表示項目時區(qū)的字符串埃疫。當(dāng)你使用startproject命令創(chuàng)建新項目時,它設(shè)置為UTC挨下。你可以設(shè)置它為任何時區(qū)熔恢,比如Europe/Madrid

這是一些可用的國際化和本地化設(shè)置臭笆。你可以在這里查看完整列表叙淌。

9.2.1.2 國際化管理命令

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

  • makemessages:它在源代碼樹上運(yùn)行,查找所有標(biāo)記為需要翻譯的字符串鹰霍,并在locale目錄中創(chuàng)建或更新.po信息文件闻鉴。每種語言創(chuàng)建一個.po文件。
  • compilemessages:編譯存在的.po信息文件為.mo文件茂洒,用于檢索翻譯孟岛。

你需要gettext工具集創(chuàng)建,更新和編譯信息文件督勺。大部分Linux發(fā)行版都包括了gettext工具集渠羞。如果你使用的是Mac OS X,最簡單的方式是用brew install gettext命令安裝智哀。你可能還需要用brew link gettext --force強(qiáng)制鏈接到它次询。對于Windows安裝,請參考這里的步驟瓷叫。

9.2.1.3 如果在Django項目中添加翻譯

讓我們看下國際化我們項目的流程屯吊。我們需要完成以下工作:

  1. 我們標(biāo)記Python代碼和目錄中需要編譯的字符串。
  2. 我們運(yùn)行makemessages命令創(chuàng)建或更新信息文件摹菠,其中包括了代碼中所有需要翻譯的字符串盒卸。
  3. 我們翻譯信息文件中的字符串,然后用compilemessages管理命令編輯它們次氨。

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

Django自帶一個中間件蔽介,它基于請求的數(shù)據(jù)決定當(dāng)前語言。位于django.middleware.locale.LocaleMiddlewareLocaleMiddleware中間件執(zhí)行以下任務(wù):

  1. 如果你使用i18_patterns糟需,也就是你使用翻譯后的URL模式屉佳,它會在被請求的URL中查找語言前綴谷朝,來決定當(dāng)前語言洲押。
  2. 如果沒有找到語言前綴,它會在當(dāng)前用戶會話中查詢LANGUAGE_SESSION_KEY圆凰。
  3. 如果沒有在會話中設(shè)置語言杈帐,它會查找?guī)М?dāng)前語言的cookie。這個自定義的cookie名由LANGUAGE_COOKIE_NAME設(shè)置提供专钉。默認(rèn)情況下挑童,該cookie名為django-language
  4. 如果沒有找到cookie跃须,它會查詢請求的Accept-Language頭站叼。
  5. 如果Accept-Language頭沒有指定語言,Django會使用LANGUAGE_CODE設(shè)置中定義的語言菇民。

默認(rèn)情況下尽楔,Django會使用LANGUAGE_CODE設(shè)置中定義的語言投储,除非你使用LocaleMiddleware。以上描述的過程只適用于使用這個中間件阔馋。

9.2.2 為國際化我們的項目做準(zhǔn)備

讓我們?yōu)槲覀兊捻椖渴褂貌煌Z言玛荞。我們將創(chuàng)建商店的英語和西拔牙語版本。編輯項目的settings.py文件呕寝,在LANGUAGE_CODE設(shè)置之后添加LANGUAGES設(shè)置:

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

LANGUAGES設(shè)置中包括兩個元組勋眯,每個元組包括語言代碼和名稱。語言代碼可以指定地區(qū)下梢,比如en-usen-gb客蹋,也可以通用,比如en孽江。在這個設(shè)置中嚼酝,我們指定我們的應(yīng)用只對英語和西班牙可用。如果我們沒有定義LANGUAGES設(shè)置竟坛,則網(wǎng)站對于Django的所有翻譯語言都可用闽巩。

如下修改LANGUAGE_CODE設(shè)置:

LANGUAGE_CODE = 'en'

MIDDLEWARE設(shè)置中添加django.middleware.locale.LocaleMiddleware。確保這個中間件在SessionMiddleware之后担汤,因為LocaleMiddleware需要使用會話數(shù)據(jù)涎跨。它還需要在CommonMiddleware之前,因為后者需要一個激活的語言解析請求的URL崭歧。MIDDLEWARE設(shè)置看起來是這樣的:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    # ...
]

中間件的順序很重要隅很,因為每個中間件都依賴前面其它中間件執(zhí)行后的數(shù)據(jù)集。中間件按MIDDLEWARE中出現(xiàn)的順序應(yīng)用在請求上率碾,并且反序應(yīng)用在響應(yīng)上叔营。

在項目主目錄中穿件以下目錄結(jié)構(gòu),與manage.py同級:

locale/
    en/
    es/

locale目錄是應(yīng)用的信息文件存儲的目錄所宰。再次編輯settings.py文件绒尊,在其中添加以下設(shè)置:

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

LOCALE_PATHS設(shè)置指定了Django查找翻譯文件的目錄。最先出現(xiàn)的路徑優(yōu)先級最高仔粥。

當(dāng)你在項目目錄中使用makemessages命令時婴谱,信息文件會在我們創(chuàng)建的locale/路徑中生成。但是躯泰,對于包括locale/目錄的應(yīng)用來說谭羔,信息文件會在這個應(yīng)用的locale/目錄中生成。

9.2.3 翻譯Python代碼

要翻譯Python代碼中的字面量麦向,你可以用django.utils.translation中的gettext()函數(shù)標(biāo)記要翻譯的字符串瘟裸。這個函數(shù)翻譯信息并返回一個字符串。慣例是把這個函數(shù)導(dǎo)入為短別名_诵竭。

你可以在這里查看所有關(guān)于翻譯的文檔话告。

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

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

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

9.2.3.2 惰性翻譯

Django的所有翻譯函數(shù)都包括惰性(lazy)版本十办,它們的后綴都是_lazy()。使用惰性函數(shù)時超棺,當(dāng)值被訪問時翻譯字符串向族,而不是惰性函數(shù)被調(diào)用時翻譯(這就是為什么它們被惰性翻譯)。當(dāng)標(biāo)記為翻譯的字符串位于加載模式時執(zhí)行的路徑中棠绘,這些惰性翻譯函數(shù)非常方便件相。

使用gettext_lazy()代替gettext()時,當(dāng)值被訪問時翻譯字符串氧苍,而不是翻譯函數(shù)調(diào)用時翻譯夜矗。Django為所有翻譯函數(shù)提供了惰性版本。

9.2.3.3 帶變量的翻譯

標(biāo)記為翻譯的字符串的字符串可以包括占位符來在翻譯中引入變量让虐。以下代碼是翻譯帶占位符的字符串:

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

通過使用占位符紊撕,你可以重新排列文本變量。比如赡突,上個例子中的英文防疫可能是Today is April 14对扶,而西拔牙語翻譯時Hoy es 14 de Abiril。當(dāng)需要翻譯的字符串中包括一個以上的參數(shù)時惭缰,總是使用字符串插值代替位置插值浪南。這樣就可以重新排列站位文本。

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

對于復(fù)數(shù)形式漱受,你可以使用ngettext()ngettext_lazy()络凿。這些函數(shù)根據(jù)一個表示對象數(shù)量的參數(shù)翻譯單數(shù)和復(fù)數(shù)形式。下面的代碼展示了如何使用它們:

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

現(xiàn)在你已經(jīng)學(xué)會了翻譯Python代碼中字面量的基礎(chǔ)昂羡,是時候翻譯我們的項目了絮记。

9.2.3.5 翻譯你的代碼

編輯項目的settings.py文件,導(dǎo)入gettext_lazy()函數(shù)虐先,并如下修改LANGUAGES設(shè)置來翻譯語言名稱:

from django.utils.translation import gettext_lazy as _

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

我們在這里使用gettext_lazy()函數(shù)代替gettext()怨愤,來避免循環(huán)導(dǎo)入,所以當(dāng)語言名稱被訪問時翻譯它們赴穗。

打開終端憔四,并在項目目錄中執(zhí)行以下命令:

django-admin makemessages --all

你會看到以下輸出:

processing locale en
processing locale es

看一眼locale/目錄,你會看這樣的文件結(jié)構(gòu):

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

為每種語言創(chuàng)建了一個.po信息文件般眉。用文本編輯器打開es/LC_MESSAGES/django.po文件。在文件結(jié)尾潜支,你會看到以下內(nèi)容:

#: myshop/settings.py:122
msgid "English"
msgstr ""

#: myshop/settings.py:123
msgid "Spanish"
msgstr ""

每個需要翻譯的字符串前面都有一條注釋甸赃,顯示它位于的文件和行數(shù)。每個翻譯包括兩個字符串:

  • msgid:源代碼中需要翻譯的字符串冗酿。
  • msgstr:對應(yīng)語言的翻譯埠对,默認(rèn)為空。你需要在這里輸入給定字符串的實際翻譯项玛。

為給的msgid字符串填入msgstr翻譯:

#: myshop/settings.py:122
msgid "English"
msgstr "Ingle?s"

#: myshop/settings.py:123
msgid "Spanish"
msgstr "Espan?ol"

保存修改的信息文件貌笨,打開終端,執(zhí)行以下命令:

django-admin compilemessages

如果一切順利襟沮,你會看到類似這樣的輸出:

processing file django.po in /Users/lakerszhy/Documents/GitHub/Django-By-Example/code/Chapter 9/myshop/locale/en/LC_MESSAGES
processing file django.po in /Users/lakerszhy/Documents/GitHub/Django-By-Example/code/Chapter 9/myshop/locale/es/LC_MESSAGES

輸出告訴你信息文件已經(jīng)編譯锥惋。再看一眼myshop項目的locale目錄,你會看到以下文件:

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

你會看到為每種語言生成了一個編譯后的.mo信息文件开伏。

我們已經(jīng)翻譯了語言名本身“虻現(xiàn)在讓我們翻譯在網(wǎng)站中顯示的模型字段名。編輯orders應(yīng)用的models.py文件固灵,為Order模型字段添加需要翻譯的名稱標(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(_('email'))
    address = models.CharField(_('address'), max_length=250)
    postal_code = models.CharField(_('postal code'), max_length=20)
    city = models.CharField(_('city'), max_length=100)
    # ...

我們?yōu)橛脩粝聠螘r顯示的字段添加了名稱捅伤,分別是first_namelast_name巫玻,email丛忆,addresspostal_codecity仍秤。記住蘸际,你也可以使用verbose_name屬性為字段命名。

orders應(yīng)用中創(chuàng)建以下目錄結(jié)構(gòu):

locale/
    en/
    es/

通過創(chuàng)建locale/目錄徒扶,這個應(yīng)用中需要翻譯的字符串會存儲在這個目錄的信息文件中粮彤,而不是主信息文件。通過這種方式姜骡,你可以為每個應(yīng)用生成獨立的翻譯文件导坟。

在項目目錄打開終端,執(zhí)行以下命令:

django-admi makemessages --all

你會看到以下輸出:

processing locale en
processing locale es

用文本編輯器開大es/LC_MESSAGES/django.po文件圈澈。你會看到Order模型需要翻譯的字符串惫周。為給的msgid字符串填入msgstr翻譯:

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

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

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

#: orders/models.py:13
msgid "address"
msgstr "direccio?n"

#: orders/models.py:14
msgid "postal code"
msgstr "co?digo postal"

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

填完之后保存文件。

除了文本編輯器康栈,你還可以使用Poedit編輯翻譯递递。Poedit是一個編輯翻譯的軟件,它使用gettext啥么。它有Linux登舞,Windows和Mac OS X版本。你可以在這里下載悬荣。

讓我們再翻譯項目中的表單菠秒。orders應(yīng)用的OrderCreateForm不需要翻譯,因為它是一個ModelForm氯迂,它的表單字段標(biāo)簽使用了Order模型字段的verbose_name屬性践叠。我們將翻譯cartcoupons應(yīng)用的表單言缤。

編輯cart應(yīng)用中的forms.py文件,為CartAddProductFormquantity字段添加一個lable屬性禁灼,然后標(biāo)記為需要翻譯:

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'))

我們?yōu)?code>code字段添加了label屬性,并標(biāo)記為需要翻譯弄捕。

9.2.4 翻譯模板

Django為翻譯模板中的字符串提供了{% trans %}{% blocktrans %}模板標(biāo)簽僻孝。要使用翻譯模板標(biāo)簽,你必須在模板開頭添加{% load i18n %}加載它們察藐。

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

{% trans %}模板標(biāo)簽允許你標(biāo)記需要翻譯的字符串皮璧,常量或者變量。在內(nèi)部分飞,Django在給定的文本上執(zhí)行gettext()悴务。以下是在模板中標(biāo)記需要翻譯的字符串:

{% trans "Text to be translated" %}

你可以使用as在變量中存儲翻譯后的內(nèi)容,然后就能在整個模板中使用這個變量譬猫。下面這個例子在greeting變量中存儲翻譯后的文本:

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

{% trans %}標(biāo)簽對簡單的翻譯字符串很有用讯檐,但它不能處理包括變量的翻譯內(nèi)容。

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

{% blocktrans %}模板標(biāo)簽允許你標(biāo)記包括字面量的內(nèi)容和使用占位符的變量內(nèi)容染服。下面這個例子展示了如何使用{% blocktrans %}標(biāo)簽標(biāo)記一個包括name變量的翻譯內(nèi)容:

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

你可以使用with引入模板表達(dá)式别洪,比如訪問對象屬性,或者對變量使用模板過濾器柳刮。這時挖垛,你必須總是使用占位符。你不能在blocktrans塊中訪問表達(dá)式或者對象屬性秉颗。下面的例子展示了如何使用with痢毒,其中引入了一個對象屬性,并使用capfirst過濾器:

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

當(dāng)需要翻譯的字符串中包括變量內(nèi)容時蚕甥,使用{% blocktrans %}代替{% trans %}哪替。

9.2.4.3 翻譯商店的模板

編輯shop應(yīng)用的shop/base.html模板。在模板開頭加載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=otal_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>

記住凭舶,我們用{% blocktrans %}標(biāo)簽顯示購物車匯總。之前購物車匯總是這樣的:

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

我們利用{% blocktrans with ... %}total_items|pluralize(在這里使用模板標(biāo)簽)和cart.get_total_price(在這里訪問對象方法)使用占位符爱沟,結(jié)果是:

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

接著編輯shop應(yīng)用的shop/product/detai.html模板帅霜,在{% extends %}標(biāo)簽(它必須總是第一個標(biāo)簽)之后加載i18n標(biāo)簽:

{% load i18n %}

然后找到這一行:

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

替換為:

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

現(xiàn)在翻譯orders應(yīng)用的模板。編輯orders應(yīng)用的orders/order/create.html模板钥顽,如下標(biāo)記需要翻譯的文本:

{% extends "shop/base.html" %}
{% load i18n %}

{% block title %}
    {% trans "Checkout" %}
{% endblock title %}

{% 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 content %}

在本章示例代碼中查看以下文件是如何標(biāo)記需要翻譯的字符串:

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

讓我們更新信息文件來引入新的翻譯字符串义屏。打開終端,執(zhí)行以下命令:

django-admin makemessages --all

.po翻譯文件位于myshop項目的locale目錄中蜂大,orders應(yīng)用現(xiàn)在包括了所有我們標(biāo)記過的翻譯字符串闽铐。

編輯項目和orders應(yīng)用的.po翻譯文件,并填寫西班牙語翻譯奶浦。你可以參考本章示例代碼的.po文件兄墅。

從項目目錄中打開終端,并執(zhí)行以下命令:

cd orders/
django-admin compilemessages
cd ../

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

執(zhí)行以下命令隙咸,在項目的信息文件中包括沒有locale目錄的應(yīng)用的翻譯。

django-admin compilemessage

9.2.5 使用Rosetta的翻譯界面

Rosetta是一個第三方應(yīng)用成洗,讓你可以用跟Django管理站點一樣的界面編輯翻譯五督。Rosetta可以很容易的編輯.po文件,并且它會更新編譯后的編譯文件瓶殃。讓我們把它添加到項目中充包。

使用pip命令安裝Rosetta:

pip install django-rosetta

然后把rosetta添加到項目settings.py文件中的INSTALLED_APP設(shè)置中:

你需要把Rosetta的URL添加到主URL配置中。編輯項目的urls.py文件遥椿,并添加下URL模式:

url(r'^rosetta/', include('rosetta.urls')),

確保把它放在shop.urls模式之后基矮,避免錯誤的匹配。

在瀏覽器中打開http://127.0.0.1:8000/admin/冠场,并用超級用戶登錄家浇。然后導(dǎo)航到http://127.0.0.1:8000/rosetta/。你會看到已經(jīng)存在的語言列表碴裙,如下圖所示:

點擊Filter中的All顯示所有可用的信息文件钢悲,包括屬于orders應(yīng)用的信息文件。在Spanish中點擊Myshop鏈接來編輯西班牙語翻譯。你會看到一個需要翻譯的字符串列表碧囊,如下圖所示:

你可以在Spanish列中輸入翻譯漫贞。Occurrences列顯示每個需要翻譯的字符串所在的文件和行數(shù)。

包括占位符的翻譯是這樣的:

Rosetta用不同的顏色顯示占位符芦昔。當(dāng)你翻譯內(nèi)容時,確保不要翻譯占位符娃肿。比如這一行字符串:

%(total_items)s item%(total_items_plural)s, $%(total_price)s

翻譯為西班牙語后是這樣的:

%(total_items)s producto%(total_items_plural)s, $%(total_price)s

你可以參考本章的示例代碼咕缎,用同樣的西班牙語翻譯你的項目。

當(dāng)你完成翻譯后料扰,點擊Save and translate next block按鈕凭豪,把翻譯保存到.po文件。保存翻譯時晒杈,Rosseta會編譯信息文件嫂伞,所以不需要執(zhí)行compilemessages命令。但是Rosetta需要寫入locale目錄的權(quán)限來寫入信息文件。確保這些目錄有合理的權(quán)利帖努。

如果你希望其他用戶也可以編輯翻譯撰豺,在瀏覽器中打開http://127.0.0.1:8000/admin/auth/group/add/,并創(chuàng)建一個translators組拼余。然后訪問http://127.0.0.1:8000/admin/auth/user/污桦,編輯你要授予翻譯權(quán)限的用戶。編輯用戶時匙监,在Premissions中凡橱,把translators組添加到每個用戶的Chosen Groups中。Resetta只對超級用戶和屬于translators組的用戶可用亭姥。

你可以在這里閱讀Rosetta的文檔稼钩。

當(dāng)你在生產(chǎn)環(huán)境添加新翻譯時,如果你的Django運(yùn)行在一個真實的web服務(wù)器上达罗,你必須在執(zhí)行compilemessages命令或者用Rosetta保存翻譯之后重啟服務(wù)器坝撑,才能讓修改生效。

9.2.6 不明確的翻譯

你可能已經(jīng)注意到了氮块,在Rosetta中有一個Fuzzy列绍载。這不是Rosetta的特征,而是有gettext提供的滔蝉。如果啟用了翻譯的fuzzy標(biāo)記击儡,那么它就不會包括在編譯后的信息文件中。這個標(biāo)記用于需要翻譯者修改的翻譯字符串蝠引。當(dāng)用新的翻譯字符串更新.po文件中阳谍,可能有些翻譯字符串自動標(biāo)記為fuzzy。當(dāng)gettext發(fā)現(xiàn)某些msgid變動不大時螃概,它會匹配為舊的翻譯矫夯,并標(biāo)記為fuzzy,以便復(fù)核吊洼。翻譯者應(yīng)該復(fù)核不明確的翻譯训貌,然后移除fuzzy標(biāo)記,并在此編譯信息文件冒窍。

9.2.7 URL模式的國際化

Django為URL提供了國際化功能递沪。它包括兩種主要的國際化URL特性:

  • URL模式中的語言前綴:把語言前綴添加到URL中,在不同的基礎(chǔ)URL下提供每種語言的版本
  • 翻譯后的URL模式:標(biāo)記需要翻譯的URL模式综液,因此同一個URL對于每種語言是不同的

翻譯URL的其中一個原因是為搜索引擎優(yōu)化你的網(wǎng)站款慨。通過在模式中添加語言前綴,你就可以為每種語言提供索引URL谬莹,而不是為所有語言提供一個索引URL檩奠。此外桩了,通過翻譯URL為不同語言,你可以為搜索引擎提供對每種語言排名更好的URL埠戳。

9.2.7.1 添加語言前綴到URL模式中

Django允許你在URL模式中添加語言前綴井誉。例如,網(wǎng)站的英語版本可以/en/起始路徑下乞而,而西班牙語版本在/es/下送悔。

要在URL模式中使用語言慢显,你需要確保settings.py文件的MIDDLEWARE設(shè)置中包括django.middleware.locale.LocaleMiddleware爪模。Django將用它從請求URL中識別當(dāng)前語言。

讓我們在URL模式中添加語言前綴荚藻。編輯myshop項目的urls.py文件屋灌,添加以下導(dǎo)入:

from django.conf.urls.i18n import i18n_patterns

然后添加i18n_patterns(),如下所示:

urlpatterns = i18n_patterns(
    url(r'^admin/', admin.site.urls),
    url(r'^cart/', include('cart.urls', namespace='cart')),
    url(r'^orders/', include('orders.urls', namespace='orders')),
    url(r'^paypal/', include('paypal.standard.ipn.urls')),
    url(r'^payment/', include('payment.urls', namespace='payment')),
    url(r'^coupons/', include('coupons.urls', namespace='coupons')),
    url(r'^rosetta/', include('rosetta.urls')),
    url(r'^', include('shop.urls', namespace='shop')),
)

你可以在patterns()i18n_patterns()中結(jié)合URL模式应狱,這樣有些模式包括語言前綴共郭,有些不包括。但是疾呻,最好只使用翻譯后的URL除嘹,避免不小心把翻譯后的URL匹配到?jīng)]有翻譯的URL模式。

啟動開發(fā)服務(wù)器岸蜗,并在瀏覽器中打開http://127.0.0.1:8000/尉咕。因為你使用了LocaleMiddleware中間件,所以Django會執(zhí)行Django如何決定當(dāng)前語言中描述的步驟璃岳,決定當(dāng)前的語言年缎,然后重定義到包括語言前綴的同一個URL×蹇叮看一下眼瀏覽器中的URL单芜,它應(yīng)該是http://127.0.0.1:8000/en/。如果瀏覽器的Accept-Language頭是西班牙語或者英語犁柜,則當(dāng)前語言是它們之一洲鸠;否則當(dāng)前語言是設(shè)置中定義的默認(rèn)LANGUAGE_CODE(英語)。

9.2.7.2 翻譯URL模式

Django支持URL模式中有翻譯后的字符串馋缅。對應(yīng)單個URL模式扒腕,你可以為每種語言使用不同的翻譯。你可以標(biāo)記需要翻譯的URL模式股囊,方式與標(biāo)記字面量一樣袜匿,使用gettext_lazy()函數(shù)。

編輯myshop項目的主urls.py文件稚疹,把翻譯字符串添加到cart居灯,orders祭务,paymentcoupons應(yīng)用的URL模式的正則表達(dá)式中:

urlpatterns = i18n_patterns(
    url(r'^admin/', admin.site.urls),
    url(_(r'^cart/'), include('cart.urls', namespace='cart')),
    url(_(r'^orders/'), include('orders.urls', namespace='orders')),
    url(r'^paypal/', include('paypal.standard.ipn.urls')),
    url(_(r'^payment/'), include('payment.urls', namespace='payment')),
    url(_(r'^coupons/'), include('coupons.urls', namespace='coupons')),
    url(r'^rosetta/', include('rosetta.urls')),
    url(r'^', include('shop.urls', namespace='shop')),
)

編輯orders應(yīng)用的urls.py文件,編輯需要翻譯的URL模式:

from django.utils.translation import gettext_lazy as _

urlpatterns = [
    url(_(r'^create/$'), views.order_create, name='order_create'),
    # ..
]

編輯payment應(yīng)用的urls.py文件怪嫌,如下修改代碼:

from django.utils.translation import gettext as _

urlpatterns = [
    url(_(r'^process/$'), views.payment_process, name='process'),
    url(_(r'^done/$'), views.payment_done, name='done'),
    url(_(r'^canceled/$'), views.payment_canceled, name='canceled'),
]

我們不要翻譯shop應(yīng)用的URL模式义锥,因為它們由變量構(gòu)建,不包括任何字面量岩灭。

打開終端拌倍,執(zhí)行以下命令更新信息文件:

django-admin makemessages --all

確保開發(fā)服務(wù)器正在運(yùn)行。在瀏覽器中打開http://127.0.0.1:8000/en/rosetta/噪径,然后點擊Spanish中的Myshop鏈接柱恤。你可以使用Display過濾器只顯示沒有翻譯的字符串。在URL翻譯中找爱,一定要保留正則表達(dá)式中的特殊字符梗顺。翻譯URL是一個精細(xì)的任務(wù);如果你修改了正則表達(dá)式车摄,就會破壞URL寺谤。

9.2.8 允許用戶切換語言

因為我們現(xiàn)在提供了多種語言,所以我們應(yīng)該讓用戶可以切換網(wǎng)站的語言吮播。我們會在網(wǎng)站中添加一個語言選擇器变屁。語言選擇器用鏈接顯示可用的語言列表。

編輯shop/base.html模板意狠,找到以下代碼:

<div id="header">
    <a href="/" class="logo">{% trans "My shop" %}</a>
</div>

替換為以下代碼:

<div id="header">
    <a href="/" class="logo">{% trans "My shop" %}</a>

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

我們是這樣構(gòu)建語言選擇器的:

  1. 我們首先用{% load i18n %}加載國際化標(biāo)簽粟关。
  2. 我們用{% get_current_language %}標(biāo)簽查詢當(dāng)前語言。
  3. 我們用{% get_available_languages %}模板標(biāo)簽獲得LANGUAGES設(shè)置中定義的語言摄职。
  4. 我們用{% get_language_info_list %}標(biāo)簽提供訪問語言屬性的便捷方式誊役。
  5. 我們構(gòu)建HTML列表顯示所有可用的語言,并在當(dāng)前激活語言上添加selected類屬性谷市。

我們用i18n提供的模板標(biāo)簽蛔垢,根據(jù)項目設(shè)置提供可用的語言。現(xiàn)在打開http://127.0.0.1:8000/迫悠。你會看到網(wǎng)站右上角有語言選擇器鹏漆,如下圖所示:

用戶現(xiàn)在可以很容易的切換語言。

9.2.9 用django-parler翻譯模型

Django沒有為翻譯模型提供好的解決方案创泄。你必須實現(xiàn)自己的解決方案來管理不同語言的內(nèi)容艺玲,或者使用第三方模塊翻譯模型。有一些第三方應(yīng)用允許你翻譯模型字段鞠抑。每種采用不同的方法存儲和訪問翻譯饭聚。其中一個是django-parler。這個模塊提供了一種非常高效的翻譯模型的方式搁拙,并且它和Django管理站點集成的非常好秒梳。

django-parler為每個模型生成包括翻譯的獨立的數(shù)據(jù)庫表法绵。這張表包括所有翻譯后的字段,以及一個翻譯所屬的原對象的外鍵酪碘。因為每行存儲單個語言的內(nèi)容朋譬,所以它還包括一個語言字段。

9.2.9.1 安裝django-parler

使用pip命令安裝django-parler

pip install django-parler

然后編輯項目的settings.py文件兴垦,把parler添加到INSTALLED_APPS設(shè)置中徙赢。并在設(shè)置文件中添加以下代碼:

PARLER_LANGUAGES = {
    None: (
        {'code': 'en', },
        {'code': 'es', },
    ),
    'default': {
        'fallback': 'en',
        'hide_untranslated': False,
    }
}

這個設(shè)置定義了django-parler的可用語言enes。我們指定默認(rèn)語言是en探越,并且指定django-parler不隱藏沒有翻譯的內(nèi)容狡赐。

9.2.9.2 翻譯模型字段

讓我們?yōu)樯唐纺夸浱砑臃g。django-parler提供了一個TranslatableModel模型類和一個TranslatedFields包裝器(wrapper)來翻譯模型字段扶关。編輯shop應(yīng)用的models.py文件阴汇,添加以下導(dǎo)入:

from parler.models import TranslatableModel, TranslatedFields

然后修改Category模型,讓nameslug字段可翻譯节槐。我們現(xiàn)在還保留非翻譯字段:

class Category(TranslatableModel):
    name = models.CharField(max_length=200, db_index=True)
    slug = models.SlugField(max_length=200, db_index=True, unique=True)

    translations = TranslatedFields(
        name = models.CharField(max_length=200, db_index=True),
        slug = models.SlugField(max_length=200, db_index=True, unique=True)
    )

現(xiàn)在Category模型繼承自TranslatableModel,而不是models.Model拐纱。并且nameslug字段都包括在TranslatedFields包裝器中铜异。

編輯Product模型,為name秸架,slugdescription字段添加翻譯揍庄。同樣保留非翻譯字段:

class Product(TranslatableModel):
    name = models.CharField(max_length=200, db_index=True)
    slug = models.SlugField(max_length=200, db_index=True)
    description = models.TextField(blank=True)
    translations = TranslatedFields(
        name = models.CharField(max_length=200, db_index=True),
        slug = models.SlugField(max_length=200, db_index=True),
        description = models.TextField(blank=True)
    )
    category = models.ForeignKey(Category, related_name='products')
    image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.PositiveIntegerField()
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

django-parler為每個TranslatableModel模型生成另一個模型。圖9.9中东抹,你可以看到Product模型的字段和生成的ProductTranslation模型:

django-parler生成的ProductTranslation模型包括name蚂子,slugdescription可翻譯字段,一個language_code字段缭黔,以及指向Product對象的外鍵master字段食茎。從ProductProductTranslation是一對多的關(guān)系。每個Product對象會為每種語言生成一個ProductTranslation對象馏谨。

因為Django為翻譯使用了單獨的數(shù)據(jù)庫表别渔,所以有些Django特性不能使用了。一個翻譯后的字段不能用作默認(rèn)的排序惧互。你可以在查詢中用翻譯后的字段過濾哎媚,但你不能再ordering元選項中包括翻譯后的字段。編輯shop應(yīng)用的models.py文件喊儡,注釋Category類中Meta類的ordering屬性:

class Meta:
    # ordering = ('name', )
    verbose_name = 'category'
    verbose_name_plural = 'categories'

我們還必須注釋Product類中Meta類的index_together屬性拨与,因為當(dāng)前django-parler版本不提供驗證它的支持:

class Meta:
    ordering = ('-created', )
    # index_together = (('id', 'slug'), )

你可以在這里閱讀更多關(guān)于django-parler和Django兼容性的信息。

9.2.9.3 創(chuàng)建自定義數(shù)據(jù)庫遷移

當(dāng)你為翻譯創(chuàng)建了新模型艾猜,你需要執(zhí)行makemigrations命令為模型生成數(shù)據(jù)庫遷移买喧,然后同步到數(shù)據(jù)庫中攀甚。但是當(dāng)你將已存在字段變?yōu)榭煞g后,你的數(shù)據(jù)庫中可能已經(jīng)存在數(shù)據(jù)了岗喉。我們將把當(dāng)前數(shù)據(jù)遷移到新的翻譯模型中秋度。因此,我們添加了翻譯后的字段钱床,但暫時保留了原來的字段荚斯。

為已存在字段添加翻譯的流程是這樣的:

  1. 我們?yōu)樾碌目煞g模型字段創(chuàng)建數(shù)據(jù)庫遷移,并保留原來的字段查牌。
  2. 我們構(gòu)建一個自定義數(shù)據(jù)庫遷移事期,從已存在字段中拷貝數(shù)據(jù)到翻譯模型中。
  3. 我們從原來的模型中移除已存在的字段纸颜。

執(zhí)行以下命令兽泣,為添加到CategoryProduct模型中的翻譯字段創(chuàng)建數(shù)據(jù)庫遷移:

python manage.py makemigrations shop --name "add_translation_model"

你會看到以下輸出:

Migrations for 'shop':
  shop/migrations/0002_add_translation_model.py
    - Create model CategoryTranslation
    - Create model ProductTranslation
    - Change Meta options on category
    - Alter index_together for product (0 constraint(s))
    - Add field master to producttranslation
    - Add field master to categorytranslation
    - Alter unique_together for producttranslation (1 constraint(s))
    - Alter unique_together for categorytranslation (1 constraint(s))

現(xiàn)在我們需要創(chuàng)建一個自定義數(shù)據(jù)庫遷移,把已存在的數(shù)據(jù)拷貝到新的翻譯模型中胁孙。使用以下命令創(chuàng)建一個空的數(shù)據(jù)庫遷移:

python manage.py makemigrations --empty shop --name "migrate_translatable_fields"

你會看到以下輸出:

Migrations for 'shop':
  shop/migrations/0003_migrate_translatable_fields.py

編輯shop/migrations/0003_migrate_translatable_fields.py文件唠倦,并添加以下代碼:

# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-17 01:18
from __future__ import unicode_literals
from django.db import models, migrations
from django.apps import apps
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist

translatable_models = {
    'Category': ['name', 'slug'],
    'Product': ['name', 'slug', 'description'],
}

def forwards_func(apps, schema_editor):
    for model, fields in translatable_models.items():
        Model = apps.get_model('shop', model)
        ModelTranslation = apps.get_model('shop', '{}Translation'.format(model))

        for obj in Model.objects.all():
            translation_fields = {field: getattr(obj, field) for field in fields}
            translation = ModelTranslation.objects.create(
                master_id=obj.pk,
                language_code=settings.LANGUAGE_CODE,
                **translation_fields
            )

def backwards_func(apps, shcema_editor):
    for model, fields in translatable_models.items():
        Model = apps.get_model('shop', model)
        ModelTranslation = apps.get_model('shop', '{}Translation'.format(model))

        for obj in Model.objects.all():
            translation = _get_translation(obj, ModelTranslation)
            for field in fields:
                setattr(obj, field, getattr(translation, field))
            obj.save()

def _get_translation(obj, MyModelTranslation):
    translation = MyModelTranslation.objects.filter(master_id=obj.pk)
    try:
        # Try default translation
        return translation.get(language_code=settings.LANGUAGE_CODE)
    except ObjectDoesNotExist:
        # Hope there is a single translation
        return translations.get()

class Migration(migrations.Migration):

    dependencies = [
        ('shop', '0002_add_translation_model'),
    ]

    operations = [
        migrations.RunPython(forwards_func, backwards_func)
    ]

這個遷移包括forwards_func()backwards_func()函數(shù),其中包含要執(zhí)行數(shù)據(jù)庫同步和反轉(zhuǎn)的代碼涮较。

遷移流程是這樣的:

  1. 我們在translatable_models字典中定義模型和可翻譯的字段稠鼻。
  2. 要同步遷移,我們用app.get_model()迭代包括翻譯的模型狂票,來獲得模型和它可翻譯的模型類候齿。
  3. 我們迭代數(shù)據(jù)庫中所有存在的對象,并為項目設(shè)置中定義的LANGUAGE_CODE創(chuàng)建一個翻譯對象闺属。我們包括了一個指向原對象的ForeignKey慌盯,以及從原字段中拷貝的每個可翻譯字段。

backwards_func()函數(shù)執(zhí)行相反的操作掂器,它查詢默認(rèn)的翻譯對象亚皂,并把可翻譯字段的值拷貝回原對象。

我們已經(jīng)創(chuàng)建了一個數(shù)據(jù)庫遷移來添加翻譯字段唉匾,以及一個從已存在字段拷貝內(nèi)容到新翻譯模型的遷移孕讳。

最后,我們需要刪除不再需要的原字段巍膘。編輯shop應(yīng)用的models.py文件厂财,移除Category模型的nameslug字段。現(xiàn)在Category模型字段是這樣的:

class Category(TranslatableModel):
    translations = TranslatedFields(
        name = models.CharField(max_length=200, db_index=True),
        slug = models.SlugField(max_length=200, db_index=True, unique=True)
    )

移除Product模型的name峡懈,slugdescription字段璃饱。它現(xiàn)在是這樣的:

class Product(TranslatableModel):
    translations = TranslatedFields(
        name = models.CharField(max_length=200, db_index=True),
        slug = models.SlugField(max_length=200, db_index=True),
        description = models.TextField(blank=True)
    )
    category = models.ForeignKey(Category, related_name='products')
    image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.PositiveIntegerField()
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

現(xiàn)在我們需要創(chuàng)建最后一個遷移,讓修改生效肪康。但是荚恶,如果我們嘗試執(zhí)行manage.py工具撩穿,我們會看到一個錯誤,因為我們還沒有讓管理站點適配可翻譯模型谒撼。讓我們先修改管理站點食寡。

9.2.9.4 在管理站點集成翻譯

Django管理站點可以很好的跟django-parler集成。django-parler包括一個TranslatableAdmin類廓潜,它覆寫了Django提供的ModelAdmin類抵皱,來管理模型翻譯。

編輯shop應(yīng)用的admin.py文件辩蛋,添加以下導(dǎo)入:

from parler.admin import TranslatableAdmin

修改CategoryAdminProductAdmin類呻畸,讓它們從TranslatableAdmin繼承。django-parler還不知道prepopulated_fields屬性悼院,但它支持相同功能的get_ prepopulated_fields()方法伤为。讓我們相應(yīng)的修改,如下所示:

from django.contrib import admin
from parler.admin import TranslatableAdmin
from .models import Category, Product

class CategoryAdmin(TranslatableAdmin):
    list_display = ('name', 'slug')

    def get_prepopulated_fields(self, request, obj=None):
        return {'slug': ('name', )}

admin.site.register(Category, CategoryAdmin)

class ProductAdmin(TranslatableAdmin):
    list_display = ('name', 'slug', 'price', 'stock', 'available', 'created', 'updated')
    list_filter = ('available', 'created', 'updated')
    list_editable = ('price', 'stock', 'available')

    def get_prepopulated_fields(self, request, obj=None):
        return {'slug': ('name', )}
        
admin.site.register(Product, ProductAdmin)

我們已經(jīng)讓管理站點可以與新的翻譯模型一起工作了【萃荆現(xiàn)在可以同步模型修改到數(shù)據(jù)庫中绞愚。

9.2.9.5 為模型翻譯同步數(shù)據(jù)庫遷移

適配管理站點之前,我們已經(jīng)從模型中移除了舊的字段∽蚍玻現(xiàn)在我們需要為這個修改創(chuàng)建一個遷移爽醋。打開終端執(zhí)行以下命令:

python manage.py makemigrations shop --name "remove_untranslated_fields"

你會看到以下輸出:

Migrations for 'shop':
  shop/migrations/0004_remove_untranslated_fields.py
    - Change Meta options on product
    - Remove field name from category
    - Remove field slug from category
    - Remove field description from product
    - Remove field name from product
    - Remove field slug from product

通過這次遷移,我們移除了原字段便脊,保留了可翻譯字段。

總結(jié)一下光戈,我們已經(jīng)創(chuàng)建了以下遷移:

  1. 添加可翻譯字段到模型中
  2. 從原字段遷移已存在字段到可翻譯字段
  3. 從模型中移除原字段

執(zhí)行以下命令哪痰,同步我們創(chuàng)建的三個遷移:

python manage.py migrate shop

你會看到包括以下行的輸出:

Applying shop.0002_add_translation_model... OK
Applying shop.0003_migrate_translatable_fields... OK
Applying shop.0004_remove_untranslated_fields... OK

現(xiàn)在模型已經(jīng)跟數(shù)據(jù)庫同步了。讓我們翻譯一個對象久妆。

使用python manage.py runserver啟動開發(fā)服務(wù)器晌杰,然后在瀏覽器中打開http://127.0.0.1:8000/en/admin/shop/category/add/。你會看到Add category頁面包括兩個標(biāo)簽頁筷弦,一個英語和一個西班牙語翻譯:

現(xiàn)在你可以添加一個翻譯肋演,然后點擊Save按鈕。確保切換標(biāo)簽頁之前保存修改烂琴,否則輸入的信息會丟失爹殊。

9.2.9.6 為翻譯適配視圖

我們必須讓shop的視圖適配翻譯的QuerySet。在命令行中執(zhí)行python manage.py shell奸绷,看一眼如何檢索和查詢翻譯字段梗夸。要獲得當(dāng)前語言的字段內(nèi)容,你只需要與訪問普遍模型字段一樣訪問該字段:

>>> from shop.models import Product
>>> product = Product.objects.first()
>>> product.name
'Black tea'

當(dāng)你訪問翻譯后的字段時号醉,它們已經(jīng)被當(dāng)前語言處理了反症。你可以在對象上設(shè)置另一個當(dāng)前語言辛块,來訪問指定的翻譯:

>>> product.set_current_language('es')
>>> product.name
'Te? negro'
>>> product.get_current_language()
'es'

當(dāng)使用filter()執(zhí)行QeurySet時,你可以在相關(guān)的翻譯對象上用translations__語法過濾:

>>> Product.objects.filter(translations__name='Black tea')
[<Product: Black tea>]

你也可以用language()管理器為對象檢索指定語言:

>>> Product.objects.language('es').all()
[<Product: Te? negro>, <Product: Te? en polvo>, <Product: Te? rojo>, <Product: Te? verde>]

正如你所看到的铅碍,訪問和查詢翻譯字段非常簡單润绵。

讓我們適配商品目錄視圖。編輯shop應(yīng)用的views.py文件胞谈,在product_list視圖中找到這一行代碼:

category = get_object_or_404(Category, slug=category_slug)

替換為以下代碼:

language = request.LANGUAGE_CODE
category = get_object_or_404(
    Category, 
    translations__language_code=language,
    translations__slug=category_slug)

接著編輯product_detail視圖尘盼,找到這一行代碼:

product = get_object_or_404(Product, id=id, slug=slug, available=True)

替換為以下代碼:

language = request.LANGUAGE_CODE
product = get_object_or_404(
    Product, 
    id=id, 
    translations__language_code=language,
    translations__slug=slug, 
    available=True)

現(xiàn)在product_listproduct_detail視圖已經(jīng)適配了用翻譯字段檢索對象。啟動開發(fā)服務(wù)器呜魄,并在瀏覽器中打開http://127.0.0.1:8000/es/悔叽。你會看到商品列表頁面,所有商品都已經(jīng)翻譯為西班牙語:

現(xiàn)在用slug字段構(gòu)建的每個商品的URL已經(jīng)翻譯為當(dāng)前語言爵嗅。例如娇澎,一個商品的西班牙語URL是http://127.0.0.1:8000/es/1/te-negro/,而英語的URL是http://127.0.0.1:8000/en/1/black-tea/睹晒。如果你導(dǎo)航到一個商品的詳情頁面趟庄,你會看到翻譯后的URL和選中語言的內(nèi)容,如下圖所示:

如果你想進(jìn)一步了解django-parler伪很,你可以在這里找到所有文檔戚啥。

你已經(jīng)學(xué)習(xí)了如何翻譯Python代碼,模板锉试,URL模式和模型字段猫十。要完成國際化和本地化過程,我們還需要顯示本地化格式的日期呆盖,時間和數(shù)組拖云。

9.2.10 格式的本地化

根據(jù)用戶的地區(qū),你可能希望以不同格式顯示日期应又,時間和數(shù)字宙项。修改項目的settings.py文件中的USE_L10N設(shè)置為True,可以啟動本地化格式株扛。

啟用USE_L10N后尤筐,當(dāng)Django在模板中輸出值時,會嘗試使用地區(qū)特定格式洞就。你可以看到盆繁,你的英文版網(wǎng)站中的十進(jìn)制用點號分隔小數(shù)部分,而不在西班牙版本中顯示為逗號奖磁。這是因為Django為es地區(qū)指定了地區(qū)格式改基。你可以在這里查看西班牙格式配置。

通常你會設(shè)置USE_L10NTrue,讓Django為每個地區(qū)應(yīng)用本地化格式秕狰。但是稠腊,有些情況下你可能不想使用地區(qū)化的值。當(dāng)輸出必須提供機(jī)器可讀的JavaScript或JSON時鸣哀,這一點尤其重要架忌。

Django提供了{% localize %}模板標(biāo)簽,運(yùn)行你在模板塊中開啟或關(guān)閉本地化我衬。這讓你可以控制格式的本地化叹放。要使用這個模板標(biāo)簽,你必須加載l10n標(biāo)簽挠羔。下面這個例子展示了如何在模板中開啟或關(guān)閉本地化:

{% load l10n %}

{% localize on %}
    {{ value }}
{% endlocalize %}

{% localize off %}
    {{ value }}
{% endlocalize %}

Django還提供了localizeunlocalize模板過濾器井仰,強(qiáng)制或避免本地化一個值,如下所示:

{{ value|localize }}
{{ value|unlocalize }}

你還可以創(chuàng)建自定義格式過濾器來指定本地格式破加。你可以在這里查看更多關(guān)于格式本地化的信息俱恶。

9.2.11 用django-localflavor驗證表單字段

django-localflavor是一個第三方模板,其中包含一組特定用途的功能范舀,比如每個國家特定的表單字段或模型字段合是。驗證本地區(qū)域,本地電話號碼锭环,身份證聪全,社會安全號碼等非常有用。這個包由一系列以ISO 3166國家代碼命名的模塊組成辅辩。

用以下命令安裝django-localflavor:

pip install django-localflavor

編輯項目的settings.py文件难礼,把localflavor添加到INSTALLED_APPS設(shè)置中。

我們會添加一個美國(U.S)郵政編碼字段玫锋,所以創(chuàng)建新訂單時需要一個有效的美國郵政編碼鹤竭。

編輯orders應(yīng)用的forms.py文件,如下修改:

from django import forms
from .models import Order
from localflavor.us.forms import USZipCodeField

class OrderCreateForm(forms.ModelForm):
    postal_code = USZipCodeField()
    class Meta:
        model = Order
        fields = ['first_name', 'last_name', 'email', 
            'address', 'postal_code', 'city']

我們從localflavorus包中導(dǎo)入了USZipCodeField字段景醇,并把它用于OrderCreateForm表單的postal_code字段。在瀏覽器中打開http://127.0.0.1:8000/en/orders/create/吝岭,嘗試輸入一個3個字母的郵政編碼三痰。你會看USZipCodeField拋出的驗證錯誤:

Enter a zip code in the format XXXXX or XXXXX-XXXX.

這只是一個簡單的例子,說明如何在你的項目中使用localflavor的自定義字段進(jìn)行驗證窜管。localflavor提供的本地組件對于讓你的應(yīng)用適應(yīng)特定國家非常有用散劫。你可以在這里閱讀django-localflavor文檔,查看每個國家所有可用的本地組件幕帆。

接下來获搏,我們將在商店中構(gòu)建一個推薦引擎。

9.3 構(gòu)建推薦引擎

推薦引擎是一個預(yù)測用戶對商品的偏好或評價的系統(tǒng)失乾。系統(tǒng)根據(jù)用戶行為和對用戶的了解選擇商品常熙。如今纬乍,很多在線服務(wù)都使用推薦系統(tǒng)。它們幫助用戶從大量的可用數(shù)據(jù)中選擇用戶可能感興趣的內(nèi)容裸卫。提供良好的建議可以增強(qiáng)用戶參與度仿贬。電子商務(wù)網(wǎng)站還可以通過推薦相關(guān)產(chǎn)品提高銷量。

我們將創(chuàng)建一個簡單墓贿,但強(qiáng)大的推薦引擎茧泪,來推測用戶通常會一起購買的商品。我們將根據(jù)歷史銷售確定通常一起購買的商品聋袋,來推薦商品队伟。我們將在兩個不同的場景推薦補(bǔ)充商品:

  • 商品詳情頁面:我們將顯示一個通常與給定商品一起購買的商品列表。它會這樣顯示:購買了這個商品的用戶還買了X幽勒,Y嗜侮,Z。我們需要一個數(shù)據(jù)結(jié)構(gòu)代嗤,存儲每個商品與顯示的商品一起購買的次數(shù)棘钞。
  • 購物車詳情頁面:根據(jù)用戶添加到購物車中的商品,我們將推薦通常與這些商品一起購買的商品干毅。這種情況下宜猜,我們計算的獲得相關(guān)商品的分?jǐn)?shù)必須匯總。

我們將使用Redis存儲一起購買的商品硝逢。記住姨拥,你已經(jīng)在第六章中使用了Redis。如果你還沒有安裝Redis渠鸽,請參考第六章叫乌。

9.3.1 根據(jù)之前的購買推薦商品

現(xiàn)在,我們將根據(jù)用戶已經(jīng)添加到購物車中的商品來推薦商品徽缚。我們將在Redis中為網(wǎng)站中每個出售的商品存儲一個鍵憨奸。商品鍵會包括一個帶評分的Redis有序集。每次完成一筆新的購買凿试,我們?yōu)槊總€一起購買的商品的評分加1窟坐。

當(dāng)一個訂單支付成功后佣盒,我們?yōu)橘徺I的每個商品存儲一個鍵,其中包括屬于同一個訂單的商品有序集。這個有序集讓我們可以為一起購買的商品評分缺狠。

編輯項目的settings.py文件萤捆,編輯以下設(shè)置:

REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 1

這是建立一個Redis服務(wù)器連接必須的設(shè)置兼贸。在shop應(yīng)用目錄中創(chuàng)建一個recommender.py文件晰绎,添加以下代碼:

import redis
from django.conf import settings
from .models import Product

# connect to Redis
r = redis.StrictRedis(
    host=settings.REDIS_HOST,
    port=settings.REDIS_PORT,
    db=settings.REDIS_DB
)

class Recommender:
    def get_product_key(self, id):
        return 'product:{}:purchased_with'.format(id)

    def products_bought(self, products):
        product_ids = [p.id for p in products]
        for product_id in product_ids:
            for with_id in product_ids:
                # get the other products bought with each product
                if product_id != with_id:
                    # increment score for product purchased together
                    r.zincrby(self.get_product_key(product_id), with_id, amount=1)

Recommender類允許我們存儲購買的商品,以及為給定的商品檢索商品推薦。get_product_key()方法接收一個Product對象的ID在跳,然后為存儲相關(guān)商品的有序集構(gòu)建Redis鍵枪萄,看起來是這樣的:product:[id]:purchased_with

product_bought()方法接收一個一起購買(也就是屬于同一個訂單)的Product對象列表硬毕。我們在這個方法中執(zhí)行以下任務(wù):

  1. 我們獲得給定的Product對象的商品ID呻引。
  2. 我們迭代商品ID。對于每個ID吐咳,我們迭代商品ID逻悠,并跳過同一個商品,這樣我們獲得了與每個商品一起購買的商品韭脊。
  3. 我們用get_product_id()方法獲得每個購買的商品的Redis商品鍵童谒。對于一個ID是33的商品,這個方法返回的鍵是product:33:purchased_with沪羔。這個鍵用于包括與這個商品一起購買的商品ID的有序集饥伊。
  4. 我們將ID包含在有序集中的商品評分加1。這個評分表示其它商品與給定商品一起購買的次數(shù)蔫饰。

因此這個方法可以保存一起購買的商品琅豆,并對它們評分。現(xiàn)在篓吁,我們需要一個方法檢索與給定的商品列表一起購買的商品茫因。在Recommender類中添加suggest_products_for()方法:

def suggest_products_for(self, products, max_results=6):
    product_ids = [p.id for p in products]
    if len(products) == 1:
        # only 1 product
        suggestions = r.zrange(
            self.get_product_key(product_ids[0]),
            0, -1, desc=True
        )[:max_results]
    else:
        # generate a temporary key
        flat_ids = ''.join([str(id) for id in product_ids])
        tmp_key = 'tmp_{}'.format(flat_ids)
        # multiple products, combine scores of all products
        # store the resulting sored set in a temporary key
        keys = [self.get_product_key(id) for id in product_ids]
        r.zunionstore(tmp_key, keys)
        # remove ids for the products the recommendation is for 
        r.zrem(tmp_key, *product_ids)
        # get the product ids by their score, descendant sort
        suggestions = r.zrange(tmp_key, 0, -1, desc=True)[:max_results]
        # remove the temporary key
        r.delete(tmp_key)
    suggested_products_ids = [int(id) for id in suggestions]

    # get suggested products and sort by order of appearance
    suggested_products = list(Product.objects.filter(id__in=suggested_products_ids))
    suggested_products.sort(key=lambda x: suggested_products_ids.index(x.id))
    return suggested_products

suggest_products_for()方法接收以下參數(shù):

  • products:要獲得推薦商品的商品列表。它可以包括一個或多個商品杖剪。
  • max_results:一個整數(shù)冻押,表示返回的推薦商品的最大數(shù)量。

在這個方法中盛嘿,我們執(zhí)行以下操作:

  1. 我們獲得給定商品對象的商品ID洛巢。
  2. 如果只給定了一個商品,我們檢索與該商品一起購買的商品ID次兆,并按它們一起購買的總次數(shù)排序稿茉。我們用Redis的ZRANGE命令進(jìn)行排序。我們限制結(jié)果數(shù)量為max_results參數(shù)指定的數(shù)量(默認(rèn)是6)芥炭。
  3. 如果給定的商品多余1個狈邑,我們用商品ID生成一個臨時的Redis鍵。
  4. 我們組合每個給定商品的有序集中包括的商品蚤认,并求和所有評分。通過Redis的ZUNIONSTORE命令實現(xiàn)這個操作糕伐。ZUNIONSTORE命令用給定的鍵執(zhí)行有序集的并集砰琢,并在新的Redis鍵中存儲元素的評分總和。你可以在這里閱讀更多關(guān)于這個命令的信息。我們在一個臨時鍵中存儲評分和陪汽。
  5. 因為我們正在匯總評分训唱,所以我們得到的有可能是正在獲得推薦商品的商品。我們用ZREM命令從生成的有序集中移除它們挚冤。
  6. 我們從臨時鍵中檢索商品ID况增,并用ZRANGE命令根據(jù)評分排序。我們限制結(jié)果數(shù)量為max_results參數(shù)指定的數(shù)量训挡。然后我們移除臨時鍵澳骤。
  7. 最后,我們用給定的ID獲得Product對象澜薄,并按ID同樣的順序進(jìn)行排序为肮。

為了更實用,讓我們再添加一個清除推薦的方法肤京。在Recommender類中添加以下方法:

def clear_purchases(self):
    for id in Product.objects.values_list('id', flat=True):
        r.delete(self.get_product_key(id))

讓我們試試推薦引擎颊艳。確保數(shù)據(jù)庫中包括幾個Product對象,并在終端使用以下命令初始化Redis服務(wù):

src/redis-server

打開另一個終端忘分,執(zhí)行python manage.py shell棋枕,輸入下面代碼檢索商品:

from shop.models import Product
black_tea = Product.objects.get(translations__name='Black tea')
red_tea = Product.objects.get(translations__name='Red tea')
green_tea = Product.objects.get(translations__name='Green tea')
tea_powder = Product.objects.get(translations__name='Tea powder')

然后添加一些測試購買到推薦引擎中:

from shop.recommender import Recommender
r = Recommender()
r.products_bought([black_tea, red_tea])
r.products_bought([black_tea, green_tea])
r.products_bought([red_tea, black_tea, tea_powder])
r.products_bought([green_tea, tea_powder])
r.products_bought([black_tea, tea_powder])
r.products_bought([red_tea, green_tea])

我們已經(jīng)存儲了以下評分:

black_tea: red_tea (2), tea_powder (2), green_tea (1)
red_tea: black_tea (2), tea_powder (1), green_tea (1)
green_tea: black_tea (1), tea_powder (1), red_tea(1)
tea_powder: black_tea (2), red_tea (1), green_tea (1)

讓我們看一眼單個商品的推薦商品:

>>> r.suggest_products_for([black_tea])
[<Product: Tea powder>, <Product: Red tea>, <Product: Green tea>]
>>> r.suggest_products_for([red_tea])
[<Product: Black tea>, <Product: Tea powder>, <Product: Green tea>]
>>> r.suggest_products_for([green_tea])
[<Product: Black tea>, <Product: Tea powder>, <Product: Red tea>]
>>> r.suggest_products_for([tea_powder])
[<Product: Black tea>, <Product: Red tea>, <Product: Green tea>]

正如你所看到的,推薦商品的順序基于它們的評分排序妒峦。讓我們用多個商品的評分總和獲得推薦商品:

>>> r.suggest_products_for([black_tea, red_tea])
[<Product: Tea powder>, <Product: Green tea>]
>>> r.suggest_products_for([green_tea, red_tea])
[<Product: Black tea>, <Product: Tea powder>]
>>> r.suggest_products_for([tea_powder, black_tea])
[<Product: Red tea>, <Product: Green tea>]

你可以看到重斑,推薦商品的順序與評分總和匹配。例如舟山,black_teared_tea的推薦商品是tea_powder(2+1)green_tea(1+1)绸狐。

我們已經(jīng)確認(rèn)推薦算法如期工作了。讓我們?yōu)榫W(wǎng)站的商品顯示推薦累盗。

編輯shop應(yīng)用的views.py文件寒矿,并添加以下導(dǎo)入:

from .recommender import Recommender

product_detail()視圖的render()函數(shù)之前添加以下代碼:

r = Recommender()
recommended_products = r.suggest_products_for([product], 4)

我們最多獲得4個推薦商品。現(xiàn)在product_detail視圖如下所示:

from .recommender import Recommender

def product_detail(request, id, slug):
    language = request.LANGUAGE_CODE
    product = get_object_or_404(
        Product, 
        id=id, 
        translations__language_code=language,
        translations__slug=slug, 
        available=True)
    cart_product_form = CartAddProductForm()
    r = Recommender()
    recommended_products = r.suggest_products_for([product], 4)
    return render(
        request,
        'shop/product/detail.html',
        {
            'product': product,
            'cart_product_form': cart_product_form,
            'recommended_products': recommended_products
        }
    )

現(xiàn)在編輯shop應(yīng)用的shop/product/detail.html模板若债,在{{ product.description|linebreaks }}之后添加以下代碼:

{% if recommended_products %}
    <div class="recommendations">
        <h3>{% trans "People who bought this also bought" %}</h3>
        {% for p in recommended_products %}
            <div class="item">
                <a href="{{ p.get_absolute_url }}">
                    ![]({% if p.image %}{{ p.image.url }}{% else %}{% static )
                </a>
                <p><a href="{{ p.get_absolute_url }}">{{ p.name }}</a></p>
            </div>
        {% endfor %}
    </div>
{% endif %}

使用python manage.py runserver啟動開發(fā)服務(wù)器符相,并在瀏覽器中打開http://127.0.0.1:8000/en/。點擊任何一個商品顯示詳情頁面蠢琳。你會看到商品下面的推薦商品啊终,如下圖所示:

接下來我們在購物車中包括商品推薦“列耄基于用戶添加到購物車中的商品生成推薦商品蓝牲。編輯cart應(yīng)用的views.py文件,添加以下導(dǎo)入:

from shop.recommender import Recommender

然后編輯cart_detail視圖泰讽,如下所示:

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

    r = Recommender()
    cart_products = [item['product'] for item in cart]
    recommended_products = r.suggest_products_for(cart_products, max_results=4)
    return render(
        request, 'cart/detail.html', 
        {
            'cart': cart, 
            'coupon_apply_form': coupon_apply_form,
            'recommended_products': recommended_products
        }
    )

編輯cart應(yīng)用的cart/detail.html模板例衍,在</table>標(biāo)簽之后添加以下代碼:

{% if recommended_products %}
    <div class="recommendations cart">
        <h3>{% trans "People who bought this also bought" %}</h3>
        {% for p in recommended_products %}
            <div class="item">
                <a href="{{ p.get_absolute_url }}">
                    ![]({% if p.image %}{{ p.image.url }}{% else %}{% static )
                </a>
                <p><a href="{{ p.get_absolute_url }}">{{ p.name }}</a></p>
            </div>
        {% endfor %}
    </div>
{% endif %}

在瀏覽器中打開http://127.0.0.1:8000/en/昔期,并添加一些商品到購物車中。當(dāng)你導(dǎo)航到http://127.0.0.1:8000/en/cart/佛玄,你會看到購物車中商品合計的推薦商品硼一,如下圖所示:

恭喜你!你已經(jīng)用Django和Redis構(gòu)建了一個完整的推薦引擎梦抢。

9.4 總結(jié)

在本章中般贼,你使用會話創(chuàng)建了優(yōu)惠券系統(tǒng)。你學(xué)習(xí)了如何進(jìn)行國際化和本地化奥吩。你還用Redis構(gòu)建了一個推薦引擎哼蛆。

在下一章中,你會開始一個新的項目圈驼。你會通過Django使用基于類的視圖構(gòu)建一個在線學(xué)習(xí)平臺人芽,你還會創(chuàng)建一個自定義的內(nèi)容管理系統(tǒng)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绩脆,一起剝皮案震驚了整個濱河市萤厅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌靴迫,老刑警劉巖惕味,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異玉锌,居然都是意外死亡名挥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門主守,熙熙樓的掌柜王于貴愁眉苦臉地迎上來禀倔,“玉大人,你說我怎么就攤上這事参淫【群” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵涎才,是天一觀的道長鞋既。 經(jīng)常有香客問我,道長耍铜,這世上最難降的妖魔是什么邑闺? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮棕兼,結(jié)果婚禮上陡舅,老公的妹妹穿的比我還像新娘。我一直安慰自己伴挚,他們只是感情好蹭沛,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布臂寝。 她就那樣靜靜地躺著,像睡著了一般摊灭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上败徊,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天帚呼,我揣著相機(jī)與錄音,去河邊找鬼皱蹦。 笑死煤杀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沪哺。 我是一名探鬼主播沈自,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼辜妓!你這毒婦竟也來了枯途?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤籍滴,失蹤者是張志新(化名)和其女友劉穎酪夷,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孽惰,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡晚岭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了勋功。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坦报。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖狂鞋,靈堂內(nèi)的尸體忽然破棺而出片择,到底是詐尸還是另有隱情,我是刑警寧澤要销,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布构回,位于F島的核電站,受9級特大地震影響疏咐,放射性物質(zhì)發(fā)生泄漏纤掸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一浑塞、第九天 我趴在偏房一處隱蔽的房頂上張望借跪。 院中可真熱鬧,春花似錦酌壕、人聲如沸掏愁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽果港。三九已至沦泌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間辛掠,已是汗流浹背谢谦。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留萝衩,地道東北人回挽。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像猩谊,于是被迫代替她去往敵國和親千劈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容