Rails i18n-翻译内含链接的文本

我想 i18n 一个文本,看起来像这样:

- 已经注册了?-登陆!

请注意,文本上有一个链接。在这个例子中,它指向谷歌-在现实中,它将指向我的应用程序的 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行)将对所有视图重复。不会很干。

我想我可以使用“本地化的部分”,但这似乎太累赘; 我认为我更喜欢第一个选项有这么多微小的视图文件。

因此,我的问题是: 有没有一种“适当”的方式来实现这一点?

54523 次浏览

Why not use the first way, but splitting it up like

log_in_message: Already signed up?
log_in_link_text: Log in!

And then

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

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>

Separating text and link in locale.yml file works for a while but with longer text those are hard to translate and maintain as the link is in separate translation-item (as in Simones answer). If you start having many strings/translations with links you can dry it a bit more.

I made one helper in my application_helper.rb:

# 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

In my en.yml:

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

And in my views:

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

This way it's easier to translate messages as also the link text is clearly defined in the locale.yml-files.

Thank you very much, holli, for sharing this approach. It works like a charm for me. Would vote you up if I could, but this is my first post so I'm lacking the proper reputation ... As an additional piece to the puzzle: The problem I realized with your approach is that it still won't work from inside the controller. I did some research and combined your approach with the one from Glenn on rubypond.

Here is what I came up with:

View helper, e.g. 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 http://stackoverflow.com/questions/2543936/rails-i18n-translating-text-with-links-inside (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

In the controller:

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

In the locale.yml:

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

In the view:

<%= render_flash_messages %>

Hope this post earns me the reputation to vote you up, holli :) Any feedback is welcome.

I took hollis solution and made a gem called it out of it. Let's look at an example:

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

And then

<p><%=t_link "log_in_message", :login => login_path %></p>

For more details, see https://github.com/iGEL/it.

We had the following:

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

or more explicitly:

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

then in ApplicationController.rb just

class ApplicationController < ActionController::Base
helper I18nHelpers

Given a key in the en.yml file like

mykey: "Click %|here|!"

can be used in ERB as

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

should generate

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

I wanted a bit more flexibility than just adding links to flash messages from YAML files (for example the logged in username etc.) so instead I wanted to use ERB notation in the string.

As I am using bootstrap_flash so I modified the helper code as follows to decode the ERB strings before displaying:

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

It is then possible to use strings like the following (using devise):

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

This may not work for all situations and there may be an argument that code and string definitions should not be mixed (especially from a DRY perspective), but this seems to work well for me. The code should be adaptable for many other situations, the important bits being the following:

require 'erb'


....


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

In en.yml

registration:
terms:
text: "I do agree with the terms and conditions: %{gtc} / %{stc}"
gtc: "GTC"
stc: "STC"

In de.yml

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

in new.html.erb [assumed]

<%= 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 %>

I think a simple way to do this is by simply doing :

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