i18n-migrations 1.2.4 → 2.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0b8b7ac35780cf4cf39728ec9c140324a1df5b21868fbeff0d8082eb2b612e1e
4
- data.tar.gz: c8c7f5867dc9d8485d527d0b9286a3925eaa69eea40e82ae5ee0a6ca2240f89e
3
+ metadata.gz: 8fc754b54a85b172209a62717aabb6207bb646be0fca6e04d777732c0c5979bb
4
+ data.tar.gz: d1b35b6d1d71c7ddfb83904cdff9cca5b307899330b363287c607220ceeccd72
5
5
  SHA512:
6
- metadata.gz: 3ebecbc35805df21ae0db20ac14931517fb6e55126a3a3b853d1486ab59264f38ea58e66077f665ed226b66afb828544866dae8810fd6c6c93c5ce52f9367d89
7
- data.tar.gz: 5a449c05dd1f4d2a2a5492cf0e0ff6fb016344fc1db735e51eaeff4bc9466f62cfb1979c381ad8d35fa503e58d8964fbe044438e9ec1ac51a09fb1483d4356bc
6
+ metadata.gz: de609cad812e1793e2e9eeff28cfe60ccda2c8b50af2fe97b9f01270f1f661c870b1be3c79bd1ffbe52414e5cb8a54fd2fdc17016eceba94593ca9254bb95d05
7
+ data.tar.gz: 4f023a9a8b03ffba035c6b293fb51ca68e9274d51cc3adf9a7658575c42df3268ad54e2a04b7ad4d63f43e55968927785f0b045becc646696f3b707eb346849c
data/README.md CHANGED
@@ -47,20 +47,10 @@ Let's imagine that your config file look like this:
47
47
 
48
48
  In your project file, you should then have all your english terms in ```config/locales/en.yml```
49
49
 
