lolita-translation 0.0.4 → 0.1.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.
- data/.document +5 -5
- data/Gemfile +9 -9
- data/LICENSE.txt +20 -20
- data/README.md +43 -44
- data/Rakefile +53 -53
- data/VERSION +5 -1
- data/app/views/components/lolita/translation/_assets.html.erb +7 -0
- data/app/views/components/lolita/translation/_language_wrap.html.erb +4 -0
- data/app/views/components/lolita/translation/_switch.html.erb +8 -0
- data/config/locales/en.yml +8 -8
- data/lib/generators/lolita_translation/USAGE +8 -8
- data/lib/generators/lolita_translation/assets_generator.rb +13 -0
- data/lib/generators/lolita_translation/has_translations_generator.rb +8 -8
- data/lib/generators/lolita_translation/install_generator.rb +13 -0
- data/lib/lolita-translation.rb +59 -11
- data/lib/lolita-translation/has_translations.rb +298 -294
- data/lib/lolita-translation/rails.rb +4 -4
- data/lib/lolita-translation/string.rb +18 -18
- data/lib/tasks/has_translations_tasks.rake +4 -4
- data/lolita-translation.gemspec +13 -9
- data/public/javascripts/lolita/translation.js +10 -0
- data/public/stylesheets/lolita/translation.css +18 -0
- data/spec/has_translations_spec.rb +43 -43
- data/spec/spec.opts +2 -2
- data/spec/spec_helper.rb +98 -98
- metadata +21 -19
- data/app/views/components/lolita/configuration/tab/translation/_display.html.erb +0 -78
- data/lib/lolita-translation/configuration/tab/translation.rb +0 -14
- data/lolita-translations.gemspec +0 -60
@@ -1,295 +1,299 @@
|
|
1
|
-
require 'stringio'
|
2
|
-
|
3
|
-
class ActiveRecord::Base
|
4
|
-
# Provides ability to add the translations for the model using delegate pattern.
|
5
|
-
# Uses has_many association to the ModelNameTranslation.
|
6
|
-
#
|
7
|
-
# For example you have model Article with attributes title and text.
|
8
|
-
# You want that attributes title and text to be translated.
|
9
|
-
# For this reason you need to generate new model ArticleTranslation.
|
10
|
-
# In migration you need to add:
|
11
|
-
#
|
12
|
-
# create_table :article_translations do |t|
|
13
|
-
# t.references :article, :null => false
|
14
|
-
# t.string :locale, :length => 2, :null => false
|
15
|
-
# t.string :name, :null => false
|
16
|
-
# end
|
17
|
-
#
|
18
|
-
# add_index :articles, [:article_id, :locale], :unique => true, :name => 'unique_locale_for_article_id'
|
19
|
-
#
|
20
|
-
# And in the Article model:
|
21
|
-
#
|
22
|
-
# translations :title, :text
|
23
|
-
#
|
24
|
-
# This will adds:
|
25
|
-
#
|
26
|
-
# * named_scope (translated) and has_many association to the Article model
|
27
|
-
# * locale presence validation to the ArticleTranslation model.
|
28
|
-
#
|
29
|
-
# Notice: if you want to have validates_presence_of :article, you should use :inverse_of.
|
30
|
-
# Support this by yourself. Better is always to use artile.translations.build() method.
|
31
|
-
#
|
32
|
-
# For more information please read API. Feel free to write me an email to:
|
33
|
-
# dmitry.polushkin@gmail.com.
|
34
|
-
#
|
35
|
-
# ===
|
36
|
-
#
|
37
|
-
# You also can pass attributes and options to the translations class method:
|
38
|
-
#
|
39
|
-
# translations :title, :text, :fallback => true, :writer => true, :nil => nil
|
40
|
-
#
|
41
|
-
# ===
|
42
|
-
#
|
43
|
-
# Configuration options:
|
44
|
-
#
|
45
|
-
# * <tt>:fallback</tt> - if translation for the current locale not found.
|
46
|
-
# By default true.
|
47
|
-
# Uses algorithm of fallback:
|
48
|
-
# 0) current translation (using I18n.locale);
|
49
|
-
# 1) default locale (using I18n.default_locale);
|
50
|
-
# 2) :nil value (see <tt>:nil</tt> configuration option)
|
51
|
-
# * <tt>:reader</tt> - add reader attributes to the model and delegate them
|
52
|
-
# to the translation model columns. Add's fallback if it is set to true.
|
53
|
-
# * <tt>:writer</tt> - add writer attributes to the model and assign them
|
54
|
-
# to the translation model attributes.
|
55
|
-
# * <tt>:nil</tt> - when reader cant find string, it returns by default an
|
56
|
-
# empty string. If you want to change this setting for example to nil,
|
57
|
-
# add :nil => nil
|
58
|
-
#
|
59
|
-
# ===
|
60
|
-
#
|
61
|
-
# When you are using <tt>:writer</tt> option, you can create translations using
|
62
|
-
# update_attributes method. For example:
|
63
|
-
#
|
64
|
-
# Article.create!
|
65
|
-
# Article.update_attributes(:title => 'title', :text => 'text')
|
66
|
-
#
|
67
|
-
# ===
|
68
|
-
#
|
69
|
-
# <tt>translated</tt> named_scope is useful when you want to find only those
|
70
|
-
# records that are translated to a specific locale.
|
71
|
-
# For example if you want to find all Articles that is translated to an english
|
72
|
-
# language, you can write: Article.translated(:en)
|
73
|
-
#
|
74
|
-
# <tt>has_translation?(locale)</tt> method, that returns true if object's model
|
75
|
-
# have a translation for a specified locale
|
76
|
-
#
|
77
|
-
# <tt>translation(locale)</tt> method finds translation with specified locale.
|
78
|
-
#
|
79
|
-
# <tt>all_translations</tt> method that returns all possible translations in
|
80
|
-
# ordered hash (useful when creating forms with nested attributes).
|
81
|
-
def self.translations(*attrs)
|
82
|
-
options = {
|
83
|
-
:fallback => true,
|
84
|
-
:reader => true,
|
85
|
-
:writer => false,
|
86
|
-
:nil => ''
|
87
|
-
}.merge(attrs.extract_options!)
|
88
|
-
options.assert_valid_keys([:fallback, :reader, :writer, :nil])
|
89
|
-
|
90
|
-
class << self
|
91
|
-
# adds :translations to :includes if current locale differs from default
|
92
|
-
#FIXME is this enough with find or need to create chain for find_last, find_first and others?
|
93
|
-
alias_method(:find_without_translations, :find) unless method_defined?(:find_without_translations)
|
94
|
-
def find(*args)
|
95
|
-
if args[0].kind_of?(Hash)
|
96
|
-
args[0][:include] ||= []
|
97
|
-
args[0][:include] << :translations
|
98
|
-
end unless I18n.locale == I18n.default_locale
|
99
|
-
find_without_translations(*args)
|
100
|
-
end
|
101
|
-
# Defines given class recursively
|
102
|
-
# Example:
|
103
|
-
# create_class('Cms::Text::Page', Object, ActiveRecord::Base)
|
104
|
-
# => Cms::Text::Page
|
105
|
-
def create_class(class_name, parent, superclass, &block)
|
106
|
-
first,*other = class_name.split("::")
|
107
|
-
if other.empty?
|
108
|
-
klass = Class.new superclass, &block
|
109
|
-
parent.const_set(first, klass)
|
110
|
-
else
|
111
|
-
klass = Class.new
|
112
|
-
parent = unless parent.const_defined?(first)
|
113
|
-
parent.const_set(first, klass)
|
114
|
-
else
|
115
|
-
first.constantize
|
116
|
-
end
|
117
|
-
create_class(other.join('::'), parent, superclass, &block)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
# defines "ModelNameTranslation" if it's not defined manualy
|
121
|
-
def define_translation_class name, attrs
|
122
|
-
klass = name.constantize rescue nil
|
123
|
-
unless klass
|
124
|
-
klass = create_class(name, Object, ActiveRecord::Base) do
|
125
|
-
# set's real table name
|
126
|
-
set_table_name name.sub('Translation','').constantize.table_name.singularize + "_translations"
|
127
|
-
cattr_accessor :translate_attrs, :master_id
|
128
|
-
# override validate to vaidate only translate fields from master Class
|
129
|
-
def validate
|
130
|
-
item = self.class.name.sub('Translation','').constantize.new(self.attributes.clone.delete_if{|k,_| !self.class.translate_attrs.include?(k.to_sym)})
|
131
|
-
was_table_name = item.class.table_name
|
132
|
-
item.class.set_table_name self.class.table_name
|
133
|
-
item.valid? rescue
|
134
|
-
self.class.translate_attrs.each do |attr|
|
135
|
-
errors_on_attr = item.errors.on(attr)
|
136
|
-
self.errors.add(attr,errors_on_attr) if errors_on_attr
|
137
|
-
end
|
138
|
-
item.class.set_table_name was_table_name
|
139
|
-
end
|
140
|
-
extend TranslationClassMethods
|
141
|
-
end
|
142
|
-
klass.translate_attrs = attrs
|
143
|
-
else
|
144
|
-
unless klass.respond_to?(:translate_attrs)
|
145
|
-
klass.send(:cattr_accessor, :translate_attrs, :master_id)
|
146
|
-
klass.send(:extend,TranslationClassMethods)
|
147
|
-
klass.translate_attrs = attrs
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
klass.extract_master_id(name)
|
152
|
-
klass
|
153
|
-
end
|
154
|
-
# creates translation table and adds missing fields
|
155
|
-
# So at first add the "translations :name, :desc" in your model
|
156
|
-
# then put YourModel.sync_translation_table! in db/seed.rb and run "rake db:seed"
|
157
|
-
# Later adding more fields in translations array, just run agin "rake db:seed"
|
158
|
-
# If you want to remove fields do it manualy, it's safer
|
159
|
-
def sync_translation_table!
|
160
|
-
out = StringIO.new
|
161
|
-
$stdout = out
|
162
|
-
translations_class = reflections[:translations].class_name.constantize
|
163
|
-
translations_table = translations_class.table_name
|
164
|
-
unless ActiveRecord::Migration::table_exists?(translations_table)
|
165
|
-
ActiveRecord::Migration.create_table translations_table do |t|
|
166
|
-
t.integer translations_class.master_id, :null => false
|
167
|
-
t.string :locale, :null => false, :limit => 5
|
168
|
-
columns_has_translations.each do |col|
|
169
|
-
t.send(col.type,col.name)
|
170
|
-
end
|
171
|
-
end
|
172
|
-
ActiveRecord::Migration.add_index translations_table, [translations_class.master_id, :locale], :unique => true
|
173
|
-
translations_class.reset_column_information
|
174
|
-
else
|
175
|
-
changes = false
|
176
|
-
columns_has_translations.each do |col|
|
177
|
-
unless translations_class.columns_hash.has_key?(col.name)
|
178
|
-
ActiveRecord::Migration.add_column(translations_table, col.name, col.type)
|
179
|
-
changes = true
|
180
|
-
end
|
181
|
-
end
|
182
|
-
translations_class.reset_column_information if changes
|
183
|
-
end
|
184
|
-
$stdout = STDOUT
|
185
|
-
end
|
186
|
-
end
|
187
|
-
|
188
|
-
translation_class_name = "#{self.name}Translation"
|
189
|
-
translation_class = self.define_translation_class(translation_class_name, attrs)
|
190
|
-
belongs_to = self.name.demodulize.underscore.to_sym
|
191
|
-
|
192
|
-
write_inheritable_attribute :has_translations_options, options
|
193
|
-
class_inheritable_reader :has_translations_options
|
194
|
-
|
195
|
-
write_inheritable_attribute :columns_has_translations, (columns rescue []).collect{|col| col if attrs.include?(col.name.to_sym)}.compact
|
196
|
-
class_inheritable_reader :columns_has_translations
|
197
|
-
|
198
|
-
# forces given locale
|
199
|
-
# I18n.locale = :lv
|
200
|
-
# a = Article.find 18
|
201
|
-
# a.title
|
202
|
-
# => "LV title"
|
203
|
-
# a.in(:en).title
|
204
|
-
# => "EN title"
|
205
|
-
def in locale
|
206
|
-
locale.to_sym == I18n.default_locale ? self : find_translation(locale)
|
207
|
-
end
|
208
|
-
|
209
|
-
def find_or_build_translation(*args)
|
210
|
-
locale = args.first.to_s
|
211
|
-
build = args.second.present?
|
212
|
-
find_translation(locale) || (build ? self.translations.build(:locale => locale) : self.translations.new(:locale => locale))
|
213
|
-
end
|
214
|
-
|
215
|
-
def translation(locale)
|
216
|
-
find_translation(locale.to_s)
|
217
|
-
end
|
218
|
-
|
219
|
-
def all_translations
|
220
|
-
t = I18n.available_locales.map do |locale|
|
221
|
-
[locale, find_or_build_translation(locale)]
|
222
|
-
end
|
223
|
-
ActiveSupport::OrderedHash[t]
|
224
|
-
end
|
225
|
-
|
226
|
-
def has_translation?(locale)
|
227
|
-
return true if locale == I18n.default_locale
|
228
|
-
find_translation(locale).present?
|
229
|
-
end
|
230
|
-
|
231
|
-
# if object is new, then nested slaves ar built for all available locales
|
232
|
-
def build_nested_translations
|
233
|
-
if (I18n.available_locales.size - 1) > self.translations.size
|
234
|
-
I18n.available_locales.clone.delete_if{|l| l == I18n.default_locale}.each do |l|
|
235
|
-
options = {:locale => l.to_s}
|
236
|
-
options[self.class.reflections[:translations].class_name.constantize.master_id] = self.id unless self.new_record?
|
237
|
-
self.translations.build(options) unless self.translations.map(&:locale).include?(l.to_s)
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
if options[:reader]
|
243
|
-
attrs.each do |name|
|
244
|
-
send :define_method, name do
|
245
|
-
unless I18n.default_locale == I18n.locale
|
246
|
-
translation = self.translation(I18n.locale)
|
247
|
-
if translation.nil?
|
248
|
-
if has_translations_options[:fallback]
|
249
|
-
(self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
|
250
|
-
else
|
251
|
-
has_translations_options[:nil]
|
252
|
-
end
|
253
|
-
else
|
254
|
-
if @return_raw_data
|
255
|
-
(self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
|
256
|
-
else
|
257
|
-
value = translation.send(name) and value.set_origins(self,name)
|
258
|
-
end
|
259
|
-
end
|
260
|
-
else
|
261
|
-
(self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
|
262
|
-
end
|
263
|
-
end
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class ActiveRecord::Base
|
4
|
+
# Provides ability to add the translations for the model using delegate pattern.
|
5
|
+
# Uses has_many association to the ModelNameTranslation.
|
6
|
+
#
|
7
|
+
# For example you have model Article with attributes title and text.
|
8
|
+
# You want that attributes title and text to be translated.
|
9
|
+
# For this reason you need to generate new model ArticleTranslation.
|
10
|
+
# In migration you need to add:
|
11
|
+
#
|
12
|
+
# create_table :article_translations do |t|
|
13
|
+
# t.references :article, :null => false
|
14
|
+
# t.string :locale, :length => 2, :null => false
|
15
|
+
# t.string :name, :null => false
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# add_index :articles, [:article_id, :locale], :unique => true, :name => 'unique_locale_for_article_id'
|
19
|
+
#
|
20
|
+
# And in the Article model:
|
21
|
+
#
|
22
|
+
# translations :title, :text
|
23
|
+
#
|
24
|
+
# This will adds:
|
25
|
+
#
|
26
|
+
# * named_scope (translated) and has_many association to the Article model
|
27
|
+
# * locale presence validation to the ArticleTranslation model.
|
28
|
+
#
|
29
|
+
# Notice: if you want to have validates_presence_of :article, you should use :inverse_of.
|
30
|
+
# Support this by yourself. Better is always to use artile.translations.build() method.
|
31
|
+
#
|
32
|
+
# For more information please read API. Feel free to write me an email to:
|
33
|
+
# dmitry.polushkin@gmail.com.
|
34
|
+
#
|
35
|
+
# ===
|
36
|
+
#
|
37
|
+
# You also can pass attributes and options to the translations class method:
|
38
|
+
#
|
39
|
+
# translations :title, :text, :fallback => true, :writer => true, :nil => nil
|
40
|
+
#
|
41
|
+
# ===
|
42
|
+
#
|
43
|
+
# Configuration options:
|
44
|
+
#
|
45
|
+
# * <tt>:fallback</tt> - if translation for the current locale not found.
|
46
|
+
# By default true.
|
47
|
+
# Uses algorithm of fallback:
|
48
|
+
# 0) current translation (using I18n.locale);
|
49
|
+
# 1) default locale (using I18n.default_locale);
|
50
|
+
# 2) :nil value (see <tt>:nil</tt> configuration option)
|
51
|
+
# * <tt>:reader</tt> - add reader attributes to the model and delegate them
|
52
|
+
# to the translation model columns. Add's fallback if it is set to true.
|
53
|
+
# * <tt>:writer</tt> - add writer attributes to the model and assign them
|
54
|
+
# to the translation model attributes.
|
55
|
+
# * <tt>:nil</tt> - when reader cant find string, it returns by default an
|
56
|
+
# empty string. If you want to change this setting for example to nil,
|
57
|
+
# add :nil => nil
|
58
|
+
#
|
59
|
+
# ===
|
60
|
+
#
|
61
|
+
# When you are using <tt>:writer</tt> option, you can create translations using
|
62
|
+
# update_attributes method. For example:
|
63
|
+
#
|
64
|
+
# Article.create!
|
65
|
+
# Article.update_attributes(:title => 'title', :text => 'text')
|
66
|
+
#
|
67
|
+
# ===
|
68
|
+
#
|
69
|
+
# <tt>translated</tt> named_scope is useful when you want to find only those
|
70
|
+
# records that are translated to a specific locale.
|
71
|
+
# For example if you want to find all Articles that is translated to an english
|
72
|
+
# language, you can write: Article.translated(:en)
|
73
|
+
#
|
74
|
+
# <tt>has_translation?(locale)</tt> method, that returns true if object's model
|
75
|
+
# have a translation for a specified locale
|
76
|
+
#
|
77
|
+
# <tt>translation(locale)</tt> method finds translation with specified locale.
|
78
|
+
#
|
79
|
+
# <tt>all_translations</tt> method that returns all possible translations in
|
80
|
+
# ordered hash (useful when creating forms with nested attributes).
|
81
|
+
def self.translations(*attrs)
|
82
|
+
options = {
|
83
|
+
:fallback => true,
|
84
|
+
:reader => true,
|
85
|
+
:writer => false,
|
86
|
+
:nil => ''
|
87
|
+
}.merge(attrs.extract_options!)
|
88
|
+
options.assert_valid_keys([:fallback, :reader, :writer, :nil])
|
89
|
+
|
90
|
+
class << self
|
91
|
+
# adds :translations to :includes if current locale differs from default
|
92
|
+
#FIXME is this enough with find or need to create chain for find_last, find_first and others?
|
93
|
+
alias_method(:find_without_translations, :find) unless method_defined?(:find_without_translations)
|
94
|
+
def find(*args)
|
95
|
+
if args[0].kind_of?(Hash)
|
96
|
+
args[0][:include] ||= []
|
97
|
+
args[0][:include] << :translations
|
98
|
+
end unless I18n.locale == I18n.default_locale
|
99
|
+
find_without_translations(*args)
|
100
|
+
end
|
101
|
+
# Defines given class recursively
|
102
|
+
# Example:
|
103
|
+
# create_class('Cms::Text::Page', Object, ActiveRecord::Base)
|
104
|
+
# => Cms::Text::Page
|
105
|
+
def create_class(class_name, parent, superclass, &block)
|
106
|
+
first,*other = class_name.split("::")
|
107
|
+
if other.empty?
|
108
|
+
klass = Class.new superclass, &block
|
109
|
+
parent.const_set(first, klass)
|
110
|
+
else
|
111
|
+
klass = Class.new
|
112
|
+
parent = unless parent.const_defined?(first)
|
113
|
+
parent.const_set(first, klass)
|
114
|
+
else
|
115
|
+
first.constantize
|
116
|
+
end
|
117
|
+
create_class(other.join('::'), parent, superclass, &block)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
# defines "ModelNameTranslation" if it's not defined manualy
|
121
|
+
def define_translation_class name, attrs
|
122
|
+
klass = name.constantize rescue nil
|
123
|
+
unless klass
|
124
|
+
klass = create_class(name, Object, ActiveRecord::Base) do
|
125
|
+
# set's real table name
|
126
|
+
set_table_name name.sub('Translation','').constantize.table_name.singularize + "_translations"
|
127
|
+
cattr_accessor :translate_attrs, :master_id
|
128
|
+
# override validate to vaidate only translate fields from master Class
|
129
|
+
def validate
|
130
|
+
item = self.class.name.sub('Translation','').constantize.new(self.attributes.clone.delete_if{|k,_| !self.class.translate_attrs.include?(k.to_sym)})
|
131
|
+
was_table_name = item.class.table_name
|
132
|
+
item.class.set_table_name self.class.table_name
|
133
|
+
item.valid? rescue
|
134
|
+
self.class.translate_attrs.each do |attr|
|
135
|
+
errors_on_attr = item.errors.on(attr)
|
136
|
+
self.errors.add(attr,errors_on_attr) if errors_on_attr
|
137
|
+
end
|
138
|
+
item.class.set_table_name was_table_name
|
139
|
+
end
|
140
|
+
extend TranslationClassMethods
|
141
|
+
end
|
142
|
+
klass.translate_attrs = attrs
|
143
|
+
else
|
144
|
+
unless klass.respond_to?(:translate_attrs)
|
145
|
+
klass.send(:cattr_accessor, :translate_attrs, :master_id)
|
146
|
+
klass.send(:extend,TranslationClassMethods)
|
147
|
+
klass.translate_attrs = attrs
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
klass.extract_master_id(name)
|
152
|
+
klass
|
153
|
+
end
|
154
|
+
# creates translation table and adds missing fields
|
155
|
+
# So at first add the "translations :name, :desc" in your model
|
156
|
+
# then put YourModel.sync_translation_table! in db/seed.rb and run "rake db:seed"
|
157
|
+
# Later adding more fields in translations array, just run agin "rake db:seed"
|
158
|
+
# If you want to remove fields do it manualy, it's safer
|
159
|
+
def sync_translation_table!
|
160
|
+
out = StringIO.new
|
161
|
+
$stdout = out
|
162
|
+
translations_class = reflections[:translations].class_name.constantize
|
163
|
+
translations_table = translations_class.table_name
|
164
|
+
unless ActiveRecord::Migration::table_exists?(translations_table)
|
165
|
+
ActiveRecord::Migration.create_table translations_table do |t|
|
166
|
+
t.integer translations_class.master_id, :null => false
|
167
|
+
t.string :locale, :null => false, :limit => 5
|
168
|
+
columns_has_translations.each do |col|
|
169
|
+
t.send(col.type,col.name)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
ActiveRecord::Migration.add_index translations_table, [translations_class.master_id, :locale], :unique => true
|
173
|
+
translations_class.reset_column_information
|
174
|
+
else
|
175
|
+
changes = false
|
176
|
+
columns_has_translations.each do |col|
|
177
|
+
unless translations_class.columns_hash.has_key?(col.name)
|
178
|
+
ActiveRecord::Migration.add_column(translations_table, col.name, col.type)
|
179
|
+
changes = true
|
180
|
+
end
|
181
|
+
end
|
182
|
+
translations_class.reset_column_information if changes
|
183
|
+
end
|
184
|
+
$stdout = STDOUT
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
translation_class_name = "#{self.name}Translation"
|
189
|
+
translation_class = self.define_translation_class(translation_class_name, attrs)
|
190
|
+
belongs_to = self.name.demodulize.underscore.to_sym
|
191
|
+
|
192
|
+
write_inheritable_attribute :has_translations_options, options
|
193
|
+
class_inheritable_reader :has_translations_options
|
194
|
+
|
195
|
+
write_inheritable_attribute :columns_has_translations, (columns rescue []).collect{|col| col if attrs.include?(col.name.to_sym)}.compact
|
196
|
+
class_inheritable_reader :columns_has_translations
|
197
|
+
|
198
|
+
# forces given locale
|
199
|
+
# I18n.locale = :lv
|
200
|
+
# a = Article.find 18
|
201
|
+
# a.title
|
202
|
+
# => "LV title"
|
203
|
+
# a.in(:en).title
|
204
|
+
# => "EN title"
|
205
|
+
def in locale
|
206
|
+
locale.to_sym == I18n.default_locale ? self : find_translation(locale)
|
207
|
+
end
|
208
|
+
|
209
|
+
def find_or_build_translation(*args)
|
210
|
+
locale = args.first.to_s
|
211
|
+
build = args.second.present?
|
212
|
+
find_translation(locale) || (build ? self.translations.build(:locale => locale) : self.translations.new(:locale => locale))
|
213
|
+
end
|
214
|
+
|
215
|
+
def translation(locale)
|
216
|
+
find_translation(locale.to_s)
|
217
|
+
end
|
218
|
+
|
219
|
+
def all_translations
|
220
|
+
t = I18n.available_locales.map do |locale|
|
221
|
+
[locale, find_or_build_translation(locale)]
|
222
|
+
end
|
223
|
+
ActiveSupport::OrderedHash[t]
|
224
|
+
end
|
225
|
+
|
226
|
+
def has_translation?(locale)
|
227
|
+
return true if locale == I18n.default_locale
|
228
|
+
find_translation(locale).present?
|
229
|
+
end
|
230
|
+
|
231
|
+
# if object is new, then nested slaves ar built for all available locales
|
232
|
+
def build_nested_translations
|
233
|
+
if (I18n.available_locales.size - 1) > self.translations.size
|
234
|
+
I18n.available_locales.clone.delete_if{|l| l == I18n.default_locale}.each do |l|
|
235
|
+
options = {:locale => l.to_s}
|
236
|
+
options[self.class.reflections[:translations].class_name.constantize.master_id] = self.id unless self.new_record?
|
237
|
+
self.translations.build(options) unless self.translations.map(&:locale).include?(l.to_s)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
if options[:reader]
|
243
|
+
attrs.each do |name|
|
244
|
+
send :define_method, name do
|
245
|
+
unless I18n.default_locale == I18n.locale
|
246
|
+
translation = self.translation(I18n.locale)
|
247
|
+
if translation.nil?
|
248
|
+
if has_translations_options[:fallback]
|
249
|
+
(self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
|
250
|
+
else
|
251
|
+
has_translations_options[:nil]
|
252
|
+
end
|
253
|
+
else
|
254
|
+
if @return_raw_data
|
255
|
+
(self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
|
256
|
+
else
|
257
|
+
value = translation.send(name) and value.set_origins(self,name)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
else
|
261
|
+
(self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
@translation_attrs = attrs
|
268
|
+
def self.translation_attrs
|
269
|
+
@translation_attrs
|
270
|
+
end
|
271
|
+
has_many :translations, :class_name => translation_class_name, :foreign_key => translation_class.master_id, :dependent => :destroy
|
272
|
+
accepts_nested_attributes_for :translations, :allow_destroy => true, :reject_if => proc { |attributes| columns_has_translations.collect{|col| attributes[col.name].blank? ? nil : 1}.compact.empty? }
|
273
|
+
translation_class.belongs_to belongs_to
|
274
|
+
translation_class.validates_presence_of :locale
|
275
|
+
translation_class.validates_uniqueness_of :locale, :scope => translation_class.master_id
|
276
|
+
|
277
|
+
# Workaround to support Rails 2
|
278
|
+
scope_method = if ActiveRecord::VERSION::MAJOR < 3 then :named_scope else :scope end
|
279
|
+
|
280
|
+
send scope_method, :translated, lambda { |locale| {:conditions => ["#{translation_class.table_name}.locale = ?", locale.to_s], :joins => :translations} }
|
281
|
+
|
282
|
+
#private is no good
|
283
|
+
|
284
|
+
def find_translation(locale)
|
285
|
+
locale = locale.to_s
|
286
|
+
translations.detect { |t| t.locale == locale }
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
module TranslationClassMethods
|
292
|
+
# sets real master_id it's aware of STI
|
293
|
+
def extract_master_id name
|
294
|
+
master_class = name.sub('Translation','').constantize
|
295
|
+
#FIXME why need to check superclass ?
|
296
|
+
class_name = master_class.name #!master_class.superclass.abstract_class? ? master_class.superclass.name : master_class.name
|
297
|
+
self.master_id = :"#{class_name.demodulize.underscore}_id"
|
298
|
+
end
|
295
299
|
end
|