simonmenke-globalize2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/LICENSE +21 -0
  2. data/README.textile +202 -0
  3. data/generators/db_backend.rb +0 -0
  4. data/generators/templates/db_backend_migration.rb +25 -0
  5. data/init.rb +1 -0
  6. data/lib/globalize/backend/chain.rb +102 -0
  7. data/lib/globalize/backend/pluralizing.rb +37 -0
  8. data/lib/globalize/backend/static.rb +60 -0
  9. data/lib/globalize/i18n/missing_translations_log_handler.rb +41 -0
  10. data/lib/globalize/i18n/missing_translations_raise_handler.rb +27 -0
  11. data/lib/globalize/load_path.rb +63 -0
  12. data/lib/globalize/locale/fallbacks.rb +63 -0
  13. data/lib/globalize/locale/language_tag.rb +81 -0
  14. data/lib/globalize/model/active_record.rb +38 -0
  15. data/lib/globalize/model/active_record/adapter.rb +96 -0
  16. data/lib/globalize/model/active_record/translated.rb +154 -0
  17. data/lib/globalize/translation.rb +32 -0
  18. data/lib/locale/root.yml +3 -0
  19. data/lib/rails_edge_load_path_patch.rb +40 -0
  20. data/notes.textile +51 -0
  21. data/rails/init.rb +9 -0
  22. data/test/backends/chained_test.rb +175 -0
  23. data/test/backends/pluralizing_test.rb +63 -0
  24. data/test/backends/static_test.rb +143 -0
  25. data/test/data/locale/all.yml +2 -0
  26. data/test/data/locale/de-DE.yml +2 -0
  27. data/test/data/locale/en-US.yml +2 -0
  28. data/test/data/locale/en-US/module.yml +2 -0
  29. data/test/data/locale/fi-FI/module.yml +2 -0
  30. data/test/data/locale/root.yml +0 -0
  31. data/test/data/no_globalize_schema.rb +11 -0
  32. data/test/data/post.rb +24 -0
  33. data/test/data/schema.rb +39 -0
  34. data/test/i18n/missing_translations_test.rb +36 -0
  35. data/test/load_path_test.rb +49 -0
  36. data/test/locale/fallbacks_test.rb +154 -0
  37. data/test/locale/language_tag_test.rb +130 -0
  38. data/test/model/active_record/migration_test.rb +73 -0
  39. data/test/model/active_record/sti_translated_test.rb +75 -0
  40. data/test/model/active_record/translated_test.rb +458 -0
  41. data/test/test_helper.rb +26 -0
  42. data/test/translation_test.rb +54 -0
  43. metadata +114 -0
