lokale 0.1.2 → 0.1.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
  SHA1:
3
- metadata.gz: 28a49cbbd5450645fa6067b0fd096191bb7a1760
4
- data.tar.gz: 578fbe40eee917b39badc508ad62a5b63d748b9d
3
+ metadata.gz: 1605552979ae04cc32ed6b6eae40be4491cb6d43
4
+ data.tar.gz: 240de056b27a37bd6b7fc2cf7b07a147ccc0c9b0
5
5
  SHA512:
6
- metadata.gz: d8bcfbc01c0144b33d804a0b0d60b1b9224ffcb1b87af2730f4cf27f86ddb2e5eca0f032dcd6486279c2bfbdeb3506cea4661bdd5445fdc74372723357594c73
7
- data.tar.gz: 063b8a079d93f1189f9d261896d818472d0b6d193c80a044f2d70810e8426f758b4e0676678b8703433a2506b0c6edc343804e927fd2917021d4718e4712b4c7
6
+ metadata.gz: 272fa81006f2b4f4cf0a0f4066e34d4926433c4998e364604dcc7701f1a0c562e93895acc3050d5d40dfd4af588e900dcebfce5fc5f939ce53b4ce709111e29f
7
+ data.tar.gz: c9d92c14f1cbef0bf2faa8884d10c3e63a058c9af00664c8be534137171c0375b81adf163579969db4ae866962242de00590f242c43bccf4c33ced8fcf91322a
data/a.sh ADDED
@@ -0,0 +1,4 @@
1
+ rake install
2
+ cd ~/Documents/hand2hand-ios
3
+ lokale -e
4
+ cd ~/Documents/lokale
data/bin/lokale CHANGED
@@ -1,64 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'lokale'
4
- require 'lokale/find_dir'
5
- require 'lokale/colorize'
6
- require 'lokale/options'
7
4
 
