i18n Pluralization - ruby-on-rails

I want to be able to translate pluralized strings in i18n in rails. A string can be :
You have 2 kids
or
You have 1 kid
I know that I can use pluralize helper method, but I want to embed this in i18n translations so that I don't have to mess up with my views at any point in the future. I read that :count is somehow used in translations for plural, but I can't find any real resources on how it gets implemented.
Notice that I know that I can pass a variable in a translation string. I also tried something like :
<%= t 'misc.kids', :kids_num => pluralize(1, 'kid') %>
Which works fine, but has a fundamental problem of the same idea. I need to specify the string 'kid' in the pluralize helper. I don't want to do that because it will lead to view problems in the future. Instead I want to keep everything in the translation and nothing in the view.
How can I do that ?

Try this:
en.yml :
en:
misc:
kids:
zero: no kids
one: 1 kid
other: %{count} kids
In a view:
You have <%= t('misc.kids', :count => 4) %>
Updated answer for languages with multiple pluralization (tested with Rails 3.0.7):
File config/initializers/pluralization.rb:
require "i18n/backend/pluralization"
I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
File config/locales/plurals.rb:
{:ru =>
{ :i18n =>
{ :plural =>
{ :keys => [:one, :few, :other],
:rule => lambda { |n|
if n == 1
:one
else
if [2, 3, 4].include?(n % 10) &&
![12, 13, 14].include?(n % 100) &&
![22, 23, 24].include?(n % 100)
:few
else
:other
end
end
}
}
}
}
}
#More rules in this file: https://github.com/svenfuchs/i18n/blob/master/test/test_data/locales/plurals.rb
#(copy the file into `config/locales`)
File config/locales/en.yml:
en:
kids:
zero: en_zero
one: en_one
other: en_other
File config/locales/ru.yml:
ru:
kids:
zero: ru_zero
one: ru_one
few: ru_few
other: ru_other
Test:
$ rails c
>> I18n.translate :kids, :count => 1
=> "en_one"
>> I18n.translate :kids, :count => 3
=> "en_other"
>> I18n.locale = :ru
=> :ru
>> I18n.translate :kids, :count => 1
=> "ru_one"
>> I18n.translate :kids, :count => 3
=> "ru_few" #works! yay!
>> I18n.translate :kids, :count => 5
=> "ru_other" #works! yay!

I hope Russian-speaking Ruby on Rails programmers could find this. Just want to share my own very precise Russian pluralization formula. It based on Unicode Specs.
Here is contents of config/locales/plurals.rb file only, everything else should be done as same as in answer above.
{:ru =>
{ :i18n =>
{ :plural =>
{ :keys => [:zero, :one, :few, :many],
:rule => lambda { |n|
if n == 0
:zero
elsif
( ( n % 10 ) == 1 ) && ( ( n % 100 != 11 ) )
# 1, 21, 31, 41, 51, 61...
:one
elsif
( [2, 3, 4].include?(n % 10) \
&& ![12, 13, 14].include?(n % 100) )
# 2-4, 22-24, 32-34...
:few
elsif ( (n % 10) == 0 || \
![5, 6, 7, 8, 9].include?(n % 10) || \
![11, 12, 13, 14].include?(n % 100) )
# 0, 5-20, 25-30, 35-40...
:many
end
}
}
}
}
}
Native speakers may enjoy cases such as 111 and 121.
And here the test results:
zero: 0 запросов/куриц/яблок
one: 1 запрос/курица/яблоко
few: 3 запроса/курицы/яблока
many: 5 запросов/куриц/яблок
one: 101 запрос/курица/яблоко
few: 102 запроса/курицы/яблока
many: 105 запросов/куриц/яблок
many: 111 запросов/куриц/яблок
many: 119 запросов/куриц/яблок
one: 121 запрос/курица/яблоко
few: 122 запроса/курицы/яблока
many: 125 запросов/куриц/яблок
Thanks for initial answer!

