Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 5: Can you pluralize two words in a string with two different counts?

I'm building a view for a store that lists a summary of a product's reviews. I'm grouping the reviews by score, and I want a string at the top of each group of the form "10 reviews of 5 stars". I know that if I just wanted to pluralize "review", I could do this in en.rb:

en: {
  reviews_header: {
    one: "%{count} review",
    other: "%{count} reviews"
  }
}

Is there a format for the reviews_header hash that lets me specify counts for both "review" and "star" so they are both pluralized when necessary? In pseudo-code, I'm imagining something like:

en: {
    reviews_header: {
        counts: [ :review_count, :star_count ],
        review_count: {
            one: {
                star_count: {
                    one: "%{review_count} review with %{star_count} star",
                    other: "%{review_count} review with %{star_count} stars"
                }
            },
            other: {
                star_count: {
                    one: "%{review_count} reviews with %{star_count} star",
                    other: "%{review_count} reviews with %{star_count} stars"
                }
            }        
        }
    }
}

And I would get the string with t(:reviews_header, review_count: 10, star_count: 5).

What I'm doing for now is I changed the string to the form "10 5-star reviews", which gets around the problem of pluralizing "star", but that won't work in other languages.

like image 572
Michael Dunn Avatar asked Jul 19 '17 19:07

Michael Dunn


1 Answers

You have a case of nested plurals here. Although my familiarity with Ruby is rudimentary, but I could not find any documentation which offers a solution for this nested plural case through Ruby's built in i18n features. However, in programming languages with support for ICU Library, it is possible to benefit from MessageFormat.

Using this library for Ruby for MessageFormat parsing and formatting, one could handcraft a nested MessageFormat to cover all the variation of this string to cover the complexities of nested plural rules in any language. Please bear in mind that you don't need most of these rules for most of languages, but there are few languages like Arabic and Russian which need many of these cases; Arabic has special cases of twos, Russian has special cases of ones (1, 21, 31, 1001, but not 11). A chart from Unicode CLDR project listing the plural rules for all languages can be found here.

Usually, I train the translators to use this online tool (which is from the same developr of the message-format-rb) and translate the MessageFormat according to the need of their language.

MessageFormat for Translators tool

Here is a complete, maximal MessageFormat following by a Ruby snippet:

{review_count, plural, 
=0 {
    {star_count, plural,
        other {no reviews}}
}
zero {
    {star_count, plural,
        zero {{review_count} reviews with {star_count} stars} 
        one {{review_count} review with {star_count} star}
        two {{review_count} reviews with {star_count} stars}
        few {{review_count} reviews with {star_count} stars}
        other {{review_count} reviews with {star_count} stars}}
}
one {
    {star_count, plural,
        zero {{review_count} review with {star_count} stars}
        one {{review_count} review with {star_count} star}
        two {{review_count} review with {star_count} stars}
        few {{review_count} review with {star_count} stars}
        other {{review_count} review with {star_count} stars}}
}
two {
    {star_count, plural,
        zero {{review_count} reviews with {star_count} stars}
        one {{review_count} review with {star_count} star}
        two {{review_count} reviews with {star_count} stars}
        few {{review_count} reviews with {star_count} stars}
        other {{review_count} reviews with {star_count} stars}}
}
 few { 
    {star_count, plural,
         zero {{review_count} reviews with {star_count} stars} 
         one {{review_count} review with {star_count} star}
         two {{review_count} reviews with {star_count} stars}
         few {{review_count} reviews with {star_count} stars}
         other {{review_count} reviews with {star_count} stars}}
}
other {
    {star_count, plural,
        zero {{review_count} reviews with {star_count} stars}
        one {{review_count} review with {star_count} star}
        two {{review_count} reviews with {star_count} stars}
        few {{review_count} reviews with {star_count} stars}
        other {{review_count} reviews with {star_count} stars}}
}
}

And the Ruby snippet:

require 'message_format'
require 'test/unit/assertions'
include Test::Unit::Assertions

icumf = "{review_count, plural, =0 {{star_count, plural,other {no reviews}}} zero { {star_count, plural, zero {{review_count} reviews with {star_count} stars} one {{review_count} review with {star_count} star} two {{review_count} reviews with {star_count} stars} few {{review_count} reviews with {star_count} stars} other {{review_count} reviews with {star_count} stars}}}one {{star_count, plural, zero {{review_count} review with {star_count} stars} one {{review_count} review with {star_count} star} two {{review_count} review with {star_count} stars} few {{review_count} review with {star_count} stars} other {{review_count} review with {star_count} stars}}} two {{star_count, plural, zero {{review_count} reviews with {star_count} stars} one {{review_count} review with {star_count} star} two {{review_count} reviews with {star_count} stars} few {{review_count} reviews with {star_count} stars} other {{review_count} reviews with {star_count} stars}}} few {{star_count, plural,zero {{review_count} reviews with {star_count} stars} one {{review_count} review with {star_count} star} two {{review_count} reviews with {star_count} stars} few {{review_count} reviews with {star_count} stars} other {{review_count} reviews with {star_count} stars}}} other {{star_count, plural, zero {{review_count} reviews with {star_count} stars} one {{review_count} review with {star_count} star} two {{review_count} reviews with {star_count} stars} few {{review_count} reviews with {star_count} stars}        other {{review_count} reviews with {star_count} stars}}}}"

# Set the locale to get the plural rules for that locale
message = MessageFormat.new(icumf, 'en')
assert_equal message.format({ :review_count => 0, :star_count => 0 }), 'no reviews'
assert_equal message.format({ :review_count => 0, :star_count => 100 }), 'no reviews'
assert_equal message.format({ :review_count => 1, :star_count => 2 }), '1 review with 2 stars'
assert_equal message.format({ :review_count => 2, :star_count => 5 }), '2 reviews with 5 stars'
like image 106
Shervin Avatar answered Oct 07 '22 05:10

Shervin