├── Gemfile
├── lib
├── has_translations
│ ├── version.rb
│ ├── railtie.rb
│ └── model_additions.rb
├── generators
│ ├── templates
│ │ ├── model.rb.erb
│ │ └── migration.rb.erb
│ └── translation_for_generator.rb
└── has_translations.rb
├── .gitignore
├── gemfiles
├── 3.2.gemfile
├── 4.0.gemfile
├── 4.1.gemfile
├── 4.2.gemfile
├── 5.0.gemfile
├── 4.0.gemfile.lock
├── 4.1.gemfile.lock
├── 3.2.gemfile.lock
├── 4.2.gemfile.lock
└── 5.0.gemfile.lock
├── Appraisals
├── Rakefile
├── .travis.yml
├── test
├── test_helper.rb
├── schema.rb
└── has_translations_test.rb
├── Gemfile.lock
├── MIT-LICENSE
├── has_translations.gemspec
└── README.md
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
--------------------------------------------------------------------------------
/lib/has_translations/version.rb:
--------------------------------------------------------------------------------
1 | module HasTranslations
2 | VERSION = '1.1.2'
3 | end
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | .bundle
3 | pkg/*
4 | .idea
5 | test.log
6 | test/has_translations_plugin.sqlite3.db
7 | test/debug.log
8 | Gemfile.lock
9 |
--------------------------------------------------------------------------------
/gemfiles/3.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "~> 3.2.22"
6 |
7 | gemspec :path => "../"
8 |
--------------------------------------------------------------------------------
/gemfiles/4.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "4.0.13"
6 |
7 | gemspec :path => "../"
8 |
--------------------------------------------------------------------------------
/gemfiles/4.1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "4.1.13"
6 |
7 | gemspec :path => "../"
8 |
--------------------------------------------------------------------------------
/gemfiles/4.2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "4.2.4"
6 |
7 | gemspec :path => "../"
8 |
--------------------------------------------------------------------------------
/gemfiles/5.0.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "rails", "5.0.0"
6 |
7 | gemspec :path => "../"
8 |
--------------------------------------------------------------------------------
/lib/generators/templates/model.rb.erb:
--------------------------------------------------------------------------------
1 | class <%= name.camelcase %> < ActiveRecord::Base
2 |
3 | attr_accessor <%= attributes.map { |attribute| ":#{attribute.name}" }.join(', ') %>
4 |
5 | end
6 |
--------------------------------------------------------------------------------
/lib/has_translations/railtie.rb:
--------------------------------------------------------------------------------
1 | module HasTranslations
2 | class Railtie < Rails::Railtie
3 | initializer 'has_translations.model_additions' do
4 | ActiveSupport.on_load :active_record do
5 | include ModelAdditions
6 | end
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise '3.2' do
2 | gem 'rails', '~> 3.2.22'
3 | end
4 |
5 | appraise '4.0' do
6 | gem 'rails', '4.0.13'
7 | end
8 |
9 | appraise '4.1' do
10 | gem 'rails', '4.1.13'
11 | end
12 |
13 | appraise '4.2' do
14 | gem 'rails', '4.2.4'
15 | end
16 |
17 |
18 | appraise '5.0' do
19 | gem 'rails', '5.0.0'
20 | end
21 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require 'rake/testtask'
3 | require 'appraisal'
4 |
5 | Rake::TestTask.new do |t|
6 | t.libs = %w(lib test)
7 | t.test_files = Dir.glob('test/**/*_test.rb').sort
8 | t.verbose = true
9 | # t.warning = false
10 | end
11 |
12 | task :default => :test
13 |
14 | desc 'Setup Appraisal.'
15 | task 'appraisal:setup' do
16 | Rake::Task['appraisal:cleanup'].invoke
17 | Rake::Task['appraisal:gemfiles'].invoke
18 | Rake::Task['appraisal:install'].invoke
19 | end
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 2.0.0
3 | - 2.1.10
4 | - 2.2.5
5 | - 2.3.1
6 | before_install:
7 | - "gem install bundler"
8 | before_script:
9 | - "bundle install"
10 | script: "bundle exec rake test"
11 | gemfile:
12 | - gemfiles/3.2.gemfile
13 | - gemfiles/4.0.gemfile
14 | - gemfiles/4.1.gemfile
15 | - gemfiles/4.2.gemfile
16 | - gemfiles/5.0.gemfile
17 | cache: bundler
18 | matrix:
19 | exclude:
20 | - rvm: 2.0.0
21 | gemfile: gemfiles/5.0.gemfile
22 | - rvm: 2.1.10
23 | gemfile: gemfiles/5.0.gemfile
24 |
--------------------------------------------------------------------------------
/lib/generators/templates/migration.rb.erb:
--------------------------------------------------------------------------------
1 | class Create<%= table_name.camelcase %> < ActiveRecord::Migration
2 |
3 | def <%= old_active_record? ? "self.up" : "change" %>
4 | create_table :<%= table_name %> do |t|
5 | t.integer :<%= foreign_key_name %>, :null => false
6 | t.string :locale, :null => false, :limit => 2
7 | <%- attributes.each do |attribute| -%>
8 | t.<%= attribute.type %> :<%= attribute.name %>, :null => false
9 | <%- end -%>
10 | end
11 |
12 | add_index :<%= table_name %>, [:<%= foreign_key_name %>, :locale], :unique => true
13 | end
14 | <% if old_active_record? %>
15 | def self.down
16 | drop_table :<%= table_name %>
17 | end
18 | <% end %>
19 | end
20 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'test/unit'
3 |
4 | require 'active_record'
5 | require 'logger'
6 |
7 | require 'has_translations'
8 |
9 | begin
10 | I18n.available_locales = :ru, :en, :es
11 | rescue
12 | p '[WARNING]: This test should have the I18n.available_locales= method, which were included in versions ~> 0.3.0'
13 | end
14 |
15 | puts "Using Rails version: #{ActiveRecord::VERSION::STRING}"
16 |
17 | ActiveRecord::Base.logger = Logger.new('test.log')
18 | #ActiveRecord::Base.logger = nil
19 | ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
20 |
21 | def setup_db
22 | ActiveRecord::Migration.verbose = false
23 | load 'schema.rb'
24 | end
25 |
26 | def teardown_db
27 | ActiveRecord::Base.connection.public_send(ActiveRecord::VERSION::MAJOR >= 5 ? :data_sources : :tables).each do |table|
28 | ActiveRecord::Base.connection.drop_table(table)
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: .
3 | specs:
4 | has_translations (1.1.2)
5 | activerecord (>= 3.2.0)
6 | activesupport (>= 3.2.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | activemodel (5.0.0)
12 | activesupport (= 5.0.0)
13 | activerecord (5.0.0)
14 | activemodel (= 5.0.0)
15 | activesupport (= 5.0.0)
16 | arel (~> 7.0)
17 | activesupport (5.0.0)
18 | concurrent-ruby (~> 1.0, >= 1.0.2)
19 | i18n (~> 0.7)
20 | minitest (~> 5.1)
21 | tzinfo (~> 1.1)
22 | appraisal (2.1.0)
23 | bundler
24 | rake
25 | thor (>= 0.14.0)
26 | arel (7.0.0)
27 | concurrent-ruby (1.0.2)
28 | i18n (0.7.0)
29 | minitest (5.9.0)
30 | power_assert (0.2.5)
31 | rake (10.4.2)
32 | sqlite3 (1.3.11)
33 | test-unit (3.1.5)
34 | power_assert
35 | thor (0.19.1)
36 | thread_safe (0.3.5)
37 | tzinfo (1.2.2)
38 | thread_safe (~> 0.1)
39 |
40 | PLATFORMS
41 | ruby
42 |
43 | DEPENDENCIES
44 | appraisal
45 | has_translations!
46 | rake
47 | sqlite3
48 | test-unit
49 |
50 | BUNDLED WITH
51 | 1.12.5
52 |
--------------------------------------------------------------------------------
/MIT-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 [name of plugin creator]
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.
21 |
--------------------------------------------------------------------------------
/has_translations.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path('../lib', __FILE__)
3 | require 'has_translations/version'
4 |
5 | Gem::Specification.new do |s|
6 | s.name = 'has_translations'
7 | s.version = HasTranslations::VERSION
8 | s.authors = ['Dmitry Polushkin']
9 | s.email = %w(dmitry.polushkin@gmail.com)
10 | s.homepage = 'http://github.com/dmitry/has_translations'
11 | s.summary = %q{Create translations for your ActiveRecord models.}
12 | s.description = %q{Create translations for your ActiveRecord models. Uses delegate pattern. Fully tested and used in a several production sites.}
13 |
14 | s.rubyforge_project = 'has_translations'
15 |
16 | s.files = `git ls-files`.split("\n")
17 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19 | s.require_paths = %w(lib)
20 |
21 | s.license = 'MIT'
22 |
23 | s.add_dependency 'activesupport', '>= 3.2.0'
24 | s.add_dependency 'activerecord', '>= 3.2.0'
25 | s.add_development_dependency 'sqlite3'
26 | s.add_development_dependency 'appraisal'
27 | s.add_development_dependency 'rake'
28 | s.add_development_dependency 'test-unit'
29 | end
30 |
--------------------------------------------------------------------------------
/lib/generators/translation_for_generator.rb:
--------------------------------------------------------------------------------
1 | require 'rails/generators/active_record'
2 |
3 | class TranslationForGenerator < ActiveRecord::Generators::Base
4 |
5 | desc "Description:\n Generate translation for ActiveRecord model"
6 | source_root File.expand_path("../templates", __FILE__)
7 |
8 | argument :attributes, :type => :array, :default => [], :banner => "field_1:text field_2:string field_3:another_type"
9 |
10 | def check_attributes
11 | puts table_name
12 | if attributes.blank?
13 | puts "Define at least one translated attribute"
14 | exit
15 | end
16 | end
17 |
18 | def create_model
19 | template 'model.rb.erb', File.join('app/models', class_path, "#{file_name}.rb")
20 | end
21 |
22 | def create_translations_migration
23 | migration_template 'migration.rb.erb', "db/migrate/create_#{table_name}"
24 | end
25 |
26 | private
27 |
28 | def name_with_translation
29 | "#{name_without_translation.underscore}_translation"
30 | end
31 |
32 | alias_method_chain :name, :translation
33 |
34 | def old_active_record?
35 | (ActiveRecord::VERSION::MAJOR < 3) || (ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0)
36 | end
37 |
38 | def foreign_key_name
39 | "#{name_without_translation.underscore}_id"
40 | end
41 |
42 | end
43 |
--------------------------------------------------------------------------------
/test/schema.rb:
--------------------------------------------------------------------------------
1 | ActiveRecord::Schema.define(:version => 0) do
2 | create_table :articles, :force => true do |t|
3 | t.string :title
4 | end
5 |
6 | create_table :article_translations, :force => true do |t|
7 | t.references :article, :null => false
8 | t.string :locale, :null => false, :limit => 2
9 | t.string :description
10 | t.text :text
11 | end
12 |
13 | create_table :teams, :force => true do |t|
14 | t.string :title
15 | end
16 |
17 | create_table :team_translations, :force => true do |t|
18 | t.references :team, :null => false
19 | t.string :locale, :null => false, :limit => 2
20 | t.text :text
21 | end
22 |
23 | create_table :organizations, :force => true do |t|
24 | t.string :title
25 | end
26 |
27 | create_table :organization_translations, :force => true do |t|
28 | t.integer :company_id, :null => false
29 | t.string :locale, :null => false, :limit => 2
30 | t.text :text
31 | end
32 | end
33 |
34 | class ArticleTranslation < ActiveRecord::Base
35 | #attr_accessible :description, :text
36 | end
37 | class Article < ActiveRecord::Base
38 | include HasTranslations::ModelAdditions
39 |
40 | has_translations :description, :text, :writer => true
41 | end
42 |
43 | class TeamTranslation < ActiveRecord::Base
44 | end
45 | class Team < ActiveRecord::Base
46 | include HasTranslations::ModelAdditions
47 |
48 | has_translations :text, :fallback => true, :nil => nil
49 | end
50 |
51 | class OrganizationTranslation < ActiveRecord::Base
52 | end
53 | class Organization < ActiveRecord::Base
54 | include HasTranslations::ModelAdditions
55 |
56 | has_translations :text, :writer => true, :foreign_key => 'company_id'
57 | end
58 |
--------------------------------------------------------------------------------
/gemfiles/4.0.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../
3 | specs:
4 | has_translations (1.1.0)
5 | activerecord (>= 3.2.0)
6 | activesupport (>= 3.2.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actionmailer (4.0.13)
12 | actionpack (= 4.0.13)
13 | mail (~> 2.5, >= 2.5.4)
14 | actionpack (4.0.13)
15 | activesupport (= 4.0.13)
16 | builder (~> 3.1.0)
17 | erubis (~> 2.7.0)
18 | rack (~> 1.5.2)
19 | rack-test (~> 0.6.2)
20 | activemodel (4.0.13)
21 | activesupport (= 4.0.13)
22 | builder (~> 3.1.0)
23 | activerecord (4.0.13)
24 | activemodel (= 4.0.13)
25 | activerecord-deprecated_finders (~> 1.0.2)
26 | activesupport (= 4.0.13)
27 | arel (~> 4.0.0)
28 | activerecord-deprecated_finders (1.0.4)
29 | activesupport (4.0.13)
30 | i18n (~> 0.6, >= 0.6.9)
31 | minitest (~> 4.2)
32 | multi_json (~> 1.3)
33 | thread_safe (~> 0.1)
34 | tzinfo (~> 0.3.37)
35 | appraisal (2.1.0)
36 | bundler
37 | rake
38 | thor (>= 0.14.0)
39 | arel (4.0.2)
40 | builder (3.1.4)
41 | concurrent-ruby (1.0.2)
42 | erubis (2.7.0)
43 | i18n (0.7.0)
44 | mail (2.6.4)
45 | mime-types (>= 1.16, < 4)
46 | mime-types (3.1)
47 | mime-types-data (~> 3.2015)
48 | mime-types-data (3.2016.0521)
49 | minitest (4.7.5)
50 | multi_json (1.12.1)
51 | power_assert (0.3.0)
52 | rack (1.5.5)
53 | rack-test (0.6.3)
54 | rack (>= 1.0)
55 | rails (4.0.13)
56 | actionmailer (= 4.0.13)
57 | actionpack (= 4.0.13)
58 | activerecord (= 4.0.13)
59 | activesupport (= 4.0.13)
60 | bundler (>= 1.3.0, < 2.0)
61 | railties (= 4.0.13)
62 | sprockets-rails (~> 2.0)
63 | railties (4.0.13)
64 | actionpack (= 4.0.13)
65 | activesupport (= 4.0.13)
66 | rake (>= 0.8.7)
67 | thor (>= 0.18.1, < 2.0)
68 | rake (11.2.2)
69 | sprockets (3.6.3)
70 | concurrent-ruby (~> 1.0)
71 | rack (> 1, < 3)
72 | sprockets-rails (2.3.3)
73 | actionpack (>= 3.0)
74 | activesupport (>= 3.0)
75 | sprockets (>= 2.8, < 4.0)
76 | sqlite3 (1.3.11)
77 | test-unit (3.2.0)
78 | power_assert
79 | thor (0.19.1)
80 | thread_safe (0.3.5)
81 | tzinfo (0.3.51)
82 |
83 | PLATFORMS
84 | ruby
85 |
86 | DEPENDENCIES
87 | appraisal
88 | has_translations!
89 | rails (= 4.0.13)
90 | rake
91 | sqlite3
92 | test-unit
93 |
94 | BUNDLED WITH
95 | 1.12.4
96 |
--------------------------------------------------------------------------------
/gemfiles/4.1.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../
3 | specs:
4 | has_translations (1.1.0)
5 | activerecord (>= 3.2.0)
6 | activesupport (>= 3.2.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actionmailer (4.1.13)
12 | actionpack (= 4.1.13)
13 | actionview (= 4.1.13)
14 | mail (~> 2.5, >= 2.5.4)
15 | actionpack (4.1.13)
16 | actionview (= 4.1.13)
17 | activesupport (= 4.1.13)
18 | rack (~> 1.5.2)
19 | rack-test (~> 0.6.2)
20 | actionview (4.1.13)
21 | activesupport (= 4.1.13)
22 | builder (~> 3.1)
23 | erubis (~> 2.7.0)
24 | activemodel (4.1.13)
25 | activesupport (= 4.1.13)
26 | builder (~> 3.1)
27 | activerecord (4.1.13)
28 | activemodel (= 4.1.13)
29 | activesupport (= 4.1.13)
30 | arel (~> 5.0.0)
31 | activesupport (4.1.13)
32 | i18n (~> 0.6, >= 0.6.9)
33 | json (~> 1.7, >= 1.7.7)
34 | minitest (~> 5.1)
35 | thread_safe (~> 0.1)
36 | tzinfo (~> 1.1)
37 | appraisal (2.1.0)
38 | bundler
39 | rake
40 | thor (>= 0.14.0)
41 | arel (5.0.1.20140414130214)
42 | builder (3.2.2)
43 | concurrent-ruby (1.0.2)
44 | erubis (2.7.0)
45 | i18n (0.7.0)
46 | json (1.8.3)
47 | mail (2.6.4)
48 | mime-types (>= 1.16, < 4)
49 | mime-types (3.1)
50 | mime-types-data (~> 3.2015)
51 | mime-types-data (3.2016.0521)
52 | minitest (5.9.0)
53 | power_assert (0.3.0)
54 | rack (1.5.5)
55 | rack-test (0.6.3)
56 | rack (>= 1.0)
57 | rails (4.1.13)
58 | actionmailer (= 4.1.13)
59 | actionpack (= 4.1.13)
60 | actionview (= 4.1.13)
61 | activemodel (= 4.1.13)
62 | activerecord (= 4.1.13)
63 | activesupport (= 4.1.13)
64 | bundler (>= 1.3.0, < 2.0)
65 | railties (= 4.1.13)
66 | sprockets-rails (~> 2.0)
67 | railties (4.1.13)
68 | actionpack (= 4.1.13)
69 | activesupport (= 4.1.13)
70 | rake (>= 0.8.7)
71 | thor (>= 0.18.1, < 2.0)
72 | rake (11.2.2)
73 | sprockets (3.6.3)
74 | concurrent-ruby (~> 1.0)
75 | rack (> 1, < 3)
76 | sprockets-rails (2.3.3)
77 | actionpack (>= 3.0)
78 | activesupport (>= 3.0)
79 | sprockets (>= 2.8, < 4.0)
80 | sqlite3 (1.3.11)
81 | test-unit (3.2.0)
82 | power_assert
83 | thor (0.19.1)
84 | thread_safe (0.3.5)
85 | tzinfo (1.2.2)
86 | thread_safe (~> 0.1)
87 |
88 | PLATFORMS
89 | ruby
90 |
91 | DEPENDENCIES
92 | appraisal
93 | has_translations!
94 | rails (= 4.1.13)
95 | rake
96 | sqlite3
97 | test-unit
98 |
99 | BUNDLED WITH
100 | 1.12.4
101 |
--------------------------------------------------------------------------------
/gemfiles/3.2.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../
3 | specs:
4 | has_translations (1.1.0)
5 | activerecord (>= 3.2.0)
6 | activesupport (>= 3.2.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actionmailer (3.2.22.2)
12 | actionpack (= 3.2.22.2)
13 | mail (~> 2.5.4)
14 | actionpack (3.2.22.2)
15 | activemodel (= 3.2.22.2)
16 | activesupport (= 3.2.22.2)
17 | builder (~> 3.0.0)
18 | erubis (~> 2.7.0)
19 | journey (~> 1.0.4)
20 | rack (~> 1.4.5)
21 | rack-cache (~> 1.2)
22 | rack-test (~> 0.6.1)
23 | sprockets (~> 2.2.1)
24 | activemodel (3.2.22.2)
25 | activesupport (= 3.2.22.2)
26 | builder (~> 3.0.0)
27 | activerecord (3.2.22.2)
28 | activemodel (= 3.2.22.2)
29 | activesupport (= 3.2.22.2)
30 | arel (~> 3.0.2)
31 | tzinfo (~> 0.3.29)
32 | activeresource (3.2.22.2)
33 | activemodel (= 3.2.22.2)
34 | activesupport (= 3.2.22.2)
35 | activesupport (3.2.22.2)
36 | i18n (~> 0.6, >= 0.6.4)
37 | multi_json (~> 1.0)
38 | appraisal (2.1.0)
39 | bundler
40 | rake
41 | thor (>= 0.14.0)
42 | arel (3.0.3)
43 | builder (3.0.4)
44 | erubis (2.7.0)
45 | hike (1.2.3)
46 | i18n (0.7.0)
47 | journey (1.0.4)
48 | json (1.8.3)
49 | mail (2.5.4)
50 | mime-types (~> 1.16)
51 | treetop (~> 1.4.8)
52 | mime-types (1.25.1)
53 | multi_json (1.12.1)
54 | polyglot (0.3.5)
55 | power_assert (0.3.0)
56 | rack (1.4.7)
57 | rack-cache (1.6.1)
58 | rack (>= 0.4)
59 | rack-ssl (1.3.4)
60 | rack
61 | rack-test (0.6.3)
62 | rack (>= 1.0)
63 | rails (3.2.22.2)
64 | actionmailer (= 3.2.22.2)
65 | actionpack (= 3.2.22.2)
66 | activerecord (= 3.2.22.2)
67 | activeresource (= 3.2.22.2)
68 | activesupport (= 3.2.22.2)
69 | bundler (~> 1.0)
70 | railties (= 3.2.22.2)
71 | railties (3.2.22.2)
72 | actionpack (= 3.2.22.2)
73 | activesupport (= 3.2.22.2)
74 | rack-ssl (~> 1.3.2)
75 | rake (>= 0.8.7)
76 | rdoc (~> 3.4)
77 | thor (>= 0.14.6, < 2.0)
78 | rake (11.2.2)
79 | rdoc (3.12.2)
80 | json (~> 1.4)
81 | sprockets (2.2.3)
82 | hike (~> 1.2)
83 | multi_json (~> 1.0)
84 | rack (~> 1.0)
85 | tilt (~> 1.1, != 1.3.0)
86 | sqlite3 (1.3.11)
87 | test-unit (3.2.0)
88 | power_assert
89 | thor (0.19.1)
90 | tilt (1.4.1)
91 | treetop (1.4.15)
92 | polyglot
93 | polyglot (>= 0.3.1)
94 | tzinfo (0.3.51)
95 |
96 | PLATFORMS
97 | ruby
98 |
99 | DEPENDENCIES
100 | appraisal
101 | has_translations!
102 | rails (~> 3.2.22)
103 | rake
104 | sqlite3
105 | test-unit
106 |
107 | BUNDLED WITH
108 | 1.12.4
109 |
--------------------------------------------------------------------------------
/lib/has_translations.rb:
--------------------------------------------------------------------------------
1 | # Provides ability to add the translations for the model using delegate pattern.
2 | # Uses has_many association to the ModelNameTranslation.
3 | #
4 | # For example you have model Article with attributes title and text.
5 | # You want that attributes title and text to be translated.
6 | # For this reason you need to generate new model ArticleTranslation.
7 | # In migration you need to add:
8 | #
9 | # create_table :article_translations do |t|
10 | # t.references :article, :null => false
11 | # t.string :locale, :length => 2, :null => false
12 | # t.string :name, :null => false
13 | # end
14 | #
15 | # add_index :articles, [:article_id, :locale], :unique => true, :name => 'unique_locale_for_article_id'
16 | #
17 | # And in the Article model:
18 | #
19 | # translations :title, :text
20 | #
21 | # This will adds:
22 | #
23 | # * named_scope (translated) and has_many association to the Article model
24 | # * locale presence validation to the ArticleTranslation model.
25 | #
26 | # Notice: if you want to have validates_presence_of :article, you should use :inverse_of.
27 | # Support this by yourself. Better is always to use artile.translations.build() method.
28 | #
29 | # ===
30 | #
31 | # You also can pass attributes and options to the translations class method:
32 | #
33 | # translations :title, :text, :fallback => true, :writer => true, :nil => nil
34 | #
35 | # ===
36 | #
37 | # Configuration options:
38 | #
39 | # * :fallback - if translation for the current locale not found.
40 | # By default false. Set to true if you want to use reader fallback.
41 | # Uses algorithm of fallback:
42 | # 0) current translation (using I18n.locale);
43 | # 1) default locale (using I18n.default_locale);
44 | # 2) first from translations association;
45 | # 3) :nil value (see :nil configuration option)
46 | # * :reader - add reader attributes to the model and delegate them
47 | # to the translation model columns. Add's fallback if it is set to true.
48 | # * :writer - add writer attributes to the model and assign them
49 | # to the translation model attributes.
50 | # * :nil - when reader cant find string, it returns by default an
51 | # empty string. If you want to change this setting for example to nil,
52 | # add :nil => nil
53 | #
54 | # ===
55 | #
56 | # When you are using :writer option, you can create translations using
57 | # update_attributes method. For example:
58 | #
59 | # Article.create!
60 | # Article.update_attributes(:title => 'title', :text => 'text')
61 | #
62 | # ===
63 | #
64 | # translated named_scope is useful when you want to find only those
65 | # records that are translated to a specific locale.
66 | # For example if you want to find all Articles that is translated to an english
67 | # language, you can write: Article.translated(:en)
68 | #
69 | # has_translation?(locale) method, that returns true if object's model
70 | # have a translation for a specified locale
71 | #
72 | # translation(locale) method finds translation with specified locale.
73 | #
74 | # all_translations method that returns all possible translations in
75 | # ordered hash (useful when creating forms with nested attributes).
76 |
77 | require "has_translations/model_additions"
78 | require "has_translations/railtie" if defined? Rails
79 |
80 | module HasTranslations
81 |
82 | end
83 |
--------------------------------------------------------------------------------
/gemfiles/4.2.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../
3 | specs:
4 | has_translations (1.1.0)
5 | activerecord (>= 3.2.0)
6 | activesupport (>= 3.2.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actionmailer (4.2.4)
12 | actionpack (= 4.2.4)
13 | actionview (= 4.2.4)
14 | activejob (= 4.2.4)
15 | mail (~> 2.5, >= 2.5.4)
16 | rails-dom-testing (~> 1.0, >= 1.0.5)
17 | actionpack (4.2.4)
18 | actionview (= 4.2.4)
19 | activesupport (= 4.2.4)
20 | rack (~> 1.6)
21 | rack-test (~> 0.6.2)
22 | rails-dom-testing (~> 1.0, >= 1.0.5)
23 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
24 | actionview (4.2.4)
25 | activesupport (= 4.2.4)
26 | builder (~> 3.1)
27 | erubis (~> 2.7.0)
28 | rails-dom-testing (~> 1.0, >= 1.0.5)
29 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
30 | activejob (4.2.4)
31 | activesupport (= 4.2.4)
32 | globalid (>= 0.3.0)
33 | activemodel (4.2.4)
34 | activesupport (= 4.2.4)
35 | builder (~> 3.1)
36 | activerecord (4.2.4)
37 | activemodel (= 4.2.4)
38 | activesupport (= 4.2.4)
39 | arel (~> 6.0)
40 | activesupport (4.2.4)
41 | i18n (~> 0.7)
42 | json (~> 1.7, >= 1.7.7)
43 | minitest (~> 5.1)
44 | thread_safe (~> 0.3, >= 0.3.4)
45 | tzinfo (~> 1.1)
46 | appraisal (2.1.0)
47 | bundler
48 | rake
49 | thor (>= 0.14.0)
50 | arel (6.0.3)
51 | builder (3.2.2)
52 | concurrent-ruby (1.0.2)
53 | erubis (2.7.0)
54 | globalid (0.3.6)
55 | activesupport (>= 4.1.0)
56 | i18n (0.7.0)
57 | json (1.8.3)
58 | loofah (2.0.3)
59 | nokogiri (>= 1.5.9)
60 | mail (2.6.4)
61 | mime-types (>= 1.16, < 4)
62 | mime-types (3.1)
63 | mime-types-data (~> 3.2015)
64 | mime-types-data (3.2016.0521)
65 | mini_portile2 (2.1.0)
66 | minitest (5.9.0)
67 | nokogiri (1.6.8)
68 | mini_portile2 (~> 2.1.0)
69 | pkg-config (~> 1.1.7)
70 | pkg-config (1.1.7)
71 | power_assert (0.3.0)
72 | rack (1.6.4)
73 | rack-test (0.6.3)
74 | rack (>= 1.0)
75 | rails (4.2.4)
76 | actionmailer (= 4.2.4)
77 | actionpack (= 4.2.4)
78 | actionview (= 4.2.4)
79 | activejob (= 4.2.4)
80 | activemodel (= 4.2.4)
81 | activerecord (= 4.2.4)
82 | activesupport (= 4.2.4)
83 | bundler (>= 1.3.0, < 2.0)
84 | railties (= 4.2.4)
85 | sprockets-rails
86 | rails-deprecated_sanitizer (1.0.3)
87 | activesupport (>= 4.2.0.alpha)
88 | rails-dom-testing (1.0.7)
89 | activesupport (>= 4.2.0.beta, < 5.0)
90 | nokogiri (~> 1.6.0)
91 | rails-deprecated_sanitizer (>= 1.0.1)
92 | rails-html-sanitizer (1.0.3)
93 | loofah (~> 2.0)
94 | railties (4.2.4)
95 | actionpack (= 4.2.4)
96 | activesupport (= 4.2.4)
97 | rake (>= 0.8.7)
98 | thor (>= 0.18.1, < 2.0)
99 | rake (11.2.2)
100 | sprockets (3.6.3)
101 | concurrent-ruby (~> 1.0)
102 | rack (> 1, < 3)
103 | sprockets-rails (3.1.1)
104 | actionpack (>= 4.0)
105 | activesupport (>= 4.0)
106 | sprockets (>= 3.0.0)
107 | sqlite3 (1.3.11)
108 | test-unit (3.2.0)
109 | power_assert
110 | thor (0.19.1)
111 | thread_safe (0.3.5)
112 | tzinfo (1.2.2)
113 | thread_safe (~> 0.1)
114 |
115 | PLATFORMS
116 | ruby
117 |
118 | DEPENDENCIES
119 | appraisal
120 | has_translations!
121 | rails (= 4.2.4)
122 | rake
123 | sqlite3
124 | test-unit
125 |
126 | BUNDLED WITH
127 | 1.12.4
128 |
--------------------------------------------------------------------------------
/gemfiles/5.0.gemfile.lock:
--------------------------------------------------------------------------------
1 | PATH
2 | remote: ../
3 | specs:
4 | has_translations (1.1.0)
5 | activerecord (>= 3.2.0)
6 | activesupport (>= 3.2.0)
7 |
8 | GEM
9 | remote: https://rubygems.org/
10 | specs:
11 | actioncable (5.0.0)
12 | actionpack (= 5.0.0)
13 | nio4r (~> 1.2)
14 | websocket-driver (~> 0.6.1)
15 | actionmailer (5.0.0)
16 | actionpack (= 5.0.0)
17 | actionview (= 5.0.0)
18 | activejob (= 5.0.0)
19 | mail (~> 2.5, >= 2.5.4)
20 | rails-dom-testing (~> 2.0)
21 | actionpack (5.0.0)
22 | actionview (= 5.0.0)
23 | activesupport (= 5.0.0)
24 | rack (~> 2.0)
25 | rack-test (~> 0.6.3)
26 | rails-dom-testing (~> 2.0)
27 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
28 | actionview (5.0.0)
29 | activesupport (= 5.0.0)
30 | builder (~> 3.1)
31 | erubis (~> 2.7.0)
32 | rails-dom-testing (~> 2.0)
33 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
34 | activejob (5.0.0)
35 | activesupport (= 5.0.0)
36 | globalid (>= 0.3.6)
37 | activemodel (5.0.0)
38 | activesupport (= 5.0.0)
39 | activerecord (5.0.0)
40 | activemodel (= 5.0.0)
41 | activesupport (= 5.0.0)
42 | arel (~> 7.0)
43 | activesupport (5.0.0)
44 | concurrent-ruby (~> 1.0, >= 1.0.2)
45 | i18n (~> 0.7)
46 | minitest (~> 5.1)
47 | tzinfo (~> 1.1)
48 | appraisal (2.1.0)
49 | bundler
50 | rake
51 | thor (>= 0.14.0)
52 | arel (7.0.0)
53 | builder (3.2.2)
54 | concurrent-ruby (1.0.2)
55 | erubis (2.7.0)
56 | globalid (0.3.6)
57 | activesupport (>= 4.1.0)
58 | i18n (0.7.0)
59 | loofah (2.0.3)
60 | nokogiri (>= 1.5.9)
61 | mail (2.6.4)
62 | mime-types (>= 1.16, < 4)
63 | method_source (0.8.2)
64 | mime-types (3.1)
65 | mime-types-data (~> 3.2015)
66 | mime-types-data (3.2016.0521)
67 | mini_portile2 (2.1.0)
68 | minitest (5.9.0)
69 | nio4r (1.2.1)
70 | nokogiri (1.6.8)
71 | mini_portile2 (~> 2.1.0)
72 | pkg-config (~> 1.1.7)
73 | pkg-config (1.1.7)
74 | power_assert (0.3.0)
75 | rack (2.0.1)
76 | rack-test (0.6.3)
77 | rack (>= 1.0)
78 | rails (5.0.0)
79 | actioncable (= 5.0.0)
80 | actionmailer (= 5.0.0)
81 | actionpack (= 5.0.0)
82 | actionview (= 5.0.0)
83 | activejob (= 5.0.0)
84 | activemodel (= 5.0.0)
85 | activerecord (= 5.0.0)
86 | activesupport (= 5.0.0)
87 | bundler (>= 1.3.0, < 2.0)
88 | railties (= 5.0.0)
89 | sprockets-rails (>= 2.0.0)
90 | rails-dom-testing (2.0.1)
91 | activesupport (>= 4.2.0, < 6.0)
92 | nokogiri (~> 1.6.0)
93 | rails-html-sanitizer (1.0.3)
94 | loofah (~> 2.0)
95 | railties (5.0.0)
96 | actionpack (= 5.0.0)
97 | activesupport (= 5.0.0)
98 | method_source
99 | rake (>= 0.8.7)
100 | thor (>= 0.18.1, < 2.0)
101 | rake (11.2.2)
102 | sprockets (3.6.3)
103 | concurrent-ruby (~> 1.0)
104 | rack (> 1, < 3)
105 | sprockets-rails (3.1.1)
106 | actionpack (>= 4.0)
107 | activesupport (>= 4.0)
108 | sprockets (>= 3.0.0)
109 | sqlite3 (1.3.11)
110 | test-unit (3.2.0)
111 | power_assert
112 | thor (0.19.1)
113 | thread_safe (0.3.5)
114 | tzinfo (1.2.2)
115 | thread_safe (~> 0.1)
116 | websocket-driver (0.6.4)
117 | websocket-extensions (>= 0.1.0)
118 | websocket-extensions (0.1.2)
119 |
120 | PLATFORMS
121 | ruby
122 |
123 | DEPENDENCIES
124 | appraisal
125 | has_translations!
126 | rails (= 5.0.0)
127 | rake
128 | sqlite3
129 | test-unit
130 |
131 | BUNDLED WITH
132 | 1.12.4
133 |
--------------------------------------------------------------------------------
/lib/has_translations/model_additions.rb:
--------------------------------------------------------------------------------
1 | module HasTranslations
2 | module ModelAdditions
3 | extend ActiveSupport::Concern
4 |
5 | module ClassMethods
6 | def translated(locale)
7 | where("#{self.has_translations_options[:translation_class].table_name}.locale = ?", locale).joins(:translations)
8 | end
9 |
10 | def has_translations(*attrs)
11 | new_options = attrs.extract_options!
12 | options = {
13 | fallback: false,
14 | reader: true,
15 | writer: false,
16 | nil: '',
17 | autosave: new_options[:writer],
18 | translation_class: nil
19 | }.merge(new_options)
20 |
21 | translation_class_name = options[:translation_class].try(:name) || "#{self.model_name}Translation"
22 | options[:translation_class] ||= translation_class_name.constantize
23 |
24 | options.assert_valid_keys(
25 | [
26 | :fallback,
27 | :reader,
28 | :writer,
29 | :nil,
30 | :inverse_of,
31 | :autosave,
32 | :translation_class,
33 | :foreign_key
34 | ]
35 | )
36 |
37 | belongs_to = self.model_name.to_s.demodulize.underscore.to_sym
38 |
39 | class_attribute :has_translations_options
40 | self.has_translations_options = options
41 |
42 | # associations, validations and scope definitions
43 | options[:translation_class].belongs_to(belongs_to, foreign_key: options[:foreign_key])
44 | has_many(
45 | :translations,
46 | class_name: translation_class_name,
47 | dependent: :destroy,
48 | autosave: options[:autosave],
49 | inverse_of: options[:inverse_of],
50 | foreign_key: options[:foreign_key]
51 | )
52 | options[:translation_class].validates(
53 | :locale,
54 | presence: true,
55 | uniqueness: {scope: (options[:foreign_key] || "#{belongs_to}_id").to_sym}
56 | )
57 |
58 | # Optionals delegated readers
59 | if options[:reader]
60 | attrs.each do |name|
61 | send :define_method, name do |*args|
62 | locale = args.first || I18n.locale
63 | translation = self.translation(locale)
64 | translation.try(name) || has_translations_options[:nil]
65 | end
66 | end
67 | end
68 |
69 | # Optionals delegated writers
70 | if options[:writer]
71 | attrs.each do |name|
72 | send :define_method, "#{name}_before_type_cast" do
73 | translation = self.translation(I18n.locale, false)
74 | translation.try(name)
75 | end
76 |
77 | send :define_method, "#{name}=" do |value|
78 | translation = find_or_build_translation(I18n.locale)
79 | translation.send(:"#{name}=", value)
80 | end
81 | end
82 | end
83 | end
84 | end
85 |
86 | def find_or_create_translation(locale)
87 | locale = locale.to_s
88 | (find_translation(locale) || self.has_translations_options[:translation_class].new).tap do |t|
89 | t.locale = locale
90 | t.send(:"#{self.class.model_name.to_s.demodulize.underscore.to_sym}_id=", self.id)
91 | end
92 | end
93 |
94 | def find_or_build_translation(locale)
95 | locale = locale.to_s
96 | (find_translation(locale) || self.translations.build).tap do |t|
97 | t.locale = locale
98 | end
99 | end
100 |
101 | def translation(locale, fallback=has_translations_options[:fallback])
102 | find_translation(locale) || (fallback && !translations.empty? ? find_translation(I18n.default_locale) || translations.first : nil)
103 | end
104 |
105 | def all_translations
106 | t = I18n.available_locales.map do |locale|
107 | [locale, find_or_create_translation(locale)]
108 | end
109 | ActiveSupport::OrderedHash[t]
110 | end
111 |
112 | def has_translation?(locale)
113 | find_translation(locale).present?
114 | end
115 |
116 | def find_translation(locale)
117 | locale = locale.to_s
118 | translations.detect { |t| t.locale == locale }
119 | end
120 | end
121 | end
122 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | HasTranslations
2 | ==============================
3 |
4 | **UNMAINTAINED**: Please use [mobility gem](https://github.com/shioyama/mobility), it has a lot of options ([including table](https://github.com/shioyama/mobility/wiki/Table-Backend)) and maintained.
5 |
6 | [](http://travis-ci.org/dmitry/has_translations)
7 | [](http://badge.fury.io/rb/has_translations)
8 |
9 | This simple plugin creates translations for your model.
10 | Uses delegation pattern: http://en.wikipedia.org/wiki/Delegation_pattern
11 |
12 | Tested with ActiveRecord versions: 3.2.x, 4.0.x, 4.1.x, 4.2.x, 5.0.x
13 | And tested with ruby 1.9.3, 2.0.0, 2.1.x, 2.2.x, 2.3.x
14 |
15 | Compatibility
16 | =============
17 |
18 | This version only support Rails 4.x.x and 3.2.x. For Rails 2.3.x support please get the 0.3.5 version of this gem and for Rails 3.1.x get the 1.0.0 of this gem.
19 | Plugin support is deprecated in Rails and will be removed soon so this version drop plugin support.
20 | To prevent method shadowing between "translations" class method and "translations" relation in models the class
21 | method has been renamed has_translations.
22 |
23 | ```ruby
24 | class Article < ActiveRecord::Base
25 | translations :title, :text
26 | end
27 | ```
28 |
29 | become
30 |
31 | ```ruby
32 | class Article < ActiveRecord::Base
33 | has_translations :title, :text
34 | end
35 | ```
36 |
37 | Installation
38 | ============
39 |
40 | ```bash
41 | gem install has_translations
42 | ```
43 |
44 | Example
45 | =======
46 |
47 | For example you have Article model and you want to have title and text to be translated.
48 |
49 | Run in command line:
50 |
51 | ```bash
52 | rails g translation_for article title:string text:text
53 | ```
54 |
55 | It will produce ArticleTranslation model and migration.
56 |
57 | Add to article model `translations :value1, :value2`:
58 |
59 | ```ruby
60 | class Article < ActiveRecord::Base
61 | has_translations :title, :text
62 | end
63 | ```
64 |
65 | And that's it. Now you can add your translations using:
66 |
67 | ```ruby
68 | article = Article.create
69 |
70 | article.translations.create(:locale => 'en', :title => 'title', :text => 'text') # or ArticleTranslation.create(:article => article, :locale => 'en', :title => 'title', :text => 'text')
71 | article.translations.create(:locale => 'ru', :title => 'заголовок', :text => 'текст')
72 | article.reload # reload cached translations association array
73 | I18n.locale = :en
74 | article.text # text
75 | I18n.locale = :ru
76 | article.title # заголовок
77 | ```
78 |
79 | You can use text filtering plugins, like acts_as_sanitiled and validations, and anything else that is available to the ActiveRecord:
80 |
81 | ```ruby
82 | class ArticleTranslation < ActiveRecord::Base
83 | acts_as_sanitiled :title, :text
84 |
85 | validates_presence_of :title, :text
86 | validates_length_of :title, :maximum => 100
87 | end
88 | ```
89 |
90 | Options:
91 |
92 | * `:fallback => true` [default: false] - fallback 1) default locale; 2) first from translations;
93 | * `:reader => false` [default: true] - add reader to the model object
94 | * `:writer => true` [default: false] - add writer to the model object
95 | * `:autosave => true` [default: false] - use [autosave option](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many) for the ActiveRecord translations relation
96 | * `:nil => nil` [default: ''] - if no model found by default returns empty string, you can set it for example to `nil` (no `lambda` supported)
97 | * `:foreign_key => nil` [default: nil] - add the foreign_key for has_many and belogns_to associations
98 |
99 | It's better to use translations with `accepts_nested_attributes_for`:
100 |
101 | ```ruby
102 | accepts_nested_attributes_for :translations
103 | ```
104 |
105 | To create a form for this you can use `all_translations` method. It's have all
106 | the locales that you have added using the `I18n.available_locales=` method.
107 | If translation for one of the locale isn't exists, it will build it with :locale.
108 | So an example which I used in the production (using `formtastic` gem):
109 |
110 | ```ruby
111 | <% semantic_form_for [:admin, @article] do |f| %>
112 | <%= f.error_messages %>
113 |
114 | <% f.inputs :name => "Basic" do %>
115 | <% object.all_translations.values.each do |translation| %>
116 | <% f.semantic_fields_for :translations, translation do |ft| %>
117 | <%= ft.input :title, :label => "Title #{ft.object.locale.to_s.upcase}" %>
118 | <%= ft.input :text, :label => "Text #{ft.object.locale.to_s.upcase}" %>
119 | <%= ft.input :locale, :as => :hidden %>
120 | <% end %>
121 | <% end %>
122 | <% end %>
123 | <% end %>
124 | ```
125 |
126 | Sometimes you have validations in the translation model, and if you want to skip
127 | the translations that you don't want to add to the database, you can use
128 | `:reject_if` option, which is available for the `accepts_nested_attributes_for`:
129 |
130 | ```ruby
131 | accepts_nested_attributes_for :translations, :reject_if => lambda { |attrs| attrs['title'].blank? && attrs['text'].blank? }
132 | ```
133 |
134 | named_scope `translated(locale)` - with that named_scope you can find only
135 | those models that is translated only to specific locale. For example if you will
136 | have 2 models, one is translated to english and the second one isn't, then it
137 | `Article.translated(:en)` will find only first one.
138 |
139 | TODO
140 | ====
141 |
142 | * caching
143 | * write more examples: fallback feature
144 | * write blog post about comparison and benefits of this plugin between another translation model plugins
145 |
146 |
147 | Alternatives
148 | ============
149 |
150 | I know three of them:
151 |
152 | * [globalize3](https://github.com/svenfuchs/globalize3) - Globalize3 is the successor of Globalize for Rails.
153 | * [puret](http://github.com/jo/puret) - special for Rails 3 and almost the same as this project.
154 | * [model_translations](http://github.com/janne/model_translations) - almost the same as this project, but more with more code in lib.
155 | * [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).
156 |
157 |
158 | Copyright (c) 2009-2016 [Dmitry Polushkin], released under the MIT license
159 |
--------------------------------------------------------------------------------
/test/has_translations_test.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'test_helper'
4 |
5 | class HasTranslationsTest < Test::Unit::TestCase
6 | def setup
7 | setup_db
8 |
9 | [Article, ArticleTranslation, Team, TeamTranslation, Organization, Organization, OrganizationTranslation].each do |k|
10 | k.delete_all
11 | end
12 | I18n.locale = :ru
13 | end
14 |
15 | def teardown
16 | teardown_db
17 | end
18 |
19 | def test_schema_has_loaded_correctly
20 | [Article, ArticleTranslation, Team, TeamTranslation, Organization, OrganizationTranslation].each do |k|
21 | assert_equal [], k.all
22 | end
23 | assert_equal :ru, I18n.locale
24 | end
25 |
26 | def test_reader_text_for_a_given_locale
27 | article = Article.create!
28 | article_translation = ArticleTranslation.new(:description => 'desc', :text => 'text')
29 | article_translation.article = article
30 | article_translation.locale = 'en'
31 | article_translation.save!
32 | assert_not_equal article.text, article_translation.text
33 | I18n.locale = :en
34 | assert_equal article.text, article_translation.text
35 | end
36 |
37 | def test_reader_for_single_column
38 | article = Article.create!
39 | %w{en ru wu}.each do |locale|
40 | translation = article.translations.build(:description=>"description in #{locale}", :text=>"text in #{locale}")
41 | translation.locale=locale
42 | translation.save!
43 | end
44 | I18n.locale = :ru
45 | assert_equal article.description(:en), "description in en"
46 | assert_equal article.text(:wu), "text in wu"
47 | assert_equal article.text, "text in ru"
48 | end
49 |
50 | def test_writer_text_for_a_given_locale
51 | article = Article.create!
52 | assert_equal '', article.text
53 | assert_equal nil, article.text_before_type_cast
54 | article.text = 'text'
55 | assert_equal 'text', article.text_before_type_cast
56 | assert_equal 0, article.translations.count
57 | article.save!
58 | assert_equal 'text', article.text_before_type_cast
59 | assert_equal 1, article.translations.length
60 | assert_equal 1, article.translations.count
61 | I18n.locale = :en
62 | assert_equal '', article.text
63 | article.update_attributes!(:text => 'text')
64 | assert_equal 2, Article.first.translations.count
65 | article.text = 'text new'
66 | article.save!
67 | assert_equal 'text new', article.text
68 | end
69 |
70 | def test_translations_association_and_translations
71 | article = Article.create!
72 | assert_equal [], article.translations
73 | article_translation = ArticleTranslation.new(:description => 'описание', :text => 'текст')
74 | article_translation.article = article
75 | article_translation.locale = 'ru'
76 | article_translation.save!
77 | assert_equal [], article.translations
78 | assert_equal [article_translation], article.reload.translations
79 | assert_equal 'текст', article.text
80 | I18n.locale = :en
81 | assert_equal '', article.text
82 | assert_equal article_translation, article.translation('ru')
83 | assert_equal article_translation, article.translation(:ru)
84 | assert article.destroy
85 | assert_equal [], ArticleTranslation.all
86 | end
87 |
88 | def test_all_translation_works_fine_with_attr_accessible
89 | article = Article.create!
90 | t = article.all_translations
91 | assert_equal 'ru', t[:ru].locale
92 | end
93 |
94 | def test_translation_validations
95 | article_translation = ArticleTranslation.create(:description => 'description', :text => 'text')
96 | assert article_translation.errors[:locale].present?
97 | # TODO may be add :inverse_of to has_many and add presence validation for the belongs_to
98 | end
99 |
100 | def test_fallback_and_nil_options
101 | article = Article.create!
102 | assert_equal '', article.text
103 | team = Team.create!
104 | assert_equal nil, team.text
105 | first_translation = TeamTranslation.create!(:team => team, :locale => 'es', :text => 'text')
106 | assert_equal first_translation.text, team.reload.text
107 | default_translation = TeamTranslation.create!(:team => team, :locale => 'en', :text => 'text')
108 | assert_equal default_translation.text, team.reload.text
109 | real_translation = TeamTranslation.create!(:team => team, :locale => 'ru', :text => 'текст')
110 | assert_equal real_translation.text, team.reload.text
111 | end
112 |
113 | def test_all_translations_sorted_build_or_translation_getted
114 | team = Team.create!
115 | team_translation = TeamTranslation.create!(:team => team, :locale => 'en', :text => 'text')
116 | assert_equal team_translation, team.all_translations[:en]
117 | assert_equal 'ru', team.all_translations[:ru].locale
118 | team_translation_new = team.translations.build(:locale => :ru)
119 | assert_equal team_translation_new.locale.to_s, team.all_translations[:ru].locale
120 | assert_equal team.all_translations[:ru].team_id, team.id
121 | end
122 |
123 | def test_all_translations_should_not_have_build_translations
124 | team = Team.create!
125 | assert_equal 0, team.translations.length
126 | team.all_translations
127 | assert_equal 0, team.translations.length
128 | end
129 |
130 | def test_has_translation?
131 | team = Team.create!
132 | assert !team.has_translation?(:en)
133 | team.translations.create!(:locale => 'en', :text => 'text')
134 | assert team.has_translation?(:en)
135 | end
136 |
137 | def test_named_scope_translated
138 | assert_equal 0, Team.translated(:en).count
139 | assert_equal 0, Team.translated(:ru).count
140 | team = Team.create!
141 | team.translations.create!(:locale => 'en', :text => 'text')
142 | assert_equal 0, Team.translated(:ru).count
143 | assert_equal 1, Team.translated(:en).count
144 | team.translations.create!(:locale => 'ru', :text => 'текст')
145 | assert_equal 1, Team.translated(:ru).count
146 | end
147 |
148 | def test_foreign_key_for_has_many_associations
149 | organization = Organization.create!
150 | organization.translations.create!(:locale => 'en', :text => 'text')
151 | assert organization.has_translation?(:en)
152 | organization.translations.create!(:locale => 'ru', :text => 'текст')
153 | assert_equal 2, organization.reload.translations.count
154 | assert_equal 1, Organization.translated(:en).count
155 | assert_equal 1, Organization.translated(:ru).count
156 | end
157 | end
158 |
--------------------------------------------------------------------------------