i18n-migrations 1.2.3 → 2.0.3

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