lokalise_rails 0.0.2.3 → 1.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 +4 -4
- data/CHANGELOG.md +20 -5
- data/README.md +79 -27
- data/lib/generators/lokalise_rails/install_generator.rb +2 -2
- data/lib/generators/templates/lokalise_rails_config.rb +36 -25
- data/lib/lokalise_rails.rb +54 -19
- data/lib/lokalise_rails/railtie.rb +2 -5
- data/lib/lokalise_rails/task_definition/base.rb +50 -6
- data/lib/lokalise_rails/task_definition/exporter.rb +80 -0
- data/lib/lokalise_rails/task_definition/importer.rb +57 -15
- data/lib/lokalise_rails/version.rb +2 -2
- data/lib/tasks/lokalise_rails_tasks.rake +14 -0
- data/lokalise_rails.gemspec +1 -2
- data/spec/dummy/config/application.rb +4 -2
- data/spec/dummy/config/lokalise_rails.rb +7 -0
- data/spec/lib/generators/lokalise_rails/install_generator_spec.rb +1 -1
- data/spec/lib/lokalise_rails/task_definition/base_spec.rb +85 -0
- data/spec/lib/lokalise_rails/task_definition/exporter_spec.rb +166 -0
- data/spec/lib/lokalise_rails/task_definition/importer_spec.rb +49 -0
- data/spec/lib/lokalise_rails_spec.rb +44 -7
- data/spec/lib/tasks/export_task_spec.rb +7 -0
- data/spec/lib/tasks/import_task_spec.rb +46 -20
- data/spec/spec_helper.rb +8 -4
- data/spec/support/file_manager.rb +81 -0
- data/spec/support/rake_utils.rb +4 -0
- data/spec/support/spec_addons.rb +8 -0
- metadata +18 -36
- data/spec/dummy/config/initializers/lokalise_rails.rb +0 -29
- data/spec/lib/lokalise_rails/importer_spec.rb +0 -30
- data/spec/support/file_utils.rb +0 -27
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
module LokaliseRails
|
6
|
+
module TaskDefinition
|
7
|
+
class Exporter < Base
|
8
|
+
class << self
|
9
|
+
# Performs translation file export from Rails to Lokalise and returns an array of queued processes
|
10
|
+
#
|
11
|
+
# @return [Array]
|
12
|
+
def export!
|
13
|
+
errors = opt_errors
|
14
|
+
|
15
|
+
if errors.any?
|
16
|
+
errors.each { |e| $stdout.puts e }
|
17
|
+
return errors
|
18
|
+
end
|
19
|
+
|
20
|
+
queued_processes = []
|
21
|
+
each_file do |full_path, relative_path|
|
22
|
+
queued_processes << api_client.upload_file(
|
23
|
+
project_id_with_branch, opts(full_path, relative_path)
|
24
|
+
)
|
25
|
+
rescue StandardError => e
|
26
|
+
$stdout.puts "Error while trying to upload #{full_path}: #{e.inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
$stdout.print 'Task complete!'
|
30
|
+
|
31
|
+
queued_processes
|
32
|
+
end
|
33
|
+
|
34
|
+
# Processes each translation file in the specified directory
|
35
|
+
def each_file
|
36
|
+
return unless block_given?
|
37
|
+
|
38
|
+
loc_path = LokaliseRails.locales_path
|
39
|
+
Dir["#{loc_path}/**/*"].sort.each do |f|
|
40
|
+
full_path = Pathname.new f
|
41
|
+
|
42
|
+
next unless file_matches_criteria? full_path
|
43
|
+
|
44
|
+
relative_path = full_path.relative_path_from Pathname.new(loc_path)
|
45
|
+
|
46
|
+
yield full_path, relative_path
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Generates export options
|
51
|
+
#
|
52
|
+
# @return [Hash]
|
53
|
+
# @param full_p [Pathname]
|
54
|
+
# @param relative_p [Pathname]
|
55
|
+
def opts(full_p, relative_p)
|
56
|
+
content = File.read full_p
|
57
|
+
|
58
|
+
lang_iso = YAML.safe_load(content)&.keys&.first
|
59
|
+
|
60
|
+
initial_opts = {
|
61
|
+
data: Base64.strict_encode64(content.strip),
|
62
|
+
filename: relative_p,
|
63
|
+
lang_iso: lang_iso
|
64
|
+
}
|
65
|
+
|
66
|
+
initial_opts.merge LokaliseRails.export_opts
|
67
|
+
end
|
68
|
+
|
69
|
+
# Checks whether the specified file has to be processed or not
|
70
|
+
#
|
71
|
+
# @return [Boolean]
|
72
|
+
# @param full_path [Pathname]
|
73
|
+
def file_matches_criteria?(full_path)
|
74
|
+
full_path.file? && proper_ext?(full_path) &&
|
75
|
+
!LokaliseRails.skip_file_export.call(full_path)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -1,42 +1,84 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'zip'
|
4
|
+
require 'yaml'
|
5
|
+
require 'open-uri'
|
6
|
+
require 'fileutils'
|
7
|
+
|
8
|
+
module LokaliseRails
|
4
9
|
module TaskDefinition
|
5
10
|
class Importer < Base
|
6
11
|
class << self
|
12
|
+
# Performs translation files import from Lokalise to Rails app
|
13
|
+
#
|
14
|
+
# @return [Boolean]
|
7
15
|
def import!
|
8
|
-
|
9
|
-
|
10
|
-
|
16
|
+
errors = opt_errors
|
17
|
+
|
18
|
+
if errors.any?
|
19
|
+
errors.each { |e| $stdout.puts e }
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
|
23
|
+
unless proceed_when_safe_mode?
|
24
|
+
$stdout.print 'Task cancelled!'
|
25
|
+
return false
|
26
|
+
end
|
11
27
|
|
12
28
|
open_and_process_zip download_files['bundle_url']
|
13
29
|
|
14
|
-
'Task complete!'
|
30
|
+
$stdout.print 'Task complete!'
|
31
|
+
true
|
15
32
|
end
|
16
33
|
|
34
|
+
# Downloads files from Lokalise using the specified options
|
35
|
+
#
|
36
|
+
# @return [Hash]
|
17
37
|
def download_files
|
18
|
-
client = ::Lokalise.client LokaliseRails.api_token
|
19
38
|
opts = LokaliseRails.import_opts
|
20
39
|
|
21
|
-
|
40
|
+
api_client.download_files project_id_with_branch, opts
|
41
|
+
rescue StandardError => e
|
42
|
+
$stdout.puts "There was an error when trying to download files: #{e.inspect}"
|
22
43
|
end
|
23
44
|
|
45
|
+
# Opens ZIP archive (local or remote) with translations and processes its entries
|
46
|
+
#
|
47
|
+
# @param path [String]
|
24
48
|
def open_and_process_zip(path)
|
25
|
-
Zip::File.open_buffer(URI.open(path))
|
49
|
+
Zip::File.open_buffer(URI.open(path)) do |zip|
|
50
|
+
fetch_zip_entries(zip) { |entry| process!(entry) }
|
51
|
+
end
|
26
52
|
end
|
27
53
|
|
28
|
-
|
54
|
+
# Iterates over ZIP entries. Each entry may be a file or folder.
|
55
|
+
def fetch_zip_entries(zip)
|
56
|
+
return unless block_given?
|
57
|
+
|
29
58
|
zip.each do |entry|
|
30
|
-
next unless
|
59
|
+
next unless proper_ext? entry.name
|
60
|
+
|
61
|
+
yield entry
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Processes ZIP entry by reading its contents and creating the corresponding translation file
|
66
|
+
def process!(zip_entry)
|
67
|
+
data = YAML.safe_load zip_entry.get_input_stream.read
|
68
|
+
subdir, filename = subdir_and_filename_for zip_entry.name
|
69
|
+
full_path = "#{LokaliseRails.locales_path}/#{subdir}"
|
70
|
+
FileUtils.mkdir_p full_path
|
31
71
|
|
32
|
-
|
33
|
-
|
34
|
-
File.open("#{LokaliseRails.locales_path}/#{filename}", 'w+:UTF-8') do |f|
|
35
|
-
f.write(data.to_yaml)
|
36
|
-
end
|
72
|
+
File.open(File.join(full_path, filename), 'w+:UTF-8') do |f|
|
73
|
+
f.write data.to_yaml
|
37
74
|
end
|
75
|
+
rescue StandardError => e
|
76
|
+
$stdout.puts "Error when trying to process #{zip_entry&.name}: #{e.inspect}"
|
38
77
|
end
|
39
78
|
|
79
|
+
# Checks whether the user wishes to proceed when safe mode is enabled and the target directory is not empty
|
80
|
+
#
|
81
|
+
# @return [Boolean]
|
40
82
|
def proceed_when_safe_mode?
|
41
83
|
return true unless LokaliseRails.import_safe_mode && !Dir.empty?(LokaliseRails.locales_path.to_s)
|
42
84
|
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require "#{Rails.root}/config/lokalise_rails"
|
5
|
+
|
6
|
+
namespace :lokalise_rails do
|
7
|
+
task :import do
|
8
|
+
LokaliseRails::TaskDefinition::Importer.import!
|
9
|
+
end
|
10
|
+
|
11
|
+
task :export do
|
12
|
+
LokaliseRails::TaskDefinition::Exporter.export!
|
13
|
+
end
|
14
|
+
end
|
data/lokalise_rails.gemspec
CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |spec|
|
|
16
16
|
|
17
17
|
spec.files = Dir['README.md', 'LICENSE',
|
18
18
|
'CHANGELOG.md', 'lib/**/*.rb',
|
19
|
+
'lib/**/*.rake',
|
19
20
|
'lokalise_rails.gemspec', '.github/*.md',
|
20
21
|
'Gemfile', 'Rakefile']
|
21
22
|
spec.test_files = Dir['spec/**/*.rb']
|
@@ -34,11 +35,9 @@ Gem::Specification.new do |spec|
|
|
34
35
|
end
|
35
36
|
spec.add_development_dependency 'rake', '~> 13.0'
|
36
37
|
spec.add_development_dependency 'rspec', '~> 3.6'
|
37
|
-
spec.add_development_dependency 'rspec-rails', '~> 4.0'
|
38
38
|
spec.add_development_dependency 'rubocop', '~> 0.60'
|
39
39
|
spec.add_development_dependency 'rubocop-performance', '~> 1.5'
|
40
40
|
spec.add_development_dependency 'rubocop-rspec', '~> 1.37'
|
41
41
|
spec.add_development_dependency 'simplecov', '~> 0.16'
|
42
|
-
spec.add_development_dependency 'sqlite3', '~> 1.4'
|
43
42
|
spec.add_development_dependency 'vcr', '~> 6.0'
|
44
43
|
end
|
@@ -4,9 +4,9 @@ require_relative 'boot'
|
|
4
4
|
|
5
5
|
require 'rails'
|
6
6
|
# Pick the frameworks you want:
|
7
|
-
require 'active_model/railtie'
|
7
|
+
# require 'active_model/railtie'
|
8
8
|
# require "active_job/railtie"
|
9
|
-
require 'active_record/railtie'
|
9
|
+
# require 'active_record/railtie'
|
10
10
|
# require "active_storage/engine"
|
11
11
|
require 'action_controller/railtie'
|
12
12
|
# require "action_mailer/railtie"
|
@@ -21,6 +21,8 @@ require 'sprockets/railtie'
|
|
21
21
|
# you've limited to :test, :development, or :production.
|
22
22
|
Bundler.require(*Rails.groups)
|
23
23
|
|
24
|
+
require 'dotenv/load'
|
25
|
+
|
24
26
|
module Dummy
|
25
27
|
class Application < Rails::Application
|
26
28
|
# Settings in config/environments/* take precedence over those specified here.
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
describe LokaliseRails::TaskDefinition::Base do
|
4
|
+
specify '.reset_client!' do
|
5
|
+
expect(described_class.api_client).to be_an_instance_of(Lokalise::Client)
|
6
|
+
described_class.reset_api_client!
|
7
|
+
current_client = described_class.instance_variable_get '@api_client'
|
8
|
+
expect(current_client).to be_nil
|
9
|
+
end
|
10
|
+
|
11
|
+
specify '.project_id_with_branch!' do
|
12
|
+
result = described_class.send :project_id_with_branch
|
13
|
+
expect(result).to be_an_instance_of(String)
|
14
|
+
expect(result).to include(':master')
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '.subdir_and_filename_for' do
|
18
|
+
it 'works properly for longer paths' do
|
19
|
+
path = 'my_path/is/here/file.yml'
|
20
|
+
result = described_class.send(:subdir_and_filename_for, path)
|
21
|
+
expect(result.length).to eq(2)
|
22
|
+
expect(result[0]).to be_an_instance_of(Pathname)
|
23
|
+
expect(result[0].to_s).to eq('my_path/is/here')
|
24
|
+
expect(result[1].to_s).to eq('file.yml')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'works properly for shorter paths' do
|
28
|
+
path = 'file.yml'
|
29
|
+
result = described_class.send(:subdir_and_filename_for, path)
|
30
|
+
expect(result.length).to eq(2)
|
31
|
+
expect(result[1]).to be_an_instance_of(Pathname)
|
32
|
+
expect(result[0].to_s).to eq('.')
|
33
|
+
expect(result[1].to_s).to eq('file.yml')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '.opt_errors' do
|
38
|
+
it 'returns an error when the API key is not set' do
|
39
|
+
allow(LokaliseRails).to receive(:api_token).and_return(nil)
|
40
|
+
errors = described_class.opt_errors
|
41
|
+
|
42
|
+
expect(LokaliseRails).to have_received(:api_token)
|
43
|
+
expect(errors.length).to eq(1)
|
44
|
+
expect(errors.first).to include('API token is not set')
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns an error when the project_id is not set' do
|
48
|
+
allow(LokaliseRails).to receive(:project_id).and_return(nil)
|
49
|
+
errors = described_class.opt_errors
|
50
|
+
|
51
|
+
expect(LokaliseRails).to have_received(:project_id)
|
52
|
+
expect(errors.length).to eq(1)
|
53
|
+
expect(errors.first).to include('Project ID is not set')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '.proper_ext?' do
|
58
|
+
it 'works properly with path represented as a string' do
|
59
|
+
path = 'my_path/here/file.yml'
|
60
|
+
expect(described_class.send(:proper_ext?, path)).to be true
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'works properly with path represented as a pathname' do
|
64
|
+
path = Pathname.new 'my_path/here/file.json'
|
65
|
+
expect(described_class.send(:proper_ext?, path)).to be false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '.api_client' do
|
70
|
+
before(:all) { described_class.reset_api_client! }
|
71
|
+
|
72
|
+
after(:all) { described_class.reset_api_client! }
|
73
|
+
|
74
|
+
it 'is possible to set timeouts' do
|
75
|
+
allow(LokaliseRails).to receive(:timeouts).and_return({
|
76
|
+
open_timeout: 100,
|
77
|
+
timeout: 500
|
78
|
+
})
|
79
|
+
|
80
|
+
expect(described_class.api_client).to be_an_instance_of(Lokalise::Client)
|
81
|
+
expect(described_class.api_client.open_timeout).to eq(100)
|
82
|
+
expect(described_class.api_client.timeout).to eq(500)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
|
5
|
+
describe LokaliseRails::TaskDefinition::Exporter do
|
6
|
+
let(:filename) { 'en.yml' }
|
7
|
+
let(:path) { "#{Rails.root}/config/locales/nested/#{filename}" }
|
8
|
+
let(:relative_name) { "nested/#{filename}" }
|
9
|
+
|
10
|
+
context 'with one translation file' do
|
11
|
+
before :all do
|
12
|
+
add_translation_files!
|
13
|
+
end
|
14
|
+
|
15
|
+
after :all do
|
16
|
+
rm_translation_files
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '.export!' do
|
20
|
+
it 'sends a proper API request' do
|
21
|
+
allow_project_id
|
22
|
+
|
23
|
+
process = VCR.use_cassette('upload_files') do
|
24
|
+
described_class.export!
|
25
|
+
end.first
|
26
|
+
|
27
|
+
expect(process.project_id).to eq(LokaliseRails.project_id)
|
28
|
+
expect(process.status).to eq('queued')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'sends a proper API request when a different branch is provided' do
|
32
|
+
allow_project_id
|
33
|
+
allow(LokaliseRails).to receive(:branch).and_return('develop')
|
34
|
+
|
35
|
+
process = VCR.use_cassette('upload_files_branch') do
|
36
|
+
described_class.export!
|
37
|
+
end.first
|
38
|
+
|
39
|
+
expect(LokaliseRails).to have_received(:branch)
|
40
|
+
expect(process.project_id).to eq(LokaliseRails.project_id)
|
41
|
+
expect(process.status).to eq('queued')
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'halts when the API key is not set' do
|
45
|
+
allow(LokaliseRails).to receive(:api_token).and_return(nil)
|
46
|
+
|
47
|
+
expect(-> { described_class.export! }).to output(/API token is not set/).to_stdout
|
48
|
+
expect(LokaliseRails).to have_received(:api_token)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'halts when the project_id is not set' do
|
52
|
+
allow(LokaliseRails).to receive(:project_id).and_return(nil)
|
53
|
+
|
54
|
+
expect(-> { described_class.export! }).to output(/Project ID is not set/).to_stdout
|
55
|
+
expect(LokaliseRails).to have_received(:project_id)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '.each_file' do
|
60
|
+
it 'yield proper arguments' do
|
61
|
+
expect { |b| described_class.each_file(&b) }.to yield_with_args(
|
62
|
+
Pathname.new(path),
|
63
|
+
Pathname.new(relative_name)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe '.opts' do
|
69
|
+
let(:base64content) { Base64.strict_encode64(File.read(path).strip) }
|
70
|
+
|
71
|
+
it 'generates proper options' do
|
72
|
+
resulting_opts = described_class.opts(path, relative_name)
|
73
|
+
|
74
|
+
expect(resulting_opts[:data]).to eq(base64content)
|
75
|
+
expect(resulting_opts[:filename]).to eq(relative_name)
|
76
|
+
expect(resulting_opts[:lang_iso]).to eq('en')
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'allows to redefine options' do
|
80
|
+
allow(LokaliseRails).to receive(:export_opts).and_return({
|
81
|
+
detect_icu_plurals: true,
|
82
|
+
convert_placeholders: true
|
83
|
+
})
|
84
|
+
|
85
|
+
resulting_opts = described_class.opts(path, relative_name)
|
86
|
+
|
87
|
+
expect(LokaliseRails).to have_received(:export_opts)
|
88
|
+
expect(resulting_opts[:data]).to eq(base64content)
|
89
|
+
expect(resulting_opts[:filename]).to eq(relative_name)
|
90
|
+
expect(resulting_opts[:lang_iso]).to eq('en')
|
91
|
+
expect(resulting_opts[:detect_icu_plurals]).to be true
|
92
|
+
expect(resulting_opts[:convert_placeholders]).to be true
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context 'with two translation files' do
|
98
|
+
let(:filename_ru) { 'ru.yml' }
|
99
|
+
let(:path_ru) { "#{Rails.root}/config/locales/#{filename_ru}" }
|
100
|
+
let(:relative_name_ru) { filename_ru }
|
101
|
+
|
102
|
+
before :all do
|
103
|
+
add_translation_files! with_ru: true
|
104
|
+
end
|
105
|
+
|
106
|
+
after :all do
|
107
|
+
rm_translation_files
|
108
|
+
end
|
109
|
+
|
110
|
+
describe '.export!' do
|
111
|
+
it 'rescues from export errors' do
|
112
|
+
allow_project_id
|
113
|
+
|
114
|
+
processes = VCR.use_cassette('upload_files_error') do
|
115
|
+
described_class.export!
|
116
|
+
end
|
117
|
+
|
118
|
+
expect(processes.length).to eq(1)
|
119
|
+
process = processes.first
|
120
|
+
expect(process.project_id).to eq(LokaliseRails.project_id)
|
121
|
+
expect(process.status).to eq('queued')
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe '.opts' do
|
126
|
+
let(:base64content_ru) { Base64.strict_encode64(File.read(path_ru).strip) }
|
127
|
+
|
128
|
+
it 'generates proper options' do
|
129
|
+
resulting_opts = described_class.opts(path_ru, relative_name_ru)
|
130
|
+
|
131
|
+
expect(resulting_opts[:data]).to eq(base64content_ru)
|
132
|
+
expect(resulting_opts[:filename]).to eq(relative_name_ru)
|
133
|
+
expect(resulting_opts[:lang_iso]).to eq('ru_RU')
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe '.each_file' do
|
138
|
+
it 'yields every translation file' do
|
139
|
+
expect { |b| described_class.each_file(&b) }.to yield_successive_args(
|
140
|
+
[
|
141
|
+
Pathname.new(path),
|
142
|
+
Pathname.new(relative_name)
|
143
|
+
],
|
144
|
+
[
|
145
|
+
Pathname.new(path_ru),
|
146
|
+
Pathname.new(relative_name_ru)
|
147
|
+
]
|
148
|
+
)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'does not yield files that have to be skipped' do
|
152
|
+
allow(LokaliseRails).to receive(:skip_file_export).twice.and_return(
|
153
|
+
->(f) { f.split[1].to_s.include?('ru') }
|
154
|
+
)
|
155
|
+
expect { |b| described_class.each_file(&b) }.to yield_successive_args(
|
156
|
+
[
|
157
|
+
Pathname.new(path),
|
158
|
+
Pathname.new(relative_name)
|
159
|
+
]
|
160
|
+
)
|
161
|
+
|
162
|
+
expect(LokaliseRails).to have_received(:skip_file_export).twice
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|