Make pluralisation code an independent lib

I considered moving the code to a service but I think that this code
can be completely independent of the Open Food Network use case. It
would be easy to move to a gem. The downcasing may need reconsidering
for general use.
This commit is contained in:
Maikel Linke
2020-01-28 16:24:21 +11:00
parent 6f8bb793e1
commit 9535c5647f
5 changed files with 114 additions and 35 deletions

View File

@@ -34,12 +34,12 @@ angular.module("admin.products").factory "OptionValueNamer", (VariantUnitManager
return unit_name if count == undefined
unit_key = @unit_key(unit_name)
return unit_name unless unit_key
I18n.t(["unit_names", unit_key], {count: count, defaultValue: unit_name})
I18n.t(["inflections", unit_key], {count: count, defaultValue: unit_name})
unit_key: (unit_name) ->
unless I18n.unit_keys
I18n.unit_keys = {}
for key, translations of I18n.t("unit_names")
for key, translations of I18n.t("inflections")
for quantifier, translation of translations
I18n.unit_keys[translation.toLowerCase()] = key

View File

@@ -2721,7 +2721,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
have_an_account: "Already have an account?"
action_login: "Log in now."
# Most popular names used in variant_unit_name.
# Singular and plural forms of commonly used words.
# We use these entries to pluralize unit names in every language.
#
# Extracted with the following query:
@@ -2731,7 +2731,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
# puts " one: \"#{name}\""
# puts " other: \"#{name}s\"";
# }
unit_names:
inflections:
each:
one: "each"
other: "each"

View File

@@ -0,0 +1,65 @@
# frozen_string_literal: true
module OpenFoodNetwork
# Pluralize or singularize words.
#
# We store some inflection data in locales and use a reverse lookup of a word
# to find the plural or singular of the same word.
#
# Here is one example with a French user:
#
# - We have a product with the variant unit name "bouquet".
# - The I18n.locale is set to :fr.
# - The French locale contains:
# bunch:
# one: "bouquet"
# other: "bouquets"
# - We create a table containing:
# "bouquet" => "bunch"
# "bouquets" => "bunch"
# - Looking up "bouquet" gives us the I18n key "bunch".
# - We find the right plural by calling I18n:
#
# I18n.t("inflections.bunch", count: 2, default: "bouquet")
#
# - This returns the correct plural "bouquets".
# - It returns the original "bouquet" if the word is missing from the locale.
module I18nInflections
# Make this a singleton to cache lookup tables.
extend self
def pluralize(word, count)
return word if count.nil?
key = i18n_key(word)
return word unless key
I18n.t(key, scope: "inflections", count: count, default: word)
end
private
def i18n_key(word)
@lookup ||= {}
# The user may switch the locale. `I18n.t` is always using the current
# locale and we need a lookup table for each of them.
unless @lookup.key?(I18n.locale)
@lookup[I18n.locale] = build_i18n_key_lookup
end
@lookup[I18n.locale][word.downcase]
end
def build_i18n_key_lookup
lookup = {}
I18n.t("inflections")&.each do |key, translations|
translations.values.each do |translation|
lookup[translation.downcase] = key
end
end
lookup
end
end
end

View File

@@ -1,3 +1,7 @@
# frozen_string_literal: true
require "open_food_network/i18n_inflections"
module OpenFoodNetwork
class OptionValueNamer
def initialize(variant = nil)
@@ -73,37 +77,7 @@ module OpenFoodNetwork
end
def pluralize(unit_name, count)
I18nUnitNames.instance.pluralize(unit_name, count)
end
# Provides efficient access to unit name inflections.
# The singleton property ensures that the init code is run once only.
# The OptionValueNamer is instantiated in loops.
class I18nUnitNames
include Singleton
def pluralize(unit_name, count)
return unit_name if count.nil?
@unit_keys ||= unit_key_lookup
key = @unit_keys[unit_name.downcase]
return unit_name unless key
I18n.t(key, scope: "unit_names", count: count, default: unit_name)
end
private
def unit_key_lookup
lookup = {}
I18n.t("unit_names").each do |key, translations|
translations.values.each do |translation|
lookup[translation.downcase] = key
end
end
lookup
end
I18nInflections.pluralize(unit_name, count)
end
end
end

View File

@@ -0,0 +1,40 @@
# frozen_string_literal: true
require 'spec_helper'
require 'open_food_network/i18n_inflections'
describe OpenFoodNetwork::I18nInflections do
let(:subject) { described_class }
it "returns the same word if no plural is known" do
expect(subject.pluralize("foo", 2)).to eq "foo"
end
it "finds the plural of a word" do
expect(subject.pluralize("bunch", 2)).to eq "bunches"
end
it "finds the singular of a word" do
expect(subject.pluralize("bunch", 1)).to eq "bunch"
end
it "ignores upper case" do
expect(subject.pluralize("Bunch", 2)).to eq "bunches"
end
it "switches locales" do
skip "French plurals not available yet"
I18n.with_locale(:fr) do
expect(subject.pluralize("bouquet", 2)).to eq "bouquets"
end
end
it "builds the lookup table once" do
# Cache the table:
subject.pluralize("bunch", 2)
# Expect only one call for the plural:
expect(I18n).to receive(:t).once.and_call_original
subject.pluralize("bunch", 2)
end
end