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 +4 -4
- data/README.md +26 -7
- data/app/models/active_translation/cache.rb +8 -0
- data/lib/active_translation/translatable.rb +60 -2
- data/lib/active_translation/translation_job.rb +1 -10
- data/lib/active_translation/version.rb +1 -1
- data/lib/generators/active_translation/templates/create_translations.rb +8 -0
- metadata +8 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1ddc62c44172b94c2c1afc7a7584b79cbe925f60968aba55da4ef16e41abc2ec
|
|
4
|
+
data.tar.gz: d9e6e12d9ac0e5caf56e1a270046dfa56bb5910ff152e0c3cc08d5f1db1c131e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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"
|
|
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
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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.
|
|
4
|
+
version: 0.7.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
-
-
|
|
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
|
-
-
|
|
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/
|
|
124
|
+
homepage: https://github.com/talentronic/active_translation
|
|
124
125
|
licenses:
|
|
125
126
|
- MIT
|
|
126
127
|
metadata:
|
|
127
|
-
homepage_uri: https://github.com/
|
|
128
|
-
source_code_uri: https://github.com/
|
|
129
|
-
changelog_uri: https://github.com/
|
|
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
|