8
- Settings.init
9
-
10
- git_path = find_git_repo
11
- if git_path.nil?
12
- puts "Could not find any git repository."
13
- exit
14
- end
15
-
16
- project_name = xcode_project_name(git_path)
17
- if project_name.nil?
18
- puts "Found git repository (#{git_path}) does not contain an Xcode project."
19
- exit
20
- end
21
-
22
- puts "Target Xcode project: '#{project_name}'".green
23
-
24
- ########################################################################################
25
-
26
- class Action
27
- def perform(agent, reporter)
28
- send(("perform_" + @type.to_s).to_sym, agent, reporter)
29
- end
30
-
31
- def perform_summary(agent, reporter)
32
- puts "Printing summary".blue
33
- reporter.print_summary
34
- end
35
-
36
- def perform_copy_base(agent, reporter)
37
- puts "Copying `en` strings files to `Base`".blue
38
- agent.copy_base
39
- puts
40
- end
41
-
42
- def perform_append(agent, reporter)
43
-
44
- end
45
- end
46
-
47
-
48
-
49
- settings = Settings.get
50
-
51
- macros = [
52
- Lokale::Macro.new("NSLocalizedString", /NSLocalizedString\("(.+?)",\s*comment:\s*"(.*?)"\)/, "Localizable.strings"),
53
- Lokale::Macro.new("PluralString", /String.localizedPlural\("(.+?)"/, nil),
54
- #LocalizationMacro.new("ObjC String", /NSLocalizedString\("(.*)",\s*(.*)\)/),
55
- #LocalizationMacro.new("ObjC Table String", /NSLocalizedStringFromTableInBundle\((.*?),/)
56
- ]
57
-
58
- agent = Lokale::Agent.new(git_path, macros)
59
- reporter = Lokale::Reporter.new(agent)
60
-
61
-
62
- settings.actions.each do |action|
63
- action.perform(agent, reporter)
64
- end
5
+ main = Lokale::Main.new
6
+ main.run
@@ -0,0 +1,16 @@
1
+
2
+ add_macro "NSLocalizedString" do |m|
3
+ m.localization_file = "Localizable.strings"
4
+
5
+ m.regex = /NSLocalizedString\("(.+?)",\s*comment:\s*"(.*?)"\)/
6
+ m.key_index = 0
7
+ m.note_index = 1
8
+ end
9
+
10
+ add_macro "PluralString" do |m|
11
+ m.regex = /String.localizedPlural\("(.+?)"/
12
+ m.key_index = 0
13
+ end
14
+
15
+ main_language "en"
16
+ base_language "Base"
@@ -0,0 +1,231 @@
1
+ require "lokale/version"
2
+ require "lokale/colorize"
3
+ require "lokale/util"
4
+ require "lokale/model"
5
+ require "xliffle"
6
+ require "set"
7
+
8
+
9
+ module Lokale
10
+ class LFile
11
+ def repeats
12
+ return [] if @parsed.nil?
13
+ uniq_keys = keys.to_set
14
+
15
+ keys.delete_if do |k|
16
+ if uniq_keys.include? k
17
+ uniq_keys.delete k
18
+ true
19
+ end
20
+ end
21
+ end
22
+
23
+ def keys
24
+ return nil if parsed.nil?
25
+ parsed.map(&:key)
26
+ end
27
+ end
28
+
29
+ class LString
30
+ def write_format
31
+ "/* #{note} */\n\"#{key}\" = \"#{str}\";\n"
32
+ end
33
+
34
+ def pretty
35
+ "\"#{key}\" = \"#{str}\";"
36
+ end
37
+
38
+ attr_accessor :source
39
+
40
+ def for_export(lang)
41
+ str = LString.new(@key, nil, @note, lang)
42
+ str.source = @str
43
+ str
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+
50
+ module Lokale
51
+ class Agent
52
+ attr_reader :proj_path, :macros, :lfiles, :sfiles_proceeded
53
+
54
+ def initialize(proj_path, macros)
55
+ @proj_path = proj_path
56
+ @macros = macros
57
+
58
+ @writer = Writer.new
59
+ @exporter = Exporter.new
60
+ @importer = Importer.new
61
+
62
+ get_localization_files
63
+ find_all_localization_calls
64
+ end
65
+
66
+ ###
67
+
68
+ def proj_files
69
+ if block_given?
70
+ Dir.glob("#{@proj_path}/**/**") { |f| yield f }
71
+ else
72
+ Dir.glob("#{@proj_path}/**/**")
73
+ end
74
+ end
75
+
76
+ def get_localization_files
77
+ return @lfiles unless @lfiles.nil?
78
+ @lfiles = proj_files.map { |file| LFile.try_to_read(file) }.compact
79
+ end
80
+
81
+ def find_all_localization_calls
82
+ @macros.each { |m| m.clear_calls }
83
+
84
+ @sfiles_proceeded = 0
85
+ proj_files do |file|
86
+ next unless file.source_file?
87
+
88
+ file_content = File.read(file)
89
+ @macros.each { |macro| macro.read_from file_content }
90
+ @sfiles_proceeded += 1
91
+ end
92
+ end
93
+
94
+ ###
95
+
96
+ def copy_base
97
+ main_lfiles = @lfiles.group_by { |f| f.lang }[Config.get.main_lang].select { |f| f.strings_file? }
98
+ base_lfiles = @lfiles.group_by { |f| f.lang }[Config.get.base_lang].select { |f| f.strings_file? }
99
+
100
+ main_lfiles.each do |en|
101
+ base = base_lfiles.select { |f| f.full_name == en.full_name }.sample
102
+ next if base.nil?
103
+ IO.copy_stream(en.path, base.path)
104
+ end
105
+ end
106
+
107
+ def append_new_macro_calls
108
+ @macros.each do |macro|
109
+ next if macro.localization_file.nil?
110
+ file = @lfiles.select { |lf| lf.full_name == macro.localization_file && lf.lang == Config.get.main_lang }.sample
111
+ next if file.nil?
112
+
113
+ found_strings = macro.found_strings.keys
114
+ proj_keys = found_strings.map { |ls| ls.key }.to_set
115
+ current_keys = file.keys.to_set
116
+ new_keys = proj_keys - current_keys
117
+
118
+ next if new_keys.empty?
119
+
120
+ new_lstrings = found_strings.select { |ls| new_keys.include? ls.key }
121
+ @writer.append_new_strings(new_lstrings, file)
122
+ end
123
+ end
124
+
125
+ def export_xliffs
126
+ files = Config.get.base_lang.nil? ? @lfiles : @lfiles.select { |lf| lf.lang != Config.get.base_lang }
127
+ main_lang = Config.get.main_lang
128
+ diffs = Exporter::Diff.find(files, main_lang)
129
+ @exporter.export(diffs) unless diffs.empty?
130
+ end
131
+
132
+ def try_to_import
133
+ end
134
+ end
135
+
136
+ class Writer
137
+ def find_append_point(content)
138
+ ignore_after = content =~ /^\s*?\/\/\s*?\n\s*?\/\/\s*?MARK/
139
+ string_regexp = /".*?"\s*=\s*".*?"\s*;\s*\n/
140
+
141
+ append_at = content.match(string_regexp).end(0) - 1
142
+ return if append_at.nil?
143
+ next_try = append_at
144
+ while next_try < ignore_after
145
+ append_at = next_try
146
+ next_try = content.match(string_regexp, next_try).end(0) - 1
147
+ end
148
+
149
+ append_at - 1
150
+ end
151
+
152
+ def append_new_strings(lstrings, file)
153
+ content = file.content
154
+
155
+ puts "Appending #{lstrings.size} new strings to file #{file.lang}/#{file.full_name}".blue
156
+ lstrings.each { |ls| puts ls.pretty }
157
+ puts
158
+
159
+ append_at = find_append_point(content)
160
+ data_to_append = "\n" + lstrings.map { |ls| ls.write_format }.join("\n")
161
+
162
+ content.insert(append_at, data_to_append)
163
+ File.write(file.path, content)
164
+ end
165
+ end
166
+
167
+
168
+ class Exporter
169
+ class Diff
170
+ attr_reader :lang, :missing_strings
171
+
172
+ def self.find(files, main_lang)
173
+ groups = files.group_by { |lf| lf.lang }
174
+ main_files = groups[main_lang]
175
+ diffs = []
176
+ groups.each do |lang, files|
177
+ next if lang == main_lang
178
+ diffs << Diff.new(main_files, files, lang)
179
+ end
180
+ diffs.delete_if { |d| d.empty? }
181
+ end
182
+
183
+ def initialize(main_files, lfiles, lang)
184
+ @lang = lang
185
+ @missing_strings = Hash.new { |h, k| h[k] = [] }
186
+ lfiles.each do |lf|
187
+ next unless lf.strings_file?
188
+ counterpart = main_files.select { |m| m.full_name == lf.full_name }.sample
189
+ next if counterpart.nil?
190
+
191
+ missing_keys = counterpart.keys - lf.keys
192
+ next if missing_keys.empty?
193
+
194
+ counterpart.parsed.each do |lstr|
195
+ next unless missing_keys.include? lstr.key
196
+ @missing_strings[lf.full_name] << lstr.for_export(lang)
197
+ end
198
+ end
199
+ end
200
+
201
+ def empty?
202
+ @missing_strings.empty? || @missing_strings.all? { |file_name, strings| strings.empty? }
203
+ end
204
+ end
205
+
206
+
207
+ def export(diffs)
208
+ # puts "Exporting stuff"
209
+ diffs.each do |d|
210
+ puts "Writing xliff for `#{d.lang}` language. Missing strings count: #{d.missing_strings.values.map { |e| e.size }.reduce(:+)}"
211
+
212
+ xliffle = Xliffle.new
213
+ d.missing_strings.each do |lfile, strings|
214
+ xfile = xliffle.file(lfile, Config.get.main_lang, d.lang)
215
+ strings.each do |lstr|
216
+ xfile.string(lstr.key, lstr.source, nil).note(lstr.note, 0)
217
+ end
218
+ end
219
+
220
+ file_name = "#{d.lang}.xliff"
221
+ File.write(file_name, xliffle.to_xliff)
222
+ end
223
+ end
224
+ end
225
+
226
+ class Importer
227
+ def look_for_strings(root_path)
228
+ end
229
+ end
230
+ end
231
+
@@ -0,0 +1,68 @@
1
+
2
+ require "optparse"
3
+ require "lokale/util"
4
+
5
+ class Action
6
+ attr_accessor :type, :arg
7
+
8
+ def initialize(type)
9
+ @type = type
10
+ end
11
+
12
+ def self.summary; Action.new(:summary) end
13
+ def self.copy_base; Action.new(:copy_base) end
14
+ def self.append; Action.new(:append) end
15
+ def self.export; Action.new(:export) end
16
+ def self.import; Action.new(:import) end
17
+ end
18
+
19
+ class Config
20
+ attr_accessor :actions
21
+
22
+ def self.init
23
+ return unless @config.nil?
24
+
25
+ actions = []
26
+
27
+ OptionParser.new do |opts|
28
+ opts.banner = "Usage: lokale [-bsh]"
29
+
30
+ opts.on("-b", "--copy-base", "Copies 'en' localization files to 'Base'") do |n|
31
+ actions << Action.copy_base
32
+ end
33
+
34
+ opts.on("-s", "--summary", "Prints project summary") do |n|
35
+ actions << Action.summary
36
+ end
37
+
38
+ opts.on("-a", "--append", "Appends new strings to english localization file") do |n|
39
+ actions << Action.append
40
+ end
41
+
42
+ opts.on("-e", "--export", "Creates xliff files with missing localization") do |n|
43
+ actions << Action.export
44
+ end
45
+
46
+ opts.on("-i", "--import", "Looks for xliffs in project dir and imports whatever possible") do |n|
47
+ actions << Action.import
48
+ end
49
+
50
+ opts.on("-h", "--help", "Prints this help") do
51
+ puts opts
52
+ exit
53
+ end
54
+ end.parse!
55
+
56
+ actions << Action.summary if actions.empty?
57
+
58
+ @config = Config.new
59
+ @config.actions = actions
60
+ end
61
+
62
+ def self.get
63
+ init if @config.nil?
64
+ @config
65
+ end
66
+ end
67
+
68
+
@@ -2,38 +2,59 @@
2
2
  # Based on https://github.com/mojombo/grit/pull/178 by https://github.com/derricks
3
3
 
4
4
 
5
- # Returns true if the given path represents a root directory (/ or C:/)
6
- def root_directory?(file_path)
7
- # Implementation inspired by http://stackoverflow.com/a/4969416:
8
- # Does file + ".." resolve to the same directory as file_path?
9
- File.directory?(file_path) &&
10
- File.expand_path(file_path) == File.expand_path(File.join(file_path, '..'))
11
- end
5
+ module ProjectFinder
6
+
7
+ # Returns true if the given path represents a root directory (/ or C:/)
8
+ def self.root_directory?(file_path)
9
+ # Implementation inspired by http://stackoverflow.com/a/4969416:
10
+ # Does file + ".." resolve to the same directory as file_path?
11
+ File.directory?(file_path) &&
12
+ File.expand_path(file_path) == File.expand_path(File.join(file_path, '..'))
13
+ end
14
+
15
+ # Returns the git root directory given a path inside the repo. Returns nil if
16
+ # the path is not in a git repo.
17
+ def self.find_git_repo(start_path = '.')
18
+ raise NoSuchPathError unless File.exists?(start_path)
12
19
 
13
- # Returns the git root directory given a path inside the repo. Returns nil if
14
- # the path is not in a git repo.
15
- def find_git_repo(start_path = '.')
16
- raise NoSuchPathError unless File.exists?(start_path)
17
-
18
- current_path = File.expand_path(start_path)
19
-
20
- # for clarity: set to an explicit nil and then just return whatever
21
- # the current value of this variable is (nil or otherwise)
22
- return_path = nil
23
-
24
- until root_directory?(current_path)
25
- if File.exists?(File.join(current_path, '.git'))
26
- # done
27
- return_path = current_path
28
- break
29
- else
30
- # go up a directory and try again
31
- current_path = File.dirname(current_path)
20
+ current_path = File.expand_path(start_path)
21
+
22
+ # for clarity: set to an explicit nil and then just return whatever
23
+ # the current value of this variable is (nil or otherwise)
24
+ return_path = nil
25
+
26
+ until root_directory?(current_path)
27
+ if File.exists?(File.join(current_path, '.git'))
28
+ # done
29
+ return_path = current_path
30
+ break
31
+ else
32
+ # go up a directory and try again
33
+ current_path = File.dirname(current_path)
34
+ end
32
35
  end
36
+ return_path
33
37
  end
34
- return_path
35
- end
36
38
 
37
- def xcode_project_name(path)
38
- Dir.glob("#{path}/**/**") { |file| return $1 if file =~ /\/(.+?)\.(?:xcodeproj|xcworkspace)$/ }
39
+ ##
40
+
41
+ def self.xcode_project_name(path)
42
+ Dir.glob("#{path}/**/**") { |file| return $1 if file =~ /\/(.+?)\.(?:xcodeproj|xcworkspace)$/ }
43
+ end
44
+
45
+ ##
46
+
47
+ def self.find_proj
48
+ project_path = find_git_repo
49
+ if project_path.nil?
50
+ raise "Could not find any git repository."
51
+ end
52
+
53
+ project_name = xcode_project_name(project_path)
54
+ if project_name.nil?
55
+ raise "Found git repository (#{project_path}) does not contain an Xcode project."
56
+ end
57
+
58
+ return project_path, project_name
59
+ end
39
60
  end
@@ -0,0 +1,98 @@
1
+
2
+ require "lokale/config"
3
+ require "lokale/model"
4
+
5
+ DEFAULT_LOKALEFILE = <<FILE
6
+
7
+ add_macro "NSLocalizedString" do |m|
8
+ m.localization_file = "Localizable.strings"
9
+
10
+ m.regex = /NSLocalizedString\\("(.+?)",\\s*comment:\\s*"(.*?)"\\)/
11
+ m.key_index = 0
12
+ m.note_index = 1
13
+ end
14
+
15
+ add_macro "PluralString" do |m|
16
+ m.regex = /String.localizedPlural\\("(.+?)"/
17
+ m.key_index = 0
18
+ end
19
+
20
+ main_language "en"
21
+ base_language "Base"
22
+
23
+ FILE
24
+
25
+
26
+ class Config
27
+ def read_lokalefile(project_path)
28
+ lokalefile_path = File.join(project_path, ".lokale")
29
+
30
+ if File.file? lokalefile_path
31
+ read_config_from_file(lokalefile_path)
32
+ else
33
+ read_default_config
34
+ end
35
+ end
36
+
37
+ def read_default_config
38
+ reset_config
39
+ instance_eval(DEFAULT_LOKALEFILE)
40
+ end
41
+
42
+ def read_config_from_file(file_path)
43
+ content = File.read(file_path)
44
+ reset_config
45
+ instance_eval(content)
46
+ fill_defaults
47
+ end
48
+ end
49
+
50
+
51
+ class Config
52
+
53
+ attr_reader :macros
54
+ attr_reader :main_lang, :base_lang
55
+
56
+ def reset_config
57
+ @macros = nil
58
+ @main_lang = nil
59
+ @base_lang = nil
60
+ end
61
+
62
+ def fill_defaults
63
+ default = Config.new
64
+ default.read_default_config
65
+
66
+ @macros ||= default.macros
67
+ @main_lang ||= default.main_lang
68
+ @base_lang ||= default.base_lang
69
+ end
70
+
71
+ private
72
+
73
+ def add_macro(name)
74
+ macro = Lokale::Macro.new(name)
75
+ yield macro
76
+ @macros ||= []
77
+ @macros << macro
78
+ end
79
+
80
+ def main_language(l)
81
+ @main_lang = l
82
+ end
83
+
84
+ def base_language(l)
85
+ @base_lang = l
86
+ end
87
+ end
88
+
89
+
90
+ def test
91
+ Config.get.read_lokalefile File.dirname(__FILE__)
92
+ p Config.get.macros
93
+ p Config.get.main_lang
94
+ p Config.get.base_lang
95
+ end
96
+
97
+ # test
98
+
@@ -0,0 +1,137 @@
1
+
2
+ class String
3
+ def localization_file?
4
+ File.directory?(self) == false &&
5
+ (self =~ /\/Pods\//) == nil &&
6
+ (self =~ /\.bundle\//) == nil &&
7
+ (self =~ /\/(.{1,8})\.lproj\//)
8
+ end
9
+
10
+ def source_file?
11
+ (File.directory?(self) == false) && (self =~ /\/Pods\//).nil? && ((self =~ /\.(swift|h|m)$/) != nil)
12
+ end
13
+ end
14
+
15
+
16
+ module Lokale
17
+ class LString
18
+ attr_accessor :key, :str, :note, :target
19
+
20
+ def initialize(key, str, note, target)
21
+ @key = key; @str = str; @note = note; @target = target
22
+ end
23
+
24
+ def self.strings_from_file(file_path, lang)
25
+ regex = /(?:\/\* (.+) \*\/.*\n)?"(.+)" *= *"(.+)";/
26
+ File.read(file_path).scan(regex).map { |m| LString.new(m[1], m[2], m[0], lang) }
27
+ end
28
+
29
+ def inspect
30
+ "<\#LS:#{@key}; s:#{@str}; n:#{@note}; t:#{@target}>"
31
+ end
32
+ end
33
+
34
+ #
35
+
36
+ class LFile
37
+ attr_reader :path, :lang, :name, :type
38
+ def initialize(file_path)
39
+ @path = file_path
40
+
41
+ File.basename(file_path) =~ /^(.+)\.([^\.]*?)$/
42
+ @name = $1
43
+ @type = $2
44
+
45
+ file_path =~ /\/(.{1,8})\.lproj\//
46
+ @lang = $1
47
+ end
48
+
49
+ def self.try_to_read(file_path)
50
+ return nil unless file_path.localization_file?
51
+ LFile.new(file_path)
52
+ end
53
+
54
+ def inspect
55
+ "<LF:#{@lang}/#{full_name}>"
56
+ end
57
+
58
+ def full_name
59
+ "#{@name}.#{@type}"
60
+ end
61
+
62
+ def strings_file?
63
+ @type == "strings" || @type == "stringsdict"
64
+ end
65
+
66
+ def content
67
+ File.read(@path)
68
+ end
69
+
70
+ def parsed
71
+ return @parsed unless @parsed.nil?
72
+ @parsed = case type
73
+ when "strings" then LString.strings_from_file(@path, @lang)
74
+ when "stringsdict" then []
75
+ else nil
76
+ end
77
+ end
78
+ end
79
+
80
+ #
81
+
82
+ class Macro
83
+ attr_accessor :regex, :name, :localization_file, :key_index, :note_index
84
+ attr_reader :found_strings
85
+
86
+ def initialize(name)
87
+ @name = name
88
+ clear_calls
89
+ end
90
+
91
+ def clear_calls
92
+ @found_strings = Hash.new { |h, k| h[k] = 0 }
93
+ end
94
+
95
+ def read_from(file)
96
+ file.scan(@regex) do |m|
97
+ lang = Config.get.main_lang
98
+ key = m[key_index] unless key_index.nil?
99
+ val = m[note_index] unless note_index.nil?
100
+ lstr = LString.new(key, val, val, lang)
101
+ @found_strings[lstr] += 1
102
+ end
103
+ end
104
+
105
+ def uniq_count
106
+ @found_strings.size
107
+ end
108
+
109
+ def total_count
110
+ @found_strings.values.reduce(:+) || 0
111
+ end
112
+
113
+ end
114
+ end
115
+
116
+
117
+
118
+
119
+ module Lokale
120
+
121
+ # Hashable
122
+ class LString
123
+ def fields
124
+ [@key, @str, @note, @target]
125
+ end
126
+
127
+ def ==(other)
128
+ self.class === other && other.fields == fields
129
+ end
130
+
131
+ alias eql? ==
132
+
133
+ def hash
134
+ fields.hash
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,102 @@
1
+ module Lokale
2
+ class Reporter
3
+ def initialize(agent)
4
+ @agent = agent
5
+ end
6
+
7
+ def print_summary
8
+ print_macro_calls_summary
9
+ print_macro_table
10
+ print_files_table
11
+ print_repeats_report
12
+ print_diferences_report
13
+ end
14
+
15
+ def print_macro_calls_summary
16
+ total_macro_calls = @agent.macros.map(&:total_count).reduce(:+)
17
+ uniq_macro_calls = @agent.macros.map(&:uniq_count).reduce(:+)
18
+ puts "Found #{total_macro_calls} localization macro calls in #{@agent.sfiles_proceeded} files."
19
+ puts "Uniq macro calls: #{uniq_macro_calls}"
20
+ puts
21
+ end
22
+
23
+ def print_files_table
24
+ languages = @agent.lfiles.map { |e| e.lang }.to_set.to_a
25
+ files = @agent.lfiles.map { |e| e.full_name }.to_set.to_a
26
+
27
+ puts "Found #{@agent.lfiles.size} localized files for #{languages.size} languages."
28
+
29
+ description_header = "[*]".rpadded(36)
30
+ languages.each { |l| description_header << l.rpadded(8) }
31
+ puts description_header
32
+
33
+ files.each do |f|
34
+ description_string = f.rpadded(36)
35
+ languages.each do |l|
36
+ lfile = @agent.lfiles.select { |lf| lf.full_name == f && lf.lang == l }
37
+ description_string << (lfile.empty? ? "-" : lfile[0].parsed.nil? ? "*" : "#{lfile[0].parsed.size}").rpadded(8)
38
+ end
39
+ puts description_string
40
+ end
41
+ puts
42
+ end
43
+
44
+ def print_macro_table
45
+ @agent.macros.each do |macro|
46
+ puts "#{macro.name}:".rpadded(24) + "total: #{macro.total_count}".rpadded(16) + "uniq: #{macro.uniq_count}"
47
+ end
48
+ puts
49
+ end
50
+
51
+ def print_repeats_report
52
+ repeats_repot = ""
53
+ @agent.lfiles.each do |lf|
54
+ repeats = lf.repeats
55
+ next if repeats.count.zero?
56
+ repeats_repot << "#{lf.lang}/#{lf.full_name} repeats:\n"
57
+ repeats_repot << repeats.join("\n")
58
+ repeats_repot << "\n"
59
+ end
60
+
61
+ if repeats_repot.empty?
62
+ puts "Repeats not found.".green
63
+ puts
64
+ else
65
+ puts "Found repeats in strings files.".red
66
+ puts repeats_repot
67
+ puts
68
+ end
69
+ end
70
+
71
+ def print_diferences_report
72
+ diferences_repot = ""
73
+
74
+ @agent.lfiles.group_by { |f| f.full_name }.each do |file_name, files|
75
+ base_lang = files.any? { |f| f.lang == "Base" } ? "Base" : "en"
76
+ files = files.select { |f| f.lang != base_lang }
77
+ all_keys = files.map(&:keys).compact.map(&:to_set)
78
+ next if all_keys.empty?
79
+ united_keys = all_keys.reduce(:|)
80
+ all_keys.map! { |ks| united_keys - ks }
81
+ next if all_keys.map(&:length).reduce(:+).zero?
82
+
83
+ diferences_repot << "Missing keys in file \"#{file_name}\":\n"
84
+ all_keys.zip(files) do |missing_keys, lfile|
85
+ next if missing_keys.size.zero?
86
+ diferences_repot << "*".red + " #{lfile.lang} - #{missing_keys.size} key(s):\n"
87
+ missing_keys.each { |k| diferences_repot << "#{k}\n" }
88
+ end
89
+ diferences_repot << "\n"
90
+ end
91
+
92
+ if diferences_repot.empty?
93
+ puts "Localization files are full.".green
94
+ puts
95
+ else
96
+ puts "Localization files are not full.".red
97
+ puts diferences_repot
98
+ puts
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,16 @@
1
+ class String
2
+ def rpadded(count=20)
3
+ "%-#{count}.#{count}s" % self
4
+ end
5
+
6
+ def lpadded(count=20)
7
+ "%#{count}.#{count}s" % self
8
+ end
9
+ end
10
+
11
+ module Then
12
+ def then
13
+ yield self
14
+ return self
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module Lokale
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
data/lib/lokale.rb CHANGED
@@ -1,305 +1,84 @@
1
- require "lokale/version"
2
- require "lokale/colorize"
3
- require "set"
4
1
 
5
- class String
6
- def rpadded(count=20)
7
- "%-#{count}.#{count}s" % self
8
- end
2
+ require 'lokale/find_dir'
3
+ require 'lokale/colorize'
4
+ require 'lokale/config'
5
+ require 'lokale/reporter'
6
+ require 'lokale/lokalefile'
7
+ require 'lokale/agent'
9
8
 
10
- def lpadded(count=20)
11
- "%#{count}.#{count}s" % self
9
+ class Action
10
+ def print(str)
11
+ puts str.blue
12
12
  end
13
13
 
14
- def localization_file?
15
- File.directory?(self) == false &&
16
- (self =~ /\/Pods\//) == nil &&
17
- (self =~ /\.bundle\//) == nil &&
18
- (self =~ /\/(.{1,8})\.lproj\//)
14
+ def perform(agent, reporter)
15
+ send(("perform_" + @type.to_s).to_sym, agent, reporter)
19
16
  end
20
17
 
21
- def source_file?
22
- (File.directory?(self) == false) && (self =~ /\/Pods\//).nil? && ((self =~ /\.(swift|h|m)$/) != nil)
18
+ def perform_summary(agent, reporter)
19
+ print "Printing summary...".blue
20
+ reporter.print_summary
23
21
  end
24
- end
25
-
26
-
27
- module Lokale
28
- class LString
29
- attr_accessor :key, :str, :note, :target
30
-
31
- def initialize(key, str, note, target)
32
- @key = key; @str = str; @note = note; @target = target
33
- end
34
22
 
35
- def self.strings_from_file(file_path, lang)
36
- regex = /(?:\/* (.+) *\/.*\n)?"(.+)" *= *"(.+)";/
37
- File.read(file_path).scan(regex).map { |m| LString.new(m[1], m[2], m[0], lang) }
38
- end
23
+ def perform_copy_base(agent, reporter)
24
+ print "Copying `en` strings files to `Base`...".blue
25
+ agent.copy_base
39
26
  end
40
27
 
41
- #
42
-
43
- class LFile
44
- attr_reader :path, :lang, :name, :type
45
- def initialize(file_path)
46
- @path = file_path
47
-
48
- File.basename(file_path) =~ /^(.+)\.([^\.]*?)$/
49
- @name = $1
50
- @type = $2
51
-
52
- file_path =~ /\/(.{1,8})\.lproj\//
53
- @lang = $1
54
- end
55
-
56
- def self.try_to_read(file_path)
57
- return nil unless file_path.localization_file?
58
- LFile.new(file_path)
59
- end
60
-
61
- def inspect
62
- "<#{@lang}/#{full_name}>"
63
- end
64
-
65
- def full_name
66
- "#{@name}.#{@type}"
67
- end
68
-
69
- def parsed
70
- return @parsed unless @parsed.nil?
71
- @parsed = case type
72
- when "strings" then LString.strings_from_file(@path, @lang)
73
- when "stringsdict" then []
74
- else nil
75
- end
76
- end
77
-
78
- def repeats
79
- return [] if @parsed.nil?
80
- keys = @parsed.map(&:key)
81
- uniq_keys = keys.to_set
82
-
83
- keys.delete_if do |k|
84
- if uniq_keys.include? k
85
- uniq_keys.delete k
86
- true
87
- end
88
- end
89
- end
90
-
91
- def keys
92
- return nil if parsed.nil?
93
- parsed.map(&:key)
94
- end
95
-
96
-
97
- def strings_file?
98
- @type == "strings" || @type == "stringsdict"
99
- end
28
+ def perform_append(agent, reporter)
29
+ print "Appending new macro calls to localization files...".blue
30
+ agent.append_new_macro_calls
100
31
  end
101
32
 
102
- #
103
-
104
- class Macro
105
- attr_reader :regex, :found_strings, :name, :file_name
106
-
107
- def initialize(name, regex, file_name)
108
- @name = name
109
- @regex = regex
110
- @file_name = file_name
111
- clear_calls
112
- end
113
-
114
- def clear_calls
115
- @found_strings = Hash.new { |h, k| h[k] = 0 }
116
- end
117
-
118
- def read_from(file)
119
- file.scan(@regex) { |m| @found_strings[m] += 1 }
120
- end
121
-
122
- def uniq_count
123
- @found_strings.size
124
- end
125
-
126
- def total_count
127
- @found_strings.values.reduce(:+) || 0
128
- end
33
+ def perform_export(agent, reporter)
34
+ print "Preparing xliff files with new localized strings...".blue
35
+ agent.export_xliffs
129
36
  end
130
37
 
38
+ def perform_import(agent, reporter)
39
+ print "Attempting to import new strings...".blue
40
+ agent.try_to_import
41
+ end
42
+ end
131
43
 
132
- #
133
- #
134
- #
135
- #
136
-
137
- class Agent
138
- attr_reader :proj_path, :macros, :lfiles, :sfiles_proceeded
139
-
140
- def initialize(proj_path, macros)
141
- @proj_path = proj_path
142
- @macros = macros
143
-
144
- get_localization_files
145
- find_all_localization_calls
146
- end
147
-
148
- def proj_files
149
- if block_given?
150
- Dir.glob("#{@proj_path}/**/**") { |f| yield f }
151
- else
152
- Dir.glob("#{@proj_path}/**/**")
153
- end
154
- end
155
-
156
- def get_localization_files
157
- return @lfiles unless @lfiles.nil?
158
- @lfiles = proj_files.map { |file| LFile.try_to_read(file) }.compact
159
- end
160
-
161
- def find_all_localization_calls
162
- @macros.each { |m| m.clear_calls }
163
44
 
164
- @sfiles_proceeded = 0
165
- proj_files do |file|
166
- next unless file.source_file?
45
+ module Lokale
46
+ class Main
167
47
 
168
- file_content = File.read(file)
169
- @macros.each { |macro| macro.read_from file_content }
170
- @sfiles_proceeded += 1
171
- end
48
+ def run
49
+ find_dir
50
+ read_config
51
+ init_workers
52
+ run_actions
172
53
  end
173
54
 
174
- def copy_base
175
- en_files = @lfiles.group_by { |f| f.lang }["en"].select { |f| f.strings_file? }
176
- base_files = @lfiles.group_by { |f| f.lang }["Base"].select { |f| f.strings_file? }
55
+ #
177
56
 
178
- en_files.each do |en|
179
- base = base_files.select { |f| f.full_name == en.full_name }.sample
180
- next if base.nil?
181
- IO.copy_stream(en.path, base.path)
57
+ def find_dir
58
+ begin
59
+ @project_path, @project_name = ProjectFinder::find_proj
60
+ rescue Exception => e
61
+ puts e
62
+ exit
182
63
  end
183
- end
184
-
185
- def append_new_macro_calls
186
-
187
- end
188
- end
189
-
190
- #
191
-
192
- class Reporter
193
- def initialize(agent)
194
- @agent = agent
195
- end
196
-
197
- def print_summary
198
- print_macro_calls_summary
199
- print_macro_table
200
- print_files_table
201
- print_repeats_report
202
- print_diferences_report
203
- end
204
64
 
205
- def print_macro_calls_summary
206
- total_macro_calls = @agent.macros.map(&:total_count).reduce(:+)
207
- uniq_macro_calls = @agent.macros.map(&:uniq_count).reduce(:+)
208
- puts "Found #{total_macro_calls} localization macro calls in #{@agent.sfiles_proceeded} files."
209
- puts "Uniq macro calls: #{uniq_macro_calls}"
210
- puts
65
+ puts "Target Xcode project: '#{@project_name}'".green
211
66
  end
212
67
 
213
- def print_files_table
214
- languages = @agent.lfiles.map { |e| e.lang }.to_set.to_a
215
- files = @agent.lfiles.map { |e| e.full_name }.to_set.to_a
216
-
217
- puts "Found #{@agent.lfiles.size} localized files for #{languages.size} languages."
218
-
219
- description_header = "[*]".rpadded(36)
220
- languages.each { |l| description_header << l.rpadded(8) }
221
- puts description_header
222
-
223
- files.each do |f|
224
- description_string = f.rpadded(36)
225
- languages.each do |l|
226
- lfile = @agent.lfiles.select { |lf| lf.full_name == f && lf.lang == l }
227
- description_string << (lfile.empty? ? "-" : lfile[0].parsed.nil? ? "*" : "#{lfile[0].parsed.size}").rpadded(8)
228
- end
229
- puts description_string
230
- end
231
- puts
68
+ def read_config
69
+ Config.init
70
+ Config.get.read_lokalefile(@project_path)
232
71
  end
233
72
 
234
- def print_macro_table
235
- @agent.macros.each do |macro|
236
- puts "#{macro.name}:".rpadded(24) + "total: #{macro.total_count}".rpadded(16) + "uniq: #{macro.uniq_count}"
237
- end
238
- puts
73
+ def init_workers
74
+ @agent = Lokale::Agent.new(@project_path, Config.get.macros)
75
+ @reporter = Lokale::Reporter.new(@agent)
239
76
  end
240
-
241
- def print_repeats_report
242
- repeats_repot = ""
243
- @agent.lfiles.each do |lf|
244
- repeats = lf.repeats
245
- next if repeats.count.zero?
246
- repeats_repot << "#{lf.lang}/#{lf.full_name} repeats:\n"
247
- repeats_repot << repeats.join("\n")
248
- repeats_repot << "\n"
249
- end
250
-
251
- if repeats_repot.empty?
252
- puts "Repeats not found.".green
253
- puts
254
- else
255
- puts "Found repeats in strings files.".red
256
- puts repeats_repot
257
- puts
258
- end
77
+
78
+ def run_actions
79
+ Config.get.actions.each { |action| action.perform(@agent, @reporter) }
259
80
  end
260
-
261
- def print_diferences_report
262
- diferences_repot = ""
263
-
264
- @agent.lfiles.group_by { |f| f.full_name }.each do |file_name, files|
265
- base_lang = files.any? { |f| f.lang == "Base" } ? "Base" : "en"
266
- files = files.select { |f| f.lang != base_lang }
267
- all_keys = files.map(&:keys).compact.map(&:to_set)
268
- next if all_keys.empty?
269
- united_keys = all_keys.reduce(:|)
270
- all_keys.map! { |ks| united_keys - ks }
271
- next if all_keys.map(&:length).reduce(:+).zero?
272
-
273
- diferences_repot << "Missing keys in file \"#{file_name}\":\n"
274
- all_keys.zip(files) do |missing_keys, lfile|
275
- next if missing_keys.size.zero?
276
- diferences_repot << "*".red + " #{lfile.lang} - #{missing_keys.size} key(s):\n"
277
- missing_keys.each { |k| diferences_repot << "#{k}\n" }
278
- end
279
- diferences_repot << "\n"
280
- end
281
-
282
- if diferences_repot.empty?
283
- puts "Localization files are full.".green
284
- puts
285
- else
286
- puts "Localization files are not full.".red
287
- puts diferences_repot
288
- puts
289
- end
290
- end
291
81
  end
292
82
  end
293
83
 
294
84
 
295
-
296
- # macros = [
297
- # Lokale::Macro.new("NSLocalizedString", /NSLocalizedString\("(.+?)",\s*comment:\s*"(.*?)"\)/),
298
- # Lokale::Macro.new("PluralString", /String.localizedPlural\("(.+?)"/),
299
- # #LocalizationMacro.new("ObjC String", /NSLocalizedString\("(.*)",\s*(.*)\)/),
300
- # #LocalizationMacro.new("ObjC Table String", /NSLocalizedStringFromTableInBundle\((.*?),/)
301
- # ]
302
-
303
- # agent = Lokale::Agent.new("/Users/crysberry/Documents/hand2hand", macros)
304
- # reporter = Lokale::Reporter.new(agent)
305
- # reporter.print_summary
data/lokale.gemspec CHANGED
@@ -31,4 +31,6 @@ Gem::Specification.new do |spec|
31
31
  spec.executables = ["lokale"]
32
32
  spec.require_paths = ["lib"]
33
33
 
34
+ spec.add_runtime_dependency 'xliffle', '0.2.2'
35
+
34
36
  end
data/test.sh ADDED
@@ -0,0 +1 @@
1
+ rake install; cd ~/Documents/hand2hand-ios; lokale; cd ~/Documents/lokale
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lokale
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Onizhuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-25 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2017-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: xliffle
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.2
13
27
  description: Write a longer description or delete this line.
14
28
  email:
15
29
  - anton.onizhuk@gmail.com
@@ -23,15 +37,23 @@ files:
23
37
  - LICENSE.txt
24
38
  - README.md
25
39
  - Rakefile
40
+ - a.sh
26
41
  - bin/console
27
42
  - bin/lokale
28
43
  - bin/setup
29
44
  - lib/lokale.rb
45
+ - lib/lokale/.lokale
46
+ - lib/lokale/agent.rb
30
47
  - lib/lokale/colorize.rb
48
+ - lib/lokale/config.rb
31
49
  - lib/lokale/find_dir.rb
32
- - lib/lokale/options.rb
50
+ - lib/lokale/lokalefile.rb
51
+ - lib/lokale/model.rb
52
+ - lib/lokale/reporter.rb
53
+ - lib/lokale/util.rb
33
54
  - lib/lokale/version.rb
34
55
  - lokale.gemspec
56
+ - test.sh
35
57
  homepage:
36
58
  licenses:
37
59
  - MIT
@@ -1,70 +0,0 @@
1
-
2
- require "optparse"
3
-
4
- class Action
5
- attr_reader :type, :arg, :precedence
6
- def initialize(type, arg, precedence)
7
- @type = type; @arg = arg; @precedence = precedence
8
- end
9
-
10
- def self.summary
11
- Action.new(:summary, nil, 10)
12
- end
13
- def self.copy_base
14
- Action.new(:copy_base, nil, 50)
15
- end
16
- def self.append
17
- Action.new(:append, nil, 60)
18
- end
19
- end
20
-
21
- class Settings
22
- attr_reader :actions
23
-
24
- def self.init
25
- actions = []
26
-
27
- OptionParser.new do |opts|
28
- opts.banner = "Usage: lokale [-bsh]"
29
-
30
- opts.on("-b", "--copy-base", "Copies 'en' localization files to 'Base'") do |n|
31
- actions << Action.copy_base
32
- end
33
-
34
- opts.on("-s", "--summary", "Prints project summary") do |n|
35
- actions << Action.summary
36
- end
37
-
38
- opts.on("-a", "--append", "Appends new strings to english localization file") do |n|
39
- actions << Action.append
40
- end
41
-
42
- opts.on("-h", "--help", "Prints this help") do
43
- puts opts
44
- exit
45
- end
46
- end.parse!
47
-
48
-
49
-
50
- if actions.empty?
51
- actions << Action.summary
52
- else
53
- actions.sort_by! { |e| -e.precedence }
54
- end
55
-
56
- @shared_settings = Settings.new(actions)
57
- end
58
-
59
- def self.get
60
- init if @shared_settings.nil?
61
- @shared_settings
62
- end
63
-
64
- def initialize(actions)
65
- @actions = actions
66
- end
67
- end
68
-
69
-
70
-