First, remember that number of plural forms depends on language, for English there are two, for Romanian there are 3 and for Arabic there are 6 !.
If you want to be able to properly use plural forms you have to use gettext.
For Ruby and rails you should check this http://www.yotabanana.com/hiki/ruby-gettext-howto-rails.html

English
It just works out of the box
en.yml:
en:
kid:
zero: 'no kids'
one: '1 kid'
other: '%{count} kids'
Usage (you can skip I18n in a view file, of course):
> I18n.t :kid, count: 1
=> "1 kid"
> I18n.t :kid, count: 3
=> "3 kids"
Russian (and other languages with multiple plural forms)
Install rails-18n gem and add translations to your .yml files as in the example:
ru.yml:
ru:
kid:
zero: 'нет детей'
one: '%{count} ребенок'
few: '%{count} ребенка'
many: '%{count} детей'
other: 'дети'
Usage:
> I18n.t :kid, count: 0
=> "нет детей"
> I18n.t :kid, count: 1
=> "1 ребенок"
> I18n.t :kid, count: 3
=> "3 ребенка"
> I18n.t :kid, count: 5
=> "5 детей"
> I18n.t :kid, count: 21
=> "21 ребенок"
> I18n.t :kid, count: 114
=> "114 детей"
> I18n.t :kid, count: ''
=> "дети"

Rails 3 handles this robustly with CLDR consideration and count interpolation variable. See http://guides.rubyonrails.org/i18n.html#pluralization
# in view
t('actors', :count => #movie.actors.size)
# locales file, i.e. config/locales/en.yml
en:
actors:
one: Actor
other: Actors

There is actually an alternative to the cumbersome i18n approach. The solution is called Tr8n.
Your above code would simply be:
<%= tr("You have {num || kid}", num: 1) %>
That's it. No need to extract your keys from your code and maintain them in resource bundles, no need to implement pluralization rules for each language. Tr8n comes with numeric context rules for all language. It also comes with gender rules, list rules and language cases.
The full definition of the above translation key would actually look like this:
<%= tr("You have {num:number || one: kid, other: kids}", num: 1) %>
But since we want to save space and time, num is automatically mapped to numeric rules and there is no need to provide all options for the rule values. Tr8n comes with pluralizers and inflectors that will do the work for you on the fly.
The translation for your key in Russian, would simply be:
"У вас есть {num || ребенок, ребенка, детей}"
By the way, your translation would be inaccurate in languages that have gender specific rules.
For example, in Hebrew, you would actually have to specify at least 2 translations for your example, as "You" would be different based on the gender of the viewing user. Tr8n handles it very well. Here is a transliteration of Hebrew translations:
"Yesh leha yeled ahad" with {context: {viewing_user: male, num: one}}
"Yesh leha {num} yeladim" with {context: {viewing_user: male, num: other}}
"Yesh lah yeled ahad" with {context: {viewing_user: female, num: one}}
"Yesh lah {num} yeladim" with {context: {viewing_user: female, num: other}}
So your single English key, in this case, needs 4 translations. All translations are done in context - you don't have to break the sentence. Tr8n has a mechanism to map one key to multiple translations based on the language and context - all done on the fly.
One last thing. What if you had to make the count part bold? It would simply be:
<%= tr("You have [bold: {num || kid}]", num: 1, bold: "<strong>{$0}</strong>") %>
Just in case you want to redefine your "bold" later - it would be very easy - you won't have to go through all your YAML files and change them - you just do it in one place.
To learn more, please take a look here:
https://github.com/tr8n/tr8n_rails_clientsdk
Disclosure: I am the developer and the maintainer of Tr8n framework and all its libraries.

About Redmine. If you copy pluralization file rules in config/locales/ as plurals.rb and other not same as locale name (ru.rb, pl.rb .. etc) these not work.
You must rename file rules to 'locale'.rb or change method in file /lib/redmine/i18n.rb
def init_translations(locale)
locale = locale.to_s
paths = ::I18n.load_path.select {|path| File.basename(path, '.*') == locale}
load_translations(paths)
translations[locale] ||= {}
end
and if you have older redmine, add
module Implementation
include ::I18n::Backend::Base
**include ::I18n::Backend::Pluralization**

I've found a very good resource with description of locales http://translate.sourceforge.net/wiki/l10n/pluralforms ( alt link ).
For example, for ukrainian, russian, belarusian and few other languages the expression will look like this plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);

