active_translation 0.7.2 → 0.7.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 94e47e7f24a4318caea63540d47bd83d639e50be79e04198fc8760fd90156120
4
- data.tar.gz: 425bbdd22608f48db0882777700576579e15859e5b824332747ddb96fa1cbe98
3
+ metadata.gz: 1ddc62c44172b94c2c1afc7a7584b79cbe925f60968aba55da4ef16e41abc2ec
4
+ data.tar.gz: d9e6e12d9ac0e5caf56e1a270046dfa56bb5910ff152e0c3cc08d5f1db1c131e
5
5
  SHA512:
6
- metadata.gz: 728d2c9dff7539257c3d9e2e2853cdb0b80f0b13b912f045c00d22e63c817c0e478c61b30825932917271acf888c903cb70765329f641b2a4eb55e1028d20f5f
7
- data.tar.gz: d44131c323130218ba82cd767e65fb1d6964b50ddda7647cfe2dd80fed50d4d243649e907708b5f70c8de019e5cefc0d2e07f59d37393e549bad0996e65fb2db
6
+ metadata.gz: 81fafcbaa0ba4a146bf68016e8a96c2a34df077cd935a1e82ef1373ffe7b9d9816e3b8c646891f2defe6d836069dd04c615ca7c71ae730859f0d6ee3ab89c320
7
+ data.tar.gz: 575572e15ebea8388c943e4d03b91d20835d444c47ec97d74abfee01ad399ae61c22b175f95d28dbcf76efa31e506da58d3179406cdadfdab0c37406b9e107b0
data/README.md CHANGED
@@ -14,8 +14,6 @@ If you find a bug or problem, please report it. If you have an idea for a new fe
14
14
 
15
15
  ActiveTranslation is a Rails plugin that lets you easily translate ActiveRecord models. With a single line added to that model, you can declare which columns, which locales, and what constraints to allow or prevent translation.
16
16
 
