aigu 0.3.1 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|