Related

Sorting number string in ruby

I have two numbers of different length:
"103" and "11"
In irb:
2.1.1 :005 > "11" > "103"
=> true
2.1.1 :006 > "11" < "103"
=> false
Why does this happen? I understand I can to a .to_i for each string, but if this is a rails query where the column type is string, anything I can do about this?
Strings are sorted lexicographically which means that "1" comes after "0", and "103" comes before "11", and before "1122344", and before "1abc".
You cannot compare strings as if they were numbers, you need to parse them as numbers before you can do that.
The only way I can think of, is to make sure they are padded with enough zeroes before they are turned into a string: "000103", "000011"...
Strings are being compared character by character. Hence '11' > '103' execution stops on a second character and returns true, since '1'.chr > '0'.chr
They are strings. And they are compared by String#ord value.
So '1'.ord # => 49 and '0'.ord # => 48. That is why
'11' > '10' # => true
# and
'11' > '100' # => true
as well as
'b' > 'a' # => true
'a'.ord # => 97
'b'.ord # => 98
# and
'b' > 'aaaaa' # => still true

How to convert a string ("€ 289,95") to a float ("289.95") in Rails?

Context
I have Rails (4.0.1 + Ruby 2.0.0) connected to a PostgreSQL database filled with strings like "€ 289,95". The values have been scraped from a website using Nokogiri. I want to convert the strings to floating points.
What I've tried
Rails console:
listing = Listing.find(1)
=> #<Listing id: 1, title: #, subtitle: #, name: #, price: "€ 289,95", url: #, created_at: #, updated_at: #>
listing_price = listing.price
=> "€ 289,95"
listing_price_1 = listing_price.gsub(/,/, ".")
=> "€ 289.95"
listing_price_2 = listing_price_1.gsub(/€\s/, "")
=> "€ 289.95"
listing_price_3 = listing_price_2.to_f
=> 0.0
Problem
The code works in irb but doesn't work in the rails console.
What I want to know
How to convert a string "€ 289,95" to a float "289.95" in Rails?
The step where your technique is failing is when trying to strip away € and the space from € 289.95 with the regexp /€\s/, but this is not matching, leaving the string unchanged.
The space character in € 289,95 is probably a non-breaking space (U+00A0) rather than a “normal” space, and would be used in the web page so that the € and the value are not separated.
In Ruby the non-breaking space is not matched by \s in a regexp, so your call to gsub doesn’t replace anything:
2.0.0p353 :001 > s = "€\u00a0289.95"
=> "€ 289.95"
2.0.0p353 :002 > s.gsub(/€\s/, "")
=> "€ 289.95"
Non-breaking space is matched by the POSIX bracket expression [[:space:]], or by the character property \{Blank}:
2.0.0p353 :003 > s.gsub /€[[:space:]]/, ""
=> "289.95"
2.0.0p353 :004 > s.gsub /€\p{Blank}/, ""
=> "289.95"
So if you wanted a more specific regexp than in the other answer you could use one of these.
"€ 289,95".sub(/\A\D+/, "").sub(",", ".").to_f
# => 289.95
listing.price.delete('€ ') # => "289,95"
listing.price.delete('€ ').tr(',', '.') # => "289.95"
listing.price.delete('€ ').tr(',', '.').to_f # => 289.95
String's 'delete' method is good for removing all occurrences of the target strings.
and 'tr' method takes a string of characters to search for, and a string of characters used to replace them.
Better probably than the accepted answer is:
"€ 289,95"[/[\d,.]+/].tr ',', '.'