17
- ActiveTranslation was built at and is sponsored by [Talentronic](https://talentronic.com)
18
-
19
17
 
20
18
  ## How does this differ from internationalization (`I18n`)?
21
19
 
@@ -35,7 +33,7 @@ Instead, what if you just add a single line to your `Category` model. Now produc
35
33
  Add the gem to your gemfile:
36
34
 
37
35
  ```ruby
38
- gem "active_translation", git: "https://github.com/seanhogge/active_translation"
36
+ gem "active_translation"
39
37
  ```
40
38
 
41
39
  And then bundle:
@@ -110,8 +108,6 @@ or
110
108
  translates :content, into: -> { I18n.available_locales - [ I18n.default_locale ] }
111
109
  ```
112
110
 
113
- > `it` is a recent Ruby syntactical grain of sugar. It's the same as `_1` which lets you skip the `{ |arg| arg == :stuff }` repetition
114
-
115
111
  #### Into All
116
112
 
117
113
  Because translating a model into all the locales your app may define is so common, you can pass `:all` to the `into` argument to achieve the same result as passing `-> { I18n.available_locales - [ I18n.default_locale ] }`.
@@ -170,6 +166,28 @@ Manual attributes have a special setter in the form of `#{locale}_#{attribute_na
170
166
 
171
167
  These attributes never trigger retranslation, and are never checked against the original text - it's entirely up to you to maintain them. However, it does get stored alongside all the other translations, keeping your database tidy and your translation code consistent.
172
168
 
169
+ ### Caching
170
+
171
+ You may have a model where many records share the same translated content. For instance a table of jobs might have thousands or millions of "housekeeper" jobs, all with the title "Housekeeper" or "Houseperson". It would be foolish and expensive to make a call to translate these repeatedly.
172
+
173
+ Thus, you can add the `cache` directive. You can set it to `true`, to a `String` or `Symbol`, or to an `Array`
174
+
175
+ If `true`, then all calls to translate that model will be cached. So if you have Job ID 1 with a title of "Sales Manager" and you translate it, Active Translation will reach out and get a translation for all the locales for "Sales Manager".
176
+
177
+ If you have Job ID 2 with the same "Sales Manager" title, translating it will result in no call being made for the title - it's already stored in the Active Translation cache.
178
+
179
+ If you have a different model, like a `JobTemplate` that has an attribute called `template_title` and no caching enabled, translating that `JobTemplate` will **still** use the cache. The overhead of this indexed database lookup is worth the savings.
180
+
181
+ So you can't control when Active Translation looks for a cached value, but you can control what gets entered into the cache.
182
+
183
+ When `cache: true` is set, all translatable attributes will be cached.
184
+
185
+ When `cache: :subhead` is set, only the `subhead` attribute translations will be cached.
186
+
187
+ When `cache: [ :name, :heading ]` is set, only the `name` and `heading` translations will be cached.
188
+
189
+ So don't cache attributes that are huge or unlikely to ever be duplicated. For instance, do not cache an attribute that holds HTML content, or probably anything that's a TEXT column in the database. Unless, of course, those large text columns actually are frequently duplicated. Use your ~~brain~~ discretion.
190
+
173
191
  ### The Show
174
192
 
175
193
  Once you have added the `translates` directive with your columns, locales, and constraints and your models have been translated to at least one locale, it's time to actually use them.
@@ -340,9 +358,10 @@ You can call `translation_config` on a model or instance to see what you've set
340
358
 
341
359
  ActiveTranslation doesn't check the accuracy of translations in any way. It assumes that the response from Google is always perfect. If you are translating sensitive content where accuracy is critical in a legal or existential sense, you must handle translation auditing separately.
342
360
 
343
- So if you use the for an EULA, make it a manual attribute or don't use ActiveTranslation for it at all.
361
+ So if you use Active Translation for a column that contains legal language like an end-user license agreement (EULA), make it a manual attribute or don't use ActiveTranslation for it at all.
362
+
363
+ ActiveTranslation doesn't redact any content. It assumes you would never send PII or financial data for translation. So please don't.
344
364
 
345
- ActiveTranslation doesn't redact any content. It assumes you would never send PII or financial data for translation. So... please don't.
346
365
 
347
366
  ## Testing
348
367
 
@@ -0,0 +1,8 @@
1
+ module ActiveTranslation
2
+ class Cache < ApplicationRecord
3
+ self.table_name = :active_translation_cache
4
+
5
+ validates :locale, presence: true
6
+ validates :checksum, presence: true
7
+ end
8
+ end
@@ -3,13 +3,14 @@ module ActiveTranslation
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  class_methods do
6
- def translates(*attributes, manual: [], into:, unless: nil, if: nil)
6
+ def translates(*attributes, manual: [], into:, unless: nil, if: nil, cache: false)
7
7
  @translation_config ||= {}
8
8
  @translation_config[:attributes] = Array(attributes).map(&:to_s)
9
9
  @translation_config[:manual_attributes] = Array(manual).map(&:to_s)
10
10
  @translation_config[:locales] = into
11
11
  @translation_config[:unless] = binding.local_variable_get(:unless)
12
12
  @translation_config[:if] = binding.local_variable_get(:if)
13
+ @translation_config[:cache] = cache
13
14
 
14
15
  has_many :translations, class_name: "ActiveTranslation::Translation", as: :translatable, dependent: :destroy
15
16
 
@@ -23,16 +24,19 @@ module ActiveTranslation
23
24
  # Respond to calls such as fr_translation or de_translation
24
25
  # Respond to calls such as model.name(locale: :fr)
25
26
  define_method(:method_missing) do |method_name, *args, &block|
26
- super() unless method_name.to_s.split("_").size == 2
27
+ # if the method name doesn't have an underscore with at least 1 character on both sides
28
+ super(method_name, *args, &block) unless method_name.to_s.split("_", 2).reject(&:blank?).size == 2
27
29
 
28
30
  locale = method_name.to_s.split("_").first
29
31
  attribute = method_name.to_s.split("_").last
30
32
 
33
+ # if something like "model.fr_title" is being called (no =)
31
34
  if translation_config[:manual_attributes].include? attribute
32
35
  translation = translations.find_by(locale: locale)
33
36
  return read_attribute(attribute) unless translation
34
37
 
35
38
  translation.translated_attributes[attribute].presence || read_attribute(attribute)
39
+ # if something like "model.fr_title = 'Zut alors' is being called"
36
40
  elsif attribute.last == "=" && translation_config[:manual_attributes].include?(attribute.delete("="))
37
41
  attribute.delete!("=")
38
42
  translation = translations.find_or_initialize_by(locale: locale.to_s)
@@ -40,8 +44,12 @@ module ActiveTranslation
40
44
  attrs[attribute] = args.first
41
45
  translation.translated_attributes = attrs
42
46
  translation.save!
47
+ # if something like "model.fr_translation" is being called
43
48
  elsif attribute == "translation" || translation_config[:attributes].include?(attribute)
44
49
  translations.find_by(locale: locale)
50
+ # if some method with an underscore that doesn't match anything above
51
+ else
52
+ super(method_name, *args, &block)
45
53
  end
46
54
  end
47
55
 
@@ -150,6 +158,52 @@ module ActiveTranslation
150
158
  end
151
159
  end
152
160
 
161
+ def translate_attribute(attribute, locale)
162
+ return nil if send(attribute).nil?
163
+
164
+ cached_translation = ActiveTranslation::Cache.find_by(
165
+ checksum: text_checksum(send(attribute)),
166
+ locale: locale,
167
+ )
168
+
169
+ translated_text = if Rails.env.test?
170
+ cached_translation&.translated_text || "[#{locale}] #{send(attribute)}"
171
+ else
172
+ cached_translation&.translated_text || ActiveTranslation::GoogleTranslate.translate(target_language_code: target_locale, text: text)
173
+ end
174
+
175
+ case translation_config[:cache]
176
+ when TrueClass
177
+ ActiveTranslation::Cache.find_or_create_by(
178
+ checksum: text_checksum(send(attribute)),
179
+ locale:,
180
+ ).update(translated_text:,)
181
+ when String, Symbol
182
+ return unless attribute.to_s == translation_config[:cache].to_s
183
+
184
+ ActiveTranslation::Cache.find_or_create_by(
185
+ checksum: text_checksum(send(attribute)),
186
+ locale:,
187
+ ).update(translated_text:,)
188
+ when Array
189
+ return unless translation_config[:cache].map(&:to_s).include? attribute.to_s
190
+
191
+ ActiveTranslation::Cache.find_or_create_by(
192
+ checksum: text_checksum(send(attribute)),
193
+ locale:,
194
+ ).update(translated_text:,)
195
+ end
196
+
197
+ translated_text
198
+ end
199
+
200
+ def translation_cached?(attribute, locale)
201
+ ActiveTranslation::Cache.find_by(
202
+ checksum: text_checksum(send(attribute)),
203
+ locale:,
204
+ )
205
+ end
206
+
153
207
  def translation_checksum
154
208
  values = translatable_attribute_names.map { |attr| read_attribute(attr).to_s }
155
209
  Digest::MD5.hexdigest(values.join)
@@ -216,6 +270,10 @@ module ActiveTranslation
216
270
  evaluate_condition(translation_config[:if])
217
271
  end
218
272
 
273
+ def text_checksum(text)
274
+ Digest::MD5.hexdigest(text)
275
+ end
276
+
219
277
  def translatable_attributes_changed?
220
278
  saved_changes.any? && saved_changes.keys.intersect?(translatable_attribute_names)
221
279
  end
@@ -6,8 +6,7 @@ module ActiveTranslation
6
6
  translated_data = {}
7
7
 
8
8
  object.translatable_attribute_names.each do |attribute|
9
- source_text = object.read_attribute(attribute)
10
- translated_data[attribute.to_s] = translate_text(source_text, locale)
9
+ translated_data[attribute.to_s] = object.translate_attribute(attribute, locale)
11
10
  end
12
11
 
13
12
  translation = object.translations
@@ -24,13 +23,5 @@ module ActiveTranslation
24
23
  source_checksum: checksum
25
24
  )
