lolita-translation 0.2.9 → 0.2.18

Sign up to get free protection for your applications and to get access to all the features.
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format d
data/Gemfile CHANGED
@@ -1,9 +1,19 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "lolita", ">=3.2.0.rc.3"
3
+ gem "lolita", "3.1.18"
4
4
  group :development do
5
5
  gem "shoulda", ">= 0"
6
6
  gem "bundler", "~> 1.0.0"
7
7
  gem "jeweler", "~> 1.5.2"
8
8
  gem "rcov", ">= 0"
9
9
  end
10
+
11
+ group :test do
12
+ gem "rails", "~>3.0.0"
13
+ gem "rspec","~>2.6.0"
14
+ gem "rspec-rails", "~>2.6.0"
15
+ gem "webmock", "~> 1.7.6"
16
+ gem "sqlite3"
17
+ gem "ffaker"
18
+ gem 'ruby-debug19', :require => 'ruby-debug'
19
+ end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.9
1
+ 0.2.18
@@ -1,7 +1,12 @@
1
1
 
2
+ require 'lolita'
2
3
  require 'lolita-translation/string.rb'
3
- require 'lolita-translation/has_translations.rb'
4
- require 'lolita-translation/rails'
4
+ require 'lolita-translation/modules'
5
+ require 'lolita-translation/model'
6
+ require 'lolita-translation/configuration'
7
+ if Lolita.rails3?
8
+ require 'lolita-translation/rails'
9
+ end
5
10
 
6
11
  module Lolita
7
12
  module Translation
@@ -11,6 +16,7 @@ module Lolita
11
16
  end
12
17
 
13
18
  class << self
19
+ # Determine whether tab is translatable. When there is one or more fields in tab, that are defined with #translates.
14
20
  def translatable?(tab)
