Rails I18n - 翻译带有链接的文本

114

我想国际化一个看起来像这样的文本:

已经注册?登录!

请注意,该文本中有一个链接。在此示例中,它指向谷歌 - 实际上,它将指向我的应用程序的 log_in_path

我找到了两种方法来做到这一点,但是它们都不太“正确”。

我知道的第一种方法涉及让我的 en.yml 文件:

log_in_message: "Already signed up? <a href='{{url}}'>Log in!</a>"

在我看来:

<p> <%= t('log_in_message', :url => login_path) %> </p>

这个方法是可行的,但是在en.yml中包含<a href=...</a>部分看起来不太干净。
我知道的另一种选择是使用本地化视图 - login.en.html.erblogin.es.html.erb
这也不太合适,因为唯一不同的行是上述行;其余的视图(~30行)将被重复用于所有视图。这样做就不够DRY。
我想我可以使用“本地化局部”,但那似乎太笨重了;我认为我更喜欢第一种选项,而不是有很多微小的视图文件。
所以我的问题是:有没有“正确”的方法来实现这个?

这个怎么样? https://dev59.com/02jWa4cB1Zd3GeqPojkY - Mark Boulder
@Wuggy Foofie,你不应该重复提问。而Simone的回答比你得到的更好。 - kikito
10个回答

199

en.yml

log_in_message_html: "This is a text, with a %{href} inside."
log_in_href: "link"

login.html.erb

<p> <%= t("log_in_message_html", href: link_to(t("log_in_href"), login_path)) %> </p>

71
在Rails 3中,这个语法已经改为使用 %{href} 在YAML翻译字符串中。由于输出自动转义,您需要显式地指定 raw.html_safe,或者在您的翻译键后缀中加上 _html,例如 login_message_html,这样转义将被自动跳过。 - coreyward
20
以防不明显(或者懒得查看编辑日志的人),上面的答案已经被编辑以包含@coreyward的评论。 - abbood
4
如果链接文本中有一个以上的单词,分割翻译将会产生奇怪的结果。例如,“我们有各种各样的东西供您购买。<a href='x'>点击这里</a>”如果您把“各种各样的东西”进行分割翻译,可能会得到两个短语,类似于“我们有一个惊人的<a href='x'>全部物品</a>供您购买”的其他语言翻译。最好找到一种不需要分割的解决方案。 - trcarden
3
我不确定为什么这有这么多的投票。这个问题(或者说,像这样的问题)的整个意图在于将单个的英文句子或表达作为i18n数据中的一个单位,以便可以将其作为单个实体进行翻译。@trcarden的评论暗示了这个问题。 - Pistos
4
一定要喜欢Rails把链接搞得复杂化。应该像这样写:t('some.key', link: link_to()).html_safe - Eddie
显示剩余6条评论

