最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Django 多語(yǔ)言教程的實(shí)現(xiàn)(i18n)

 更新時(shí)間:2018年07月07日 08:45:31   作者:ocavue  
這篇文章主要介紹了Django 多語(yǔ)言教程的實(shí)現(xiàn)(i18n),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧

最近公司準(zhǔn)備擴(kuò)張海外業(yè)務(wù),所以要給 Django 系統(tǒng)添加 國(guó)際化與本土化 支持。國(guó)際化一般簡(jiǎn)稱 i18n ,代表 Internationalization 中 i 和 n 有 18 個(gè)字母;本地化簡(jiǎn)稱 L10n ,表示 Localization 中 l 和 n 中有 10 個(gè)字母。有趣的一點(diǎn)是,一般會(huì)用小寫的 i 和大寫的 L 防止混淆。

簡(jiǎn)單來(lái)說(shuō):i18n 是為國(guó)際化搭建框架,L10n 是針對(duì)不同地區(qū)的適配。舉個(gè)簡(jiǎn)單的例子:

i18n:

datetime.now().strftime('%Y/%m/%d') # before i18n
datetime.now().strftime(timeformat) # after i18n

L10n:

timeformat = {
 'cn': '%Y/%m/%d',
 'us': '%m/%d/%Y',
 'fr': '%d/%m/%Y',
 ...
}

更加具體的定義可以看https://www.w3.org/International/questions/qa-i18n的解釋。

i18n 的范圍非常廣,包括多語(yǔ)言、時(shí)區(qū)、貨幣單位、單復(fù)數(shù)、字符編碼甚至是文字閱讀順序(RTL)等等。這篇文章只關(guān)注 i18n 的多語(yǔ)言 方面。

↑ 阿拉伯語(yǔ)的 windows 系統(tǒng),文字甚至界面的方向都與中文版的相反

基本步驟

Django 作為一個(gè)大而全的框架,已經(jīng)提供了一套多語(yǔ)言的解決方案,我稍微對(duì)比了一下,并沒能找到在 Django 體系下比官方方案還好用的庫(kù)。Django 的方案可以簡(jiǎn)單分為四步:

  1. 一些必要的配置
  2. 在代碼中標(biāo)記需要翻譯的文本
  3. 使用 makemessages 命令生成 po 文件
  4. 編譯 compilemessages 命令編譯 mo 文件

下面我們?cè)敿?xì)來(lái)看看

第一步:配置

首先在 settings.py 中加入這幾個(gè)內(nèi)容

LOCALE_PATHS = (
 os.path.join(__file__, 'language'),
)
MIDDLEWARE = (
 ...
 'django.middleware.locale.LocaleMiddleware',
 ...
)
LANGUAGES = (
 ('en', 'English'),
 ('zh', '中文'),
)

LOCALE_PATHS :指定下面第三步和第四步生成文件的位置。老版的 Django 需要手動(dòng)新建好這個(gè)目錄。

LocaleMiddleware :可以讓 Django 識(shí)別并選擇合適的語(yǔ)言。

LANGUAGES :指定了這個(gè)工程能提供哪些語(yǔ)言。

第二步:標(biāo)記文本

之前沒有多語(yǔ)言的需要,所以大家在 AJAX 相應(yīng)代碼中直接寫了中文,比如這樣:

return JsonResponse({"msg": "內(nèi)容過(guò)長(zhǎng)", "code": 1, "data": None})

現(xiàn)在需要多語(yǔ)言了,就需要告訴 Django 哪些內(nèi)容是需要翻譯的。對(duì)于上面的例子來(lái)說(shuō),就是寫成這樣:

from django.utils.translation import gettext as _

return JsonResponse({"msg": _("內(nèi)容過(guò)長(zhǎng)"), "code": 1, "data": None})

這里使用 gettext 函數(shù)將原本的字符串包裹起來(lái),這樣的話,Django 就可以根據(jù)當(dāng)前語(yǔ)言返回合適的字符串。一般會(huì)使用單個(gè)下劃線 _ 提高可讀性。

因?yàn)槲宜編缀跛星昂蠖送ㄐ哦际褂?AJAX,所以并沒有怎么用上 Django 的模板功能(順便一提,我司前端使用的多語(yǔ)言工具是 i18next )。不過(guò)在這里也一并寫下 Django 模板的標(biāo)記方法:

