Skip to content

Commit

Permalink
Phew! Refactored to support returning arrays ... just like the YAML v…
Browse files Browse the repository at this point in the history
…ersion.
  • Loading branch information
Shane Mingins authored and Shane Mingins committed Mar 25, 2009
1 parent 7fef5a4 commit 6504231
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 81 deletions.
2 changes: 1 addition & 1 deletion lib/controllers/translations_controller.rb
Expand Up @@ -6,7 +6,7 @@ class TranslationsController < ActionController::Base
# GET /translations
# GET /translations.xml
def index
@translations = @locale.translations.find(:all)
@translations = @locale.translations.find(:all, :order => "raw_key, pluralization_index")

respond_to do |format|
format.html # index.html.erb
Expand Down
87 changes: 62 additions & 25 deletions lib/i18n_backend_database/database.rb
Expand Up @@ -40,34 +40,36 @@ def translate(locale, key, options = {})

options[:scope] = [options[:scope]] unless options[:scope].is_a?(Array) || options[:scope].blank?
key = "#{options[:scope].join('.')}.#{key}".to_sym if options[:scope] && key.is_a?(Symbol)
count = (options[:count].nil? || options[:count] == 1) ? 1 : 0

count = options[:count]
# pull out values for interpolation
values = options.reject { |name, value| [:scope, :default].include?(name) }

translation = lookup(@locale, key, count)
entry = lookup(@locale, key)

unless translation || @locale.default_locale?
default_locale_translation = lookup(Locale.default_locale, key, count)
translation = @locale.create_translation(key, key, count) if default_locale_translation
# if no entry exists for the current locale and the current locale is not the default locale then lookup translations for the default locale for this key
unless entry || @locale.default_locale?
entry = use_and_copy_default_locale_translations_if_they_exist(@locale, key)
end

# if we have no translation and some defaults ... start looking them up
unless key.is_a?(String) || translation || options[:default].blank?
# if we have no entry and some defaults ... start looking them up
unless key.is_a?(String) || entry || options[:default].blank?
default = options[:default].is_a?(Array) ? options[:default].shift : options.delete(:default)
return translate(@locale.code, default, options.dup)
end

# we check the database before creating a translation as we can have translations with nil values
# if we still have no blasted translation just go and create one for the current locale!
unless translation
translation = @locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), count) ||
@locale.create_translation(key, key, count)
unless entry
pluralization_index = (options[:count].nil? || options[:count] == 1) ? 1 : 0
translation = @locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) ||
@locale.create_translation(key, key, pluralization_index)
entry = translation.value_or_default
end

value = translation.value_or_default(key)
@cache_store.write(Translation.ck(@locale, key, count), value)
interpolate(@locale.code, value, values)
@cache_store.write(Translation.ck(@locale, key), entry)
entry = pluralize(@locale, entry, count)
entry = interpolate(@locale.code, entry, values)
entry.is_a?(Array) ? entry.dup : entry # array's can get frozen with cache writes
end

# Acts the same as +strftime+, but returns a localized version of the
Expand All @@ -79,11 +81,12 @@ def localize(locale, object, format = :default)
type = object.respond_to?(:sec) ? 'time' : 'date'
format = translate(locale, "#{type}.formats.#{format.to_s}") unless format.to_s.index('%') # lookup keyed formats unless a custom format is passed

format.gsub!(/%a/, translate(locale, "date.abbr_day_names.#{object.wday}"))
format.gsub!(/%A/, translate(locale, "date.day_names.#{object.wday}"))
format.gsub!(/%b/, translate(locale, "date.abbr_month_names.#{object.mon}"))
format.gsub!(/%B/, translate(locale, "date.month_names.#{object.mon}"))
format.gsub!(/%p/, translate(locale, "time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour
format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday])
format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday])
format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon])
format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon])
format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour

object.strftime(format)
end