50
- To create a new locale (like es.yml):
51
-
52
- 1. Translate all the terms w/ google translate
53
-
54
- > i18n-migrate new_locale es
55
-
56
- 2. Create a spreadsheet that is world editable (for now). You'll want to add the link to it to your config file. It should look like:
57
-
58
- | key | en | es | notes |
59
-
60
- 2. Push this to your google spreadsheet (the -f means it won't try to pull first)
61
-
62
- > i18n-migrate push -f es
50
+ You can:
63
51
 
52
+ * [Create a new locale](https://github.com/transparentclassroom/i18n-migrations/wiki/Create-a-new-locale-(language))
53
+ * [Add a migration](https://github.com/transparentclassroom/i18n-migrations/wiki/Add-a-migration)
64
54
 
65
55
  ## Development
66
56
 
@@ -71,8 +61,9 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
71
61
  ## Contributing
72
62
 
73
63
  1. Fork it ( https://github.com/[my-github-username]/i18n-migrations/fork )
74
- 2. Create your feature branch (`git checkout -b my-new-feature`)
75
- 3. Commit your changes (`git commit -am 'Add some feature'`)
76
- 4. Push to the branch (`git push origin my-new-feature`)
77
- 5. Create a new Pull Request
64
+ 1. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 1. Make sure tests are passing (`rake`)
66
+ 1. Commit your changes (`git commit -am 'Add some feature'`)
67
+ 1. Push to the branch (`git push origin my-new-feature`)
68
+ 1. Create a new Pull Request
78
69
 
@@ -10,59 +10,60 @@ def extract_option(name)
10
10
  !!ARGV.delete(name)
11
11
  end
12
12
 
13
- case ARGV.shift
14
- when 'setup'
15
- puts 'Where should we create a default config file? [.]'
16
- dir = gets.chomp
17
- dir = dir == '' ? '.' : dir
18
- file = I18n::Migrations::Config.copy_default_config_file(dir)
19
-
20
- puts 'You will need to configure this file before you can get going.'
21
- puts File.expand_path(file)
22
-
23
- when 'new'
24
- name = ARGV.shift
25
- if name
26
- migrator.new_migration name
27
- else
28
- STDERR.puts 'Usage: im new [name]'
29
- exit 1
30
- end
31
-
32
- when 'migrate'
33
- migrator.migrate(ARGV[0] || 'all')
34
-
35
- when 'rollback'
36
- migrator.rollback(ARGV[0] || 'all')
37
-
38
- when 'redo'
39
- migrator.rollback(ARGV[0] || 'all')
40
- migrator.migrate(ARGV[0] || 'all')
41
-
42
- when 'pull'
43
- migrator.pull(ARGV[0] || 'all')
13
+ begin
14
+ case ARGV.shift
15
+ when 'setup'
16
+ puts 'Where should we create a default config file? [.]'
17
+ dir = gets.chomp
18
+ dir = dir == '' ? '.' : dir
19
+ file = I18n::Migrations::Config.copy_default_config_file(dir)
20
+
21
+ puts 'You will need to configure this file before you can get going.'
22
+ puts File.expand_path(file)
23
+
24
+ when 'new'
25
+ name = ARGV.shift
26
+ if name
27
+ migrator.new_migration name
28
+ else
29
+ STDERR.puts 'Usage: im new [name]'
30
+ exit 1
31
+ end
32
+
33
+ when 'migrate'
34
+ migrator.migrate(ARGV[0] || 'all')
35
+
36
+ when 'rollback'
37
+ migrator.rollback(ARGV[0] || 'all')
38
+
39
+ when 'redo'
40
+ migrator.rollback(ARGV[0] || 'all')
41
+ migrator.migrate(ARGV[0] || 'all')
42
+
43
+ when 'pull'
44
+ migrator.pull(ARGV[0] || 'all')
45
+
46
+ when 'push'
47
+ force = extract_option('-f')
48
+ migrator.push(ARGV[0] || 'all', force)
49
+
50
+ when 'validate'
51
+ migrator.validate(ARGV[0] || 'all')
52
+
53
+ when 'new_locale'
54
+ locale = ARGV.shift
55
+ if locale
56
+ migrator.new_locale(locale)
57
+ else
58
+ STDERR.puts 'Usage: im new_locale [name]'
59
+ exit 1
60
+ end
61
+
62
+ when 'version'
63
+ migrator.version
44
64
 
45
- when 'push'
46
- force = extract_option('-f')
47
- migrator.push(ARGV[0] || 'all', force)
48
-
49
- when 'validate'
50
- migrator.validate(ARGV[0] || 'all')
51
-
52
- when 'new_locale'
53
- locale = ARGV.shift
54
- if locale
55
- migrator.new_locale(locale)
56
65
  else
57
- STDERR.puts 'Usage: im new_locale [name]'
58
- exit 1
59
- end
60
-
61
- when 'version'
62
- migrator.version
63
-
64
- else
65
- puts <<-USAGE
66
+ puts <<-USAGE
66
67
  Usage: i18n-migrate [command]
67
68
 
68
69
  Commands:
@@ -78,5 +79,9 @@ Commands:
78
79
  version - Print version of locales.
79
80
 
80
81
  i18n-migrations version #{I18n::Migrations::VERSION}
81
- USAGE
82
+ USAGE
83
+ end
84
+ rescue Exception
85
+ puts "Error\n#{$!.message}".red
86
+ exit 1
82
87
  end
@@ -20,27 +20,22 @@ module I18n
20
20
  force_push(locale)
21
21
  end
22
22
 
23
- # do this just once
24
- unless @migrations_synced
25
- sync_migrations(locale.migrations)
26
- @migrations_synced = true
27
- end
28
23
  pull(locale)
29
24
  end
30
25
 
31
26
  # this will replace everything about a locale, it will create a locale that does not yet exist
32
27
  def force_push(locale)
33
- data, notes = locale.read_data_and_notes(parse: false)
28
+ data, metadata = locale.read_data_and_metadata(parse: false)
34
29
  client.put_locale(locale.name,
35
30
  name: locale.name,
36
31
  yaml_file: data,
37
- yaml_notes_file: notes)
32
+ yaml_metadata_file: metadata)
38
33
 
39
34
  end
40
35
 
41
36
  def pull_from_crowd_translate(locale)
42
37
  data = client.get_locale_file(locale.name)
43
- locale.write_raw_data("#{locale.name}.yml", data)
38
+ locale.data_file.write(data)
44
39
  locale.write_remote_version(YAML::load(data)[locale.name])
45
40
  end
46
41
 
@@ -58,9 +53,7 @@ module I18n
58
53
  begin
59
54
  client.put_migration(version: version, ruby_file: migrations.get_migration(version: version))
60
55
  rescue
61
- puts "There was an error updating migration:".red
62
- puts " #{migrations.migration_file(version: version)}".bold
63
- raise
56
+ raise CrowdTranslateError, "... while updating migration: #{migrations.migration_file(version: version)}\n#{$!.message}"
64
57
  end
65
58
  end
66
59
  end
@@ -4,16 +4,16 @@ require 'active_support/core_ext/object'
4
4
  module I18n
5
5
  module Migrations
6
6
  module Backends
7
+ class CrowdTranslateError < StandardError
8
+ end
9
+
7
10
  class CrowdTranslateClient
8
11
  def initialize
9
12
  token = ENV['CROWD_TRANSLATE_API_TOKEN']
10
13
  raise("You must define CROWD_TRANSLATE_API_TOKEN in order to talk to Crowd Translate") unless token.present?
11
14
 
12
- server = ENV['CROWD_TRANSLATE_SERVER'] || 'https://crowd-translate.herokuapp.com'
13
- @faraday = Faraday.new(
14
- url: "#{server}/api/v1",
15
- headers: { 'X-CrowdTranslateApiToken' => token },
16
- )
15
+ @server = ENV['CROWD_TRANSLATE_SERVER'] || 'https://crowd-translate.herokuapp.com'
16
+ @faraday = Faraday.new(url: base_url, headers: { 'X-CrowdTranslateApiToken' => token })
17
17
  end
18
18
 
19
19
  def get_locale_file(locale_code)
@@ -28,35 +28,43 @@ module I18n
28
28
  put("migrations/#{version}.json", migration: { ruby_file: ruby_file })
29
29
  end
30
30
 
31
- def put_locale(locale_code, name:, yaml_file:, yaml_notes_file:)
31
+ def put_locale(locale_code, name:, yaml_file:, yaml_metadata_file:)
32
32
  put("locales/#{locale_code}",
33
- locale: { name: name, yaml_file: yaml_file, yaml_notes_file: yaml_notes_file })
33
+ locale: { name: name, yaml_file: yaml_file, yaml_metadata_file: yaml_metadata_file })
34
34
  end
35
35
 
36
36
  private
37
37
 
38
38
  def get(path)
39
39
  puts "GET #{path}".bold
40
- parse_response @faraday.get path
40
+ parse_response 'GET', path, @faraday.get(path)
41
41
  end
42
42
 
43
43
  def put(path, params = {})
44
44
  puts "PUT #{path} #{params.to_s[0..50]}#{'...' if params.to_s.length > 50}".bold
45
- parse_response @faraday.put path, params
45
+ parse_response 'PUT', path, @faraday.put(path, params)
46
46
  end
47
47
 
48
- def parse_response(response)
48
+ def parse_response(method, path, response)
49
49
  if response.success?
50
50
  response.body
51
51
  else
52
- error = begin
53
- JSON.parse(response.body)['error']
54
- rescue
55
- response.body
52
+ error = if response.status == 503
53
+ 'Server timed out - your request may have succeed, check the server for more details'
54
+ else
55
+ begin
56
+ JSON.parse(response.body)['error']
57
+ rescue
58
+ response.body
59
+ end
56
60
  end
57
- raise error
61
+ raise CrowdTranslateError, "... while calling #{method} #{File.join(base_url, path)}\n#{error}"
58
62
  end
59
63
  end
64
+
65
+ def base_url
66
+ "#{@server}/api/v1"
67
+ end
60
68
  end
61
69
  end
62
70
  end
@@ -1,4 +1,5 @@
1
1
  require_relative './google_spreadsheet'
2
+ require_relative '../metadata'
2
3
 
3
4
  module I18n
4
5
  module Migrations
@@ -33,10 +34,14 @@ module I18n
33
34
  @config.google_service_account_key_path).sheet
34
35
  end
35
36
 
37
+ def sync_migrations(migrations)
38
+ # nothing to do here in this backend
39
+ end
40
+
36
41
  def pull_from_sheet(sheet, locale)
37
42
  puts "Pulling #{locale.name}"
38
43
  data = {}
39
- notes = {}
44
+ metadata = Metadata.new
40
45
  count = 0
41
46
 
42
47
  (2..sheet.num_rows).each do |row|
@@ -44,14 +49,14 @@ module I18n
44
49
  if key.present?
45
50
  locale.assign_complex_key(data, key.split('.'), value.present? ? value : '')
46
51
  if note.present?
47
- locale.assign_complex_key(notes, key.split('.'), note)
52
+ metadata[key] = parse_metadatum(note)
48
53
  end
49
54
  count += 1
50
- print '.'
55
+ #print '.'
51
56
  end
52
57
  end
53
58
 
54
- locale.write_data_and_notes(data, notes)
59
+ locale.write_data_and_metadata(data, metadata)
55
60
  locale.write_remote_version(data)
56
61
 
57
62
  puts "\n#{count} keys"
@@ -59,7 +64,7 @@ module I18n
59
64
 
60
65
  def push_to_sheet(sheet, locale)
61
66
  main_data = locale.main_locale.read_data
62
- data, notes = locale.read_data_and_notes
67
+ data, metadata = locale.read_data_and_metadata
63
68
  row = 2
64
69
 
65
70
  puts "Pushing #{locale.name}"
@@ -68,9 +73,9 @@ module I18n
68
73
  sheet[row, 1] = key
69
74
  sheet[row, 2] = value
70
75
  sheet[row, 3] = data[key]
71
- sheet[row, 4] = notes[key]
76
+ sheet[row, 4] = unparse_metadatum(metadata[key])
72
77
  row += 1
73
- print '.'
78
+ #print '.'
74
79
  end
75
80
 
76
81
  sheet.synchronize
@@ -78,6 +83,28 @@ module I18n
78
83
 
79
84
  puts "\n#{main_data.keys.length} keys"
80
85
  end
86
+
87
+ def parse_metadatum(text)
88
+ m = Metadata::Metadatum.new({})
89
+ m.notes = text.gsub("[autotranslated]") do
90
+ m.autotranslated = true
91
+ ''
92
+ end.gsub(/\[error: ([^\]]+)\]/) do
93
+ m.errors << $1
94
+ ''
95
+ end.strip
96
+ m
97
+ end
98
+
99
+ def unparse_metadatum(metadatum)
100
+ string = []
101
+ string << '[autotranslated]' if metadatum.autotranslated
102
+ metadatum.errors.each do |error|
103
+ string << "[error: #{error}]"
104
+ end
105
+ string << metadatum.notes unless metadatum.notes.blank?
106
+ string.join("\n")
107
+ end
81
108
  end
