i18n-migrations 1.2.4 → 2.0.4

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 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