lokale 0.1.2 → 0.1.3

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