i18n-migrations 1.2.3 → 2.0.3

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: 13e2d9e3d4aa1ec30fa0584c624d96388fac4aa31e11a6705fa61c39f8ff9a6c
4
- data.tar.gz: a88a38c024f92727aaf57bff6950ae5a9d3665d9b86cd26a382cecdb13ec085e
3
+ metadata.gz: 81cdab3c95ecdf3eadada65b83f94e51733cca841bda8862def4150f3ca182ce
4
+ data.tar.gz: bcce6ac35266718cc3e49ec7289cba1dedd8f0e012c916a82c1fe323390034a7
5
5
  SHA512:
6
- metadata.gz: e9eec7ad2d69e380a74b03b26e95be54c9bfb5d59f69cc1115f457f0fbecd0c6f0cec334d2f92b8970550cf42e2e0e9a46818d6b69a2bb7bfbb2f7bd086320ea
7
- data.tar.gz: be7bac66cd9be3219e930e9568762c1ffe07b313d7712ab4f7ce424d85625c4770b35bbf749d69dd528dd656c29ab1b615f1cd337ef612d9e32221ec2d3716dc
6
+ metadata.gz: 65f1fd2c2b047d76b74f6ca85699d8ee5e618b9d35a8e4d24d19a42c851833cfd4cc4f22668f31aecc9eaa2806fffa4801e262ac6193944497fadc48c3c4dcc3
7
+ data.tar.gz: e041b073626e8546226677bfb927a85a6c3235a233efe33a992b6e7dcb1da2f67c02469552b328b09e6b06ca642fd8579ec378fa08d605a5086342ec75cce5b6
@@ -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
@@ -6,8 +6,8 @@ module I18n
6
6
  class CrowdTranslateBackend
7
7
  attr_reader :client
8
8
 
9
- def initialize
10
- @client = CrowdTranslateClient.new
9
+ def initialize(client: CrowdTranslateClient.new)
10
+ @client = client
11
11
  end
12
12
 
13
13
  def pull(locale)
@@ -15,22 +15,49 @@ module I18n
15
15
  locale.migrate!
16
16
  end
17
17
 
18
- def push(locale, force = false)
19
- raise "CrowdTranslate does not support -f flag yet" if force
20
-
21
- # do this just once
22
- unless @migrations_synced
23
- @client.sync_migrations(locale.migrations)
24
- @migrations_synced = true
18
+ def push(locale, force: false)
19
+ if force
20
+ force_push(locale)
25
21
  end
22
+
26
23
  pull(locale)
27
24
  end
28
25
 
26
+ # this will replace everything about a locale, it will create a locale that does not yet exist
27
+ def force_push(locale)
28
+ data, metadata = locale.read_data_and_metadata(parse: false)
29
+ client.put_locale(locale.name,
30
+ name: locale.name,
31
+ yaml_file: data,
32
+ yaml_metadata_file: metadata)
33
+
34
+ end
35
+
29
36
  def pull_from_crowd_translate(locale)
30
- data = @client.get_locale_file(locale.name)
31
- locale.write_raw_data("#{locale.name}.yml", data)
37
+ data = client.get_locale_file(locale.name)
38
+ locale.data_file.write(data)
32
39
  locale.write_remote_version(YAML::load(data)[locale.name])
33
40
  end
41
+
42
+ def sync_migrations(migrations)
43
+ local_versions = migrations.all_versions
44
+ remote_versions = client.get_migration_versions
45
+
46
+ if (extra_versions = remote_versions - local_versions).present?
47
+ raise("You may not upload migrations to the server because it has migrations not found locally: " +
48
+ "#{extra_versions.join(', ')}")
49
+ end
50
+
51
+ if (versions_to_add = local_versions - remote_versions).present?
52
+ versions_to_add.each do |version|
53
+ begin
54
+ client.put_migration(version: version, ruby_file: migrations.get_migration(version: version))
55
+ rescue
56
+ raise CrowdTranslateError, "... while updating migration: #{migrations.migration_file(version: version)}\n#{$!.message}"
57
+ end
58
+ end
59
+ end
60
+ end
34
61
  end
35
62
  end
36
63
  end
@@ -4,58 +4,48 @@ 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
- def sync_migrations(migrations)
20
- local_versions = migrations.all_versions
21
- remote_versions = JSON.parse(get('migrations.json'))
19
+ def get_locale_file(locale_code)
20
+ get("locales/#{locale_code}.yml")
21
+ end
22
22
 
23
- if (extra_versions = remote_versions - local_versions).present?
24
- raise("You may not upload migrations to the server because it has migrations not found locally: " +
25
- "#{extra_versions.join(', ')}")
26
- end
23
+ def get_migration_versions
24
+ JSON.parse(get('migrations.json'))
25
+ end
27
26
 
28
- if (versions_to_add = local_versions - remote_versions).present?
29
- versions_to_add.each do |version|
30
- begin
31
- put("migrations/#{version}.json",
32
- migration: { ruby_file: migrations.get_migration(version: version) })
33
- rescue
34
- puts "There was an error updating migration:".red
35
- puts " #{migrations.migration_file(version: version)}".bold
36
- raise
37
- end
38
- end
39
- end
27
+ def put_migration(version:, ruby_file:)
28
+ put("migrations/#{version}.json", migration: { ruby_file: ruby_file })
40
29
  end
41
30
 
