lokalise_rails 1.4.0 → 4.0.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.
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