active_translation 0.7.3 → 0.7.5

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: 95d3e129763f16bedcfba3e7fdc8b2571650f1492609d78649f84789f1e85580
4
- data.tar.gz: 2a46a6f9ab2f2fd145791db066954d705095357a854a98db0801dd3c4ffaab85
3
+ metadata.gz: 41efab1065efc26a1d0e04a05ec1be8f0f4b5a9143b0d486c958540f8cb970bc
4
+ data.tar.gz: 02b939cc57e481288dd6d691f65c86cb6bacac2eface6866fa3f8f2f91765dda
5
5
  SHA512:
6
- metadata.gz: ca396a230135fb092c5dfe8e3aa43dcef5efd734de459cce75d8da8b9e2783642f4d713c756dbd528873e5b2e48bd9109114c5ff4dcb2a12188d01ce0d3aca14
7
- data.tar.gz: ceb3e4c83ef21ac1edbc03591ba07accb98e5aa1cb75f58f154d0cd674060d5650876f3d01b3d62cebf2f2a21fe2f760627048b8cfdbc42edfd44e7ca41af9bd
6
+ metadata.gz: 5b816b22734257d8818fe47035c50793afaf5801de186da7922479803051181d5e58c3859ac2123d916593431c412b0fae9acd24d049aee4534e6cc9b164c576
7
+ data.tar.gz: a440a16da19267c58e99ae3f017139465aa2ddec3b6418ce9b61ef023778221c71460932e28631638f30ee2fa3b2519603806f06beabcb58ed4099f354506951
data/README.md CHANGED
@@ -33,7 +33,7 @@ Instead, what if you just add a single line to your `Category` model. Now produc
33
33
  Add the gem to your gemfile:
34
34
 
35
35
  ```ruby
36
- gem "active_translation", git: "https://github.com/seanhogge/active_translation"
36
+ gem "active_translation"
37
37
  ```
38
38
 
39
39
  And then bundle:
@@ -108,8 +108,6 @@ or
108
108
  translates :content, into: -> { I18n.available_locales - [ I18n.default_locale ] }
109
109
  ```
110
110
 
111
- > `it` is a recent Ruby syntactical grain of sugar. It's the same as `_1` which lets you skip the `{ |arg| arg == :stuff }` repetition
112
-
113
111
  #### Into All
114
112
 
115
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 ] }`.
@@ -168,6 +166,28 @@ Manual attributes have a special setter in the form of `#{locale}_#{attribute_na
168
166
 
169
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.
170
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
+
171
191
  ### The Show
172
192
 
173
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.
@@ -338,9 +358,9 @@ You can call `translation_config` on a model or instance to see what you've set
338
358
 
339
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.
340
360
 
341
- 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.
342
362
 
343
- ActiveTranslation doesn't redact any content. It assumes you would never send PII or financial data for translation. So... please don't.
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
365
 
346
366
  ## Testing
@@ -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: 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.3"
2
+ VERSION = "0.7.5"
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_translation
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.3
4
+ version: 0.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Talentronic
@@ -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