voltron-translate 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1658719544a250f6aae2906d090e405a7614db52
4
- data.tar.gz: 1a44aea7a63899dc077fa3871a6334e6e528aa3b
3
+ metadata.gz: 496b01a5fc6aef8382150266d811b2b0feddbd4e
4
+ data.tar.gz: 6db2b1e2291a17e376c74cee8744a674632c17d7
5
5
  SHA512:
6
- metadata.gz: 6d48356fb02213c0d4d41e53cf2c6fc23c0bbba719a81252dadbc50e0acdcb89e8fe239d12f98d5bf77c2b44f3cf165c4ae1d6c640ef8334c4c78dc59f47f229
7
- data.tar.gz: 5ceea071fb55ec9ccfc5b5fbda61d5c9c3966ff5fc0e95938c32c62085ce53cc31bb266eb6b99bdf50b742d347c3c35ad651ab9aeec1807f8ca074046c72ca4d
6
+ metadata.gz: eef4569dd7db4562ceed56dce7154fe081708b4f975b0b5b600a973bf68b30caf347cd8799a8a1253b87c22542ed69668da41e1501027ed9c04200b52313bd99
7
+ data.tar.gz: e27b55db4f9163ec69576d22a89845dff355f01a52d55dffa16705311cb5ed41d296924b8e878f7984cb8310e64901aedfd291d3542d769b25b187eec08130f4
data/README.md CHANGED
@@ -26,6 +26,8 @@ Then run the following to create the voltron.rb initializer (if not exists alrea
26
26
 
27
27
  ## Usage
28
28
 
29
+ ### The Double Underscore Method
30
+
29
31
  Voltron Translate extends ActiveRecord::Base, ActionController::Base, ActionMailer::Base, and ActionView::Base with a __ (double underscore) method that makes internationalization and/or translating static phrases easier.
30
32
 
31
33
  Once installed, from any class that extends from any of the three rails classes you can use the double underscore method to allow for real time text translation. For example:
@@ -102,12 +104,103 @@ __("User with name %{person_name} has been saved successfully.", :de, person_nam
102
104
 
103
105
  Will always look for the above translation within de.csv
104
106
 
107
+ ### Backend Translations
108
+
109
+ To add support for translations of dynamic text, i.e. - Text entered into a form, Voltron Translate adds a `translates` class method to models.
110
+
111
+ ```ruby
112
+ class Company < ActiveRecord::Base
113
+
114
+ # translates :attribute_name1, :attribute_name2, :attribute_name3, ..., options={}
115
+ translates :name, :greeting, { locales: [:en, :es, :de, :"en-GB"], default: :en }
116
+
117
+ end
118
+ ```
119
+
120
+ Options to the `translates` method are optional, and, if any/all or omitted, the defaults are as follows:
121
+
122
+ locales -> Defaults to Voltron.config.translates.locales, which itself defaults to Rails.application.config.i18n.available_locales
123
+ default -> nil, will just return the value of the original attribute, i.e. - "name" or "greeting"
124
+
125
+ The `translates` method adds locale specific version of the attribute(s) to the model with the following methods:
126
+
127
+ `<attribute>_<locale>`
128
+
129
+ `<attribute>_<locale>=`
130
+
131
+ `<attribute>_<locale>?`
132
+
133
+ `<attribute>_<locale>_will_change!`
134
+
135
+ `<attribute>_<locale>_changed?`
136
+
137
+ `<attribute>_<locale>_was`
138
+
139
+ In addition, it will override the `<attribute>` method with one that takes a single, optional argument: the locale you want to return the text for. Consider the following:
140
+
141
+ ```ruby
142
+ # Voltron.config.translate.locales = [:en, :es, :"en-GB"]
143
+ class Company < ActiveRecord::Base
144
+
145
+ translates :name
146
+
147
+ end
148
+ ```
149
+
150
+ ```ruby
151
+ @company = Company.create(name: 'Company Name', name_es: 'Spanish Company Name', name_en_gb: 'British Company Name')
152
+
153
+ @company.name # Returns 'Company Name'
154
+ @company.name(:es) # Returns 'Spanish Company Name'
155
+ @company.name(:invalid_locale) # Returns 'Company Name', since it ultimately will fall back to the original attribute
156
+
157
+ # OR, access the locale directly:
158
+
159
+ @company.name_es # Returns 'Spanish Company Name'
160
+ @company.name_en_gb # Returns 'British Company Name'
161
+
162
+ # Without specifying a specific locale in either the method call or +translates+ option, it will try and base it's lookup by the value of I18n.locale
163
+
164
+ I18n.locale = :en
165
+ @company.name # Returns 'Company Name'
166
+
167
+ I18n.locale = :es
168
+ @company.name # Returns 'Spanish Company Name', since our global locale is set to :es
169
+ ```
170
+
171
+ Should go without saying, but to set the translation text on the frontend, you'd just create a separate form field for each locales text:
172
+
173
+ ```erb
174
+ <%= form_for @company do |f| %>
175
+
176
+ <div>
177
+ <%= f.label :name %>
178
+ <%= f.text_field :name %>
179
+ </div>
180
+
181
+ <div>
182
+ <%= f.label :name_es %>
183
+ <%= f.text_field :name_es %>
184
+ </div>
185
+
186
+ <div>
187
+ <%= f.label :name_en_gb %>
188
+ <%= f.text_field :name_en_gb %>
189
+ </div>
190
+
191
+ <% end %>
192
+ ```
193
+
194
+ Add the appropriate attributes to your strong params, so on, so on...
195
+
105
196
  ## Things to Note
106
197
 
107
198
  Setting `Voltron.config.translate.enabled` to `false` will never break any __() call, it simply causes it to ignore the locale argument (if specified) and return the interpolated string using the latter arguments (again, if any)
108
199
 
109
200
  Disabling translations simply disables any IO related actions that would occur normally, like building or looking up translations when __() methods are called.
110
201
 
202
+ It also disables the locale specific text translation on any method call that was targeted with `translates`, meaning `@company.name(:es)` would be the equivalent of calling `@company.name`. Note that `@company.name_es` would still work as it normally would.
203
+
111
204
  ## Development
112
205
 
113
206
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,7 @@
1
+ module Voltron
2
+ class Translation < ActiveRecord::Base
3
+
4
+ belongs_to :resource, polymorphic: true
5
+
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ class CreateVoltronTranslations < ActiveRecord::Migration
2
+ def change
3
+ create_table :voltron_translations do |t|
4
+ t.integer :resource_id
5
+ t.string :resource_type
6
+ t.string :attribute_name
7
+ t.string :locale
8
+ t.text :translation
9
+ end
10
+ end
11
+ end
@@ -3,6 +3,8 @@ module Voltron
3
3
  module Generators
4
4
  class InstallGenerator < Rails::Generators::Base
5
5
 
6
+ source_root File.expand_path("../../../templates", __FILE__)
7
+
6
8
  desc 'Add Voltron Translate initializer'
7
9
 
8
10
  def inject_initializer
@@ -27,8 +29,11 @@ module Voltron
27
29
  # Whether or not translation is enabled
28
30
  # config.translate.enabled = true
29
31
 
30
- # Which locales to build translation files for
31
- # config.translate.locales << :en
32
+ # Which locales to build translation files for. This setting also
33
+ # determines the global default locales used with the `translates` class method
34
+ # For example, if this is [:en, :es, :de], calling `translates :attribute` in a model
35
+ # Will expose the methods `attribute_en`, `attribute_es`, and `attribute_de`
36
+ # config.translate.locales = Rails.application.config.i18n.available_locales
32
37
 
33
38
  # In what environments can translation generation occur. Recommended to keep this as development (default)
34
39
  # config.translate.build_environment << :development
@@ -36,6 +41,38 @@ CONTENT
36
41
  end
37
42
  end
38
43
  end
44
+
45
+ def copy_migrations
46
+ copy_migration 'create_voltron_translations'
47
+ end
48
+
49
+ protected
50
+
51
+ def copy_migration(filename)
52
+ if migration_exists?(Rails.root.join('db', 'migrate'), filename)
53
+ say_status('skipped', "Migration #{filename}.rb already exists")
54
+ else
55
+ copy_file "db/migrate/#{filename}.rb", Rails.root.join('db', 'migrate', "#{migration_number}_#{filename}.rb")
56
+ end
57
+ end
58
+
59
+ def migration_exists?(dirname, filename)
60
+ Dir.glob("#{dirname}/[0-9]*_*.rb").grep(/\d+_#{filename}.rb$/).first
61
+ end
62
+
63
+ def migration_id_exists?(dirname, id)
64
+ Dir.glob("#{dirname}/#{id}*").length > 0
65
+ end
66
+
67
+ def migration_number
68
+ @migration_number ||= Time.now.strftime('%Y%m%d%H%M%S').to_i
69
+
70
+ while migration_id_exists?(Rails.root.join('db', 'migrate'), @migration_number) do
71
+ @migration_number += 1
72
+ end
73
+
74
+ @migration_number
75
+ end
39
76
  end
40
77
  end
41
78
  end
@@ -12,7 +12,7 @@ module Voltron
12
12
  def initialize
13
13
  @build_environment ||= [:development]
14
14
  @enabled ||= true
15
- @locales ||= [:en]
15
+ @locales ||= I18n.available_locales
16
16
  end
17
17
 
18
18
  def enabled?
@@ -20,7 +20,7 @@ module Voltron
20
20
  end
21
21
 
22
22
  def buildable?
23
- [build_environment].flatten.map(&:to_s).include?(Rails.env.to_s)
23
+ Array.wrap(build_environment).map(&:to_s).include?(Rails.env.to_s)
24
24
  end
25
25
  end
26
26
  end
@@ -0,0 +1,121 @@
1
+ module Voltron
2
+ module Translatable
3
+
4
+ def translates(*attributes)
5
+ include InstanceMethods
6
+
7
+ options = (attributes.extract_options!).with_indifferent_access
8
+ locales = Array.wrap(options[:locales] || Voltron.config.translate.locales).map(&:to_s).map(&:underscore)
9
+
10
+ attributes.each do |attribute|
11
+
12
+ column = self.columns_hash[attribute.to_s]
13
+
14
+ raise ::ActiveRecord::UnknownAttributeError.new(self.new, attribute) if column.nil?
15
+
16
+ raise ::Voltron::Translate::InvalidColumnTypeError.new("Invalid type '#{column.type}' for attribute: #{attribute}. Translations only work on string and text attribute types.") unless [:string, :text].include?(column.type)
17
+
18
+ # Override the attribute with a method that accepts a specific locale as an argument
19
+ # If specified, will attempt to fetch that locale's translation, otherwise the default
20
+ # locale specified for the attribute, and ultimately the current locale translation
21
+ # If still nil, returns the value from super
22
+ define_method :"#{attribute}" do |locale=nil|
23
+ # +action_view/helpers/targs+ exist when this method is called from within
24
+ # ActionView::Helpers. In other words, form helper tags. In that
25
+ # case we want the actual value of the attribute, not whatever the locale is
26
+ return super() if caller.any? { |l| /action_view\/helpers\/tags/.match(l) } || !Voltron.config.translate.enabled?
27
+ try(:"#{attribute}_#{locale.to_s.underscore}") || try(:"#{attribute}_#{options[:default].to_s.underscore}") || try(:"#{attribute}_#{I18n.locale.to_s.underscore}") || super()
28
+ end
29
+
30
+ locales.each do |locale|
31
+
32
+ # Define setter, i.e. - +attribute_es=+
33
+ define_method :"#{attribute}_#{locale}=" do |val|
34
+ attribute_will_change! "#{attribute}_#{locale}"
35
+ instance_variable_set("@#{attribute}_#{locale}", val)
36
+ end
37
+
38
+ # Define getter, i.e - +attribute_es+
39
+ # If nil, calling this method will attempt to fetch the value
40
+ # We do this to avoid preloading the translations association records
41
+ define_method :"#{attribute}_#{locale}" do
42
+ if instance_variable_get("@#{attribute}_#{locale}").nil?
43
+ instance_variable_set("@#{attribute}_#{locale}", send(:"#{attribute}_#{locale}_was"))
44
+ end
45
+ instance_variable_get("@#{attribute}_#{locale}")
46
+ end
47
+
48
+ # Define the changed? method, i.e. - +attribute_es_changed?+
49
+ define_method :"#{attribute}_#{locale}_changed?" do
50
+ changed.include?("#{attribute}_#{locale}")
51
+ end
52
+
53
+ # Define the was method, i.e. - +attribute_es_was+
54
+ define_method :"#{attribute}_#{locale}_was" do
55
+ translations.find_by(attribute_name: attribute, locale: locale).try(:translation)
56
+ end
57
+
58
+ define_method :"#{attribute}_#{locale}_will_change!" do
59
+ attribute_will_change! "#{attribute}_#{locale}"
60
+ end
61
+
62
+ define_method :"#{attribute}_#{locale}?" do
63
+ instance_variable_get("@#{attribute}_#{locale}").present?
64
+ end
65
+
66
+ end
67
+ end
68
+
69
+ # In case +translates+ was called multiple times, merge in the new attributes/locales
70
+ # with the pre-existing ones
71
+ all_attributes = @_translations.try(:keys) || []
72
+ all_attributes += attributes
73
+ all_attributes.uniq!
74
+
75
+ all_locales = @_translations.try(:values) || []
76
+ all_locales += locales
77
+ all_locales.flatten!
78
+ all_locales.uniq!
79
+
80
+ has_many :translations, as: :resource, class_name: 'Voltron::Translation', dependent: :destroy
81
+
82
+ before_save :build_translations
83
+
84
+ accepts_nested_attributes_for :translations, reject_if: :all_blank, allow_destroy: true
85
+
86
+ @_translations = all_attributes.map { |a| { a.to_s => all_locales } }.reduce(Hash.new, :merge)
87
+ end
88
+
89
+ module InstanceMethods
90
+
91
+ # Before validation, iterate over all possible translation methods
92
+ # and either update the corresponding translation record or build it,
93
+ # so it can be saved when the parent record is saved
94
+ def build_translations
95
+ self.translations_attributes = translation_methods.map do |m, t|
96
+ if send(:"#{m}_changed?")
97
+ # Find the translation if it previously existed, or create new one
98
+ translation = translations.where(attribute_name: t[:attribute], locale: t[:locale]).first || Voltron::Translation.new(attribute_name: t[:attribute], locale: t[:locale])
99
+ # Set the translation text on our returned translation object
100
+ translation.translation = instance_variable_get("@#{m}")
101
+ # Return the attributes of our translation that will be assigned to self.translations_attributes
102
+ translation.attributes
103
+ end
104
+ end.compact
105
+ end
106
+
107
+ private
108
+
109
+ def translate_translations
110
+ self.class.instance_variable_get('@_translations')
111
+ end
112
+
113
+ def translation_methods
114
+ translate_translations.map do |attribute, locales|
115
+ locales.map { |locale| { :"#{attribute}_#{locale}" => { attribute: attribute, locale: locale } } }
116
+ end.flatten.reduce(Hash.new, :merge)
117
+ end
118
+
119
+ end
120
+ end
121
+ end
@@ -1,12 +1,14 @@
1
- require 'voltron'
2
-
3
1
  module Voltron
4
2
  module Translate
5
- class Railtie < Rails::Railtie
3
+ class Engine < Rails::Engine
4
+
5
+ isolate_namespace Voltron
6
+
6
7
  initializer 'voltron.translate.initialize' do
7
8
  ::ActionController::Base.send :include, ::Voltron::Translate
8
9
  ::ActiveRecord::Base.send :include, ::Voltron::Translate
9
10
  ::ActiveRecord::Base.send :extend, ::Voltron::Translate
11
+ ::ActiveRecord::Base.send :extend, ::Voltron::Translatable
10
12
  ::ActionView::Base.send :include, ::Voltron::Translate
11
13
  ::ActionMailer::Base.send :include, ::Voltron::Translate
12
14
  end
@@ -1,5 +1,5 @@
1
1
  module Voltron
2
2
  module Translate
3
- VERSION = '0.1.6'.freeze
3
+ VERSION = '0.2.0'.freeze
4
4
  end
5
5
  end
@@ -1,6 +1,7 @@
1
1
  require 'voltron'
2
2
  require 'voltron/translate/version'
3
3
  require 'voltron/config/translate'
4
+ require 'voltron/translatable'
4
5
  require 'digest'
5
6
  require 'csv'
6
7
  require 'google_hash'
@@ -8,6 +9,8 @@ require 'google_hash'
8
9
  module Voltron
9
10
  module Translate
10
11
 
12
+ class InvalidColumnTypeError < StandardError; end
13
+
11
14
  def __(text, locale = I18n.locale, **args)
12
15
  return (text % args) unless Voltron.config.translate.enabled?
13
16
 
@@ -143,4 +146,4 @@ module Voltron
143
146
  end
144
147
  end
145
148
 
146
- require 'voltron/translate/railtie' if defined?(Rails)
149
+ require 'voltron/translate/engine' if defined?(Rails)
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency 'rails', '>= 4.2'
22
- spec.add_dependency 'voltron', '>= 0.2.0'
22
+ spec.add_dependency 'voltron', '~> 0.2.4'
23
23
  spec.add_dependency 'google_hash', '>= 0.9.0'
24
24
 
25
25
  spec.add_development_dependency 'bundler', '>= 1.12'
@@ -28,4 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency 'rspec-rails', '>= 3.4'
29
29
  spec.add_development_dependency 'sqlite3', '>= 1.2'
30
30
  spec.add_development_dependency 'simplecov', '0.11.0'
31
+ spec.add_development_dependency 'factory_girl_rails', '>= 4.7'
32
+ spec.add_development_dependency 'byebug'
31
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: voltron-translate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Hainer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-03-07 00:00:00.000000000 Z
11
+ date: 2017-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -28,16 +28,16 @@ dependencies:
28
28
  name: voltron
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.2.0
33
+ version: 0.2.4
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.2.0
40
+ version: 0.2.4
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: google_hash
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +136,34 @@ dependencies:
136
136
  - - '='
137
137
  - !ruby/object:Gem::Version
138
138
  version: 0.11.0
139
+ - !ruby/object:Gem::Dependency
140
+ name: factory_girl_rails
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '4.7'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '4.7'
153
+ - !ruby/object:Gem::Dependency
154
+ name: byebug
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
139
167
  description:
140
168
  email:
141
169
  - eric@commercekitchen.com
@@ -151,12 +179,15 @@ files:
151
179
  - LICENSE.txt
152
180
  - README.md
153
181
  - Rakefile
182
+ - app/models/voltron/translation.rb
154
183
  - bin/console
155
184
  - bin/setup
185
+ - lib/generators/templates/db/migrate/create_voltron_translations.rb
156
186
  - lib/generators/voltron/translate/install_generator.rb
157
187
  - lib/voltron/config/translate.rb
188
+ - lib/voltron/translatable.rb
158
189
  - lib/voltron/translate.rb
159
- - lib/voltron/translate/railtie.rb
190
+ - lib/voltron/translate/engine.rb
160
191
  - lib/voltron/translate/version.rb
161
192
  - voltron-translate.gemspec
162
193
  homepage: https://github.com/ehainer/voltron-translate