26
25
  end
27
-
28
- private
29
-
30
- def translate_text(text, target_locale)
31
- return "[#{target_locale}] #{text}" if Rails.env.test?
32
-
33
- ActiveTranslation::GoogleTranslate.translate(target_language_code: target_locale, text: text)
34
- end
35
26
  end
36
27
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveTranslation
2
- VERSION = "0.7.2"
2
+ VERSION = "0.7.4"
3
3
  end
@@ -10,5 +10,13 @@ class CreateTranslations < ActiveRecord::Migration[7.0]
10
10
 
11
11
  add_index :active_translation_translations, [ :translatable_type, :translatable_id, :locale ],
12
12
  unique: true, name: "index_translations_on_translatable_and_locale"
13
+
14
+ create_table :active_translation_cache do |t|
15
+ t.string :locale, null: false
16
+ t.string :checksum
17
+ t.text :translated_text
18
+ end
19
+
20
+ add_index :active_translation_cache, [ :checksum, :locale ], unique: true
13
21
  end
14
22
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_translation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.7.4
5
5
  platform: ruby
6
6
  authors:
7
- - Sean Hogge
7
+ - Talentronic
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
@@ -91,7 +91,7 @@ dependencies:
91
91
  version: 1.4.0
92
92
  description: Easily translate specific attributes of any ActiveRecord model
93
93
  email:
94
- - sean@seanhogge.com
94
+ - devs@talentronic.com
95
95
  executables: []
96
96
  extensions: []
97
97
  extra_rdoc_files: []
@@ -106,6 +106,7 @@ files:
106
106
  - app/lib/active_translation/google_translate.rb
107
107
  - app/mailers/active_translation/application_mailer.rb
108
108
  - app/models/active_translation/application_record.rb
109
+ - app/models/active_translation/cache.rb
109
110
  - app/models/active_translation/translation.rb
110
111
  - app/views/layouts/active_translation/application.html.erb
111
112
  - config/routes.rb
@@ -120,13 +121,13 @@ files:
120
121
  - lib/generators/active_translation/templates/active_translation.rb
121
122
  - lib/generators/active_translation/templates/create_translations.rb
122
123
  - lib/tasks/active_translation_tasks.rake
123
- homepage: https://github.com/seanhogge/active_translation
124
+ homepage: https://github.com/talentronic/active_translation
124
125
  licenses:
125
126
  - MIT
126
127
  metadata:
127
- homepage_uri: https://github.com/seanhogge/active_translation
128
- source_code_uri: https://github.com/seanhogge/active_translation
129
- changelog_uri: https://github.com/seanhogge/active_translation
128
+ homepage_uri: https://github.com/talentronic/active_translation
129
+ source_code_uri: https://github.com/talentronic/active_translation
130
+ changelog_uri: https://github.com/talentronic/active_translation
130
131
  rdoc_options: []
131
132
  require_paths:
132
133
  - lib