Expand Down Expand Up @@ -120,15 +123,49 @@ def locale_in_context(locale)
end

# lookup key in cache and db, if the db is hit the value is cached
def lookup(locale, key, count)
cache_key = Translation.ck(locale, key, count)
def lookup(locale, key)
cache_key = Translation.ck(locale, key)
if @cache_store.exist?(cache_key) && value = @cache_store.read(cache_key)
translation = Translation.new(:value => value)
return value
else
translations = locale.translations.find_all_by_key(Translation.hk(key))
case translations.size
when 0
value = nil
when 1
value = translations.first.value_or_default
else
value = translations.inject([]) do |values, t|
values[t.pluralization_index] = t.value_or_default
values
end
end

@cache_store.write(cache_key, (value.nil? ? nil : value))
return value
end
end

# looks up translations for the default locale, and if they exist untranslated records are created for the locale and the default locale values are returned
def use_and_copy_default_locale_translations_if_they_exist(locale, key)
default_locale_entry = lookup(Locale.default_locale, key)
return unless default_locale_entry

if default_locale_entry.is_a?(Array)
default_locale_entry.each_with_index do |entry, index|
locale.create_translation(key, nil, index) if entry
end
else
translation = locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), count)
@cache_store.write(cache_key, (translation.nil? ? nil : translation.value))
locale.create_translation(key, nil)
end
return translation

return default_locale_entry
end

def pluralize(locale, entry, count)
return entry unless entry.is_a?(Array) and count
count = count == 1 ? 1 : 0
entry.compact[count]
end

# Interpolates values into a given string.
Expand Down
33 changes: 19 additions & 14 deletions lib/i18n_util.rb
Expand Up @@ -23,7 +23,7 @@ def self.load_from_yml(file_name)

if value.is_a?(Array)
value.each_with_index do |v, index|
create_translation(locale, "#{key}.#{index}", pluralization_index, v.to_s) unless v.nil?
create_translation(locale, "#{key}", index, v) unless v.nil?
end
else
create_translation(locale, key, pluralization_index, value)
Expand All @@ -36,7 +36,10 @@ def self.load_from_yml(file_name)
# Finds or creates a translation record and updates the value
def self.create_translation(locale, key, pluralization_index, value)
translation = locale.translations.find_by_key_and_pluralization_index(Translation.hk(key), pluralization_index) # find existing record by hash key
translation = locale.translations.build(:key =>key, :pluralization_index => pluralization_index) unless translation # or build new one with raw key
unless translation # or build new one with raw key
translation = locale.translations.build(:key =>key, :pluralization_index => pluralization_index)
puts "from yaml create translation for #{locale.code} : #{key} : #{pluralization_index}" unless RAILS_ENV['test']
end
translation.value = value
translation.save!
end
Expand All @@ -62,17 +65,15 @@ def self.seed_application_translations
object = object[/'(.*?)'/, 1] || object[/"(.*?)"/, 1]
options = {}
interpolation_arguments.each { |arg| options[arg.to_sym] = nil }
next if object.nil?

begin
I18n.t(object, options) # default locale first
locales = Locale.available_locales
locales.delete(I18n.default_locale)
# translate for other locales
locales.each do |locale|
I18n.t(object, options.merge(:locale => locale))
end
rescue Exception => e
# ignore errors
puts "translating for #{object} with options #{options.inspect}" unless RAILS_ENV['test']
I18n.t(object, options) # default locale first
locales = Locale.available_locales
locales.delete(I18n.default_locale)
# translate for other locales
locales.each do |locale|
I18n.t(object, options.merge(:locale => locale))
end

end
Expand All @@ -89,15 +90,19 @@ def self.translated_objects(dir='app')
end
end
end
assets
assets.uniq
end