11
在locale.yml文件中区分文本和链接可以一定程度上起到作用,但是对于长度较长的文本来说,这样做会变得难以翻译和维护,因为链接是在单独的translation-item中(如Simones答案中所示)。如果您开始有许多带有链接的字符串/翻译,您可以再次简化它。
我在我的application_helper.rb中创建了一个helper。
# Converts
# "string with __link__ in the middle." to
# "string with #{link_to('link', link_url, link_options)} in the middle."
def string_with_link(str, link_url, link_options = {})
  match = str.match(/__([^_]{2,30})__/)
  if !match.blank?
    raw($` + link_to($1, link_url, link_options) + $')
  else
    raise "string_with_link: No place for __link__ given in #{str}" if Rails.env.test?
    nil
  end
end

在我的en.yml文件中:

log_in_message: "Already signed up? __Log in!__"

在我看来:

<p><%= string_with_link(t('.log_in_message'), login_path) %></p>

这样做可以更轻松地翻译消息,因为链接文本在locale.yml文件中明确定义。


6
好的解决方案。我将其放入一个 Gem 中,使你可以定义像 这是一个%{link:到谷歌的链接} 这样的内容。它允许你在单个字符串中有多个链接,处理 XSS 并允许嵌套翻译。请查看 https://github.com/iGEL/i18n_link/。 - iGEL
我用“str = t str”完成了它,所以我只需在函数中提供翻译键。更加舒适! - Tim Kretschmer
1
如果可以的话,我会给@iGEL更多赞。该项目已经移至https://github.com/iGEL/it,如果你想在Rails 3+控制器中使用它作为“flash”消息,请像这样使用view_context.it(key, ...) - Chris Beck
这里有一个更好的示例,可以在控制器中使用--https://github.com/iGEL/it/issues/10 - Chris Beck

9

我采用了Hollis的解决方案,并根据此制作了一个名为it的宝石。让我们看一个例子:

log_in_message: "Already signed up? %{login:Log in!}"

并且,接下来
<p><%=t_link "log_in_message", :login => login_path %></p>

更多细节请参见https://github.com/iGEL/it


5
en.yml 文件中。
registration:
    terms:
      text: "I do agree with the terms and conditions: %{gtc} / %{stc}"
      gtc: "GTC"
      stc: "STC"

de.yml文件中

registration:
    terms:
      text: "Ich stimme den Geschäfts- und Nutzungsbedingungen zu: %{gtc} / %{stc}"
      gtc: "AGB"
      stc: "ANB"

new.html.erb 中 [假设]

<%= t(
   'registration.terms.text',
    gtc:  link_to(t('registration.terms.gtc'),  terms_and_conditions_home_index_url + "?tab=gtc"),
    stc: link_to(t('registration.terms.stc'), terms_and_conditions_home_index_url + "?tab=stc")
 ).html_safe %>

3
非常感谢您分享这种方法,holli。对我来说,它非常有效。如果可以的话,我会给您投票的,但这是我的第一篇帖子,所以我缺乏适当的声望...作为拼图的另一个部分:我意识到您的方法存在问题,即它仍然无法从控制器内部工作。我进行了一些研究,并将您的方法与Glenn on rubypond的方法结合起来。

以下是我想出的:

视图助手,例如application_helper.rb

  def render_flash_messages
    messages = flash.collect do |key, value|
      content_tag(:div, flash_message_with_link(key, value), :class => "flash #{key}") unless key.to_s =~ /_link$/i
    end
    messages.join.html_safe
  end

  def flash_message_with_link(key, value)
    link = flash["#{key}_link".to_sym]
    link.nil? ? value : string_with_link(value, link).html_safe
  end

  # Converts
  # "string with __link__ in the middle." to
  # "string with #{link_to('link', link_url, link_options)} in the middle."
  # --> see https://dev59.com/hHE85IYBdhLWcg3w64IA (holli)
  def string_with_link(str, link_url, link_options = {})
    match = str.match(/__([^_]{2,30})__/)
    if !match.blank?
      $` + link_to($1, link_url, link_options) + $'
    else
      raise "string_with_link: No place for __link__ given in #{str}" if Rails.env.test?
      nil
    end
  end

在控制器中:
flash.now[:alert] = t("path.to.translation")
flash.now[:alert_link] = here_comes_the_link_path # or _url

在 locale.yml 文件中:

path:
  to:
    translation: "string with __link__ in the middle"

在视图中:
<%= render_flash_messages %>

希望这篇文章能为我赢得投票的声誉,holli :) 欢迎任何反馈。

2
我们有以下内容:
module I18nHelpers
  def translate key, options={}, &block
    s = super key, options  # Default translation
    if block_given?
      String.new(ERB::Util.html_escape(s)).gsub(/%\|([^\|]*)\|/){
        capture($1, &block)  # Pass in what's between the markers
      }.html_safe
    else
      s
    end
  end
  alias :t :translate
end

更明确地说:

