diff --git a/.gitignore b/.gitignore index 7933bee6..8b046dce 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ Thumbs.db .tmp* tmp/ local/tr8n_server/db/*.sqlite3 +local/tr8n_server/db/test.sqlite3 local/tr8n_server/log/*.log local/tr8n_server/tmp/ -local/tr8n_server/.sass-cache/ \ No newline at end of file +local/tr8n_server/.sass-cache/ diff --git a/app/models/tr8n/language.rb b/app/models/tr8n/language.rb index c9974db3..c7ee0893 100644 --- a/app/models/tr8n/language.rb +++ b/app/models/tr8n/language.rb @@ -36,25 +36,33 @@ class Tr8n::Language < ActiveRecord::Base has_many :translation_key_locks, :class_name => 'Tr8n::TranslationKeyLock', :dependent => :destroy has_many :language_metrics, :class_name => 'Tr8n::LanguageMetric' - def self.find_or_create(lcl, english_name) - find_by_locale(lcl) || create(:locale => lcl, :english_name => english_name) + def self.cache_key(locale) + "language_#{locale}" + end + + def cache_key + self.class.cache_key(locale) end def self.for(locale) return nil if locale.nil? - Tr8n::Cache.fetch("language_#{locale}") do + Tr8n::Cache.fetch(cache_key(locale)) do find_by_locale(locale) end end + def self.find_or_create(lcl, english_name) + find_by_locale(lcl) || create(:locale => lcl, :english_name => english_name) + end + def rules - Tr8n::Cache.fetch("language_rules_#{id}") do + Tr8n::Cache.fetch("language_rules_#{locale}") do language_rules end end def cases - Tr8n::Cache.fetch("language_cases_#{id}") do + Tr8n::Cache.fetch("language_cases_#{locale}") do language_cases end end @@ -64,6 +72,7 @@ def reset! reset_language_cases! end + # reloads rules for the language from the yml file def reset_language_rules! rules.delete_all Tr8n::Config.language_rule_classes.each do |rule_class| @@ -73,6 +82,7 @@ def reset_language_rules! end end + # reloads language cases for the language from the yml file def reset_language_cases! cases.delete_all Tr8n::Config.default_language_cases_for(locale).each do |lcase| @@ -308,7 +318,9 @@ def translations_changed! end def update_cache - Tr8n::Cache.delete("language_#{locale}") + Tr8n::Cache.delete(cache_key) + Tr8n::Cache.delete("language_rules_#{locale}") + Tr8n::Cache.delete("language_cases_#{locale}") Tr8n::Cache.delete("featured_languages") Tr8n::Cache.delete("enabled_languages") end diff --git a/app/models/tr8n/numeric_rule.rb b/app/models/tr8n/numeric_rule.rb index 742bb77f..4f7aed39 100644 --- a/app/models/tr8n/numeric_rule.rb +++ b/app/models/tr8n/numeric_rule.rb @@ -52,8 +52,13 @@ def self.number_token_value(token) token.send(Tr8n::Config.rules_engine[:numeric_rule][:object_method]) end - def number_token_value(token) - self.class.number_token_value(token) + def self.sanitize_values(values) + return [] unless values + values.split(",").collect{|val| val.strip} + end + + def self.humanize_values(values) + sanitize_values(values).join(", ") end # FORM: [object, singular, plural] @@ -89,30 +94,7 @@ def self.default_transform(*args) args[0].pluralize end - def evaluate(token) - token_value = number_token_value(token) - return false unless token_value - - result1 = evaluate_partial_rule(token_value.to_s, definition[:part1].to_sym, sanitize_values(definition[:value1])) - return result1 unless definition[:multipart].to_s == "true" - - result2 = evaluate_partial_rule(token_value.to_s, definition[:part2].to_sym, sanitize_values(definition[:value2])) - return (result1 or result2) if definition[:operator] == "or" - return (result1 and result2) - - false - end - - def sanitize_values(values) - return [] unless values - values.split(",").collect{|val| val.strip} - end - - def humanize_values(values) - sanitize_values(values).join(", ") - end - - def evaluate_partial_rule(token_value, name, values) + def self.evaluate_rule_fragment(token_value, name, values) if name == :is return true if values.include?(token_value) return false @@ -140,6 +122,32 @@ def evaluate_partial_rule(token_value, name, values) false end + def evaluate(token) + token_value = number_token_value(token) + return false unless token_value + + result1 = self.class.evaluate_rule_fragment(token_value.to_s, definition[:part1].to_sym, sanitize_values(definition[:value1])) + return result1 unless definition[:multipart].to_s == "true" + + result2 = self.class.evaluate_rule_fragment(token_value.to_s, definition[:part2].to_sym, sanitize_values(definition[:value2])) + return (result1 or result2) if definition[:operator] == "or" + return (result1 and result2) + + false + end + + def number_token_value(token) + self.class.number_token_value(token) + end + + def sanitize_values(values) + self.class.sanitize_values(values) + end + + def humanize_values(values) + self.class.humanize_values(values) + end + def to_hash { :type => self.class.dependency, :multipart => definition[:multipart], :operator => definition[:operator], @@ -148,7 +156,6 @@ def to_hash } end - # used to describe a context of a given translation def description rule_desc = describe_partial_rule(definition[:part1].to_sym, definition[:value1]) diff --git a/spec/models/tr8n/gender_rule_spec.rb b/spec/models/tr8n/gender_rule_spec.rb index a0a951f8..bee6a374 100644 --- a/spec/models/tr8n/gender_rule_spec.rb +++ b/spec/models/tr8n/gender_rule_spec.rb @@ -1,5 +1,141 @@ require File.expand_path('../../spec_helper', File.dirname(__FILE__)) describe Tr8n::GenderRule do - + before :all do + @lang = Tr8n::Language.create(:locale => "elb", :english_name => "Elbonian") + end + + after :all do + @lang.destroy + end + + describe 'class methods' do + it 'should respec configuration settings' do + Tr8n::Config.stub!(:rules_engine).and_return({ + :gender_rule => { + token_suffixes: ["user", "actor", "target"], + object_method: "gender", + method_values: { + female: "f", + male: "m", + neutral: "n", + unknown: "u" + } + } + }) + + Tr8n::GenderRule.dependency.should eq("gender") + Tr8n::GenderRule.suffixes.should eq(["user", "actor", "target"]) + + Tr8n::GenderRule.gender_object_value_for(:female).should eq("f") + Tr8n::GenderRule.gender_object_value_for(:male).should eq("m") + Tr8n::GenderRule.gender_object_value_for(:neutral).should eq("n") + Tr8n::GenderRule.gender_object_value_for(:unknown).should eq("u") + + obj = mock("object_with_gender") + obj.should_receive(:gender).and_return("m") + Tr8n::GenderRule.gender_token_value(obj).should eq("m") + end + + describe 'default transform without token value' do + it 'should always use a musculine form' do + Tr8n::GenderRule.default_transform("he").should eq("he") + Tr8n::GenderRule.default_transform("he", "she").should eq("he") + Tr8n::GenderRule.default_transform("his", "her").should eq("his") + end + end + + describe 'transform with a token value' do + it 'should return the form based on the token value' do + male = mock("male") + male.stub!(:gender).and_return("male") + female = mock("male") + female.stub!(:gender).and_return("female") + unknwon = mock("unknwon") + unknwon.stub!(:gender).and_return("unknwon") + + Tr8n::GenderRule.transform(male, "registered on").should eq("registered on") + Tr8n::GenderRule.transform(male, "he", "she").should eq("he") + Tr8n::GenderRule.transform(male, "his", "her").should eq("his") + Tr8n::GenderRule.transform(male, "he", "she", "he/she").should eq("he") + + Tr8n::GenderRule.transform(female, "registered on").should eq("registered on") + Tr8n::GenderRule.transform(female, "he", "she").should eq("she") + Tr8n::GenderRule.transform(female, "his", "her").should eq("her") + Tr8n::GenderRule.transform(female, "he", "she", "he/she").should eq("she") + + Tr8n::GenderRule.transform(unknwon, "registered on").should eq("registered on") + Tr8n::GenderRule.transform(unknwon, "he", "she").should eq("he/she") + Tr8n::GenderRule.transform(unknwon, "his", "her").should eq("his/her") + Tr8n::GenderRule.transform(unknwon, "he", "she", "he/she").should eq("he/she") + end + end + end + + describe 'instance methods' do + describe 'evaluate rule' do + it 'should return results based on gender' do + male = mock("male") + male.stub!(:gender).and_return("male") + female = mock("male") + female.stub!(:gender).and_return("female") + unknwon = mock("unknwon") + unknwon.stub!(:gender).and_return("unknwon") + + definition = {operator: "is", value: "male"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.evaluate(male).should be_true + rule.evaluate(female).should be_false + rule.evaluate(unknwon).should be_false + + definition = {operator: "is_not", value: "male"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.evaluate(male).should be_false + rule.evaluate(female).should be_true + rule.evaluate(unknwon).should be_true + + definition = {operator: "is", value: "female"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.evaluate(male).should be_false + rule.evaluate(female).should be_true + rule.evaluate(unknwon).should be_false + + definition = {operator: "is_not", value: "female"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.evaluate(male).should be_true + rule.evaluate(female).should be_false + rule.evaluate(unknwon).should be_true + end + end + + describe 'hashing a rule' do + it 'should produce a correct hash' do + definition = {operator: "is", value: "male"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.to_hash.should eq({:type=>"gender", :operator=>"is", :value=>"male"}) + end + end + + describe 'describing a rule' do + it 'should produce a correct description' do + definition = {operator: "is", value: "male"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.description.should eq("is a male") + + definition = {operator: "is", value: "unknown"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.description.should eq("has an unknown gender") + + definition = {operator: "is_not", value: "female"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.description.should eq("is not a female") + + definition = {operator: "is_not", value: "unknown"} + rule = Tr8n::GenderRule.create(:language => @lang, :definition => definition) + rule.description.should eq("does not have an unknown gender") + end + end + + end + end diff --git a/spec/models/tr8n/language_spec.rb b/spec/models/tr8n/language_spec.rb index e0263f09..952af278 100644 --- a/spec/models/tr8n/language_spec.rb +++ b/spec/models/tr8n/language_spec.rb @@ -1,6 +1,13 @@ require File.expand_path('../../spec_helper', File.dirname(__FILE__)) describe Tr8n::Language do + describe 'cache key' do + it 'shold use locale' do + lang = Tr8n::Language.find_or_create('test', 'Test Language') + lang.cache_key.should eq("language_test") + end + end + describe 'finding or creating a new language' do context 'none existing language' do it 'should not be found' do @@ -67,4 +74,6 @@ end end + + end \ No newline at end of file diff --git a/spec/models/tr8n/numeric_rule_spec.rb b/spec/models/tr8n/numeric_rule_spec.rb index a715b273..46c747e5 100644 --- a/spec/models/tr8n/numeric_rule_spec.rb +++ b/spec/models/tr8n/numeric_rule_spec.rb @@ -1,5 +1,162 @@ require File.expand_path('../../spec_helper', File.dirname(__FILE__)) describe Tr8n::NumericRule do - + before :all do + @lang = Tr8n::Language.create(:locale => "elb", :english_name => "Elbonian") + end + + after :all do + @lang.destroy + end + + describe 'class methods' do + it 'should respec configuration settings' do + Tr8n::Config.stub!(:rules_engine).and_return({ + :numeric_rule => { + token_suffixes: ["count", "num"], + object_method: "to_i" + } + }) + + Tr8n::NumericRule.dependency.should eq("number") + Tr8n::NumericRule.suffixes.should eq(["count", "num"]) + Tr8n::NumericRule.number_token_value(5).should eq(5) + + obj = mock("numeric_object") + obj.should_receive(:to_i).and_return(42) + Tr8n::NumericRule.number_token_value(obj).should eq(42) + end + + describe 'default transform without token value' do + it 'should return the pluralized form of the noun' do + Tr8n::NumericRule.default_transform("car", "cars").should eq("cars") + Tr8n::NumericRule.default_transform("car").should eq("cars") + end + end + + describe 'transform with a token value' do + it 'should return the form based on the token value' do + Tr8n::NumericRule.transform(1, "person", "people").should eq("person") + Tr8n::NumericRule.transform(2, "person", "people").should eq("people") + Tr8n::NumericRule.transform(2, "car").should eq("cars") + end + end + + describe 'sanitize values' do + it 'should strip values' do + Tr8n::NumericRule.sanitize_values("1, 2, 3 , 4 ").should eq(["1","2","3","4"]) + Tr8n::NumericRule.sanitize_values("1,2,3,4").should eq(["1","2","3","4"]) + end + end + + describe 'humanize values' do + it 'should strip values' do + Tr8n::NumericRule.humanize_values("1, 2, 3 , 4 ").should eq("1, 2, 3, 4") + Tr8n::NumericRule.humanize_values("1,2,3,4").should eq("1, 2, 3, 4") + end + end + + describe 'evaluating a rule fragment' do + it 'should return correct results' do + Tr8n::NumericRule.evaluate_rule_fragment(5, :is, [5]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(5, :is, [2,3,5]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(5, :is, [4]).should be_false + + Tr8n::NumericRule.evaluate_rule_fragment(5, :is_not, [4]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(5, :is_not, [4,2,3]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(5, :is_not, [5]).should be_false + + Tr8n::NumericRule.evaluate_rule_fragment(5, :ends_in, [5]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(25, :ends_in, [5]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(25, :ends_in, [2,3,4,5]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(5, :ends_in, [2]).should be_false + Tr8n::NumericRule.evaluate_rule_fragment(5, :ends_in, [2,3,4]).should be_false + + Tr8n::NumericRule.evaluate_rule_fragment(5, :does_not_end_in, [2,3,4]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(25, :does_not_end_in, [2,4]).should be_true + Tr8n::NumericRule.evaluate_rule_fragment(25, :does_not_end_in, [2,5]).should be_false + Tr8n::NumericRule.evaluate_rule_fragment(25, :does_not_end_in, [5]).should be_false + end + end + end + + describe 'instance methods' do + describe 'creating a rule' do + it 'should create a rule object' do + definition = {multipart: false, part1: "is", value1: "1"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.should be_a(Tr8n::NumericRule) + end + end + + describe 'evaluating a simple rule' do + it 'should return correct results' do + definition = {multipart: false, part1: "is", value1: "1,2,3,4"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.evaluate(5).should be_false + rule.evaluate(1).should be_true + + definition = {multipart: false, part1: "is_not", value1: "2,3,4,5"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.evaluate(5).should be_false + rule.evaluate(1).should be_true + + definition = {multipart: false, part1: "ends_in", value1: "2,3,4,5"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.evaluate(25).should be_true + rule.evaluate(1).should be_false + + definition = {multipart: false, part1: "does_not_end_in", value1: "2,3,4,5"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.evaluate(25).should be_false + rule.evaluate(1).should be_true + end + end + + describe 'evaluating multipart rule' do + it 'should return correct results' do + definition = {multipart: true, part1: "ends_in", value1: "1", operator: "and", part2: "does_not_end_in", value2: "11"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.evaluate(1).should be_true + rule.evaluate(21).should be_true + rule.evaluate(231).should be_true + rule.evaluate(1021).should be_true + rule.evaluate(2).should be_false + rule.evaluate(11).should be_false + rule.evaluate(211).should be_false + rule.evaluate(1011).should be_false + + definition = {multipart: true, part1: "ends_in", value1: "2,3,4", operator: "and", part2: "does_not_end_in", value2: "12,13,14"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.evaluate(2).should be_true + rule.evaluate(1023).should be_true + rule.evaluate(34).should be_true + rule.evaluate(1013).should be_false + rule.evaluate(14).should be_false + end + end + + describe 'hashing a rule' do + it 'should produce a correct hash' do + definition = {multipart: true, part1: "ends_in", value1: "2,3,4", operator: "and", part2: "does_not_end_in", value2: "12,13,14"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.to_hash.should eq({:type=>"number", :multipart=>true, :operator=>"and", :part1=>"ends_in", + :value1=>"2,3,4", :part2=>"does_not_end_in", :value2=>"12,13,14"}) + end + end + + describe 'describing a rule' do + it 'should produce a correct description' do + definition = {multipart: true, part1: "ends_in", value1: "2,3,4", operator: "and", part2: "does_not_end_in", value2: "12,13,14"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.description.should eq("ends in 2, 3, 4, but not in 12, 13, 14") + + definition = {multipart: false, part1: "does_not_end_in", value1: "2,3,4,5"} + rule = Tr8n::NumericRule.create(:language => @lang, :definition => definition) + rule.description.should eq("does not end in 2, 3, 4, 5") + end + end + + end + end diff --git a/spec/models/tr8n/translation_key_spec.rb b/spec/models/tr8n/translation_key_spec.rb index e1f0eada..f7326a82 100644 --- a/spec/models/tr8n/translation_key_spec.rb +++ b/spec/models/tr8n/translation_key_spec.rb @@ -1,7 +1,7 @@ require File.expand_path('../../spec_helper', File.dirname(__FILE__)) describe Tr8n::TranslationKey do - describe '#creation' do + describe 'creation' do before :all do @user = User.create(:first_name => "Mike", :gender => "male") @@ -57,11 +57,62 @@ key.translate(@english, :user => [@user, lambda{|user| user.name}]).should == "Dear Mike" key.translate(@english, :user => [@user, lambda{|user, tom| "#{user.name} and #{tom}"}, "Tom"]).should == "Dear Mike and Tom" - # key = Tr8n::TranslationKey.find_or_create("{user:gender} updated {user:gender|his,her} profile") - # key.translate(@english, {:user => @user}).should == "Mike updated his profile" - # key.translate(@english, {:user => @user2}).should == "Anna updated her profile" + key = Tr8n::TranslationKey.find_or_create("{user:gender} updated {user:gender|his,her} profile") + key.translate(@english, {:user => @user}).should == "Mike updated his profile" + key.translate(@english, {:user => @user2}).should == "Anna updated her profile" end end - + + describe "translating labels into a foreign language" do + context "labels with no rules" do + it "should return correct translations" do + key = Tr8n::TranslationKey.find_or_create("Hello World") + key.add_translation("Privet Mir", nil, @russian, @translator) + key.translate(@russian).should eq("Privet Mir") + + key = Tr8n::TranslationKey.find_or_create("Hello {user}") + key.add_translation("Privet {user}", nil, @russian, @translator) + key.translate(@russian, {:user => @user}).should eq("Privet Mike") + + key = Tr8n::TranslationKey.find_or_create("You have {count} messages.") + key.add_translation("U vas est {count} soobshenii.", nil, @russian, @translator) + key.translate(@russian, {:count => 5}).should eq("U vas est 5 soobshenii.") + end + end + + context "labels with numeric rules" do + it "should return correct translations" do + + definition = {multipart: true, part1: "ends_in", value1: "1", operator: "and", part2: "does_not_end_in", value2: "11"} + rule1 = Tr8n::NumericRule.create(:language => @russian, :definition => definition) + + definition = {multipart: true, part1: "ends_in", value1: "2,3,4", operator: "and", part2: "does_not_end_in", value2: "12,13,14"} + rule2 = Tr8n::NumericRule.create(:language => @russian, :definition => definition) + + definition = {multipart: false, part1: "ends_in", value1: "0,5,6,7,8,9,11,12,13,14"} + rule3 = Tr8n::NumericRule.create(:language => @russian, :definition => definition) + + key = Tr8n::TranslationKey.find_or_create("You have {count||message}.") + key.add_translation("U vas est {count} soobshenie.", [{:token=>"count", :rule_id=>[rule1.id]}], @russian, @translator) + key.add_translation("U vas est {count} soobsheniya.", [{:token=>"count", :rule_id=>[rule2.id]}], @russian, @translator) + key.add_translation("U vas est {count} soobshenii.", [{:token=>"count", :rule_id=>[rule3.id]}], @russian, @translator) + + key.translate(@russian, {:count => 1}).should eq("U vas est 1 soobshenie.") + key.translate(@russian, {:count => 101}).should eq("U vas est 101 soobshenie.") + key.translate(@russian, {:count => 11}).should eq("U vas est 11 soobshenii.") + key.translate(@russian, {:count => 111}).should eq("U vas est 111 soobshenii.") + + key.translate(@russian, {:count => 5}).should eq("U vas est 5 soobshenii.") + key.translate(@russian, {:count => 26}).should eq("U vas est 26 soobshenii.") + key.translate(@russian, {:count => 106}).should eq("U vas est 106 soobshenii.") + + key.translate(@russian, {:count => 3}).should eq("U vas est 3 soobsheniya.") + key.translate(@russian, {:count => 13}).should eq("U vas est 13 soobshenii.") + key.translate(@russian, {:count => 23}).should eq("U vas est 23 soobsheniya.") + key.translate(@russian, {:count => 103}).should eq("U vas est 103 soobsheniya.") + end + end + end + end end