lokalise_rails 1.4.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/Gemfile +11 -0
  4. data/README.md +74 -54
  5. data/lib/generators/templates/lokalise_rails_config.rb +8 -4
  6. data/lib/lokalise_rails/global_config.rb +11 -0
  7. data/lib/lokalise_rails/utils.rb +20 -0
  8. data/lib/lokalise_rails/version.rb +3 -1
  9. data/lib/lokalise_rails.rb +4 -76
  10. data/lib/tasks/lokalise_rails_tasks.rake +6 -3
  11. data/lokalise_rails.gemspec +4 -16
  12. data/spec/dummy/config/lokalise_rails.rb +3 -3
  13. data/spec/lib/{lokalise_rails_spec.rb → lokalise_rails/global_config_spec.rb} +27 -5
  14. data/spec/lib/lokalise_rails/version_spec.rb +7 -0
  15. data/spec/lib/tasks/export_task_spec.rb +22 -15
  16. data/spec/lib/tasks/import_task_spec.rb +32 -97
  17. data/spec/lib/utils_spec.rb +16 -0
  18. data/spec/spec_helper.rb +0 -1
  19. data/spec/support/file_manager.rb +23 -20
  20. data/spec/support/spec_addons.rb +8 -3
  21. metadata +16 -185
  22. data/lib/lokalise_rails/error.rb +0 -10
  23. data/lib/lokalise_rails/task_definition/base.rb +0 -64
  24. data/lib/lokalise_rails/task_definition/exporter.rb +0 -73
  25. data/lib/lokalise_rails/task_definition/importer.rb +0 -108
  26. data/spec/dummy/config/initializers/application_controller_renderer.rb +0 -9
  27. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -8
  28. data/spec/dummy/config/initializers/content_security_policy.rb +0 -29
  29. data/spec/dummy/config/initializers/inflections.rb +0 -17
  30. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  31. data/spec/dummy/config/initializers/wrap_parameters.rb +0 -16
  32. data/spec/lib/lokalise_rails/task_definition/base_spec.rb +0 -81
  33. data/spec/lib/lokalise_rails/task_definition/exporter_spec.rb +0 -160
  34. data/spec/lib/lokalise_rails/task_definition/importer_spec.rb +0 -54