82
109
  end
83
110
  end
@@ -9,7 +9,7 @@ module I18n
9
9
  end
10
10
 
11
11
  # key is provided so we can figure out if this is text or html
12
- # returns [translated term, notes]
12
+ # returns [translated term, errors || []]
13
13
  def lookup(term, key: nil)
14
14
  return [term, ''] if @from_locale == @to_locale
15
15
 
@@ -19,11 +19,7 @@ module I18n
19
19
  format: format(key),
20
20
  q: term)
21
21
  translated_term = JSON.parse(response.body)['data']['translations'].first['translatedText']
22
- translated_term, errors = fix(term, translated_term, key: key)
23
- unless errors.empty?
24
- STDERR.puts "'#{term}' => '#{translated_term}'\n#{errors.join(', ').red}"
25
- end
26
- [translated_term, (errors.map { |e| "[error: #{e}]" } + ['[autotranslated]']).join("\n")]
22
+ fix(term, translated_term, key: key)
27
23
  end
28
24
 
29
25
  VARIABLE_STRING_REGEX = /%\{[^\}]+\}/
@@ -42,7 +38,7 @@ module I18n
42
38
  # returns updated after term, errors
43
39
  def fix(before, after, key: nil)
44
40
  is_html = format(key) == :html
45
- errors = ["#{@to_locale}: #{key}"]
41
+ errors = []
46
42
 
