rails i18n - перевод текста со ссылками внутри

101

Я бы хотел написать такой текст:

Уже зарегистрировались? Авторизоваться!

Обратите внимание, что в тексте есть ссылка. В этом примере он указывает на Google - на самом деле он указывает на мое приложение 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>

Это работает , но мне кажется , что эта <a href=...</a>деталь en.ymlне очень чиста.

Другой известный мне вариант - это использование локализованных представлений - login.en.html.erbи login.es.html.erb.

Это также не кажется правильным, поскольку единственной другой строкой будет вышеупомянутая; остальная часть представления (~ 30 строк) будет повторяться для всех представлений. Было бы не очень СУХОЕ.

Думаю, я мог бы использовать «локализованные частичные файлы», но это кажется слишком громоздким; Я думаю, что предпочитаю первый вариант иметь так много маленьких файлов просмотра.

Итак, мой вопрос: есть ли «правильный» способ реализовать это?

кикито
источник
Как насчет этого? stackoverflow.com/questions/12334183/…
Марк Боулдер
@Wuggy Foofie Не надо было дублировать вопрос. И ответ Симоны лучше, чем у тебя.
kikito

Ответы:

179

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>
Симоне Карлетти
источник
66
В Rails 3 синтаксис для этого был изменен на %{href}строку перевода YAML. Кроме того, поскольку вывод автоматически экранируется, вам нужно либо указать, rawлибо .html_safeявно, либо суффиксировать ключ перевода с помощью _html, так как in login_message_htmlи экранирование будет пропущено автоматически.
Coreyward 09
15
на всякий случай, если это не очевидно (и для тех, кому лень проверять журнал редактирования) .. ответ выше уже был отредактирован, чтобы включить комментарий @ coreyward.
abbood
2
Если у вас есть что-то большее, чем одно слово в тексте ссылки, такие переводы приведут к странным переводам. Например, «У нас есть удивительное <a href='x'> предложение разных вещей </a>, которое вы можете купить. Вы отправляете нарезанный предмет переводчику, и вы, вероятно, получите две фразы, которые читаются как« Мы у вас есть потрясающая <a href='x'> куча товаров </a>, которые вы можете купить "на других языках. Лучше всего найти решение, которое не разделяет их на части.
trcarden
3
@Archonic Это неправда. t('string')идентично t("string"). Это одно и то же.
meagar
3
Полюбил рельсы, усложняющие поиск ссылок. должен выглядеть такt('some.key', link: link_to()).html_safe
Эдди
11

Разделение текста и ссылки в файле locale.yml работает некоторое время, но с более длинным текстом их трудно переводить и поддерживать, поскольку ссылка находится в отдельном элементе перевода (как в ответе Симонеса). Если у вас появляется много строк / переводов со ссылками, вы можете еще немного подсушить.

Я сделал один помощник в своем 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

В моем 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, который позволяет вам определять ссылки на вещи This is a %{link:link to Google}. Он позволяет вам иметь несколько ссылок в одной строке, заботится о XSS и разрешает вложенные переводы. Посмотрите на github.com/iGEL/i18n_link
iGEL
Я сделал это с помощью "str = t str", поэтому я просто даю ключ перевода в функции. более удобный!
Тим Кречмер
1
Если бы я мог, я бы проголосовал больше за @iGEL. Проект перемещен на github.com/iGEL/it, и если вы хотите использовать его в контроллере для flashсообщений в Rails 3+, сделайте это так,view_context.it(key, ...)
Крис Бек,
Вот лучший пример использования его в контроллере - github.com/iGEL/it/issues/10
Крис Бек
5

В 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"

в 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

Большое спасибо, Холли, за то, что поделились этим подходом. На меня это действует как оберег. Я бы проголосовал за вас, если бы мог, но это мой первый пост, поэтому мне не хватает должной репутации ... В качестве дополнительной части головоломки: проблема, которую я понял с вашим подходом, заключается в том, что он все еще не работает изнутри контроллер. Я провел небольшое исследование и совместил ваш подход с подходом Гленна на Рубинском пруду .

Вот что я придумал:

Просмотр помощника, например 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

В контроллере:

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

Надеюсь, этот пост заработает мне репутацию, чтобы проголосовать за вас, привет :) Любые отзывы приветствуются.

Emrass
источник
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 (например, имя пользователя, вошедшее в систему и т. Д.), Поэтому вместо этого я хотел использовать нотацию 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."

Это может работать не во всех ситуациях, и может быть аргумент, что определения кода и строк не следует смешивать (особенно с точки зрения СУХОЙ), но мне кажется, что это хорошо работает. Код должен быть адаптирован для многих других ситуаций, при этом важны следующие моменты:

require 'erb'

....

        begin
          msg = ERB.new(msg).result(binding) if msg
        rescue Exception=>e
          puts e.message
          puts e.backtrace
        end
Зеланикс
источник
-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