Permalink
Browse files

Big refactoring towards version 0.2.0. Stripping off all features tha…

…t have been duplicated in I18n. Only model translations related features will remain in Globalize2.
  • Loading branch information...
1 parent 4c466db commit f49d8e13d3866ab0956e14e193cfbaf5759b27ab Sven Fuchs committed Nov 15, 2009
Showing with 621 additions and 1,823 deletions.
  1. +13 −13 README.textile
  2. +3 −3 generators/templates/db_backend_migration.rb
  3. +15 −0 lib/globalize.rb
  4. +155 −0 lib/globalize/active_record.rb
  5. +86 −0 lib/globalize/active_record/adapter.rb
  6. +25 −0 lib/globalize/active_record/attributes.rb
  7. +40 −0 lib/globalize/active_record/migration.rb
  8. +0 −102 lib/globalize/backend/chain.rb
  9. +0 −37 lib/globalize/backend/pluralizing.rb
  10. +0 −61 lib/globalize/backend/static.rb
  11. +0 −63 lib/globalize/load_path.rb
  12. +0 −63 lib/globalize/locale/fallbacks.rb
  13. +0 −81 lib/globalize/locale/language_tag.rb
  14. +0 −56 lib/globalize/model/active_record.rb
  15. +0 −100 lib/globalize/model/active_record/adapter.rb
  16. +0 −174 lib/globalize/model/active_record/translated.rb
  17. +0 −32 lib/globalize/translation.rb
  18. 0 lib/{globalize → }/i18n/missing_translations_log_handler.rb
  19. 0 lib/{globalize → }/i18n/missing_translations_raise_handler.rb
  20. +0 −3 lib/locale/root.yml
  21. +0 −40 lib/rails_edge_load_path_patch.rb
  22. +5 −5 notes.textile
  23. +102 −0 test/active_record/fallbacks_test.rb
  24. +21 −26 test/{model → }/active_record/migration_test.rb
  25. +4 −30 test/{model → }/active_record/sti_translated_test.rb
  26. +38 −0 test/active_record/translates_test.rb
  27. +30 −0 test/active_record/translation_class_test.rb
  28. +43 −124 test/{model/active_record/translated_test.rb → active_record_test.rb}
  29. +0 −175 test/backends/chained_test.rb
  30. +0 −63 test/backends/pluralizing_test.rb
  31. +0 −147 test/backends/static_test.rb
  32. +0 −2 test/data/locale/all.yml
  33. +0 −2 test/data/locale/de-DE.yml
  34. +0 −2 test/data/locale/en-US.yml
  35. +0 −2 test/data/locale/en-US/module.yml
  36. +0 −2 test/data/locale/fi-FI/module.yml
  37. 0 test/data/locale/root.yml
  38. +1 −0 test/data/models.rb
  39. +7 −7 test/data/schema.rb
  40. +6 −6 test/i18n/missing_translations_test.rb
  41. +0 −49 test/load_path_test.rb
  42. +0 −154 test/locale/fallbacks_test.rb
  43. +0 −130 test/locale/language_tag_test.rb
  44. +27 −15 test/test_helper.rb
  45. +0 −54 test/translation_test.rb