47
43
  # do not translate
48
44
  @do_not_translate.each do |term, bad_translations|
@@ -114,7 +110,7 @@ module I18n
114
110
  end
115
111
 
116
112
 
117
- [after, errors.length > 1 ? errors : []]
113
+ [after, errors]
118
114
  end
119
115
 
120
116
  private
@@ -3,6 +3,7 @@ require 'yaml'
3
3
  require 'active_support'
4
4
  require 'colorize'
5
5
  require 'active_support/core_ext/object'
6
+ require_relative './metadata'
6
7
 
7
8
  # this class does all the work, but doesn't hold config or do more than one locale
8
9
  module I18n
@@ -16,7 +17,7 @@ module I18n
16
17
  name, locales_dir, main_locale_name, migrations, dictionary
17
18
  end
18
19
 
19
- def validate(data, notes)
20
+ def validate(data, metadata)
20
21
  fix_count, error_count = 0, 0
21
22
  main_data = main_locale.read_data
22
23
  main_data.each do |key, main_term|
@@ -31,7 +32,7 @@ module I18n
31
32
  puts
32
33
  fix_count += 1
33
34
  end
34
- replace_errors_in_notes(notes, key, errors)
35
+ metadata[key].errors = errors
35
36
  if errors.length > 0
36
37
  puts "Error #{errors.join(', ').red} #{key.yellow}"
