Add translation to I18N dynamically - ruby-on-rails

I have added humanized-money-accessors as described here: Decimals and commas when entering a number into a Ruby on Rails form
Now I have two attributes in my model for the same type of data: the original version and the human-readable version. The problem: Since I am using activerecord-translation-yml-files, I have to put in the same translation for original attribute and the humanized_attribute, because my forms show the name of thie humanized_attribute, but on validation errors, the name of the original attribute is shown.
Is it possible to add translations dynamically? This way I could add the translation for the humanized-version of the field when the humanized_accessor-class-method is called, getting the original translation string from the yml file, instead of writing both of them (with the same value) into the yml-file, just to have more DRY.

This is dependent on the I18n gem's internal API not changing but it is possible using I18n.backend.store_translations.
This contrived example demonstrates:
I18n.with_locale(:fake_locale) { I18n.t('some_word') }
=> "translation missing: fake_locale.some_word"
I18n.backend.store_translations(:fake_locale, some_word: 'fake translation')
I18n.with_locale(:fake_locale) { I18n.t('some_word') }
=> "fake translation"
Important: This is only done in memory. Some persistence or re-generation mechanism is necessary to prevent these from disappearing when you redeploy/restart the server.

You might want to check out globalize3 gem. You have railscast tutorial http://railscasts.com/episodes/338-globalize3?view=asciicast.

Related

Overwrite the default Parsley translation for validations in rails

I already have all my error messages in all the languages I need them in rails config locales, so how do I change the default message for parsley required (and other validators) from
This field is required
And then how to add messages for a different language?
easy :)
Parsley.addMessage('en', 'required', "something else");
Parsley.addMessage('en', 'maxlength', 'something else max is %s')
Parsley.addCatalog('zh-HANS', {required: "其他的东西"}, true);
this adds the new language 'zh-HANS' to the catalog, with one translation and the final paramater true says to change Parsley to that locale, so you would say false if you were just populating a bunch of different locals. The dedicated method to change to the locale is Parsley.setLocale('zh-HANS')
Rails part
(in case you are using rails and have a similar situation, read on.)
regarding rails and getting the translations into the javascript, I like to use data attributes, so if the I18n.locale is zh-HANS I would add a div with some id like
%div#parsley-translations{data:{'current-locale'=> I18n.locale, required: I18n.t('forms.errors.required'), 'maxlength'=>I18n.t('forms.errors.maxlength')}}
then in the javascript
var translationData = $('#parsley-translations').data();
// now use AddLocale with all the keys and values from translationData for the translationData.currentLocale - Will adjust this once I have tested and got it working

How to deal with Money in Rails and Mongoid

