Skip to content
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 @svenfuchs svenfuchs 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
26 README.textile
@@ -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
View
6 generators/templates/db_backend_migration.rb
@@ -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
15 lib/globalize.rb
@@ -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)
View
155 lib/globalize/active_record.rb
@@ -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
View
86 lib/globalize/active_record/adapter.rb
@@ -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
View
25 lib/globalize/active_record/attributes.rb
@@ -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
View
40 lib/globalize/active_record/migration.rb
@@ -0,0 +1,40 @@
+module Globalize
+ module ActiveRecord
+ module Migration
+ def create_translation_table!(fields)
+ translated_attribute_names.each do |f|
+ raise MigrationMissingTranslatedField, "Missing translated field #{f}" unless fields[f]
+ end
+
+ fields.each do |name, type|
+ if translated_attribute_names.include?(name) && ![:string, :text].include?(type)
+ raise BadMigrationFieldType, "Bad field type for #{name}, should be :string or :text"
+ end
+ end
+
+ self.connection.create_table(translation_table_name) do |t|
+ t.references self.table_name.singularize
+ t.string :locale
+ fields.each do |name, type|
+ t.column name, type
+ end
+ t.timestamps
+ end
+
+ self.connection.add_index(translation_table_name, "#{self.table_name.singularize}_id", :name => translation_index_name)
+ end
+
+ def translation_index_name
+ require 'digest/sha1'
+ # FIXME what's the max size of an index name?
+ index_name = "index_#{translation_table_name}_on_#{self.table_name.singularize}_id"
+ index_name.size < 50 ? index_name : "index_#{Digest::SHA1.hexdigest(index_name)}"
+ end
+
+ def drop_translation_table!
+ self.connection.remove_index(translation_table_name, :name => translation_index_name) rescue nil
+ self.connection.drop_table(translation_table_name)
+ end
+ end
+ end
+end
View
102 lib/globalize/backend/chain.rb
@@ -1,102 +0,0 @@
-module I18n
- class << self
- def chain_backends(*args)
- self.backend = Globalize::Backend::Chain.new(*args)
- end
- end
-end
-
-module Globalize
- module Backend
- class Chain
- def initialize(*args)
- add(*args) unless args.empty?
- end
-
- # Change this to a) accept any number of backends and b) accept classes.
- # When classes are passed instantiate them and add the instances as backends.
- # Return the added backends from #add.
- #
- # Add an initialize method that accepts the same arguments and passes them
- # to #add, so we could:
- # I18n.backend = Globalize::Backend::Chain.new(Globalize::Backend::Foo, Globalize::Backend::Bar)
- # Globalize::Backend::Chain.new(:foo, :bar)
- # Globalize.chain_backends :foo, :bar
- def add(*backends)
- backends.each do |backend|
- backend = Globalize::Backend.const_get(backend.to_s.capitalize) if backend.is_a? Symbol
- backend = backend.new if backend.is_a? Class
- self.backends << backend
- end
- end
-
- def load_translations(*args)
- backends.each{|backend| backend.load_translations(*args) }
- end
-
- # For defaults:
- # Never pass any default option to the backends but instead implement our own default
- # mechanism (e.g. symbols as defaults would need to be passed to the whole chain to
- # be translated).
- #
- # For namespace lookup:
- # Only return if the result is not a hash OR count is not present, otherwise merge them.
- # So in effect the count variable would control whether we have a namespace lookup or a
- # pluralization going on.
- #
- # Exceptions:
- # Make sure that we catch MissingTranslationData exceptions and raise
- # one in the end when no translation was found at all.
- #
- # For bulk translation:
- # If the key is an array we need to call #translate for each of the
- # keys and collect the results.
-
- def translate(locale, key, options = {})
- raise I18n::InvalidLocale.new(locale) if locale.nil?
- return key.map{|k| translate locale, k, options } if key.is_a? Array
-
- default = options.delete(:default)
- result = backends.inject({}) do |namespace, backend|
- begin
- translation = backend.translate(locale.to_sym, key, options)
- if namespace_lookup?(translation, options)
- namespace.merge! translation
- elsif translation
- return translation
- end
- rescue I18n::MissingTranslationData
- end
- end
- result || default(locale, default, options) || raise(I18n::MissingTranslationData.new(locale, key, options))
- end
-
- def localize(locale, object, format = :default)
- backends.each do |backend|
- result = backend.localize(locale, object, format) and return result
- end
- end
-
- protected
- def backends
- @backends ||= []
- end
-
- def default(locale, default, options = {})
- case default
- when String then default
- when Symbol then translate locale, default, options
- when Array then default.each do |obj|
- result = default(locale, obj, options.dup) and return result
- end and nil
- end
- rescue I18n::MissingTranslationData
- nil
- end
-
- def namespace_lookup?(result, options)
- result.is_a?(Hash) and not options.has_key?(:count)
- end
- end
- end
-end
View
37 lib/globalize/backend/pluralizing.rb
@@ -1,37 +0,0 @@
-require 'i18n/backend/simple'
-
-module Globalize
- module Backend
- class Pluralizing < I18n::Backend::Simple
- def pluralize(locale, entry, count)
- return entry unless entry.is_a?(Hash) and count
- key = :zero if count == 0 && entry.has_key?(:zero)
- key ||= pluralizer(locale).call(count)
- raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
- translation entry[key], :plural_key => key
- end
-
- def add_pluralizer(locale, pluralizer)
- pluralizers[locale.to_sym] = pluralizer
- end
-
- def pluralizer(locale)
- pluralizers[locale.to_sym] || default_pluralizer
- end
-
- protected
- def default_pluralizer
- pluralizers[:en]
- end
-
- def pluralizers
- @pluralizers ||= { :en => lambda{|n| n == 1 ? :one : :other } }
- end
-
- # Overwrite this method to return something other than a String
- def translation(string, attributes)
- string
- end
- end
- end
-end
View
61 lib/globalize/backend/static.rb
@@ -1,61 +0,0 @@
-require 'globalize/backend/pluralizing'
-require 'globalize/locale/fallbacks'
-require 'globalize/translation'
-
-module Globalize
- module Backend
- class Static < Pluralizing
- def initialize(*args)
- add(*args) unless args.empty?
- end
-
- def translate(locale, key, options = {})
- result, default, fallback = nil, options.delete(:default), nil
- I18n.fallbacks[locale].each do |fallback|
- begin
- result = super(fallback, key, options) and break
- rescue I18n::MissingTranslationData
- end
- end
- result ||= default locale, default, options
-
- attrs = {:requested_locale => locale, :locale => fallback, :key => key, :options => options}
- translation(result, attrs)
-# translation(result, attrs) || raise(I18n::MissingTranslationData.new(locale, key, options))
- end
-
- protected
-
- alias :orig_interpolate :interpolate unless method_defined? :orig_interpolate
- def interpolate(locale, string, values = {})
- result = orig_interpolate(locale, string, values)
- translation = translation(string)
- translation.nil? ? result : translation.replace(result)
- end
-
- def translation(result, meta = nil)
- return unless result
-
- case result
- when Numeric
- result
- when String
- result = Translation::Static.new(result) unless result.is_a? Translation::Static
- result.set_meta meta
- result
- when Hash
- Hash[*result.map do |key, value|
- [key, translation(value, meta)]
- end.flatten]
- when Array
- result.map do |value|
- translation(value, meta)
- end
- else
- result
- # raise "unexpected translation type: #{result.inspect}"
- end
- end
- end
- end
-end
View
63 lib/globalize/load_path.rb
@@ -1,63 +0,0 @@
-# Locale load_path and Locale loading support.
-#
-# To use this include the Globalize::LoadPath::I18n module to I18n like this:
-#
-# I18n.send :include, Globalize::LoadPath::I18n
-#
-# Clients can add load_paths using:
-#
-# I18n.load_path.add load_path, 'rb', 'yml' # pass any number of extensions like this
-# I18n.load_path << 'path/to/dir' # usage without an extension, defaults to 'yml'
-#
-# And load locale data using either of:
-#
-# I18n.load_locales 'en-US', 'de-DE'
-# I18n.load_locale 'en-US'
-#
-# This will lookup all files named like:
-#
-# 'path/to/dir/all.yml'
-# 'path/to/dir/en-US.yml'
-# 'path/to/dir/en-US/*.yml'
-#
-# The filenames will be passed to I18n.load_translations which delegates to
-# the backend. So the actual behaviour depends on the implementation of the
-# backend. I18n::Backend::Simple will be able to read YAML and plain Ruby
-# files. See the documentation for I18n.load_translations for details.
-
-module Globalize
- class LoadPath < Array
- def extensions
- @extensions ||= ['rb', 'yml']
- end
- attr_writer :extensions
-
- def locales
- @locales ||= ['*']
- end
- attr_writer :locales
-
- def <<(path)
- push path
- end
-
- def push(*paths)
- super(*paths.map{|path| filenames(path) }.flatten.uniq.sort)
- end
-
- protected
-
- def filenames(path)
- return [path] if File.file? path
- patterns(path).map{|pattern| Dir[pattern] }
- end
-
- def patterns(path)
- locales.map do |locale|
- extensions.map do |extension|
- %W(#{path}/all.#{extension} #{path}/#{locale}.#{extension} #{path}/#{locale}/**/*.#{extension})
- end
- end.flatten.uniq
- end
- end
-end
View
63 lib/globalize/locale/fallbacks.rb
@@ -1,63 +0,0 @@
-require 'globalize/locale/language_tag'
-
-module I18n
- @@fallbacks = nil
-
- class << self
- # Returns the current fallbacks. Defaults to +Globalize::Locale::Fallbacks+.
- def fallbacks
- @@fallbacks ||= Globalize::Locale::Fallbacks.new
- end
-
- # Sets the current fallbacks. Used to set a custom fallbacks instance.
- def fallbacks=(fallbacks)
- @@fallbacks = fallbacks
- end
- end
-end
-
-module Globalize
- module Locale
- class Fallbacks < Hash
- def initialize(*defaults)
- @map = {}
- map defaults.pop if defaults.last.is_a?(Hash)
-
- defaults = [I18n.default_locale.to_sym] if defaults.empty?
- self.defaults = defaults
- end
-
- def defaults=(defaults)
- @defaults = defaults.map{|default| compute(default, false) }.flatten << :root
- end
- attr_reader :defaults
-
- def [](tag)
- tag = tag.to_sym
- has_key?(tag) ? fetch(tag) : store(tag, compute(tag))
- end
-
- def map(mappings)
- mappings.each do |from, to|
- from, to = from.to_sym, Array(to)
- to.each do |to|
- @map[from] ||= []
- @map[from] << to.to_sym
- end
- end
- end
-
- protected
-
- def compute(tags, include_defaults = true)
- result = Array(tags).collect do |tag|
- tags = LanguageTag::tag(tag.to_sym).parents(true).map! {|t| t.to_sym }
- tags.each{|tag| tags += compute(@map[tag]) if @map[tag] }
- tags
- end.flatten
- result.push *defaults if include_defaults
- result.uniq
- end
- end
- end
-end
View
81 lib/globalize/locale/language_tag.rb
@@ -1,81 +0,0 @@
-# for specifications see http://en.wikipedia.org/wiki/IETF_language_tag
-#
-# SimpleParser does not implement advanced usages such as grandfathered tags
-
-module Globalize
- module Locale
- module Rfc4646
- SUBTAGS = [:language, :script, :region, :variant, :extension, :privateuse, :grandfathered]
- FORMATS = {:language => :downcase, :script => :capitalize, :region => :upcase, :variant => :downcase}
- end
-
- class LanguageTag < Struct.new(*Rfc4646::SUBTAGS)
- class << self
- def parser
- @@parser ||= SimpleParser
- end
-
- def parser=(parser)
- @@parser = parser
- end
-
- def tag(tag)
- matches = parser.match(tag)
- new *matches if matches
- end
- end
-
- Rfc4646::FORMATS.each do |name, format|
- define_method(name) { self[name].send(format) unless self[name].nil? }
- end
-
- def to_sym
- to_s.to_sym
- end
-
- def to_s
- @tag ||= to_a.compact.join("-")
- end
-
- def to_a
- members.collect {|attr| self.send(attr) }
- end
-
- def parent
- segs = to_a.compact
- segs.length < 2 ? nil : LanguageTag.tag(segs[0..(segs.length-2)].join('-'))
- end
-
- def parents(include_self = true)
- result, parent = [], self.dup
- result << parent if include_self
- while parent = parent.parent
- result << parent
- end
- result
- end
-
- module SimpleParser
- PATTERN = %r{\A(?:
- ([a-z]{2,3}(?:(?:-[a-z]{3}){0,3})?|[a-z]{4}|[a-z]{5,8}) # language
- (?:-([a-z]{4}))? # script
- (?:-([a-z]{2}|\d{3}))? # region
- (?:-([0-9a-z]{5,8}|\d[0-9a-z]{3}))* # variant
- (?:-([0-9a-wyz](?:-[0-9a-z]{2,8})+))* # extension
- (?:-(x(?:-[0-9a-z]{1,8})+))?| # privateuse subtag
- (x(?:-[0-9a-z]{1,8})+)| # privateuse tag
- /* ([a-z]{1,3}(?:-[0-9a-z]{2,8}){1,2}) */ # grandfathered
- )\z}xi
-
- class << self
- def match(tag)
- c = PATTERN.match(tag.to_s).captures
- c[0..4] << (c[5].nil? ? c[6] : c[5]) << c[7] # TODO c[7] is grandfathered, throw a NotImplemented exception here?
- rescue
- false
- end
- end
- end
- end
- end
-end
View
56 lib/globalize/model/active_record.rb
@@ -1,56 +0,0 @@
-require 'globalize/translation'
-require 'globalize/locale/fallbacks'
-require 'globalize/model/active_record/adapter'
-require 'globalize/model/active_record/translated'
-
-module Globalize
- module Model
- module ActiveRecord
- class << self
- def create_proxy_class(klass)
- module_names = klass.name.split('::')
- klass_name = module_names.pop
- target = module_names.empty? ? Object : module_names.join('::').constantize
-
- proxy_class_name = "#{klass_name}Translation"
- proxy_class = nil
- begin
- proxy_class = proxy_class_name.constantize
- rescue NameError
- proxy_class = target.const_set proxy_class_name, Class.new(::ActiveRecord::Base)
- end
-
- proxy_class.instance_eval do
- belongs_to "#{klass.name.underscore.gsub('/', '_')}".intern
- end
- proxy_class.class_eval do
- def locale
- read_attribute(:locale).to_sym
- end
-
- def locale=(locale)
- write_attribute(:locale, locale.to_s)
- end
- end
-
- return proxy_class
- end
-
- def define_accessors(klass, attr_names)
- attr_names.each do |attr_name|
- klass.send :define_method, attr_name, lambda {
- globalize.fetch self.class.locale, attr_name
- }
- klass.send :define_method, "#{attr_name}_before_type_cast", lambda {
- globalize.fetch self.class.locale, attr_name
- }
- klass.send :define_method, "#{attr_name}=", lambda {|val|
- globalize.stash self.class.locale, attr_name, val
- self[attr_name] = val
- }
- end
- end
- end
- end
- end
-end
View
100 lib/globalize/model/active_record/adapter.rb
@@ -1,100 +0,0 @@
-module Globalize
- module Model
- class AttributeStash < Hash
- def contains?(locale, attr_name)
- locale = locale.to_sym
- self[locale] ||= {}
- self[locale].has_key? attr_name
- end
-
- def read(locale, attr_name)
- locale = locale.to_sym
- self[locale] ||= {}
- self[locale][attr_name]
- end
-
- def write(locale, attr_name, value)
- locale = locale.to_sym
- self[locale] ||= {}
- self[locale][attr_name] = value
- end
- end
-
- class Adapter
- def initialize(record)
- @record = record
-
- # TODO what exactly are the roles of cache and stash
- @cache = AttributeStash.new
- @stash = AttributeStash.new
- end
-
- def fetch(locale, attr_name)
- # locale = I18n.locale
- is_cached = @cache.contains?(locale, attr_name)
- is_cached ? @cache.read(locale, attr_name) : begin
- value = fetch_attribute locale, attr_name
- @cache.write locale, attr_name, value if value && value.locale == locale
- value
- end
- end
-
- def stash(locale, attr_name, value)
- @stash.write locale, attr_name, value
- @cache.write locale, attr_name, value
- end
-
- def update_translations!
- @stash.each do |locale, attrs|
- translation = @record.globalize_translations.find_or_initialize_by_locale(locale.to_s)
- attrs.each{|attr_name, value| translation[attr_name] = value }
- translation.save!
- end
- @stash.clear
- end
-
- # Clears the cache
- def clear
- @cache.clear
- @stash.clear
- end
-
- def clear_cache
- @cache.clear
- end
-
- private
-
- def fetch_attribute(locale, attr_name)
- fallbacks = I18n.fallbacks[locale].map{|tag| tag.to_s}.map(&:to_sym)
-
- # If the translations were included with
- # :include => globalize_translations
- # there is no need to query them again.
- unless @record.globalize_translations.loaded?
- translations = @record.globalize_translations.by_locales(fallbacks)
- else
- translations = @record.globalize_translations
- end
- result, 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 @globalize_set_translations cache first to see if we've just changed the
- # attribute and not saved yet.
- fallbacks.each do |fallback|
- # TODO should we be checking stash or just cache?
- result = @cache.read(fallback, attr_name) || begin
- translation = translations.detect {|tr| tr.locale == fallback }
- translation && translation.send(attr_name)
- end
- if result
- locale = fallback
- break
- end
- end
- result && Translation::Attribute.new(result, :locale => locale, :requested_locale => requested_locale)
- end
- end
- end
-end
View
174 lib/globalize/model/active_record/translated.rb
@@ -1,174 +0,0 @@
-require 'digest/sha1'
-
-module Globalize
- module Model
- class MigrationError < StandardError; end
- class MigrationMissingTranslatedField < MigrationError; end
- class BadMigrationFieldType < MigrationError; end
-
- module ActiveRecord
- module Translated
- def self.included(base)
- base.extend ActMethods
- end
-
- module ActMethods
- def translates(*attr_names)
- options = attr_names.extract_options!
- options[:translated_attributes] = attr_names
-
- # Only set up once per class
- unless included_modules.include? InstanceMethods
- class_inheritable_accessor :globalize_options, :globalize_proxy
-
- include InstanceMethods
- extend ClassMethods
-
- self.globalize_proxy = Globalize::Model::ActiveRecord.create_proxy_class(self)
- has_many(
- :globalize_translations,
- :class_name => globalize_proxy.name,
- :extend => Extensions,
- :dependent => :delete_all,
- :foreign_key => class_name.foreign_key
- )
-
- after_save :update_globalize_record
- end
-
- self.globalize_options = options
- Globalize::Model::ActiveRecord.define_accessors(self, attr_names)
-
- # Import any callbacks that have been defined by extensions to Globalize2
- # and run them.
- extend Callbacks
- Callbacks.instance_methods.each { |callback| send(callback) }
- end
-
- def locale=(locale)
- @@locale = locale
- end
-
- def locale
- (defined?(@@locale) && @@locale) || I18n.locale
- end
- end
-
- # Dummy Callbacks module. Extensions to Globalize2 can insert methods into here
- # and they'll be called at the end of the translates class method.
- module Callbacks
- end
-
- # Extension to the has_many :globalize_translations association
- module Extensions
- def by_locales(locales)
- find :all, :conditions => { :locale => locales.map(&:to_s) }
- end
- end
-
- module ClassMethods
- def method_missing(method, *args)
- if method.to_s =~ /^find_by_(\w+)$/ && globalize_options[:translated_attributes].include?($1.to_sym)
- find(:first, :joins => :globalize_translations,
- :conditions => [ "#{i18n_attr($1)} = ? AND #{i18n_attr('locale')} IN (?)",
- args.first,I18n.fallbacks[I18n.locale].map{|tag| tag.to_s}])
- else
- super
- end
- end
-
- def create_translation_table!(fields)
- translated_fields = self.globalize_options[:translated_attributes]
- translated_fields.each do |f|
- raise MigrationMissingTranslatedField, "Missing translated field #{f}" unless fields[f]
- end
-
- fields.each do |name, type|
- if translated_fields.include?(name) && ![:string, :text].include?(type)
- raise BadMigrationFieldType, "Bad field type for #{name}, should be :string or :text"
- end
- end
-
- self.connection.create_table(translation_table_name) do |t|
- t.references self.table_name.singularize
- t.string :locale
- fields.each do |name, type|
- t.column name, type
- end
- t.timestamps
- end
-
- self.connection.add_index(translation_table_name, "#{self.table_name.singularize}_id", :name => translation_index_name)
- end
-
- def set_translation_table_name(table_name)
- globalize_proxy.set_table_name(table_name)
- end
-
- def translation_table_name
- globalize_proxy.table_name
- end
-
- def translation_index_name
- # FIXME what's the max size of an index name?
- index_name = "index_#{translation_table_name}_on_#{self.table_name.singularize}_id"
- index_name.size < 50 ? index_name : "index_#{Digest::SHA1.hexdigest(index_name)}"
- end
-
- def drop_translation_table!
- self.connection.remove_index(translation_table_name, :name => translation_index_name)
- self.connection.drop_table translation_table_name
- end
-
- private
-
- def i18n_attr(attribute_name)
- self.base_class.name.underscore.gsub('/', '_') + "_translations.#{attribute_name}"
- end
- end
-
- module InstanceMethods
- def reload(options = nil)
- globalize.clear_cache
-
- # clear all globalized attributes
- # TODO what's the best way to handle this?
- self.class.globalize_options[:translated_attributes].each do |attr|
- @attributes.delete(attr.to_s)
- end
-
- super(options)
- end
-
- def translated_attributes
- self.class.globalize_options[:translated_attributes].inject({}) {|h, tf| h[tf] = send(tf); h }
- end
-
- def globalize
- @globalize ||= Adapter.new self
- end
-
- def update_globalize_record
- globalize.update_translations!
- end
-
- def translated_locales
- globalize_translations.scoped(:select => 'DISTINCT locale').map do |translation|
- translation.locale.to_sym
- end
- end
-
- def set_translations(options)
- options.keys.each do |key|
- translation = globalize_translations.find_by_locale(key.to_s) ||
- globalize_translations.build(:locale => key.to_s)
- translation.update_attributes!(options[key])
- end
- end
- end
- end
- end
- end
-end
-
-ActiveRecord::Base.send(:include, Globalize::Model::ActiveRecord::Translated)
View
32 lib/globalize/translation.rb
@@ -1,32 +0,0 @@
-module Globalize
- # Translations are simple value objects that carry some context information
- # alongside the actual translation string.
-
- class Translation < String
- class Attribute < Translation
- attr_accessor :requested_locale, :locale, :key
- end
-
- class Static < Translation
- attr_accessor :requested_locale, :locale, :key, :options, :plural_key, :original
-
- def initialize(string, meta = nil)
- self.original = string
- super
- end
- end
-
- def initialize(string, meta = nil)
- set_meta meta
- super string
- end
-
- def fallback?
- locale.to_sym != requested_locale.to_sym
- end
-
- def set_meta(meta)
- meta.each {|name, value| send :"#{name}=", value } if meta
- end
- end
-end
View
0 .../i18n/missing_translations_log_handler.rb → lib/i18n/missing_translations_log_handler.rb
File renamed without changes.
View
0 ...18n/missing_translations_raise_handler.rb → ...18n/missing_translations_raise_handler.rb
File renamed without changes.
View
3 lib/locale/root.yml
@@ -1,3 +0,0 @@
-root:
- bidi:
- direction: left-to-right
View
40 lib/rails_edge_load_path_patch.rb
@@ -1,40 +0,0 @@
-module I18n
- @@load_path = nil
- @@default_locale = :'en-US'
-
- class << self
- def load_path
- @@load_path ||= []
- end
-
- def load_path=(load_path)
- @@load_path = load_path
- end
- end
-end
-
-I18n::Backend::Simple.module_eval do
- def initialized?
- @initialized ||= false
- end
-
- protected
-
- def init_translations
- load_translations(*I18n.load_path)
- @initialized = true
- end
-
- def lookup(locale, key, scope = [])
- return unless key
- init_translations unless initialized?
- keys = I18n.send :normalize_translation_keys, locale, key, scope
- keys.inject(translations){|result, k| result[k.to_sym] or return nil }
- end
-end
-
-rails_dir = File.expand_path "#{File.dirname(__FILE__)}/../../../rails/"
-paths = %w(actionpack/lib/action_view/locale/en-US.yml
- activerecord/lib/active_record/locale/en-US.yml
- activesupport/lib/active_support/locale/en-US.yml)
-paths.each{|path| I18n.load_path << "#{rails_dir}/#{path}" }
View
10 notes.textile
@@ -15,12 +15,12 @@ h1. Some Notes
h1. Schema
-There will be two db tables.
+There will be two db tables.
# globalize_translations will have: locale, key, translation, created_at, updated_at.
# globalize_translations_map will have: key, translation_id.
-globalize_translations_map will let us easily fetch entire sub-trees of namespaces.
+globalize_translations_map will let us easily fetch entire sub-trees of namespaces.
However, this table may not be necessary, as it may be feasible to just use key LIKE "some.namespace.%".
h1. Caching
@@ -41,11 +41,11 @@ key IN (key, default_key)
</code>
</pre>
-The Ruby code would then pick the first translation that satisfies a fallback, in fallback order.
+The Ruby code would then pick the first translation that satisfies a fallback, in fallback order.
Of course, the records with the supplied key would take precedence of those with the default key.
h1. Misc
-We should revisit the :zero plural code. On the one hand it's certainly useful for
+We should revisit the :zero plural code. On the one hand it's certainly useful for
many apps in many languages. On the other hand it's not mentioned in CLDR, and not a real
-concept in language pluralization. Right now, I'm feeling it's still a good idea to keep it in.
+concept in language pluralization. Right now, I'm feeling it's still a good idea to keep it in.
View
102 test/active_record/fallbacks_test.rb
@@ -0,0 +1,102 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require File.join( File.dirname(__FILE__) + '/../data/models' )
+
+if I18n.respond_to?(:fallbacks)
+ class TranslatedTest < ActiveSupport::TestCase
+ def setup
+ I18n.locale = :'en-US'
+ I18n.fallbacks.clear
+ reset_db!
+ ActiveRecord::Base.locale = nil
+ end
+
+ def teardown
+ I18n.fallbacks.clear
+ end
+
+ test "keeping one field in new locale when other field is changed" do
+ I18n.fallbacks.map 'de-DE' => [ 'en-US' ]
+ post = Post.create :subject => 'foo'
+ I18n.locale = 'de-DE'
+ post.content = 'bar'
+ assert_equal 'foo', post.subject
+ end
+
+ test "modifying non-required field in a new locale" do
+ I18n.fallbacks.map 'de-DE' => [ 'en-US' ]
+ post = Post.create :subject => 'foo'
+ I18n.locale = 'de-DE'
+ post.content = 'bar'
+ assert post.save
+ end
+
+ test "resolves a simple fallback" do
+ I18n.locale = 'de-DE'
+ post = Post.create :subject => 'foo'
+ I18n.locale = 'de'
+ post.subject = 'baz'
+ post.content = 'bar'
+ post.save
+ I18n.locale = 'de-DE'
+ assert_equal 'foo', post.subject
+ assert_equal 'bar', post.content
+ end
+
+ test "resolves a simple fallback without reloading" do
+ I18n.locale = 'de-DE'
+ post = Post.new :subject => 'foo'
+ I18n.locale = 'de'
+ post.subject = 'baz'
+ post.content = 'bar'
+ I18n.locale = 'de-DE'
+ assert_equal 'foo', post.subject
+ assert_equal 'bar', post.content
+ end
+
+ test "resolves a complex fallback without reloading" do
+ I18n.fallbacks.map 'de' => %w(en he)
+ I18n.locale = 'de'
+ post = Post.new
+ I18n.locale = 'en'
+ post.subject = 'foo'
+ I18n.locale = 'he'
+ post.subject = 'baz'
+ post.content = 'bar'
+ I18n.locale = 'de'
+ assert_equal 'foo', post.subject
+ assert_equal 'bar', post.content
+ end
+
+ test 'fallbacks with lots of locale switching' do
+ I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
+ post = Post.create :subject => 'foo'
+
+ I18n.locale = :'de-DE'
+ assert_equal 'foo', post.subject
+
+ I18n.locale = :'en-US'
+ post.update_attribute :subject, 'bar'
+
+ I18n.locale = :'de-DE'
+ assert_equal 'bar', post.subject
+ end
+
+ test 'fallbacks with lots of locale switching' do
+ I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
+ child = Child.create :content => 'foo'
+
+ I18n.locale = :'de-DE'
+ assert_equal 'foo', child.content
+
+ I18n.locale = :'en-US'
+ child.update_attribute :content, 'bar'
+
+ I18n.locale = :'de-DE'
+ assert_equal 'bar', child.content
+ end
+ end
+end
+
+# TODO should validate_presence_of take fallbacks into account? maybe we need
+# an extra validation call, or more options for validate_presence_of.
+
View
47 test/model/active_record/migration_test.rb → test/active_record/migration_test.rb
@@ -1,36 +1,32 @@
-require File.join( File.dirname(__FILE__), '..', '..', 'test_helper' )
-require 'active_record'
-require 'globalize/model/active_record'
-
-# Hook up model translation
-ActiveRecord::Base.send(:include, Globalize::Model::ActiveRecord::Translated)
-
-# Load Post model
-require File.join( File.dirname(__FILE__), '..', '..', 'data', 'models' )
+require File.join( File.dirname(__FILE__) + '/../test_helper' )
+require File.join( File.dirname(__FILE__) + '/../data/models' )
class MigrationTest < ActiveSupport::TestCase
def setup
- reset_db! File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'no_globalize_schema.rb'))
+ reset_db!
+ Post.drop_translation_table!
end
test 'globalize table added' do
- assert !Post.connection.table_exists?( :post_translations )
- assert !Post.connection.index_exists?( :post_translations, :post_id )
- Post.create_translation_table! :subject => :string, :content => :text
- assert Post.connection.table_exists?( :post_translations )
- assert Post.connection.index_exists?( :post_translations, :post_id )
- columns = Post.connection.columns( :post_translations )
- assert locale = columns.detect {|c| c.name == 'locale' }
+ assert !Post.connection.table_exists?(:post_translations)
+ assert !Post.connection.index_exists?(:post_translations, :post_id)
+
+ Post.create_translation_table!(:subject => :string, :content => :text)
+ assert Post.connection.table_exists?(:post_translations)
+ assert Post.connection.index_exists?(:post_translations, :post_id)
+
+ columns = Post.connection.columns(:post_translations)
+ assert locale = columns.detect { |c| c.name == 'locale' }
assert_equal :string, locale.type
- assert subject = columns.detect {|c| c.name == 'subject' }
+ assert subject = columns.detect { |c| c.name == 'subject' }
assert_equal :string, subject.type
- assert content = columns.detect {|c| c.name == 'content' }
+ assert content = columns.detect { |c| c.name == 'content' }
assert_equal :text, content.type
- assert post_id = columns.detect {|c| c.name == 'post_id' }
+ assert post_id = columns.detect { |c| c.name == 'post_id' }
assert_equal :integer, post_id.type
- assert created_at = columns.detect {|c| c.name == 'created_at' }
+ assert created_at = columns.detect { |c| c.name == 'created_at' }
assert_equal :datetime, created_at.type
- assert updated_at = columns.detect {|c| c.name == 'updated_at' }
+ assert updated_at = columns.detect { |c| c.name == 'updated_at' }
assert_equal :datetime, updated_at.type
end
@@ -46,13 +42,13 @@ def setup
end
test 'exception on missing field inputs' do
- assert_raise Globalize::Model::MigrationMissingTranslatedField do
+ assert_raise Globalize::MigrationMissingTranslatedField do
Post.create_translation_table! :content => :text
end
end
test 'exception on bad input type' do
- assert_raise Globalize::Model::BadMigrationFieldType do
+ assert_raise Globalize::BadMigrationFieldType do
Post.create_translation_table! :subject => :string, :content => :integer
end
end
@@ -104,16 +100,15 @@ class UltraLongModelNameWithoutProper < ActiveRecord::Base
end
test 'globalize table dropped when table has long name' do
+ UltraLongModelNameWithoutProper.drop_translation_table!
UltraLongModelNameWithoutProper.create_translation_table!(
:subject => :string, :content => :text
)
-
UltraLongModelNameWithoutProper.drop_translation_table!
assert !UltraLongModelNameWithoutProper.connection.table_exists?(
:ultra_long_model_name_without_proper_translations
)
-
assert !UltraLongModelNameWithoutProper.connection.index_exists?(
:ultra_long_model_name_without_proper_translations,
:ultra_long_model_name_without_proper_id
View
34 ...odel/active_record/sti_translated_test.rb → test/active_record/sti_translated_test.rb
@@ -1,22 +1,10 @@
-require File.join( File.dirname(__FILE__), '..', '..', 'test_helper' )
-require 'active_record'
-require 'globalize/model/active_record'
-
-# Hook up model translation
-ActiveRecord::Base.send(:include, Globalize::Model::ActiveRecord::Translated)
-
-# Load Post model
-require File.join( File.dirname(__FILE__), '..', '..', 'data', 'models' )
+require File.join( File.dirname(__FILE__) + '/../test_helper' )
+require File.join( File.dirname(__FILE__) + '/../data/models' )
class StiTranslatedTest < ActiveSupport::TestCase
def setup
I18n.locale = :'en-US'
- I18n.fallbacks.clear
- reset_db! File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'schema.rb'))
- end
-
- def teardown
- I18n.fallbacks.clear
+ reset_db!
end
test "works with simple dynamic finders" do
@@ -43,20 +31,6 @@ def teardown
assert_equal [ 'content' ], child.changed
end
- test 'fallbacks with lots of locale switching' do
- I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
- child = Child.create :content => 'foo'
-
- I18n.locale = :'de-DE'
- assert_equal 'foo', child.content
-
- I18n.locale = :'en-US'
- child.update_attribute :content, 'bar'
-
- I18n.locale = :'de-DE'
- assert_equal 'bar', child.content
- end
-
test "saves all locales, even after locale switching" do
child = Child.new :content => 'foo'
I18n.locale = 'de-DE'
@@ -72,4 +46,4 @@ def teardown
I18n.locale = 'he-IL'
assert_equal 'baz', child.content
end
-end
+end
View
38 test/active_record/translates_test.rb
@@ -0,0 +1,38 @@
+require File.join( File.dirname(__FILE__) + '/../test_helper' )
+require File.join( File.dirname(__FILE__) + '/../data/models' )
+
+class TranslatesTest < ActiveSupport::TestCase
+ def setup
+ I18n.locale = nil
+ ActiveRecord::Base.locale = nil
+ reset_db!
+ end
+
+ test 'defines a :locale accessors on ActiveRecord::Base' do
+ ActiveRecord::Base.locale = :'de-DE'
+ assert_equal :'de-DE', ActiveRecord::Base.locale
+ end
+
+ test 'the :locale reader on ActiveRecord::Base defaults to I18n.locale' do
+ I18n.locale = :'en-US'
+ assert_equal I18n.locale, ActiveRecord::Base.locale
+ end
+
+ test 'translation_class returns the Translation class' do
+ assert_equal Post::Translation, Post.translation_class
+ end
+
+ test 'defines a has_many association on the model class' do
+ assert_has_many Post, :translations
+ end
+
+ test 'sets the given attributes to translated_attribute_names' do
+ assert_equal [:subject, :content], Post.translated_attribute_names
+ end
+
+ test 'defines accessors for the translated attributes' do
+ post = Post.new
+ assert post.respond_to?(:subject)
+ assert post.respond_to?(:subject=)
+ end
+end
View
30 test/active_record/translation_class_test.rb
@@ -0,0 +1,30 @@
+require File.join( File.dirname(__FILE__) + '/../test_helper' )
+require File.join( File.dirname(__FILE__) + '/../data/models' )
+
+class TranlationClassTest < ActiveSupport::TestCase
+ def setup
+ reset_db!
+ end
+
+ test 'defines a Translation class nested in the model class' do
+ assert Post.const_defined?(:Translation)
+ end
+
+ test 'defines a belongs_to association' do
+ assert_belongs_to Post::Translation, :post
+ end
+
+ test 'defines a reader for :locale that always returns a symbol' do
+ post = Post::Translation.new
+ post.write_attribute('locale', 'de')
+ assert_equal :de, post.locale
+ end
+
+ test 'defines a write for :locale that always writes a string' do
+ post = Post::Translation.new
+ post.locale = :de
+ assert_equal 'de', post.read_attribute('locale')
+ end
+end
+
+
View
167 test/model/active_record/translated_test.rb → test/active_record_test.rb
@@ -1,34 +1,22 @@
-require File.join( File.dirname(__FILE__), '..', '..', 'test_helper' )
-require 'active_record'
-require 'globalize/model/active_record'
-
-# Hook up model translation
-ActiveRecord::Base.send(:include, Globalize::Model::ActiveRecord::Translated)
-
-# Load Post model
-require File.join( File.dirname(__FILE__), '..', '..', 'data', 'models' )
+require File.dirname(__FILE__) + '/test_helper'
+require File.join( File.dirname(__FILE__) + '/data/models' )
class TranslatedTest < ActiveSupport::TestCase
def setup
I18n.locale = :'en-US'
- I18n.fallbacks.clear
- reset_db! File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'schema.rb'))
+ reset_db!
ActiveRecord::Base.locale = nil
end
- def teardown
- I18n.fallbacks.clear
- end
-
test "modifiying translated fields" do
- post = Post.create :subject => 'foo'
+ post = Post.create(:subject => 'foo')
assert_equal 'foo', post.subject
post.subject = 'bar'
assert_equal 'bar', post.subject
end
test "modifiying translated fields while switching locales" do
- post = Post.create :subject => 'foo'
+ post = Post.create(:subject => 'foo')
assert_equal 'foo', post.subject
I18n.locale = :'de-DE'
post.subject = 'bar'
@@ -41,15 +29,15 @@ def teardown
test "has post_translations" do
post = Post.create
- assert_nothing_raised { post.globalize_translations }
+ assert_nothing_raised { post.translations }
end
test "has German post_translations" do
I18n.locale = :de
- post = Post.create :subject => 'foo'
- assert_equal 1, post.globalize_translations.size
+ post = Post.create(:subject => 'foo')
+ assert_equal 1, post.translations.size
I18n.locale = :en
- assert_equal 1, post.globalize_translations.size
+ assert_equal 1, post.translations.size
end
test "returns the value passed to :subject" do
@@ -58,7 +46,7 @@ def teardown
end
test "translates subject and content into en-US" do
- post = Post.create :subject => 'foo', :content => 'bar'
+ post = Post.create(:subject => 'foo', :content => 'bar')
assert_equal 'foo', post.subject
assert_equal 'bar', post.content
assert post.save
@@ -68,7 +56,7 @@ def teardown
end
test "finds a German post" do
- post = Post.create :subject => 'foo (en)', :content => 'bar'
+ post = Post.create(:subject => 'foo (en)', :content => 'bar')
I18n.locale = 'de-DE'
post = Post.first
post.subject = 'baz (de)'
@@ -78,7 +66,7 @@ def teardown
assert_equal 'foo (en)', Post.first.subject
end
- test "saves an English post and loads test correctly" do
+ test "saves an English post and loads correctly" do
assert_nil Post.first
post = Post.create :subject => 'foo', :content => 'bar'
assert post.save
@@ -88,13 +76,13 @@ def teardown
end
test "updates an attribute" do
- post = Post.create :subject => 'foo', :content => 'bar'
+ post = Post.create(:subject => 'foo', :content => 'bar')
post.update_attribute :subject, 'baz'
assert_equal 'baz', Post.first.subject
end
test "update_attributes failure" do
- post = Post.create :subject => 'foo', :content => 'bar'
+ post = Post.create(:subject => 'foo', :content => 'bar')
assert !post.update_attributes( { :subject => '' } )
assert_nil post.reload.attributes['subject']
assert_equal 'foo', post.subject
@@ -120,22 +108,6 @@ def teardown
assert_equal 'bar', post.subject
end
- test "keeping one field in new locale when other field is changed" do
- I18n.fallbacks.map 'de-DE' => [ 'en-US' ]
- post = Post.create :subject => 'foo'
- I18n.locale = 'de-DE'
- post.content = 'bar'
- assert_equal 'foo', post.subject
- end
-
- test "modifying non-required field in a new locale" do
- I18n.fallbacks.map 'de-DE' => [ 'en-US' ]
- post = Post.create :subject => 'foo'
- I18n.locale = 'de-DE'
- post.content = 'bar'
- assert post.save
- end
-
test "returns the value for the correct locale, after locale switching, without saving" do
post = Post.create :subject => 'foo'
I18n.locale = 'de-DE'
@@ -162,61 +134,24 @@ def teardown
assert_equal 'baz', post.subject
end
- test "resolves a simple fallback" do
- I18n.locale = 'de-DE'
- post = Post.create :subject => 'foo'
- I18n.locale = 'de'
- post.subject = 'baz'
- post.content = 'bar'
- post.save
- I18n.locale = 'de-DE'
- assert_equal 'foo', post.subject
- assert_equal 'bar', post.content
- end
-
- test "resolves a simple fallback without reloading" do
- I18n.locale = 'de-DE'
- post = Post.new :subject => 'foo'
- I18n.locale = 'de'
- post.subject = 'baz'
- post.content = 'bar'
- I18n.locale = 'de-DE'
- assert_equal 'foo', post.subject
- assert_equal 'bar', post.content
- end
-
- test "resolves a complex fallback without reloading" do
- I18n.fallbacks.map 'de' => %w(en he)
- I18n.locale = 'de'
- post = Post.new
- I18n.locale = 'en'
- post.subject = 'foo'
- I18n.locale = 'he'
- post.subject = 'baz'
- post.content = 'bar'
- I18n.locale = 'de'
- assert_equal 'foo', post.subject
- assert_equal 'bar', post.content
- end
-
test "returns nil if no translations are found" do
- post = Post.new :subject => 'foo'
+ post = Post.new(:subject => 'foo')
assert_equal 'foo', post.subject
assert_nil post.content
end
test "returns nil if no translations are found; reloaded" do
- post = Post.create :subject => 'foo'
+ post = Post.create(:subject => 'foo')
post = Post.first
assert_equal 'foo', post.subject
assert_nil post.content
end
test "works with associations" do
blog = Blog.create
- post1 = blog.posts.create :subject => 'foo'
+ post1 = blog.posts.create(:subject => 'foo')
I18n.locale = 'de-DE'
- post2 = blog.posts.create :subject => 'bar'
+ post2 = blog.posts.create(:subject => 'bar')
assert_equal 2, blog.posts.size
I18n.locale = 'en-US'
assert_equal 'foo', blog.posts.first.subject
@@ -226,17 +161,17 @@ def teardown
end
test "works with simple dynamic finders" do
- foo = Post.create :subject => 'foo'
- Post.create :subject => 'bar'
+ foo = Post.create(:subject => 'foo')
+ Post.create(:subject => 'bar')
post = Post.find_by_subject('foo')
assert_equal foo, post
end
test 'change attribute on globalized model' do
- post = Post.create :subject => 'foo', :content => 'bar'
+ post = Post.create(:subject => 'foo', :content => 'bar')
assert_equal [], post.changed
post.subject = 'baz'
- assert_equal [ 'subject' ], post.changed
+ assert_equal ['subject'], post.changed
post.content = 'quux'
assert_member 'subject', post.changed
assert_member 'content', post.changed
@@ -250,20 +185,6 @@ def teardown
assert_equal [ 'subject' ], post.changed
end
- test 'fallbacks with lots of locale switching' do
- I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
- post = Post.create :subject => 'foo'
-
- I18n.locale = :'de-DE'
- assert_equal 'foo', post.subject
-
- I18n.locale = :'en-US'
- post.update_attribute :subject, 'bar'
-
- I18n.locale = :'de-DE'
- assert_equal 'bar', post.subject
- end
-
test 'reload' do
post = Post.create :subject => 'foo', :content => 'bar'
post.subject = 'baz'
@@ -309,62 +230,62 @@ def teardown
ActiveRecord::Base.locale = :de
assert_equal :'en-US', I18n.locale
Post.create :subject => 'foo'
- assert_equal :de, Post.first.globalize_translations.first.locale
+ assert_equal :de, Post.first.translations.first.locale
end
test "attribute loading goes by content locale and not global locale" do
post = Post.create :subject => 'foo'
assert_equal :'en-US', ActiveRecord::Base.locale
ActiveRecord::Base.locale = :de
assert_equal :'en-US', I18n.locale
- post.update_attribute :subject, 'foo [de]'
+ post.update_attribute(:subject, 'foo [de]')
assert_equal 'foo [de]', Post.first.subject
ActiveRecord::Base.locale = :'en-US'
assert_equal 'foo', Post.first.subject
end
test "access content locale before setting" do
- Globalize::Model::ActiveRecord::Translated::ActMethods.class_eval "remove_class_variable(:@@locale)"
+ Globalize::ActiveRecord::ActMacro.class_eval "remove_class_variable(:@@locale)"
assert_nothing_raised { ActiveRecord::Base.locale }
end
- test "translated_locales" do
+ test "available_locales" do
Post.locale = :de
post = Post.create :subject => 'foo'
Post.locale = :es
post.update_attribute :subject, 'bar'
Post.locale = :fr
post.update_attribute :subject, 'baz'
- assert_equal [ :de, :es, :fr ], post.translated_locales
- assert_equal [ :de, :es, :fr ], Post.first.translated_locales
+ assert_equal [ :de, :es, :fr ], post.available_locales
+ assert_equal [ :de, :es, :fr ], Post.first.available_locales
end
-
+
test "saving record correctly after post-save reload" do
- reloader = Reloader.create :content => 'foo'
+ reloader = Reloader.create(:content => 'foo')
assert_equal 'foo', reloader.content
end
- test "including globalize_translations" do
+ test "including translations" do
I18n.locale = :de
- Post.create :subject => "Foo1", :content => "Bar1"
- Post.create :subject => "Foo2", :content => "Bar2"
+ Post.create(:subject => "Foo1", :content => "Bar1")
+ Post.create(:subject => "Foo2", :content => "Bar2")
class << Post
- def tranlsations_included
- self.all(:include => :globalize_translations)
+ def translations_included
+ self.all(:include => :translations)
end
end
default = Post.all.map {|x| [x.subject, x.content]}
- with_include = Post.tranlsations_included.map {|x| [x.subject, x.content]}
+ with_include = Post.translations_included.map {|x| [x.subject, x.content]}
assert_equal default, with_include
end
test "setting multiple translations at once with options hash" do
Post.locale = :de
- post = Post.create :subject => "foo1", :content => "foo1"
+ post = Post.create(:subject => "foo1", :content => "foo1")
Post.locale = :en
- post.update_attributes( :subject => "bar1", :content => "bar1" )
+ post.update_attributes(:subject => "bar1", :content => "bar1")
options = { :de => {:subject => "foo2", :content => "foo2"},
:en => {:subject => "bar2", :content => "bar2"} }
@@ -434,7 +355,7 @@ def tranlsations_included
test "translating subclass of untranslated comment model" do
translated_comment = TranslatedComment.create(:post => @post)
- assert_nothing_raised { translated_comment.globalize_translations }
+ assert_nothing_raised { translated_comment.translations }
end
test "modifiying translated comments works as expected" do
@@ -450,9 +371,9 @@ def tranlsations_included
I18n.locale = :en
assert_equal 'foo', translated_comment.content
- assert_equal 2, translated_comment.globalize_translations.size
+ assert_equal 2, translated_comment.translations.size
end
-
+
test "can create a proxy class for a namespaced model" do
module Foo
module Bar
@@ -462,7 +383,7 @@ class Baz < ActiveRecord::Base
end
end
end
-
+
test "attribute translated before type cast" do
Post.locale = :en
post = Post.create :subject => 'foo', :content => 'bar'
@@ -472,14 +393,12 @@ class Baz < ActiveRecord::Base
Post.locale = :en
assert_equal 'foo', post.subject_before_type_cast
end
-
+
test "don't override existing translation model" do
assert PostTranslation.new.respond_to?(:existing_method)
end
end
-# TODO should validate_presence_of take fallbacks into account? maybe we need
-# an extra validation call, or more options for validate_presence_of.
# TODO error checking for fields that exist in main table, don't exist in
# proxy table, aren't strings or text
#
View
175 test/backends/chained_test.rb
@@ -1,175 +0,0 @@
-require File.join( File.dirname(__FILE__), '..', 'test_helper' )
-require 'globalize/backend/chain'
-
-module Globalize
- module Backend
- class Dummy
- def translate(locale, key, options = {})
- end
- end
- end
-end
-
-class ChainedTest < ActiveSupport::TestCase
-
- test "instantiates a chained backend and sets test as backend" do
- assert_nothing_raised { I18n.chain_backends }
- assert_instance_of Globalize::Backend::Chain, I18n.backend
- end
-
- test "passes all given arguments to the chained backends #initialize method" do
- Globalize::Backend::Chain.expects(:new).with(:spec, :simple)
- I18n.chain_backends :spec, :simple
- end
-
- test "passes all given arguments to #add assuming that they are backends" do
- # no idea how to spec that
- end
-end
-
-class AddChainedTest < ActiveSupport::TestCase
- def setup
- I18n.backend = Globalize::Backend::Chain.new
- end
-
- test "accepts an instance of a backend" do
- assert_nothing_raised { I18n.backend.add Globalize::Backend::Dummy.new }
- assert_instance_of Globalize::Backend::Dummy, I18n.backend.send(:backends).first
- end
-
- test "accepts a class and instantiates the backend" do
- assert_nothing_raised { I18n.backend.add Globalize::Backend::Dummy }
- assert_instance_of Globalize::Backend::Dummy, I18n.backend.send(:backends).first
- end
-
- test "accepts a symbol, constantizes test as a backend class and instantiates the backend" do
- assert_nothing_raised { I18n.backend.add :dummy }
- assert_instance_of Globalize::Backend::Dummy, I18n.backend.send(:backends).first
- end
-
- test "accepts any number of backend instances, classes or symbols" do
- assert_nothing_raised { I18n.backend.add Globalize::Backend::Dummy.new, Globalize::Backend::Dummy, :dummy }
- assert_instance_of Globalize::Backend::Dummy, I18n.backend.send(:backends).first
- assert_equal [ Globalize::Backend::Dummy, Globalize::Backend::Dummy, Globalize::Backend::Dummy ],
- I18n.backend.send(:backends).map{|backend| backend.class }
- end
-
-end
-
-class TranslateChainedTest < ActiveSupport::TestCase
- def setup
- I18n.locale = :en
- I18n.backend = Globalize::Backend::Chain.new
- @first_backend = I18n::Backend::Simple.new
- @last_backend = I18n::Backend::Simple.new
- I18n.backend.add @first_backend
- I18n.backend.add @last_backend
- end
-
- test "delegates #translate to all backends in the order they were added" do
- @first_backend.expects(:translate).with(:en, :foo, {})
- @last_backend.expects(:translate).with(:en, :foo, {})
- I18n.translate :foo
- end
-
- test "returns the result from #translate from the first backend if test's not nil" do
- @first_backend.store_translations :en, {:foo => 'foo from first backend'}
- @last_backend.store_translations :en, {:foo => 'foo from last backend'}
- result = I18n.translate :foo
- assert_equal 'foo from first backend', result
- end
-
- test "returns the result from #translate from the second backend if the first one returned nil" do
- @first_backend.store_translations :en, {}
- @last_backend.store_translations :en, {:foo => 'foo from last backend'}
- result = I18n.translate :foo
- assert_equal 'foo from last backend', result
- end
-
- test "looks up a namespace from all backends and merges them (if a result is a hash and no count option is present)" do
- @first_backend.store_translations :en, {:foo => {:bar => 'bar from first backend'}}
- @last_backend.store_translations :en, {:foo => {:baz => 'baz from last backend'}}
- result = I18n.translate :foo
- assert_equal( {:bar => 'bar from first backend', :baz => 'baz from last backend'}, result )
- end
-
- test "raises a MissingTranslationData exception if no translation was found" do
- assert_raise( I18n::MissingTranslationData ) { I18n.translate :not_here, :raise => true }
- end
-
- test "raises an InvalidLocale exception if the locale is nil" do
- assert_raise( I18n::InvalidLocale ) { Globalize::Backend::Chain.new.translate nil, :foo }
- end
-
- test "bulk translates a number of keys from different backends" do
- @first_backend.store_translations :en, {:foo => 'foo from first backend'}
- @last_backend.store_translations :en, {:bar => 'bar from last backend'}
- result = I18n.translate [:foo, :bar]
- assert_equal( ['foo from first backend', 'bar from last backend'], result )
- end
-
- test "still calls #translate on all the backends" do
- @last_backend.expects :translate
- I18n.translate :not_here, :default => 'default'
- end
-
- test "returns a given default string when no backend returns a translation" do
- result = I18n.translate :not_here, :default => 'default'
- assert_equal 'default', result
- end
-
-end
-
-class CustomLocalizeBackend < I18n::Backend::Simple
- def localize(locale, object, format = :default)
- "result from custom localize backend" if locale == 'custom'
- end
-end
-
-class LocalizeChainedTest < ActiveSupport::TestCase
- def setup
- I18n.locale = :en
- I18n.backend = Globalize::Backend::Chain.new
- @first_backend = CustomLocalizeBackend.new
- @last_backend = I18n::Backend::Simple.new
- I18n.backend.add @first_backend
-