37
38
  puts "#{@main_locale_name.bold}: #{main_term}"
@@ -46,18 +47,18 @@ module I18n
46
47
  end
47
48
 
48
49
  def update_info
49
- data, notes = read_data_and_notes
50
- yield data, notes
51
- write_data_and_notes(data, notes)
50
+ data, metadata = read_data_and_metadata
51
+ yield data, metadata
52
+ write_data_and_metadata(data, metadata)
52
53
  end
53
54
 
54
55
  def migrate!
55
- update_info do |data, notes|
56
- migrate(data, notes)
56
+ update_info do |data, metadata|
57
+ migrate(data, metadata)
57
58
  end
58
59
  end
59
60
 
60
- def migrate(data, notes)
61
+ def migrate(data, metadata)
61
62
  missing_versions = (@migrations.all_versions - read_versions(data)).sort
62
63
  if missing_versions.empty?
63
64
  puts "#{@name}: up-to-date"
@@ -65,11 +66,11 @@ module I18n
65
66
  end
66
67
  puts "#{@name}: Migrating #{missing_versions.join(', ')}"
67
68
  missing_versions.each do |version|
68
- migrate_to_version(data, notes, version, :up)
69
+ migrate_to_version(data, metadata, version, :up)
69
70
  end
70
71
  end
71
72
 
72
- def rollback(data, notes)
73
+ def rollback(data, metadata)
73
74
  last_version = read_versions(data).last
74
75
  if last_version == nil
75
76
  puts "#{@name}: no more migrations to roll back"
@@ -78,24 +79,25 @@ module I18n
78
79
  puts "#{@name}: Rolling back #{last_version}"
79
80
  raise "Can't find #{last_version}.rb to rollback" unless @migrations.all_versions.include?(last_version)
80
81
 
81
- migrate_to_version(data, notes, last_version, :down)
82
+ migrate_to_version(data, metadata, last_version, :down)
82
83
  end
83
84
 
84
85
  def create(limit = nil)
85
- new_data, new_notes = {}, {}
86
+ new_data, new_metadata = {}, Metadata.new
86
87
  count = 0
87
88
  main_data = main_locale.read_data
88
89
  main_data.each do |key, term|
89
90
  if key == 'VERSION'
90
91
  new_data['VERSION'] = main_data['VERSION']
91
92
  else
92
- new_data[key], new_notes[key] = @dictionary.lookup(term, key: key)
93
+ new_data[key], errors = @dictionary.lookup(term, key: key)
94
+ new_metadata[key].errors = errors
93
95
  end
94
96
  print '.'.green
95
97
  break if limit && limit < (count += 1)
96
98
  end
97
99
  puts
98
- write_data_and_notes(new_data, new_notes)
100
+ write_data_and_metadata(new_data, new_metadata)
99
101
  end
100
102
 
101
103
  def main_locale?
@@ -107,29 +109,27 @@ module I18n
107
109
  end
108
110
 
109
111
  def read_data(parse: true)
110
- read_from_file("#{@name}.yml", parse: parse)
112
+ contents = data_file.read
113
+ return contents unless parse
114
+
115
+ hash = {}
116
+ add_to_hash(hash, parse_yaml(contents)[@name.to_s])
117
+ hash
111
118
  end
112
119
 
113
- def read_data_and_notes(parse: true)
120
+ def read_data_and_metadata(parse: true)
114
121
  data = read_data(parse: parse)
115
- notes = main_locale? ? (parse ? {} : '--- {}') : read_from_file("../#{@name}_notes.yml", parse: parse)
116
- [data, notes]
122
+ metadata = read_metadata(parse: parse)
123
+ [data, metadata]
117
124
  end
118
125
 
119
- def write_data_and_notes(data, notes)
126
+ def write_data_and_metadata(data, metadata)
120
127
  write_data(data)
121
- write_to_file("../#{@name}_notes.yml", notes) unless main_locale?
122
- end
123
-
124
- def write_raw_data(filename, data)
125
- File.open(File.join(@locales_dir, filename), 'w') do |file|
126
- file << data
127
- end
128
+ write_metadata(metadata)
128
129
  end
129
130
 
130
131
  def write_remote_version(data)