Rails shorter "time_ago_in_words"

Is there a different time calculation in rails besides "time_ago_in_words"?? I want to be able to use just 'h' for hours 'd' days 'm' for months... ex. 3d, or 4h, or 5m
My code now...
<%= time_ago_in_words(feed_item.created_at) %> ago.
The components that make up this string can be localised, and are in the datetime.distance_in_words namespace
For example stick
en:
datetime:
distance_in_words:
x_minutes:
one: "1m"
other: "%{count}m"
And rails will say 10m instead of 10 minutes. Repeat as needed for hours, seconds days etc. you can check locales/en.yml in action_view for all the keys.
If you only want the short format you could create a pseudo locale that only used those keys and use it like so
time_ago_in_words created_at, false, :locale => :en_abbrev
In the newer versions of rails, you can specify a scope as an option for the method like so:
time_ago_in_words(company.updated_at, scope: 'datetime.distance_in_words.abbrv')
Then you just need to have your regular i18n file structured like so:
en:
datetime:
distance_in_words:
abbrv:
about_x_hours:
one: ~ 1h
other: ~ %{count}h
about_x_months:
one: ~ 1mo
other: ~ %{count}mo
about_x_years:
one: ~ 1y
other: ~ %{count}y
almost_x_years:
...
This information is also available in the documentation:
https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html#method-i-distance_of_time_in_words
Here's some other ways to do it. You run the method like normal, and then you use gsub on the string.
Chained
string.gsub(/ mi.*/, 'm')
.gsub(/ h.*/, 'h')
.gsub(/ d.*/, 'd')
.gsub(/ mo.*/, 'mo')
.gsub(/ y.*/, 'y')
Hash
string.gsub(/ .+/, {
' minute'=> 'm', ' minutes'=>'m',
' hour' => 'h', ' hours' => 'h',
' day' => 'd', ' days' => 'd',
' month' => 'mo', ' months' => 'mo',
' year' => 'y', ' years' => 'y'
})
Block
string.gsub(/ .+/) { |x| x[/mo/] ? 'mo' : x[1] }
They all do the same except for when the string is "less than a minute". The chained solution returns "less than a minute". The hash solution returns "less". The block solution returns "lesst".
Just change the locale file for this one case
en:
datetime:
distance_in_words:
less_than_x_minutes:
one: '<1m'
Or add a return to clause at the top of your method
def my_method(string)
return '<1m' if string == 'less than a minute'
# code
end
Note: Does not include solutions for include_seconds: true option.

Is it possible to simulate the behaviour of sprintf("%g") using the Rails NumberHelper methods?

sprintf("%g", [float]) allows me to format a floating point number without specifying precision, such that 10.00 is rendered as 10 and 10.01 is rendered as 10.01, and so on. This is neat.
In my application I'm rendering numbers using the Rails NumberHelper methods so that I can take advantage of the localization features, but I can't figure out how to achieve the above functionality through these helpers since they expect an explicit :precision option.
Is there a simple way around this?
Why not just use Ruby's Kernel::sprintf with NumberHelper? Recommended usage with this syntax: str % arg where str is the format string (%g in your case):
>> "%g" % 10.01
=> "10.01"
>> "%g" % 10
=> "10"
Then you can use the NumberHelper to print just the currency symbol:
>> foo = ActionView::Base.new
>> foo.number_to_currency(0, :format => "%u") + "%g"%10.0
=> "$10"
and define your own convenience method:
def pretty_currency(val)
number_to_currency(0, :format => "%u") + "%g"%val
end
pretty_currency(10.0) # "$10"
pretty_currency(10.01) # "$10.01"
I have solved this by adding another method to the NumberHelper module as follows:
module ActionView
module Helpers #:nodoc:
module NumberHelper
# Formats a +number+ such that the the level of precision is determined using the logic of sprintf("%g%"), that
# is: "Convert a floating point number using exponential form if the exponent is less than -4 or greater than or
# equal to the precision, or in d.dddd form otherwise."
# You can customize the format in the +options+ hash.
#
# ==== Options
# * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
#
# ==== Examples
# number_with_auto_precision(111.2345) # => "111.2345"
# number_with_auto_precision(111) # => "111"
# number_with_auto_precision(1111.2345, :separator => ',', :delimiter => '.') # "1,111.2345"
# number_with_auto_precision(1111, :separator => ',', :delimiter => '.') # "1,111"
def number_with_auto_precision(number, *args)
options = args.extract_options!
options.symbolize_keys!
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
separator ||= (options[:separator] || defaults[:separator])
delimiter ||= (options[:delimiter] || defaults[:delimiter])
begin
number_with_delimiter("%g" % number,
:separator => separator,
:delimiter => delimiter)
rescue
number
end
end
end
end
end
It is the specific call to number_with_delimiter with the %g option which renders the number as described in the code comments above.
This works great for me, but I'd welcome thoughts on this solution.

