aigu 0.3.1 → 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 +4 -4
- data/.rubocop.yml +52 -0
- data/README.md +89 -5
- data/Rakefile +1 -1
- data/aigu.gemspec +7 -4
- data/bin/aigu +1 -1
- data/bin/phare +16 -0
- data/lib/aigu.rb +11 -0
- data/lib/aigu/android_exporter.rb +165 -0
- data/lib/aigu/android_importer.rb +130 -0
- data/lib/aigu/cli.rb +14 -9
- data/lib/aigu/core_exporter.rb +82 -0
- data/lib/aigu/core_importer.rb +82 -0
- data/lib/aigu/exporter.rb +1 -5
- data/lib/aigu/extensions/hash.rb +6 -0
- data/lib/aigu/importer.rb +1 -3
- data/lib/aigu/ios_exporter.rb +165 -0
- data/lib/aigu/ios_importer.rb +181 -0
- data/lib/aigu/mergeer.rb +51 -0
- data/lib/aigu/puller.rb +44 -0
- data/lib/aigu/pusher.rb +44 -0
- data/lib/aigu/unmergeer.rb +55 -0
- data/lib/aigu/version.rb +1 -1
- data/spec/aigu/android_exporter_spec.rb +40 -0
- data/spec/aigu/android_importer_spec.rb +131 -0
- data/spec/aigu/core_exporter_spec.rb +53 -0
- data/spec/aigu/core_importer_spec.rb +102 -0
- data/spec/aigu/exporter_spec.rb +2 -2
- data/spec/aigu/importer_spec.rb +2 -2
- data/spec/aigu/ios_exporter_spec.rb +115 -0
- data/spec/aigu/ios_importer_spec.rb +175 -0
- data/spec/spec_helper.rb +7 -1
- metadata +72 -4
data/lib/aigu/cli.rb
CHANGED
@@ -10,7 +10,7 @@ module Aigu
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def run
|
13
|
-
service_name = "#{@command}er".capitalize
|
13
|
+
service_name = "#{@command}er".split('_').map(&:capitalize).join
|
14
14
|
|
15
15
|
begin
|
16
16
|
service_class = Aigu.const_get(service_name)
|
@@ -28,22 +28,18 @@ module Aigu
|
|
28
28
|
def parse_options_from_yaml(options)
|
29
29
|
file = File.join(Dir.pwd, '.aigu.yml')
|
30
30
|
|
31
|
-
if File.
|
31
|
+
if File.exist?(file)
|
32
32
|
# Load YAML content
|
33
33
|
content = YAML.load_file(file)
|
34
34
|
|
35
|
-
# Symbolize keys
|
36
|
-
|
37
|
-
memo.merge! key.to_sym => value
|
38
|
-
end
|
39
|
-
|
40
|
-
# Merge with existing options
|
41
|
-
options = options.merge(content)
|
35
|
+
# Symbolize keys and merge with existing options
|
36
|
+
options = options.merge(content.symbolize_keys)
|
42
37
|
end
|
43
38
|
|
44
39
|
options
|
45
40
|
end
|
46
41
|
|
42
|
+
# rubocop:disable Metrics/MethodLength
|
47
43
|
def parse_options_from_arguments(options)
|
48
44
|
OptionParser.new do |opts|
|
49
45
|
opts.banner = 'Usage: aigu [options]'
|
@@ -72,6 +68,14 @@ module Aigu
|
|
72
68
|
options[:ignore] = ignore.split(',')
|
73
69
|
end
|
74
70
|
|
71
|
+
opts.on('--accent-api-key=', 'Accent API key') do |key|
|
72
|
+
options[:'accent-api-key'] = key
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on('--accent-url=', 'Accent URL (ex: http://accent.mirego.com)') do |url|
|
76
|
+
options[:'accent-url'] = url
|
77
|
+
end
|
78
|
+
|
75
79
|
opts.on_tail('-h', '--help', 'Show this message') do
|
76
80
|
puts opts
|
77
81
|
exit
|
@@ -81,5 +85,6 @@ module Aigu
|
|
81
85
|
|
82
86
|
options
|
83
87
|
end
|
88
|
+
# rubocop:enable Metrics/MethodLength
|
84
89
|
end
|
85
90
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Aigu
|
2
|
+
class CoreExporter
|
3
|
+
ENUM_LINE_REGEX = /^\s*(?<key>\w+)\s?\("(?<value_en>.*)",\s?"(?<value_fr>.*)"\),?\s$/
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
@output_file = opts[:'output-file']
|
7
|
+
@input_directory = opts[:'input-directory']
|
8
|
+
@locale = opts[:locale]
|
9
|
+
@ignore = opts[:ignore]
|
10
|
+
end
|
11
|
+
|
12
|
+
def process!
|
13
|
+
puts "Generating Accent JSON file `#{@output_file}` based on Core Java Enum files in `#{@input_directory}` directory"
|
14
|
+
|
15
|
+
if @ignore
|
16
|
+
print 'Ignoring '
|
17
|
+
puts @ignore.join(', ')
|
18
|
+
end
|
19
|
+
|
20
|
+
puts '---'
|
21
|
+
|
22
|
+
build_output
|
23
|
+
write_json_file
|
24
|
+
|
25
|
+
puts '---'
|
26
|
+
puts 'Done'
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def build_output
|
32
|
+
@output = {}
|
33
|
+
|
34
|
+
pattern = File.join(@input_directory, '**', 'CoreLocalizedStrings.java')
|
35
|
+
Dir[pattern].each do |filepath|
|
36
|
+
if ignored_filepath?(filepath)
|
37
|
+
puts "Ignoring #{filepath}"
|
38
|
+
else
|
39
|
+
puts "Processing #{filepath}"
|
40
|
+
|
41
|
+
content = File.open(filepath, 'rb:bom|utf-8').read
|
42
|
+
|
43
|
+
content = parse_java_file(content, @locale)
|
44
|
+
@output.merge! content
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
@output
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_json_file
|
52
|
+
file_path = @output_file
|
53
|
+
puts "Generating #{file_path}"
|
54
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
55
|
+
|
56
|
+
File.open(file_path, 'w+') do |file|
|
57
|
+
file << JSON.pretty_generate(JSON.parse(@output.to_json))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_java_file(java_file_content, locale = '')
|
62
|
+
string_hash = {}
|
63
|
+
|
64
|
+
java_file_content.each_line do |line|
|
65
|
+
next if line.include?('/* <-- !!LOCALIZED STRINGS!! */')
|
66
|
+
|
67
|
+
match_data = line.match(ENUM_LINE_REGEX)
|
68
|
+
if match_data
|
69
|
+
string_hash[match_data[:key]] = locale == 'fr' ? match_data[:value_fr] : match_data[:value_en]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
string_hash
|
74
|
+
end
|
75
|
+
|
76
|
+
def ignored_filepath?(filepath)
|
77
|
+
@ignore && @ignore.any? do |pattern|
|
78
|
+
return File.fnmatch(pattern, filepath, File::FNM_PATHNAME | File::FNM_DOTMATCH)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Aigu
|
2
|
+
class CoreImporter
|
3
|
+
ENUM_LINE_REGEX = /^\s*(?<key>\w+)\s?\("(?<value_en>.*)",\s?"(?<value_fr>.*)"\)(?<comma>,?)\s$/
|
4
|
+
|
5
|
+
def initialize(opts = {})
|
6
|
+
@input_file = opts[:'input-file']
|
7
|
+
@output_directory = opts[:'output-directory']
|
8
|
+
@locale = opts[:locale]
|
9
|
+
end
|
10
|
+
|
11
|
+
def process!
|
12
|
+
puts "Updating Core Java Enum files in `#{@output_directory}` based on Accent-generated `#{@input_file}` file"
|
13
|
+
puts '---'
|
14
|
+
|
15
|
+
parse_json
|
16
|
+
read_java
|
17
|
+
@java_content = update_in_memory(@java_content, @object, @locale)
|
18
|
+
write_java
|
19
|
+
|
20
|
+
puts '---'
|
21
|
+
puts 'Done'
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def parse_json
|
27
|
+
json = File.read(@input_file)
|
28
|
+
@object = JSON.parse(json)
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_java
|
32
|
+
pattern = File.join(@output_directory, '**', 'CoreLocalizedStrings.java')
|
33
|
+
@filepath = Dir[pattern].first
|
34
|
+
puts "Processing #{@filepath}"
|
35
|
+
|
36
|
+
@java_content = File.open(@filepath, 'rb:bom|utf-8').read
|
37
|
+
end
|
38
|
+
|
39
|
+
def write_java
|
40
|
+
puts "Updating #{@filepath}"
|
41
|
+
File.open(@filepath, 'w+:bom|utf-8') do |file|
|
42
|
+
file << @java_content
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_in_memory(java_file_content, content, locale = '')
|
47
|
+
in_section = false
|
48
|
+
new_content = ''
|
49
|
+
|
50
|
+
java_file_content.each_line do |line|
|
51
|
+
if line.include?('/* !!LOCALIZED STRINGS!! --> */')
|
52
|
+
in_section = true
|
53
|
+
elsif line.include?('/* <-- !!LOCALIZED STRINGS!! */')
|
54
|
+
in_section = false
|
55
|
+
end
|
56
|
+
|
57
|
+
if in_section
|
58
|
+
line_content = build_line_inside_localized_section(line, content, locale)
|
59
|
+
end
|
60
|
+
new_content << (line_content || line)
|
61
|
+
end
|
62
|
+
new_content
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_line_inside_localized_section(line, content, locale)
|
66
|
+
line_content = nil
|
67
|
+
match_data = line.match(ENUM_LINE_REGEX)
|
68
|
+
|
69
|
+
if match_data
|
70
|
+
key = match_data[:key]
|
71
|
+
|
72
|
+
line_content = " #{key}(\""
|
73
|
+
line_content << (locale != 'fr' ? content[key] : match_data[:value_en])
|
74
|
+
line_content << '", "'
|
75
|
+
line_content << (locale == 'fr' ? content[key] : match_data[:value_fr])
|
76
|
+
line_content << "\")#{match_data[:comma]}\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
line_content
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/aigu/exporter.rb
CHANGED
@@ -66,9 +66,7 @@ module Aigu
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def flattenize_content_values(hash)
|
69
|
-
|
70
|
-
|
71
|
-
hash.reduce({}) do |memo, (key, value)|
|
69
|
+
hash.each_with_object({}) do |(key, value), memo|
|
72
70
|
if value.is_a?(Array)
|
73
71
|
value.each_with_index do |array_value, index|
|
74
72
|
tainted_key = "#{key}___KEY___#{index}"
|
@@ -77,8 +75,6 @@ module Aigu
|
|
77
75
|
else
|
78
76
|
memo[key] = sanitize_value_to_string(value)
|
79
77
|
end
|
80
|
-
|
81
|
-
memo
|
82
78
|
end
|
83
79
|
end
|
84
80
|
|
data/lib/aigu/extensions/hash.rb
CHANGED
data/lib/aigu/importer.rb
CHANGED
@@ -68,7 +68,7 @@ module Aigu
|
|
68
68
|
end
|
69
69
|
|
70
70
|
def expand_content_values(content)
|
71
|
-
content.
|
71
|
+
content.each_with_object({}) do |(key, value), memo|
|
72
72
|
match_data = key.match(ARRAY_REGEX)
|
73
73
|
|
74
74
|
if match_data
|
@@ -79,8 +79,6 @@ module Aigu
|
|
79
79
|
else
|
80
80
|
memo[key] = sanitize_string_to_value(value)
|
81
81
|
end
|
82
|
-
|
83
|
-
memo
|
84
82
|
end
|
85
83
|
end
|
86
84
|
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Aigu
|
2
|
+
class IosExporter
|
3
|
+
PROP_LINE_REGEX = /^\s*"(?<key>.+)"\s?=\s?"(?<value>.*)";\s$/
|
4
|
+
|
5
|
+
DICT_DICT_OPEN_REGEX = /^\s*<dict>\s*$/
|
6
|
+
DICT_DICT_CLOSE_REGEX = /^\s*<\/dict>\s*$/
|
7
|
+
DICT_KEY_REGEX = /^\s*<key>(?<text>.*)<\/key>\s*$/
|
8
|
+
DICT_STRING_REGEX = /^\s*<string>(?<text>.*)<\/string>\s*$/
|
9
|
+
|
10
|
+
def initialize(opts = {})
|
11
|
+
@output_file = opts[:'output-file']
|
12
|
+
@input_directory = opts[:'input-directory']
|
13
|
+
@locale = opts[:locale]
|
14
|
+
@ignore = opts[:ignore]
|
15
|
+
end
|
16
|
+
|
17
|
+
def process!
|
18
|
+
puts "Generating Accent JSON file `#{@output_file}` based on IOS strings files in `#{@input_directory}` directory"
|
19
|
+
|
20
|
+
if @ignore
|
21
|
+
print 'Ignoring '
|
22
|
+
puts @ignore.join(', ')
|
23
|
+
end
|
24
|
+
|
25
|
+
puts '---'
|
26
|
+
|
27
|
+
build_output
|
28
|
+
write_json_file
|
29
|
+
|
30
|
+
puts '---'
|
31
|
+
puts 'Done'
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def build_output
|
37
|
+
@output = {}
|
38
|
+
@locale || @locale = 'en'
|
39
|
+
|
40
|
+
pattern = File.join(@input_directory, "#{@locale}.lproj", 'Localizable.strings')
|
41
|
+
filepath = Dir[pattern].first
|
42
|
+
|
43
|
+
assign_output_from_file!(filepath)
|
44
|
+
|
45
|
+
pattern = File.join(@input_directory, "#{@locale}.lproj", 'Localizable.stringsdict')
|
46
|
+
filepath = Dir[pattern].first
|
47
|
+
|
48
|
+
assign_output_from_file!(filepath)
|
49
|
+
|
50
|
+
@output
|
51
|
+
end
|
52
|
+
|
53
|
+
def assign_output_from_file!(filepath)
|
54
|
+
if filepath
|
55
|
+
puts "Processing #{filepath}"
|
56
|
+
|
57
|
+
file_content = File.open(filepath, 'rt').read
|
58
|
+
|
59
|
+
@output.merge! parse_strings_file(file_content)
|
60
|
+
else
|
61
|
+
puts 'Strings file not found'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_strings_file(file_content)
|
66
|
+
file_content.each_line.each_with_object({}) do |line, string_hash|
|
67
|
+
match_data = line.match(PROP_LINE_REGEX)
|
68
|
+
|
69
|
+
if match_data
|
70
|
+
string_hash[match_data[:key]] = replace_string_interpolations(match_data[:value])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# This takes a string and replaces Android interpolations to iOS interpolations.
|
76
|
+
# Note that %s works on iOS but it doesn’t work with NSString because they’re objects.
|
77
|
+
# To make it work with objects, we need to convert all %s to %@.
|
78
|
+
# Example: 'Android interpolation %s or %1$s' => 'iOS interpolation %@ or %1$@'.
|
79
|
+
def replace_string_interpolations(string)
|
80
|
+
string.gsub(/%([0-9]+\$)?s/, '%\\1@')
|
81
|
+
end
|
82
|
+
|
83
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
|
84
|
+
def parse_stringsdict_file(file_content)
|
85
|
+
# Quite custom parsing to reuse same logic on importer
|
86
|
+
# Assume a clean structure of file to KISS
|
87
|
+
|
88
|
+
in_root_dict = false
|
89
|
+
bundle_key = nil
|
90
|
+
string_key = nil
|
91
|
+
in_string_dict = false
|
92
|
+
plural_key = nil
|
93
|
+
|
94
|
+
file_content.each_line.each_with_object({}) do |line, string_hash|
|
95
|
+
if line.match(DICT_DICT_OPEN_REGEX)
|
96
|
+
# <dict>
|
97
|
+
if !in_root_dict
|
98
|
+
in_root_dict = true
|
99
|
+
elsif string_key
|
100
|
+
in_string_dict = true
|
101
|
+
end
|
102
|
+
elsif line.match(DICT_DICT_CLOSE_REGEX)
|
103
|
+
# </dict>
|
104
|
+
if in_string_dict
|
105
|
+
in_string_dict = false
|
106
|
+
string_key = nil
|
107
|
+
plural_key = nil
|
108
|
+
elsif bundle_key
|
109
|
+
bundle_key = nil
|
110
|
+
else
|
111
|
+
in_root_dict = false
|
112
|
+
end
|
113
|
+
elsif matched_data = line.match(DICT_KEY_REGEX)
|
114
|
+
# <key>..</key>
|
115
|
+
if in_root_dict
|
116
|
+
if bundle_key
|
117
|
+
if string_key
|
118
|
+
plural_key = matched_data[:text]
|
119
|
+
else
|
120
|
+
string_key = matched_data[:text]
|
121
|
+
end
|
122
|
+
else
|
123
|
+
bundle_key = matched_data[:text]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
elsif matched_data = line.match(DICT_STRING_REGEX)
|
127
|
+
# <string>..</string>
|
128
|
+
if string_key && !in_string_dict
|
129
|
+
string_key = nil
|
130
|
+
elsif in_string_dict && plural_key
|
131
|
+
if %w(zero one other).include? plural_key
|
132
|
+
hash_key = "__@DICT__#{bundle_key}__@STRING__#{string_key}__@#{plural_key.upcase}"
|
133
|
+
string_hash[hash_key] = cleanup_input_xml(matched_data[:text])
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
|
140
|
+
|
141
|
+
def write_json_file
|
142
|
+
file_path = @output_file
|
143
|
+
puts "Generating #{file_path}"
|
144
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
145
|
+
|
146
|
+
File.open(file_path, 'w+') do |file|
|
147
|
+
file << JSON.pretty_generate(JSON.parse(@output.to_json))
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Try to fix issue
|
152
|
+
def cleanup_input_xml(xml)
|
153
|
+
replace_html_name(xml, 'quot', '"')
|
154
|
+
replace_html_name(xml, 'amp', '&')
|
155
|
+
replace_html_name(xml, 'apos', "'")
|
156
|
+
replace_html_name(xml, 'lt', '<')
|
157
|
+
replace_html_name(xml, 'gt', '>')
|
158
|
+
xml
|
159
|
+
end
|
160
|
+
|
161
|
+
def replace_html_name(xml, html_name, character)
|
162
|
+
xml.gsub!(/&#{html_name};/, character)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module Aigu
|
2
|
+
class IosImporter
|
3
|
+
DICT_DICT_OPEN_REGEX = /^\s*<dict>\s*$/
|
4
|
+
DICT_DICT_CLOSE_REGEX = /^\s*<\/dict>\s*$/
|
5
|
+
DICT_KEY_REGEX = /^\s*<key>(?<text>.*)<\/key>\s*$/
|
6
|
+
DICT_STRING_REGEX = /^(?<left>\s*<string>)(?<text>.*)(?<right><\/string>\s*)$/
|
7
|
+
|
8
|
+
def initialize(opts = {})
|
9
|
+
@input_file = opts[:'input-file']
|
10
|
+
@output_directory = opts[:'output-directory']
|
11
|
+
@locale = opts[:locale]
|
12
|
+
end
|
13
|
+
|
14
|
+
def process!
|
15
|
+
puts "Generating IOS strings files in `#{@output_directory}` based on Accent-generated `#{@input_file}` file"
|
16
|
+
puts '---'
|
17
|
+
|
18
|
+
parse_json
|
19
|
+
strings, dict = split_dict
|
20
|
+
write_strings_files(strings)
|
21
|
+
write_stringsdict_file(dict)
|
22
|
+
|
23
|
+
puts '---'
|
24
|
+
puts 'Done'
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def parse_json
|
30
|
+
json = File.read(@input_file)
|
31
|
+
@object = JSON.parse(json)
|
32
|
+
end
|
33
|
+
|
34
|
+
def split_dict
|
35
|
+
strings = {}
|
36
|
+
dict = {}
|
37
|
+
|
38
|
+
@object.each_pair do |key, value|
|
39
|
+
match_data = key.match(/^__@DICT__.*/)
|
40
|
+
|
41
|
+
if match_data
|
42
|
+
dict[key] = value
|
43
|
+
else
|
44
|
+
strings[key] = value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
[strings, dict]
|
49
|
+
end
|
50
|
+
|
51
|
+
def write_strings_files(content)
|
52
|
+
@locale || @locale = 'en'
|
53
|
+
|
54
|
+
file_path = File.join(@output_directory, "#{@locale}.lproj", 'Localizable.strings')
|
55
|
+
puts "Generating #{file_path}"
|
56
|
+
FileUtils.mkdir_p(File.dirname(file_path))
|
57
|
+
|
58
|
+
File.open(file_path, 'w+:bom|utf-8') do |file|
|
59
|
+
file << format_strings_file(content)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def format_strings_file(content)
|
64
|
+
file_content = ''
|
65
|
+
|
66
|
+
content.each_pair do |key, value|
|
67
|
+
file_content << '"' << key << '" = "' << replace_string_interpolations(value) << '";' << "\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
file_content
|
71
|
+
end
|
72
|
+
|
73
|
+
# This takes a string and replaces iOS interpolations to Android interpolations
|
74
|
+
# so that it is reusable across platforms.
|
75
|
+
# Example: 'iOS interpolation %@ or %1$@' => 'Android interpolation %s or %1$s'.
|
76
|
+
def replace_string_interpolations(string)
|
77
|
+
string.gsub(/%([0-9]+\$)?@/, '%\\1s')
|
78
|
+
end
|
79
|
+
|
80
|
+
def write_stringsdict_file(content)
|
81
|
+
# uses same logic as exporter to parse current file & update in memory
|
82
|
+
|
83
|
+
# read file
|
84
|
+
file_path = File.join(@output_directory, "#{@locale}.lproj", 'Localizable.stringsdict')
|
85
|
+
puts "Updating #{file_path}"
|
86
|
+
|
87
|
+
file_content = File.open(file_path, 'rb:bom|utf-8').read
|
88
|
+
|
89
|
+
# in memory update
|
90
|
+
file_content = update_stringsdict_content(file_content, content)
|
91
|
+
|
92
|
+
# write back
|
93
|
+
File.open(file_path, 'w+:bom|utf-8') do |file|
|
94
|
+
file << file_content
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
|
99
|
+
def update_stringsdict_content(file_content, new_hash)
|
100
|
+
# Custom parsing in order to keep the file as-is, with any comments and spaces that dev used
|
101
|
+
|
102
|
+
new_content = ''
|
103
|
+
in_root_dict = false
|
104
|
+
bundle_key = nil
|
105
|
+
string_key = nil
|
106
|
+
in_string_dict = false
|
107
|
+
plural_key = nil
|
108
|
+
|
109
|
+
file_content.each_line do |line|
|
110
|
+
if line.match(DICT_DICT_OPEN_REGEX)
|
111
|
+
# <dict>
|
112
|
+
if !in_root_dict
|
113
|
+
in_root_dict = true
|
114
|
+
elsif string_key
|
115
|
+
in_string_dict = true
|
116
|
+
end
|
117
|
+
|
118
|
+
# dict open lines, keep as is
|
119
|
+
new_content << line
|
120
|
+
elsif line.match(DICT_DICT_CLOSE_REGEX)
|
121
|
+
# </dict>
|
122
|
+
if in_string_dict
|
123
|
+
in_string_dict = false
|
124
|
+
string_key = nil
|
125
|
+
plural_key = nil
|
126
|
+
elsif bundle_key
|
127
|
+
bundle_key = nil
|
128
|
+
else
|
129
|
+
in_root_dict = false
|
130
|
+
end
|
131
|
+
|
132
|
+
# dict close lines, keep as is
|
133
|
+
new_content << line
|
134
|
+
elsif matched_data = line.match(DICT_KEY_REGEX)
|
135
|
+
# <key>..</key>
|
136
|
+
if in_root_dict
|
137
|
+
if bundle_key
|
138
|
+
if string_key
|
139
|
+
plural_key = matched_data[:text]
|
140
|
+
else
|
141
|
+
string_key = matched_data[:text]
|
142
|
+
end
|
143
|
+
else
|
144
|
+
bundle_key = matched_data[:text]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# key lines, keep as is
|
149
|
+
new_content << line
|
150
|
+
elsif matched_data = line.match(DICT_STRING_REGEX)
|
151
|
+
# <string>..</string>
|
152
|
+
if string_key && !in_string_dict
|
153
|
+
string_key = nil
|
154
|
+
|
155
|
+
# useless string lines, keep as is
|
156
|
+
new_content << line
|
157
|
+
elsif in_string_dict && plural_key
|
158
|
+
if %w(zero one other).include? plural_key
|
159
|
+
hash_key = "__@DICT__#{bundle_key}__@STRING__#{string_key}__@#{plural_key.upcase}"
|
160
|
+
|
161
|
+
# replace value, keeps other parts of the line as is
|
162
|
+
new_content << matched_data[:left] << new_hash[hash_key].encode(xml: :text) << matched_data[:right]
|
163
|
+
else
|
164
|
+
# useless string lines, keep as is
|
165
|
+
new_content << line
|
166
|
+
end
|
167
|
+
else
|
168
|
+
# useless string lines, keep as is
|
169
|
+
new_content << line
|
170
|
+
end
|
171
|
+
else
|
172
|
+
# unknown line, keep as is
|
173
|
+
new_content << line
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
new_content
|
178
|
+
end
|
179
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Metrics/BlockNesting
|
180
|
+
end
|
181
|
+
end
|