globalize2 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/README.textile +27 -143
- data/VERSION +1 -1
- data/generators/templates/db_backend_migration.rb +3 -3
- data/globalize2.gemspec +30 -49
- data/init.rb +1 -8
- data/lib/globalize.rb +15 -0
- data/lib/globalize/active_record.rb +195 -0
- data/lib/globalize/active_record/adapter.rb +80 -0
- data/lib/globalize/active_record/attributes.rb +25 -0
- data/lib/globalize/active_record/migration.rb +40 -0
- data/lib/{globalize/i18n → i18n}/missing_translations_log_handler.rb +8 -8
- data/lib/{globalize/i18n → i18n}/missing_translations_raise_handler.rb +3 -5
- data/test/active_record/fallbacks_test.rb +102 -0
- data/test/{model/active_record → active_record}/migration_test.rb +21 -26
- data/test/{model/active_record → active_record}/sti_translated_test.rb +4 -30
- data/test/active_record/translates_test.rb +87 -0
- data/test/active_record/translation_class_test.rb +30 -0
- data/test/active_record/validation_tests.rb +75 -0
- data/test/active_record_test.rb +451 -0
- data/test/data/models.rb +16 -0
- data/test/data/schema.rb +23 -7
- data/test/i18n/missing_translations_test.rb +6 -6
- data/test/test_helper.rb +55 -15
- metadata +33 -46
- data/lib/globalize/backend/chain.rb +0 -102
- data/lib/globalize/backend/pluralizing.rb +0 -37
- data/lib/globalize/backend/static.rb +0 -61
- data/lib/globalize/load_path.rb +0 -63
- data/lib/globalize/locale/fallbacks.rb +0 -63
- data/lib/globalize/locale/language_tag.rb +0 -81
- data/lib/globalize/model/active_record.rb +0 -56
- data/lib/globalize/model/active_record/adapter.rb +0 -100
- data/lib/globalize/model/active_record/translated.rb +0 -174
- data/lib/globalize/translation.rb +0 -32
- data/lib/locale/root.yml +0 -3
- data/lib/rails_edge_load_path_patch.rb +0 -40
- data/notes.textile +0 -51
- data/test/backends/chained_test.rb +0 -175
- data/test/backends/pluralizing_test.rb +0 -63
- data/test/backends/static_test.rb +0 -147
- data/test/data/locale/all.yml +0 -2
- data/test/data/locale/de-DE.yml +0 -2
- data/test/data/locale/en-US.yml +0 -2
- data/test/data/locale/en-US/module.yml +0 -2
- data/test/data/locale/fi-FI/module.yml +0 -2
- data/test/data/locale/root.yml +0 -0
- data/test/load_path_test.rb +0 -49
- data/test/locale/fallbacks_test.rb +0 -154
- data/test/locale/language_tag_test.rb +0 -130
- data/test/model/active_record/translated_test.rb +0 -487
- data/test/translation_test.rb +0 -54
@@ -0,0 +1,80 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
class Adapter
|
4
|
+
# The cache caches attributes that already were looked up for read access.
|
5
|
+
# The stash keeps track of new or changed values that need to be saved.
|
6
|
+
attr_reader :record, :cache, :stash
|
7
|
+
|
8
|
+
def initialize(record)
|
9
|
+
@record = record
|
10
|
+
@cache = Attributes.new
|
11
|
+
@stash = Attributes.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch(locale, attr_name)
|
15
|
+
cache.contains?(locale, attr_name) ?
|
16
|
+
cache.read(locale, attr_name) :
|
17
|
+
cache.write(locale, attr_name, fetch_attribute(locale, attr_name))
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(locale, attr_name, value)
|
21
|
+
stash.write(locale, attr_name, value)
|
22
|
+
cache.write(locale, attr_name, value)
|
23
|
+
end
|
24
|
+
|
25
|
+
def save_translations!
|
26
|
+
stash.each do |locale, attrs|
|
27
|
+
translation = record.translations.find_or_initialize_by_locale(locale.to_s)
|
28
|
+
attrs.each { |attr_name, value| translation[attr_name] = value }
|
29
|
+
translation.save!
|
30
|
+
end
|
31
|
+
stash.clear
|
32
|
+
end
|
33
|
+
|
34
|
+
def reset
|
35
|
+
cache.clear
|
36
|
+
# stash.clear
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def fetch_translation(locale)
|
42
|
+
locale = locale.to_sym
|
43
|
+
record.translations.loaded? ? record.translations.detect { |t| t.locale == locale } :
|
44
|
+
record.translations.by_locale(locale)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_translations(locale)
|
48
|
+
# only query if not already included with :include => translations
|
49
|
+
record.translations.loaded? ? record.translations :
|
50
|
+
record.translations.by_locales(Globalize.fallbacks(locale))
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_attribute(locale, attr_name)
|
54
|
+
translations = fetch_translations(locale)
|
55
|
+
value, requested_locale = nil, locale
|
56
|
+
|
57
|
+
Globalize.fallbacks(locale).each do |fallback|
|
58
|
+
translation = translations.detect { |t| t.locale == fallback }
|
59
|
+
value = translation && translation.send(attr_name)
|
60
|
+
locale = fallback && break if value
|
61
|
+
end
|
62
|
+
|
63
|
+
set_metadata(value, :locale => locale, :requested_locale => requested_locale)
|
64
|
+
value
|
65
|
+
end
|
66
|
+
|
67
|
+
def set_metadata(object, metadata)
|
68
|
+
if object.respond_to?(:translation_metadata)
|
69
|
+
object.translation_metadata.merge!(meta_data)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def translation_metadata_accessor(object)
|
74
|
+
return if obj.respond_to?(:translation_metadata)
|
75
|
+
class << object; attr_accessor :translation_metadata end
|
76
|
+
object.translation_metadata ||= {}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# Helper class for storing values per locale. Used by Globalize::Adapter
|
2
|
+
# to stash and cache attribute values.
|
3
|
+
module Globalize
|
4
|
+
module ActiveRecord
|
5
|
+
class Attributes < Hash
|
6
|
+
def [](locale)
|
7
|
+
locale = locale.to_sym
|
8
|
+
self[locale] = {} unless has_key?(locale)
|
9
|
+
self.fetch(locale)
|
10
|
+
end
|
11
|
+
|
12
|
+
def contains?(locale, attr_name)
|
13
|
+
self[locale].has_key?(attr_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def read(locale, attr_name)
|
17
|
+
self[locale][attr_name]
|
18
|
+
end
|
19
|
+
|
20
|
+
def write(locale, attr_name, value)
|
21
|
+
self[locale][attr_name] = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Globalize
|
2
|
+
module ActiveRecord
|
3
|
+
module Migration
|
4
|
+
def create_translation_table!(fields)
|
5
|
+
translated_attribute_names.each do |f|
|
6
|
+
raise MigrationMissingTranslatedField, "Missing translated field #{f}" unless fields[f]
|
7
|
+
end
|
8
|
+
|
9
|
+
fields.each do |name, type|
|
10
|
+
if translated_attribute_names.include?(name) && ![:string, :text].include?(type)
|
11
|
+
raise BadMigrationFieldType, "Bad field type for #{name}, should be :string or :text"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
self.connection.create_table(translation_table_name) do |t|
|
16
|
+
t.references self.table_name.singularize
|
17
|
+
t.string :locale
|
18
|
+
fields.each do |name, type|
|
19
|
+
t.column name, type
|
20
|
+
end
|
21
|
+
t.timestamps
|
22
|
+
end
|
23
|
+
|
24
|
+
self.connection.add_index(translation_table_name, "#{self.table_name.singularize}_id", :name => translation_index_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
def translation_index_name
|
28
|
+
require 'digest/sha1'
|
29
|
+
# FIXME what's the max size of an index name?
|
30
|
+
index_name = "index_#{translation_table_name}_on_#{self.table_name.singularize}_id"
|
31
|
+
index_name.size < 50 ? index_name : "index_#{Digest::SHA1.hexdigest(index_name)}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def drop_translation_table!
|
35
|
+
self.connection.remove_index(translation_table_name, :name => translation_index_name) rescue nil
|
36
|
+
self.connection.drop_table(translation_table_name)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
# but additionally logs missing translations to a given log.
|
3
3
|
#
|
4
4
|
# Useful for identifying missing translations during testing.
|
5
|
-
#
|
6
|
-
# E.g.
|
7
5
|
#
|
8
|
-
#
|
6
|
+
# E.g.
|
7
|
+
#
|
8
|
+
# require 'globalize/i18n/missing_translations_log_handler'
|
9
9
|
# I18n.missing_translations_logger = RAILS_DEFAULT_LOGGER
|
10
10
|
# I18n.exception_handler = :missing_translations_log_handler
|
11
11
|
#
|
@@ -16,7 +16,7 @@
|
|
16
16
|
|
17
17
|
module I18n
|
18
18
|
@@missing_translations_logger = nil
|
19
|
-
|
19
|
+
|
20
20
|
class << self
|
21
21
|
def missing_translations_logger
|
22
22
|
@@missing_translations_logger ||= begin
|
@@ -24,18 +24,18 @@ module I18n
|
|
24
24
|
Logger.new(STDOUT)
|
25
25
|
end
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
def missing_translations_logger=(logger)
|
29
29
|
@@missing_translations_logger = logger
|
30
30
|
end
|
31
|
-
|
31
|
+
|
32
32
|
def missing_translations_log_handler(exception, locale, key, options)
|
33
33
|
if MissingTranslationData === exception
|
34
34
|
missing_translations_logger.warn(exception.message)
|
35
|
-
return exception.message
|
35
|
+
return exception.message
|
36
36
|
else
|
37
37
|
raise exception
|
38
38
|
end
|
39
39
|
end
|
40
40
|
end
|
41
|
-
end
|
41
|
+
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
# but also raises on missing translations.
|
3
3
|
#
|
4
4
|
# Useful for identifying missing translations during testing.
|
5
|
-
#
|
6
|
-
# E.g.
|
7
5
|
#
|
8
|
-
#
|
6
|
+
# E.g.
|
7
|
+
#
|
8
|
+
# require 'globalize/i18n/missing_translations_raise_handler'
|
9
9
|
# I18n.exception_handler = :missing_translations_raise_handler
|
10
10
|
module I18n
|
11
11
|
class << self
|
@@ -13,8 +13,6 @@ module I18n
|
|
13
13
|
raise exception
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
# self.exception_handler = :missing_translations_raise_handler
|
18
16
|
end
|
19
17
|
|
20
18
|
I18n.exception_handler = :missing_translations_raise_handler
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../data/models')
|
3
|
+
|
4
|
+
if I18n.respond_to?(:fallbacks)
|
5
|
+
class TranslatedTest < ActiveSupport::TestCase
|
6
|
+
def setup
|
7
|
+
I18n.locale = :'en-US'
|
8
|
+
I18n.fallbacks.clear
|
9
|
+
reset_db!
|
10
|
+
ActiveRecord::Base.locale = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
I18n.fallbacks.clear
|
15
|
+
end
|
16
|
+
|
17
|
+
test "keeping one field in new locale when other field is changed" do
|
18
|
+
I18n.fallbacks.map 'de-DE' => [ 'en-US' ]
|
19
|
+
post = Post.create :subject => 'foo'
|
20
|
+
I18n.locale = 'de-DE'
|
21
|
+
post.content = 'bar'
|
22
|
+
assert_equal 'foo', post.subject
|
23
|
+
end
|
24
|
+
|
25
|
+
test "modifying non-required field in a new locale" do
|
26
|
+
I18n.fallbacks.map 'de-DE' => [ 'en-US' ]
|
27
|
+
post = Post.create :subject => 'foo'
|
28
|
+
I18n.locale = 'de-DE'
|
29
|
+
post.content = 'bar'
|
30
|
+
assert post.save
|
31
|
+
end
|
32
|
+
|
33
|
+
test "resolves a simple fallback" do
|
34
|
+
I18n.locale = 'de-DE'
|
35
|
+
post = Post.create :subject => 'foo'
|
36
|
+
I18n.locale = 'de'
|
37
|
+
post.subject = 'baz'
|
38
|
+
post.content = 'bar'
|
39
|
+
post.save
|
40
|
+
I18n.locale = 'de-DE'
|
41
|
+
assert_equal 'foo', post.subject
|
42
|
+
assert_equal 'bar', post.content
|
43
|
+
end
|
44
|
+
|
45
|
+
test "resolves a simple fallback without reloading" do
|
46
|
+
I18n.locale = 'de-DE'
|
47
|
+
post = Post.new :subject => 'foo'
|
48
|
+
I18n.locale = 'de'
|
49
|
+
post.subject = 'baz'
|
50
|
+
post.content = 'bar'
|
51
|
+
I18n.locale = 'de-DE'
|
52
|
+
assert_equal 'foo', post.subject
|
53
|
+
assert_equal 'bar', post.content
|
54
|
+
end
|
55
|
+
|
56
|
+
test "resolves a complex fallback without reloading" do
|
57
|
+
I18n.fallbacks.map 'de' => %w(en he)
|
58
|
+
I18n.locale = 'de'
|
59
|
+
post = Post.new
|
60
|
+
I18n.locale = 'en'
|
61
|
+
post.subject = 'foo'
|
62
|
+
I18n.locale = 'he'
|
63
|
+
post.subject = 'baz'
|
64
|
+
post.content = 'bar'
|
65
|
+
I18n.locale = 'de'
|
66
|
+
assert_equal 'foo', post.subject
|
67
|
+
assert_equal 'bar', post.content
|
68
|
+
end
|
69
|
+
|
70
|
+
test 'fallbacks with lots of locale switching' do
|
71
|
+
I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
|
72
|
+
post = Post.create :subject => 'foo'
|
73
|
+
|
74
|
+
I18n.locale = :'de-DE'
|
75
|
+
assert_equal 'foo', post.subject
|
76
|
+
|
77
|
+
I18n.locale = :'en-US'
|
78
|
+
post.update_attribute :subject, 'bar'
|
79
|
+
|
80
|
+
I18n.locale = :'de-DE'
|
81
|
+
assert_equal 'bar', post.subject
|
82
|
+
end
|
83
|
+
|
84
|
+
test 'fallbacks with lots of locale switching' do
|
85
|
+
I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
|
86
|
+
child = Child.create :content => 'foo'
|
87
|
+
|
88
|
+
I18n.locale = :'de-DE'
|
89
|
+
assert_equal 'foo', child.content
|
90
|
+
|
91
|
+
I18n.locale = :'en-US'
|
92
|
+
child.update_attribute :content, 'bar'
|
93
|
+
|
94
|
+
I18n.locale = :'de-DE'
|
95
|
+
assert_equal 'bar', child.content
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# TODO should validate_presence_of take fallbacks into account? maybe we need
|
101
|
+
# an extra validation call, or more options for validate_presence_of.
|
102
|
+
|
@@ -1,36 +1,32 @@
|
|
1
|
-
require File.
|
2
|
-
require '
|
3
|
-
require 'globalize/model/active_record'
|
4
|
-
|
5
|
-
# Hook up model translation
|
6
|
-
ActiveRecord::Base.send(:include, Globalize::Model::ActiveRecord::Translated)
|
7
|
-
|
8
|
-
# Load Post model
|
9
|
-
require File.join( File.dirname(__FILE__), '..', '..', 'data', 'models' )
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../data/models')
|
10
3
|
|
11
4
|
class MigrationTest < ActiveSupport::TestCase
|
12
5
|
def setup
|
13
|
-
reset_db!
|
6
|
+
reset_db!
|
7
|
+
Post.drop_translation_table!
|
14
8
|
end
|
15
9
|
|
16
10
|
test 'globalize table added' do
|
17
|
-
assert !Post.connection.table_exists?(
|
18
|
-
assert !Post.connection.index_exists?(
|
19
|
-
|
20
|
-
|
21
|
-
assert Post.connection.
|
22
|
-
|
23
|
-
|
11
|
+
assert !Post.connection.table_exists?(:post_translations)
|
12
|
+
assert !Post.connection.index_exists?(:post_translations, :post_id)
|
13
|
+
|
14
|
+
Post.create_translation_table!(:subject => :string, :content => :text)
|
15
|
+
assert Post.connection.table_exists?(:post_translations)
|
16
|
+
assert Post.connection.index_exists?(:post_translations, :post_id)
|
17
|
+
|
18
|
+
columns = Post.connection.columns(:post_translations)
|
19
|
+
assert locale = columns.detect { |c| c.name == 'locale' }
|
24
20
|
assert_equal :string, locale.type
|
25
|
-
assert subject = columns.detect {|c| c.name == 'subject' }
|
21
|
+
assert subject = columns.detect { |c| c.name == 'subject' }
|
26
22
|
assert_equal :string, subject.type
|
27
|
-
assert content = columns.detect {|c| c.name == 'content' }
|
23
|
+
assert content = columns.detect { |c| c.name == 'content' }
|
28
24
|
assert_equal :text, content.type
|
29
|
-
assert post_id = columns.detect {|c| c.name == 'post_id' }
|
25
|
+
assert post_id = columns.detect { |c| c.name == 'post_id' }
|
30
26
|
assert_equal :integer, post_id.type
|
31
|
-
assert created_at = columns.detect {|c| c.name == 'created_at' }
|
27
|
+
assert created_at = columns.detect { |c| c.name == 'created_at' }
|
32
28
|
assert_equal :datetime, created_at.type
|
33
|
-
assert updated_at = columns.detect {|c| c.name == 'updated_at' }
|
29
|
+
assert updated_at = columns.detect { |c| c.name == 'updated_at' }
|
34
30
|
assert_equal :datetime, updated_at.type
|
35
31
|
end
|
36
32
|
|
@@ -46,13 +42,13 @@ class MigrationTest < ActiveSupport::TestCase
|
|
46
42
|
end
|
47
43
|
|
48
44
|
test 'exception on missing field inputs' do
|
49
|
-
assert_raise Globalize::
|
45
|
+
assert_raise Globalize::MigrationMissingTranslatedField do
|
50
46
|
Post.create_translation_table! :content => :text
|
51
47
|
end
|
52
48
|
end
|
53
49
|
|
54
50
|
test 'exception on bad input type' do
|
55
|
-
assert_raise Globalize::
|
51
|
+
assert_raise Globalize::BadMigrationFieldType do
|
56
52
|
Post.create_translation_table! :subject => :string, :content => :integer
|
57
53
|
end
|
58
54
|
end
|
@@ -104,16 +100,15 @@ class MigrationTest < ActiveSupport::TestCase
|
|
104
100
|
end
|
105
101
|
|
106
102
|
test 'globalize table dropped when table has long name' do
|
103
|
+
UltraLongModelNameWithoutProper.drop_translation_table!
|
107
104
|
UltraLongModelNameWithoutProper.create_translation_table!(
|
108
105
|
:subject => :string, :content => :text
|
109
106
|
)
|
110
|
-
|
111
107
|
UltraLongModelNameWithoutProper.drop_translation_table!
|
112
108
|
|
113
109
|
assert !UltraLongModelNameWithoutProper.connection.table_exists?(
|
114
110
|
:ultra_long_model_name_without_proper_translations
|
115
111
|
)
|
116
|
-
|
117
112
|
assert !UltraLongModelNameWithoutProper.connection.index_exists?(
|
118
113
|
:ultra_long_model_name_without_proper_translations,
|
119
114
|
:ultra_long_model_name_without_proper_id
|
@@ -1,22 +1,10 @@
|
|
1
|
-
require File.
|
2
|
-
require '
|
3
|
-
require 'globalize/model/active_record'
|
4
|
-
|
5
|
-
# Hook up model translation
|
6
|
-
ActiveRecord::Base.send(:include, Globalize::Model::ActiveRecord::Translated)
|
7
|
-
|
8
|
-
# Load Post model
|
9
|
-
require File.join( File.dirname(__FILE__), '..', '..', 'data', 'models' )
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + '/../data/models')
|
10
3
|
|
11
4
|
class StiTranslatedTest < ActiveSupport::TestCase
|
12
5
|
def setup
|
13
6
|
I18n.locale = :'en-US'
|
14
|
-
|
15
|
-
reset_db! File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'data', 'schema.rb'))
|
16
|
-
end
|
17
|
-
|
18
|
-
def teardown
|
19
|
-
I18n.fallbacks.clear
|
7
|
+
reset_db!
|
20
8
|
end
|
21
9
|
|
22
10
|
test "works with simple dynamic finders" do
|
@@ -43,20 +31,6 @@ class StiTranslatedTest < ActiveSupport::TestCase
|
|
43
31
|
assert_equal [ 'content' ], child.changed
|
44
32
|
end
|
45
33
|
|
46
|
-
test 'fallbacks with lots of locale switching' do
|
47
|
-
I18n.fallbacks.map :'de-DE' => [ :'en-US' ]
|
48
|
-
child = Child.create :content => 'foo'
|
49
|
-
|
50
|
-
I18n.locale = :'de-DE'
|
51
|
-
assert_equal 'foo', child.content
|
52
|
-
|
53
|
-
I18n.locale = :'en-US'
|
54
|
-
child.update_attribute :content, 'bar'
|
55
|
-
|
56
|
-
I18n.locale = :'de-DE'
|
57
|
-
assert_equal 'bar', child.content
|
58
|
-
end
|
59
|
-
|
60
34
|
test "saves all locales, even after locale switching" do
|
61
35
|
child = Child.new :content => 'foo'
|
62
36
|
I18n.locale = 'de-DE'
|
@@ -72,4 +46,4 @@ class StiTranslatedTest < ActiveSupport::TestCase
|
|
72
46
|
I18n.locale = 'he-IL'
|
73
47
|
assert_equal 'baz', child.content
|
74
48
|
end
|
75
|
-
end
|
49
|
+
end
|