Gettext program - ruby-on-rails

Given the phrase:
"You've successfully donated €10 to Doctors of the World"
I wanted to dynamically generate this text for different amounts and charity names, which i did using
_("You've donated %{amount} to %{charity_name}")
Where charity_name comes from a list of possible charities and each charity is a hash with data about the charity.
I'm not a french speaker and only learnt basic french in school but the problem with doing this (which is probably immediately obvious to any speaker of a language with gendered articles) is that the "to" part of the translation can take various forms a la, au, a'l or aux depending on the noun.
eg "Vous souhaitez donner 10€ aux Médecins du Monde"
What is the best way to handle this using gettext, given that this will need to be scaled to other languages? There are only a few cases where this will need to be done because most cases of dynamic text (99%+ can be handled fine with parameters.
I've thought of 3 ways to do this:
1) Have highly dynamical text such as this generated from a function, one per message per language as necessary. The function accepts an amount and charity name as a parameter and returns the translated text.
2) Manually add a translation for "to " for each charity and use that in place of %{charity_name} and then get the translation from the po file.
3) Add an entry in each charity hash specifying the form of the "to " eg the hash for les Médecins du Monde would also store aux Médecins du Monde.
Are any of these methods viable or is there a better alternative I'm not thinking off?

May be not the best approach but I've used this for several times.
Consider a table with below fields:
id, name_en, name_ru, created_at, updated_at
I assume that you use I18n to get language parameter.
controller
def index
#lang = params[:locale] || I18n.locale
#examples = Example.all
end
view
<% #examples.each do |ex| %>
<li>
<%= ex.send("name_#{#lang}") %>
</li>
<% end %>
Above code will present name_ru (name in russian) or name_en (in english) based of I18n.locale

Related

What exactly is a template engine?

I have done some googling and I understand that the template engine of Ruby is erb but what does a template engine mean?
Template engine are nothing but presentation layer, that represent actual logic/variable of rails application in html format. So end user can get what they are looking for.
Consider if you have #users object and you want to loop through each record and represent name of each user detail on webpage. There is no way in simple html to do so. So erb and other template engines comes in picture.
These engines covert/evaluate values of variables, functions and user defined syntax to html format and represent to end user.
Hope this helps you.
Template is a pattern, mold, or the like, usually consisting of a thin plate of wood or metal, serving as a gauge or guide in mechanical work that determines or serves as a pattern; a model:
- Dictionary Definition
In our world of Programming, The template is a framework of any repetitive work sharing a common pattern. We extract out the common pattern as a template, now you can embed the changing/dynamic data and create your copy of the work.
Example: You notice forms you get in hospitals or Govt. offices where there are blank-fields for you to write your dynamic data. At last, you get your copy of the application. Don't you?
To facilitate this we have ERB as an engine in the standard library of Ruby.
Let's get our hand dirty; Try this and see results:
require 'erb'
your_template = <<-TEMP
This document is a template for <%= title %> <%= full_name %>.
Now we are going to have a line written <%= n %> times.
<% n.times do %>
This line must repeat <%= n %> times.
<% end %>
TEMP
title = 'Mr.'
full_name = 'Shiva Gaire'
n = 5
generated_doc = ERB.new(your_template).result
puts generated_doc
Output
This document is a template for Mr. Shiva Gaire.
Now we are going to have a line written 5 times.
This line must repeat 5 times.
This line must repeat 5 times.
This line must repeat 5 times.
This line must repeat 5 times.
This line must repeat 5 times.

Rails - Database translation on user selection values

On my rails app there is a select box with values of, fuel_types, such as, Oil, Gas, Petroleum, Diesel. The default language is en so this is fine.
When user changes website to french these data should be shown as french and when user submits the form, the data should be in fr however, I can not do it by using
fr:
fuel_types:
oil: ""
gas: ""
petroleum: ""
Because then database data will be mixed with fr and en language. I then have to show these data in the show page, if fr or en.
Is this possible with globalize3 gem ?
I see that static content in database can be translated with globalize3 but while user filling the form it is either fr or en according to locale variable then the database will be populated with en and fr values, it will make almost impossible to search data and process.
btw: I have a model called fuel_types with name id columns, where I keep oil, gas, petroleum values to show in select tag. Then I save them to car model.
EDIT
In this way you are right!. But what if I have;
...
<%= f.collection_select(:fuel_type, Fuel.all, :name, :name, {}, class: "Select-control u-sizeFull") %>
...
I have model called Fuel and this fuel (belongs to car model) should be seen as the locale variable. Then should be written in one language to database?
So for edited question: third and fourth argument can be anything that responds to call so instead of using name in fourth argument use proc {|fuel| translate(fuel.name)} or any other translation method you have, just call it in proc.