# Populate translation records from the default locale to other locales if no record exists.
def self.synchronize_translations
non_default_locales = Locale.non_defaults
Locale.default_locale.translations.each do |t|
non_default_locales.each do |locale|
locale.translations.create!(:key => t.key, :value => nil, :ignore_hash_key => true) unless locale.translations.exists?(:key => t.key, :pluralization_index => t.pluralization_index)
unless locale.translations.exists?(:key => t.key, :pluralization_index => t.pluralization_index)
value = t.value =~ /^---(.*)\n/ ? t.value : nil # well will copy across YAML, like symbols
locale.translations.create!(:key => t.raw_key, :value => value, :pluralization_index => t.pluralization_index)
puts "synchronizing has created translation for #{locale.code} : #{t.raw_key} : #{t.pluralization_index}" unless RAILS_ENV['test']
end
end
end
end
Expand Down
22 changes: 11 additions & 11 deletions lib/models/translation.rb
@@ -1,14 +1,12 @@
class Translation < ActiveRecord::Base
belongs_to :locale
validates_presence_of :key
before_create :generate_hash_key
before_validation_on_create :generate_hash_key
after_update :update_cache

named_scope :untranslated, :conditions => {:value => nil}
named_scope :translated, :conditions => "value IS NOT NULL"
named_scope :untranslated, :conditions => {:value => nil}, :order => :raw_key
named_scope :translated, :conditions => "value IS NOT NULL", :order => :raw_key

attr_accessor :ignore_hash_key

def default_locale_value(rescue_value='No default locale value')
begin
Locale.default_locale.translations.find_by_key_and_pluralization_index(self.key, self.pluralization_index).value
Expand All @@ -17,8 +15,9 @@ def default_locale_value(rescue_value='No default locale value')
end
end

def value_or_default(key)
self.value || self.default_locale_value(key)
def value_or_default
value = self.value || self.default_locale_value(self.raw_key)
value =~ /^---(.*)\n/ ? YAML.load(value) : value # supports using YAML e.g. order: [ :year, :month, :day ] values are stored as Symbols "--- :year\n", "--- :month\n", "--- :day\n"
end

# create hash key
Expand All @@ -27,18 +26,19 @@ def self.hk(key)
end

# create cache key
def self.ck(locale, key, pluralization_index, hash=true)
def self.ck(locale, key, hash=true)
key = self.hk(key) if hash
"#{locale.code}:#{key}:#{pluralization_index}"
"#{locale.code}:#{key}"
end

protected
def generate_hash_key
self.key = Translation.hk(key) unless ignore_hash_key
self.raw_key = key.to_s
self.key = Translation.hk(key)
end

def update_cache
new_cache_key = Translation.ck(self.locale, self.key, self.pluralization_index, false)
new_cache_key = Translation.ck(self.locale, self.key, false)
I18n.backend.cache_store.write(new_cache_key, self.value)
end
end
20 changes: 20 additions & 0 deletions lib/views/translations/edit.html.erb
@@ -1,5 +1,25 @@
<h1>Editing translation for <%= @locale.code %></h1>

<p>
<b>Key:</b>
<%=h @translation.key %>
</p>

<p>
<b>Raw Key:</b>
<%=h @translation.raw_key %>
</p>

<p>
<b>Pluralization Index:</b>
<%=h @translation.pluralization_index %>
</p>

<p>
<b>Default Locale Value:</b>
<%=h @translation.default_locale_value %>
</p>

<% form_for([@locale, @translation]) do |f| %>
<%= f.error_messages %>
Expand Down
2 changes: 2 additions & 0 deletions lib/views/translations/index.html.erb
Expand Up @@ -3,13 +3,15 @@
<table>
<tr>
<th>Key</th>
<th>Raw Key</th>
<th>Value</th>
<th>Default Locale Value</th>
</tr>

