lit 0.4.0.pre.alpha.5 → 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
![travis status](https://travis-ci.org/prograils/lit.svg)
|
10
|
+
[![travis status](https://travis-ci.org/prograils/lit.svg)](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
|