kriss-gettext_i18n 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. data/CHANGELOG.rdoc +8 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +78 -0
  4. data/VERSION.yml +4 -0
  5. data/gettext_i18n.gemspec +86 -0
  6. data/init.rb +1 -0
  7. data/lib/gettext_i18n.rb +25 -0
  8. data/lib/globalize/backend/gettext.rb +26 -0
  9. data/lib/globalize/i18n.rb +46 -0
  10. data/lib/rails/extend.rb +7 -0
  11. data/lib/ruby/string.rb +15 -0
  12. data/lib/ruby/symbol.rb +15 -0
  13. data/test/gettext_i18n_test.rb +114 -0
  14. data/test/locales/en.yml +2 -0
  15. data/test/locales/pl.yml +6 -0
  16. data/test/locales/root.yml +2 -0
  17. data/test/test_helper.rb +13 -0
  18. data/vendor/globalize2/LICENSE +21 -0
  19. data/vendor/globalize2/README.textile +202 -0
  20. data/vendor/globalize2/Rakefile +22 -0
  21. data/vendor/globalize2/generators/db_backend.rb +0 -0
  22. data/vendor/globalize2/generators/templates/db_backend_migration.rb +25 -0
  23. data/vendor/globalize2/init.rb +10 -0
  24. data/vendor/globalize2/lib/globalize/backend/chain.rb +102 -0
  25. data/vendor/globalize2/lib/globalize/backend/pluralizing.rb +37 -0
  26. data/vendor/globalize2/lib/globalize/backend/static.rb +59 -0
  27. data/vendor/globalize2/lib/globalize/i18n/missing_translations_log_handler.rb +41 -0
  28. data/vendor/globalize2/lib/globalize/i18n/missing_translations_raise_handler.rb +27 -0
  29. data/vendor/globalize2/lib/globalize/load_path.rb +63 -0
  30. data/vendor/globalize2/lib/globalize/locale/fallbacks.rb +63 -0
  31. data/vendor/globalize2/lib/globalize/locale/language_tag.rb +81 -0
  32. data/vendor/globalize2/lib/globalize/model/active_record/adapter.rb +96 -0
  33. data/vendor/globalize2/lib/globalize/model/active_record/translated.rb +161 -0
  34. data/vendor/globalize2/lib/globalize/model/active_record.rb +38 -0
  35. data/vendor/globalize2/lib/globalize/translation.rb +32 -0
  36. data/vendor/globalize2/lib/locale/root.yml +3 -0
  37. data/vendor/globalize2/lib/rails_edge_load_path_patch.rb +40 -0
  38. data/vendor/globalize2/notes.textile +51 -0
  39. data/vendor/globalize2/test/backends/chained_test.rb +175 -0
  40. data/vendor/globalize2/test/backends/pluralizing_test.rb +63 -0
  41. data/vendor/globalize2/test/backends/static_test.rb +143 -0
  42. data/vendor/globalize2/test/data/locale/all.yml +2 -0
  43. data/vendor/globalize2/test/data/locale/de-DE.yml +2 -0
  44. data/vendor/globalize2/test/data/locale/en-US/module.yml +2 -0
  45. data/vendor/globalize2/test/data/locale/en-US.yml +2 -0
  46. data/vendor/globalize2/test/data/locale/fi-FI/module.yml +2 -0
  47. data/vendor/globalize2/test/data/locale/root.yml +0 -0
  48. data/vendor/globalize2/test/data/no_globalize_schema.rb +11 -0
  49. data/vendor/globalize2/test/data/post.rb +24 -0
  50. data/vendor/globalize2/test/data/schema.rb +39 -0
  51. data/vendor/globalize2/test/i18n/missing_translations_test.rb +36 -0
  52. data/vendor/globalize2/test/load_path_test.rb +49 -0
  53. data/vendor/globalize2/test/locale/fallbacks_test.rb +154 -0
  54. data/vendor/globalize2/test/locale/language_tag_test.rb +130 -0
  55. data/vendor/globalize2/test/model/active_record/migration_test.rb +78 -0
  56. data/vendor/globalize2/test/model/active_record/sti_translated_test.rb +75 -0
  57. data/vendor/globalize2/test/model/active_record/translated_test.rb +458 -0
  58. data/vendor/globalize2/test/test_helper.rb +36 -0
  59. data/vendor/globalize2/test/translation_test.rb +54 -0
  60. metadata +121 -0