42
- def get_locale_file(locale_code)
43
- get("locales/#{locale_code}.yml")
31
+ def put_locale(locale_code, name:, yaml_file:, yaml_metadata_file:)
32
+ put("locales/#{locale_code}",
33
+ locale: { name: name, yaml_file: yaml_file, yaml_metadata_file: yaml_metadata_file })
44
34
  end
45
35
 
46
36
  private
47
37
 
48
38
  def get(path)
49
39
  puts "GET #{path}".bold
50
- parse_response @faraday.get path
40
+ parse_response 'GET', path, @faraday.get(path)
51
41
  end
52
42
 
53
43
  def put(path, params = {})
54
44
  puts "PUT #{path} #{params.to_s[0..50]}#{'...' if params.to_s.length > 50}".bold
55
- parse_response @faraday.put path, params
45
+ parse_response 'PUT', path, @faraday.put(path, params)
56
46
  end
57
47
 
58
- def parse_response(response)
48
+ def parse_response(method, path, response)
59
49
  if response.success?
60
50
  response.body
61
51
  else
@@ -64,9 +54,13 @@ module I18n
64
54
  rescue
65
55
  response.body
66
56
  end
67
- raise error
57
+ raise CrowdTranslateError, "... while calling #{method} #{File.join(base_url, path)}\n#{error}"
68
58
  end
69
59
  end
60
+
61
+ def base_url
62
+ "#{@server}/api/v1"
63
+ end
70
64
  end
71
65
  end
72
66
  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
@@ -16,7 +17,7 @@ module I18n
16
17
  locale.migrate!
17
18
  end
18
19
 
19
- def push(locale, force = false)
20
+ def push(locale, force: false)
20
21
  return if locale.main_locale?
21
22
 
22
23
  sheet = get_google_spreadsheet(locale.name)
@@ -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?
@@ -106,30 +108,28 @@ module I18n
106
108
  read_versions(read_data).last
107
109
  end
108
110
 
109
- def read_data
110
- read_from_file("#{@name}.yml")
111
- end
111
+ def read_data(parse: true)
112
+ contents = data_file.read
113
+ return contents unless parse
112
114
 
113
- def read_data_and_notes
114
- data = read_data
115
- notes = main_locale? ? {} : read_from_file("../#{@name}_notes.yml")
116
- [data, notes]
115
+ hash = {}
116
+ add_to_hash(hash, parse_yaml(contents)[@name.to_s])
117
+ hash
117
118
  end
118
119
 
119
- def write_data_and_notes(data, notes)
120
- write_data(data)
121
- write_to_file("../#{@name}_notes.yml", notes) unless main_locale?
120
+ def read_data_and_metadata(parse: true)
121
+ data = read_data(parse: parse)
122
+ metadata = read_metadata(parse: parse)
123
+ [data, metadata]
122
124
  end
123
125
 
124
- def write_raw_data(filename, data)
125
- File.open(File.join(@locales_dir, filename), 'w') do |file|
126
- file << data
127
- end
126
+ def write_data_and_metadata(data, metadata)
127
+ write_data(data)
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
185
+ end
186
+
187
+ def write_metadata(metadata)
188
+ metadata_file.write(metadata.to_yaml)
167
189
  end
168
190
 
169
- def migrate_to_version(data, notes, version, direction)
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,26 +207,12 @@ module I18n
185
207
  (data['VERSION'] && data['VERSION'].split("\n")) || []
186
208
  end
187
209
 
188
- def read_from_file(filename)
189
- filename = File.join(@locales_dir, filename)
190
- begin
191
- hash = {}
192
- add_to_hash(hash, YAML.load(File.read(filename))[@name.to_s])
193
- hash
194
- rescue
195
- puts "Error loading #{filename}"
196
- raise
197
- end
210
+ def file(filename)
211
+ Pathname.new(File.join(@locales_dir, filename))
198
212
  end
199
213
 
200
- def write_to_file(filename, hash)
201
- # we have to go from flat keys -> values to a hash that contains other hashes
202
- complex_hash = {}
203
- hash.keys.sort.each do |key|
204
- value = hash[key]
205
- assign_complex_key(complex_hash, key.split('.'), value.present? ? value : '')
206
- end
207
- write_raw_data(filename, { @name => complex_hash }.to_yaml)
214
+ def parse_yaml(string)
215
+ YAML.load(string)
208
216
  end
209
217
 
210
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,14 +20,14 @@ 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(data: data, metadata: metadata)
31
31
  migration = begin
32
32
  migration_class_name.constantize.new(locale_code: locale,
33
33
  translations: translations,
@@ -42,33 +42,37 @@ module I18n
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(data:, metadata:)
48
+ @data, @metadata = 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)
61
- end
63
+ @metadata[key].errors = errors
64
+ @metadata[key].notes = nil
65
+ @metadata[key].autotranslated = autotranslated
62
66
  end
63
67
 
64
68
  def delete_term(key)
65
69
  @data.delete(key)
66
- @notes.delete(key)
70
+ @metadata.delete(key)
67
71
  end
68
72
 
69
73
  def move_term(from, to)
70
74
  @data[to] = @data.delete(from)
71
- @notes[to] = @notes.delete(from)
75
+ @metadata[to] = @metadata.delete(from)
72
76
  end
73
77
  end
74
78
  end
@@ -64,22 +64,23 @@ 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
- backend.push(locale, force)
83
+ backend.push(locale, force: force)
83
84
  wait
84
85
  end
85
86
  end
@@ -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.3"
3
+ VERSION = "2.0.3"
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.3
4
+ version: 2.0.3
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-24 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