translated 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []