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 +4 -4
- data/bin/i18n-migrate +57 -52
- data/lib/i18n/migrations/backends/crowd_translate_backend.rb +38 -11
- data/lib/i18n/migrations/backends/crowd_translate_client.rb +24 -30
- data/lib/i18n/migrations/backends/google_spreadsheets_backend.rb +35 -8
- data/lib/i18n/migrations/google_translate_dictionary.rb +4 -8
- data/lib/i18n/migrations/locale.rb +66 -58
- data/lib/i18n/migrations/metadata.rb +59 -0
- data/lib/i18n/migrations/migration.rb +3 -3
- data/lib/i18n/migrations/migration_factory.rb +17 -13
- data/lib/i18n/migrations/migrator.rb +25 -10
- data/lib/i18n/migrations/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81cdab3c95ecdf3eadada65b83f94e51733cca841bda8862def4150f3ca182ce
|
4
|
+
data.tar.gz: bcce6ac35266718cc3e49ec7289cba1dedd8f0e012c916a82c1fe323390034a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65f1fd2c2b047d76b74f6ca85699d8ee5e618b9d35a8e4d24d19a42c851833cfd4cc4f22668f31aecc9eaa2806fffa4801e262ac6193944497fadc48c3c4dcc3
|
7
|
+
data.tar.gz: e041b073626e8546226677bfb927a85a6c3235a233efe33a992b6e7dcb1da2f67c02469552b328b09e6b06ca642fd8579ec378fa08d605a5086342ec75cce5b6
|
data/bin/i18n-migrate
CHANGED
@@ -10,59 +10,60 @@ def extract_option(name)
|
|
10
10
|
!!ARGV.delete(name)
|
11
11
|
end
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
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 =
|
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
|
19
|
-
|
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 =
|
31
|
-
locale.
|
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
|
20
|
-
|
21
|
-
|
19
|
+
def get_locale_file(locale_code)
|
20
|
+
get("locales/#{locale_code}.yml")
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
23
|
+
def get_migration_versions
|
24
|
+
JSON.parse(get('migrations.json'))
|
25
|
+
end
|
27
26
|
|
28
|
-
|
29
|
-
|
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
|
43
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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.
|
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,
|
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] =
|
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,
|
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
|
-
|
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 = [
|
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
|
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,
|
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
|
-
|
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,
|
50
|
-
yield data,
|
51
|
-
|
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,
|
56
|
-
migrate(data,
|
56
|
+
update_info do |data, metadata|
|
57
|
+
migrate(data, metadata)
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
60
|
-
def migrate(data,
|
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,
|
69
|
+
migrate_to_version(data, metadata, version, :up)
|
69
70
|
end
|
70
71
|
end
|
71
72
|
|
72
|
-
def rollback(data,
|
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,
|
82
|
+
migrate_to_version(data, metadata, last_version, :down)
|
82
83
|
end
|
83
84
|
|
84
85
|
def create(limit = nil)
|
85
|
-
new_data,
|
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],
|
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
|
-
|
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
|
-
|
111
|
-
|
111
|
+
def read_data(parse: true)
|
112
|
+
contents = data_file.read
|
113
|
+
return contents unless parse
|
112
114
|
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
120
|
-
|
121
|
-
|
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
|
125
|
-
|
126
|
-
|
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
|
-
|
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
|
-
|
154
|
+
def data_file
|
155
|
+
file("#{name}.yml")
|
156
|
+
end
|
155
157
|
|
156
|
-
def
|
157
|
-
|
158
|
+
def metadata_file
|
159
|
+
file("../#{name}_metadata.yml")
|
160
|
+
end
|
158
161
|
|
159
|
-
|
160
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
189
|
-
|
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
|
201
|
-
|
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,
|
107
|
-
@translations.set_term(key, value: value,
|
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:,
|
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,
|
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
|
-
#
|
45
|
+
# metadata = some keys -> metadata about the translation in this locale
|
46
46
|
class Translations
|
47
|
-
def initialize(data:,
|
48
|
-
@data, @
|
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:,
|
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
|
-
|
58
|
-
|
59
|
-
|
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
|
-
@
|
70
|
+
@metadata.delete(key)
|
67
71
|
end
|
68
72
|
|
69
73
|
def move_term(from, to)
|
70
74
|
@data[to] = @data.delete(from)
|
71
|
-
@
|
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,
|
68
|
-
locale.rollback(data,
|
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,
|
101
|
-
locale.validate(data,
|
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:
|
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 {
|
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
|
-
|
137
|
+
report_locale_on_error(locale_for(l), &block)
|
123
138
|
end
|
124
139
|
end
|
125
140
|
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:
|
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-
|
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
|