Intercept and handle missing translations in helper - ruby-on-rails

I have a helper function that generates useful HTML markup. My functions are usually called with some parameters, including some text translated with I18n my_helper( t(:translation_symbol) ).
I use I18n t translation helper for that, which (if left untouched) outputs some <span class="translation missing">...</span> for texts without translation
I was wondering how to best "intercept" this translation missing so
I can render them appropriately in my helpers
Normal behavior outside my helpers
In my views I should be able to call both
<%= my_helper(t(:my_text)) %> # My helper needs to handle missing translations
<%= t(:my_text) %> # I want default I18n markup (or a custom one) '<span class="translation missing">fr.my_text</span>
My helper
def my_helper(text=nil)
# Suppose my_text doesn't have a translation, text will have value
# => '<span class="translation-missing">fr.my_text</span>'
if translation_missing?(text)
# Code to handle missing translation
doSomething(remove_translation_missing_markup(text))
notify_translation_missing
else
doSomething(text)
end
end
def translation_missing?(text)
# Uses some regex pattern to detect '<span class="translation-missing">'
end
def remove_translation_missing_markup(text)
# Uses some regex pattern to extract my_text from '<span class="translation-missing">fr.my_text</span>'
end
Is there a better way around this ? I feel bad about using a dirty regex solution.
EDIT : extra requirements
No additional markup on views : I don't want to look at my files individually to add raise: true or default: xxx for every translation. If there is a need to change the behavior everywhere I can override the t method.
In my helpers, I need a convenient way to manipulate, just the translated text if the translation was found, but for missing translations, I need to easily be able to extract the full path of the translation (fr.namespace.translation_symbol), and the original translation symbol (translation_symbol), so I can add myself translation_missing in my custom markup.
EDIT2 : I am thinking of something like that
Override t helper to rescue I18n::MissingTranslationData => e
If the exception is raised, create and return a custom object that
If rendered in a html.erb, will output the usual <span class="translation..."
Has useful fields (translation path, translation_string) that I can reuse in my helpers