<title>{% trans "This is the title." %}</title>
<title>{% trans myvar %}</title>

其中 trans 標(biāo)簽告訴 Django 需要翻譯這個(gè)括號(hào)里面的內(nèi)容。更具體的用法可以參考官方文檔。

第三步: makemessages

在執(zhí)行這一步之前,請(qǐng)先通過(guò) xgettext --version 確認(rèn)自己是否安裝了GNU gettext。GNU gettext 是一個(gè)標(biāo)準(zhǔn) i18n L10n 庫(kù),Django 和很多其他語(yǔ)言和庫(kù)的多語(yǔ)言模塊都調(diào)用了 GNU gettext,所以接下來(lái)講的一些 Django 特性實(shí)際上要?dú)w功于 GNU gettext。如果沒有安裝的話可以通過(guò)下面的方法安裝:

ubuntu:

$ apt update
$ apt install gettext

macOS :

$ brew install gettext
$ brew link --force gettext

windows

安裝完 GNU gettext 后,對(duì) Django 工程執(zhí)行下面的命令

$ python3 manage.py makemessages --local en

之后可以找到生成的文件: language/en/LC_MESSAGES/django.po 。把上面命令中的 en 替換成其他語(yǔ)言,就可以生成不同語(yǔ)言的 django.po 文件。里面的內(nèi)容大概是這樣的:

#: path/file.py:397
msgid "訂單已刪除"
msgstr ""

...

Django 會(huì)找到被 gettext 函數(shù)包裹的所有字符串,以 msgid 的形式保存在 django.po 。每個(gè) msgid 下面的 msgstr 就代表你要把這個(gè) msgid 翻譯成什么。通過(guò)修改這個(gè)文件可以告訴 Django 翻譯的內(nèi)容。同時(shí)通過(guò)注釋說(shuō)明了這個(gè) msgid 出現(xiàn)在哪個(gè)文件的哪一行。

關(guān)于這個(gè)文件,發(fā)現(xiàn)幾點(diǎn)有趣的特性:

  1. Django 會(huì)把多個(gè)文件中相同的 msgid 歸類在一起?!敢淮尉庉嫞教幏g」
  2. 如果以后源碼中某個(gè) msgid 被刪了,那么再次執(zhí)行 makemessages 命令后,這個(gè) msgid 和它的 msgstr 會(huì)以注釋的形式繼續(xù)保存在 django.po 中。
  3. 既然源碼中的字符串只是一個(gè)所謂的 id,那么我就可以在源碼中寫沒有實(shí)際含義的字符串,比如 _("ERROR_MSG42"),然后將 "ERROR_MSG42" 同時(shí)翻譯成中文和英文。
  4. 這個(gè)文件中會(huì)保留模板字符串的占位符,比如可以使用命名占位符做到在不同語(yǔ)言中使用不同占位符順序的功能,下面給出了一個(gè)例子:

py file:

_('Today is {month} {day}.').format(month=m, day=d)
_('Today is %(month)s %(day)s.') % {'month': m, 'day': d}

po file

msgid "Today is {month} {day}."
msgstr "Aujourd'hui est {day} {month}."

msgid "Today is %(month)s %(day)s."
msgstr "Aujourd'hui est %(day)s %(month)s."

第四步: compilemessages

修改好 django.po 文件后,執(zhí)行下面的命令:

$ python3 manage.py compilemessages --local en

Django 會(huì)調(diào)用程序,根據(jù) django.po 編譯出一個(gè)名為 django.mo 的二進(jìn)制文件,位置和 django.po 所在位置相同。這個(gè)文件才是程序執(zhí)行的時(shí)候會(huì)去讀取的文件。

執(zhí)行完上面四步后,修改瀏覽器的語(yǔ)言設(shè)置,就可以看到 Django 的不同輸出了。

↑ Chrome 的語(yǔ)言設(shè)置

高級(jí)特性

i18n_patterns

有的時(shí)候,我們希望可以通過(guò) URL 來(lái)選擇不同的語(yǔ)言。這樣做有很多好處,比如同一個(gè) URL 返回的數(shù)據(jù)的語(yǔ)言一定是一致的。Django 的文檔就使用了這種做法:

簡(jiǎn)體中文:https://docs.djangoproject.com/zh-hans/2.0/

英文:https://docs.djangoproject.com/en/2.0/

具體的做法是在 URL 中添加 <slug:slug>