@@ -1,73 +0,0 @@
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
- check_options_errors!
14
-
15
- queued_processes = []
16
- each_file do |full_path, relative_path|
17
- queued_processes << api_client.upload_file(
18
- project_id_with_branch, opts(full_path, relative_path)
19
- )
20
- rescue StandardError => e
21
- raise e.class, "Error while trying to upload #{full_path}: #{e.message}"
22
- end
23
-
24
- $stdout.print 'Task complete!'
25
-
26
- queued_processes
27
- end
28
-
29
- # Processes each translation file in the specified directory
30
- def each_file
31
- return unless block_given?
32
-
33
- loc_path = LokaliseRails.locales_path
34
- Dir["#{loc_path}/**/*"].sort.each do |f|
35
- full_path = Pathname.new f
36
-
37
- next unless file_matches_criteria? full_path
38
-
39
- relative_path = full_path.relative_path_from Pathname.new(loc_path)
40
-
41
- yield full_path, relative_path
42
- end
43
- end
44
-
45
- # Generates export options
46
- #
47
- # @return [Hash]
48
- # @param full_p [Pathname]
49
- # @param relative_p [Pathname]
50
- def opts(full_p, relative_p)
51
- content = File.read full_p
52
-
53
- initial_opts = {
54
- data: Base64.strict_encode64(content.strip),
55
- filename: relative_p,
56
- lang_iso: LokaliseRails.lang_iso_inferer.call(content)
57
- }
58
-
59
- initial_opts.merge LokaliseRails.export_opts
60
- end
61
-
62
- # Checks whether the specified file has to be processed or not
63
- #
64
- # @return [Boolean]
65
- # @param full_path [Pathname]
66
- def file_matches_criteria?(full_path)
67
- full_path.file? && proper_ext?(full_path) &&
68
- !LokaliseRails.skip_file_export.call(full_path)
69
- end
70
- end
71
- end
72
- end
73
- end
@@ -1,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'zip'
4
- require 'yaml'
5
- require 'open-uri'
6
- require 'fileutils'
7
-
8
- module LokaliseRails
9
- module TaskDefinition
10
- class Importer < Base
11
- class << self
12
- # Performs translation files import from Lokalise to Rails app
13
- #
14
- # @return [Boolean]
15
- def import!
16
- check_options_errors!
17
-
18
- unless proceed_when_safe_mode?
19
- $stdout.print 'Task cancelled!'
20
- return false
21
- end
22
-
23
- open_and_process_zip download_files['bundle_url']
24
-
25
- $stdout.print 'Task complete!'
26
- true
27
- end
28
-
29
- # Downloads files from Lokalise using the specified options
30
- #
31
- # @return [Hash]
32
- def download_files
33
- opts = LokaliseRails.import_opts
34
-
35
- api_client.download_files project_id_with_branch, opts
36
- rescue StandardError => e
37
- raise e.class, "There was an error when trying to download files: #{e.message}"
38
- end
39
-
40
- # Opens ZIP archive (local or remote) with translations and processes its entries
41
- #
42
- # @param path [String]
43
- def open_and_process_zip(path)
44
- Zip::File.open_buffer(open_file_or_remote(path)) do |zip|
45
- fetch_zip_entries(zip) { |entry| process!(entry) }
46
- end
47
- rescue StandardError => e
48
- raise e.class, "There was an error when trying to process the downloaded files: #{e.message}"
49
- end
50
-
51
- # Iterates over ZIP entries. Each entry may be a file or folder.
52
- def fetch_zip_entries(zip)
53
- return unless block_given?
54
-
55
- zip.each do |entry|
56
- next unless proper_ext? entry.name
57
-
58
- yield entry
59
- end
60
- end
61
-
62
- # Processes ZIP entry by reading its contents and creating the corresponding translation file
63
- def process!(zip_entry)
64
- data = data_from zip_entry
65
- subdir, filename = subdir_and_filename_for zip_entry.name
66
- full_path = "#{LokaliseRails.locales_path}/#{subdir}"
67
- FileUtils.mkdir_p full_path
68
-
69
- File.open(File.join(full_path, filename), 'w+:UTF-8') do |f|
70
- f.write LokaliseRails.translations_converter.call(data)
71
- end
72
- rescue StandardError => e
73
- raise e.class, "Error when trying to process #{zip_entry&.name}: #{e.message}"
74
- end
75
-
76
- # Checks whether the user wishes to proceed when safe mode is enabled and the target directory is not empty
77
- #
78
- # @return [Boolean]
79
- def proceed_when_safe_mode?
80
- return true unless LokaliseRails.import_safe_mode && !Dir.empty?(LokaliseRails.locales_path.to_s)
81
-
82
- $stdout.puts "The target directory #{LokaliseRails.locales_path} is not empty!"
83
- $stdout.print 'Enter Y to continue: '
84
- answer = $stdin.gets
85
- answer.to_s.strip == 'Y'
86
- end
87
-
88
- # Opens a local file or a remote URL using the provided patf
89
- #
90
- # @return [String]
91
- def open_file_or_remote(path)
92
- parsed_path = URI.parse(path)
93
- if parsed_path&.scheme&.include?('http')
94
- parsed_path.open
95
- else
96
- File.open path
97
- end
98
- end
99
-
100
- private
101
-
102
- def data_from(zip_entry)
103
- LokaliseRails.translations_loader.call zip_entry.get_input_stream.read
104
- end
105
- end
106
- end
107
- end
108
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
- # Be sure to restart your server when you modify this file.
3
-
4
- # ActiveSupport::Reloader.to_prepare do
5
- # ApplicationController.renderer.defaults.merge!(
6
- # http_host: 'example.org',
7
- # https: false
8
- # )
9
- # end
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
- # Be sure to restart your server when you modify this file.
3
-
4
- # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
5
- # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
6
-
7
- # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
8
- # Rails.backtrace_cleaner.remove_silencers!
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
- # Be sure to restart your server when you modify this file.
3
-
4
- # Define an application-wide content security policy
5
- # For further information see the following documentation
6
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
7
-
8
- # Rails.application.config.content_security_policy do |policy|
9
- # policy.default_src :self, :https
10
- # policy.font_src :self, :https, :data
11
- # policy.img_src :self, :https, :data
12
- # policy.object_src :none
13
- # policy.script_src :self, :https
14
- # policy.style_src :self, :https
15
-
16
- # # Specify URI for violation reports
17
- # # policy.report_uri "/csp-violation-report-endpoint"
18
- # end
19
-
20
- # If you are using UJS then enable automatic nonce generation
21
- # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
22
-
23
- # Set the nonce only to specific directives
24
- # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
25
-
26
- # Report CSP violations to a specified URI
27
- # For further information see the following documentation:
28
- # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
29
- # Rails.application.config.content_security_policy_report_only = true
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
- # Be sure to restart your server when you modify this file.
3
-
4
- # Add new inflection rules using the following format. Inflections
5
- # are locale specific, and you may define rules for as many different
6
- # locales as you wish. All of these examples are active by default:
7
- # ActiveSupport::Inflector.inflections(:en) do |inflect|
8
- # inflect.plural /^(ox)$/i, '\1en'
9
- # inflect.singular /^(ox)en/i, '\1'
10
- # inflect.irregular 'person', 'people'
11
- # inflect.uncountable %w( fish sheep )
12
- # end
13
-
14
- # These inflection rules are supported but not enabled by default:
15
- # ActiveSupport::Inflector.inflections(:en) do |inflect|
16
- # inflect.acronym 'RESTful'
17
- # end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
- # Be sure to restart your server when you modify this file.
3
-
4
- # Add new mime types for use in respond_to blocks:
5
- # Mime::Type.register "text/richtext", :rtf
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Be sure to restart your server when you modify this file.
4
-
5
- # This file contains settings for ActionController::ParamsWrapper which
6
- # is enabled by default.
7
-
8
- # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
9
- ActiveSupport.on_load(:action_controller) do
10
- wrap_parameters format: [:json]
11
- end
12
-
13
- # To enable root element in JSON for ActiveRecord objects.
14
- # ActiveSupport.on_load(:active_record) do
15
- # self.include_root_in_json = true
16
- # end
@@ -1,81 +0,0 @@
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 '.check_options_errors!' do
38
- it 'raises an error when the API key is not set' do
39
- allow(LokaliseRails).to receive(:api_token).and_return(nil)
40
-
41
- expect(-> { described_class.check_options_errors! }).to raise_error(LokaliseRails::Error, /API token is not set/i)
42
-
43
- expect(LokaliseRails).to have_received(:api_token)
44
- end
45
-
46
- it 'returns an error when the project_id is not set' do
47
- allow_project_id nil do
48
- expect(-> { described_class.check_options_errors! }).to raise_error(LokaliseRails::Error, /ID is not set/i)
49
- end
50
- end
51
- end
52
-
53
- describe '.proper_ext?' do
54
- it 'works properly with path represented as a string' do
55
- path = 'my_path/here/file.yml'
56
- expect(described_class.send(:proper_ext?, path)).to be true
57
- end
58
-
59
- it 'works properly with path represented as a pathname' do
60
- path = Pathname.new 'my_path/here/file.json'
61
- expect(described_class.send(:proper_ext?, path)).to be false
62
- end
63
- end
64
-
65
- describe '.api_client' do
66
- before(:all) { described_class.reset_api_client! }
67
-
68
- after(:all) { described_class.reset_api_client! }
69
-
70
- it 'is possible to set timeouts' do
71
- allow(LokaliseRails).to receive(:timeouts).and_return({
72
- open_timeout: 100,
73
- timeout: 500
74
- })
75
-
76
- expect(described_class.api_client).to be_an_instance_of(Lokalise::Client)
77
- expect(described_class.api_client.open_timeout).to eq(100)
78
- expect(described_class.api_client.timeout).to eq(500)
79
- end
80
- end
81
- end
@@ -1,160 +0,0 @@
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 raise_error(LokaliseRails::Error, /API token is not set/i)
48
- expect(LokaliseRails).to have_received(:api_token)
49
- end
50
-
51
- it 'halts when the project_id is not set' do
52
- allow_project_id nil do
53
- expect(-> { described_class.export! }).to raise_error(LokaliseRails::Error, /ID is not set/i)
54
- end
55
- end
56
- end
57
-
58
- describe '.each_file' do
59
- it 'yield proper arguments' do
60
- expect { |b| described_class.each_file(&b) }.to yield_with_args(
61
- Pathname.new(path),
62
- Pathname.new(relative_name)
63
- )
64
- end
65
- end
66
-
67
- describe '.opts' do
68
- let(:base64content) { Base64.strict_encode64(File.read(path).strip) }
69
-
70
- it 'generates proper options' do
71
- resulting_opts = described_class.opts(path, relative_name)
72
-
73
- expect(resulting_opts[:data]).to eq(base64content)
74
- expect(resulting_opts[:filename]).to eq(relative_name)
75
- expect(resulting_opts[:lang_iso]).to eq('en')
76
- end
77
-
78
- it 'allows to redefine options' do
79
- allow(LokaliseRails).to receive(:export_opts).and_return({
80
- detect_icu_plurals: true,
81
- convert_placeholders: true
82
- })
83
-
84
- resulting_opts = described_class.opts(path, relative_name)
85
-
86
- expect(LokaliseRails).to have_received(:export_opts)
87
- expect(resulting_opts[:data]).to eq(base64content)
88
- expect(resulting_opts[:filename]).to eq(relative_name)
89
- expect(resulting_opts[:lang_iso]).to eq('en')
90
- expect(resulting_opts[:detect_icu_plurals]).to be true
91
- expect(resulting_opts[:convert_placeholders]).to be true
92
- end
93
- end
94
- end
95
-
96
- context 'with two translation files' do
97
- let(:filename_ru) { 'ru.yml' }
98
- let(:path_ru) { "#{Rails.root}/config/locales/#{filename_ru}" }
99
- let(:relative_name_ru) { filename_ru }
100
-
101
- before :all do
102
- add_translation_files! with_ru: true
103
- end
104
-
105
- after :all do
106
- rm_translation_files
107
- end
108
-
109
- describe '.export!' do
110
- it 're-raises export errors' do
111
- allow_project_id
112
-
113
- VCR.use_cassette('upload_files_error') do
114
- expect { described_class.export! }.to raise_error(Lokalise::Error::BadRequest, /Unknown `lang_iso`/)
115
- end
116
- end
117
- end
118
-
119
- describe '.opts' do
120
- let(:base64content_ru) { Base64.strict_encode64(File.read(path_ru).strip) }
121
-
122
- it 'generates proper options' do
123
- resulting_opts = described_class.opts(path_ru, relative_name_ru)
124
-
125
- expect(resulting_opts[:data]).to eq(base64content_ru)
126
- expect(resulting_opts[:filename]).to eq(relative_name_ru)
127
- expect(resulting_opts[:lang_iso]).to eq('ru_RU')
128
- end
129
- end
130
-
131
- describe '.each_file' do
132
- it 'yields every translation file' do
133
- expect { |b| described_class.each_file(&b) }.to yield_successive_args(
134
- [
135
- Pathname.new(path),
136
- Pathname.new(relative_name)
137
- ],
138
- [
139
- Pathname.new(path_ru),
140
- Pathname.new(relative_name_ru)
141
- ]
142
- )
143
- end
144
-
145
- it 'does not yield files that have to be skipped' do
146
- allow(LokaliseRails).to receive(:skip_file_export).twice.and_return(
147
- ->(f) { f.split[1].to_s.include?('ru') }
148
- )
149
- expect { |b| described_class.each_file(&b) }.to yield_successive_args(
150
- [
151
- Pathname.new(path),
152
- Pathname.new(relative_name)
153
- ]
154
- )
155
-
156
- expect(LokaliseRails).to have_received(:skip_file_export).twice
157
- end
158
- end
159
- end
160
- end
@@ -1,54 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- describe LokaliseRails::TaskDefinition::Importer do
4
- describe '.open_and_process_zip' do
5
- let(:faulty_trans) { "#{Rails.root}/public/faulty_trans.zip" }
6
-
7
- it 're-raises errors during file processing' do
8
- expect(-> { described_class.open_and_process_zip(faulty_trans) }).
9
- to raise_error(Psych::DisallowedClass, /Error when trying to process fail\.yml/)
10
- end
11
-
12
- it 're-raises errors during file opening' do
13
- expect(-> { described_class.open_and_process_zip('http://fake.url/wrong/path.zip') }).
14
- to raise_error(SocketError, /Failed to open TCP connection/)
15
- end
16
- end
17
-
18
- describe '.download_files' do
19
- it 'returns a proper download URL' do
20
- allow_project_id '189934715f57a162257d74.88352370' do
21
- response = VCR.use_cassette('download_files') do
22
- described_class.download_files
23
- end
24
-
25
- expect(response['project_id']).to eq('189934715f57a162257d74.88352370')
26
- expect(response['bundle_url']).to include('s3-eu-west-1.amazonaws.com')
27
- end
28
- end
29
-
30
- it 're-raises errors during file download' do
31
- allow_project_id 'invalid'
32
- VCR.use_cassette('download_files_error') do
33
- expect(-> { described_class.download_files }).
34
- to raise_error(Lokalise::Error::BadRequest, /Invalid `project_id` parameter/)
35
- end
36
- end
37
- end
38
-
39
- describe '.import!' do
40
- it 'halts when the API key is not set' do
41
- allow(LokaliseRails).to receive(:api_token).and_return(nil)
42
- expect(-> { described_class.import! }).to raise_error(LokaliseRails::Error, /API token is not set/i)
43
- expect(LokaliseRails).to have_received(:api_token)
44
- expect(count_translations).to eq(0)
45
- end
46
-
47
- it 'halts when the project_id is not set' do
48
- allow_project_id nil do
49
- expect(-> { described_class.import! }).to raise_error(LokaliseRails::Error, /ID is not set/i)
50
- expect(count_translations).to eq(0)
51
- end
52
- end
53
- end
54
- end