View
@@ -1,10 +1,10 @@
h1. Globalize2
-Globalize2 is the successor of Globalize for Rails.
+Globalize2 is the successor of Globalize for Rails.
It is compatible with and builds on the new "I18n api in Ruby on Rails":http://rails-i18n.org. and adds model translations as well as a bunch of other useful features, such as Locale fallbacks (RFC4647 compliant) and automatic loading of Locale data from defined directory/file locations.
-Globalize2 is much more lightweight and modular than its predecessor was. Content translations in Globalize2 use default ActiveRecord features and do not limit any functionality any more.
+Globalize2 is much more lightweight and modular than its predecessor was. Content translations in Globalize2 use default ActiveRecord features and do not limit any functionality any more.
All features and tools in Globalize2 are implemented in the most unobstrusive and loosely-coupled way possible, so you can pick whatever features or tools you need for your application and combine them with other tools from other libraries or plugins.
@@ -84,26 +84,26 @@ The Simple backend has its pluralization algorithm baked in hardcoded. This algo
To add custom pluralization logic to Globalize' Static backend you can do something like this:
<pre><code>
-@backend.add_pluralizer :cz, lambda{|c|
- c == 1 ? :one : (2..4).include?(c) ? :few : :other
+@backend.add_pluralizer :cz, lambda{|c|
+ c == 1 ? :one : (2..4).include?(c) ? :few : :other
}
</code></pre>
h2. Locale Fallbacks
-Globalize2 ships with a Locale fallback tool which extends the I18n module to hold a fallbacks instance which is set to an instance of Globalize::Locale::Fallbacks by default but can be swapped with a different implementation.
+Globalize2 ships with a Locale fallback tool which extends the I18n module to hold a fallbacks instance which is set to an instance of Globalize::Locale::Fallbacks by default but can be swapped with a different implementation.
Globalize2 fallbacks will compute a number of other locales for a given locale. For example:
<pre><code>
I18n.fallbacks[:"es-MX"] # => [:"es-MX", :es, :"en-US", :en]
</code></pre>
-Globalize2 fallbacks always fall back to
+Globalize2 fallbacks always fall back to
-* all parents of a given locale (e.g. :es for :"es-MX"),
-* then to the fallbacks' default locales and all of their parents and
-* finally to the :root locale.
+* all parents of a given locale (e.g. :es for :"es-MX"),
+* then to the fallbacks' default locales and all of their parents and
+* finally to the :root locale.
The default locales are set to [:"en-US"] by default but can be set to something else. The root locale is a concept borrowed from "CLDR":http://unicode.org and makes sense for storing common locale data which works as a last default fallback (e.g. "ltr" for bidi directions).
@@ -170,17 +170,17 @@ I18n.locale = :de
# Translation::Attribute
title = Post.first.title # assuming that no translation can be found:
title.locale # => :en
-title.requested_locale # => :de
+title.requested_locale # => :de
title.fallback? # => true
# Translation::Static
rails = I18n.t :rails # assuming that no translation can be found:
rails.locale # => :en
-rails.requested_locale # => :de
+rails.requested_locale # => :de
rails.fallback? # => true
rails.options # returns the options passed to #t
rails.plural_key # returns the plural_key (e.g. :one, :other)
-rails.original # returns the original translation with no values
+rails.original # returns the original translation with no values
# interpolated to it (e.g. "Hi {{name}}!")
</code></pre>
@@ -190,7 +190,7 @@ A simple exception handler that behaves like the default exception handler but a
Useful for identifying missing translations during testing.
-E.g.
+E.g.
require 'globalize/i18n/missing_translations_log_handler
I18n.missing_translations_logger = RAILS_DEFAULT_LOGGER
@@ -8,16 +8,16 @@ def self.up
end
# TODO: FINISH DOING MIGRATION -- stopped in the middle
-
+
create_table :globalize_translations_map do |t|
t.string :key, :null => false
t.integer :translation_id, :null => false
end
-
+
add_index :taggings, :tag_id
add_index :taggings, [:taggable_id, :taggable_type]
end
-
+
def self.down
drop_table :globalize_translations
drop_table :tags
View
@@ -0,0 +1,15 @@
+module Globalize
+ autoload :ActiveRecord, 'globalize/active_record'
+
+ class << self
+ def fallbacks?
+ I18n.respond_to?(:fallbacks)
+ end
+
+ def fallbacks(locale)
+ fallbacks? ? I18n.fallbacks[locale] : [locale.to_sym]
+ end
+ end
+end
+
+ActiveRecord::Base.send(:include, Globalize::ActiveRecord)
@@ -0,0 +1,155 @@
+module Globalize
+ class MigrationError < StandardError; end
+ class MigrationMissingTranslatedField < MigrationError; end
+ class BadMigrationFieldType < MigrationError; end
+
+ module ActiveRecord
+ autoload :Adapter, 'globalize/active_record/adapter'
+ autoload :Attributes, 'globalize/active_record/attributes'
+ autoload :Migration, 'globalize/active_record/migration'
+
+ def self.included(base)
+ base.extend ActMacro
+ end
+
+ class << self
+ def build_translation_class(target, options)
+ options[:table_name] ||= "#{target.table_name.singularize}_translations"
+
+ klass = target.const_defined?(:Translation) ?
+ target.const_get(:Translation) :
+ target.const_set(:Translation, Class.new(::ActiveRecord::Base))
+
+ klass.class_eval do
+ set_table_name(options[:table_name])
+ belongs_to target.name.underscore.gsub('/', '_')
+ def locale; read_attribute(:locale).to_sym; end
+ def locale=(locale); write_attribute(:locale, locale.to_s); end
+ end
+
+ klass
+ end
+ end
+
+ module ActMacro
+ def locale
+ (defined?(@@locale) && @@locale) || I18n.locale
+ end
+
+ def locale=(locale)
+ @@locale = locale
+ end
+
+ def translates(*attr_names)
+ return if translates?
+ options = attr_names.extract_options!
+
+ class_inheritable_accessor :translation_class, :translated_attribute_names
+ self.translation_class = ActiveRecord.build_translation_class(self, options)
+ self.translated_attribute_names = attr_names.map(&:to_sym)
+
+ after_save :save_translations!
+ has_many :translations, :class_name => translation_class.name,
+ :foreign_key => class_name.foreign_key,
+ :dependent => :delete_all,
+ :extend => HasManyExtensions
+
+ include InstanceMethods
+ extend ClassMethods, Migration
+
+ attr_names.each { |attr_name| translated_attr_accessor(attr_name) }
+ end
+
+ def translates?
+ included_modules.include?(InstanceMethods)
+ end
+ end
+
+ module HasManyExtensions
+ def by_locale(locale)
+ first(:conditions => { :locale => locale.to_s })
+ end
+
+ def by_locales(locales)
+ all(:conditions => { :locale => locales.map(&:to_s) })
+ end
+ end
+
+ module ClassMethods
+ delegate :set_translation_table_name, :to => :translation_class
+
+ def translation_table_name
+ translation_class.table_name
+ end
+
+ def respond_to?(method)
+ method.to_s =~ /^find_by_(\w+)$/ && translated_attribute_names.include?($1.to_sym) || super
+ end
+
+ def method_missing(method, *args)
+ if method.to_s =~ /^find_by_(\w+)$/ && translated_attribute_names.include?($1.to_sym)
+ find(:first, :joins => :translations, :conditions => [
+ "#{translated_attr_name($1)} = ? AND #{translated_attr_name('locale')} IN (?)",
+ args.first, Globalize.fallbacks(locale).map(&:to_s)])
+ else
+ super
+ end
+ end
+
+ protected
+
+ def translated_attr_accessor(name)
+ define_method "#{name}=", lambda { |value|
+ globalize.write(self.class.locale, name, value)
+ self[name] = value
+ }
+ define_method name, lambda {
+ globalize.fetch(self.class.locale, name)
+ }
+ alias_method "#{name}_before_type_cast", name
+ end
+
+ def translated_attr_name(name)
+ "#{translation_class.table_name}.#{name}"
+ end
+ end
+
+ module InstanceMethods
+ def globalize
+ @globalize ||= Adapter.new self
+ end
+
+ def available_locales
+ translations.scoped(:select => 'DISTINCT locale').map do |translation|
+ translation.locale.to_sym
+ end
+ end
+
+ def translated_attributes
+ translated_attribute_names.inject({}) do |attributes, name|
+ attributes.merge(name => send(name))
+ end
+ end
+
+ def set_translations(options)
+ options.keys.each do |key|
+ translation = translations.find_by_locale(key.to_s) ||
+ translations.build(:locale => key.to_s)
+ translation.update_attributes!(options[key])
+ end
+ end
+
+ def reload(options = nil)
+ translated_attribute_names.each { |name| @attributes.delete(name.to_s) }
+ globalize.reset
+ super(options)
+ end
+
+ protected
+
+ def save_translations!
+ globalize.save_translations!
+ end
+ end
+ end
+end
@@ -0,0 +1,86 @@
+module Globalize
+ module ActiveRecord
+ class Adapter
+ # The cache caches attributes that already were looked up for read access.
+ # The stash keeps track of new or changed values that need to be saved.
+ attr_reader :record, :cache, :stash
+
+ def initialize(record)
+ @record = record
+ @cache = Attributes.new
+ @stash = Attributes.new
+ end
+
+ def fetch(locale, attr_name)
+ cache.contains?(locale, attr_name) ?
+ cache.read(locale, attr_name) :
+ cache.write(locale, attr_name, fetch_attribute(locale, attr_name))
+ end
+
+ def write(locale, attr_name, value)
+ stash.write(locale, attr_name, value)
+ cache.write(locale, attr_name, value)
+ end
+
+ def save_translations!
+ stash.each do |locale, attrs|
+ translation = record.translations.find_or_initialize_by_locale(locale.to_s)
+ attrs.each { |attr_name, value| translation[attr_name] = value }
+ translation.save!
+ end
+ stash.clear
+ end
+
+ def reset
+ cache.clear
+ # stash.clear
+ end
+
+ protected
+
+ def fetch_translation(locale)
+ locale = locale.to_sym
+ record.translations.loaded? ? record.translations.detect { |t| t.locale == locale } :
+ record.translations.by_locale(locale)
+ end
+
+ def fetch_translations(locale)
+ # only query if not already included with :include => translations
+ record.translations.loaded? ? record.translations :
+ record.translations.by_locales(Globalize.fallbacks(locale))
+ end
+
+ def fetch_attribute(locale, attr_name)
+ translations = fetch_translations(locale)
+ value, requested_locale = nil, locale
+
+ # Walk through the fallbacks, starting with the current locale itself,
+ # and moving to the next best choice, until we find a match. Check the
+ # cache first to see if we've changed the attribute and not saved yet.
+ Globalize.fallbacks(locale).each do |fallback|
+ # TODO should we be checking stash or just cache?
+ value = cache.read(fallback, attr_name) || begin
+ translation = translations.detect { |t| t.locale == fallback }
+ translation && translation.send(attr_name)
+ end
+ locale = fallback && break if value
+ end
+
+ set_metadata(value, :locale => locale, :requested_locale => requested_locale)
+ value
+ end
+
+ def set_metadata(object, metadata)
+ if object.respond_to?(:translation_metadata)
+ object.translation_metadata.merge!(meta_data)
+ end
+ end
+
+ def translation_metadata_accessor(object)
+ return if obj.respond_to?(:translation_metadata)
+ class << object; attr_accessor :translation_metadata end
+ object.translation_metadata ||= {}
+ end
+ end
+ end
+end
@@ -0,0 +1,25 @@
+# Helper class for storing values per locale. Used by Globalize::Adapter
+# to stash and cache attribute values.
+module Globalize
+ module ActiveRecord
+ class Attributes < Hash
+ def [](locale)
+ locale = locale.to_sym
+ self[locale] = {} unless has_key?(locale)
+ self.fetch(locale)
+ end
+
+ def contains?(locale, attr_name)
+ self[locale].has_key?(attr_name)
+ end
+
+ def read(locale, attr_name)
+ self[locale][attr_name]
+ end
+
+ def write(locale, attr_name, value)
+ self[locale][attr_name] = value
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit f49d8e1

Please sign in to comment.