rails i18n - traduzindo texto com links dentro

101

Eu gostaria de i18n um texto parecido com este:

Já se inscreveu? Conecte-se!

Observe que há um link no texto. Neste exemplo, ele aponta para o google - na realidade, ele aponta para o meu aplicativo log_in_path.

Encontrei duas maneiras de fazer isso, mas nenhuma delas parece "certa".

A primeira maneira que conheço envolve ter este meu en.yml:

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

E na minha opinião:

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

Isso funciona , mas ter o <a href=...</a>papel en.ymlnão me parece muito claro.

A outra opção que conheço é usar visualizações localizadas - login.en.html.erb, e login.es.html.erb.

Isso também não parece certo, pois a única linha diferente seria a mencionada anteriormente; o resto da visualização (aproximadamente 30 linhas) seria repetido para todas as visualizações. Não seria muito SECO.

Eu acho que poderia usar "parciais localizadas", mas isso parece muito complicado; Acho que prefiro a primeira opção a ter tantos arquivos de visualização minúsculos.

Portanto, minha pergunta é: existe uma maneira "adequada" de implementar isso?

kikito
fonte
Que tal isso? stackoverflow.com/questions/12334183/…
Mark Boulder
@Wuggy Foofie Você não deveria ter duplicado a pergunta. E a resposta de Simone é melhor do que as que você recebeu.
kikito

Respostas:

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
fonte
66
No Rails 3, a sintaxe para isso mudou para %{href}na string de tradução YAML. Além disso, como a saída é escapada automaticamente, você precisa especificar rawou .html_safeexplicitamente, ou sufocar sua chave de tradução com _html, como em login_message_htmle o escape será ignorado automaticamente.
coreyward
15
apenas no caso de não ser óbvio (e para aqueles com preguiça de verificar o log de edição) .. a resposta acima já foi editada para incluir o comentário de @coreyward.
abbood
2
Se você tiver algo mais do que uma única palavra no texto do link, dividir as traduções como esta resultará em traduções estranhas. Por exemplo, "Temos uma incrível <a href='x'> oferta de coisas variadas </a> que você pode comprar. Você envia a coisa picada para um tradutor e é provável que receba duas frases como" Nós tem um incrível <a href='x'> monte de itens </a> que você pode comprar "em outros idiomas. Melhor encontrar uma solução que não os separe.
trcarden
3
@Archonic Isso não é verdade. t('string')é idêntico a t("string"). Eles são a mesma coisa.
meagar
3
Tenho que amar trilhos complicando o f out dos links. deve ser assimt('some.key', link: link_to()).html_safe
Eddie
11

Separar texto e link no arquivo locale.yml funciona por um tempo, mas com textos mais longos, eles são difíceis de traduzir e manter, pois o link está em um item de tradução separado (como na resposta de Simones). Se você começar a ter muitas strings / traduções com links, você pode secar um pouco mais.

Fiz um auxiliar em meu 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

Em meu en.yml:

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

E na minha opinião:

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

Desta forma, é mais fácil traduzir as mensagens, pois também o texto do link está claramente definido nos arquivos locale.yml.

Holli
fonte
6
Ótima solução. Coloquei isso em um Gem, que permite definir o link de coisas This is a %{link:link to Google}. Ele permite que você tenha vários links em uma única string, cuida do XSS e permite traduções aninhadas. Dê uma olhada em github.com/iGEL/i18n_link
iGEL
eu fiz isso com "str = t str", então apenas forneço a chave de tradução na função. mais confortável!
Tim Kretschmer
1
Eu votaria mais positivamente no @iGEL se pudesse. O projeto foi movido para github.com/iGEL/it e se você quiser usá-lo em um controlador para uma flashmensagem no Rails 3+ faça assimview_context.it(key, ...)
Chris Beck
Aqui está um exemplo melhor para usá-lo em um controlador - github.com/iGEL/it/issues/10
Chris Beck
8

Peguei a solução de hollis e fiz uma joiait sair dela. Vejamos um exemplo:

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

E depois

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

Para obter mais detalhes, consulte https://github.com/iGEL/it .

iGEL
fonte
5

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

em new.html.erb [assumido]

<%= 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 %>
Emu
fonte
3

Muito obrigado, holli, por compartilhar essa abordagem. Funciona como um encanto para mim. Você votaria se pudesse, mas esta é minha primeira postagem, então não tenho a reputação adequada ... Como uma peça adicional para o quebra-cabeça: O problema que percebi com sua abordagem é que ainda não funciona por dentro o controlador. Eu fiz algumas pesquisas e combinei sua abordagem com a de Glenn em Rubypond .

Aqui está o que eu descobri:

Ver auxiliar, por exemplo, 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

No controlador:

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

No locale.yml:

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

Na vista:

<%= render_flash_messages %>

Espero que este post me garanta a reputação de votar em você, holli :) Qualquer comentário é bem-vindo.

Emrass
fonte
2

Tínhamos o seguinte:

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

ou mais explicitamente:

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

então em ApplicationController.rb apenas

class ApplicationController < ActionController::Base
  helper I18nHelpers

Dada uma chave no en.ymlarquivo como

mykey: "Click %|here|!"

pode ser usado em ERB como

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

deve gerar

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

Eu queria um pouco mais de flexibilidade do que apenas adicionar links a mensagens flash de arquivos YAML (por exemplo, o nome de usuário conectado, etc.), então, em vez disso, queria usar a notação ERB na string.

Como estou usando bootstrap_flash, modifiquei o código auxiliar da seguinte maneira para decodificar as strings ERB antes de exibir:

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

Então, é possível usar strings como o seguinte (usando devise):

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

Isso pode não funcionar para todas as situações e pode haver um argumento de que as definições de código e string não devem ser misturadas (especialmente de uma perspectiva DRY), mas isso parece funcionar bem para mim. O código deve ser adaptável a muitas outras situações, sendo os bits importantes os seguintes:

require 'erb'

....

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

Acho que uma maneira simples de fazer isso é simplesmente fazer:

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

Por que não usar a primeira maneira, mas dividindo-a como

log_in_message: Already signed up?
log_in_link_text: Log in!

E depois

<p> <%= t('log_in_message') %> <%= link_to t('log_in_link_text'), login_path %> </p>
alex.zherdev
fonte
Desculpe, esta solução não funcionará. Lembre-se de que eu queria traduzir o texto para outros idiomas. Isso significa que em algumas ocasiões o "link" pode estar no início ou no meio do texto. Sua solução força o link a estar no final (não traduz bem).
kikito