by Anastasia
由Anastasia
In one of my previous articles, I talked about the process of internationalizing Rails applications. That article explained all I18n basics, but it was revolving around placing all translations inside YAML files. There is nothing wrong with this approach, but unfortunately it does not always work.
在之前的一篇文章中,我谈到了Rails应用程序国际化的过程。 那篇文章解释了所有I18n基础知识,但围绕着将所有翻译内容放入YAML文件中展开。 这种方法没有错,但是不幸的是,它并不总是有效。
Suppose your website has lots of user-generated content which should be adapted for different languages. I propose that you store your translations inside the database. Why will YAML files not work in this case?
假设您的网站上有许多用户生成的内容,这些内容应适用于不同的语言。 我建议您将翻译存储在数据库中。 为什么在这种情况下YAML文件不起作用?
It appears that the I18n module allows you to define a custom backend that, for instance, may be powered by ActiveRecord. Luckily, there is no need to craft your own solution as there is already an existing one: Globalize. Globalize is a battle-tested library characterized as “Rails I18n de-facto standard library for ActiveRecord model/data translation.” With its help you can easily translate model attributes, scope them, introduce fallbacks, and so on.
看来,I18n模块允许您定义一个自定义后端 ,例如,可以由ActiveRecord提供支持。 幸运的是,由于已经存在一个解决方案,因此无需设计自己的解决方案: Globalize 。 Globalize是经过实践检验的库,其特征是“用于ActiveRecord模型/数据转换的Rails I18n实际标准库”。 借助它的帮助,您可以轻松地转换模型属性,对其进行范围划分,引入后备等等。
So, in this article we are going to talk about Globalize and see it in action by creating a sample Rails application. Shall we start?
因此,在本文中,我们将讨论Globalize,并通过创建示例Rails应用程序来了解它的实际作用。 我们可以开始了吗?
Let’s get started by generating a new Rails app:
让我们开始生成一个新的Rails应用程序:
rails new GlobalizeSample
I’ll assume you are using Rails 5.2.1 for this demo but still the described concepts apply to earlier versions as well.
我假设您在此演示中使用Rails 5.2.1 ,但所描述的概念仍然适用于早期版本。
Let’s suppose we are building an international online shop showcasing various products. These products will be added by the administrator, and so we can’t know what the content will be ahead of the game. This means that the traditional method of using YAML files to store translations won’t work. Our content manager will have access to the CMS only, and we would rather not give them access to the source code of the app (I shudder to think about it!).
假设我们正在建立一个展示各种产品的国际网上商店 。 这些产品将由管理员添加,因此我们不知道游戏前会包含哪些内容。 这意味着使用YAML文件存储翻译的传统方法将不起作用。 我们的内容管理器将只能访问CMS,而我们不希望他们访问应用程序的源代码(我不禁要考虑!)。
But, fear not, in the next section we will overcome this problem easily. For now, however, let’s take care of the basics.
但是,不用担心,在下一节中,我们将轻松克服此问题。 但是,现在让我们来了解一些基础知识。
Utilize code generator and create a new scaffold for the Product
:
利用代码生成器并为Product
创建一个新的支架:
rails g scaffold Product title:string description:text
This should create a model, controller, routes, and views for the products. Don’t forget to run the migration:
这应该为产品创建模型,控制器,路线和视图。 不要忘记运行迁移:
rails db:migrate
Now start the server:
现在启动服务器:
rails s
Visit the http://localhost:3000/products
path and make sure that you are able to add, modify, and delete the products.
访问http://localhost:3000/products
路径,并确保您能够添加,修改和删除产品。
In order to see the Globalize library in action, we will need a way to switch the app’s locale. I won’t cover this process in detail (as we have a separate article on the topic) so let’s do it quickly.
为了查看Globalize库的运行情况,我们需要一种方法来切换应用的语言环境。 我不会详细介绍此过程(因为我们有关于该主题的单独文章 ),所以让我们快速进行。
First, add the list of supported locales to the config/application.rb
:
首先,将支持的语言环境列表添加到config/application.rb
:
# ... config.i18n.available_locales = [:en, :ru]
I will be supporting English and Russian, but you may choose any other languages.
我将支持英语和俄语,但您可以选择其他任何语言。
Next, tweak the config/routes.rb
and wrap the product resource with a scope. Also, while we are here, add a root route:
接下来,调整config/routes.rb
并使用范围包装产品资源。 另外,当我们在这里时,添加一条根路由:
scope "(:locale)", locale: /#{I18n.available_locales.join("|")}/ do # <== add this resources :products root 'products#index' # <== add this end # <== add this
After that, modify the application_controller.rb
file:
之后,修改application_controller.rb
文件:
# ... before_action :set_locale private def set_locale I18n.locale = extract_locale || I18n.default_locale end def extract_locale parsed_locale = params[:locale] I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil end def default_url_options { locale: I18n.locale } end
This code will set the locale on every request while making sure the chosen language is actually supported. Also, it will add a locale
GET param to each link generated with the link_to
helper.
该代码将在每个请求上设置语言环境,同时确保实际支持所选语言。 另外,它将为使用link_to
帮助程序生成的每个链接添加一个locale
GET参数。
Lastly, add two links to the application layout:
最后,添加两个指向应用程序布局的链接:
<!-- views/layouts/application.html.erb --> <ul> <li><%= link_to 'English', root_path(locale: :en) %></li> <li><%= link_to 'Русский', root_path(locale: :ru) %></li> </ul>
To ensure that this new feature works, add translation for the Products page title:
为确保此新功能正常运行,请为“产品”页面标题添加翻译:
# config/locales/en.yml en: products: index: title: Our Products
# config/locales/ru.yml ru: products: index: title: Наши продукты
Now simply utilize these translations inside the views/products/index.html.erb
:
现在,只需在views/products/index.html.erb
内部利用这些翻译:
<!-- ... --> <h1><%= t '.title' %></h1> <!-- ... -->
Note that we can take advantage of the “lazy lookup” because the translation keys were named in the proper way.
请注意,我们可以利用“惰性查找”功能,因为转换键是以正确的方式命名的。
Translate other static content as necessary, then reload the server, and make sure that the locale can be properly switched. Great!
根据需要转换其他静态内容,然后重新加载服务器,并确保可以正确切换语言环境。 大!
Okay, the ground work is done and we may proceed to the next part. Before Globalize can get into the game, it should be added to the Gemfile
:
好的,基础工作已经完成,我们可以继续进行下一部分。 在Globalize进入游戏之前,应将其添加到Gemfile
:
# ... gem 'globalize', git: 'https://github.com/globalize/globalize'
At the time of writing this article, the stable version was not yet compatible with Rails 5.2, so we have to install directly from the master
branch. Also note that the latest stable does not support ActiveRecord 4.1 and below, therefore refer to the documentation to learn which Globalize version to use for older AR.
在撰写本文时,该稳定版本尚未与Rails 5.2兼容,因此我们必须直接从master
分支进行安装。 另请注意,最新的稳定器不支持ActiveRecord 4.1及更低版本,因此请参考文档以了解用于较旧的AR的哪个Globalize版本。
Next, you have to decide which model attributes will be translated with Globalize. We are going to translate both :title
and :description
so list them in the model in the following way:
接下来,您必须决定要使用Globalize转换哪些模型属性。 我们将同时翻译:title
和:description
因此可以通过以下方式在模型中列出它们:
# models/products.rb # ... translates :title, :description
This will allow you to store store translations inside the database per locale. To make it work, however, you also need to create a special translation table.
这将使您可以按语言环境在数据库内部存储商店翻译。 为了使其工作,您还需要创建一个特殊的转换表 。
So, if you are creating a new model and a migration, things are as simple as using a create_translation_table!
method as explained here. Our case is a bit more complex, because we already have a products
table with some data. Therefore it is required to move these data to the translation table. Start by generating a new migration:
因此,如果您要创建新模型并进行迁移,那么事情就像使用create_translation_table!
一样简单create_translation_table!
此处解释的方法。 我们的情况要复杂一些,因为我们已经有一个包含一些数据的products
表。 因此,需要将这些数据移到转换表中。 首先生成一个新的迁移:
rails g migration translate_products
Now flesh it out with the following code:
现在用以下代码充实它:
# db/migrate/xyz_translate_products.rb class TranslateProducts < ActiveRecord::Migration[5.2] def change reversible do |dir| # <=== 1 dir.up do Product.create_translation_table!({ # <=== 2 title: :string, # <=== 3 description: :text }, { migrate_data: true, # <=== 4 remove_source_columns: true # <=== 5 }) end dir.down do Product.drop_translation_table! migrate_data: true # <=== 6 end end end end
I’ve pinpointed the main things to note about this code:
我已经确定了有关此代码的主要注意事项:
We are creating a translation table for the Product
.
我们正在为Product
创建一个转换表。
Carefully list all the fields that should be translated as well as their types. As you recall, these fields were passed to the translates
method inside the model.
仔细列出所有应翻译的字段及其类型。 您还记得,这些字段已传递给模型内部的translates
方法。
Don’t forget to provide the migrate_data
option that should preserve your original database records.
别忘了提供应保留原始数据库记录的migrate_data
选项。
remove_source_columns
will ensure that the original columns (:title
and :description
) will be removed from the products
table. You may also perform this step later in a separate migration.
remove_source_columns
将确保从products
表中删除原始列( :title
和:description
)。 您也可以稍后在单独的迁移中执行此步骤。
Run the migration:
运行迁移:
rails db:migrate
After this command finishes its job, you will see a new product_translations
table:
该命令完成工作后,您将看到一个新的product_translations
表:
As you see, there is a product_id
column that establishes relation to the product, and also a locale
field to denote which language this translation is for. When you migrate your original data, it will be associated with the app’s default locale (which is English in our case). Override this behavior by using a with_locale
method, for example:
如您所见,这里有一个product_id
列,该列建立与产品的关系,还有一个locale
字段,表示此翻译的语言。 迁移原始数据时,原始数据将与应用程序的默认语言环境(在我们的示例中为英语)相关联。 通过使用with_locale
方法覆盖此行为,例如:
I18n.with_locale(:ru) do Post.create_translation_table!(...) end
If you need to add more translated fields to the table later, utilize an add_translation_fields!
method as shown in this example. Also, don’t forget to define these new fields in the model.
如果以后需要在表中添加更多翻译字段,请使用add_translation_fields!
方法如本例所示 。 另外,不要忘记在模型中定义这些新字段。
At this point Globalize is integrated into our application and ready to get rolling! Perform the following steps to see it in action:
此时,Globalize已集成到我们的应用程序中,随时可以开始使用! 执行以下步骤以查看实际效果:
As a result you should see two translations being stored inside the product_translations
table:
结果,您应该看到两个翻译存储在product_translations
表中:
Great job!
很好!
What happens if Globalize cannot find translated attributes for the given locale? As we’ve seen in the previous section, by default it will return blank values (which are actually nil
s). However, it is possible to enable I18n fallbacks and display attribute values from another locale. To achieve that, just turn fallbacks on inside the config/application.rb
file:
如果Globalize找不到给定语言环境的翻译属性,会发生什么? 如上一节所述,默认情况下它将返回空白值(实际上为nil
)。 但是,可以启用I18n后备并显示其他语言环境的属性值。 为此,只需在config/application.rb
文件中启用后备:
# ... config.i18n.fallbacks = true
Now when the translated attribute is nil
, Globalize will try to load values from another locale. To make sure this feature is working, reload the server, create a new product, and then switch to another language. The title and description should fallback to another locale.
现在,当转换后的属性为nil
,Globalize将尝试从另一个语言环境加载值。 为确保此功能正常工作,请重新加载服务器,创建新产品,然后切换到另一种语言。 标题和描述应回退到其他语言环境。
If you would like to employ fallbacks when the translation values are also blank (not nil
), set the fallbacks_for_empty_translations
option to true
:
如果您希望在转换值也为空白(而不是nil
)时采用回退,请将fallbacks_for_empty_translations
选项设置为true
:
# models/product.rb # ... translates :title, :description, fallbacks_for_empty_translations: true
Also note that it is possible to define a custom fallback chain globally in the following way:
还请注意,可以通过以下方式全局定义自定义后备链:
# somewhere in an initializer Globalize.fallbacks = {:en => [:de, :ru]}
Globalize provides a special model scope called with_translations
that can be used to load translation for a given language. In this example we are loading all translations for the English locale only:
全球化提供了一个特殊的模型范围,称为with_translations
,可用于加载给定语言的翻译。 在此示例中,我们仅加载英语语言环境的所有翻译:
Product.with_translations('en')
On top of that, it is possible to display translation for a desired locale in your views. To achieve that, use a with_locale
method:
最重要的是,可以在视图中显示所需语言环境的翻译。 为此,请使用with_locale
方法:
<% Globalize.with_locale(:en) do %> <!-- render your stuff here... --> <% end %>
What’s interesting is that Globalize even supports interpolation in the translated attributes. It works in the same way as interpolation in YAML translation files:
有趣的是,Globalize甚至支持在转换的属性中进行插值。 它的工作方式与YAML转换文件中的插值相同:
product.title = "Product for %{someone}" product.title someone: "John" # => "Product for John"
So, the placeholder here is %{someone}
. To provide its value, simply pass a hash to the proper model attribute. Really convenient!
因此,此处的占位符为%{someone}
。 要提供其值,只需将哈希传递给适当的model属性即可。 真的很方便!
In this section we have seen how to store translations inside database with the help of Globalize solution. We have discussed its basics, seen how to install and configure it, how to migrate data properly, how to define translations, provide fallbacks and utilize scopes. All in all, we have covered nearly everything Globalize has to offer, and so you may now apply these concepts into practice! Also don’t forget that Globalize can safely play with YAML files, so you can mix and match these approaches as you see fit.
在本节中,我们已经看到了如何借助Globalize解决方案将翻译存储在数据库中。 我们已经讨论了其基础知识,了解了如何安装和配置它,如何正确迁移数据,如何定义翻译,提供后备功能和利用范围。 总而言之,我们几乎涵盖了Globalize提供的所有内容,因此您现在可以将这些概念付诸实践! 同样不要忘记Globalize可以安全地处理YAML文件,因此您可以根据需要混合和匹配这些方法。
Which solution do you utilize to internationalize user-generated content? Would you give Globalize a go? Share your thoughts in the comments section!
您使用哪种解决方案来国际化用户生成的内容? 您愿意放弃全球化吗? 在评论部分分享您的想法!
Supporting multiple languages on a big website may become a serious pain. You must make sure that all the keys are translated for each and every locale. Luckily, there is a solution to this problem: the Lokalise platform that makes working with the localization files much simpler. Let me guide you through the initial setup which is nothing complex really.
在大型网站上支持多种语言可能会变得很痛苦。 您必须确保为每个语言环境翻译了所有键。 幸运的是,有一个解决此问题的方法:使用Lokalise平台可以更轻松地处理本地化文件 。 让我指导您完成初始设置,这实际上并不复杂。
To get started, grab your free trial
首先, 请免费试用
Lokalise has many more features including support for dozens of platforms and formats, and even the possibility to upload screenshots in order to read texts from them. So, stick with Lokalise and make your life easier!
Lokalise具有更多功能,包括对数十种平台和格式的支持,甚至可以上传屏幕截图以从中读取文本。 因此,坚持使用Lokalise,让您的生活更轻松!
Originally published at blog.lokalise.co on November 16, 2018.
最初于2018年11月16日发布在blog.lokalise.co 。