I18n to call different Model Attribute depending on Locale?

So I am building an on-line shop and I want two language options, English and Spanish.
I am using I18n as you would normally do for all my static text and headings ect.
But, I have a products Model that can have new Products created for listing on the site. This has fields like :name_en and :name_es, :description_en and :description_es ect.
When the admin uploads a new product they obviously need to add the English and the Spanish text.
Because I have only 2 locales what I would like to do i think is call something like
<%= Product.name_"#{I18n.locale.downcase}" %>
But obviously this does not work. How can i, or just can I, interpolate a method or Attribute?
Have I missed something obvious here and just going about it the wrong way or is there a way to do this along the lines of my thinking?
Any Help massively appreciated.
Thanks
You can use send method. Try something like:
<%= Product.send("name_#{I18n.locale.downcase}") %>
Just a word of explanation, the following are equal:
string = "Hello"
string.upcase
# => "HELLO"
string.send("upcase")
# => "HELLO"
Hope that puts you in proper direction!
Good luck!

How to automatically link to objects in text submission in Rails

So, you're in Github filing an issue and you refer to issue #31. Then, while writing this issue, you note that #johnmetta has suggested some possible solutions that he's working on. Then you hit "Submit New Issue" and when you do, "#31" and "#johnmetta" are links, and #johnmetta has been notified, and issue #31 has a notification that it has been referenced.
I realize that there are more than one technologies at work here (Javascript goodies, etc), but what I'm looking for are some examples of how to do this type of thing in the Rails world. It's an interestingly difficult subject to search for.
What I've come up with conceptually is:
Have some identifier, such as # or # that is reserved
Upon submission, search for that identifier in the appropriate attribute
Upon finding it, search for the appropriate model with a field matching what follows
Once finding that, replace that text string with a link
Optionally, do whatever necessary to notify the referenced object
That said, it seems like it's super simple (explicitly coded, assumes friendly_id).
def prettify_user_links(str, source):
result = str
str.scan(/(#\S+)+/).each do |mtch|
# Strip off whatever identifier we're using
search_string = mtch[0].gsub('#','')
# Search for the matching model in the appropriate table
user = User.find(search_string)
if user
# If we find a matching model, create some link text and link it
link_txt = "<a href=>'#{user.url}'>#{mtch}</a>"
result.gsub!(search_string, link_txt)
# Notification. Not sure how/where, maybe with a message bus, or something more brute-force like
Comment.create :user_id => user.id, :body => "You have been mentioned in #{link_to comment.excerpt, comment} by #{link_to comment.owner, owner}"
return result
That would be my first cut, but I feel there have to be much more elegant solutions.
An additional aspect to this question: How would you grab a snippit of surrounding text. The brute force way would be to search n words before and m words after that string and grab all of that, then grab that sub-string from the results and do the search. Still, seems like there'd be a more elegant solution.
What you've described is the basic way; anything else is not terribly more elegant. It's helpful to see it as two parts: one is on receipt of the comment (when you should do notifications) and the other is on display of the comment, when you should do linkification.
This allows you to keep the original comment in its original form, which is helpful.
Perhaps put an after_create (so notifications aren't sent on every edit) on the comment model (assuming a comment model that includes a 'body' field):
[edit: added contextual info]
after_create :notify_mentions
def notify_mentions
body.scan %r{(.{0,40})#(\w+)(.{0,20})} do |match|
username = match[1]
context = [match.first, match.last]
Notification.send(match, context, self) if User.exists?(:login => username)
end
end
I use \w+ in place of \S+ because people often say things like:
Hey #JohnMetta, how are you doing?
and \S+ will capture the , which might be wrong. Pulling the # out of the capture group lets me ignore it during notification.
The context in the above match groups consists of the 40 characters before and 20 characters after the matched username for your snippet. Adjust to taste.
Then when displaying the message, you essentially create a helper something like what you had:
def linkify(body)
body.gsub %r{#\w+} do |match|
link_to match, :controller => :users, :action => :show, :id => match
end
end
#gsub is awesome like that, in that it takes a block and replaces with the contents.
It's not a lot more elegant than what you had, but it should give a pretty decent result.

Localizing a text field containing a number in Ruby on Rails

I am currently working on a project to internationalize one of our ruby-on-rails web applications so that it can be used in other countries (France will be the first one in this case).
A particular issue I haven't worked out yet is with the displaying of numeric fields. When displaying numbers for display purposes only, I do the following:
<%= number_to_percentage(tax.rate, :precision => 2)%>
In English, this shows 17.50, but in French it shows 17,50 (with a comma in place of the decimal point) which is as expected. The problem comes in the Edit form, when I show a text field
<%= f.text_field :rate, :size => 15 %>
When this renders a text box on the screen, the text box always shows 17.50 with a full stop rather than a comma for French. I am not sure that is correct.
When I tried doing the following:
<%= f.text_field :rate, :size => 15, :value => number_with_precision(f.object.rate, :precision => 2) %>
This did indeed show 17,50 in the text box for French, but when I click on the Update button to save the form, the Ruby validation kicks in and tells me that 17,50 is not a number (or rather it says "n'est pas un nombre"). I have to enter 17.50 to get it to save.
To be honest, I am not entirely sure on the correct thing to do here. Should all countries enter numbers with full stops in text boxes, or is there a way to get Ruby-on-Rails to display commas, and validate them appropriately?
TL;DR
This is the kind of things I hate to do over and over again (I'm serving french users, they're easily confused with dots as the decimal separator).
I exclusively use the delocalize gem now, which does the format translation automatically for you. You just have to install the gem and leave your forms as-is, everything should be taken care of for you.
I like to read, give me the long explanation
The basic conversion is quite simple, you have to convert back and forth between the following formats:
The backend one, which is usually English, used by your persistent storage (SQL database, NoSQL store, YAML, flat text file, whatever struck your fancy, ...).
The frontend one, which is whatever format your user prefers. I'm going to use French here to stick to the question*.
* also because I'm quite partial towards it ;-)
This means that you have two points where you need to do a conversion:
Outbound: when outputting your HTML, you will need to convert from English to French.
Inbound: When processing the result of the form POST, you will need to convert back from French to English.
The manual way
Let's say I have the following model, with the rate field as a decimal number with a precision of 2 (eg. 19.60):
class Tax < ActiveRecord::Base
# the attr_accessor isn't really necessary here, I just want to show that there's a database field
attr_accessor :rate
end
The outbound conversion step (English => French) can be done by overriding text_field_tag:
ActionView::Helpers::FormTagHelper.class_eval do
include ActionView::Helpers::NumberHelper
alias original_text_field_tag text_field_tag
def text_field_tag(name, value = nil, options = {})
value = options.delete(:value) if options.key?(:value)
if value.is_a?(Numeric)
value = number_with_delimiter(value) # this method uses the current locale to format our value
end
original_text_field_tag(name, value, options)
end
end
The inbound conversion step (French => English) will be handled in the model. We will override the rate attribute writer to replace every French separator with the English one:
class Tax < ActiveRecord::Base
def rate=(rate)
write_attribute(:rate, rate.gsub(I18n.t('number.format.separator'), '.')
end
end
This look nice because there's only one attribute in the example and one type of data to parse, but imagine having to do this for every number, date or time field in your model. That's not my idea of fun.
This also is a naïve* way of doing the conversions, it does not handle:
Dates
Times
Delimiters (eg. 100,000.84)
* did I already tell you I like French?
Enter delocalize
Delocalize is working on the same principle I outlined above, but does the job much more comprehensively:
It handles Date, Time, DateTime and numbers.
You don't have to do anything, just install the gem. It checks your ActiveRecord columns to determine if it's a type that needs conversion and does it automatically.
The number conversions are pretty straightforward, but the date ones are really interesting. When parsing the result of a form, it will try the date formats defined in your locale file in descending order and should be able to understand a date formatted like this: 15 janvier 2012.
Every outbound conversion will be done automatically.
It's tested.
It's active.
One caveat though: it doesn't handle client-side validations. If you're using them, you will have to figure out how to use i18n in your favourite JavaScript framework.
This is the gsub technique :
In your model :
before_validation :prepare_number
def prepare_number
self.rate.gsub(/,/, '.') if self.rate.match /,\S/
end

Resources