Is there equivalent for PHP's print_r in Ruby / Rails?

In PHP you can do:
print_r($var) or vardump($var)
which prints "human-readible" information about variable.
Is there equivalent functions / helpers for those in Ruby / Rails ?
In Rails templates you can do
<%= debug an_object %>
and it will do nice HTML PRE output.
Try using pp.
You will need to require it in scripts (or in irb if your .irbc doesn't already do this):
require 'pp'
Then you can 'PrettyPrint' an object thus:
pp object
Instead of requiring 'pp' and using pp, you can simply do
p object
Tested example
require 'pp'
class A
def initialize
#a = 'somevar'
#b = [1,2,3]
#c = {'var' => 'val'}
end
end
a = A.new
pp a # Gives -> #<A:0x2c6d048 #a="somevar", #b=[1, 2, 3], #c={"var"=>"val"}>
p a # Gives -> #<A:0x2c6d048 #a="somevar", #b=[1, 2, 3], #c={"var"=>"val"}>. No need to require 'pp'
There's the method inspect which helps. Sometimes calling the to_s method on an object will help (to_s returns a string representation of the object). You can also query methods, local_variables, class_variables, instance_variables, constants and global_variables.
p ['Hello',"G'day",'Bonjour','Hola'].inspect
# >> "[\"Hello\", \"G'day\", \"Bonjour\", \"Hola\"]"
p ['Hello',"G'day",'Bonjour','Hola'].to_s
# >> "HelloG'dayBonjourHola"
p Array.new.methods
# >> ["select", "[]=", "inspect", "compact"...]
monkey = 'baboon'
p local_variables
# >> ["monkey"]
class Something
def initialize
#x, #y = 'foo', 'bar'
##class_variable = 'gorilla'
end
end
p Something.class_variables
# >> ["##class_variable"]
s = Something.new
p s.instance_variables
# >> ["#x", "#y"]
p IO.constants
# >> ["TRUNC", "SEEK_END", "LOCK_SH"...]
p global_variables
# >> ["$-d", "$\"", "$$", "$<", "$_", "$-K"...]
I know this is an old post, but it is the first thing that Google pops up when searching for "Ruby equivalent of PHP print_r". I'm using Ruby in the command line mode, and there's really not a very good equivalent. "pp" is ok for fairly simple structures, but as soon as you start nesting hashes in arrays in hashes in more arrays, it turns into a jumble pretty fast. Since I haven't found a good emulation of print_r, I wrote one myself. It's good enough for my purposes, not overly complicated and I thought I'd share it to save other people some headache. Compare the output with the real PHP print_r
def print_r(inHash, *indent)
#indent = indent.join
if (inHash.class.to_s == "Hash") then
print "Hash\n#{#indent}(\n"
inHash.each { |key, value|
if (value.class.to_s =~ /Hash/) || (value.class.to_s =~ /Array/) then
print "#{#indent} [#{key}] => "
self.print_r(value, "#{#indent} ")
else
puts "#{#indent} [#{key}] => #{value}"
end
}
puts "#{#indent})\n"
elsif (inHash.class.to_s == "Array") then
print "Array\n#{#indent}(\n"
inHash.each_with_index { |value,index|
if (value.class.to_s == "Hash") || (value.class.to_s == "Array") then
print "#{#indent} [#{index}] => "
self.print_r(value, "#{#indent} ")
else
puts "#{#indent} [#{index}] => #{value}"
end
}
puts "#{#indent})\n"
end
# Pop last indent off
8.times {#indent.chop!}
end
Here's an example (made messy on purpose to show why the PHP print_r is so nice):
carTools = [ "Socket Set", "Combination Wrenches", "Oil Filter puller", "Brake Compressor" ]
houseTools =[ "Circular Saw", "Miter Saw", "Drill" ]
garageItems = Hash["Car1" => "Ford Mustang", "Car2" => "Honda Civic", "Bike1" => "IronHorse"]
garageItems["Tools"] = Hash["Car Tools" => carTools, "House Tools" => houseTools]
constructionSupplies = Hash["Plywood" => ["3/4\" T&G Plywood Sheets", "1/2\" Plywood Sheets"],
"Boards" => ["2x4s", "2x6s", "Engineered I-Joists"],
"Drywall" => ["4x8 1/2\" Sheetrock", "Mesh tape", "Paper tape", "Joint compount"]]
carParts = Hash["Mustang" => ["Clutch", "Transmission", "3.55 Ring & Pinion Gears", "Differential", "30# Injectors", "Pro-M 77mm MAF"]]
garageItems["Supplies"] = ["Oil", "WD40", constructionSupplies, carParts, "Brake Fluid"]
print_r(garageItems)
Output of print_r (actually comprehensible by a human):
Hash
(
[Car1] => Ford Mustang
[Car2] => Honda Civic
[Bike1] => IronHorse
[Tools] => Hash
(
[Car Tools] => Array
(
[0] => Socket Set
[1] => Combination Wrenches
[2] => Oil Filter puller
[3] => Brake Compressor
)
[House Tools] => Array
(
[0] => Circular Saw
[1] => Miter Saw
[2] => Drill
)
)
[Supplies] => Array
(
[0] => Oil
[1] => WD40
[2] => Hash
(
[Plywood] => Array
(
[0] => 3/4" T&G Plywood Sheets
[1] => 1/2" Plywood Sheets
)
[Boards] => Array
(
[0] => 2x4s
[1] => 2x6s
[2] => Engineered I-Joists
)
[Drywall] => Array
(
[0] => 4x8 1/2" Sheetrock
[1] => Mesh tape
[2] => Paper tape
[3] => Joint compount
)
)
[3] => Hash
(
[Mustang] => Array
(
[0] => Clutch
[1] => Transmission
[2] => 3.55 Ring & Pinion Gears
[3] => Differential
[4] => 30# Injectors
[5] => Pro-M 77mm MAF
)
)
[4] => Brake Fluid
)
)
Check out the guide for debugging rails:
http://guides.rubyonrails.com/debugging_rails_applications.html
hints:
script/console is great to try stuff in the context of your app
script/server --debugger to start the server with a debugger turned on, you can then use 'debug' in your code to break into an interactive shell
One approach I lean on a lot is this:
logger.debug "OBJECT: #{an_object.to_yaml}"
Easy to read, although it can get a little unwieldy for large objects.
Guess I'm a little late to this, but what about logger.info [debug|warning]? Use this from Controllers and Models. It will show up in your log files (development.log when in dev mode); and the above mentioned <%= debug("str: " + str) %> for views.
These aren't exact answers to your questions but you can also use script/console to load your rails app in to an interactive session.
Lastly, you can place debugger in a line of your rails application and the browser will "hang" when your app executes this line and you'll be able to be in a debug session from the exact line your placed your debugger in the source code.

Resources