15
21
  fields = tab.fields.reject{|field|
16
22
  field.dbi!=tab.dbi
@@ -19,6 +25,7 @@ module Lolita
19
25
  tab.dbi.klass.respond_to?(:translations) && (fields.map(&:name) & tab.dbi.klass.translation_attrs).any?
20
26
  end
21
27
 
28
+ # Create nested form configuration for views.
22
29
  def create_translations_nested_form(resource,tab)
23
30
  resource.build_nested_translations
24
31
  nested_form = Lolita::Configuration::NestedForm.new(tab,:translations)
@@ -37,39 +44,3 @@ module Lolita
37
44
  end
38
45
 
39
46
  Lolita.add_module Lolita::Translation
40
-
41
- Lolita::Hooks.component(:"/lolita/configuration/tab/form").before do
42
- tab = self.component_locals[:tab]
43
- if Lolita::Translation.translatable?(tab)
44
- self.send(:render_component,"lolita/translation",:switch, :tab => tab)
45
- end
46
- end
47
-
48
- Lolita::Hooks.component(:"/lolita/configuration/tab/fields").after do
49
- tab = self.component_locals[:tab]
50
- if Lolita::Translation.translatable?(tab)
51
- self.render_component Lolita::Translation.create_translations_nested_form(self.resource,tab)
52
- end
53
- end
54
-
55
- Lolita::Hooks.component(:"/lolita/configuration/tab/fields").around do
56
- tab = self.component_locals[:tab]
57
- if Lolita::Translation.translatable?(tab)
58
- self.send(:render_component,"lolita/translation",:language_wrap,:tab => tab, :content => let_content)
59
- else
60
- let_content
61
- end
62
- end
63
-
64
- Lolita::Hooks.component(:"/lolita/configuration/nested_form/fields").around do
65
- tab = self.component_locals[:nested_form].parent
66
- if Lolita::Translation.translatable?(tab)
67
- self.send(:render_component,"lolita/translation",:language_wrap, :tab => tab, :content => let_content)
68
- else
69
- let_content
70
- end
71
- end
72
-
73
- Lolita::Hooks.component(:"/lolita/configuration/tabs/display").before do
74
- self.render_component "lolita/translation", :assets
75
- end
@@ -0,0 +1,192 @@
1
+ module Lolita
2
+ module Translation
3
+
4
+ module SingletonMethods
5
+ # Provides ability to add the translations for the model using delegate pattern.
6
+ # Uses has_many association to the ModelNameTranslation.
7
+ #
8
+ # For example you have model Article with attributes title and text.
9
+ # You want that attributes title and text to be translated.
10
+ # For this reason you need to generate new model ArticleTranslation.
11
+ # In migration you need to add:
12
+ #
13
+ # create_table :article_translations do |t|
14
+ # t.references :article, :null => false
15
+ # t.string :locale, :length => 2, :null => false
16
+ # t.string :name, :null => false
17
+ # end
18
+ #
19
+ # add_index :articles, [:article_id, :locale], :unique => true, :name => 'unique_locale_for_article_id'
20
+ #
21
+ # And in the Article model:
22
+ #
23
+ # translations :title, :text
24
+ #
25
+ # This will adds:
26
+ #
27
+ # * named_scope (translated) and has_many association to the Article model
28
+ # * locale presence validation to the ArticleTranslation model.
29
+ #
30
+ # Notice: if you want to have validates_presence_of :article, you should use :inverse_of.
31
+ # Support this by yourself. Better is always to use artile.translations.build() method.
32
+ #
33
+ # For more information please read API. Feel free to write me an email to:
34
+ # dmitry.polushkin@gmail.com.
35
+ #
36
+ # ===
37
+ #
38
+ # You also can pass attributes and options to the translations class method:
39
+ #
40
+ # translations :title, :text, :fallback => true, :writer => true, :nil => nil
41
+ #
42
+ # ===
43
+ #
44
+ # Configuration options:
45
+ #
46
+ # * <tt>:fallback</tt> - if translation for the current locale not found.
47
+ # By default true.
48
+ # Uses algorithm of fallback:
49
+ # 0) current translation (using I18n.locale);
50
+ # 1) default locale (using I18n.default_locale);
51
+ # 2) :nil value (see <tt>:nil</tt> configuration option)
52
+ # * <tt>:reader</tt> - add reader attributes to the model and delegate them
53
+ # to the translation model columns. Add's fallback if it is set to true.
54
+ # * <tt>:writer</tt> - add writer attributes to the model and assign them
55
+ # to the translation model attributes.
56
+ # * <tt>:nil</tt> - when reader cant find string, it returns by default an
57
+ # empty string. If you want to change this setting for example to nil,
58
+ # add :nil => nil
59
+ #
60
+ # ===
61
+ #
62
+ # When you are using <tt>:writer</tt> option, you can create translations using
63
+ # update_attributes method. For example:
64
+ #
65
+ # Article.create!
66
+ # Article.update_attributes(:title => 'title', :text => 'text')
67
+ #
68
+ # ===
69
+ #
70
+ # <tt>translated</tt> named_scope is useful when you want to find only those
71
+ # records that are translated to a specific locale.
72
+ # For example if you want to find all Articles that is translated to an english
73
+ # language, you can write: Article.translated(:en)
74
+ #
75
+ # <tt>has_translation?(locale)</tt> method, that returns true if object's model
76
+ # have a translation for a specified locale
77
+ #
78
+ # <tt>translation(locale)</tt> method finds translation with specified locale.
79
+ #
80
+ # <tt>all_translations</tt> method that returns all possible translations in
81
+ # ordered hash (useful when creating forms with nested attributes).
82
+ def translations *attrs
83
+ @translations_config ||= Lolita::Translation::Configuration.new(self,*attrs)
84
+ if block_given?
85
+ yield @translations_config
86
+ end
87
+ @translations_config
88
+ end
89
+ end
90
+
91
+ class Configuration
92
+
93
+ attr_reader :klass, :options, :translation_class_name, :translation_class,:attrs
94
+
95
+ def initialize(base_class, *attrs)
96
+ @klass = base_class
97
+ @attrs = attrs
98
+ initialize_options
99
+ include_modules
100
+ initialize_default_attributes
101
+ extend_klass
102
+ extend_translation_class
103
+ end
104
+
105
+ private
106
+
107
+ def extend_klass
108
+ config = self
109
+ @klass.class_eval do
110
+
111
+ class_attribute :has_translations_options
112
+ self.has_translations_options = config.options
113
+
114
+ class_attribute :columns_has_translations
115
+ self.columns_has_translations = (columns rescue []).collect{|col| col if config.attrs.include?(col.name.to_sym)}.compact
116
+
117
+ class_attribute :translation_attrs
118
+ self.translation_attrs = config.attrs
119
+
120
+ has_many :translations, :class_name => config.translation_class_name, :foreign_key => config.translation_class.master_id, :dependent => :destroy
121
+ 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? }
122
+
123
+ scope :translated, lambda { |locale|
124
+ where("#{translation_class.table_name}.locale = ?", locale.to_s).joins(:translations)
125
+ }
126
+
127
+ class << self
128
+ alias_method_chain :find, :translations
129
+ end
130
+ end
131
+ override_readers
132
+ end
133
+
134
+ def extend_translation_class
135
+ translation_class.belongs_to @klass.name.demodulize.underscore.to_sym
136
+ translation_class.validates_presence_of :locale
137
+ translation_class.validates_uniqueness_of :locale, :scope => translation_class.master_id
138
+ end
139
+
140
+ def override_readers
141
+ if options[:reader]
142
+ attrs.each do |name|
143
+ override_reader(name)
144
+ end
145
+ end
146
+ end
147
+
148
+ def override_reader(name)
149
+ @klass.send :define_method, name do
150
+ unless ::I18n.default_locale == ::I18n.locale
151
+ translation = self.translation(::I18n.locale)
152
+ if translation.nil?
153
+ if has_translations_options[:fallback]
154
+ (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name) else
155
+ has_translations_options[:nil]
156
+ end
157
+ else
158
+ if @return_raw_data
159
+ (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
160
+ else
161
+ value = translation.send(name) and value.set_origins(self,name)
162
+ end
163
+ end
164
+ else
165
+ (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
166
+ end
167
+ end
168
+ end
169
+
170
+ def initialize_default_attributes
171
+ @translation_class_name = "#{@klass.name}Translation"
172
+ @translation_class = Lolita::Translation::TranslationModel.new(self).klass
173
+ end
174
+
175
+ def include_modules
176
+ @klass.send(:include, Lolita::Translation::InstanceMethods)
177
+ @klass.extend(Lolita::Translation::ClassMethods)
178
+ end
179
+
180
+ def initialize_options
181
+ @options = {
182
+ :fallback => true,
183
+ :reader => true,
184
+ :writer => false,
185
+ :nil => ''
186
+ }.merge(@attrs.extract_options!)
187
+ @options.assert_valid_keys([:fallback, :reader, :writer, :nil,:table_name])
188
+ end
189
+
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,100 @@
1
+ module Lolita
2
+ module Translation
3
+ class ClassFactory
4
+
5
+ def self.create(class_name, parent, superclass=nil, &block)
6
+ first,*other = class_name.split("::")
7
+ if other.empty?
8
+ klass = superclass ? Class.new(superclass, &block) : Class.new(&block)
9
+ parent.const_set(first, klass)
10
+ else
11
+ klass = Class.new
12
+ parent = unless parent.const_defined?(first)
13
+ parent.const_set(first, klass)
14
+ else
15
+ first.constantize
16
+ end
17
+ self.create(other.join('::'), parent, superclass, &block)
18
+ end
19
+ end
20
+ end
21
+
22
+ module ModelInstanceMethods
23
+ # override validate to vaidate only translate fields from master Class
24
+ def validate
25
+ item = self.class.name.sub('Translation','').constantize.new(self.attributes.clone.delete_if{|k,_| !self.class.translate_attrs.include?(k.to_sym)})
26
+ item_adapter = Lolita::DBI::Base.create(item.class)
27
+ self_adapter = Lolita::DBI::Base.create(self.class)
28
+ was_table_name = item_adapter.collection_name
29
+ item_adapter.collection_name = self_adapter.collection_name
30
+ item.valid? rescue
31
+ self.class.translate_attrs.each do |attr|
32
+ errors_on_attr = item.errors.on(attr)
33
+ self.errors.add(attr,errors_on_attr) if errors_on_attr
34
+ end
35
+ item_adapter.collection_name = was_table_name
36
+ end
37
+ end
38
+
39
+ module ModelClassMethods
40
+ # sets real master_id it's aware of STI
41
+ def extract_master_id name
42
+ master_class = name.sub('Translation','').constantize
43
+ #FIXME why need to check superclass ?
44
+ class_name = master_class.name #!master_class.superclass.abstract_class? ? master_class.superclass.name : master_class.name
45
+ self.master_id = :"#{class_name.demodulize.underscore}_id"
46
+ end
47
+ end
48
+
49
+ class TranslationModel
50
+
51
+ attr_reader :adapter,:klass
52
+
53
+ def initialize(config)
54
+ @config = config
55
+ @adapter = Lolita::DBI::Base.create(@config.klass)
56
+ @klass = begin
57
+ @config.translation_class_name.constantize
58
+ rescue
59
+ self.create
60
+ end
61
+ fix_class_attrs
62
+ @klass.extract_master_id(@config.translation_class_name)
63
+ end
64
+
65
+ def create
66
+ translation_model = self
67
+ config = @config
68
+ new_klass = Lolita::Translation::ClassFactory.create(@config.translation_class_name, Object, get_orm_class) do
69
+ if translation_model.adapter.dbi.adapter_name == :mongoid
70
+ include Mongoid::Document
71
+ end
72
+ # set's real table name
73
+ translation_adapter = Lolita::DBI::Base.create(self)
74
+ translation_adapter.collection_name = config.options[:table_name] || translation_model.adapter.collection_name.to_s + "_translations"
75
+
76
+ cattr_accessor :translate_attrs, :master_id
77
+
78
+ include Lolita::Translation::ModelInstanceMethods
79
+ extend Lolita::Translation::ModelClassMethods
80
+ end
81
+ new_klass.translate_attrs = @config.attrs
82
+ new_klass
83
+ end
84
+
85
+ private
86
+
87
+ def get_orm_class()
88
+ @adapter.dbi.adapter_name == :active_record ? ActiveRecord::Base : nil
89
+ end
90
+
91
+ def fix_class_attrs
92
+ unless @klass.respond_to?(:translate_attrs)
93
+ @klass.send(:cattr_accessor, :translate_attrs, :master_id)
94
+ @klass.send(:extend,Lolita::Translation::ModelClassMethods)
95
+ @klass.translate_attrs = @config.attrs
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,130 @@
1
+ module Lolita
2
+ module Translation
3
+ module ClassMethods
4
+
5
+ def find_with_translations(*args,&block)
6
+ unless ::I18n.locale == ::I18n.default_locale
7
+ if args && args[0].kind_of?(Hash)
8
+ args[0][:include] ||=[]
9
+ args[0][:include] << :translations
10
+ end
11
+ end
12
+ find_without_translations *args, &block
13
+ end
14
+ # creates translation table and adds missing fields
15
+ # So at first add the "translations :name, :desc" in your model
16
+ # then put YourModel.sync_translation_table! in db/seed.rb and run "rake db:seed"
17
+ # Later adding more fields in translations array, just run agin "rake db:seed"
18
+ # If you want to remove fields do it manualy, it's safer
19
+ def sync_translation_table!
20
+ out = StringIO.new
21
+ $stdout = out
22
+ self_adapter = Lolita::DBI::Base.create(self)
23
+ translations_class = self.reflect_on_association(:translations).klass
24
+ translations_adapter = Lolita::DBI::Base.create(translations_class)
25
+
26
+ if translations_adapter.dbi.adapter_name == :active_record
27
+ translations_table = translations_adapter.collection_name
28
+
29
+ unless ActiveRecord::Migration::table_exists?(translations_table)
30
+ ActiveRecord::Migration.create_table translations_table do |t|
31
+ t.integer translations_class.master_id, :null => false
32
+ t.string :locale, :null => false, :limit => 5
33
+ columns_has_translations.each do |col|
34
+ t.send(col.type,col.name)
35
+ end
36
+ end
37
+ ActiveRecord::Migration.add_index translations_table, [translations_class.master_id, :locale], :unique => true
38
+ translations_class.reset_column_information
39
+ else
40
+ changes = false
41
+ columns_has_translations.each do |col|
42
+ unless translations_class.columns_hash.has_key?(col.name)
43
+ ActiveRecord::Migration.add_column(translations_table, col.name, col.type)
44
+ changes = true
45
+ end
46
+ end
47
+ translations_class.reset_column_information if changes
48
+ end
49
+ elsif translations_adapter.dbi.adapter_name == :mongoid
50
+ unless translations_class.fields["locale"]
51
+ translations_class.class_eval do
52
+ field(self.master_id, :type => Integer)
53
+ field :locale, :type => String
54
+ columns_has_translations.each do |col|
55
+ field col.name, :type => col.type
56
+ end
57
+ index(
58
+ [
59
+ [ self.master_id, Mongo::ASCENDING ],
60
+ [ :locale, Mongo::ASCENDING ]
61
+ ],
62
+ unique: true
63
+ )
64
+ end
65
+ else
66
+ columns_has_translations.each do |col|
67
+ unless translations_class.fields[col.name.to_s]
68
+ translations_class.field(col.name,:type => col.type)
69
+ end
70
+ end
71
+ end
72
+ end
73
+ $stdout = STDOUT
74
+ end
75
+ end
76
+
77
+ module InstanceMethods
78
+
79
+ # forces given locale
80
+ # I18n.locale = :lv
81
+ # a = Article.find 18
82
+ # a.title
83
+ # => "LV title"
84
+ # a.in(:en).title
85
+ # => "EN title"
86
+ def in locale
87
+ locale.to_sym == ::I18n.default_locale ? self : find_translation(locale)
88
+ end
89
+
90
+ def find_or_build_translation(*args)
91
+ locale = args.first.to_s
92
+ build = args.second.present?
93
+ find_translation(locale) || (build ? self.translations.build(:locale => locale) : self.translations.new(:locale => locale))
94
+ end
95
+
96
+ def translation(locale)
97
+ find_translation(locale.to_s)
98
+ end
99
+
100
+ def all_translations
101
+ t = ::I18n.available_locales.map do |locale|
102
+ [locale, find_or_build_translation(locale)]
103
+ end
104
+ ActiveSupport::OrderedHash[t]
105
+ end
106
+
107
+ def has_translation?(locale)
108
+ return true if locale == ::I18n.default_locale
109
+ find_translation(locale).present?
110
+ end
111
+
112
+ # if object is new, then nested slaves ar built for all available locales
113
+ def build_nested_translations
114
+ if (::I18n.available_locales.size - 1) > self.translations.size
115
+ ::I18n.available_locales.clone.delete_if{|l| l == ::I18n.default_locale}.each do |l|
116
+ options = {:locale => l.to_s}
117
+ self_adapter = Lolita::DBI::Base.create(self.class)
118
+ options[self_adapter.reflect_on_association(:translations).klass.master_id] = self.id unless self.new_record?
119
+ self.translations.build(options) unless self.translations.map(&:locale).include?(l.to_s)
120
+ end
121
+ end
122
+ end
123
+
124
+ def find_translation(locale)
125
+ locale = locale.to_s
126
+ translations.detect { |t| t.locale == locale }
127
+ end
128
+ end
129
+ end
130
+ end
@@ -1,5 +1,41 @@
1
- module LolitaHasTranslation
1
+ module LolitaTranslation
2
2
  class Engine < Rails::Engine
3
3
 
4
4
  end
5
+ end
6
+
7
+ Lolita::Hooks.component(:"/lolita/configuration/tab/form").before do
8
+ tab = self.component_locals[:tab]
9
+ if Lolita::Translation.translatable?(tab)
10
+ self.send(:render_component,"lolita/translation",:switch, :tab => tab)
11
+ end
12
+ end
13
+
14
+ Lolita::Hooks.component(:"/lolita/configuration/tab/fields").after do
15
+ tab = self.component_locals[:tab]
16
+ if Lolita::Translation.translatable?(tab)
17
+ self.render_component Lolita::Translation.create_translations_nested_form(self.resource,tab)
18
+ end
19
+ end
20
+
21
+ Lolita::Hooks.component(:"/lolita/configuration/tab/fields").around do
22
+ tab = self.component_locals[:tab]
23
+ if Lolita::Translation.translatable?(tab)
24
+ self.send(:render_component,"lolita/translation",:language_wrap,:tab => tab, :content => let_content)
25
+ else
26
+ let_content
27
+ end
28
+ end
29
+
30
+ Lolita::Hooks.component(:"/lolita/configuration/nested_form/fields").around do
31
+ tab = self.component_locals[:nested_form].parent
32
+ if Lolita::Translation.translatable?(tab)
33
+ self.send(:render_component,"lolita/translation",:language_wrap, :tab => tab, :content => let_content)
34
+ else
35
+ let_content
36
+ end
37
+ end
38
+
39
+ Lolita::Hooks.component(:"/lolita/configuration/tabs/display").before do
40
+ self.render_component "lolita/translation", :assets
5
41
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "lolita-translation"
8
- s.version = "0.2.9"
8
+ s.version = "0.2.18"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["ITHouse", "Gatis Tomsons", "Arturs Meisters"]
12
- s.date = "2011-11-03"
12
+ s.date = "2011-12-13"
13
13
  s.description = "Translates models in Lolita"
14
14
  s.email = "support@ithouse.lv"
15
15
  s.extra_rdoc_files = [
@@ -18,15 +18,13 @@ Gem::Specification.new do |s|
18
18
  ]
19
19
  s.files = [
20
20
  ".document",
21
+ ".rspec",
21
22
  "Gemfile",
22
23
  "LICENSE.txt",
23
24
  "README.md",
24
25
  "Rakefile",
25
26
  "VERSION",
26
27
  "app/assets/.gitkeep",
27
- "app/assets/javascripts/.gitkeep",
28
- "app/assets/javascripts/lolita/translation/application.js",
29
- "app/assets/stylesheets/lolita/translation/application.css",
30
28
  "app/views/components/lolita/translation/_assets.html.erb",
31
29
  "app/views/components/lolita/translation/_language_wrap.html.erb",
32
30
  "app/views/components/lolita/translation/_switch.html.erb",
@@ -35,19 +33,23 @@ Gem::Specification.new do |s|
35
33
  "lib/generators/lolita_translation/USAGE",
36
34
  "lib/generators/lolita_translation/has_translations_generator.rb",
37
35
  "lib/lolita-translation.rb",
38
- "lib/lolita-translation/has_translations.rb",
36
+ "lib/lolita-translation/configuration.rb",
37
+ "lib/lolita-translation/model.rb",
38
+ "lib/lolita-translation/modules.rb",
39
39
  "lib/lolita-translation/rails.rb",
40
40
  "lib/lolita-translation/string.rb",
41
41
  "lib/tasks/has_translations_tasks.rake",
42
42
  "lolita-translation.gemspec",
43
+ "public/javascripts/.gitkeep",
44
+ "public/javascripts/lolita/translation/application.js",
45
+ "public/stylesheets/lolita/translation/application.css",
43
46
  "spec/has_translations_spec.rb",
44
- "spec/spec.opts",
45
47
  "spec/spec_helper.rb"
46
48
  ]
47
49
  s.homepage = "http://github.com/ithouse/lolita-translations"
48
50
  s.licenses = ["MIT"]
49
51
  s.require_paths = ["lib"]
50
- s.rubygems_version = "1.8.10"
52
+ s.rubygems_version = "1.8.11"
51
53
  s.summary = "Lolita models translation plugin"
52
54
  s.test_files = [
53
55
  "spec/has_translations_spec.rb",
@@ -58,20 +60,20 @@ Gem::Specification.new do |s|
58
60
  s.specification_version = 3
59
61
 
60
62
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
61
- s.add_runtime_dependency(%q<lolita>, [">= 3.2.0.rc.3"])
63
+ s.add_runtime_dependency(%q<lolita>, ["= 3.1.18"])
62
64
  s.add_development_dependency(%q<shoulda>, [">= 0"])
63
65
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
64
66
  s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
65
67
  s.add_development_dependency(%q<rcov>, [">= 0"])
66
68
  else
67
- s.add_dependency(%q<lolita>, [">= 3.2.0.rc.3"])
69
+ s.add_dependency(%q<lolita>, ["= 3.1.18"])
68
70
  s.add_dependency(%q<shoulda>, [">= 0"])
69
71
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
70
72
  s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
71
73
  s.add_dependency(%q<rcov>, [">= 0"])
72
74
  end
73
75
  else
74
- s.add_dependency(%q<lolita>, [">= 3.2.0.rc.3"])
76
+ s.add_dependency(%q<lolita>, ["= 3.1.18"])
75
77
  s.add_dependency(%q<shoulda>, [">= 0"])
76
78
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
77
79
  s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
File without changes
data/spec/spec_helper.rb CHANGED
@@ -1,14 +1,19 @@
1
1
  # encoding: utf-8
2
2
  require 'rubygems'
3
- gem 'rails', '~>2.3'
4
- require 'i18n'
3
+ require 'bundler'
4
+ begin
5
+ Bundler.setup(:default, :test)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
5
11
  require 'active_record'
6
- require 'spec'
7
- require 'faker'
8
-
9
- require 'ruby-debug'
12
+ require 'active_support'
13
+ require 'logger'
14
+ require 'ffaker'
15
+ require File.expand_path('lib/lolita-translation')
10
16
 
11
- require File.dirname(__FILE__)+'/../init.rb'
12
17
  ActiveRecord::Base.logger = Logger.new(File.open("#{File.dirname(__FILE__)}/database.log", 'w+'))
13
18
  ActiveRecord::Base.establish_connection({ :database => ":memory:", :adapter => 'sqlite3', :timeout => 500 })
14
19
 
@@ -49,23 +54,28 @@ ActiveRecord::Schema.define do
49
54
  end
50
55
 
51
56
  class News < ActiveRecord::Base
57
+ include Lolita::Translation
52
58
  belongs_to :category, :dependent => :destroy
53
59
  has_one :meta_data, :as => :metaable, :dependent => :destroy
54
60
  translations :title, :body
55
61
  end
56
62
 
57
63
  class Category < ActiveRecord::Base
64
+ include Lolita::Translation
58
65
  has_many :news
59
66
  has_and_belongs_to_many :groups
60
67
  translations :name
61
68
  end
62
69
 
63
70
  class Group < ActiveRecord::Base
71
+ include Lolita::Translation
64
72
  has_and_belongs_to_many :categories
65
73
  translations :name
66
74
  end
67
75
 
68
76
  class MetaData < ActiveRecord::Base
77
+ set_table_name :meta_datas
78
+ include Lolita::Translation
69
79
  belongs_to :metaable, :polymorphic => true
70
80
  translations :title, :url, :keywords, :description
71
81
  end
@@ -89,7 +99,7 @@ class ::Hash
89
99
  self
90
100
  end
91
101
  end
92
- Spec::Runner.configure do |config|
102
+ RSpec.configure do |config|
93
103
  config.before(:each) do
94
104
  News.delete_all
95
105
  Category.delete_all
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lolita-translation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.2.18
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,22 +11,22 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2011-11-03 00:00:00.000000000Z
14
+ date: 2011-12-13 00:00:00.000000000Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: lolita
18
- requirement: &84206130 !ruby/object:Gem::Requirement
18
+ requirement: &16064780 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
- - - ! '>='
21
+ - - =
22
22
  - !ruby/object:Gem::Version
23
- version: 3.2.0.rc.3
23
+ version: 3.1.18
24
24
  type: :runtime
25
25
  prerelease: false
26
- version_requirements: *84206130
26
+ version_requirements: *16064780
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: shoulda
29
- requirement: &84205770 !ruby/object:Gem::Requirement
29
+ requirement: &16063400 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ! '>='
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: '0'
35
35
  type: :development
36
36
  prerelease: false
37
- version_requirements: *84205770
37
+ version_requirements: *16063400
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: bundler
40
- requirement: &84205350 !ruby/object:Gem::Requirement
40
+ requirement: &16062360 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ~>
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: 1.0.0
46
46
  type: :development
47
47
  prerelease: false
48
- version_requirements: *84205350
48
+ version_requirements: *16062360
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: jeweler
51
- requirement: &84204930 !ruby/object:Gem::Requirement
51
+ requirement: &16061500 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ~>
@@ -56,10 +56,10 @@ dependencies:
56
56
  version: 1.5.2
57
57
  type: :development
58
58
  prerelease: false
59
- version_requirements: *84204930
59
+ version_requirements: *16061500
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: rcov
62
- requirement: &84204370 !ruby/object:Gem::Requirement
62
+ requirement: &16060600 !ruby/object:Gem::Requirement
63
63
  none: false
64
64
  requirements:
65
65
  - - ! '>='
@@ -67,7 +67,7 @@ dependencies:
67
67
  version: '0'
68
68
  type: :development
69
69
  prerelease: false
70
- version_requirements: *84204370
70
+ version_requirements: *16060600
71
71
  description: Translates models in Lolita
72
72
  email: support@ithouse.lv
73
73
  executables: []
@@ -77,15 +77,13 @@ extra_rdoc_files:
77
77
  - README.md
78
78
  files:
79
79
  - .document
80
+ - .rspec
80
81
  - Gemfile
81
82
  - LICENSE.txt
82
83
  - README.md
83
84
  - Rakefile
84
85
  - VERSION
85
86
  - app/assets/.gitkeep
86
- - app/assets/javascripts/.gitkeep
87
- - app/assets/javascripts/lolita/translation/application.js
88
- - app/assets/stylesheets/lolita/translation/application.css
89
87
  - app/views/components/lolita/translation/_assets.html.erb
90
88
  - app/views/components/lolita/translation/_language_wrap.html.erb
91
89
  - app/views/components/lolita/translation/_switch.html.erb
@@ -94,13 +92,17 @@ files:
94
92
  - lib/generators/lolita_translation/USAGE
95
93
  - lib/generators/lolita_translation/has_translations_generator.rb
96
94
  - lib/lolita-translation.rb
97
- - lib/lolita-translation/has_translations.rb
95
+ - lib/lolita-translation/configuration.rb
96
+ - lib/lolita-translation/model.rb
97
+ - lib/lolita-translation/modules.rb
98
98
  - lib/lolita-translation/rails.rb
99
99
  - lib/lolita-translation/string.rb
100
100
  - lib/tasks/has_translations_tasks.rake
101
101
  - lolita-translation.gemspec
102
+ - public/javascripts/.gitkeep
103
+ - public/javascripts/lolita/translation/application.js
104
+ - public/stylesheets/lolita/translation/application.css
102
105
  - spec/has_translations_spec.rb
103
- - spec/spec.opts
104
106
  - spec/spec_helper.rb
105
107
  homepage: http://github.com/ithouse/lolita-translations
106
108
  licenses:
@@ -115,9 +117,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
117
  - - ! '>='
116
118
  - !ruby/object:Gem::Version
117
119
  version: '0'
118
- segments:
119
- - 0
120
- hash: 941878035
121
120
  required_rubygems_version: !ruby/object:Gem::Requirement
122
121
  none: false
123
122
  requirements:
@@ -126,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
125
  version: '0'
127
126
  requirements: []
128
127
  rubyforge_project:
129
- rubygems_version: 1.8.10
128
+ rubygems_version: 1.8.11
130
129
  signing_key:
131
130
  specification_version: 3
132
131
  summary: Lolita models translation plugin
@@ -1,387 +0,0 @@
1
- require 'stringio'
2
-
3
- module Lolita
4
- module Translation
5
- module SingletonMethods
6
- # Provides ability to add the translations for the model using delegate pattern.
7
- # Uses has_many association to the ModelNameTranslation.
8
- #
9
- # For example you have model Article with attributes title and text.
10
- # You want that attributes title and text to be translated.
11
- # For this reason you need to generate new model ArticleTranslation.
12
- # In migration you need to add:
13
- #
14
- # create_table :article_translations do |t|
15
- # t.references :article, :null => false
16
- # t.string :locale, :length => 2, :null => false
17
- # t.string :name, :null => false
18
- # end
19
- #
20
- # add_index :articles, [:article_id, :locale], :unique => true, :name => 'unique_locale_for_article_id'
21
- #
22
- # And in the Article model:
23
- #
24
- # translations :title, :text
25
- #
26
- # This will adds:
27
- #
28
- # * named_scope (translated) and has_many association to the Article model
29
- # * locale presence validation to the ArticleTranslation model.
30
- #
31
- # Notice: if you want to have validates_presence_of :article, you should use :inverse_of.
32
- # Support this by yourself. Better is always to use artile.translations.build() method.
33
- #
34
- # For more information please read API. Feel free to write me an email to:
35
- # dmitry.polushkin@gmail.com.
36
- #
37
- # ===
38
- #
39
- # You also can pass attributes and options to the translations class method:
40
- #
41
- # translations :title, :text, :fallback => true, :writer => true, :nil => nil
42
- #
43
- # ===
44
- #
45
- # Configuration options:
46
- #
47
- # * <tt>:fallback</tt> - if translation for the current locale not found.
48
- # By default true.
49
- # Uses algorithm of fallback:
50
- # 0) current translation (using I18n.locale);
51
- # 1) default locale (using I18n.default_locale);
52
- # 2) :nil value (see <tt>:nil</tt> configuration option)
53
- # * <tt>:reader</tt> - add reader attributes to the model and delegate them
54
- # to the translation model columns. Add's fallback if it is set to true.
55
- # * <tt>:writer</tt> - add writer attributes to the model and assign them
56
- # to the translation model attributes.
57
- # * <tt>:nil</tt> - when reader cant find string, it returns by default an
58
- # empty string. If you want to change this setting for example to nil,
59
- # add :nil => nil
60
- #
61
- # ===
62
- #
63
- # When you are using <tt>:writer</tt> option, you can create translations using
64
- # update_attributes method. For example:
65
- #
66
- # Article.create!
67
- # Article.update_attributes(:title => 'title', :text => 'text')
68
- #
69
- # ===
70
- #
71
- # <tt>translated</tt> named_scope is useful when you want to find only those
72
- # records that are translated to a specific locale.
73
- # For example if you want to find all Articles that is translated to an english
74
- # language, you can write: Article.translated(:en)
75
- #
76
- # <tt>has_translation?(locale)</tt> method, that returns true if object's model
77
- # have a translation for a specified locale
78
- #
79
- # <tt>translation(locale)</tt> method finds translation with specified locale.
80
- #
81
- # <tt>all_translations</tt> method that returns all possible translations in
82
- # ordered hash (useful when creating forms with nested attributes).
83
- def translations *attrs
84
- include Lolita::Translation::InstanceMethods
85
- options = {
86
- :fallback => true,
87
- :reader => true,
88
- :writer => false,
89
- :nil => ''
90
- }.merge(attrs.extract_options!)
91
- options.assert_valid_keys([:fallback, :reader, :writer, :nil,:table_name])
92
- self.extend(Lolita::Translation::ClassMethods)
93
- self.class_eval do
94
- translation_class_name = "#{self.name}Translation"
95
- translation_class = self.define_translation_class(translation_class_name, attrs,options)
96
- belongs_to = self.name.demodulize.underscore.to_sym
97
-
98
- write_inheritable_attribute :has_translations_options, options
99
- class_inheritable_reader :has_translations_options
100
-
101
- write_inheritable_attribute :columns_has_translations, (columns rescue []).collect{|col| col if attrs.include?(col.name.to_sym)}.compact
102
- class_inheritable_reader :columns_has_translations
103
-
104
- if options[:reader]
105
- attrs.each do |name|
106
- send :define_method, name do
107
- unless ::I18n.default_locale == ::I18n.locale
108
- translation = self.translation(::I18n.locale)
109
- if translation.nil?
110
- if has_translations_options[:fallback]
111
- (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
112
- else
113
- has_translations_options[:nil]
114
- end
115
- else
116
- if @return_raw_data
117
- (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
118
- else
119
- value = translation.send(name) and value.set_origins(self,name)
120
- end
121
- end
122
- else
123
- (self[name].nil? || self[name].blank?) ? has_translations_options[:nil] : self[name].set_origins(self,name)
124
- end
125
- end
126
- end
127
- end
128
-
129
- @translation_attrs = attrs
130
- def self.translation_attrs
131
- @translation_attrs
132
- end
133
- #adapter = Lolita::DBI::Base.create(self)
134
- has_many :translations, :class_name => translation_class_name, :foreign_key => translation_class.master_id, :dependent => :destroy
135
- 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? }
136
- translation_class.belongs_to belongs_to
137
- translation_class.validates_presence_of :locale
138
- translation_class.validates_uniqueness_of :locale, :scope => translation_class.master_id
139
-
140
- # Workaround to support Rails 2
141
- scope_method =:scope
142
-
143
- send scope_method, :translated, lambda { |locale|
144
- where("#{translation_class.table_name}.locale = ?", locale.to_s).joins(:translations)
145
- }
146
-
147
- class << self
148
-
149
- def find_with_translations(*args,&block)
150
- unless ::I18n.locale == ::I18n.default_locale
151
- if args && args[0].kind_of?(Hash)
152
- args[0][:include] ||=[]
153
- args[0][:include] << :translations
154
- end
155
- end
156
- find_without_translations *args, &block
157
- end
158
- alias_method_chain :find, :translations
159
- end
160
-
161
- end
162
- end
163
- end
164
-
165
- module ClassMethods
166
- # adds :translations to :includes if current locale differs from default
167
- #FIXME is this enough with find or need to create chain for find_last, find_first and others?
168
- # def find(*args)
169
- # if args[0].kind_of?(Hash)
170
- # args[0][:include] ||= []
171
- # args[0][:include] << :translations
172
- # end unless I18n.locale == I18n.default_locale
173
- # find_without_translations(*args)
174
- # end
175
-
176
- # Defines given class recursively
177
- # Example:
178
- # create_class('Cms::Text::Page', Object, ActiveRecord::Base)
179
- # => Cms::Text::Page
180
- def create_class(class_name, parent, superclass=nil, &block)
181
- first,*other = class_name.split("::")
182
- if other.empty?
183
- klass = superclass ? Class.new(superclass, &block) : Class.new(&block)
184
- parent.const_set(first, klass)
185
- else
186
- klass = Class.new
187
- parent = unless parent.const_defined?(first)
188
- parent.const_set(first, klass)
189
- else
190
- first.constantize
191
- end
192
- create_class(other.join('::'), parent, superclass, &block)
193
- end
194
- end
195
-
196
- # defines "ModelNameTranslation" if it's not defined manualy
197
- def define_translation_class name, attrs, options = {}
198
- klass = name.constantize rescue nil
199
- adapter = Lolita::DBI::Base.create(self)
200
- unless klass
201
- klass = create_class(name, Object, get_orm_class(adapter)) do
202
- if adapter.dbi.adapter_name == :mongoid
203
- include Mongoid::Document
204
- end
205
- # set's real table name
206
- translation_adapter = Lolita::DBI::Base.create(self)
207
- translation_adapter.collection_name = options[:table_name] || adapter.collection_name.to_s.singularize + "_translation"
208
-
209
- cattr_accessor :translate_attrs, :master_id
210
-
211
- # before friendly_id 4.x
212
- if adapter.klass.respond_to?(:uses_friendly_id?) && adapter.klass.send(:uses_friendly_id?)
213
- parent_config = adapter.klass.friendly_id_config
214
-
215
- has_friendly_id parent_config.method,
216
- :allow_nil => parent_config.allow_nil,
217
- :approximate_ascii => parent_config.approximate_ascii,
218
- :ascii_approximation_options => [:russian],
219
- :max_length => parent_config.max_length,
220
- :reserved_words => parent_config.reserved_words,
221
- :use_slug => parent_config.use_slug
222
- end
223
-
224
- # override validate to vaidate only translate fields from master Class
225
- def validate
226
- item = self.class.name.sub('Translation','').constantize.new(self.attributes.clone.delete_if{|k,_| !self.class.translate_attrs.include?(k.to_sym)})
227
- item_adapter = Lolita::DBI::Adapter.add(item.class)
228
- self_adapter = Lolita::DBI::Adapter.add(self)
229
- was_table_name = item_adapter.collection_name
230
- item_adapter.collection_name = self_adapter.collection_name
231
- item.valid? rescue
232
- self.class.translate_attrs.each do |attr|
233
- errors_on_attr = item.errors.on(attr)
234
- self.errors.add(attr,errors_on_attr) if errors_on_attr
235
- end
236
- item_adapter.collection_name = was_table_name
237
- end
238
- extend Lolita::TranslationClassMethods
239
-
240
- end
241
- klass.translate_attrs = attrs
242
- else
243
- unless klass.respond_to?(:translate_attrs)
244
- klass.send(:cattr_accessor, :translate_attrs, :master_id)
245
- klass.send(:extend,TranslationClassMethods)
246
- klass.translate_attrs = attrs
247
- end
248
- end
249
-
250
- klass.extract_master_id(name)
251
- klass
252
- end
253
-
254
- def get_orm_class(adapter)
255
- adapter.dbi.adapter_name == :active_record ? ActiveRecord::Base : nil
256
- end
257
- # creates translation table and adds missing fields
258
- # So at first add the "translations :name, :desc" in your model
259
- # then put YourModel.sync_translation_table! in db/seed.rb and run "rake db:seed"
260
- # Later adding more fields in translations array, just run agin "rake db:seed"
261
- # If you want to remove fields do it manualy, it's safer
262
- def sync_translation_table!
263
- out = StringIO.new
264
- $stdout = out
265
- self_adapter = Lolita::DBI::Base.create(self)
266
- translations_class = self.reflect_on_association(:translations).klass
267
- translations_adapter = Lolita::DBI::Base.create(translations_class)
268
-
269
- if translations_adapter.dbi.adapter_name == :active_record
270
- translations_table = translations_adapter.collection_name
271
-
272
- unless ActiveRecord::Migration::table_exists?(translations_table)
273
- ActiveRecord::Migration.create_table translations_table do |t|
274
- t.integer translations_class.master_id, :null => false
275
- t.string :locale, :null => false, :limit => 5
276
- columns_has_translations.each do |col|
277
- t.send(col.type,col.name)
278
- end
279
- end
280
- ActiveRecord::Migration.add_index translations_table, [translations_class.master_id, :locale], :unique => true
281
- translations_class.reset_column_information
282
- else
283
- changes = false
284
- columns_has_translations.each do |col|
285
- unless translations_class.columns_hash.has_key?(col.name)
286
- ActiveRecord::Migration.add_column(translations_table, col.name, col.type)
287
- changes = true
288
- end
289
- end
290
- translations_class.reset_column_information if changes
291
- end
292
- elsif translations_adapter.dbi.adapter_name == :mongoid
293
- unless translations_class.fields["locale"]
294
- translations_class.class_eval do
295
- field(self.master_id, :type => Integer)
296
- field :locale, :type => String
297
- columns_has_translations.each do |col|
298
- field col.name, :type => col.type
299
- end
300
- index(
301
- [
302
- [ self.master_id, Mongo::ASCENDING ],
303
- [ :locale, Mongo::ASCENDING ]
304
- ],
305
- unique: true
306
- )
307
- end
308
- else
309
- columns_has_translations.each do |col|
310
- unless translations_class.fields[col.name.to_s]
311
- translations_class.field(col.name,:type => col.type)
312
- end
313
- end
314
- end
315
- end
316
- $stdout = STDOUT
317
- end
318
- end
319
-
320
- module InstanceMethods
321
-
322
- # forces given locale
323
- # I18n.locale = :lv
324
- # a = Article.find 18
325
- # a.title
326
- # => "LV title"
327
- # a.in(:en).title
328
- # => "EN title"
329
- def in locale
330
- locale.to_sym == ::I18n.default_locale ? self : find_translation(locale)
331
- end
332
-
333
- def find_or_build_translation(*args)
334
- locale = args.first.to_s
335
- build = args.second.present?
336
- find_translation(locale) || (build ? self.translations.build(:locale => locale) : self.translations.new(:locale => locale))
337
- end
338
-
339
- def translation(locale)
340
- find_translation(locale.to_s)
341
- end
342
-
343
- def all_translations
344
- t = ::I18n.available_locales.map do |locale|
345
- [locale, find_or_build_translation(locale)]
346
- end
347
- ActiveSupport::OrderedHash[t]
348
- end
349
-
350
- def has_translation?(locale)
351
- return true if locale == ::I18n.default_locale
352
- find_translation(locale).present?
353
- end
354
-
355
- # if object is new, then nested slaves ar built for all available locales
356
- def build_nested_translations
357
- if (::I18n.available_locales.size - 1) > self.translations.size
358
- ::I18n.available_locales.clone.delete_if{|l| l == ::I18n.default_locale}.each do |l|
359
- options = {:locale => l.to_s}
360
- self_adapter = Lolita::DBI::Base.create(self.class)
361
- options[self_adapter.reflect_on_association(:translations).klass.master_id] = self.id unless self.new_record?
362
- self.translations.build(options) unless self.translations.map(&:locale).include?(l.to_s)
363
- end
364
- end
365
- end
366
-
367
-
368
- #private is no good
369
-
370
- def find_translation(locale)
371
- locale = locale.to_s
372
- translations.detect { |t| t.locale == locale }
373
- end
374
- end
375
- end
376
-
377
- module TranslationClassMethods
378
- # sets real master_id it's aware of STI
379
- def extract_master_id name
380
- master_class = name.sub('Translation','').constantize
381
- #FIXME why need to check superclass ?
382
- class_name = master_class.name #!master_class.superclass.abstract_class? ? master_class.superclass.name : master_class.name
383
- self.master_id = :"#{class_name.demodulize.underscore}_id"
384
- end
385
- end
386
- end
387
-
data/spec/spec.opts DELETED
@@ -1,3 +0,0 @@
1
- --colour
2
- --reverse
3
- --backtrace