hooktstudios-globalize3 0.2.0.beta8

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2008, 2009, 2010 Sven Fuchs, Joshua Harvey, Clemens Kofler, John-Paul Bader
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,205 @@
1
+ h1. Globalize3
2
+
3
+ Globalize3 is the successor of Globalize for Rails. Globalize is targeted at ActiveRecord 3. It is compatible with and builds on the new "I18n API in Ruby on Rails":http://guides.rubyonrails.org/i18n.html and adds model translations to ActiveRecord.
4
+
5
+ Globalize3 is much more lightweight and compatible than its predecessor Globalize for Rails was. Model translations in Globalize3 use default ActiveRecord features and do not limit any ActiveRecord functionality any more.
6
+
7
+ h2. Requirements
8
+
9
+ ActiveRecord > 3.0.0
10
+ I18n
11
+
12
+ h2. Installation
13
+
14
+ To install Globalize3 with its default setup just use:
15
+
16
+ <pre><code>
17
+ $ gem install globalize3
18
+ </code></pre>
19
+
20
+ h2. Model translations
21
+
22
+ Model translations allow you to translate your models' attribute values. E.g.
23
+
24
+ <pre><code>
25
+ class Post < ActiveRecord::Base
26
+ translates :title, :text
27
+ end
28
+ </code></pre>
29
+
30
+ Allows you to translate the attributes :title and :text per locale:
31
+
32
+ <pre><code>
33
+ I18n.locale = :en
34
+ post.title # => Globalize3 rocks!
35
+
36
+ I18n.locale = :he
37
+ post.title # => גלובאלייז2 שולט!
38
+ </code></pre>
39
+
40
+ In order to make this work, you'll need to add the appropriate translation tables. Globalize3 comes with a handy helper method to help you do this. It's called @create_translation_table!@. Here's an example:
41
+
42
+ <em>Note that if you're using rails ~> 3.1.0.rc migrations has only the <tt>change</tt> instance method</em>
43
+
44
+ h3. Rails 3.0
45
+
46
+ <pre><code>
47
+ class CreatePosts < ActiveRecord::Migration
48
+ def self.up
49
+ create_table :posts do |t|
50
+ t.timestamps
51
+ end
52
+ Post.create_translation_table! :title => :string, :text => :text
53
+ end
54
+ def self.down
55
+ drop_table :posts
56
+ Post.drop_translation_table!
57
+ end
58
+ end
59
+ </code></pre>
60
+
61
+ h3. Rails ~> 3.1.0.rc
62
+
63
+ <em>Do not use the change method</em>
64
+
65
+ <pre><code>
66
+ class CreatePosts < ActiveRecord::Migration
67
+ def up
68
+ create_table :posts do |t|
69
+ t.timestamps
70
+ end
71
+ Post.create_translation_table! :title => :string, :text => :text
72
+ end
73
+ def down
74
+ drop_table :posts
75
+ Post.drop_translation_table!
76
+ end
77
+ end
78
+ </code></pre>
79
+
80
+ Note that the ActiveRecord model @Post@ must already exist and have a @translates@ directive listing the translated fields.
81
+
82
+ h2. Migrating existing data to and from the translated version
83
+
84
+ As well as creating a translation table, you can also use @create_translation_table!@ to migrate across any
85
+ existing data to the default locale. This can also operate in reverse to restore any translations from the default locale
86
+ back to the model when you don't want to use a translation table anymore using @drop_translation_table!@
87
+
88
+ This feature makes use of @untranslated_attributes@ which allows access to the model's attributes as they were before
89
+ the translation was applied. Here's an example (which assumes you already have a model called @Post@ and its table exists):
90
+
91
+ <pre><code>
92
+ class TranslatePosts < ActiveRecord::Migration
93
+ def self.up
94
+ Post.create_translation_table!({
95
+ :title => :string,
96
+ :text => :text
97
+ }, {
98
+ :migrate_data => true
99
+ })
100
+ end
101
+ def self.down
102
+ Post.drop_translation_table! :migrate_data => true
103
+ end
104
+ end
105
+ </code></pre>
106
+
107
+ h2. Versioning with Globalize3
108
+
109
+ Globalize3 nicely integrates with
110
+ "paper_trail":https://github.com/airblade/paper_trail. To add versioning
111
+ support to your model, you'll want to add the <code>:versioning => true</code>
112
+ option to your call to <code>translates</code>. An example from our test suite:
113
+
114
+ <pre><code>
115
+ translates :title, :content, :published, :published_at, :versioning => true
116
+ </code></pre>
117
+
118
+ You will also need to have already generated the versions table that paper_trail
119
+ expects. See the paper_trail README for more details.
120
+
121
+ Also, please see the tests in test/globalize3/versioning_test.rb for some current gotchas.
122
+
123
+ h2. I18n fallbacks for empty translations
124
+
125
+ It is possible to enable fallbacks for empty translations. It will depend on the configuration setting you have set for I18n translations in your Rails config.
126
+
127
+ You can enable them by adding the next line to @config/application.rb@ (or only @config/environments/production.rb@ if you only want them in production)
128
+
129
+ <pre><code>config.i18n.fallbacks = true</code></pre>
130
+
131
+ By default, globalize3 will only use fallbacks when your translation model does not exist or the translation value for the item you've requested is @nil@. However it is possible to also use fallbacks for @blank@ translations by adding @:fallbacks_for_empty_translations => true@ to the @translates@ method.
132
+
133
+ <pre><code>
134
+ class Post < ActiveRecord::Base
135
+ translates :title, :name
136
+ end
137
+
138
+ puts post.translations.inspect
139
+ # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize3 rocks!", name: "Globalize3">, #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
140
+
141
+ I18n.locale = :en
142
+ post.title # => 'Globalize3 rocks!'
143
+ post.name # => 'Globalize3'
144
+
145
+ I18n.locale = :nl
146
+ post.title # => ''
147
+ post.name # => 'Globalize3'
148
+ </code></pre>
149
+ <pre><code>
150
+ class Post < ActiveRecord::Base
151
+ translates :title, :name, :fallbacks_for_empty_translations => true
152
+ end
153
+
154
+ puts post.translations.inspect
155
+ # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize3 rocks!", name: "Globalize3">, #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
156
+
157
+ I18n.locale = :en
158
+ post.title # => 'Globalize3 rocks!'
159
+ post.name # => 'Globalize3'
160
+
161
+ I18n.locale = :nl
162
+ post.title # => 'Globalize3 rocks!'
163
+ post.name # => 'Globalize3'
164
+ </code></pre>
165
+
166
+
167
+ h2. Scoping objects by those with translations
168
+
169
+ To only return objects that have a translation for the given locale we can use the `with_translations` scope. This will only return records that have a translations for the passed in locale.
170
+
171
+ <pre><code>
172
+
173
+ Post.with_translations('en') # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize3 rocks!", name: "Globalize3">, #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
174
+
175
+ Post.with_translations(I18n.locale) # => [#<Post::Translation id: 1, post_id: 1, locale: "en", title: "Globalize3 rocks!", name: "Globalize3">, #<Post::Translation id: 2, post_id: 1, locale: "nl", title: '', name: nil>]
176
+
177
+ Post.with_translations('de') # => []
178
+
179
+ </code></pre>
180
+
181
+ h2. Changes since Globalize2
182
+
183
+ * `translation_table_name` was renamed to `translations_table_name`
184
+ * `available_locales` has been removed. please use `translated_locales`
185
+
186
+ h2. Migration from Globalize for Rails (version 1)
187
+
188
+ See this script by Tomasz Stachewicz: http://gist.github.com/120867
189
+
190
+ h2. Alternative Solutions
191
+
192
+ * "Veger's fork":http://github.com/veger/globalize2 - uses default AR schema for the default locale, delegates to the translations table for other locales only
193
+ * "TranslatableColumns":http://github.com/iain/translatable_columns - have multiple languages of the same attribute in a model (Iain Hecker)
194
+ * "localized_record":http://github.com/glennpow/localized_record - allows records to have localized attributes without any modifications to the database (Glenn Powell)
195
+ * "model_translations":http://github.com/janne/model_translations - Minimal implementation of Globalize2 style model translations (Jan Andersson)
196
+
197
+ h2. Related solutions
198
+
199
+ * "globalize2_versioning":http://github.com/joshmh/globalize2_versioning - acts_as_versioned style versioning for globalize2 (Joshua Harvey)
200
+ * "i18n_multi_locales_validations":http://github.com/ZenCocoon/i18n_multi_locales_validations - multi-locales attributes validations to validates attributes from globalize2 translations models (Sébastien Grosjean)
201
+ * "globalize2 Demo App":http://github.com/svenfuchs/globalize2-demo - demo application for globalize2 (Sven Fuchs)</li>
202
+ * "migrate_from_globalize1":http://gist.github.com/120867 - migrate model translations from Globalize1 to globalize2 (Tomasz Stachewicz)</li>
203
+ * "easy_globalize2_accessors":http://github.com/astropanic/easy_globalize2_accessors - easily access (read and write) globalize2-translated fields (astropanic, Tomasz Stachewicz)</li>
204
+ * "globalize2-easy-translate":http://github.com/bsamman/globalize2-easy-translate - adds methods to easily access or set translated attributes to your model (bsamman)</li>
205
+ * "batch_translations":http://github.com/alvarezrilla/batch_translations - allow saving multiple globalize2 translations in the same request (Jose Alvarez Rilla)</li>
@@ -0,0 +1,22 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Run all tests.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'Globalize3'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README')
21
+ rdoc.rdoc_files.include('lib/**/*.rb')
22
+ end
@@ -0,0 +1,52 @@
1
+ require 'active_record'
2
+ require 'patches/active_record/xml_attribute_serializer'
3
+ require 'patches/active_record/query_method'
4
+
5
+ module Globalize
6
+ autoload :ActiveRecord, 'globalize/active_record'
7
+ autoload :Versioning, 'globalize/versioning'
8
+
9
+ class << self
10
+ def locale
11
+ read_locale || I18n.locale
12
+ end
13
+
14
+ def locale=(locale)
15
+ set_locale(locale)
16
+ end
17
+
18
+ def with_locale(locale, &block)
19
+ previous_locale = read_locale
20
+ set_locale(locale)
21
+ result = yield(locale)
22
+ set_locale(previous_locale)
23
+ result
24
+ end
25
+
26
+ def with_locales(*locales, &block)
27
+ locales.flatten.map do |locale|
28
+ with_locale(locale, &block)
29
+ end
30
+ end
31
+
32
+ def fallbacks?
33
+ I18n.respond_to?(:fallbacks)
34
+ end
35
+
36
+ def fallbacks(locale = self.locale)
37
+ fallbacks? ? I18n.fallbacks[locale] : [locale.to_sym]
38
+ end
39
+
40
+ protected
41
+
42
+ def read_locale
43
+ Thread.current[:globalize_locale]
44
+ end
45
+
46
+ def set_locale(locale)
47
+ Thread.current[:globalize_locale] = locale.to_sym rescue nil
48
+ end
49
+ end
50
+ end
51
+
52
+ ActiveRecord::Base.extend(Globalize::ActiveRecord::ActMacro)
@@ -0,0 +1,12 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ autoload :ActMacro, 'globalize/active_record/act_macro'
4
+ autoload :Adapter, 'globalize/active_record/adapter'
5
+ autoload :Attributes, 'globalize/active_record/attributes'
6
+ autoload :ClassMethods, 'globalize/active_record/class_methods'
7
+ autoload :Exceptions, 'globalize/active_record/exceptions'
8
+ autoload :InstanceMethods, 'globalize/active_record/instance_methods'
9
+ autoload :Migration, 'globalize/active_record/migration'
10
+ autoload :Translation, 'globalize/active_record/translation'
11
+ end
12
+ end
@@ -0,0 +1,56 @@
1
+ module Globalize
2
+ module ActiveRecord
3
+ module ActMacro
4
+ def translates(*attr_names)
5
+ return if translates?
6
+
7
+ options = attr_names.extract_options!
8
+ options[:table_name] ||= "#{table_name.singularize}_translations"
9
+
10
+ class_attribute :translated_attribute_names, :translation_options, :fallbacks_for_empty_translations
11
+ self.translated_attribute_names = attr_names.map(&:to_sym)
12
+ self.translation_options = options
13
+ self.fallbacks_for_empty_translations = options[:fallbacks_for_empty_translations]
14
+
15
+ include InstanceMethods
16
+ extend ClassMethods, Migration
17
+
18
+ translation_class.table_name = options[:table_name] if translation_class.table_name.blank?
19
+
20
+ has_many :translations, :class_name => translation_class.name,
21
+ :foreign_key => class_name.foreign_key,
22
+ :dependent => :destroy,
23
+ :extend => HasManyExtensions
24
+
25
+ after_create :save_translations!
26
+ after_update :save_translations!
27
+
28
+ if options[:versioning]
29
+ ::ActiveRecord::Base.extend(Globalize::Versioning::PaperTrail)
30
+
31
+ translation_class.has_paper_trail
32
+ delegate :version, :versions, :to => :translation
33
+ end
34
+
35
+ attr_names.each { |attr_name| translated_attr_accessor(attr_name) }
36
+ end
37
+
38
+ def class_name
39
+ @class_name ||= begin
40
+ class_name = table_name[table_name_prefix.length..-(table_name_suffix.length + 1)].downcase.camelize
41
+ pluralize_table_names ? class_name.singularize : class_name
42
+ end
43
+ end
44
+
45
+ def translates?
46
+ included_modules.include?(InstanceMethods)
47
+ end
48
+ end
49
+
50
+ module HasManyExtensions
51
+ def find_or_initialize_by_locale(locale)
52
+ with_locale(locale.to_s).first || build(:locale => locale.to_s)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,101 @@
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_accessor :record, :stash, :translations
7
+ private :record=, :stash=
8
+
9
+ delegate :translation_class, :to => :'record.class'
10
+
11
+ def initialize(record)
12
+ self.record = record
13
+ self.stash = Attributes.new
14
+ end
15
+
16
+ def fetch_stash(locale, name)
17
+ value = stash.read(locale, name)
18
+ return value if value
19
+ return nil
20
+ end
21
+
22
+ def fetch(locale, name)
23
+ Globalize.fallbacks(locale).each do |fallback|
24
+ value = fetch_stash(fallback, name) || fetch_attribute(fallback, name)
25
+
26
+ unless fallbacks_for?(value)
27
+ set_metadata(value, :locale => fallback, :requested_locale => locale)
28
+ return value
29
+ end
30
+ end
31
+ return nil
32
+ end
33
+
34
+ def write(locale, name, value)
35
+ stash.write(locale, name, value)
36
+ end
37
+
38
+ def save_translations!
39
+ stash.each do |locale, attrs|
40
+ if attrs.any?
41
+ translation = record.translations.find_or_initialize_by_locale(locale.to_s)
42
+ attrs.each { |name, value| translation[name] = value }
43
+ translation.save!
44
+ end
45
+ end
46
+
47
+ record.translations.each(&:reload)
48
+ record.translations.reset
49
+ reset
50
+ end
51
+
52
+ def reset
53
+ stash.clear
54
+ end
55
+
56
+ protected
57
+
58
+ def type_cast(name, value)
59
+ if value.nil?
60
+ nil
61
+ elsif column = column_for_attribute(name)
62
+ column.type_cast(value)
63
+ else
64
+ value
65
+ end
66
+ end
67
+
68
+ def column_for_attribute(name)
69
+ translation_class.columns_hash[name.to_s]
70
+ end
71
+
72
+ def unserializable_attribute?(name, column)
73
+ column.text? && translation_class.serialized_attributes[name.to_s]
74
+ end
75
+
76
+ def fetch_attribute(locale, name)
77
+ translation = record.translation_for(locale)
78
+ return translation && translation.send(name)
79
+ end
80
+
81
+ def set_metadata(object, metadata)
82
+ object.translation_metadata.merge!(meta_data) if object.respond_to?(:translation_metadata)
83
+ object
84
+ end
85
+
86
+ def translation_metadata_accessor(object)
87
+ return if obj.respond_to?(:translation_metadata)
88
+ class << object; attr_accessor :translation_metadata end
89
+ object.translation_metadata ||= {}
90
+ end
91
+
92
+ def fallbacks_for?(object)
93
+ object.nil? || (fallbacks_for_empty_translations? && object.blank?)
94
+ end
95
+
96
+ def fallbacks_for_empty_translations?
97
+ record.fallbacks_for_empty_translations
98
+ end
99
+ end
100
+ end
101
+ end