lolita-translation 0.0.1

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 ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rails", ">=3.0.0"
4
+ group :development do
5
+ gem "shoulda", ">= 0"
6
+ gem "bundler", "~> 1.0.0"
7
+ gem "jeweler", "~> 1.5.2"
8
+ gem "rcov", ">= 0"
9
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 ITHouse
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,180 @@
1
+ LolitaHasTranslations
2
+ ======================
3
+
4
+ This is a fork of http://github.com/dmitry/has_translations with small changes.
5
+
6
+ 1. The main difference is that the translations table holds only translations, but not the original data from default_locale, so:
7
+
8
+ I18n.default_locale = :en
9
+ I18n.locale = :lv
10
+
11
+ a = Article.create :title => "Title in EN"
12
+ a.title
13
+ # returns blank, because current locale is LV and there is no translation in it
14
+ => ""
15
+ I18n.locale = :en
16
+ a.title
17
+ => "Title in EN"
18
+ a.translations.create :title => "Title in LV", :locale => 'lv'
19
+ I18n.locale = :lv
20
+ a.title
21
+ => "Title in LV"
22
+
23
+ 2. When a "find" is executed and current language is not the same as default language then :translations are added to :includes
24
+ to pre fetch all translations.
25
+
26
+ 3. The "ModelNameTranslation" class is created for you automaticly with all validations for ranslated fields. Of course you can create it manualy for custom vlidations and other.
27
+
28
+ 4. You dont have to create migration for the translation table, just add a line for every translated model in `db/seed.rb`
29
+
30
+ TextPage.sync_translation_table!
31
+ Blog::Article.sync_translation_table!
32
+
33
+ And run `rake db:seed` and it will do it for you. It also updates the table if you add news columns in the `translations :name, :title .....` method.
34
+
35
+ HasTranslations v0.3.1
36
+ ======================
37
+
38
+ This simple plugin creates translations for your model.
39
+ Uses delegation pattern: http://en.wikipedia.org/wiki/Delegation_pattern
40
+
41
+ Tested with ActiveRecord versions: 2.3.5, 2.3.9, 3.0.0 (to test with Rails 3 run `rake RAILS_VERSION=3.0`)
42
+
43
+ Installation
44
+ ============
45
+
46
+ gem install has_translations
47
+
48
+ or as a plugin
49
+
50
+ script/plugin install git://github.com/dmitry/has_translations.git
51
+
52
+ Example
53
+ =======
54
+
55
+ For example you have Article model and you want to have title and text to be translated.
56
+
57
+ Create model named ArticleTranslation (Rule: [CamelCaseModelName]Translation)
58
+
59
+ Migration should have `locale` as a string with two letters and `belongs_to associative id`, like:
60
+
61
+ class CreateArticleTranslations < ActiveRecord::Migration
62
+ def self.up
63
+ create_table :article_translations do |t|
64
+ t.integer :article_id, :null => false
65
+ t.string :locale, :null => false, :limit => 2
66
+ t.string :title, :null => false
67
+ t.text :text, :null => false
68
+ end
69
+
70
+ add_index :article_translations, [:article_id, :locale], :unique => true
71
+ end
72
+
73
+ def self.down
74
+ drop_table :article_translations
75
+ end
76
+ end
77
+
78
+ Add to article model `translations :value1, :value2`:
79
+
80
+ class Article < ActiveRecord::Base
81
+ translations :title, :text
82
+ end
83
+
84
+ And that's it. Now you can add your translations using:
85
+
86
+ article = Article.create
87
+
88
+ article.translations.create(:locale => 'en', :title => 'title', :text => 'text') # or ArticleTranslation.create(:article => article, :locale => 'en', :title => 'title', :text => 'text')
89
+ article.translations.create(:locale => 'ru', :title => 'заголовок', :text => 'текст')
90
+ article.reload # reload cached translations association array
91
+ I18n.locale = :en
92
+ article.text # text
93
+ I18n.locale = :ru
94
+ article.title # заголовок
95
+
96
+ You can use text filtering plugins, like acts_as_sanitiled and validations, and anything else that is available to the ActiveRecord:
97
+
98
+ class ArticleTranslation < ActiveRecord::Base
99
+ acts_as_sanitiled :title, :text
100
+
101
+ validates_presence_of :title, :text
102
+ validates_length_of :title, :maximum => 100
103
+ end
104
+
105
+ Options:
106
+
107
+ * :fallback => true [default: false] - fallback 1) default locale; 2) first from translations;
108
+ * :reader => false [default: true] - add reader to the model object
109
+ * :writer => true [default: false] - add writer to the model object
110
+ * :nil => nil [default: ''] - if no model found by default returns empty string, you can set it for example to `nil` (no `lambda` supported)
111
+
112
+ It's better to use translations with `accepts_nested_attributes_for`:
113
+
114
+ accepts_nested_attributes_for :translations
115
+
116
+ To create a form for this you can use `all_translations` method. It's have all
117
+ the locales that you have added using the `I18n.available_locales=` method.
118
+ If translation for one of the locale isn't exists, it will build it with :locale.
119
+ So an example which I used in the production (using `formtastic` gem):
120
+
121
+ <% semantic_form_for [:admin, @article] do |f| %>
122
+ <%= f.error_messages %>
123
+
124
+ <% f.inputs :name => "Basic" do %>
125
+ <% object.all_translations.values.each do |translation| %>
126
+ <% f.semantic_fields_for :translations, translation do |ft| %>
127
+ <%= ft.input :title, :label => "Title #{ft.object.locale.to_s.upcase}" %>
128
+ <%= ft.input :text, :label => "Text #{ft.object.locale.to_s.upcase}" %>
129
+ <%= ft.input :locale, :as => :hidden %>
130
+ <% end %>
131
+ <% end %>
132
+ <% end %>
133
+ <% end %>
134
+
135
+ Sometimes you have validations in the translation model, and if you want to skip
136
+ the translations that you don't want to add to the database, you can use
137
+ `:reject_if` option, which is available for the `accepts_nested_attributes_for`:
138
+
139
+ accepts_nested_attributes_for :translations, :reject_if => lambda { |attrs| attrs['title'].blank? && attrs['text'].blank? }
140
+
141
+ named_scope `translated(locale)` - with that named_scope you can find only
142
+ those models that is translated only to specific locale. For example if you will
143
+ have 2 models, one is translated to english and the second one isn't, then it
144
+ `Article.translated(:en)` will find only first one.
145
+
146
+ PS
147
+ ==
148
+
149
+ I suggest you to use latest i18n gem, include it in your rails 2 environment:
150
+
151
+ config.gem 'i18n', :version => '0.4.1' # change version to the latest
152
+
153
+ TODO
154
+ ====
155
+
156
+ * add installation description to readme
157
+ * model and migration generators
158
+ * caching
159
+ * write more examples: fallback feature
160
+ * write blog post about comparison and benefits of this plugin between another translation model plugins
161
+
162
+
163
+ Alternatives
164
+ ============
165
+
166
+ I know three of them:
167
+
168
+ * [puret](http://github.com/jo/puret) - special for Rails 3 and almost the same as this project.
169
+ * [globalite2](http://github.com/joshmh/globalize2) - a lot of magic.
170
+ * [model_translations](http://github.com/janne/model_translations) - almost the same as this project, but more with more code in lib.
171
+ * [translatable_columns](http://github.com/iain/translatable_columns) - different approach: every column have own postfix "_#{locale}" in the same table (sometimes it could be fine).
172
+
173
+
174
+ Used in
175
+ =======
176
+
177
+ [noch.es](http://noch.es/), [eten.es](http://www.eten.es), [sem.ee](http://sem.ee/)
178
+
179
+
180
+ Copyright (c) 2009-2010 [Dmitry Polushkin], released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "lolita-translation"
16
+ gem.homepage = "http://github.com/ithouse/lolita-translations"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Lolita models translation plugin}
19
+ gem.description = %Q{Translates models in Lolita}
20
+ gem.email = "support@ithouse.lv"
21
+ gem.authors = ["ITHouse", "Gatis Tomsons", "Arturs Meisters"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'spec'
32
+ test.pattern = 'spec/**/*_spec.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'spec'
39
+ test.pattern = 'spec/**/*_spec.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "lolita-translation #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,76 @@
1
+ <% if resource.new_record? %>
2
+ <p>-</p>
3
+ <% else %>
4
+ <form "#">
5
+ <div class="tab-content">
6
+ <div class="field" id="field_translation_language">
7
+ <label for="translation_language"><%=I18n.t("lolita.tabs.translation.fields.language")%>
8
+ <div class="field-value" id="field_translation_language_value">
9
+ <%= select_tag :has_translations_locale, options_for_select([""] + I18n.available_locales.clone.delete_if{|l| l == I18n.default_locale}), :onchange => "switch_has_translations_language($(this))" %>
10
+ </div>
11
+ </div>
12
+
13
+ <div id="has_translations_fields" class="field" style="display: none">
14
+ <% resource.class.columns_has_translations.each do |column| %>
15
+ <label for="<%=column.name%>_field"><%=resource.class.human_attribute_name(column.name)%></label>
16
+ <div class="fl field-value" id="<%=column.name%>_field">
17
+ <% if column.type == :string %>
18
+ <%= text_field_tag 'has_translations_' + column.name, nil, :class => 'txt' %>
19
+ <% elsif column.type == :text %>
20
+ <%= text_area_tag 'has_translations_' + column.name, nil, :class => "textarea",:rows=>20, :cols=>40 %>
21
+ <% end %>
22
+ </div>
23
+ <% end %>
24
+ </div>
25
+ </div>
26
+ </form>
27
+ <% resource.build_nested_translations %>
28
+ <%= form_for resource, :url => "/" do |form| %>
29
+ <%= form.fields_for :translations do |tf| %>
30
+ <div id="object_translation_fields_<%= tf.object.locale %>">
31
+ <% resource.class.columns_has_translations.each do |column| %>
32
+ <%= tf.hidden_field column.name, :class => column.name %>
33
+ <% end %>
34
+ <%= tf.hidden_field :id, :class => "id" %>
35
+ <%= tf.hidden_field tf.object.class.master_id, :value => resource.id %>
36
+ <%= tf.hidden_field :locale, :class => "locale" %>
37
+ </div>
38
+ <% end %>
39
+ <% end %>
40
+ <script type="text/javascript">
41
+ var columns = [<%= resource.class.columns_has_translations.map{|r| "'#{r.name}'"}.join(",") %>];
42
+ var blur = false;
43
+ function switch_has_translations_language(select){
44
+ var locale = select.val();
45
+ if(locale == ""){
46
+ $('#has_translations_fields').hide()
47
+ }else{
48
+ $.each(columns,function(i,column){
49
+ var content = $('#object_translation_fields_'+locale+' .'+column).val();
50
+ $('#has_translations_'+column).val(content);
51
+ if($('#has_translations_'+column).get(0).tagName == 'TEXTAREA'){
52
+ tinymce.EditorManager.getInstanceById('has_translations_'+column).setContent(content);
53
+ }
54
+ });
55
+ $('#has_translations_fields').show()
56
+ }
57
+ if(!blur){
58
+ $.each($('#has_translations_fields textarea'), function(i,area){
59
+ var item = $(area);
60
+ var tiny_editor = tinymce.EditorManager.getInstanceById(item.attr("id"));
61
+ tinymce.dom.Event.add(tiny_editor.getWin(), "blur", function(){
62
+ var textarea = $(tinymce.EditorManager.getInstanceById(item.attr("id")).getElement());
63
+ tiny_editor.save();
64
+ textarea.blur();
65
+ });
66
+ });
67
+ blur = true;
68
+ }
69
+ }
70
+ $.each(columns,function(i,column){
71
+ $('#has_translations_'+column).blur(function(){
72
+ $('#object_translation_fields_'+$("#has_translations_locale").val()+' .'+$(this).attr("name").split("_")[2]).val($(this).val())
73
+ })
74
+ })
75
+ </script>
76
+ <% end %>
@@ -0,0 +1,78 @@
1
+ <% if @object.new_record? %>
2
+ <p>Tulkošana iespējama tikai eksistējošiem ierakstiem.</p>
3
+ <% else %>
4
+ <div class="managed-create-line">
5
+ <span class="brick"><%= t "fields.language" %></span>
6
+ <div class="fl">
7
+ <%= select_tag :has_translations_locale, options_for_select([""] + I18n.available_locales.clone.delete_if{|l| l == I18n.default_locale}), :onchange => "switch_has_translations_language($(this))" %>
8
+ </div>
9
+ </div>
10
+ <div id="has_translations_fields" style="display: none">
11
+ <% @object.class.columns_has_translations.each do |column| %>
12
+ <div class="managed-create-line">
13
+ <span class="brick"><%= @object.class.human_attribute_name(column.name) %></span>
14
+ <div class="fl">
15
+ <% if column.type == :string %>
16
+ <%= text_field_tag 'has_translations_' + column.name, nil, :class => 'txt' %>
17
+ <% elsif column.type == :text %>
18
+ <%= text_area_tag 'has_translations_' + column.name, nil, :class => "textarea" %>
19
+ <script type="text/javascript">
20
+ tinyMCE.execCommand('mceRemoveControl', false,'has_translations_<%= column.name %>');
21
+ tinyMCE.execCommand('mceAddControl', false,'has_translations_<%= column.name %>');
22
+ </script>
23
+ <br /><br />
24
+ <% end %>
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+ </div>
29
+ <div class="clear"></div>
30
+ <div></div>
31
+ <% @object.build_nested_translations %>
32
+ <% object_form.fields_for :translations do |tf| %>
33
+ <div id="object_translation_fields_<%= tf.object.locale %>">
34
+ <% @object.class.columns_has_translations.each do |column| %>
35
+ <%= tf.hidden_field column.name, :class => column.name %>
36
+ <% end %>
37
+ <%= tf.hidden_field :id, :class => "id" %>
38
+ <%= tf.hidden_field tf.object.class.master_id, :value => @object.id %>
39
+ <%= tf.hidden_field :locale, :class => "locale" %>
40
+ </div>
41
+ <% end %>
42
+ <script type="text/javascript">
43
+ var columns = <%= @object.class.columns_has_translations.map(&:name).inspect %>;
44
+ var blur = false;
45
+ function switch_has_translations_language(select){
46
+ var locale = select.val();
47
+ if(locale == ""){
48
+ $('#has_translations_fields').hide()
49
+ }else{
50
+ $.each(columns,function(i,column){
51
+ var content = $('#object_translation_fields_'+locale+' .'+column).val();
52
+ $('#has_translations_'+column).val(content);
53
+ if($('#has_translations_'+column).get(0).tagName == 'TEXTAREA'){
54
+ tinymce.EditorManager.getInstanceById('has_translations_'+column).setContent(content);
55
+ }
56
+ });
57
+ $('#has_translations_fields').show()
58
+ }
59
+ if(!blur){
60
+ $.each($('#has_translations_fields textarea'), function(i,area){
61
+ var item = $(area);
62
+ var tiny_editor = tinymce.EditorManager.getInstanceById(item.attr("id"));
63
+ tinymce.dom.Event.add(tiny_editor.getWin(), "blur", function(){
64
+ var textarea = $(tinymce.EditorManager.getInstanceById(item.attr("id")).getElement());
65
+ tiny_editor.save();
66
+ textarea.blur();
67
+ });
68
+ });
69
+ blur = true;
70
+ }
71
+ }
72
+ $.each(columns,function(i,column){
73
+ $('#has_translations_'+column).blur(function(){
74
+ $('#object_translation_fields_'+$("#has_translations_locale").val()+' .'+$(this).attr("name").split("_")[2]).val($(this).val())
75
+ })
76
+ })
77
+ </script>
78
+ <% end %>
@@ -0,0 +1,8 @@
1
+ en:
2
+ lolita:
3
+ tabs:
4
+ titles:
5
+ translation: Translation
6
+ translation:
7
+ fields:
8
+ language: Language
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Explain the generator
3
+
4
+ Example:
5
+ ./script/generate has_translations Thing
6
+
7
+ This will create:
8
+ what/will/it/create
@@ -0,0 +1,8 @@
1
+ class HasTranslationsGenerator < Rails::Generator::NamedBase
2
+ def manifest
3
+ record do |m|
4
+ # m.directory "lib"
5
+ # m.template 'README', "README"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ module Lolita
2
+ module Configuration
3
+ module Tab
4
+ class Translation < Lolita::Configuration::Tab::Base
5
+
6
+ def initialize(dbi,*args,&block)
7
+ @type=:translation
8
+ super
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,295 @@
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
+ has_many :translations, :class_name => translation_class_name, :foreign_key => translation_class.master_id, :dependent => :destroy
268
+ 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? }
269
+ translation_class.belongs_to belongs_to
270
+ translation_class.validates_presence_of :locale
271
+ translation_class.validates_uniqueness_of :locale, :scope => translation_class.master_id
272
+
273
+ # Workaround to support Rails 2
274
+ scope_method = if ActiveRecord::VERSION::MAJOR < 3 then :named_scope else :scope end
275
+
276
+ send scope_method, :translated, lambda { |locale| {:conditions => ["#{translation_class.table_name}.locale = ?", locale.to_s], :joins => :translations} }
277
+
278
+ #private is no good
279
+
280
+ def find_translation(locale)
281
+ locale = locale.to_s
282
+ translations.detect { |t| t.locale == locale }
283
+ end
284
+ end
285
+ end
286
+
287
+ module TranslationClassMethods
288
+ # sets real master_id it's aware of STI
289
+ def extract_master_id name
290
+ master_class = name.sub('Translation','').constantize
291
+ #FIXME why need to check superclass ?
292
+ class_name = master_class.name #!master_class.superclass.abstract_class? ? master_class.superclass.name : master_class.name
293
+ self.master_id = :"#{class_name.demodulize.underscore}_id"
294
+ end
295
+ end
@@ -0,0 +1,5 @@
1
+ module LolitaHasTranslation
2
+ class Engine < Rails::Engine
3
+
4
+ end
5
+ end
@@ -0,0 +1,19 @@
1
+ class String
2
+ attr_accessor :origin_model, :origin_name
3
+ def set_origins obj, name
4
+ self.origin_model = obj
5
+ self.origin_name = name
6
+ self
7
+ end
8
+ # forces given locale
9
+ # I18n.locale = :lv
10
+ # a = Article.find 18
11
+ # a.title
12
+ # => "LV title"
13
+ # a.title.in(:en)
14
+ # => "EN title"
15
+ def in locale
16
+ return self unless self.origin_model
17
+ translation = self.origin_model.in(locale) and translation.send(self.origin_name)
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+
2
+ require 'lolita-translation/string.rb'
3
+ require 'lolita-translation/has_translations.rb'
4
+ require 'lolita-translation/rails'
5
+
6
+ module Lolita
7
+ module Configuration
8
+ module Tab
9
+ autoload :Translation, "lolita-translation/configuration/tab/translation"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :has_translations do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,77 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{lolita-translation}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["ITHouse", "Gatis Tomsons", "Arturs Meisters"]
12
+ s.date = %q{2011-08-01}
13
+ s.description = %q{Translates models in Lolita}
14
+ s.email = %q{support@ithouse.lv}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "LICENSE.txt",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "app/views/components/lolita/configuration/tab/translation/_display.html.erb",
27
+ "app/views/lolita/has_translations/_tab.html.erb",
28
+ "config/locales/en.yml",
29
+ "lib/generators/lolita_translation/USAGE",
30
+ "lib/generators/lolita_translation/has_translations_generator.rb",
31
+ "lib/lolita-translation.rb",
32
+ "lib/lolita-translation/configuration/tab/translation.rb",
33
+ "lib/lolita-translation/has_translations.rb",
34
+ "lib/lolita-translation/rails.rb",
35
+ "lib/lolita-translation/string.rb",
36
+ "lib/tasks/has_translations_tasks.rake",
37
+ "lolita-translation.gemspec",
38
+ "lolita-translations.gemspec",
39
+ "spec/has_translations_spec.rb",
40
+ "spec/spec.opts",
41
+ "spec/spec_helper.rb"
42
+ ]
43
+ s.homepage = %q{http://github.com/ithouse/lolita-translations}
44
+ s.licenses = ["MIT"]
45
+ s.require_paths = ["lib"]
46
+ s.rubygems_version = %q{1.6.2}
47
+ s.summary = %q{Lolita models translation plugin}
48
+ s.test_files = [
49
+ "spec/has_translations_spec.rb",
50
+ "spec/spec_helper.rb"
51
+ ]
52
+
53
+ if s.respond_to? :specification_version then
54
+ s.specification_version = 3
55
+
56
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
57
+ s.add_runtime_dependency(%q<rails>, [">= 3.0.0"])
58
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
59
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
60
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
61
+ s.add_development_dependency(%q<rcov>, [">= 0"])
62
+ else
63
+ s.add_dependency(%q<rails>, [">= 3.0.0"])
64
+ s.add_dependency(%q<shoulda>, [">= 0"])
65
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
66
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
67
+ s.add_dependency(%q<rcov>, [">= 0"])
68
+ end
69
+ else
70
+ s.add_dependency(%q<rails>, [">= 3.0.0"])
71
+ s.add_dependency(%q<shoulda>, [">= 0"])
72
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
73
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
74
+ s.add_dependency(%q<rcov>, [">= 0"])
75
+ end
76
+ end
77
+
@@ -0,0 +1,60 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{lolita-translations}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["ITHouse", "Gatis Tomsons", "Arturs Meisters"]
12
+ s.date = %q{2011-08-01}
13
+ s.description = %q{Translates models in Lolita}
14
+ s.email = %q{support@ithouse.lv}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "LICENSE.txt",
23
+ "Rakefile",
24
+ "VERSION"
25
+ ]
26
+ s.homepage = %q{http://github.com/ithouse/lolita-translations}
27
+ s.licenses = ["MIT"]
28
+ s.require_paths = ["lib"]
29
+ s.rubygems_version = %q{1.6.2}
30
+ s.summary = %q{Lolita models translation plugin}
31
+ s.test_files = [
32
+ "spec/has_translations_spec.rb",
33
+ "spec/spec_helper.rb"
34
+ ]
35
+
36
+ if s.respond_to? :specification_version then
37
+ s.specification_version = 3
38
+
39
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
40
+ s.add_runtime_dependency(%q<rails>, [">= 3.0.0"])
41
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
42
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
43
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
44
+ s.add_development_dependency(%q<rcov>, [">= 0"])
45
+ else
46
+ s.add_dependency(%q<rails>, [">= 3.0.0"])
47
+ s.add_dependency(%q<shoulda>, [">= 0"])
48
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
49
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
50
+ s.add_dependency(%q<rcov>, [">= 0"])
51
+ end
52
+ else
53
+ s.add_dependency(%q<rails>, [">= 3.0.0"])
54
+ s.add_dependency(%q<shoulda>, [">= 0"])
55
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
56
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
57
+ s.add_dependency(%q<rcov>, [">= 0"])
58
+ end
59
+ end
60
+
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ describe 'HasTranslations' do
5
+ it "should switch locales" do
6
+ g = Group.create!(:name => "Sport")
7
+ c = Category.create!(:name => "Golf", :groups => [g])
8
+ z = News.create!(:title => "Tiger Woods sucks", :body => Faker::Lorem::paragraphs(10).join, :category_id => c.id)
9
+ # translate
10
+ g.translations.create!(:locale => 'lv', :name => "Sports")
11
+ c.translations.create!(:locale => 'lv', :name => "Golfs")
12
+ z.translations.create!(:locale => 'lv', :title => "Taigers Vuds nekam neder")
13
+
14
+ g.name.should == "Sport"
15
+ c.name.should == "Golf"
16
+ z.title.should == "Tiger Woods sucks"
17
+
18
+ I18n.locale = :lv
19
+
20
+ g.name.should == "Sports"
21
+ c.name.should == "Golfs"
22
+ z.title.should == "Taigers Vuds nekam neder"
23
+
24
+ z.category.name.should == "Golfs"
25
+ z.category.groups.first.name.should == "Sports"
26
+
27
+ z.destroy
28
+ c.destroy
29
+ g.destroy
30
+ #--------------------------------
31
+ I18n.locale = I18n.default_locale
32
+ end
33
+
34
+ it "should load requested locale with 'in' method" do
35
+ g = Group.create!(:name => "Sport")
36
+ g.translations.create!(:locale => 'lv', :name => "Sports")
37
+
38
+ g = Group.find_by_name "Sport"
39
+ g.name.should == "Sport"
40
+ g.name.in(:lv).should == "Sports"
41
+ g.name.should == "Sport"
42
+ end
43
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --reverse
3
+ --backtrace
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ gem 'rails', '~>2.3'
4
+ require 'i18n'
5
+ require 'active_record'
6
+ require 'spec'
7
+ require 'faker'
8
+
9
+ require 'ruby-debug'
10
+
11
+ require File.dirname(__FILE__)+'/../init.rb'
12
+ ActiveRecord::Base.logger = Logger.new(File.open("#{File.dirname(__FILE__)}/database.log", 'w+'))
13
+ ActiveRecord::Base.establish_connection({ :database => ":memory:", :adapter => 'sqlite3', :timeout => 500 })
14
+
15
+ # setup I18n
16
+ I18n.available_locales = [:en,:lv,:ru,:fr]
17
+ I18n.default_locale = :en
18
+ I18n.locale = :en
19
+
20
+ # Add models
21
+ ActiveRecord::Schema.define do
22
+ create_table :news, :force => true do |t|
23
+ t.string :title
24
+ t.string :slug
25
+ t.text :body
26
+ t.integer :category_id
27
+ t.integer :trx_id
28
+ end
29
+ create_table :categories, :force => true do |t|
30
+ t.string :name
31
+ t.string :desc
32
+ t.integer :trx_id
33
+ end
34
+ create_table :groups, :force => true do |t|
35
+ t.string :name
36
+ end
37
+ create_table :categories_groups, :force => true, :id => false do |t|
38
+ t.integer :category_id
39
+ t.integer :group_id
40
+ end
41
+ create_table :meta_datas, :force => true do |t|
42
+ t.string :title
43
+ t.string :url
44
+ t.string :keywords
45
+ t.text :description
46
+ t.string :metaable_type
47
+ t.integer :metaable_id
48
+ end
49
+ end
50
+
51
+ class News < ActiveRecord::Base
52
+ belongs_to :category, :dependent => :destroy
53
+ has_one :meta_data, :as => :metaable, :dependent => :destroy
54
+ translations :title, :body
55
+ end
56
+
57
+ class Category < ActiveRecord::Base
58
+ has_many :news
59
+ has_and_belongs_to_many :groups
60
+ translations :name
61
+ end
62
+
63
+ class Group < ActiveRecord::Base
64
+ has_and_belongs_to_many :categories
65
+ translations :name
66
+ end
67
+
68
+ class MetaData < ActiveRecord::Base
69
+ belongs_to :metaable, :polymorphic => true
70
+ translations :title, :url, :keywords, :description
71
+ end
72
+
73
+ # build translation tables
74
+
75
+ News.sync_translation_table!
76
+ Category.sync_translation_table!
77
+ Group.sync_translation_table!
78
+ MetaData.sync_translation_table!
79
+
80
+ # this is included in Lolita by default
81
+ class ::Hash
82
+ # converts all keys to symbols, but RECURSIVE
83
+ def symbolize_keys!
84
+ each do |k,v|
85
+ sym = k.respond_to?(:to_sym) ? k.to_sym : k
86
+ self[sym] = Hash === v ? v.symbolize_keys! : v
87
+ delete(k) unless k == sym
88
+ end
89
+ self
90
+ end
91
+ end
92
+ Spec::Runner.configure do |config|
93
+ config.before(:each) do
94
+ News.delete_all
95
+ Category.delete_all
96
+ MetaData.delete_all
97
+ end
98
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lolita-translation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - ITHouse
9
+ - Gatis Tomsons
10
+ - Arturs Meisters
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2011-08-01 00:00:00.000000000 +03:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: rails
19
+ requirement: &74136740 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ! '>='
23
+ - !ruby/object:Gem::Version
24
+ version: 3.0.0
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: *74136740
28
+ - !ruby/object:Gem::Dependency
29
+ name: shoulda
30
+ requirement: &74136150 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ! '>='
34
+ - !ruby/object:Gem::Version
35
+ version: '0'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: *74136150
39
+ - !ruby/object:Gem::Dependency
40
+ name: bundler
41
+ requirement: &74135560 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 1.0.0
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: *74135560
50
+ - !ruby/object:Gem::Dependency
51
+ name: jeweler
52
+ requirement: &74118460 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: 1.5.2
58
+ type: :development
59
+ prerelease: false
60
+ version_requirements: *74118460
61
+ - !ruby/object:Gem::Dependency
62
+ name: rcov
63
+ requirement: &74117810 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: *74117810
72
+ description: Translates models in Lolita
73
+ email: support@ithouse.lv
74
+ executables: []
75
+ extensions: []
76
+ extra_rdoc_files:
77
+ - LICENSE.txt
78
+ - README.md
79
+ files:
80
+ - .document
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - VERSION
86
+ - app/views/components/lolita/configuration/tab/translation/_display.html.erb
87
+ - app/views/lolita/has_translations/_tab.html.erb
88
+ - config/locales/en.yml
89
+ - lib/generators/lolita_translation/USAGE
90
+ - lib/generators/lolita_translation/has_translations_generator.rb
91
+ - lib/lolita-translation.rb
92
+ - lib/lolita-translation/configuration/tab/translation.rb
93
+ - lib/lolita-translation/has_translations.rb
94
+ - lib/lolita-translation/rails.rb
95
+ - lib/lolita-translation/string.rb
96
+ - lib/tasks/has_translations_tasks.rake
97
+ - lolita-translation.gemspec
98
+ - lolita-translations.gemspec
99
+ - spec/has_translations_spec.rb
100
+ - spec/spec.opts
101
+ - spec/spec_helper.rb
102
+ has_rdoc: true
103
+ homepage: http://github.com/ithouse/lolita-translations
104
+ licenses:
105
+ - MIT
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ! '>='
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ segments:
117
+ - 0
118
+ hash: 283995089
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 1.6.2
128
+ signing_key:
129
+ specification_version: 3
130
+ summary: Lolita models translation plugin
131
+ test_files:
132
+ - spec/has_translations_spec.rb
133
+ - spec/spec_helper.rb