@@ -0,0 +1,81 @@
1
+ # for specifications see http://en.wikipedia.org/wiki/IETF_language_tag
2
+ #
3
+ # SimpleParser does not implement advanced usages such as grandfathered tags
4
+
5
+ module Globalize
6
+ module Locale
7
+ module Rfc4646
8
+ SUBTAGS = [:language, :script, :region, :variant, :extension, :privateuse, :grandfathered]
9
+ FORMATS = {:language => :downcase, :script => :capitalize, :region => :upcase, :variant => :downcase}
10
+ end
11
+
12
+ class LanguageTag < Struct.new(*Rfc4646::SUBTAGS)
13
+ class << self
14
+ def parser
15
+ @@parser ||= SimpleParser
16
+ end
17
+
18
+ def parser=(parser)
19
+ @@parser = parser
20
+ end
21
+
22
+ def tag(tag)
23
+ matches = parser.match(tag)
24
+ new *matches if matches
25
+ end
26
+ end
27
+
28
+ Rfc4646::FORMATS.each do |name, format|
29
+ define_method(name) { self[name].send(format) unless self[name].nil? }
30
+ end
31
+
32
+ def to_sym
33
+ to_s.to_sym
34
+ end
35
+
36
+ def to_s
37
+ @tag ||= to_a.compact.join("-")
38
+ end
39
+
40
+ def to_a
41
+ members.collect {|attr| self.send(attr) }
42
+ end
43
+
44
+ def parent
45
+ segs = to_a.compact
46
+ segs.length < 2 ? nil : LanguageTag.tag(segs[0..(segs.length-2)].join('-'))
47
+ end
48
+
49
+ def parents(include_self = true)
50
+ result, parent = [], self.dup
51
+ result << parent if include_self
52
+ while parent = parent.parent
53
+ result << parent
54
+ end
55
+ result
56
+ end
57
+
58
+ module SimpleParser
59
+ PATTERN = %r{\A(?:
60
+ ([a-z]{2,3}(?:(?:-[a-z]{3}){0,3})?|[a-z]{4}|[a-z]{5,8}) # language
61
+ (?:-([a-z]{4}))? # script
62
+ (?:-([a-z]{2}|\d{3}))? # region
63
+ (?:-([0-9a-z]{5,8}|\d[0-9a-z]{3}))* # variant
64
+ (?:-([0-9a-wyz](?:-[0-9a-z]{2,8})+))* # extension
65
+ (?:-(x(?:-[0-9a-z]{1,8})+))?| # privateuse subtag
66
+ (x(?:-[0-9a-z]{1,8})+)| # privateuse tag
67
+ /* ([a-z]{1,3}(?:-[0-9a-z]{2,8}){1,2}) */ # grandfathered
68
+ )\z}xi
69
+
70
+ class << self
71
+ def match(tag)
72
+ c = PATTERN.match(tag.to_s).captures
73
+ c[0..4] << (c[5].nil? ? c[6] : c[5]) << c[7] # TODO c[7] is grandfathered, throw a NotImplemented exception here?
74
+ rescue
75
+ false
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,96 @@
1
+ module Globalize
2
+ module Model
3
+ class AttributeStash < Hash
4
+ def contains?(locale, attr_name)
5
+ locale = locale.to_sym
6
+ self[locale] ||= {}
7
+ self[locale].has_key? attr_name
8
+ end
9
+
10
+ def read(locale, attr_name)
11
+ locale = locale.to_sym
12
+ self[locale] ||= {}
13
+ self[locale][attr_name]
14
+ end
15
+
16
+ def write(locale, attr_name, value)
17
+ locale = locale.to_sym
18
+ self[locale] ||= {}
19
+ self[locale][attr_name] = value
20
+ end
21
+ end
22
+
23
+ class Adapter
24
+ def initialize(record)
25
+ @record = record
26
+
27
+ # TODO what exactly are the roles of cache and stash
28
+ @cache = AttributeStash.new
29
+ @stash = AttributeStash.new
30
+ end
31
+
32
+ def fetch(locale, attr_name)
33
+ # locale = I18n.locale
34
+ is_cached = @cache.contains?(locale, attr_name)
35
+ is_cached ? @cache.read(locale, attr_name) : begin
36
+ value = fetch_attribute locale, attr_name
37
+ @cache.write locale, attr_name, value if value && value.locale == locale
38
+ value
39
+ end
40
+ end
41
+
42
+ def stash(locale, attr_name, value)
43
+ @stash.write locale, attr_name, value
44
+ @cache.write locale, attr_name, value
45
+ end
46
+
47
+ def update_translations!
48
+ @stash.each do |locale, attrs|
49
+ translation = @record.globalize_translations.find_or_initialize_by_locale(locale.to_s)
50
+ attrs.each{|attr_name, value| translation[attr_name] = value }
51
+ translation.save!
52
+ end
53
+ @stash.clear
54
+ end
55
+
56
+ # Clears the cache
57
+ def clear
58
+ @cache.clear
59
+ @stash.clear
60
+ end
61
+
62
+ private
63
+
64
+ def fetch_attribute(locale, attr_name)
65
+ fallbacks = I18n.fallbacks[locale].map{|tag| tag.to_s}.map(&:to_sym)
66
+
67
+ # If the translations were included with
68
+ # :include => globalize_translations
69
+ # there is no need to query them again.
70
+ unless @record.globalize_translations.loaded?
71
+ translations = @record.globalize_translations.by_locales(fallbacks)
72
+ else
73
+ translations = @record.globalize_translations
74
+ end
75
+ result, requested_locale = nil, locale
76
+
77
+ # Walk through the fallbacks, starting with the current locale itself, and moving
78
+ # to the next best choice, until we find a match.
79
+ # Check the @globalize_set_translations cache first to see if we've just changed the
80
+ # attribute and not saved yet.
81
+ fallbacks.each do |fallback|
82
+ # TODO should we be checking stash or just cache?
83
+ result = @stash.read(fallback, attr_name) || begin
84
+ translation = translations.detect {|tr| tr.locale == fallback }
85
+ translation && translation.send(attr_name)
86
+ end
87
+ if result
88
+ locale = fallback
89
+ break
90
+ end
91
+ end
92
+ result && Translation::Attribute.new(result, :locale => locale, :requested_locale => requested_locale)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,161 @@
1
+ module Globalize
2
+ module Model
3
+
4
+ class MigrationError < StandardError; end
5
+ class UntranslatedMigrationField < MigrationError; end
6
+ class MigrationMissingTranslatedField < MigrationError; end
7
+ class BadMigrationFieldType < MigrationError; end
8
+
9
+ module ActiveRecord
10
+ module Translated
11
+ def self.included(base)
12
+ base.extend ActMethods
13
+ end
14
+
15
+ module ActMethods
16
+ def translates(*attr_names)
17
+ options = attr_names.extract_options!
18
+ options[:translated_attributes] = attr_names
19
+
20
+ # Only set up once per class
21
+ unless included_modules.include? InstanceMethods
22
+ class_inheritable_accessor :globalize_options, :globalize_proxy
23
+
24
+ include InstanceMethods
25
+ extend ClassMethods
26
+
27
+ self.globalize_proxy = Globalize::Model::ActiveRecord.create_proxy_class(self)
28
+ has_many(
29
+ :globalize_translations,
30
+ :class_name => globalize_proxy.name,
31
+ :extend => Extensions,
32
+ :dependent => :delete_all,
33
+ :foreign_key => class_name.foreign_key
34
+ )
35
+
36
+ after_save :update_globalize_record
37
+ end
38
+
39
+ self.globalize_options = options
40
+ Globalize::Model::ActiveRecord.define_accessors(self, attr_names)
41
+
42
+ # Import any callbacks that have been defined by extensions to Globalize2
43
+ # and run them.
44
+ extend Callbacks
45
+ Callbacks.instance_methods.each {|cb| send cb }
46
+ end
47
+
48
+ def locale=(locale)
49
+ @@locale = locale
50
+ end
51
+
52
+ def locale
53
+ (defined?(@@locale) && @@locale) || I18n.locale
54
+ end
55
+ end
56
+
57
+ # Dummy Callbacks module. Extensions to Globalize2 can insert methods into here
58
+ # and they'll be called at the end of the translates class method.
59
+ module Callbacks
60
+ end
61
+
62
+ # Extension to the has_many :globalize_translations association
63
+ module Extensions
64
+ def by_locales(locales)
65
+ find :all, :conditions => { :locale => locales.map(&:to_s) }
66
+ end
67
+ end
68
+
69
+ module ClassMethods
70
+ def method_missing(method, *args)
71
+ if method.to_s =~ /^find_by_(\w+)$/ && globalize_options[:translated_attributes].include?($1.to_sym)
72
+ find(:first, :joins => :globalize_translations,
73
+ :conditions => [ "#{i18n_attr($1)} = ? AND #{i18n_attr('locale')} IN (?)",
74
+ args.first,I18n.fallbacks[I18n.locale].map{|tag| tag.to_s}])
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ def create_translation_table!(fields)
81
+ translated_fields = self.globalize_options[:translated_attributes]
82
+ translated_fields.each do |f|
83
+ raise MigrationMissingTranslatedField, "Missing translated field #{f}" unless fields[f]
84
+ end
85
+ fields.each do |name, type|
86
+ unless translated_fields.member? name
87
+ raise UntranslatedMigrationField, "Can't migrate untranslated field: #{name}"
88
+ end
89
+ unless [ :string, :text ].member? type
90
+ raise BadMigrationFieldType, "Bad field type for #{name}, should be :string or :text"
91
+ end
92
+ end
93
+ translation_table_name = self.name.underscore + '_translations'
94
+ self.connection.create_table(translation_table_name) do |t|
95
+ t.references self.table_name.singularize
96
+ t.string :locale
97
+ fields.each do |name, type|
98
+ t.column name, type
99
+ end
100
+ t.timestamps
101
+ end
102
+
103
+ self.connection.add_index(
104
+ translation_table_name, "#{self.table_name.singularize}_id"
105
+ )
106
+ end
107
+
108
+ def drop_translation_table!
109
+ translation_table_name = self.name.underscore + '_translations'
110
+ self.connection.remove_index(
111
+ translation_table_name, "#{self.table_name.singularize}_id"
112
+ )
113
+ self.connection.drop_table translation_table_name
114
+ end
115
+
116
+ private
117
+
118
+ def i18n_attr(attribute_name)
119
+ self.base_class.name.underscore + "_translations.#{attribute_name}"
120
+ end
121
+ end
122
+
123
+ module InstanceMethods
124
+ def reload(options = nil)
125
+ globalize.clear
126
+
127
+ # clear all globalized attributes
128
+ # TODO what's the best way to handle this?
129
+ self.class.globalize_options[:translated_attributes].each do |attr|
130
+ @attributes.delete attr.to_s
131
+ end
132
+
133
+ super options
134
+ end
135
+
136
+ def globalize
137
+ @globalize ||= Adapter.new self
138
+ end
139
+
140
+ def update_globalize_record
141
+ globalize.update_translations!
142
+ end
143
+
144
+ def translated_locales
145
+ globalize_translations.scoped(:select => 'DISTINCT locale').map {|gt| gt.locale.to_sym }
146
+ end
147
+
148
+ def set_translations options
149
+ options.keys.each do |key|
150
+
151
+ translation = globalize_translations.find_by_locale(key.to_s) ||
152
+ globalize_translations.build(:locale => key.to_s)
153
+ translation.update_attributes!(options[key])
154
+ end
155
+ end
156
+
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,38 @@
1
+ require 'globalize/translation'
2
+ require 'globalize/locale/fallbacks'
3
+ require 'globalize/model/active_record/adapter'
4
+ require 'globalize/model/active_record/translated'
5
+
6
+ module Globalize
7
+ module Model
8
+ module ActiveRecord
9
+ class << self
10
+ def create_proxy_class(klass)
11
+ Object.const_set "#{klass.name}Translation", Class.new(::ActiveRecord::Base){
12
+ belongs_to "#{klass.name.underscore}".intern
13
+
14
+ def locale
15
+ read_attribute(:locale).to_sym
16
+ end
17
+
18
+ def locale=(locale)
19
+ write_attribute(:locale, locale.to_s)
20
+ end
21
+ }
22
+ end
23
+
24
+ def define_accessors(klass, attr_names)
25
+ attr_names.each do |attr_name|
26
+ klass.send :define_method, attr_name, lambda {
27
+ globalize.fetch self.class.locale, attr_name
28
+ }
29
+ klass.send :define_method, "#{attr_name}=", lambda {|val|
30
+ globalize.stash self.class.locale, attr_name, val
31
+ self[attr_name] = val
32
+ }
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ module Globalize
2
+ # Translations are simple value objects that carry some context information
3
+ # alongside the actual translation string.
4
+
5
+ class Translation < String
6
+ class Attribute < Translation
7
+ attr_accessor :requested_locale, :locale, :key
8
+ end
9
+
10
+ class Static < Translation
11
+ attr_accessor :requested_locale, :locale, :key, :options, :plural_key, :original
12
+
13
+ def initialize(string, meta = nil)
14
+ self.original = string
15
+ super
16
+ end
17
+ end
18
+
19
+ def initialize(string, meta = nil)
20
+ set_meta meta
21
+ super string
22
+ end
23
+
24
+ def fallback?
25
+ locale.to_sym != requested_locale.to_sym
26
+ end
27
+
28
+ def set_meta(meta)
29
+ meta.each {|name, value| send :"#{name}=", value } if meta
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ root:
2
+ bidi:
3
+ direction: left-to-right
@@ -0,0 +1,40 @@
1
+ module I18n
2
+ @@load_path = nil
3
+ @@default_locale = :'en-US'
4
+
5
+ class << self
6
+ def load_path
7
+ @@load_path ||= []
8
+ end
9
+
10
+ def load_path=(load_path)
11
+ @@load_path = load_path
12
+ end
13
+ end
14
+ end
15
+
16
+ I18n::Backend::Simple.module_eval do
17
+ def initialized?
18
+ @initialized ||= false
19
+ end
20
+
21
+ protected
22
+
23
+ def init_translations
24
+ load_translations(*I18n.load_path)
25
+ @initialized = true
26
+ end
27
+
28
+ def lookup(locale, key, scope = [])
29
+ return unless key
30
+ init_translations unless initialized?
31
+ keys = I18n.send :normalize_translation_keys, locale, key, scope
32
+ keys.inject(translations){|result, k| result[k.to_sym] or return nil }
33
+ end
34
+ end
35
+
36
+ rails_dir = File.expand_path "#{File.dirname(__FILE__)}/../../../rails/"
37
+ paths = %w(actionpack/lib/action_view/locale/en-US.yml
38
+ activerecord/lib/active_record/locale/en-US.yml
39
+ activesupport/lib/active_support/locale/en-US.yml)
40
+ paths.each{|path| I18n.load_path << "#{rails_dir}/#{path}" }
@@ -0,0 +1,51 @@
1
+ Stopped DB Backend in the middle, here's where we left off:
2
+
3
+ h1. Some Notes
4
+
5
+ * Started doing the migration generator in generators/db_backend.rb
6
+ * Translation keys will be in dotted string format
7
+ * Question: Do we need a plural_key column, or can we build it in to the dotted key?
8
+ * We will probably have to code the following methods from scratch, to optimize db calls:
9
+ ** translate
10
+ ** localize
11
+ ** pluralize
12
+ * We should refactor @interpolation@ code so that it can be included into backend code without inheriting SimpleBackend
13
+ ** Rationale: interpolation is something done entirely after a string is fetched from the data store
14
+ ** Alternately, it could be done from within the I18n module
15
+
16
+ h1. Schema
17
+
18
+ There will be two db tables.
19
+
20
+ # globalize_translations will have: locale, key, translation, created_at, updated_at.
21
+ # globalize_translations_map will have: key, translation_id.
22
+
23
+ globalize_translations_map will let us easily fetch entire sub-trees of namespaces.
24
+ However, this table may not be necessary, as it may be feasible to just use key LIKE "some.namespace.%".
25
+
26
+ h1. Caching
27
+
28
+ We'll almost certainly want to implement caching in the backend. Should probably be a customized
29
+ implementation based on the Rails caching mechanism, to support memcached, etc.
30
+
31
+ h1. Queries
32
+
33
+ We'll want to pull in lots of stuff at once and return a single translation based on some
34
+ quick Ruby selection. The query will look something like this:
35
+
36
+ <pre>
37
+ <code>
38
+ SELECT * FROM globalize_translations
39
+ WHERE locale in (<fallbacks>) AND
40
+ key IN (key, default_key)
41
+ </code>
42
+ </pre>
43
+
44
+ The Ruby code would then pick the first translation that satisfies a fallback, in fallback order.
45
+ Of course, the records with the supplied key would take precedence of those with the default key.
46
+
47
+ h1. Misc
48
+
49
+ We should revisit the :zero plural code. On the one hand it's certainly useful for
50
+ many apps in many languages. On the other hand it's not mentioned in CLDR, and not a real
51
+ concept in language pluralization. Right now, I'm feeling it's still a good idea to keep it in.
@@ -0,0 +1,175 @@
1
+ require File.join( File.dirname(__FILE__), '..', 'test_helper' )
2
+ require 'globalize/backend/chain'
3
+
4
+ module Globalize
5
+ module Backend
6
+ class Dummy
7
+ def translate(locale, key, options = {})
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ class ChainedTest < ActiveSupport::TestCase
14
+
15
+ test "instantiates a chained backend and sets test as backend" do
16
+ assert_nothing_raised { I18n.chain_backends }
17
+ assert_instance_of Globalize::Backend::Chain, I18n.backend
18
+ end
19
+
20
+ test "passes all given arguments to the chained backends #initialize method" do
21
+ Globalize::Backend::Chain.expects(:new).with(:spec, :simple)
22
+ I18n.chain_backends :spec, :simple
23
+ end
24
+
25
+ test "passes all given arguments to #add assuming that they are backends" do
26
+ # no idea how to spec that
27
+ end
28
+ end
29
+
30
+ class AddChainedTest < ActiveSupport::TestCase
31
+ def setup
32
+ I18n.backend = Globalize::Backend::Chain.new
33
+ end
34
+
35
+ test "accepts an instance of a backend" do
36
+ assert_nothing_raised { I18n.backend.add Globalize::Backend::Dummy.new }
37
+ assert_instance_of Globalize::Backend::Dummy, I18n.backend.send(:backends).first
38
+ end
39
+
40
+ test "accepts a class and instantiates the backend" do
41
+ assert_nothing_raised { I18n.backend.add Globalize::Backend::Dummy }
42
+ assert_instance_of Globalize::Backend::Dummy, I18n.backend.send(:backends).first
43
+ end
44
+
45
+ test "accepts a symbol, constantizes test as a backend class and instantiates the backend" do
46
+ assert_nothing_raised { I18n.backend.add :dummy }
47
+ assert_instance_of Globalize::Backend::Dummy, I18n.backend.send(:backends).first
48
+ end
49
+
50
+ test "accepts any number of backend instances, classes or symbols" do
51
+ assert_nothing_raised { I18n.backend.add Globalize::Backend::Dummy.new, Globalize::Backend::Dummy, :dummy }
52
+ assert_instance_of Globalize::Backend::Dummy, I18n.backend.send(:backends).first
53
+ assert_equal [ Globalize::Backend::Dummy, Globalize::Backend::Dummy, Globalize::Backend::Dummy ],
54
+ I18n.backend.send(:backends).map{|backend| backend.class }
55
+ end
56
+
57
+ end
58
+
59
+ class TranslateChainedTest < ActiveSupport::TestCase
60
+ def setup
61
+ I18n.locale = :en
62
+ I18n.backend = Globalize::Backend::Chain.new
63
+ @first_backend = I18n::Backend::Simple.new
64
+ @last_backend = I18n::Backend::Simple.new
65
+ I18n.backend.add @first_backend
66
+ I18n.backend.add @last_backend
67
+ end
68
+
69
+ test "delegates #translate to all backends in the order they were added" do
70
+ @first_backend.expects(:translate).with(:en, :foo, {})
71
+ @last_backend.expects(:translate).with(:en, :foo, {})
72
+ I18n.translate :foo
73
+ end
74
+
75
+ test "returns the result from #translate from the first backend if test's not nil" do
76
+ @first_backend.store_translations :en, {:foo => 'foo from first backend'}
77
+ @last_backend.store_translations :en, {:foo => 'foo from last backend'}
78
+ result = I18n.translate :foo
79
+ assert_equal 'foo from first backend', result
80
+ end
81
+
82
+ test "returns the result from #translate from the second backend if the first one returned nil" do
83
+ @first_backend.store_translations :en, {}
84
+ @last_backend.store_translations :en, {:foo => 'foo from last backend'}
85
+ result = I18n.translate :foo
86
+ assert_equal 'foo from last backend', result
87
+ end
88
+
89
+ test "looks up a namespace from all backends and merges them (if a result is a hash and no count option is present)" do
90
+ @first_backend.store_translations :en, {:foo => {:bar => 'bar from first backend'}}
91
+ @last_backend.store_translations :en, {:foo => {:baz => 'baz from last backend'}}
92
+ result = I18n.translate :foo
93
+ assert_equal( {:bar => 'bar from first backend', :baz => 'baz from last backend'}, result )
94
+ end
95
+
96
+ test "raises a MissingTranslationData exception if no translation was found" do
97
+ assert_raise( I18n::MissingTranslationData ) { I18n.translate :not_here, :raise => true }
98
+ end
99
+
100
+ test "raises an InvalidLocale exception if the locale is nil" do
101
+ assert_raise( I18n::InvalidLocale ) { Globalize::Backend::Chain.new.translate nil, :foo }
102
+ end
103
+
104
+ test "bulk translates a number of keys from different backends" do
105
+ @first_backend.store_translations :en, {:foo => 'foo from first backend'}
106
+ @last_backend.store_translations :en, {:bar => 'bar from last backend'}
107
+ result = I18n.translate [:foo, :bar]
108
+ assert_equal( ['foo from first backend', 'bar from last backend'], result )
109
+ end
110
+
111
+ test "still calls #translate on all the backends" do
112
+ @last_backend.expects :translate
113
+ I18n.translate :not_here, :default => 'default'
114
+ end
115
+
116
+ test "returns a given default string when no backend returns a translation" do
117
+ result = I18n.translate :not_here, :default => 'default'
118
+ assert_equal 'default', result
119
+ end
120
+
121
+ end
122
+
123
+ class CustomLocalizeBackend < I18n::Backend::Simple
124
+ def localize(locale, object, format = :default)
125
+ "result from custom localize backend" if locale == 'custom'
126
+ end
127
+ end
128
+
129
+ class LocalizeChainedTest < ActiveSupport::TestCase
130
+ def setup
131
+ I18n.locale = :en
132
+ I18n.backend = Globalize::Backend::Chain.new
133
+ @first_backend = CustomLocalizeBackend.new
134
+ @last_backend = I18n::Backend::Simple.new
135
+ I18n.backend.add @first_backend
136
+ I18n.backend.add @last_backend
137
+ @time = Time.now
138
+ end
139
+
140
+ test "delegates #localize to all backends in the order they were added" do
141
+ @first_backend.expects(:localize).with(:en, @time, :default)
142
+ @last_backend.expects(:localize).with(:en, @time, :default)
143
+ I18n.localize @time
144
+ end
145
+
146
+ test "returns the result from #localize from the first backend if test's not nil" do
147
+ @last_backend.expects(:localize).never
148
+ result = I18n.localize @time, :locale => 'custom'
149
+ assert_equal 'result from custom localize backend', result
150
+ end
151
+
152
+ test "returns the result from #localize from the second backend if the first one returned nil" do
153
+ @last_backend.expects(:localize).returns "value from last backend"
154
+ result = I18n.localize @time
155
+ assert_equal 'value from last backend', result
156
+ end
157
+ end
158
+
159
+ class NamespaceChainedTest < ActiveSupport::TestCase
160
+ def setup
161
+ @backend = Globalize::Backend::Chain.new
162
+ end
163
+
164
+ test "returns false if the given result is not a Hash" do
165
+ assert !@backend.send(:namespace_lookup?, 'foo', {})
166
+ end
167
+
168
+ test "returns false if a count option is present" do
169
+ assert !@backend.send(:namespace_lookup?, {:foo => 'foo'}, {:count => 1})
170
+ end
171
+
172
+ test "returns true if the given result is a Hash AND no count option is present" do
173
+ assert @backend.send(:namespace_lookup?, {:foo => 'foo'}, {})
174
+ end
175
+ end