@@ -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}" }
data/notes.textile ADDED
@@ -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.
data/rails/init.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'rails_edge_load_path_patch.rb' unless I18n.respond_to?(:load_path)
2
+
3
+ ActiveRecord::Base.send :include, Globalize::Model::ActiveRecord::Translated
4
+
5
+ I18n.backend = Globalize::Backend::Static.new
6
+
7
+ I18n.load_path = Globalize::LoadPath.new I18n.load_path
8
+ I18n.load_path << "#{File.dirname(__FILE__)}/lib/locale"
9
+ I18n.load_path << "#{RAILS_ROOT}/lib/locale"
@@ -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
@@ -0,0 +1,63 @@
1
+ require File.join( File.dirname(__FILE__), '..', 'test_helper' )
2
+ require 'globalize/backend/pluralizing'
3
+
4
+ class PluralizingTest < ActiveSupport::TestCase
5
+ def setup
6
+ @backend = Globalize::Backend::Pluralizing.new
7
+ @cz_pluralizer = lambda{|c| c == 1 ? :one : (2..4).include?(c) ? :few : :other }
8
+ end
9
+
10
+ test "#pluralizer returns the pluralizer for a given locale if defined" do
11
+ assert_instance_of Proc, @backend.pluralizer(:en)
12
+ end
13
+
14
+ test "#pluralizer returns the default pluralizer if no pluralizer is defined for the given locale" do
15
+ assert_equal @backend.pluralizer(:en), @backend.pluralizer(:de)
16
+ end
17
+
18
+ test "#add_pluralizer allows to store a pluralizer per locale" do
19
+ assert_nothing_raised { @backend.add_pluralizer(:cz, @cz_pluralizer) }
20
+ assert_equal @cz_pluralizer, @backend.pluralizer(:cz)
21
+ end
22
+
23
+ end
24
+
25
+ class PluralizePluralizingTest < ActiveSupport::TestCase
26
+ def setup
27
+ @backend = Globalize::Backend::Pluralizing.new
28
+ @cz_pluralizer = lambda{|c| c == 1 ? :one : (2..4).include?(c) ? :few : :other }
29
+ @backend.store_translations :en, :foo => {:one => 'one en foo', :other => 'many en foos'}
30
+ @backend.store_translations :cz, :foo => {:one => 'one cz foo', :few => 'few cz foos', :other => 'many cz foos'}
31
+ end
32
+
33
+ test "looks up the :one translation when count is 1" do
34
+ assert_equal 'one en foo', @backend.translate(:en, :foo, :count => 1)
35
+ end
36
+
37
+ test "looks up the :other translation when count is 2" do
38
+ assert_equal 'many en foos', @backend.translate(:en, :foo, :count => 2)
39
+ end
40
+ end
41
+
42
+ class CzPluralizingTest < ActiveSupport::TestCase
43
+ def setup
44
+ @backend = Globalize::Backend::Pluralizing.new
45
+ @cz_pluralizer = lambda{|c| c == 1 ? :one : (2..4).include?(c) ? :few : :other }
46
+ @backend.store_translations :en, :foo => {:one => 'one en foo', :other => 'many en foos'}
47
+ @backend.store_translations :cz, :foo => {:one => 'one cz foo', :few => 'few cz foos', :other => 'many cz foos'}
48
+ @backend.add_pluralizer(:cz, @cz_pluralizer)
49
+ end
50
+
51
+ test "looks up the :one translation when count is 1 (:cz)" do
52
+ assert_equal 'one cz foo', @backend.translate(:cz, :foo, :count => 1)
53
+ end
54
+
55
+ test "looks up the :few translation when count is 2 (:cz)" do
56
+ assert_equal 'few cz foos', @backend.translate(:cz, :foo, :count => 2)
57
+ end
58
+
59
+ test "looks up the :other translation when count is 5 (:cz)" do
60
+ assert_equal 'many cz foos', @backend.translate(:cz, :foo, :count => 5)
61
+ end
62
+
63
+ end
@@ -0,0 +1,143 @@
1
+ require File.join( File.dirname(__FILE__), '..', 'test_helper' )
2
+ require 'globalize/backend/static'
3
+ require 'globalize/translation'
4
+ require 'action_view'
5
+ include ActionView::Helpers::NumberHelper
6
+
7
+ I18n.locale = :'en-US' # Need to set this, since I18n defaults to 'en'
8
+
9
+ class StaticTest < ActiveSupport::TestCase
10
+ def setup
11
+ I18n.backend = Globalize::Backend::Static.new
12
+ translations = {:"en-US" => {:foo => "foo in en-US", :boz => 'boz', :buz => {:bum => 'bum'}},
13
+ :"en" => {:bar => "bar in en"},
14
+ :"de-DE" => {:baz => "baz in de-DE"},
15
+ :"de" => {:boo => "boo in de", :number => { :currency => { :format => { :unit => '€', :format => '%n %u'}}}}}
16
+ translations.each do |locale, data|
17
+ I18n.backend.store_translations locale, data
18
+ end
19
+ I18n.fallbacks.map :"de-DE" => :"en-US", :he => :en
20
+ end
21
+
22
+ test "returns an instance of Translation:Static" do
23
+ translation = I18n.translate :foo
24
+ assert_instance_of Globalize::Translation::Static, translation
25
+ end
26
+
27
+ test "returns the translation in en-US if present" do
28
+ assert_equal "foo in en-US", I18n.translate(:foo, :locale => :"en-US")
29
+ end
30
+
31
+ test "returns the translation in en if en-US is not present" do
32
+ assert_equal "bar in en", I18n.translate(:bar, :locale => :"en-US")
33
+ end
34
+
35
+ test "returns the translation in de-DE if present" do
36
+ assert_equal "baz in de-DE", I18n.translate(:baz, :locale => :"de-DE")
37
+ end
38
+
39
+ test "returns the translation in de if de-DE is not present" do
40
+ assert_equal "boo in de", I18n.translate(:boo, :locale => :"de-DE")
41
+ end
42
+
43
+ test "returns the translation in en-US if none of de-DE and de are present" do
44
+ assert_equal "foo in en-US", I18n.translate(:foo, :locale => :"de-DE")
45
+ end
46
+
47
+ test "returns the translation in en if none of de-DE, de and en-US are present" do
48
+ assert_equal "bar in en", I18n.translate(:bar, :locale => :"de-DE")
49
+ end
50
+
51
+ test "returns the translation in en if none in he is present" do
52
+ assert_equal "bar in en", I18n.translate(:bar, :locale => :he)
53
+ end
54
+
55
+ test "returns the given default String when the key is not present for any locale" do
56
+ assert_equal "default", I18n.translate(:missing, :default => "default")
57
+ end
58
+
59
+ test "returns the fallback translation for the key if present for a fallback locale" do
60
+ I18n.backend.store_translations :de, :non_default => "non_default in de"
61
+ assert_equal "non_default in de", I18n.translate(:non_default, :default => "default", :locale => :"de-DE")
62
+ end
63
+
64
+ test "returns an array of translations" do
65
+ assert_instance_of Array, I18n.translate([:foo, :boz])
66
+ end
67
+
68
+ test "returns an array of instances of Translation::Static" do
69
+ assert_equal [Globalize::Translation::Static], I18n.translate([:foo, :boz]).map(&:class).uniq
70
+ end
71
+
72
+ test "returns a hash of translations" do
73
+ assert_instance_of Hash, I18n.translate(:"buz")
74
+ end
75
+
76
+ test "returns an array of translations 2" do
77
+ assert_equal [Globalize::Translation::Static], I18n.translate(:"buz").values.map(&:class)
78
+ end
79
+
80
+ test "returns currency properly formated" do
81
+ currency = number_to_currency(10)
82
+ assert_equal "$10.00", currency
83
+ end
84
+
85
+ test "returns currency properly formated for locale" do
86
+ currency = number_to_currency(10, :locale => :'de')
87
+ assert_equal "10.000 €", currency
88
+ end
89
+
90
+ test "returns currency properly formated from parameters" do
91
+ currency = number_to_currency(10, :format => "%n %u", :unit => '€')
92
+ assert_equal "10.00 €", currency
93
+ end
94
+
95
+ test "makes sure interpolation does not break even with False as string" do
96
+ assert_equal "translation missing: en, support, array, skip_last_comma", I18n.translate(:"support.array.skip_last_comma")
97
+ end
98
+ end
99
+
100
+ class TranslationStaticTest < ActiveSupport::TestCase
101
+ def setup
102
+ I18n.backend = Globalize::Backend::Static.new
103
+ translations = {
104
+ :greeting => "Hi {{name}}",
105
+ :messages => { :one => "You have one message.", :other => "You have {{count}} messages."}
106
+ }
107
+ I18n.backend.store_translations :"en", translations
108
+ end
109
+
110
+ def greeting
111
+ I18n.translate :greeting, :locale => :"en-US", :name => "Joshua"
112
+ end
113
+
114
+ test "stores the actual locale" do
115
+ assert_equal :en, greeting.locale
116
+ end
117
+
118
+ test "stores the requested locale" do
119
+ assert_equal :'en-US', greeting.requested_locale
120
+ end
121
+
122
+ test "stores the requested key" do
123
+ assert_equal :greeting, greeting.key
124
+ end
125
+
126
+ test "stores the options given to #translate" do
127
+ assert_equal( {:name => "Joshua"}, greeting.options )
128
+ end
129
+
130
+ test "stores the original translation before test was interpolated" do
131
+ assert_equal "Hi {{name}}", greeting.original
132
+ end
133
+
134
+ test "stores the plural_key :one if pluralized as such" do
135
+ message = I18n.translate :messages, :locale => :"en-US", :count => 1
136
+ assert_equal :one, message.plural_key
137
+ end
138
+
139
+ test "stores the plural_key :other if pluralized as such" do
140
+ messages = I18n.translate :messages, :locale => :"en-US", :count => 2
141
+ assert_equal :other, messages.plural_key
142
+ end
143
+ end