lit 0.4.0.pre.alpha.5 → 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 +5 -5
- data/README.md +81 -5
- data/Rakefile +9 -15
- data/app/controllers/lit/cloud_translations_controller.rb +26 -0
- data/app/controllers/lit/localizations_controller.rb +2 -1
- data/app/helpers/lit/localizations_helper.rb +4 -0
- data/app/views/lit/cloud_translations/show.js.erb +14 -0
- data/app/views/lit/localizations/_form.html.erb +27 -0
- data/config/routes.rb +2 -0
- data/lib/lit/cloud_translation.rb +53 -0
- data/lib/lit/cloud_translation/providers/base.rb +66 -0
- data/lib/lit/cloud_translation/providers/google.rb +115 -0
- data/lib/lit/cloud_translation/providers/yandex.rb +82 -0
- data/lib/lit/import.rb +0 -2
- data/lib/lit/loader.rb +2 -1
- data/lib/lit/version.rb +1 -1
- metadata +107 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ab6fb4274b54171f64b3e24002ee6f2d23645ffa88cb9fb6d9bfe919b95d27bc
|
4
|
+
data.tar.gz: 52a3826a8da2dae3c37c08383ebd58e2547a7214e6b2eae4b83f22cfd027c7c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: becbc8639368bad9748d0815adf579031a6beb4f79610ae9920f896e5a6d9ae6c39bd071363756d1f937578b75745eed80b4c80805caf58e221f1cdee630481d
|
7
|
+
data.tar.gz: 01a66a00855eba89bbfe2057d8792d978e1435ea3a79e986463d0948d771304a96e9ebaf048000a2f2e748a529d5f56b931a04d6413d942ef327fc09bfe8097c
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@ translating app by non-technicals.
|
|
7
7
|
|
8
8
|
Highly inspired by Copycopter by thoughtbot.
|
9
9
|
|
10
|
-

