在之前的文章中,我介绍了Elixir的各个方面, Elixir是一种现代的函数式编程语言。 但是,今天,我想抛开语言本身,讨论一个用Elixir编写的名为Phoenix的非常快速和可靠的MVC框架。
该框架出现于近五年前,此后受到了一定的关注。 当然,它还不如Rails或Django流行,但它确实具有巨大的潜力,我非常喜欢它。
在本文中,我们将了解如何在Phoenix应用程序中引入I18n 。 你问什么是I18n ? 嗯,这是一个小写字母 ,表示“国际化”,因为第一个字母“ i”和最后一个字母“ n”之间恰好有18个字符。 可能您还遇到了一个L10n的别名,表示“本地化”。 这些天的开发人员是如此懒惰,他们甚至不能写几个额外的字符,是吗?
国际化是一个非常重要的过程,特别是如果您预见到世界各地的人们正在使用该应用程序。 毕竟,并不是每个人都非常了解英语,将应用程序翻译成用户的母语会给人留下很好的印象。
看来,翻译Phoenix应用程序的过程与翻译Rails应用程序有些不同(但与Django中的相同过程非常相似)。 为了翻译Phoenix应用程序,我们使用了非常流行的解决方案Gettext ,该解决方案已经存在了25多年了。 Gettext可与特殊类型的文件(即PO和POT)一起使用,并支持作用域,复数和其他功能。
因此,在这篇文章中,我将向您解释什么是Gettext,PO与POT有何不同,如何在Phoenix中本地化消息以及在哪里存储翻译。 此外,我们还将看到如何切换应用程序的语言环境以及如何使用复数规则和域。
我们可以开始了吗?
Gettext的国际化
Gettext是经过考验的开源国际化工具,最初由Sun Microsystems于1990年推出。1995年,GNU发布了自己的Gettext版本,该版本现在被认为是最受欢迎的版本(最新版本为0.19.8)。撰写本文时)。 Gettext可用于创建从Web应用程序到操作系统的任何大小和类型的多语言系统。 这个解决方案非常复杂,我们当然不会讨论其所有功能。 完整的Gettext文档可在gnu.org上找到。
Gettext为您提供了执行本地化所需的所有必要工具,并对翻译文件的命名和组织方式提出了一些要求。 两种文件类型用于托管翻译: PO和MO 。
PO ( 可移植对象)文件存储给定字符串的翻译以及复数规则和元数据。 这些文件的结构非常简单,可以很容易地被人编辑,因此在本文中我们将坚持使用它们。 每个PO文件包含一种语言的翻译(或部分翻译),并且应存储在以该语言命名的目录中: en , fr , de等。
MO ( 机器对象)文件包含二进制数据,并不意味着人类可以直接对其进行编辑。 它们很难一起使用,讨论它们超出了本文的范围。
为了使事情变得更复杂,还有POT(便携式对象模板)文件。 它们仅托管要翻译的数据字符串,而不托管翻译本身。 基本上,POT文件仅用作为各种语言环境创建PO文件的蓝图。
Phoenix示例应用程序
好吧,现在让我们继续练习! 如果您想继续,请确保已安装以下内容:
通过运行以下命令创建没有数据库的新示例应用程序:
mix phx.new i18ndemo --no-ecto
--no-ecto
表示应用程序不应使用该数据库( Ecto是与数据库本身进行通信的工具)。 请注意,生成器可能需要几分钟来准备所有内容。
现在,使用cd
转到新创建的i18ndemo
文件夹,并运行以下命令来启动服务器:
mix phx.server
接下来,打开浏览器并导航到http://localhost:4000
,您将在其中看到“欢迎使用Phoenix!”。 信息。
您好,Gettext!
关于Phoenix应用程序,有趣的是,特别是欢迎消息是,默认情况下已使用Gettext。 继续并打开demo/lib/demo_web/templates/page/index.html.eex
文件,它是默认的起始页面。 除去此代码之外的所有内容:
<div class="jumbotron">
<h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
</div>
此欢迎消息利用gettext
函数,该函数接受要翻译的字符串作为第一个参数。 尽管它与Rails I18n和某些其他框架中使用的键有所不同,但可以将其视为转换键 。 在Rails中,我们将使用诸如page.welcome
类的键,而此处的转换后的字符串本身就是一个键。 因此,如果找不到翻译,我们可以直接显示此字符串。 即使是一个不懂英语的用户,也至少可以对发生的事情有基本的了解。
实际上,这种方法非常方便-停下来再想一想。 您有一个应用程序,其中所有消息均为英文。 如果您想对其进行国际化,在最简单的情况下,您要做的就是用gettext
函数包装您的消息并为其提供翻译(稍后,我们将看到提取密钥的过程可以很容易地实现自动化,从而加快了速度)事情甚至更多)。
好的,让我们回到小代码段,看看传递给gettext
的第二个参数: name: "Phoenix"
。 这就是所谓的绑定 -用%{}
包装的参数,我们想插入到给定的转换中。 在此示例中,只有一个名为name
参数。
我们也可以在此页面上添加更多消息,以进行演示:
<div class="jumbotron">
<h2><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h2>
<h3><%= gettext "We are using version %{version}",
version: "1.3" %></h3>
</div>
添加新的翻译
现在,我们在根页面上有两条消息,我们应该在哪里添加翻译? 似乎所有翻译都存储在priv/gettext
文件夹下,该文件夹具有预定义的结构。 让我们花点时间讨论如何组织Gettext文件(这不仅适用于Phoenix,而且适用于使用Gettext的任何应用程序)。
首先,我们应该创建一个文件夹,该文件夹以将要存储翻译的语言环境命名。 在内部,应该有一个名为LC_MESSAGES
的文件夹,其中包含一个或多个带有实际翻译的.po
文件。 在最简单的情况下,每个区域设置一个default.po
文件。 default
为域(或范围)的名称。 域用于将翻译划分为不同的组:例如,您可能具有名为admin
, wysiwig
, cart
和其他名称的域。 当您有包含数百条消息的大型应用程序时,这很方便。 但是,对于较小的应用程序,仅拥有一个default
域就足够了。
因此,我们的文件结构可能如下所示:
- 恩
- LC_MESSAGES
- default.po
- 管理员
- LC_MESSAGES
- RU
- LC_MESSAGES
- default.po
- 管理员
- LC_MESSAGES
要开始创建PO文件,我们首先需要相应的模板(POT)。 我们可以手动创建它,但是我懒得用这种方式来创建它。 让我们改为运行以下命令:
mix gettext.extract
这是一个非常方便的工具,可以扫描项目的文件并检查Gettext是否在任何地方使用。 脚本完成工作后,将创建一个新的priv/gettext/default.pot
文件,其中包含要翻译的字符串。
正如我们已经了解的那样,POT文件是模板,因此它们仅存储密钥本身,而不存储翻译,因此请勿手动修改此类文件。 打开一个新创建的文件,并查看其内容:
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
msgid ""
msgstr ""
#: lib/demo_web/templates/page/index.html.eex:3
msgid "We are using version %{version}"
msgstr ""
#: lib/demo_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr ""
方便,不是吗? 我们所有的消息都是自动插入的,我们可以轻松地准确看到它们的位置。 msgid
,因为你可能已经猜到,是关键,而msgstr
将会包含一个翻译。
当然,下一步是生成PO文件。 跑:
mix gettext.merge priv/gettext
该脚本将利用default.pot
模板,并在priv/gettext/en/LC_MESSAGES
文件夹中创建default.po
文件。 目前,我们只有英语语言环境,但是在下一节中还将添加对另一种语言的支持。
顺便说一句,可以使用以下命令一次性创建或更新POT模板和所有PO文件:
mix gettext.extract --merge
现在,让我们打开priv/gettext/en/LC_MESSAGES/default.po
文件,其中包含以下内容:
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"
#: lib/demo_web/templates/page/index.html.eex:3
msgid "We are using version %{version}"
msgstr ""
#: lib/demo_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr ""
这是我们应该执行实际翻译的文件。 当然,这样做是没有意义的,因为消息已经是英文了,所以让我们继续下一节并添加对第二种语言的支持。
多个地区
当然,Phoenix应用程序的默认语言环境是英语,但是可以通过调整config/config.exs
文件轻松更改此设置。 例如,让我们将默认语言环境设置为俄语(随意使用您选择的任何其他语言):
config :demo, I18ndemoWeb.Gettext, default_locale: "ru"
指定所有支持的语言环境的完整列表也是一个好主意:
config :demo, I18ndemoWeb.Gettext, default_locale: "ru", locales: ~w(en ru)
现在,我们需要生成一个新的PO文件,其中包含俄语语言环境的翻译。 它可以通过运行来完成gettext.merge
再次剧本,但有--locale
开关:
mix gettext.merge priv/gettext --locale ru
显然,将生成一个带有.po
文件的priv/gettext/ru/LC_MESSAGES
文件夹。 注意,顺便说一句,除了default.po
文件,我们还有errors.po
。 这是翻译错误消息的默认位置,但是在本文中,我们将忽略它。
现在通过添加一些翻译来调整priv/gettext/ru/LC_MESSAGES/default.po
:
#: lib/demo_web/templates/page/index.html.eex:3
msgid "We are using version %{version}"
msgstr "Используется версия %{version}"
#: lib/demo_web/templates/page/index.html.eex:2
msgid "Welcome to %{name}!"
msgstr "Добро пожаловать в приложение %{name}!"
现在,根据所选的语言环境,Phoenix将提供英语或俄语翻译。 但是等一下! 我们实际上如何在应用程序的语言环境之间切换? 让我们继续下一节并找出答案!
在语言环境之间切换
现在已经有了一些翻译,我们需要使我们的用户能够在语言环境之间切换。 似乎有一个名为set_locale的第三方插件。 它通过从URL或Accept-Language
HTTP标头中提取所选语言环境来工作。 因此,要在URL中指定语言环境,请输入http://localhost:4000/en/some_path
。 如果未指定语言环境(或请求使用不受支持的语言),则会发生以下两种情况之一:
- 如果请求包含一个
Accept-Language
HTTP标头,并且支持此语言环境,则该用户将被重定向到具有相应语言环境的页面。 - 否则,用户将被自动重定向到包含默认语言环境代码的URL。
打开mix.exs
文件并将set_locale
放到deps
函数中:
defp deps do
[
# ...
{:set_locale, "~> 0.2.1"}
]
end
我们还必须将其添加到application
功能中:
def application do
[
mod: {Demo.Application, []},
extra_applications: [:logger, :runtime_tools, :set_locale]
]
end
接下来,安装所有内容:
mix deps.get
我们位于lib/demo_web/router.ex
路由器lib/demo_web/router.ex
需要进行一些更改。 具体来说,我们需要在:browser
管道中添加一个新的插件:
pipeline :browser do
# ...
plug SetLocale, gettext: DemoWeb.Gettext, default_locale: "ru"
end
另外,创建一个新范围:
scope "/:locale", DemoWeb do
pipe_through :browser
get "/", PageController, :index
end
就是这样! 您可以引导服务器并导航到http://localhost:4000/ru
和http://localhost:4000/en
。 请注意,消息已正确翻译,这正是我们所需要的!
或者,您可以使用模块插头自己编写类似的功能代码。 在Phoenix官方指南中可以找到一个小例子。
最后要提到的是,在某些情况下,您可能需要强制执行特定的语言环境。 为此,只需利用with_locale
函数 :
Gettext.with_locale I18ndemoWeb.Gettext, "en", fn ->
MyApp.I18ndemoWeb.gettext("test")
end
多元化
我们已经了解了将Phoenix与Gettext结合使用的基础知识,因此该讨论一些稍微复杂的事情了。 多元化是其中之一。 基本上,使用复数和单数形式是一项非常常见的工作,尽管可能会很复杂。 英语中的现象或多或少是显而易见的,因为您拥有“ 1个苹果”,“ 2个苹果”,“ 9000个苹果”等(尽管“ 1牛”,“ 2牛”!)。
不幸的是,在某些其他语言(如俄语或波兰语)中,规则更加复杂。 例如,对于苹果,您会说“ 1яблоко”,“ 2яблока”,“ 9000яблок”。 但幸运的是,Phoenix具有Gettext.Plural
行为(您可能在我之前的文章中看到了这种行为),它支持许多不同的语言。 因此,我们要做的就是利用ngettext
函数。
该函数接受三个必需的参数:单数形式的字符串,复数形式的字符串和计数。 第四个参数是可选的,可以包含应插入转换中的绑定。
让我们通过修改demo/lib/demo_web/templates/page/index.html.eex
文件demo/lib/demo_web/templates/page/index.html.eex
用户ngettext
来查看ngettext
的作用:
<p>
<%= ngettext "You have one buck. Ow :(", "You have %{count} bucks", 540 %>
</p>
%{count}
是将被数字替换的插值(在这种情况下为540
)。 添加上面的字符串后,不要忘记更新模板和所有PO文件:
mix gettext.extract --merge
您将看到一个新的块已添加到两个default.po
文件中:
msgid "You have one buck. Ow :("
msgid_plural "You have %{count} bucks"
msgstr[0] ""
msgstr[1] ""
我们一次没有两个,只有一个和两个:单数和复数形式。 只有一条消息时, msgstr[0]
将包含一些要显示的文本。 msgstr[1]
当然包含有多个消息时显示的文本。 这对于英语来说还可以,但对于俄语,这还不够,我们需要介绍第三种情况:
msgid "You have one buck. Ow :("
msgid_plural "You have %{count} bucks"
msgstr[0] "У 1 доллар. Маловато будет!"
msgstr[1] "У вас %{count} доллара"
msgstr[2] "У вас %{count} долларов"
情况0
用于1块钱,情况1
用于0或几块钱。 否则使用情况2
。
使用域限定翻译范围
我想在本文中讨论的另一个主题是领域 。 众所周知,域通常用于范围转换,主要是在大型应用程序中。 基本上,它们的行为类似于namespaces 。
毕竟,您可能最终会遇到在多个地方使用了相同密钥的情况,但是在翻译时应该有所不同。 或者,如果您在单个default.po
文件中包含太多翻译,并希望以某种方式拆分它们。 那时,域名可以派上用场。
Gettext开箱即用地支持多个域。 您所要做的就是利用dgettext
函数,该函数的作用与gettext
几乎相同。 唯一的区别是它接受域名作为第一个参数。 例如,让我们介绍一个通知域来显示通知。 在demo/lib/demo_web/templates/page/index.html.eex
文件中再添加三行代码:
<p>
<%= dgettext "notifications", "Heads up: %{msg}", msg: "something has happened!" %>
</p>
现在我们需要创建新的POT和PO文件:
mix gettext.extract --merge
脚本完成其工作后,将创建notifications.pot
以及两个notifications.po
文件。 再次注意,它们是以域命名的。 您现在要做的就是通过修改priv/ru/LC_MESSAGES/notifications.po
文件来添加俄语翻译:
msgid "Heads up: %{msg}}"
msgstr "Внимание: %{msg}"
如果您想对存储在给定域下的消息进行复数怎么办? 这就像利用dngettext
函数一样简单。 它的工作方式与ngettext
但也接受域名称作为第一个参数:
dgettext "domain", "Singular string %{msg}", "Plural string %{msg}", 10, msg: "demo"
结论
在本文中,我们已经了解了如何在Gettext的帮助下在Phoenix应用程序中引入国际化。 您已经了解了什么是Gettext以及可以使用的文件类型。 我们已将此解决方案付诸实践,使用PO和POT文件,并利用了各种Gettext函数。
我们还看到了增加对多个语言环境的支持的方法,并增加了在它们之间轻松切换的方法。 最后,我们已经了解了如何使用复数规则以及如何借助域来限定翻译范围。
希望本文对您有用! 如果您想在Phoenix框架中了解有关Gettext的更多信息,可以参考官方指南 ,该指南提供了有用的示例以及所有可用功能的API参考。
我感谢您与我在一起并很快见到您!
翻译自: https://code.tutsplus.com/tutorials/phoenix-i18n--cms-30010