rails i18n: traducción de texto con enlaces dentro

101

Me gustaría i18n un texto que se parece a esto:

¿Ya estás registrado? ¡Iniciar sesión!

Tenga en cuenta que hay un enlace en el texto. En este ejemplo, apunta a Google; en realidad, apuntará al de mi aplicación log_in_path.

Encontré dos formas de hacer esto, pero ninguna parece "correcta".

La primera forma que conozco implica tener este mi en.yml:

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

Y en mi opinión:

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

Esto funciona , pero tener la <a href=...</a>pieza puesta en.ymlno me parece muy limpio.

La otra opción que conozco es usar vistas localizadas - login.en.html.erby login.es.html.erb.

Esto tampoco se siente bien ya que la única línea diferente sería la mencionada anteriormente; el resto de la vista (~ 30 líneas) se repetirá para todas las vistas. No estaría muy SECO.

Supongo que podría usar "parciales localizados", pero eso parece demasiado complicado; Creo que prefiero la primera opción a tener tantos archivos de visualización pequeños.

Entonces mi pregunta es: ¿hay una forma "adecuada" de implementar esto?

kikito
fuente
¿Qué pasa con esto? stackoverflow.com/questions/12334183/…
Mark Boulder
@Wuggy Foofie No debería haber duplicado la pregunta. Y la respuesta de Simone es mejor que las que tienes.
kikito

Respuestas:

178

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>
Simone Carletti
fuente
66
En Rails 3, la sintaxis para esto ha cambiado a %{href}en la cadena de traducción YAML. Además, debido a que la salida se escapa automáticamente, debe especificar rawo .html_safeexplícitamente, o agregar un sufijo a su clave de traducción _html, ya que en login_message_htmly el escape se omitirá automáticamente.
coreyward
15
por si acaso no es obvio (y para aquellos que son demasiado perezosos para revisar el registro de edición) ... la respuesta anterior ya fue editada para incluir el comentario de @ coreyward.
Abbood
2
Si tiene algo más de una palabra en el texto del enlace, dividir traducciones como esta producirá traducciones extrañas. Por ejemplo, "Tenemos una <a href='x'> increíble oferta de artículos variados </a> que puedes comprar. Envías el artículo cortado a un traductor y es probable que obtengas dos frases que digan" Nosotros Tienen un <a href='x'> increíble grupo de artículos </a> que puede comprar "en otros idiomas. Lo mejor es encontrar una solución que no los divida.
trcarden
3
@Archonic Eso no es cierto. t('string')es idéntico a t("string"). Son lo mismo.
meagar
3
Me encantan los rieles que complican los enlaces. debería verse asít('some.key', link: link_to()).html_safe
Eddie
11

La separación de texto y enlace en el archivo locale.yml funciona por un tiempo, pero con texto más largo es difícil traducirlos y mantenerlos, ya que el enlace está en un elemento de traducción separado (como en la respuesta de Simones). Si comienza a tener muchas cadenas / traducciones con enlaces, puede secarlo un poco más.

Hice un ayudante en mi 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 mi en.yml:

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

Y en mi opinión:

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

De esta manera es más fácil traducir mensajes ya que también el texto del enlace está claramente definido en los archivos locale.yml.

holli
fuente
6
Gran solucion Pongo esto en una Gema, que te permite definir el enlace de cosas This is a %{link:link to Google}. Le permite tener varios enlaces en una sola cadena, se encarga de XSS y permite traducciones anidadas. Eche un vistazo a github.com/iGEL/i18n_link
iGEL
Lo hice con "str = t str", así que solo doy la clave de traducción en la función. ¡más cómodo!
Tim Kretschmer
1
Votaría más a @iGEL si pudiera. El proyecto se ha movido a github.com/iGEL/it y si desea usarlo en un controlador para un flashmensaje en Rails 3+, hágalo asíview_context.it(key, ...)
Chris Beck
Aquí hay un mejor ejemplo para usarlo en un controlador: github.com/iGEL/it/issues/10
Chris Beck
8

Tomé la solución de Hollis e hice una gema llamadait con ella. Veamos un ejemplo:

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

Y entonces

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

Para obtener más detalles, consulte https://github.com/iGEL/it .

iGEL
fuente
5

En en.yml

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

En de.yml

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

en new.html.erb [asumido]

<%= 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 %>
Emú
fuente
3

Muchas gracias, holli, por compartir este enfoque. Funciona de maravilla para mí. Le votaría a favor si pudiera, pero esta es mi primera publicación, así que me falta la reputación adecuada ... Como pieza adicional al rompecabezas: el problema del que me di cuenta con su enfoque es que todavía no funciona desde adentro. el controlador. Investigué un poco y combiné tu enfoque con el de Glenn sobre rubypond .

Esto es lo que se me ocurrió:

Ver ayuda, por ejemplo, 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

En el controlador:

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

En locale.yml:

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

En la vista:

<%= render_flash_messages %>

Espero que esta publicación me gane la reputación de votarte, holli :) Cualquier comentario es bienvenido.

Emrass
fuente
2

Tuvimos lo siguiente:

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

o más explícitamente:

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

luego en ApplicationController.rb solo

class ApplicationController < ActionController::Base
  helper I18nHelpers

Dada una clave en el en.ymlarchivo como

mykey: "Click %|here|!"

se puede utilizar en ERB como

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

debería generar

Click <a href="http://foo.com">here</a>!
Jaime Cham
fuente
1

Quería un poco más de flexibilidad que simplemente agregar enlaces a mensajes flash desde archivos YAML (por ejemplo, el nombre de usuario registrado, etc.), así que en su lugar quería usar la notación ERB en la cadena.

Mientras lo estoy usando bootstrap_flash, modifiqué el código auxiliar de la siguiente manera para decodificar las cadenas ERB antes de mostrarlas:

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

Entonces es posible usar cadenas como las siguientes (usando devise):

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

Es posible que esto no funcione para todas las situaciones y puede haber un argumento de que el código y las definiciones de cadena no deben mezclarse (especialmente desde una perspectiva DRY), pero esto parece funcionar bien para mí. El código debe poder adaptarse a muchas otras situaciones, siendo los bits importantes los siguientes:

require 'erb'

....

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

Creo que una forma sencilla de hacer esto es simplemente haciendo:

<%= link_to some_path do %>
<%= t '.some_locale_key' %>
<% end %>
Sankalp Singha
fuente
-4

¿Por qué no usar la primera forma, pero dividiéndolo como

log_in_message: Already signed up?
log_in_link_text: Log in!

Y entonces

<p> <%= t('log_in_message') %> <%= link_to t('log_in_link_text'), login_path %> </p>
alex.zherdev
fuente
Lo siento, esta solución no funcionará. Tenga en cuenta que quería traducir el texto a otros idiomas. Esto significa que, en algunas ocasiones, el "enlace" podría estar al principio o en medio del texto. Tu solución obliga al enlace a estar al final (no se traduce bien).
kikito