<% for translation in @translations %>
<tr>
<td><%=h translation.key %></td>
<td><%=h translation.raw_key %></td>
<td><%=h translation.value %></td>
<td><%=h translation.default_locale_value %></td>
<td><%= link_to 'Show', locale_translation_path(@locale, translation) %></td>
Expand Down
15 changes: 15 additions & 0 deletions lib/views/translations/show.html.erb
Expand Up @@ -8,6 +8,21 @@
<%=h @translation.key %>
</p>

<p>
<b>Raw Key:</b>
<%=h @translation.raw_key %>
</p>

<p>
<b>Pluralization Index:</b>
<%=h @translation.pluralization_index %>
</p>

<p>
<b>Default Locale Value:</b>
<%=h @translation.default_locale_value %>
</p>

<p>
<b>Value:</b>
<%=h @translation.value %>
Expand Down
40 changes: 20 additions & 20 deletions spec/caching_spec.rb
Expand Up @@ -29,26 +29,26 @@

@backend.translate("en", :"models.translation.attributes.locale.blank", options).should == "is blank moron!"

@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}:1").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.models.translation.blank")}:1").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.messages.blank")}:1").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.models.translation.blank")}").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.messages.blank")}").should be_true

@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}:1").should == nil
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.models.translation.blank")}:1").should == nil
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.messages.blank")}:1").should == "is blank moron!"
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}").should == nil
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.models.translation.blank")}").should == nil
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.messages.blank")}").should == "is blank moron!"
end

it "should update a cache record if the translation record changes" do
hash_key = Translation.hk("blah")
@backend.translate("en", "blah")
@backend.cache_store.read("en:#{hash_key}:1").should == "blah"
@backend.cache_store.read("en:#{hash_key}").should == "blah"

translation = @english_locale.translations.find_by_key(Translation.hk("blah"))
translation.value.should == "blah"

translation.update_attribute(:value, "foo")
translation.value.should == "foo"
@backend.cache_store.read("en:#{hash_key}:1").should == "foo"
@backend.cache_store.read("en:#{hash_key}").should == "foo"
end
end

Expand All @@ -66,21 +66,21 @@

@backend.translate("es", :"models.translation.attributes.locale.blank", options).should == "is blank moron!"

@backend.cache_store.exist?("es:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}:1").should be_true
@backend.cache_store.exist?("es:#{Translation.hk("activerecord.errors.models.translation.blank")}:1").should be_true
@backend.cache_store.exist?("es:#{Translation.hk("activerecord.errors.messages.blank")}:1").should be_true
@backend.cache_store.exist?("es:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}").should be_true
@backend.cache_store.exist?("es:#{Translation.hk("activerecord.errors.models.translation.blank")}").should be_true
@backend.cache_store.exist?("es:#{Translation.hk("activerecord.errors.messages.blank")}").should be_true

@backend.cache_store.read("es:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}:1").should == nil
@backend.cache_store.read("es:#{Translation.hk("activerecord.errors.models.translation.blank")}:1").should == nil
@backend.cache_store.read("es:#{Translation.hk("activerecord.errors.messages.blank")}:1").should == "is blank moron!"
@backend.cache_store.read("es:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}").should == nil
@backend.cache_store.read("es:#{Translation.hk("activerecord.errors.models.translation.blank")}").should == nil
@backend.cache_store.read("es:#{Translation.hk("activerecord.errors.messages.blank")}").should == "is blank moron!"

@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}:1").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.models.translation.blank")}:1").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.messages.blank")}:1").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.models.translation.blank")}").should be_true
@backend.cache_store.exist?("en:#{Translation.hk("activerecord.errors.messages.blank")}").should be_true

@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}:1").should == nil
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.models.translation.blank")}:1").should == nil
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.messages.blank")}:1").should == "is blank moron!"
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.models.translation.attributes.locale.blank")}").should == nil
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.models.translation.blank")}").should == nil
@backend.cache_store.read("en:#{Translation.hk("activerecord.errors.messages.blank")}").should == "is blank moron!"
end
end
end
Expand Down

0 comments on commit 6504231

Please sign in to comment.