urlpatterns = ([
 path('category/<slug:slug>/', news_views.category),
 path('<slug:slug>/', news_views.details),
])

詳細(xì)的做法可以參考 Django 的官方文檔。

Django 如何決定使用哪種語(yǔ)言

我們之前講過(guò) LocaleMiddleware 可以決定使用何種語(yǔ)言。具體來(lái)說(shuō), LocaleMiddleware 是按照下面的順序(優(yōu)先級(jí)遞減):

  1. i18n_patterns
  2.  request.session[settings.LANGUAGE_SESSION_KEY]
  3. request.COOKIES[settings.LANGUAGE_COOKIE_NAME]
  4. request.META['HTTP_ACCEPT_LANGUAGE'] ,即 HTTP 請(qǐng)求中的 Accept-Language header
  5. settings.LANGUAGE_CODE

我司選擇把語(yǔ)言信息放到 Cookies 中,當(dāng)用戶手動(dòng)選擇語(yǔ)言時(shí),可以讓前端直接修改 Cookies,而不需要請(qǐng)求后臺(tái)的某個(gè)接口。沒有手動(dòng)設(shè)置過(guò)語(yǔ)言的用戶就沒有這個(gè) Cookies,跟隨瀏覽器設(shè)置。話說(shuō) settings.LANGUAGE_COOKIE_NAME 的默認(rèn)值是 django_language ,前端不想在他們的代碼中出現(xiàn) django ,所以我在 settings.py 中添加了 LANGUAGE_COOKIE_NAME = app_language :joy:。

你也可以通過(guò) request.LANGUAGE_CODE 在 View 中手動(dòng)獲知 LocaleMiddleware 選用了哪種語(yǔ)言。你甚至可以通過(guò) activate 函數(shù)手動(dòng)指定當(dāng)前線程使用的語(yǔ)言:

from django.utils.translation import activate

activate('en')

ugettext

Python2 時(shí)代,為了區(qū)分 unicode strings 和 bytestrings,有 ugettextgettext 兩個(gè)函數(shù)。在 Python3 中,由于字符串編碼的統(tǒng)一, ugettextgettext 是等價(jià)的。官方說(shuō)未來(lái)可能會(huì)廢棄 ugettext ,但是截止到現(xiàn)在(Django 2.0), ugettext 還沒廢棄。

gettext_lazy

這里先用一個(gè)例子直觀地看一下 gettext_lazygettext 的區(qū)別

from django.utils.translation import gettext, gettext_lazy, activate, get_language

gettext_str = gettext("Hello World!")
gettext_lazy_str = gettext_lazy("Hello World!")

print(type(gettext_str))
# <class 'str'>
print(type(gettext_lazy_str))
# <class 'django.utils.functional.lazy.<locals>.__proxy__'>

print("current language:", get_language())
# current language: zh
print(gettext_str, gettext_lazy_str)
# 你好世界! 你好世界!

activate("en")

print("current language:", get_language())
# current language: en
print(gettext_str, gettext_lazy_str)
# 你好世界! Hello World!

gettext 函數(shù)返回的是一個(gè)字符串,但是 gettext_lazy 返回的是一個(gè)代理對(duì)象。這個(gè)對(duì)象會(huì)在被使用的時(shí)候,才根據(jù)當(dāng)前線程中語(yǔ)言決定翻譯成什么文字。

這個(gè)功能在 Django 的 models 中尤其的有用。因?yàn)?models 中定義字符串的代碼只會(huì)執(zhí)行一次。在之后的請(qǐng)求中,根據(jù)語(yǔ)言的不同,這個(gè)所謂字符串要有不同的表現(xiàn)。

from django.utils.translation import gettext_lazy as _

class MyThing(models.Model):
 name = models.CharField(help_text=_('This is the help text'))

class YourThing(models.Model):
 kind = models.ForeignKey(
  ThingKind,
  on_delete=models.CASCADE,
  related_name='kinds',
  verbose_name=_('kind'),
 )

使用 AST / FST 修改源碼

由于我司工程非常龐大,人力給每個(gè)字符串添加 _( ... ) 過(guò)于繁瑣。所以我試圖尋找一種自動(dòng)化的方式。

