#--
# Copyright (c) 2010-2011 Michael Berkovich, tr8n.net
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
class Tr8n::Translation < ActiveRecord::Base
set_table_name :tr8n_translations
after_save :clear_cache
after_destroy :clear_cache
belongs_to :language, :class_name => "Tr8n::Language"
belongs_to :translation_key, :class_name => "Tr8n::TranslationKey"
belongs_to :translator, :class_name => "Tr8n::Translator"
has_many :translation_votes, :class_name => "Tr8n::TranslationVote", :dependent => :destroy
serialize :rules
alias :key :translation_key
alias :votes :translation_votes
# TODO: move this to config file
VIOLATION_INDICATOR = -10
def vote!(translator, score)
score = score.to_i
vote = Tr8n::TranslationVote.find_or_create(self, translator)
vote.update_attributes(:vote => score.to_i)
update_rank!
# update the translation key timestamp
key.touch
self.translator.update_rank!(language) if self.translator
# add the translator to the watch list
self.translator.update_attributes(:reported => true) if score < VIOLATION_INDICATOR
translator.voted_on_translation!(self)
translator.update_metrics!(language)
end
def update_rank!
update_attributes(:rank => Tr8n::TranslationVote.where(:translation_id => self.id).sum(:vote))
end
def reset_votes!(translator)
Tr8n::TranslationVote.delete_all("translation_id = #{self.id}")
vote!(translator, 1)
end
# TODO: move this stuff to decorators
def rank_style(rank)
Tr8n::Config.default_rank_styles.each do |range, color|
return color if range.include?(rank)
end
"color:grey"
end
# TODO: move this stuff to decorators
def rank_label
return "0" if rank.blank?
prefix = (rank > 0) ? "+" : ""
"#{prefix}#{rank}".html_safe
end
# populate language rules from the internal rules hash
def rules
return nil if super.nil? or super.empty?
@loaded_rules ||= begin
rulz = []
super.each do |rule|
[rule[:rule_id]].flatten.each do |rule_id|
language_rule = Tr8n::LanguageRule.by_id(rule_id)
rulz << rule.merge({:rule => language_rule}) if language_rule
end
end
rulz
end
end
# generates a hash of token => rule_id
# TODO: is this still being used?
# Warning: same token can have multiple rules in a single translation
def rules_hash
return nil if rules.nil? or rules.empty?
@rules_hash ||= begin
rulz = {}
rules.each do |rule|
rulz[rule[:token]] = rule[:rule_id]
end
rulz
end
end
# generates the hash without rule ids, but with full definitions
def rules_api_hash
@rules_api_hash ||= (rules || []).collect{|rule_hash| rule_hash[:rule].to_api_hash.merge(:token => rule_hash[:token])}
end
# serilaize translation to API hash to be used for synchronization
def to_api_hash
{:locale => language.locale, :label => label, :rank => rank, :rules => rules_api_hash}
end
# create translation from API hash for a specific key
def self.create_from_api_hash(tkey, translator, hash, opts = {})
return if hash[:label].blank? # don't add empty translations
lang = Tr8n::Language.for(hash[:locale])
return unless lang # don't add translations for an unsupported language
tkey.translations.each do |trn|
# if an identical translation exists, don't add it
return if trn.to_api_hash == hash
end
# generate rules for the translation
rules = nil
if hash[:rules].any?
hash[:rules].each do |rule_hash|
return unless rule_hash[:token] and rule_hash[:type] and rule_hash[:definition]
rule = Tr8n::LanguageRule.for_definition(lang, translator, rule_hash[:type], rule_hash[:definition], opts)
return unless rule # if the rule has not been created, we should not even add the translation
rules << {:token => rule_hash[:token], :rule_id => rule.id}
end
end
tkey.add_translation(hash[:label], rules, lang, translator)
end
# deprecated - api_hash should be used instead
def rules_definitions
return nil if rules.nil? or rules.empty?
@rules_definitions ||= begin
rulz = {}
rules.each do |rule|
rulz[rule[:token].clone] = rule[:rule].to_hash
end
rulz
end
end
# TODO: move to decorators
def context
return nil if rules.nil? or rules.empty?
@context ||= begin
context_rules = []
rules.each do |rule|
context_rules << "#{rule[:token]} #{rule[:rule].description}"
end
context_rules.join(" and ").html_safe
end
end
# checks if the translation is valid for the given tokens
def matches_rules?(token_values)
return true if rules.nil? # doesn't have any rules
return false if rules.empty? # had some rules that have been removed
rules.each do |rule|
token_value = token_values[rule[:token].to_sym]
token_value = token_value.first if token_value.is_a?(Array)
result = rule[:rule].evaluate(token_value)
return false unless result
end
true
end
# used by the permutation generator
def matches_rule_definitions?(new_rules_hash)
rules_hash == new_rules_hash
end
def self.default_translation(translation_key, language, translator)
trans = where("translation_key_id = ? and language_id = ? and translator_id = ? and rules is null", translation_key.id, language.id, translator.id).order("rank desc").first
trans ||= new(:translation_key => translation_key, :language => language, :translator => translator, :label => translation_key.sanitized_label)
trans
end
def blank?
self.label.blank?
end
def uniq?
# for now, treat all translations as uniq
return true
trns = self.class.where("translation_key_id = ? and language_id = ? and label = ?", translation_key.id, language.id, label)
trns = trns.where("id <> ?", self.id) if self.id
trns.count == 0
end
def clean?
language.clean_sentence?(label)
end
def can_be_edited_by?(editor)
return false if translation_key.locked?
translator == editor
end
def can_be_deleted_by?(editor)
return false if translation_key.locked?
return true if editor.manager?
translator == editor
end
def save_with_log!(translator)
if self.id
translator.updated_translation!(self) if changed?
else
translator.added_translation!(self)
end
save
end
def destroy_with_log!(translator)
translator.deleted_translation!(self)
destroy
end
def clear_cache
Tr8n::Cache.delete("translations_#{language.locale}_#{translation_key.key}")
translation_key.update_translation_count!
end
###############################################################
## Search Related Stuff
###############################################################
def self.filter_status_options
[["all translations", "all"],
["accepted translations", "accepted"],
["pending translations", "pending"],
["rejected translations", "rejected"]].collect{|option| [option.first.trl("Translation filter status option"), option.last]}
end
def self.filter_submitter_options
[["anyone", "anyone"],
["me", "me"]].collect{|option| [option.first.trl("Translation filter submitter option"), option.last]}
end
def self.filter_date_options
[["any date", "any"],
["today", "today"],
["yesterday", "yesterday"],
["in the last week", "last_week"]].collect{|option| [option.first.trl("Translation filter date option"), option.last]}
end
def self.filter_order_by_options
[["date", "date"],
["rank", "rank"]].collect{|option| [option.first.trl("Translation filter order by option"), option.last]}
end
def self.filter_group_by_options
[["nothing", "nothing"],
["translator", "translator"],
["context rule", "context"],
["rank", "rank"],
["date", "date"]].collect{|option| [option.first.trl("Translation filter group by option"), option.last]}
end
def self.for_params(params, language = Tr8n::Config.current_language)
results = self.where("language_id = ?", language.id)
# ensure that only allowed translations are visible
results = results.where("translation_key_id in (select id from tr8n_translation_keys where level <= ?)", Tr8n::Config.current_translator.level)
results = results.where("label like ?", "%#{params[:search]}%") unless params[:search].blank?
if params[:with_status] == "accepted"
results = results.where("rank >= ?", Tr8n::Config.translation_threshold)
elsif params[:with_status] == "pending"
results = results.where("rank >= 0 and rank < ?", Tr8n::Config.translation_threshold)
elsif params[:with_status] == "rejected"
results = results.where("rank < 0")
end
if params[:submitted_by] == "me"
results = results.where("translator_id = ?", Tr8n::Config.current_translator.id)
end
if params[:submitted_on] == "today"
date = Date.today
results = results.where("created_at >= ? and created_at < ?", date, date + 1.day)
elsif params[:submitted_on] == "yesterday"
date = Date.today - 1.days
results = results.where("created_at >= ? and created_at < ?", date, date + 1.day)
elsif params[:submitted_on] == "last_week"
date = Date.today - 7.days
results = results.where("created_at >= ? and created_at < ?", date, Date.today)
end
results
end
end