I know this question has been asked before, but most answers I've found are related to ActiveRecord or old (most cases, both) and I was wondering whether there's a new take on this.
Is short, my Rails app is an API, so please keep this in mind (can't normally use lots of helpful little view related helpers).
I've been reading about this and found the MoneyRails which seems quite neat. The problem I'm having with it is that when I retrieve the data, it returns an object instead of the an usable value:
class MyModel
include Mongoid::Document
...
field :price_GBP, type: Money
...
end
So to create the document I send a number and it created the document fine. Now when I query the same document it returns an object for price_GBP, which is fine, but my main grip is that it return the value fractional as in my_obj.price_GBP[:fractional] as a string rather than a number.
I'd rather not have my client to have to do the conversion fro string to number than to decimal.
I guess I could create a little helper that would convert the value in such circumstances like so (in my Model):
def my_helper
self.price_GBP = BigDecimal(self.price_GBP) # or something along those lines
end
Then in my controller:
#my_model = Model.find(id)
#my_model.price_GBP = #my_model.price_GBP = #my_model.my_helper
render json: #my_model
With the above in mind, would this be the best approach? If yes, what's the point of using the MoneyRails gem then?
Secondly, if not using the MoneyRails gem, should I use BigDecimal or Float as the field type?
When I tried BigDecimal, the data was saved ok, but when I've retrieve it, I got an string rather than a number. Is this the correct behaviour?
When I tried Float it all worked fine, but I read somewhere that Float is not the most accurate.
What are your thoughts?
Avoid using Float if you're planning on performing any type of arithmetic on the currency values. BigDecimal is good or you can also represent the value in cents and store it as an Integer. This is actually how the Money gem works.
My recommendation would be to continue to use the MoneyRails gem and use the built-in helpers to output the values. You mentioned not being able to use the helpers but I don't see what's preventing that - Rails includes jbuilder which allows you to formulate your JSON structure in a view with access to all "helpful little view related helpers" - for example
# app/views/someresource/show.json.jbuilder
# ...other attributes
json.price_GBP = humanized_money(#my_model.price_GBP)

Keeping track of changes using rails - "changed?"

I am building a multi lingual website, using ruby on rails, where part of the content is supposed to be user generated and they are supposed to be able to create different versions of it for all languages. The language support is handled by i18n gem.
Part of their content is created using Markdown through http://daringfireball.net/projects/markdown/basics .
In my database I save: object.content_markdown_en, object.content_html_en, object.content_markdown_sv, object.content_html_sv and so on for the different locales.
Now if a user changes the content, new html is supposed to be generated. But it seems unnecessary to regenerate the html for all locales if he only made changes in one of the languages.
I thought there might be some way to use something like
if object.content_markdown_[locale]_changed?
generate_new_html
end
that can be run in a loop for all possible locales. But I can't find any nice ways of doing this.
How about:
[:en, :sv].each do |locale|
if object.send("content_markdown_#{ locale }_changed?".to_sym)
send("generate_new_#{ locale }_html".to_sym)
end
end
You can use send to call methods by name:
object.send("content_markdown_#{locale}_changed?".to_sym)
Your loop would look like this:
%w(en sv).each { |locale|
if object.send("content_markdown_#{locale}_changed?".to_sym)
generate_new_html
end
}
However, using a separate translation table might be a better approach.

Hook into Rails' I18n

I've written a little library to hold translated content in model attributes. All you have to do is add the following to a model:
class Page < ActiveRecord::Base
i18n_attributes :title, :content
end
By convention, the data is written to the real attributes i18n_title and i18n_content as a hash (or hstore hash for Postgres). And a number of getters and setters give you access to "localized virtual attributes":
page = Page.new
page.title_en = 'Hello'
page.title_es = 'Hola'
page.i18n_title # => { en: "Hello", es: "Hola" }
I18n.locale = :es
page.title # => "Hola"
page.title_en # => "Hello"
You can use these virtual attributes in forms as well, but there's a downside: The form builder uses I18n to get the label and translate attribute validation errors. And it does of course look for keys such as activerecord.attributes.page.title_en if you use title_enin the form.
It would be very cumbersome to replicate the same translation for every available_locale in the locales/en.yml etc files:
activerecord:
attributes:
page:
title_en: "Title"
title_es: "Title"
...
What I'd like to do is execute some code after Rails has loaded all locales in the boot process and then clone translations for these keys. Is there a way to do this? Maybe a hook which gets called after the translations have been loaded from the YAML files? (The translations are not yet loaded when my lib loads.)
Or do you see another way to tackle this problem? (I've tried to alias I18n.translate, but I'm afraid this might cause major headache in the future.)
Thanks for your hints!
Although you dropped this approach, please let me share my thoughts:
I don't think it is incredible useful to add other locale strings into a translation file for a specific localization. Since a config/locales/$locale.yml usually starts (at least in my case) with
$locale:
...
there is no need for activerecord.attributes.page.title_es in an English localization file. I'd just put it in es.yml as activerecord.attributes.page.title.
I mean: isn't that the whole purpose of separate localization files? (Or from the developer/translator point of view: In which file should I search for .title_es, in en.yml, es.yml or both?)

How to translate (I18N) error texts raised by validations in the model (Ruby On Rails)

I am running an application on Ruby On Rails (3.1) and need to handle translations into various languages. I got my controller texts properly handled using the I18N feautures, but what about validations in models, especially those like this:
validate :valid_quantities?
def valid_quantities?
if self.quantity*self.unitprice < 1.00
errors.add("The transaction value", "is < 1.00")
return false
end
How would I code this to provide support for other languages?
In addition, how to I handle the formatting of the numbers? I cannot call the ActionView helpers and user e.g. number_to_currency
I would use this:
total_price = self.quantity*self.unitprice
errors.add(:transaction_value, :transaction_undervalued, { value: total_price })
IMHO you would better use a simple keyword like :transaction_undervalued, that way I18n look in several namespaces according to rails guides - i18n - error message scopes:
activerecord.errors.models.[model_name].attributes.transaction_undervalued
activerecord.errors.models.[model_name]
activerecord.errors.messages
errors.attributes.transaction_undervalued
errors.messages
*replace [model_name] with the model is using this validation
For the locales part, this is an example in /config/locales/en.yml
en:
errors: &errors
messages:
transaction_undervalued: "The transaction value is %{value}. That is < 1.00"
For standard validations, see http://guides.rubyonrails.org/i18n.html#error-message-scopes. For your custom validations, why don't you use I18n.t for that?
errors.add(:transaction_value, I18n.t("errors.attributes.transaction_value.below_1"))
Ok, I made partial progress, looks like the following works:
Change the code in the model validation to
errors.add(" ",I18n.t(:valid_quantities,:amount=>1.00))
Enter the translation into de.yml
de:
valid_quantities: "Der Mindestwert einer Order ist %{amount}"
But I am still looking for a way to format that number correctly (1.00 for English, 1,00 for German).
You need to use method I18n.l for localising numbers and dates.
Rails guides has some addition info about the localisation of errors, whereby you should be able to add the translations straight into the translation file: http://guides.rubyonrails.org/i18n.html#translations-for-active-record-models

Resources