131
- write_to_file("../#{@name}_remote_version.yml",
132
- { 'VERSION' => read_versions(data) })
132
+ remote_version_file.write({ 'VERSION' => read_versions(data) }.to_yaml)
133
133
  end
134
134
 
135
135
  def main_locale
@@ -151,26 +151,48 @@ module I18n
151
151
  end
152
152
  end
153
153
 
154
- private
154
+ def data_file
155
+ file("#{name}.yml")
156
+ end
155
157
 
156
- def replace_errors_in_notes(all_notes, key, errors)
157
- return if all_notes[key].blank? && errors.empty?
158
+ def metadata_file
159
+ file("../#{name}_metadata.yml")
160
+ end
158
161
 
159
- notes = all_notes[key]
160
- notes = notes.present? ? notes.split("\n") : []
161
- notes = notes.reject { |n| n.start_with?("[error:") }
162
- all_notes[key] = (errors.map { |e| "[error: #{e}]" } + notes).join("\n")
162
+ def remote_version_file
163
+ file("../#{@name}_remote_version.yml")
163
164
  end
164
165
 
166
+ private
167
+
165
168
  def write_data(data)
166
- write_to_file("#{@name}.yml", data)
169
+ # we have to go from flat keys -> values to a hash that contains other hashes
170
+ complex_hash = {}
171
+ data.keys.sort.each do |key|
172
+ value = data[key]
173
+ assign_complex_key(complex_hash, key.split('.'), value.present? ? value : '')
174
+ end
175
+ data_file.write({ @name => complex_hash }.to_yaml)
176
+ end
177
+
178
+ def read_metadata(parse: true)
179
+ contents = if metadata_file.exist?
180
+ metadata_file.read
181
+ else
182
+ {}.to_yaml
183
+ end
184
+ parse ? Metadata.new(parse_yaml(contents)) : contents
167
185
  end
168
186
 
169
- def migrate_to_version(data, notes, version, direction)
187
+ def write_metadata(metadata)
188
+ metadata_file.write(metadata.to_yaml)
189
+ end
190
+
191
+ def migrate_to_version(data, metadata, version, direction)
170
192
  migrations.play_migration(version: version,
171
193
  locale: @name,
172
194
  data: data,
173
- notes: notes,
195
+ metadata: metadata,
174
196
  dictionary: @dictionary,
175
197
  direction: direction)
176
198
 
@@ -185,29 +207,12 @@ module I18n
185
207
  (data['VERSION'] && data['VERSION'].split("\n")) || []
186
208
  end
187
209
 
188
- def read_from_file(filename, parse: true)
189
- filename = File.join(@locales_dir, filename)
190
- begin
191
- contents = File.read(filename)
192
- return contents unless parse
193
-
194
- hash = {}
195
- add_to_hash(hash, YAML.load(contents)[@name.to_s])
196
- hash
197
- rescue
198
- puts "Error loading #{filename}"
199
- raise
200
- end
210
+ def file(filename)
211
+ Pathname.new(File.join(@locales_dir, filename))
201
212
  end
202
213
 
203
- def write_to_file(filename, hash)
204
- # we have to go from flat keys -> values to a hash that contains other hashes
205
- complex_hash = {}
206
- hash.keys.sort.each do |key|
207
- value = hash[key]
208
- assign_complex_key(complex_hash, key.split('.'), value.present? ? value : '')
209
- end
210
- write_raw_data(filename, { @name => complex_hash }.to_yaml)
214
+ def parse_yaml(string)
215
+ YAML.load(string)
211
216
  end
212
217
 
213
218
  # flattens new_hash and adds it to hash
