Different text i18n in Rails - ruby-on-rails

In my rails application I have a partial(file1/_partial.html.erb) that must be rendered in Two form file2/form1.html.erb and file3/_partial.html.erb, I'am wondering what is the best solution to i18n a text in the partial
actually what I'm doing is
<%= (t :to_ranslate, :scope => 'file2.form1' ) %>
file2:
form1:
to_ranslate: "translated"
and this is working but I'm wondering if there is a solution where can I have different translation based on template so for the same partial i will have different text based on where it is rendered

If you do not need different translations when included in different templates you can just use the "Lazy Lookup" as described here in 4.1.4: http://guides.rubyonrails.org/i18n.html#looking-up-translations
It also applies to rendering partials, for example:
in file1/_partial.html.erb
<%= (t '.to_translate') %>
in the translations.yml:
en:
file1:
_partial:
to_translate: "translated"
For using different translations depending on where you render from, like so (untested but the principle should work):
in file1/_partial.html.erb
<%= (t "#{translation_scope.to_s}.to_translate') %>
in file2/form1.html.erb
<%= render '/file1/partial', translation_scope: 'file2._partial' %>
in file3/_partial.html.erb
<%= render '/file1/partial', translation_scope: 'file3._partial' %>
in the translations.yml
en:
file1:
_partial:
to_translate: "translated"
file2:
_partial:
to_translate: "translated"
file2:
_partial:
to_translate: "translated"
This would have the advantage that it still works when you do not specify the translation_scope (falling back to its default) and also when you pass it.
This is the reason why i would prefer it over using the scope: 'xx' like you did.
And the whole interface seems a little bit like inheritance in the views with the same structure everywhere.
In a more abstract and complicated way it would also be possible to use fallbacks for this, but in the example here this would be overkill. On larger projects it might make sense to implement something like this.

as far as i know rails is internationalized by default out of the box
just place the text as you would normally and it should work no problem.
reference: http://guides.rubyonrails.org/i18n.html

Related

i18n rails lazy look-up from a helper?

I'm using I18n lazy look-up e.g. t('.field') with Rails 4 and from both controllers and views, I18n does the appropriate look-up. But it doesn't from helpers and I was wondering whether there is a reason why or a solution?
It seems that if I create a helper function, say 'help()', and call it from multiple different views, I have to define the same I18n strings twice (yes, I use aliases ;-) ). So I need to define both
(views), view1.help.field ...and...
(views), view2.help.field
Any nice ways to avoid this?
I18n.t uses the current request as a context. So when you call:
<%= t('.hello') %>
From apps/views/users/show.html.erb it will use the key users.show.hello. However on apps/views/pets/show.html.erb the translation is missing as you have noticed.
It does not matter if you are calling it in your view or in a helper since both use the view context.
If you want to avoid this you would simply state the translation key explicitly:
<%= t('users.show.hello') %>
If you want to use dynamic lookup and fall back to a fixed key you could do it like this:
<%= t('.hello', default: t('users.show.hello')) %>

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).

Truncate + Sanitize in Rails Views

I ran into a small problem today when I was trying to use sanitize and truncate in conjunction with one another to create an excerpt for my blog. Each of the methods worked by itself, but used together it would only truncate. I tried both of these syntaxes (the former being recommended in a blog post titled "Six Ruby on Rails Tips & Tricks"):
<%= truncate(sanitize(post.content), length: 580) %>
<%= sanitize(truncate(post.content, length: 580, separator: '<p>')) %>
And then I tried to put truncate in the controller, and sanitized that object in the view, but still no.
Finally I got it to work like this:
<%= sanitize(post.content.truncate(580, separator: '</p>')) %>
What I'd like to know is why didn't it work when I wrapped a method in another method? What's the difference with the last way I tried it?
TIA
'bondibox'
This worked for me:
<%= sanitize(article.description[0..100]) %>
Truncate and Sanitize are not included in controllers, they are part of ActionView::Helpers::TextHelper and ActionView::Helpers::SanitizeHelper respectively. These modules are not included per default in a controller, so you cannot use it in there.
However, both are included in the views (templates) and you can therefore use them in there. You can include the modules stated above in a controller class to use them there, but i would not recommend it.
The reason the second statement works is because Rails extends some base objects from Ruby, like String with several methods. so you actually call sanitize on a truncated version of a string truncated by a method of String.
The combination of both is a bit tricky. I cannot really tell you why the combination of the module version of sanitize and truncate does not work without a little more info. What exactly did you try to accomplish here (examples?)
An old question, but landed on it while struggling with this myself. The problem is that sanitize will not strip all HTML tags, but just a few that are defined by default, such as script. So you have to specifiy that no tags are allowed:
truncate sanitize(post.content, tags: [], attributes: []), length: 580
Thanks Ruben,
<%= sanitize(article.description[0..100]) %>
is working perfectly in Rails 6.
Our experience is that all other combinations w/ truncate aren't working.

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