|
10
|
+
[](https://travis-ci.org/prograils/lit)
|
11
11
|
|
12
12
|
### Features
|
13
13
|
|
@@ -39,7 +39,7 @@ gem 'lit'
|
|
39
39
|
|
40
40
|
3. run installation generator `bundle exec rails g lit:install`
|
41
41
|
(for production/staging environment `redis` is suggested as key value engine. `hash` will not work in multi process environment)
|
42
|
-
|
42
|
+
|
43
43
|
4. Add `config.i18n.available_locales = [...]` to `application.rb` - it's required to precompile appropriate language flags in lit backend.
|
44
44
|
|
45
45
|
5. After doing above and restarting app, point your browser to `http://app/lit`
|
@@ -107,6 +107,79 @@ Keys marked as deleted (i.e. still existing but deleted from the Lit UI) are *no
|
|
107
107
|
|
108
108
|
Deleted keys whose translations are encountered during import are restored automatically.
|
109
109
|
|
110
|
+
### Cloud translation services
|
111
|
+
|
112
|
+
Lit can use external translation services such as [Google Cloud Translation API](https://cloud.google.com/translate/) and [Yandex.Translate API](https://translate.yandex.com/developers) to tentatively translate localizations to a given language.
|
113
|
+
Currently, Google and Yandex translation providers are supported, but extending it to any other translation provider of your choice is as easy as subclassing `Lit::CloudTranslation::Providers::Base`; see classes in `lib/lit/cloud_translation/providers` for reference.
|
114
|
+
|
115
|
+
#### Usage
|
116
|
+
|
117
|
+
Configure your translation provider using one of routines described below. When a translation provider is configured, each localization in Lit web UI will have a "Translate using _Provider Name_" button next to it, which by default translates to the localization's language from the localization currently saved for the app's `I18n.default_locale`.
|
118
|
+
Next to the button, there is a dropdown that allows translating from the key's localization in a language different than the default one.
|
119
|
+
|
120
|
+
#### Google Cloud Translation API
|
121
|
+
|
122
|
+
Insert this into your Lit initializer:
|
123
|
+
```
|
124
|
+
require 'lit/cloud_translation/providers/google'
|
125
|
+
|
126
|
+
Lit::CloudTranslation.provider = Lit::CloudTranslation::Providers::Google
|
127
|
+
```
|
128
|
+
|
129
|
+
...and make sure you have this in your Gemfile:
|
130
|
+
```
|
131
|
+
gem 'google-cloud-translate'
|
132
|
+
```
|
133
|
+
|
134
|
+
To use translation via Google, you need to obtain a [service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys) containing all the credentials required by the API.
|
135
|
+
|
136
|
+
These credentials can be given in three ways:
|
137
|
+
* via a `.json` keyfile, the path to which should be stored in the `GOOGLE_TRANSLATE_API_KEYFILE` environment variable,
|
138
|
+
* programmatically, in the initializer - be sure to use secrets in all the sensitive fields so you don't expose private credentials in the code:
|
139
|
+
```
|
140
|
+
Lit::CloudTranslation.configure do |config|
|
141
|
+
config.keyfile_hash = {
|
142
|
+
'type' => 'service_account',
|
143
|
+
'project_id' => 'foo',
|
144
|
+
'private_key_id' => 'keyid',
|
145
|
+
... # see Google docs link above for reference
|
146
|
+
}
|
147
|
+
end
|
148
|
+
```
|
149
|
+
* directly via `GOOGLE_TRANSLATE_API_<element>` environment variables, where e.g. the `GOOGLE_TRANSLATE_API_PROJECT_ID` variable corresponds to the `project_id` element of a JSON keyfile. Typically, only the following variables are mandatory:
|
150
|
+
* `GOOGLE_TRANSLATE_API_PROJECT_ID`
|
151
|
+
* `GOOGLE_TRANSLATE_API_PRIVATE_KEY` (make sure that it contains correct line breaks and markers of the private key's begin and end)
|
152
|
+
* `GOOGLE_TRANSLATE_API_CLIENT_EMAIL`
|
153
|
+
|
154
|
+
#### Yandex.Translate API
|
155
|
+
|
156
|
+
Insert this into your Lit initializer:
|
157
|
+
```
|
158
|
+
require 'lit/cloud_translation/providers/yandex'
|
159
|
+
|
160
|
+
Lit::CloudTranslation.provider = Lit::CloudTranslation::Providers::Yandex
|
161
|
+
```
|
162
|
+
|
163
|
+
To use Yandex translation, an [API key must be obtained](https://translate.yandex.com/developers/keys). Then, you can pass it to your application via the `YANDEX_TRANSLATE_API_KEY` environment variable.
|
164
|
+
|
165
|
+
The API key can also be set programmatically in your Lit initializer (again, be sure to use secrets if you choose to do so):
|
166
|
+
```
|
167
|
+
Lit::CloudTranslation.configure do |config|
|
168
|
+
config.api_key = 'the_api_key'
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
### 0.3 -> 1.0 upgrade guide
|
173
|
+
|
174
|
+
Also applies to upgrading from `0.4.pre.alpha` versions.
|
175
|
+
|
176
|
+
1. Specify `gem 'lit', '~> 1.0'` in your Gemfile and run `bundle update lit`.
|
177
|
+
2. Run Lit migrations - `rails db:migrate`.
|
178
|
+
* __Caution:__ One of the new migrations adds a unique index in `lit_localizations` on `(localization_key_id, locale_id)`, which may cause constraint violations in some cases. If you encounter such errors during running this migration - in this case you'll need to enter Rails console and remove duplicates manually. The following query might be helpful to determine duplicate locale/localization key ID pairs:
|
179
|
+
```
|
180
|
+
Lit::Localization.group(:locale_id, :localization_key_id).having('count(*) > 1').count
|
181
|
+
```
|
182
|
+
|
110
183
|
### 0.2 -> 0.3 upgrade guide
|
111
184
|
|
112
185
|
1. Specify exact lit version in your Gemfile: `gem 'lit', '~> 0.3.0'`
|
@@ -197,10 +270,13 @@ Lit.store_request_info = true
|
|
197
270
|
|
198
271
|
### Testing
|
199
272
|
|
200
|
-
|
201
|
-
|
202
|
-
|
273
|
+
1. `gem install bundler && bundle install` - ensure Bundler and all required gems are installed
|
274
|
+
2. `bundle exec appraisal install` - install gems from appraisal's gemfiles
|
275
|
+
3. `cp test/dummy/config/database.yml.sample test/dummy/config/database.yml` - move a database.yml in place (remember to fill your DB credentials in it)
|
276
|
+
4. `RAILS_ENV=test appraisal rails-5.2 rake db:setup` - setup lit DB (see test/config/database.yml); do it only once, it does not matter which Rails version you use for `appraisal`
|
277
|
+
5. `bundle exec appraisal rake` - run the tests!
|
203
278
|
|
204
279
|
### License
|
205
280
|
|
206
281
|
Lit is free software, and may be redistributed under the terms specified in the MIT-LICENSE file.
|
282
|
+
|
data/Rakefile
CHANGED
@@ -1,10 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
rescue LoadError
|
6
|
-
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
7
|
-
end
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
APP_RAKEFILE = File.expand_path('../test/dummy/Rakefile', __FILE__)
|
4
|
+
load 'rails/tasks/engine.rake'
|
8
5
|
|
9
6
|
begin
|
10
7
|
require 'rdoc/task'
|
@@ -22,17 +19,14 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
22
19
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
20
|
end
|
24
21
|
|
25
|
-
|
26
|
-
load 'rails/tasks/engine.rake'
|
27
|
-
|
28
|
-
Bundler::GemHelper.install_tasks
|
29
|
-
|
30
|
-
require 'rake/testtask'
|
31
|
-
|
22
|
+
require "rake/testtask"
|
32
23
|
Rake::TestTask.new(:test) do |t|
|
33
24
|
puts "Storage: #{ENV['LIT_STORAGE'] || 'redis'}"
|
34
|
-
t.libs << 'lib'
|
35
25
|
t.libs << 'test'
|
26
|
+
t.libs << 'lib'
|
36
27
|
t.pattern = 'test/**/*_test.rb'
|
37
28
|
t.verbose = false
|
38
29
|
end
|
30
|
+
|
31
|
+
task :default => :test
|
32
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lit
|
4
|
+
class CloudTranslationsController < ::Lit::ApplicationController
|
5
|
+
def show
|
6
|
+
params.delete(:from) if params[:from] == 'auto'
|
7
|
+
@target_localization = Localization.find(params[:localization_id])
|
8
|
+
@localization_key = @target_localization.localization_key
|
9
|
+
if params[:from]
|
10
|
+
@localization = @localization_key.localizations.joins(:locale)
|
11
|
+
.find_by!(lit_locales: { locale: params[:from] })
|
12
|
+
end
|
13
|
+
opts =
|
14
|
+
{
|
15
|
+
# if :from was auto, translate from the target localization's
|
16
|
+
# current text itself
|
17
|
+
text: (@localization || @target_localization).value,
|
18
|
+
from: params[:from],
|
19
|
+
to: @target_localization.locale.locale
|
20
|
+
}.compact
|
21
|
+
@translated_text = Lit::CloudTranslation.translate(opts)
|
22
|
+
rescue Lit::CloudTranslation::TranslationError => e
|
23
|
+
@error_message = "Translation failed. #{e.message}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -12,5 +12,9 @@ module Lit
|
|
12
12
|
def allow_wysiwyg_editor?(key)
|
13
13
|
Lit.all_translations_are_html_safe || key.to_s =~ /(\b|_|\.)html$/
|
14
14
|
end
|
15
|
+
|
16
|
+
def available_locales_with_default_first
|
17
|
+
I18n.available_locales.sort_by { |l| l == I18n.default_locale ? 0 : 1 }
|
18
|
+
end
|
15
19
|
end
|
16
20
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<% if @translated_text.is_a?(String) %>
|
2
|
+
$('.localization_row[data-id="<%= @target_localization.id %>"] textarea').val("<%= j @translated_text %>");
|
3
|
+
<% elsif @translated_text.is_a?(Array) %>
|
4
|
+
$inputs = $('.localization_row[data-id="<%= @target_localization.id %>"] input[type=text]');
|
5
|
+
var translated_elements = JSON.parse("<%= j @translated_text.to_json.html_safe %>");
|
6
|
+
for (i in $inputs.toArray()) {
|
7
|
+
$input = $($inputs[i]);
|
8
|
+
$input.val(translated_elements[i]);
|
9
|
+
}
|
10
|
+
<% elsif @error_message.present? %>
|
11
|
+
alert("<%= j @error_message %>")
|
12
|
+
<% else %>
|
13
|
+
alert("An unknown error has occurred while translating.")
|
14
|
+
<% end %>
|
@@ -19,5 +19,32 @@
|
|
19
19
|
|
20
20
|
<button class="btn btn-primary btn-sm" type="submit"><%= I18n.t('lit.common.update', default: "Update") %></button>
|
21
21
|
<button class="btn btn-default btn-sm cancel" type="reset"><%= I18n.t('lit.common.cancel', default: 'Cancel') %></button>
|
22
|
+
|
23
|
+
<% if Lit::CloudTranslation.provider %>
|
24
|
+
<div class="btn-group pull-right">
|
25
|
+
<%= link_to "Translate using #{Lit::CloudTranslation.provider.name.demodulize}", cloud_translation_path(from: I18n.default_locale, localization_id: @localization.id), remote: true, class: 'btn btn-sm btn-default' %>
|
26
|
+
<button class="btn btn-sm dropdown-toggle" data-toggle="dropdown">
|
27
|
+
<span class="caret"></span>
|
28
|
+
<span class="sr-only">Source languages</span>
|
29
|
+
</button>
|
30
|
+
<ul class="dropdown-menu">
|
31
|
+
<li>
|
32
|
+
<a>Translate from:</a>
|
33
|
+
</li>
|
34
|
+
<li role="separator" class="divider"></li>
|
35
|
+
<% (available_locales_with_default_first + [:auto]).each_with_index do |source_locale, i| %>
|
36
|
+
<li>
|
37
|
+
<%= link_to cloud_translation_path(from: source_locale, localization_id: @localization.id), remote: true do %>
|
38
|
+
<% if i == 0 %>
|
39
|
+
<strong><%= source_locale.to_s %></strong>
|
40
|
+
<% else %>
|
41
|
+
<%= source_locale.to_s %>
|
42
|
+
<% end %>
|
43
|
+
<% end %>
|
44
|
+
</li>
|
45
|
+
<% end %>
|
46
|
+
</ul>
|
47
|
+
</div>
|
48
|
+
<% end %>
|
22
49
|
<% end %>
|
23
50
|
|
data/config/routes.rb
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lit
|
4
|
+
module CloudTranslation
|
5
|
+
class TranslationError < StandardError; end
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Sets the provider for cloud translations.
|
10
|
+
# @param [Class] provider Selected translation provider,
|
11
|
+
# descending from Lit::CloudTranslation::Providers::Base
|
12
|
+
def provider=(provider)
|
13
|
+
@provider = provider
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the currently active cloud translation provider,
|
17
|
+
# descending from Lit::CloudTranslation::Providers::Base.
|
18
|
+
def provider
|
19
|
+
@provider
|
20
|
+
end
|
21
|
+
|
22
|
+
# Uses the active translation provider to translate a text or array of
|
23
|
+
# texts.
|
24
|
+
# @param [String, Array] text The text (or array of texts) to translate
|
25
|
+
# @param [Symbol, String] from The language to translate from. If not given,
|
26
|
+
# auto-detection will be attempted.
|
27
|
+
# @param [Symbol, String] to The language to translate to.
|
28
|
+
# @param [Hash] opts Additional, provider-specific optional parameters.
|
29
|
+
def translate(text:, from: nil, to:, **opts)
|
30
|
+
provider.translate(text: text, from: from, to: to, **opts)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Optional if provider-speciffic environment variables are set correctly.
|
34
|
+
# Configures the cloud translation provider with specific settings,
|
35
|
+
# overriding those from environment if needed.
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
# Lit::CloudTranslation.configure do |config|
|
39
|
+
# # For Yandex, this overrides the YANDEX_TRANSLATE_API_KEY env
|
40
|
+
# config.api_key = 'my_awesome_api_key'
|
41
|
+
# end
|
42
|
+
def configure(&block)
|
43
|
+
unless provider
|
44
|
+
raise 'Translation provider not selected yet. Use `Lit::CloudTranslation' \
|
45
|
+
'.provider = PROVIDER_KLASS` before calling #configure.'
|
46
|
+
end
|
47
|
+
provider.tap do |p|
|
48
|
+
p.configure(&block)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end # descending from Lit::CloudTranslation::Providers::Base.
|
53
|
+
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Lit::CloudTranslation::Providers
|
4
|
+
# Abstract base class for cloud translation providers, providing a skeleton
|
5
|
+
# for the provider's functionality (mainly the #translate method) as well as
|
6
|
+
# a configuration management mechanism.
|
7
|
+
class Base
|
8
|
+
attr_reader :config
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
default_config.each do |key, value|
|
12
|
+
config[key] ||= value
|
13
|
+
end
|
14
|
+
@config = config
|
15
|
+
end
|
16
|
+
|
17
|
+
# Translates a given text from a given language to a different one.
|
18
|
+
# @param [String, Array] text The text (or array of texts) to translate
|
19
|
+
# @param [Symbol, String] from The language to translate from. If not given,
|
20
|
+
# auto-detection will be attempted.
|
21
|
+
# @param [Symbol, String] to The language to translate to.
|
22
|
+
# @param [Hash] opts Additional, provider-specific optional parameters.
|
23
|
+
def translate(text:, from: nil, to:, **opts) # rubocop:disable Lint/UnusedMethodArgument, Metrics/LineLength
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
# Loads specific information from environment variables or other sources
|
30
|
+
# as the default configuartion for the translation provider.
|
31
|
+
#
|
32
|
+
# This can be overridden using `Lit::CloudTranslation.configure`.
|
33
|
+
def default_config
|
34
|
+
{}
|
35
|
+
end
|
36
|
+
|
37
|
+
private_class_method :new
|
38
|
+
|
39
|
+
class << self
|
40
|
+
# Using the provider object's singleton instance, translates a given text from
|
41
|
+
# a given language to a different one.
|
42
|
+
# @param [String, Array] text The text (or array of texts) to translate
|
43
|
+
# @param [Symbol, String] from The language to translate from. If not given,
|
44
|
+
# auto-detection will be attempted.
|
45
|
+
# @param [Symbol, String] to The language to translate to.
|
46
|
+
# @param [Hash] opts Additional, provider-specific optional parameters.
|
47
|
+
def translate(text:, from: nil, to:, **opts)
|
48
|
+
instance.translate(text: text, from: from, to: to, **opts)
|
49
|
+
end
|
50
|
+
|
51
|
+
def configure
|
52
|
+
yield config if block_given?
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def instance
|
58
|
+
@instance ||= new(config)
|
59
|
+
end
|
60
|
+
|
61
|
+
def config
|
62
|
+
@config ||= OpenStruct.new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
begin
|
5
|
+
require 'google/cloud/translate'
|
6
|
+
rescue LoadError
|
7
|
+
raise StandardError, 'You need to add "gem \'google-cloud-translate\'" to your Gemfile to support Google Cloud Translation'
|
8
|
+
end
|
9
|
+
|
10
|
+
module Lit::CloudTranslation::Providers
|
11
|
+
# Google Cloud Translation API provider for Lit translation suggestions.
|
12
|
+
#
|
13
|
+
# Configuration:
|
14
|
+
#
|
15
|
+
# require 'lit/cloud_translation/providers/google'
|
16
|
+
#
|
17
|
+
# Lit::CloudTranslation.provider = Lit::CloudTranslation::Providers::Google
|
18
|
+
#
|
19
|
+
# # Service account configuration can be given via a file pointed to by
|
20
|
+
# # ENV['GOOGLE_TRANSLATE_API_KEYFILE'] (see here:
|
21
|
+
# # https://cloud.google.com/iam/docs/creating-managing-service-account-keys)
|
22
|
+
# #
|
23
|
+
# # Instead of providing the keyfile, credentials can be given using
|
24
|
+
# # GOOGLE_TRANSLATE_API_<element> environment variables, where e.g.
|
25
|
+
# # the GOOGLE_TRANSLATE_API_PROJECT_ID variable corresponds to the
|
26
|
+
# # `project_id` element of your credentials. Typically, only the following
|
27
|
+
# # variables are mandatory:
|
28
|
+
# # * GOOGLE_TRANSLATE_API_PROJECT_ID
|
29
|
+
# # * GOOGLE_TRANSLATE_API_PRIVATE_KEY_ID
|
30
|
+
# # * GOOGLE_TRANSLATE_API_PRIVATE_KEY (be sure that it contains correct line breaks)
|
31
|
+
# # * GOOGLE_TRANSLATE_API_CLIENT_EMAIL
|
32
|
+
# # * GOOGLE_TRANSLATE_API_CLIENT_ID
|
33
|
+
# #
|
34
|
+
# # Alternatively, the contents of that file can be given as a Ruby hash
|
35
|
+
# # and passed like the following (be careful to use secrets or something
|
36
|
+
# # that prevents exposing private credentials):
|
37
|
+
#
|
38
|
+
# Lit::CloudTranslation.configure do |config|
|
39
|
+
# config.keyfile_hash = {
|
40
|
+
# 'type' => 'service_account',
|
41
|
+
# 'project_id' => 'foo',
|
42
|
+
# 'private_key_id' => 'keyid',
|
43
|
+
# ... # see link above for reference
|
44
|
+
# }
|
45
|
+
# end
|
46
|
+
class Google < Base
|
47
|
+
def translate(text:, from: nil, to:, **opts)
|
48
|
+
@client ||=
|
49
|
+
::Google::Cloud::Translate.new(project_id: config.keyfile_hash['project_id'],
|
50
|
+
credentials: config.keyfile_hash)
|
51
|
+
result = @client.translate(sanitize_text(text), from: from, to: to, **opts)
|
52
|
+
unsanitize_text(
|
53
|
+
case result
|
54
|
+
when ::Google::Cloud::Translate::Translation then result.text
|
55
|
+
when Array then result.map(&:text)
|
56
|
+
end
|
57
|
+
)
|
58
|
+
rescue Signet::AuthorizationError => e
|
59
|
+
error_description =
|
60
|
+
'Google credentials error: ' + # rubocop:disable Style/RescueModifier
|
61
|
+
JSON.parse(e.response.body)['error_description'] rescue 'Unknown error'
|
62
|
+
raise ::Lit::CloudTranslation::TranslationError, error_description,
|
63
|
+
cause: e
|
64
|
+
rescue ::Google::Cloud::Error => e
|
65
|
+
raise ::Lit::CloudTranslation::TranslationError, e.message, cause: e
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def default_config
|
71
|
+
if ENV['GOOGLE_TRANSLATE_API_KEYFILE'].blank?
|
72
|
+
env_keys = ENV.keys.grep(/\AGOOGLE_TRANSLATE_API_/)
|
73
|
+
keyfile_keys = env_keys.map(&:downcase).map { |k| k.gsub('google_translate_api_', '') }
|
74
|
+
keyfile_key_to_env_key_mapping = keyfile_keys.zip(env_keys).to_h
|
75
|
+
return {
|
76
|
+
keyfile_hash: keyfile_key_to_env_key_mapping.transform_values do |env_key|
|
77
|
+
ENV[env_key]
|
78
|
+
end
|
79
|
+
}
|
80
|
+
end
|
81
|
+
{ keyfile_hash: JSON.parse(File.read(ENV['GOOGLE_TRANSLATE_API_KEYFILE'])) }
|
82
|
+
rescue JSON::ParserError
|
83
|
+
raise
|
84
|
+
rescue Errno::ENOENT
|
85
|
+
{ keyfile_hash: nil }
|
86
|
+
end
|
87
|
+
|
88
|
+
def require_config!
|
89
|
+
return if config.keyfile_hash.present?
|
90
|
+
raise 'GOOGLE_TRANSLATE_API_KEYFILE env or `config.keyfile_hash` not given'
|
91
|
+
end
|
92
|
+
|
93
|
+
def sanitize_text(text_or_array)
|
94
|
+
case text_or_array
|
95
|
+
when String
|
96
|
+
text_or_array.gsub(/%{(.+?)}/, '<code>__LIT__\1__LIT__</code>')
|
97
|
+
when Array
|
98
|
+
text_or_array.map { |s| sanitize_text(s) }
|
99
|
+
else
|
100
|
+
raise TypeError
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def unsanitize_text(text_or_array)
|
105
|
+
case text_or_array
|
106
|
+
when String
|
107
|
+
text_or_array.gsub(/<code>__LIT__(.+?)__LIT__<\/code>/, '%{\1}')
|
108
|
+
when Array
|
109
|
+
text_or_array.map { |s| unsanitize_text(s) }
|
110
|
+
else
|
111
|
+
raise TypeError
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'net/http'
|
5
|
+
|
6
|
+
module Lit::CloudTranslation::Providers
|
7
|
+
# Yandex Translate API provider for Lit translation suggestions.
|
8
|
+
#
|
9
|
+
# Configuration:
|
10
|
+
#
|
11
|
+
# require 'lit/cloud_translation/providers/yandex'
|
12
|
+
#
|
13
|
+
# Lit::CloudTranslation.provider = Lit::CloudTranslation::Providers::Yandeex
|
14
|
+
#
|
15
|
+
# # API key can be given via ENV['YANDEX_TRANSLATE_API_KEY'].
|
16
|
+
# #
|
17
|
+
# # Alternatively, it can be set programmatically after setting provider:
|
18
|
+
#
|
19
|
+
# Lit::CloudTranslation.configure do |config|
|
20
|
+
# config.api_key = 'the_api_key'
|
21
|
+
# end
|
22
|
+
class Yandex < Base
|
23
|
+
def translate(text:, from: nil, to:, **opts) # rubocop:disable Metrics/MethodLength, Metrics/LineLength
|
24
|
+
# puts "api key is: #{config.api_key}"
|
25
|
+
# puts "translating #{text} from #{from} to #{to}"
|
26
|
+
uri = URI('https://translate.yandex.net/api/v1.5/tr.json/translate')
|
27
|
+
params = {
|
28
|
+
key: config.api_key,
|
29
|
+
text: sanitize_text(text),
|
30
|
+
lang: [from, to].compact.join('-'),
|
31
|
+
format: opts[:format],
|
32
|
+
options: opts[:options]
|
33
|
+
}.compact
|
34
|
+
uri.query = URI.encode_www_form(params)
|
35
|
+
res = Net::HTTP.get_response(uri)
|
36
|
+
|
37
|
+
unsanitize_text(
|
38
|
+
case res
|
39
|
+
when Net::HTTPOK
|
40
|
+
translations = JSON.parse(res.body)['text']
|
41
|
+
translations.size == 1 ? translations.first : translations
|
42
|
+
else
|
43
|
+
raise ::Lit::CloudTranslation::TranslationError,
|
44
|
+
(JSON.parse(res.body)['message'] rescue "Unknown error: #{res.body}") # rubocop:disable Style/RescueModifier, Metrics/LineLength
|
45
|
+
end
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def default_config
|
52
|
+
{ api_key: ENV['YANDEX_TRANSLATE_API_KEY'] }
|
53
|
+
end
|
54
|
+
|
55
|
+
def require_config!
|
56
|
+
return if config.api_key.present?
|
57
|
+
raise 'YANDEX_TRANSLATE_API_KEY env or `config.api_key` not given'
|
58
|
+
end
|
59
|
+
|
60
|
+
def sanitize_text(text_or_array)
|
61
|
+
case text_or_array
|
62
|
+
when String
|
63
|
+
text_or_array.gsub(/%{(.+?)}/, '%{_LIT_\1_LIT_}')
|
64
|
+
when Array
|
65
|
+
text_or_array.map { |s| sanitize_text(s) }
|
66
|
+
else
|
67
|
+
raise TypeError
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def unsanitize_text(text_or_array)
|
72
|
+
case text_or_array
|
73
|
+
when String
|
74
|
+
text_or_array.gsub(/%{_LIT_(.+?)_LIT_}/, '%{\1}')
|
75
|
+
when Array
|
76
|
+
text_or_array.map { |s| unsanitize_text(s) }
|
77
|
+
else
|
78
|
+
raise TypeError
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/lit/import.rb
CHANGED
@@ -33,7 +33,6 @@ module Lit
|
|
33
33
|
yml = parsed_yaml[locale.to_s]
|
34
34
|
Hash[*Lit::Cache.flatten_hash(yml)].each do |key, default_translation|
|
35
35
|
next if default_translation.nil? && skip_nil
|
36
|
-
puts key
|
37
36
|
upsert(locale, key, default_translation)
|
38
37
|
end
|
39
38
|
end
|
@@ -52,7 +51,6 @@ module Lit
|
|
52
51
|
row_translations.each do |locale, value|
|
53
52
|
next unless locale_keys.blank? || locale_keys.map(&:to_sym).include?(locale.to_sym)
|
54
53
|
next if value.nil? && skip_nil
|
55
|
-
puts key
|
56
54
|
upsert(locale, key, value)
|
57
55
|
end
|
58
56
|
end
|
data/lib/lit/loader.rb
CHANGED
@@ -3,6 +3,7 @@ require 'lit/i18n_backend'
|
|
3
3
|
require 'lit/cache'
|
4
4
|
require 'lit/export'
|
5
5
|
require 'lit/import'
|
6
|
+
require 'lit/cloud_translation'
|
6
7
|
|
7
8
|
module Lit
|
8
9
|
class Loader
|
@@ -10,7 +11,7 @@ module Lit
|
|
10
11
|
attr_accessor :logger
|
11
12
|
def initialize
|
12
13
|
self.logger ||= Logger.new($stdout)
|
13
|
-
self.logger.info 'initializing Lit'
|
14
|
+
self.logger.info 'initializing Lit' unless ::Rails.env.test?
|
14
15
|
self.cache ||= Cache.new
|
15
16
|
I18n.backend = I18nBackend.new(self.cache)
|
16
17
|
end
|
data/lib/lit/version.rb
CHANGED
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: '1.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maciej Litwiniuk
|
8
8
|
- Piotr Boniecki
|
9
9
|
- Michał Buszkiewicz
|
10
|
+
- Szymon Soppa
|
10
11
|
autorequire:
|
11
12
|
bindir: bin
|
12
13
|
cert_chain: []
|
13
|
-
date:
|
14
|
+
date: 2019-01-09 00:00:00.000000000 Z
|
14
15
|
dependencies:
|
15
16
|
- !ruby/object:Gem::Dependency
|
16
17
|
name: rails
|
@@ -55,47 +56,131 @@ dependencies:
|
|
55
56
|
- !ruby/object:Gem::Version
|
56
57
|
version: '0'
|
57
58
|
- !ruby/object:Gem::Dependency
|
58
|
-
name:
|
59
|
+
name: sass-rails
|
59
60
|
requirement: !ruby/object:Gem::Requirement
|
60
61
|
requirements:
|
61
62
|
- - ">="
|
62
63
|
- !ruby/object:Gem::Version
|
63
64
|
version: '0'
|
64
|
-
type: :
|
65
|
+
type: :runtime
|
65
66
|
prerelease: false
|
66
67
|
version_requirements: !ruby/object:Gem::Requirement
|
67
68
|
requirements:
|
68
69
|
- - ">="
|
69
70
|
- !ruby/object:Gem::Version
|
70
71
|
version: '0'
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: appraisal
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - "~>"
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: 2.2.0
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - "~>"
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.2.0
|
71
86
|
- !ruby/object:Gem::Dependency
|
72
87
|
name: devise
|
73
88
|
requirement: !ruby/object:Gem::Requirement
|
74
89
|
requirements:
|
75
|
-
- - "
|
90
|
+
- - "~>"
|
76
91
|
- !ruby/object:Gem::Version
|
77
|
-
version:
|
92
|
+
version: 4.5.0
|
78
93
|
type: :development
|
79
94
|
prerelease: false
|
80
95
|
version_requirements: !ruby/object:Gem::Requirement
|
81
96
|
requirements:
|
82
|
-
- - "
|
97
|
+
- - "~>"
|
83
98
|
- !ruby/object:Gem::Version
|
84
|
-
version:
|
99
|
+
version: 4.5.0
|
85
100
|
- !ruby/object:Gem::Dependency
|
86
|
-
name:
|
101
|
+
name: google-cloud-translate
|
87
102
|
requirement: !ruby/object:Gem::Requirement
|
88
103
|
requirements:
|
89
|
-
- - "
|
104
|
+
- - "~>"
|
90
105
|
- !ruby/object:Gem::Version
|
91
|
-
version:
|
106
|
+
version: 1.2.4
|
92
107
|
type: :development
|
93
108
|
prerelease: false
|
94
109
|
version_requirements: !ruby/object:Gem::Requirement
|
95
110
|
requirements:
|
96
|
-
- - "
|
111
|
+
- - "~>"
|
97
112
|
- !ruby/object:Gem::Version
|
98
|
-
version:
|
113
|
+
version: 1.2.4
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: minitest
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - "~>"
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 5.11.3
|
121
|
+
type: :development
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - "~>"
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: 5.11.3
|
128
|
+
- !ruby/object:Gem::Dependency
|
129
|
+
name: minitest-vcr
|
130
|
+
requirement: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - "~>"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: 1.4.0
|
135
|
+
type: :development
|
136
|
+
prerelease: false
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - "~>"
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 1.4.0
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: pry-byebug
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - "~>"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: 3.6.0
|
149
|
+
type: :development
|
150
|
+
prerelease: false
|
151
|
+
version_requirements: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - "~>"
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: 3.6.0
|
156
|
+
- !ruby/object:Gem::Dependency
|
157
|
+
name: vcr
|
158
|
+
requirement: !ruby/object:Gem::Requirement
|
159
|
+
requirements:
|
160
|
+
- - "~>"
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 4.0.0
|
163
|
+
type: :development
|
164
|
+
prerelease: false
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - "~>"
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: 4.0.0
|
170
|
+
- !ruby/object:Gem::Dependency
|
171
|
+
name: webmock
|
172
|
+
requirement: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - "~>"
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: 3.4.2
|
177
|
+
type: :development
|
178
|
+
prerelease: false
|
179
|
+
version_requirements: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - "~>"
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: 3.4.2
|
99
184
|
description: "Translate your apps with pleasure (sort of...) and for free.\n It's
|
100
185
|
simple i18n web interface, build on top of twitter\n bootstrap,
|
101
186
|
that one may find helpful in translating app by\n non-technicals. "
|
@@ -375,6 +460,7 @@ files:
|
|
375
460
|
- app/controllers/lit/api/v1/localization_keys_controller.rb
|
376
461
|
- app/controllers/lit/api/v1/localizations_controller.rb
|
377
462
|
- app/controllers/lit/application_controller.rb
|
463
|
+
- app/controllers/lit/cloud_translations_controller.rb
|
378
464
|
- app/controllers/lit/concerns/request_info_store.rb
|
379
465
|
- app/controllers/lit/concerns/request_keys_store.rb
|
380
466
|
- app/controllers/lit/dashboard_controller.rb
|
@@ -410,6 +496,7 @@ files:
|
|
410
496
|
- app/views/layouts/lit/_messages.html.erb
|
411
497
|
- app/views/layouts/lit/_navigation.html.erb
|
412
498
|
- app/views/layouts/lit/application.html.erb
|
499
|
+
- app/views/lit/cloud_translations/show.js.erb
|
413
500
|
- app/views/lit/dashboard/index.html.erb
|
414
501
|
- app/views/lit/incomming_localizations/accept.js.erb
|
415
502
|
- app/views/lit/incomming_localizations/destroy.js.erb
|
@@ -458,6 +545,10 @@ files:
|
|
458
545
|
- lib/lit/adapters/hash_storage.rb
|
459
546
|
- lib/lit/adapters/redis_storage.rb
|
460
547
|
- lib/lit/cache.rb
|
548
|
+
- lib/lit/cloud_translation.rb
|
549
|
+
- lib/lit/cloud_translation/providers/base.rb
|
550
|
+
- lib/lit/cloud_translation/providers/google.rb
|
551
|
+
- lib/lit/cloud_translation/providers/yandex.rb
|
461
552
|
- lib/lit/engine.rb
|
462
553
|
- lib/lit/export.rb
|
463
554
|
- lib/lit/i18n_backend.rb
|
@@ -482,12 +573,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
482
573
|
version: '0'
|
483
574
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
484
575
|
requirements:
|
485
|
-
- - "
|
576
|
+
- - ">="
|
486
577
|
- !ruby/object:Gem::Version
|
487
|
-
version:
|
578
|
+
version: '0'
|
488
579
|
requirements: []
|
489
580
|
rubyforge_project:
|
490
|
-
rubygems_version: 2.
|
581
|
+
rubygems_version: 2.7.8
|
491
582
|
signing_key:
|
492
583
|
specification_version: 4
|
493
584
|
summary: Database powered i18n backend with web gui
|