@@ -0,0 +1,59 @@
1
+ # this class stores metadata about terms in a locale
2
+ # specifically things like errors, notes, autotranslated
3
+ # it acts kind of like a hash where you give it a key and it returns a metadatum object
4
+ class Metadata
5
+ def initialize(hash = {})
6
+ @hash = hash
7
+ end
8
+
9
+ def [](key)
10
+ if @hash[key].is_a?(Metadatum)
11
+ @hash[key]
12
+ else
13
+ @hash[key] = Metadatum.new(@hash[key])
14
+ end
15
+ end
16
+
17
+ def []=(key, value)
18
+ raise("you may only assign a metadatum") unless value.is_a?(Metadatum)
19
+ @hash[key] = value.dup
20
+ end
21
+
22
+ def delete(key)
23
+ metadatum = self[key]
24
+ @hash.delete(key)
25
+ metadatum
26
+ end
27
+
28
+ def to_h
29
+ compacted_hash = {}
30
+ @hash.keys.sort.each do |key|
31
+ value = @hash[key].to_h
32
+ compacted_hash[key] = value if value.present?
33
+ end
34
+ compacted_hash
35
+ end
36
+
37
+ def to_yaml
38
+ to_h.to_yaml
39
+ end
40
+
41
+ class Metadatum
42
+ attr_accessor :errors, :notes, :autotranslated
43
+
44
+ def initialize(hash)
45
+ safe_hash = hash || {}
46
+ @errors = safe_hash['errors'] || []
47
+ @notes = safe_hash['notes']
48
+ @autotranslated = !!safe_hash['autotranslated']
49
+ end
50
+
51
+ def to_h
52
+ hash = {}
53
+ hash['errors'] = @errors unless @errors.empty?
54
+ hash['notes'] = @notes unless @notes.blank?
55
+ hash['autotranslated'] = @autotranslated if autotranslated
56
+ hash
57
+ end
58
+ end
59
+ end
@@ -101,10 +101,10 @@ module I18n
101
101
 
102
102
  def assign_translation(key, term, overrides)
103
103
  if overrides[@locale_code.to_sym]
104
- @translations.set_term(key, value: overrides[@locale_code.to_sym])
104
+ @translations.set_term(key, value: overrides[@locale_code.to_sym], errors: [], autotranslated: false)
105
105
  else
106
- value, notes = @dictionary.lookup(term, key: key)
107
- @translations.set_term(key, value: value, notes: notes)
106
+ value, errors = @dictionary.lookup(term, key: key)
107
+ @translations.set_term(key, value: value, errors: errors, autotranslated: true)
108
108
  end
109
109
  end
110
110
  end
@@ -20,55 +20,70 @@ module I18n
20
20
  File.join(@migration_dir, "#{version}.rb")
21
21
  end
22
22
 
23
- def play_migration(version:, locale:, data:, notes:, dictionary:, direction:)
23
+ def play_migration(version:, locale:, data:, metadata:, dictionary:, direction:)
24
24
  filename = File.join(@migration_dir, "#{version}.rb")
25
25
  require filename
26
26
 
27
27
  raise("Can't parse version: #{version}") unless version =~ /^(\d{12})_(.*)/
28
28
  migration_class_name = "#{$2.camelcase}#{$1}"
29
29
 
30
- translations = Translations.new(data: data, notes: notes)
30
+ translations = Translations.new(locale_code: locale, data: data, metadata: metadata)
31
31
  migration = begin
32
- migration_class_name.constantize.new(locale_code: locale,
33
- translations: translations,
34
- dictionary: dictionary,
35
- direction: direction)
36
- rescue
37
- raise "Couldn't load migration #{migration_class_name} in #{filename}"
38
- end
32
+ migration_class_name.constantize.new(locale_code: locale,
33
+ translations: translations,
34
+ dictionary: dictionary,
35
+ direction: direction)
36
+ rescue
37
+ raise "Couldn't load migration #{migration_class_name} in #{filename}"
38
+ end
39
39
 
40
40
  migration.change
41
41
  end
42
42
 
43
43
  # This is a facade over our translations
44
44
  # data = all keys -> all translations in this locale
45
- # notes = some keys -> notes about the translation in this locale
45
+ # metadata = some keys -> metadata about the translation in this locale
46
46
  class Translations
47
- def initialize(data:, notes:)
48
- @data, @notes = data, notes
47
+ def initialize(locale_code:, data:, metadata:)
48
+ @locale_code, @data, @metadata = locale_code, data, metadata
49
49
  end
50
50
 
51
51
  def get_term(key)
52
52
  @data[key]
53
53
  end
54
54
 
55
- def set_term(key, value:, notes: nil)
55
+ def set_term(key, value:, errors:, autotranslated:)
56
+ # translated_term, errors = lookup_with_errors(term, key: key)
57
+ # unless errors.empty?
58
+ # STDERR.puts "'#{term}' => '#{translated_term}'\n#{errors.join(', ').red}"
59
+ # end
60
+ # [translated_term, (errors.map { |e| "[error: #{e}]" } + ['[autotranslated]']).join("\n")]
61
+
56
62
  @data[key] = value