Rails 3: Where should a helper that uses h (i.e. html_escape) live?

I'm writing a webapp in Ruby on Rails 3. Rails 3 automatically escapes any potentially-bad strings, which is generally a good thing, but means if you assemble HTML yourself, you have to call html_safe on it.
I have a Card model, which has several text fields, the contents of which are not trusted (may contain evil HTML or script). I have a function which performs a few transforms on one of these text fields, using other knowledge about the specific Card, to produce HTML output. I want to embed the HTML produced by this function in several places throughout several parts of my app.
Conceptually, this helper is to do with the View. However, I can't find any way to write functions in my View files; it seems they have to go in Helpers or the Controller/Model.
Since this function is very much specific to a Card object, the next best option would be to have a function inside my Card model card.rb:
class Card < ActiveRecord::Base
[...]
def format(unsafe_text)
initial_text = h unsafe_text # aka html_escape unsafe_text
# assembles HTML output based on initial_text and fields of self
output_text.html_safe!
end
Then I'd like to call this in assorted views by doing things like:
Rules text: <%= format(#card.rulestext) %>
However, there's a big problem here as well. In the Card model card.rb, I am able to use the html_safe! function, but I'm not able to use h or html_escape. It seems that the h and html_escape functions are only available in ERB views, not in the helpers or controllers!
There are a few workarounds. I can make format not sanitize its input, and go
Rules text: <%= format(h(#card.rulestext)) %>
But that's both prone to dangerous slipups (one missing h() and we've got problems) and is very non-DRY. At the moment I'm using a partial to gain access to the h() function:
(in a normal view)
Rules text: <%= render 'formattext', :text=> #card.rulestext %>
(app/views/shared/_formattext.html.erb)
<%= #card.format(html_escape(text)) %>
But this still feels dangerous. All I have to do is make single forgetful call to format(sometext) in a view, rather than calling render 'formattext', :text=> sometext, and I've got unescaped text running around.
Is there any better way to do this? Is there a way to write helper functions to live in the View rather than the Model or the Controller?
Place the logic that does your view assembly into a CardHelper:
app/helpers/card_helper.rb
class CardHelper
def rules(card)
initial_text = h card.rules_text
# assembles HTML output based on initial_text and fields of card
output_text.html_safe
end
end
It's not clear from your example whether you want to format several fields via the format method. If that's the case, then you might be able to do:
class CardHelper
def format(card, attribute)
initial_text = h card[attribute]
# assembles HTML output based on initial_text and fields of card
output_text.html_safe
end
end
You can use this helper like any other:
class CardsController
helper CardHelper
end
and in your views:
<%= rules(#card) %>
or
<%= format(#card, :rules) %>
Escaping the content for view is a View responsibility, this is the reason why the h helper is not available in controllers or models.
Still, I don't understand why can't you simply sanitize the content in the view.
Also note that, in Rails 3, you don't need to call the h helper.
Content is sanitized automatically by default unless you flag it as html_safe!.
The main reason why is not logically true to use the h helper in the model is because the model should work view-independently. In other words, the model should not care whether the content is going to be embedded in a HTML document or JSON file (which requires a different escaping approach compared to HTML).

Resources