module I18nHelpers

  # Allows an I18n to include the special %|something| marker.
  # "something" will then be passed in to the given block, which
  # can generate whatever HTML is needed.
  #
  # Normal and _html keys are supported.
  #
  # Multiples are ok
  #
  #     mykey:  "Click %|here| and %|there|"
  #
  # Nesting should work too.
  #
  def translate key, options={}, &block

    s = super key, options  # Default translation

    if block_given?

      # Escape if not already raw HTML (html_escape won't escape if already html_safe)
      s = ERB::Util.html_escape(s)

      # ActiveSupport::SafeBuffer#gsub broken, so convert to String.
      # See https://github.com/rails/rails/issues/1555
      s = String.new(s)

      # Find the %|| pattern to substitute, then replace it with the block capture
      s = s.gsub /%\|([^\|]*)\|/ do
        capture($1, &block)  # Pass in what's between the markers
      end

      # Mark as html_safe going out
      s = s.html_safe
    end

    s
  end
  alias :t :translate


end

然后在ApplicationController.rb中就这样

class ApplicationController < ActionController::Base
  helper I18nHelpers

假设在en.yml文件中有一个键,如下所示:

mykey: "Click %|here|!"

可以在ERB中使用

<%= t '.mykey' do |text| %>
  <%= link_to text, 'http://foo.com' %>
<% end %>

应该生成

Click <a href="http://foo.com">here</a>!

1
我希望比在YAML文件中添加链接到Flash消息(例如已登录的用户名等)更加灵活,因此我想在字符串中使用ERB标记。 我正在使用bootstrap_flash,因此我修改了辅助代码如下以解码ERB字符串在显示之前:
require 'erb'

module BootstrapFlashHelper
  ALERT_TYPES = [:error, :info, :success, :warning] unless const_defined?(:ALERT_TYPES)

  def bootstrap_flash
    flash_messages = []
    flash.each do |type, message|
      # Skip empty messages, e.g. for devise messages set to nothing in a locale file.
      next if message.blank?

      type = type.to_sym
      type = :success if type == :notice
      type = :error   if type == :alert
      next unless ALERT_TYPES.include?(type)

      Array(message).each do |msg|
        begin
          msg = ERB.new(msg).result(binding) if msg
        rescue Exception=>e
          puts e.message
          puts e.backtrace
        end
        text = content_tag(:div,
                           content_tag(:button, raw("&times;"), :class => "close", "data-dismiss" => "alert") +
                           msg.html_safe, :class => "alert fade in alert-#{type}")
        flash_messages << text if msg
      end
    end
    flash_messages.join("\n").html_safe
  end
end

接下来可以使用类似以下的字符串(使用devise):

signed_in: "Welcome back <%= current_user.first_name %>! <%= link_to \"Click here\", account_path %> for your account."

这种方法并不适用于所有情况,也有人认为代码和字符串定义不应混合(特别是从DRY的角度),但对我来说似乎很有效。该代码应适用于许多其他情况,其中关键部分如下:
require 'erb'

....

        begin
          msg = ERB.new(msg).result(binding) if msg
        rescue Exception=>e
          puts e.message
          puts e.backtrace
        end

-1
如果你的链接是固定的,你可以使用类似这样的方式:
description: "Text <a href=https://example.com target=_blank >Link description</a>.

然后在你的视图中使用html_safe:
t(".description").html_safe

这不是"Rails"的方式。这个问题的最佳答案提出了一个更好的解决方案。 - ShadowCrafter_01

-2
我认为实现这个的简单方法就是直接这样做:

<%= link_to some_path do %>
<%= t '.some_locale_key' %>
<% end %>

-4
为什么不使用第一种方式,而是像这样分割它呢?
log_in_message: Already signed up?
log_in_link_text: Log in!

然后

<p> <%= t('log_in_message') %> <%= link_to t('log_in_link_text'), login_path %> </p>

抱歉,这个解决方案行不通。请记住,我想要将文本翻译成其他语言。这意味着在某些情况下,“链接”可能在文本开头或中间。您的解决方案强制将链接放在末尾(翻译效果不佳)。 - kikito

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接