57
- if notes.present?
58
- @notes[key] = notes
59
- else
60
- @notes.delete(key)
63
+ @metadata[key].errors = errors
64
+ @metadata[key].notes = nil
65
+ @metadata[key].autotranslated = autotranslated
66
+ if errors.present?
67
+ puts [
68
+ "=" * 100,
69
+ "#{@locale_code}: #{key}",
70
+ value,
71
+ errors.map { |e| "Error: #{e}".red }.join("\n"),
72
+ "=" * 100,
73
+ ].compact.join("\n")
74
+ # puts ["Error with #{@locale_code}: #{key}: \n #{errors.join("\n ")}".red,
75
+ # value].compact.join("\n")
61
76
  end
62
77
  end
63
78
 
64
79
  def delete_term(key)
65
80
  @data.delete(key)
66
- @notes.delete(key)
81
+ @metadata.delete(key)
67
82
  end
68
83
 
69
84
  def move_term(from, to)
70
85
  @data[to] = @data.delete(from)
71
- @notes[to] = @notes.delete(from)
86
+ @metadata[to] = @metadata.delete(from)
72
87
  end
73
88
  end
74
89
  end
@@ -64,20 +64,21 @@ end
64
64
 
65
65
  def rollback(locale_or_all)
66
66
  each_locale(locale_or_all) do |locale|
67
- locale.update_info do |data, notes|
68
- locale.rollback(data, notes)
67
+ locale.update_info do |data, metadata|
68
+ locale.rollback(data, metadata)
69
69
  end
70
70
  end
71
71
  end
72
72
 
73
73
  def pull(locale_or_all)
74
74
  each_locale(locale_or_all) do |locale|
75
- next if locale.main_locale?
75
+ # next if locale.main_locale?
76
76
  backend.pull(locale)
77
77
  end
78
78
  end
79
79
 
80
80
  def push(locale_or_all, force = false)
81
+ backend.sync_migrations(new_migrations)
81
82
  each_locale(locale_or_all, concurrency: config.push_concurrency) do |locale|
82
83
  backend.push(locale, force: force)
83
84
  wait
@@ -97,15 +98,29 @@ end
97
98
  def validate(locale_or_all)
98
99
  each_locale(locale_or_all, async: false) do |locale|
99
100
  next if locale.main_locale?
100
- locale.update_info do |data, notes|
101
- locale.validate(data, notes)
101
+ locale.update_info do |data, metadata|
102
+ locale.validate(data, metadata)
102
103
  end
103
104
  end
104
105
  end
105
106
 
107
+ private def report_locale_on_error(locale, &block)
108
+ begin
109
+ block.call locale
110
+ rescue Backends::CrowdTranslateError
111
+ puts "Error\n... while working with #{locale.name}\n#{$!.message}".red
112
+ # we want a readable error for our users, and the error happened in an external system,
113
+ # so don't give them a stack trace
114
+ rescue
115
+ puts "Error\n... while working with #{locale.name}\n#{$!.message}".red
116
+ raise
117
+ end
118
+ end
119
+
106
120
  private def each_locale(name = 'all',
107
- async: config.google_spreadsheet?,
108
- concurrency: config.concurrency)
121
+ async: true,
122
+ concurrency: config.concurrency,
123
+ &block)
109
124
  locale_names = name == 'all' ? all_locale_names : [name]
110
125
 
111
126
  if async
@@ -113,13 +128,13 @@ end
113
128
  locale_names.each_slice(concurrency) do |some_locale_names|
114
129
  threads = some_locale_names.map do |l|
115
130
  locale = locale_for(l)
116
- Thread.new { yield locale }
131
+ Thread.new { report_locale_on_error(locale, &block) }
117
132
  end
118
133
  threads.each(&:join)
119
134
  end
120
135
  else
121
136
  locale_names.each do |l|
122
- yield locale_for(l)
137
+ report_locale_on_error(locale_for(l), &block)
123
138
  end
124
139
  end
125
140
  end
@@ -1,5 +1,5 @@
1
1
  module I18n
2
2
  module Migrations
3
- VERSION = "1.2.4"
3
+ VERSION = "2.0.4"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n-migrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.4
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Lightsmith
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-04 00:00:00.000000000 Z
11
+ date: 2020-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -130,6 +130,7 @@ files:
130
130
  - lib/i18n/migrations/config.rb
131
131
  - lib/i18n/migrations/google_translate_dictionary.rb
132
132
  - lib/i18n/migrations/locale.rb
133
+ - lib/i18n/migrations/metadata.rb
133
134
  - lib/i18n/migrations/migration.rb
134
135
  - lib/i18n/migrations/migration_factory.rb
135
136
  - lib/i18n/migrations/migrator.rb