web-dev-qa-db-fra.com

rails i18n - traduction de texte avec des liens à l'intérieur

Je voudrais i18n un texte qui ressemble à ceci:

Déjà inscrit? Connectez-vous!

Notez qu'il y a un lien sur le texte. Sur cet exemple, il pointe vers google - en réalité, il pointera vers log_in_path.

J'ai trouvé deux façons de procéder, mais aucune ne semble "correcte".

La première façon que je connais consiste à avoir ceci mon en.yml:

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

Et à mon avis:

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

Cela fonctionne , mais ayant le <a href=...</a> partie sur le en.yml ne me semble pas très propre.

L'autre option que je connais utilise vues localisées - login.en.html.erb, et login.es.html.erb.

Cela ne semble pas non plus juste, car la seule ligne différente serait celle susmentionnée; le reste de la vue (~ 30 lignes) serait répété pour toutes les vues. Ce ne serait pas très SEC.

Je suppose que je pourrais utiliser des "partiels localisés" mais cela semble trop lourd; Je pense que je préfère la première option à avoir autant de petits fichiers d'affichage.

Ma question est donc la suivante: existe-t-il une manière "appropriée" de mettre cela en œuvre?

92
kikito

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>
171
Simone Carletti

La séparation du texte et du lien dans le fichier locale.yml fonctionne pendant un certain temps, mais avec du texte plus long, il est difficile de les traduire et de les conserver car le lien se trouve dans un élément de traduction distinct (comme dans la réponse de Simones). Si vous commencez à avoir de nombreuses chaînes/traductions avec des liens, vous pouvez les sécher un peu plus.

J'ai créé un assistant dans mon 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

Dans mon en.yml:

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

Et selon moi:

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

De cette façon, il est plus facile de traduire les messages car le texte du lien est également clairement défini dans les fichiers locale.yml.

11
holli

J'ai pris la solution de Hollis et en ai fait n bijou appelé it . Regardons un exemple:

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

Et alors

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

Pour plus de détails, voir https://github.com/iGEL/it .

7
iGEL

Dans en.yml

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

Dans de.yml

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

dans new.html.erb [supposé]

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

Merci beaucoup, holli, d'avoir partagé cette approche. Cela fonctionne comme un charme pour moi. Je voterais pour vous si je le pouvais, mais c'est mon premier post, donc je n'ai pas la bonne réputation ... Comme pièce supplémentaire au puzzle: Le problème que j'ai réalisé avec votre approche est qu'elle ne fonctionnera toujours pas de l'intérieur le controlle. J'ai fait quelques recherches et combiné votre approche avec celle de Glenn sur rubypond .

Voici ce que j'ai trouvé:

Voir l'aide, par ex. 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

Dans le contrôleur:

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

Dans le locale.yml:

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

Dans la vue:

<%= render_flash_messages %>

J'espère que ce message me vaut la réputation de vous voter, holli :) Tout commentaire est le bienvenu.

3
emrass

Nous avions ce qui suit:

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 plus explicitement:

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

puis dans ApplicationController.rb juste

class ApplicationController < ActionController::Base
  helper I18nHelpers

Étant donné une clé dans le en.yml fichier comme

mykey: "Click %|here|!"

peut être utilisé dans ERB comme

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

devrait générer

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

Je voulais un peu plus de flexibilité que d'ajouter simplement des liens vers des messages flash à partir de fichiers YAML (par exemple, le nom d'utilisateur connecté, etc.), donc je voulais plutôt utiliser la notation ERB dans la chaîne.

Comme j'utilise bootstrap_flash j'ai donc modifié le code d'aide comme suit pour décoder les chaînes ERB avant d'afficher:

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

Il est alors possible d'utiliser des chaînes comme celles-ci (en utilisant devise):

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

Cela peut ne pas fonctionner dans toutes les situations et il peut y avoir un argument selon lequel les définitions de code et de chaîne ne doivent pas être mélangées (en particulier du point de vue DRY), mais cela semble bien fonctionner pour moi. Le code devrait être adaptable pour de nombreuses autres situations, les bits importants étant les suivants:

require 'erb'

....

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