I think you can leverage the fact that I18n can be configured to raise an exception when translation missing. You can either set ActionView::Base.raise_on_missing_translations = true to globally raise the exception upon missing translations or you can pass :raise => true option to the t translation helper.
Update: since you are already using t helpers in your views and you don't want to change them, I think the only option for you is overriding the t helper in your ApplicationHelper:
# ApplicationHelper
def t(key, options = {})
# try to translate as usual
I18n.t(key, options.merge(raise: true)
rescue I18n::MissingTranslationData => e
# do whatever you want on translation missing, e.g. send notification to someone
# ...
"Oh-oh!"
end
This helper either returns the translated text or "oh-oh" if translation missing. And it should work as-is in your current views.

Since missing translations seems to be a pain point in your app, rather than have your app code be constantly on the lookout for whether a translation exists or not, if possible I would advocate ensuring that you always have translations for every potential call to t, regardless of locale (this is all on the assumption that the :translation_symbol keys in your calls to my_helper(t(:translation_symbol)) are all static, kept in your config/locales directory, and aren't generated dynamically).
You can do this using the I18n-tasks gem to:
Ensure your app does not have missing or unused keys, so you should be able to delete the missing translation handling parts of your helper methods
Make your test suite/build process fail if there are any missing or unused translations, so no one else that touches the codebase accidentally sneaks any through. See instructions on copying over their RSpec test.

Why not set the default option to the translate method as follows:
<%= t(:my_text, default: "not here") %>
See http://guides.rubyonrails.org/i18n.html#defaults for details about setting defaults for the I18n.translate method.

Related

Set a prefix path for I18n keys within non-standard Rails classes?

A Rails project of mine uses rails-i18n for localization purposes. From within views (ActionView) and controllers (ActionController) one can call the (I18n.)t method to get values from the appropriate locale YAML.
If you start the localization-key with a period, like t(".title"), Rails will add a prefix path for the file you're currently in, using a feature called lazy lookup. So the ".title"-key get's a prefix from within your users/show.html.erb file to become "users.show.title".
Works like a charm, but now I have a couple of classes that are neither views, nor controllers, and I want to use the t-method from there as well. Calling I18n.t works fine, but because my custom class doesn't inherit from any Rails classes, it doesn't get a prefix. I can work around this quite easily, but all my workarounds look ugly, and I have the feeling there's a method in one of the super classes that's used to determine the prefix — but I can't find it in the documentation.
Is there a (class-)method one can override, that I18n.t uses for the lazy lookup?
I guess if it's good enough for ActionPack's AbstractController::Translation, it's good enough for me to copy in my custom class scenario:
def translate(*args)
key = args.first
if key.is_a?(String) && (key[0] == '.')
key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }"
args[0] = key
end
I18n.translate(*args)
end
alias :t :translate
(wherein I'll replace the controller_path part)

HTML "class" and "id" attributes using hyphens instead of undescores in Rails 4

Rails fills the id and class of an element generated with various input tag helpers (text_field for example) by itself. For example, in a form_for(#athlete), adding a text_field :height generates
<input name="athlete_height" ... />
I am a strong advocate of the use of hyphens in HTML ids and classes (and in CSS selectors consequently) (by reading MDN, because I like to write using the conventions dictated by the language - like in data-stuff - and because it's just better looking).
I read here and there that in Rails you can't fix this. This would be quite disappointing.
Do you know a way around this problem?
I'm afraid, underscores are hardcoded. From https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/form_helper.rb, lines 446-450:
options[:html].reverse_merge!(
class: as ? "#{action}_#{as}" : dom_class(object, action),
id: (as ? [namespace, action, as] : [namespace, dom_id(object, action)]).compact.join("_").presence,
method: method
)
you can always provide your own id and class, or patch code of ActionView, which may be hard, tedious and error-prone
Recently ran into this very same situation and #lobanovadik has a good solution: just write your own helper method.
# app/helpers/dom_helper.rb
module DomHelper
dom_id_helper(model)
dom_id(model).dasherize
end
end
# view.html.erb
<%= link_to "Text", path, id: dom_id_helper(#model) %>
#=> Text
This has the benefit of not monkey-patching Rails or messing with any default methods/configuration. Thus, you won't "break" any updates to Rails.
It also gives you greater flexibility because you can now use both dashes or underscores depending on the situation. For instance, let's say there's a gem that expects IDs to have underscores...you won't break it.
As a personal preference, I always append _helper to all my own helper methods, so that I know it's a helper method and that it came from me and not from Rails (easier to debug).

How to structure Util classes in RoR

I have a template that users can upload that generates a report. They can put special tags into the html template and it will replace with data from the db. Quick example:
<div class="customer-info">
<h1>{customer_name}</h1>
<h2>{customer_address_line1}</h2>
<h2>{customer_address_line2}</h2>
<h2>{customer_address_city}, {customer_address_state} {customer_address_zip}</h2>
</div>
I have a controller that looks up the customer and then parses the template and replaces the tokens.
Right now I have the parse code in the controller creating a fat controller. Not good.
But where should I move the code? Model folder? Create a Util folder and put it there?
Just not sure what the Rails Way would be.
I was curious about this too, and found a very similar discussion here. Honestly, I think it depends on how much parse code there is. If there are only a very few lines, then the model is a safe place. If it's going to be a large package, especially a re-usable one, the /lib/ folder may be a better bet for the parsing itself. However, you definitely should remove it from the controller, as you suggested.
I agree that the logic shouldn't be in the controller, but let's get a
little more specific about how you'd go about implementing this.
First, where are you storing your templates in the database? They
should be stored in their own model, let's call it
CustomerTemplate and give an attribute :template of type Text.
So now we have two types of objects, Customers and
CustomerTemplates. How to render a customer given a template? It
honestly wouldn't be terrible to just have a render function in
the CustomerTemplate model that takes a customer and renders it, but
it is putting some logic inside your app that doesn't strictly belong
there. You should separate out the "customer specific rendering logic"
from the "rendering my simple custom template language".
So, let's create a simple template handler for your custom language,
which I'm going to nickname Curly. This handler should know nothing about
customers. All it does is take a string and interpolate values inside
{}'s. This way if you want to add new template types in the future —
say, to render another model like an invoice — you can use the same
template type.
Templates in Rails are classes which respond to call and are
registered with ActionView::Template. The simplest example is Builder.
Here's a quickly written Template handler which renders Curly. The
call function returns a string which is eval'd, so the string has to
be valid ruby code. The string eval in scoped by the render call, so
it has access to any variables passed in via the { locals: {} }
option to render.
# In lib/curly_template_handler.rb
class CurlyTemplateHandler
def self.call(template)
src = template.source
"""
r = '#{src}'.gsub(/{([^}]*)}/) { |s|
local_assigns[$1.to_sym] || s
}
raw r
"""
end
end
Make sure the handler is initialized, and let's set it to listen for
the :curly type.
# In config/initializers/register_curly_template.rb
ActionView::Template.register_template_handler(:curly, CurlyTemplateHandler)
We need to add lib/ to autoload_paths so the class is loaded:
# config/application.rb
config.autoload_paths += %W(#{config.root}/lib)
Finally, we can render our template in our view! I'm embedding the string here, but you'd really get it from a CustomerTemplate object:
<%= render(inline: "<h2>{customer_name}</h2><p>{customer_address}</p>",
type: :curly,
locals: { customer_name: #customer.name,
customer_address: #customer.address }) %>
DO NOT USE MY EXAMPLE CODE IN PRODUCTION! I left out a bunch of corner
cases which you'll need to handle, like sanitizing user input.

How to extend the I18n.translate method in rails?

right now when if I have in my app some code like
<%= t :test %>
and there is no translation available for :test I get something like this in my view:
<span class="translation_missing" title="translation missing: en.test">Test</span>
What I would like to add is to include a similar span even for existing translations, so if the translation exists I would like to get something like:
<span class="translation_existing" title="translation existing: en.test">Translated string</span>
note the different class name in the span. And the string should be really translated.
How can I achieve this result by overriding the translate method?
Thank you in advance
Gnagno
"t" is a view helper :
http://api.rubyonrails.org/classes/ActionView/Helpers/TranslationHelper.html#method-i-t
it is easy to view its source code and to create your own, you can also override the helper by redefining it in your ApplicationHelper.
But IMHO, you should not redefine it, but create your own one with a different name. There are so many cases where you'll want to use the "t" helper in parts of your views that would screw up if you insert additional HTML markup (ie. : I use the "t" helper in the section of my pages to translate the title, meta tags, etc)...
I created a helper that overwrites 't' method, which is just shortcut for 'translate':
def t(*a) # I don't care what params go in
key = a.first # just want to override behaviour based on the key
#if my overrides contain the key
if value = #i18n_overrides["#{I18n.locale}.#{key}"]
return value # return the overrided value
end
translate(*a) # otherwise letting i18n do its thing
end
More clean approaches would be these, but they didn't work for me:
Tried using alias but realised that t or translate dont' exist in that scope at the point of execution of class body.
Tried calling ActionView::Helpers::TranslationHelper.translate instead of just translate but that didn't work either.

Rails way to offer modified attributes

The case is simple: I have markdown in my database, and want it parsed on output(*).
#post.body is mapped to the posts.body column in the database. Simple, default Activerecord ORM. That column stores the markdown text a user inserts.
Now, I see four ways to offer the markdown rendered version to my views:
First, in app/models/post.rb:
# ...
def body
markdown = RDiscount.new(body)
markdown.to_html
end
Allowing me to simply call #post.body and get an already rendered version. I do see lots of potential problems with that, e.g. on edit the textfield being pre-filled with the rendered HMTL instead of the markdown code.
Second option would be a new attribute in the form of a method
In app/models/post.rb:
# ...
def body_mardownified
markdown = RDiscount.new(body)
markdown.to_html
end
Seems cleanest to me.
Or, third in a helper in app/helpers/application_helper.rb
def markdownify(string)
markdown = RDiscount.new(string)
markdown.to_html
end
Which is used in the view, instead of <%= body %>, <%= mardownify(body) %>.
The fourth way, would be to parse this in the PostsController.
def index
#posts = Post.find(:all)
#posts.each do |p|
p.body = RDiscount.new(string).to_html
#rendered_posts << p
end
end
I am not too familiar with Rails 3 proper method and attribute architecture. How should I go with this? Is there a fifth option? Should I be aware of gotchas, pitfalls or performance issues with one or another of these options?
(*) In future, potentially updated with a database caching layer, or even special columns for rendered versions. But that is beyond the point, merely pointing out, so to avoid discussion on filter-on-output versus filter-on-input :).
The first option you've described won't work as-is. It will cause an infinite loop because when you call RDiscount.new(body) it will use the body method you've just defined to pass into RDiscount (which in turn will call itself again, and again, and so on). If you want to do it this way, you'd need to use RDiscount.new(read_attribute('body')) instead.
Apart from this fact, I think the first option would be confusing for someone new looking at your app as it would not be instantly clear when they see in your view #post.body that this is in fact a modified version of the body.
Personally, I'd go for the second or third options. If you're going to provide it from the model, having a method which describes what it's doing to the body will make it very obvious to anyone else what is going on. If the html version of body will only ever be used in views or mailers (which would be logical), I'd argue that it makes more sense to have the logic in a helper as it seems like the more logical place to have a method that outputs html.
Do not put it in the controller as in your fourth idea, it's really not the right place for it.
Yet another way would be extending the String class with a to_markdown method. This has the benefit of working on any string anywhere in your application
class String
def to_markdown
RDiscount.new(self)
end
end
#post.body.to_markdown
normal bold italic
If you were using HAML, for example in app/views/posts/show.html.haml
:markdown
= #post.body
http://haml-lang.com/docs/yardoc/file.HAML_REFERENCE.html#markdown-filter
How about a reader for body that accepts a parse_with parameter?
def body(parse_with=nil)
b = read_attribute('body')
case parse_with
when :markdown then RDiscount.new(b)
when :escape then CGI.escape(b)
else b
end
end
This way, a regular call to body will function as it used to, and you can pass a parameter to specify what to render with:
#post.body
normal **bold** *italic*
#post.body(:markdown)
normal bold italic

Resources