一開始選擇的是 Python 內(nèi)置的 ast (Abstract syntax tree 語(yǔ)法抽象樹) 模塊 ?;舅悸肥峭ㄟ^(guò) ast 找到工程中的所有字符串,再給這些字符串添加 _( ... ) 。最后把修改后的語(yǔ)法樹重新轉(zhuǎn)為代碼。

但是由于 ast 對(duì)格式信息的支持不佳,修改代碼后容易造成格式混亂。所以找到了名為 FST (Full Syntax Tree 全面抽象樹) 的改進(jìn)方式。我選擇的 FST 庫(kù)是 redbaron 。核心的代碼如下:

root = RedBaron(original_code)

for node in root.find_all("StringNode"):
 if (
  has_chinese_char(node)
  and not is_aleady_gettext(node)
  and not is_docstring(node)
 ):
  node.replace("_({})".format(node))

modified_code = root.dumps()

我把完整的代碼放到了 Gist 上,因?yàn)槭且粋€(gè)一次性腳本,寫的比較隨意,大家可以參考。

使用 redbaron 的過(guò)程中也發(fā)現(xiàn)了一些問(wèn)題,一并記錄這里:最大問(wèn)題是 redbaron 已經(jīng)停止維護(hù) 了!所以不能支持一些新語(yǔ)法,比如 Python3.6 的 f-string。其次是這個(gè)庫(kù)和 ast 標(biāo)準(zhǔn)庫(kù)相比,運(yùn)行速度很慢,每次跑這個(gè)腳本我的電腦都發(fā)出了飛機(jī)引擎般的聲音。第三點(diǎn)是會(huì)產(chǎn)生一些奇怪的格式:

修改前:

OutStockSheet = {
 1: '未出庫(kù)',
 2: '已出庫(kù)',
 3: '已刪除'
}

修改后( '已刪除' 右邊的括號(hào)跑到了下一行):

OutStockSheet = {
 1: _('未出庫(kù)'),
 2: _('已出庫(kù)'),
 3: _('已刪除'
)}

最后一點(diǎn)倒是可以通過(guò)格式化工具解決,問(wèn)題不大。

utf8 vs utf-8

項(xiàng)目中有些 py 文件比較老,在文件開頭使用了 # coding: utf8 的標(biāo)示。對(duì)于 Python 來(lái)說(shuō),utf8 是 utf-8 的別名,所以沒有任何問(wèn)題。Django 在調(diào)用 GNU gettext 時(shí),會(huì)使用參數(shù)指定編碼為 utf-8,但是 GNU 也會(huì)讀取文件中的編碼標(biāo)示,而且它的優(yōu)先級(jí)更高。不幸的是 utf8 對(duì) GNU gettext 來(lái)說(shuō)是一個(gè)未知編碼,于是 GNU gettext 會(huì)降級(jí)使用 ASCII 編碼,然后在遇到中文字符時(shí)報(bào)錯(cuò)(真笨?。?/p>

$ python3 manage.py makemessages --local en
...
xgettext: ./path/filename.py:1: Unknown encoding "utf8". Proceeding with ASCII instead.
xgettext: Non-ASCII comment at or before ./path/filename.py:26.

所以我需要把 # coding: utf8 改成 # coding: utf-8 ,或者干脆刪掉這行,反正 Python3 已經(jīng)默認(rèn)使用 utf-8 編碼了。

總結(jié)

Django (和其背后的 GNU gettext) 的多語(yǔ)言功能非常全面,堪稱博大精深,比如處理單復(fù)數(shù)的ngettext,處理多義詞的pgettext。HTTP 響應(yīng)中使用翻譯后的文本,但是在日志中留下翻譯前文本的gettext_noop。

這篇文章主要講了我在實(shí)踐中用到的功能和遇到的坑,希望可以幫助大家了解 Django 多語(yǔ)言的基本用法。歡迎大家評(píng)論:clap:。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論

吉木萨尔县| 百色市| 龙川县| 怀仁县| 雷州市| 荔浦县| 临澧县| 张家港市| 邵东县| 慈利县| 通许县| 建平县| 无极县| 石阡县| 蒙阴县| 固阳县| 平昌县| 上虞市| 永寿县| 梧州市| 中阳县| 昌宁县| 涡阳县| 南岸区| 嘉定区| 林周县| 泗洪县| 当阳市| 澄江县| 娱乐| 光泽县| 金昌市| 德安县| 沅陵县| 曲靖市| 阿图什市| 九江市| 湄潭县| 香河县| 综艺| 金华市|