translated 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 29074d491110db08e39419d73a816b72827cbcf43376d6767ca8b51db29ac282
4
+ data.tar.gz: 87902d648e5d79b93792174219dcc2b1aa4d5f8e2d8c165964bf9942f70ce87f
5
+ SHA512:
6
+ metadata.gz: 6864357eddf64bb99813934c9984d35fa77a819c779eb109192ef18c414a70ec53cb9893f555277c102f82427be55464a41cd3ee2a07d0d4004ba3c98eaa06ef
7
+ data.tar.gz: a8cf2bc3c1193fa62067ccaf58fc18e469c8d939efa906933ee40502b050b1ddda87320b32625796026e2c3eabd66c41b0c8697682634da179c1b1af7da36af3
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) Comfortly Inc.
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.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Translated
2
+ Simple, automatic translations for your Rails app.
3
+
4
+ ## Usage
5
+ Translated can handle both plain text and rich text (using actiontext).
6
+
7
+ These helpers will let you work with the content attribute like you normally would. The return of this attribute will always be in the language specified by I18n.current_locale. So if it's set to :es, then you'll get Spanish. When you set the value it will save that for the current_locale and then create translations for every locale listed in the I18n.available_locales list.
8
+
9
+ For plain text, simply add this to your model:
10
+
11
+ ```ruby
12
+ class Message < ApplicationRecord
13
+ has_translated_text :content
14
+ end
15
+ ```
16
+
17
+ For rich text:
18
+
19
+ ```ruby
20
+ class Message < ApplicationRecord
21
+ has_translated_rich_text :content
22
+ end
23
+ ```
24
+
25
+ You can use this attribute just like any other actiontext attribute.
26
+
27
+ ## Installation
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem "translated"
32
+ ```
33
+
34
+ And then execute:
35
+ ```bash
36
+ $ bundle
37
+ ```
38
+
39
+ Install the migrations:
40
+ ```bash
41
+ $ bin/rails translated:install:migrations
42
+ ```
43
+
44
+ ## License
45
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Translated
4
+ class UpdateTranslationsJob < ApplicationJob
5
+ queue_as :default
6
+
7
+ def perform(translated_text_field)
8
+ translated_text_field.update_translations
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Translated
4
+ module Translatable
5
+ extend ActiveSupport::Concern
6
+
7
+ class_methods do
8
+ def has_translated_text_field(name, validates: {}) # rubocop:disable Naming/PredicateName
9
+ class_eval <<-CODE, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
10
+ def #{name}
11
+ translation = #{name}_translation || build_#{name}_translation
12
+ translation&.for_locale(I18n.locale)
13
+ end
14
+
15
+ def #{name}?
16
+ #{name}.present?
17
+ end
18
+
19
+ def #{name}=(content)
20
+ translation = self.#{name}_translation || build_#{name}_translation
21
+ translation.set_locale(I18n.locale, content)
22
+ @_#{name}_translation_changed = true
23
+ content
24
+ end
25
+
26
+ private
27
+
28
+ def #{name}_translation_changed?
29
+ defined?(:@_#{name}_translation_changed) && @_#{name}_translation_changed
30
+ end
31
+ CODE
32
+
33
+ has_one :"#{name}_translation",
34
+ -> { where(field: name.to_s) },
35
+ class_name: 'Translated::TranslatedTextField',
36
+ as: :translatable,
37
+ dependent: :destroy,
38
+ inverse_of: :translatable
39
+ after_save -> { public_send(:"#{name}_translation").save }, if: :"#{name}_translation_changed?"
40
+
41
+ validates name, validates if validates.present?
42
+
43
+ scope :with_translations, -> { includes(:translated_text_fields) }
44
+ end
45
+
46
+ def has_translated_rich_text(name) # rubocop:disable Naming/PredicateName
47
+ class_eval <<-CODE, __FILE__, __LINE__ + 1 # rubocop:disable Style/DocumentDynamicEvalDefinition
48
+ def #{name}
49
+ public_send(:"#{name}_\#{I18n.locale}")
50
+ end
51
+
52
+ def #{name}?
53
+ #{name}.present?
54
+ end
55
+
56
+ def #{name}=(body)
57
+ self.public_send(:"#{name}_\#{I18n.locale}=", body)
58
+
59
+ I18n.available_locales.each do |locale|
60
+ next if locale == I18n.locale
61
+
62
+ generate_translation_for_#{name}(I18n.locale, locale)
63
+ end
64
+
65
+ body
66
+ end
67
+
68
+ private
69
+
70
+ def generate_translation_for_#{name}(from, to)
71
+ self.public_send(:"#{name}_\#{to}=", Translator.new.translate(public_send(:"#{name}_\#{from}").body.to_html, from:, to:))
72
+ end
73
+ CODE
74
+
75
+ I18n.available_locales.each do |locale|
76
+ has_rich_text :"#{name}_#{locale}"
77
+ end
78
+
79
+ scope :"with_rich_text_#{name}", lambda {
80
+ includes(I18n.available_locales.map do |locale|
81
+ :"rich_text_#{name}_#{locale}"
82
+ end)
83
+ }
84
+ scope :"with_rich_text_#{name}_and_embeds", lambda {
85
+ includes(I18n.available_locales.to_h do |locale|
86
+ [:"rich_text_#{name}_#{locale}",
87
+ { embeds_attachments: :blob }]
88
+ end)
89
+ }
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Translated
4
+ class TranslatedTextField < ApplicationRecord
5
+ serialize :content, coder: JSON
6
+
7
+ belongs_to :translatable, polymorphic: true
8
+
9
+ validates :language, presence: true, inclusion: { in: I18n.available_locales.map(&:to_s) }
10
+
11
+ after_commit :update_translations_later, if: :needs_translations?
12
+
13
+ after_initialize do
14
+ self.content ||= {}
15
+ end
16
+
17
+ def for_locale(locale)
18
+ content.fetch(locale.to_s, nil)
19
+ end
20
+
21
+ def set_locale(locale, value)
22
+ locale = locale.to_s
23
+ self.language = locale
24
+
25
+ self.content ||= {}
26
+ self.content[locale] = value
27
+
28
+ @_needs_translations = true
29
+ value
30
+ end
31
+
32
+ def update_translations
33
+ source = content[language]
34
+
35
+ I18n.available_locales.each do |locale|
36
+ next if locale == language.to_sym
37
+
38
+ content[locale.to_s] = source.presence && Translator.new.translate(source, from: language, to: locale.to_s)
39
+ end
40
+
41
+ save!
42
+ end
43
+
44
+ private
45
+
46
+ def needs_translations?
47
+ defined?(:@_needs_translations) && @_needs_translations
48
+ end
49
+
50
+ def update_translations_later
51
+ UpdateTranslationsJob.perform_later(self)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rest-client'
4
+
5
+ module Translated
6
+ class Translator
7
+ API_HOST = ENV.fetch('TRANSLATOR_API_HOST', 'http://localhost:3000')
8
+
9
+ def translate(text, from:, to:)
10
+ response = RestClient.post(
11
+ "#{API_HOST}/translate",
12
+ { text: text, from: from, to: to }.to_json,
13
+ content_type: :json,
14
+ accept: :json
15
+ )
16
+ JSON.parse(response.body)['translated_text']
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ class CreateTranslatedTranslatedTextFields < ActiveRecord::Migration[7.2]
2
+ def change
3
+ create_table :translated_translated_text_fields do |t|
4
+ t.references :translatable, polymorphic: true, null: false
5
+ t.string :field
6
+ t.string :language
7
+ t.text :content
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :translated do
5
+ # # Task goes here
6
+ # end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/engine'
4
+
5
+ module Translated
6
+ class Engine < Rails::Engine
7
+ isolate_namespace Translated
8
+ config.autoload_once_paths = %W(
9
+ #{root}/app/jobs
10
+ #{root}/app/models
11
+ #{root}/app/models/concerns
12
+ )
13
+
14
+ initializer 'translated.translatable' do
15
+ ActiveSupport.on_load :active_record do
16
+ include Translated::Translatable
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Translated
4
+ VERSION = '0.1.0'
5
+ end
data/lib/translated.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'translated/version'
4
+ require 'translated/engine'
5
+
6
+ module Translated
7
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: translated
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Trae Robrock
8
+ - Andrew Katz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2024-05-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activejob
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '7.1'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '7.1'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activerecord
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '7.1'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '7.1'
42
+ - !ruby/object:Gem::Dependency
43
+ name: railties
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '7.1'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '7.1'
56
+ - !ruby/object:Gem::Dependency
57
+ name: activesupport
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '7.1'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '7.1'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rest-client
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 2.1.0
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 2.1.0
84
+ description: Simple, automatic translations for your Rails app.
85
+ email:
86
+ - trobrock@comfort.ly
87
+ - andrew@comfort.ly
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - MIT-LICENSE
93
+ - README.md
94
+ - Rakefile
95
+ - app/jobs/translated/update_translations_job.rb
96
+ - app/models/concerns/translated/translatable.rb
97
+ - app/models/translated/translated_text_field.rb
98
+ - app/models/translated/translator.rb
99
+ - db/migrate/202405031152_create_translated_translated_text_fields.rb
100
+ - lib/tasks/translated_tasks.rake
101
+ - lib/translated.rb
102
+ - lib/translated/engine.rb
103
+ - lib/translated/version.rb
104
+ homepage: https://github.com/getcomfortly/translated
105
+ licenses:
106
+ - MIT
107
+ metadata:
108
+ allowed_push_host: https://rubygems.org
109
+ homepage_uri: https://translatedrb.com
110
+ source_code_uri: https://github.com/getcomfortly/translated
111
+ rubygems_mfa_required: 'true'
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: 3.0.0
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 3.5.3
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Simple, automatic translations for your Rails app.
131
+ test_files: []