phrase 0.2.5 → 0.3.0
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.
- data/.gitignore +1 -0
- data/lib/phrase.rb +1 -0
- data/lib/phrase/api/client.rb +2 -4
- data/lib/phrase/tool.rb +12 -269
- data/lib/phrase/tool/commands.rb +11 -0
- data/lib/phrase/tool/commands/base.rb +73 -0
- data/lib/phrase/tool/commands/init.rb +47 -0
- data/lib/phrase/tool/commands/pull.rb +99 -0
- data/lib/phrase/tool/commands/push.rb +106 -0
- data/lib/phrase/tool/commands/show_help.rb +28 -0
- data/lib/phrase/tool/commands/show_version.rb +17 -0
- data/lib/phrase/tool/config.rb +2 -1
- data/lib/phrase/tool/formats.rb +58 -0
- data/lib/phrase/tool/formats/base.rb +15 -0
- data/lib/phrase/tool/formats/gettext.rb +7 -0
- data/lib/phrase/tool/formats/ini.rb +7 -0
- data/lib/phrase/tool/formats/json.rb +7 -0
- data/lib/phrase/tool/formats/plist.rb +7 -0
- data/lib/phrase/tool/formats/properties.rb +7 -0
- data/lib/phrase/tool/formats/qt_phrase_book.rb +7 -0
- data/lib/phrase/tool/formats/qt_translation_source.rb +7 -0
- data/lib/phrase/tool/formats/resx.rb +7 -0
- data/lib/phrase/tool/formats/strings.rb +23 -0
- data/lib/phrase/tool/formats/xliff.rb +7 -0
- data/lib/phrase/tool/formats/xml.rb +38 -0
- data/lib/phrase/tool/formats/yaml.rb +7 -0
- data/lib/phrase/tool/locale.rb +42 -0
- data/lib/phrase/tool/options.rb +1 -1
- data/lib/phrase/version.rb +1 -1
- data/phrase.gemspec +1 -0
- metadata +51 -18
data/.gitignore
CHANGED
data/lib/phrase.rb
CHANGED
data/lib/phrase/api/client.rb
CHANGED
@@ -22,11 +22,9 @@ class Phrase::Api::Client
|
|
22
22
|
|
23
23
|
def fetch_locales
|
24
24
|
result = perform_api_request("/locales", :get)
|
25
|
-
locales = []
|
26
25
|
parsed(result).map do |locale|
|
27
|
-
|
26
|
+
{id: locale['id'], name: locale['name'], code: locale['code'], is_default: locale['is_default']}
|
28
27
|
end
|
29
|
-
locales
|
30
28
|
end
|
31
29
|
|
32
30
|
def fetch_blacklisted_keys
|
@@ -74,7 +72,7 @@ class Phrase::Api::Client
|
|
74
72
|
true
|
75
73
|
end
|
76
74
|
|
77
|
-
def download_translations_for_locale(name, format
|
75
|
+
def download_translations_for_locale(name, format)
|
78
76
|
raise "You must specify a name" if name.nil? or name.blank?
|
79
77
|
raise "You must specify a format" if format.nil? or format.blank?
|
80
78
|
|
data/lib/phrase/tool.rb
CHANGED
@@ -7,290 +7,33 @@ require 'phrase'
|
|
7
7
|
class Phrase::Tool
|
8
8
|
autoload :Config, 'phrase/tool/config'
|
9
9
|
autoload :Options, 'phrase/tool/options'
|
10
|
+
autoload :Formats, 'phrase/tool/formats'
|
11
|
+
autoload :Commands, 'phrase/tool/commands'
|
10
12
|
autoload :TagValidator, 'phrase/tool/tag_validator'
|
11
|
-
|
12
|
-
ALLOWED_FILE_TYPES = %w(yml pot po xml strings json resx ts qph ini plist properties xlf)
|
13
|
-
ALLOWED_DOWNLOAD_FORMATS = %w(yml po xml strings json resx ts qph ini plist properties xlf)
|
14
|
-
FORMATS_CONTAINING_LOCALE = %q(po yml qph ts xlf)
|
15
|
-
DEFAULT_DOWNLOAD_FORMAT = "yml"
|
16
|
-
DEFAULT_TARGET_FOLDER = "phrase/locales/"
|
17
|
-
|
18
|
-
attr_accessor :config, :options
|
13
|
+
autoload :Locale, 'phrase/tool/locale'
|
19
14
|
|
20
15
|
def initialize(argv)
|
21
16
|
@args = argv
|
22
17
|
end
|
23
18
|
|
24
19
|
def run
|
25
|
-
|
20
|
+
command_name = @args.first
|
21
|
+
@options = Phrase::Tool::Options.new(@args, command_name)
|
26
22
|
|
27
|
-
|
28
|
-
@options = Phrase::Tool::Options.new(@args, subcommand)
|
29
|
-
|
30
|
-
case subcommand
|
23
|
+
command = case command_name
|
31
24
|
when /init/
|
32
|
-
|
25
|
+
Phrase::Tool::Commands::Init.new(@options, @args)
|
33
26
|
when /push/
|
34
|
-
|
27
|
+
Phrase::Tool::Commands::Push.new(@options, @args)
|
35
28
|
when /pull/
|
36
|
-
|
29
|
+
Phrase::Tool::Commands::Pull.new(@options, @args)
|
37
30
|
else
|
38
31
|
if @options.get(:version)
|
39
|
-
|
32
|
+
Phrase::Tool::Commands::ShowVersion.new(@options, @args)
|
40
33
|
else
|
41
|
-
|
34
|
+
Phrase::Tool::Commands::ShowHelp.new(@options, @args)
|
42
35
|
end
|
43
36
|
end
|
44
|
-
|
45
|
-
|
46
|
-
protected
|
47
|
-
|
48
|
-
def init
|
49
|
-
secret = @options.get(:secret)
|
50
|
-
unless secret.present?
|
51
|
-
print_error "Need a secret to init, but found none."
|
52
|
-
print_error "Please provide the --secret=YOUR_SECRET parameter."
|
53
|
-
exit(41)
|
54
|
-
end
|
55
|
-
|
56
|
-
@config.secret = secret
|
57
|
-
print_message "Wrote secret to config file .phrase"
|
58
|
-
|
59
|
-
default_locale_name = @options.get(:default_locale)
|
60
|
-
create_locale(default_locale_name)
|
61
|
-
make_locale_default(default_locale_name)
|
62
|
-
end
|
63
|
-
|
64
|
-
def push
|
65
|
-
check_config_available
|
66
|
-
tags = @options.get(:tags)
|
67
|
-
locale = @options.get(:locale)
|
68
|
-
|
69
|
-
unless tags.empty? or valid_tags_are_given?(tags)
|
70
|
-
print_error "Invalid tags: Only letters, numbers, underscores and dashes are allowed"
|
71
|
-
exit(43)
|
72
|
-
end
|
73
|
-
|
74
|
-
files = choose_files_to_upload
|
75
|
-
if files.empty?
|
76
|
-
print_message "Could not find any files to upload :("
|
77
|
-
exit(43)
|
78
|
-
end
|
79
|
-
|
80
|
-
upload_files(files, tags, locale)
|
81
|
-
end
|
82
|
-
|
83
|
-
def pull
|
84
|
-
check_config_available
|
85
|
-
|
86
|
-
create_locales_folder!
|
87
|
-
|
88
|
-
locale = args[1]
|
89
|
-
locales = []
|
90
|
-
if locale && locale.strip != ''
|
91
|
-
locales = [locale]
|
92
|
-
else
|
93
|
-
locales = fetch_locales
|
94
|
-
end
|
95
|
-
|
96
|
-
format = @options.get(:format) || DEFAULT_DOWNLOAD_FORMAT
|
97
|
-
target = @options.get(:target) || DEFAULT_TARGET_FOLDER
|
98
|
-
|
99
|
-
unless ALLOWED_DOWNLOAD_FORMATS.include?(format)
|
100
|
-
print_error "Invalid format: #{format}"
|
101
|
-
exit(43)
|
102
|
-
end
|
103
|
-
|
104
|
-
locales.each do |locale_name|
|
105
|
-
print "Downloading phrase.#{locale_name}.#{format}..."
|
106
|
-
fetch_translations_for_locale(locale_name, format, target)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def print_usage
|
111
|
-
$stderr.puts <<USAGE
|
112
|
-
usage: phrase <command> [<args>]
|
113
|
-
|
114
|
-
phrase init --secret=<YOUR SECRET> --default-locale=<DEFAULT LOCALE>
|
115
|
-
|
116
|
-
phrase push FILE [--tags=<tags>] [--locale=<locale>]
|
117
|
-
phrase push DIRECTORY [--tags=<tags>] [--locale=<locale>]
|
118
|
-
|
119
|
-
phrase pull [LOCALE] [--target=<target-folder>] [--format=<format>]
|
120
|
-
|
121
|
-
phrase --version
|
122
|
-
USAGE
|
123
|
-
end
|
124
|
-
|
125
|
-
def print_version
|
126
|
-
print_message "phrase version #{Phrase::VERSION}"
|
127
|
-
end
|
128
|
-
|
129
|
-
private
|
130
|
-
def choose_files_to_upload
|
131
|
-
file_name = args[1]
|
132
|
-
recursive = @options.get(:recursive)
|
133
|
-
|
134
|
-
unless file_name
|
135
|
-
if self.class.rails_default_locale_folder_is_available
|
136
|
-
file_name = self.class.rails_default_locale_folder
|
137
|
-
print_message "No file or directory specified, using #{self.class.rails_default_locale_folder}"
|
138
|
-
else
|
139
|
-
print_error "Need either a file or directory:"
|
140
|
-
print_error "phrase push FILE"
|
141
|
-
print_error "phrase push DIRECTORY"
|
142
|
-
exit(46)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
unless File.exist?(file_name)
|
147
|
-
print_error "The file #{file_name} could not be found."
|
148
|
-
exit(42)
|
149
|
-
end
|
150
|
-
|
151
|
-
if File.directory?(file_name)
|
152
|
-
pattern = recursive ? "#{File.expand_path(file_name)}/**/*" : "#{File.expand_path(file_name)}/**"
|
153
|
-
files = Dir.glob(pattern)
|
154
|
-
else
|
155
|
-
files = [file_name]
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def upload_files(files, tags=[], locale=nil)
|
160
|
-
files.each do |file|
|
161
|
-
upload_file(file, tags, locale)
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def upload_file(file, tags=[], locale=nil)
|
166
|
-
valid = true
|
167
|
-
|
168
|
-
if File.directory?(file)
|
169
|
-
valid = false
|
170
|
-
end
|
171
|
-
|
172
|
-
unless file_valid?(file)
|
173
|
-
valid = false
|
174
|
-
print_error "Notice: Could not upload #{file} (type not supported)"
|
175
|
-
end
|
176
|
-
|
177
|
-
if valid
|
178
|
-
begin
|
179
|
-
tagged = " (tagged: #{tags.join(", ")})" if tags.size > 0
|
180
|
-
print_message "Uploading #{file}#{tagged}..."
|
181
|
-
api_client.upload(file, File.read(file), tags, locale)
|
182
|
-
print_message "OK"
|
183
|
-
rescue Exception => e
|
184
|
-
print_message "Failed"
|
185
|
-
print_server_error(e.message, file)
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
def fetch_translations_for_locale(name, format=DEFAULT_DOWNLOAD_FORMAT, target=DEFAULT_TARGET_FOLDER)
|
191
|
-
begin
|
192
|
-
content = api_client.download_translations_for_locale(name, format)
|
193
|
-
print_message "OK"
|
194
|
-
store_translations_file(name, content, format, target)
|
195
|
-
rescue Exception => e
|
196
|
-
print_error "Failed"
|
197
|
-
print_server_error(e.message)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
def store_translations_file(name, content, format=DEFAULT_DOWNLOAD_FORMAT, target=DEFAULT_TARGET_FOLDER)
|
202
|
-
directory = target
|
203
|
-
directory << "/" unless directory.end_with?("/")
|
204
|
-
|
205
|
-
if File.directory?(directory)
|
206
|
-
File.open("#{directory}phrase.#{name}.#{format}", "w") do |file|
|
207
|
-
file.write(content)
|
208
|
-
end
|
209
|
-
else
|
210
|
-
print_error("Cannot write file to target folder (#{directory})")
|
211
|
-
exit(101)
|
212
|
-
end
|
213
|
-
end
|
214
|
-
|
215
|
-
def fetch_locales
|
216
|
-
begin
|
217
|
-
locales = api_client.fetch_locales
|
218
|
-
print_message "Fetched all locales"
|
219
|
-
return locales
|
220
|
-
rescue Exception => e
|
221
|
-
print_message "Failed"
|
222
|
-
print_server_error(e.message)
|
223
|
-
exit(47)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
def create_locale(name)
|
228
|
-
begin
|
229
|
-
api_client.create_locale(name)
|
230
|
-
print_message "Created locale \"#{name}\""
|
231
|
-
rescue Exception => e
|
232
|
-
print_message "Notice: Locale \"#{name}\" could not be created (maybe it already exists)"
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
def make_locale_default(name)
|
237
|
-
begin
|
238
|
-
api_client.make_locale_default(name)
|
239
|
-
print_message "Locale \"#{name}\" is now the default locale"
|
240
|
-
rescue Exception => e
|
241
|
-
print_message "Notice: Locale \"#{name}\" could not be made the default locale"
|
242
|
-
print_server_error(e.message)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def api_client
|
247
|
-
Phrase::Api::Client.new(@config.secret)
|
248
|
-
end
|
249
|
-
|
250
|
-
def print_server_error(message, filename=nil)
|
251
|
-
print_error "#{message} (#{filename})"
|
252
|
-
end
|
253
|
-
|
254
|
-
def print_message(message)
|
255
|
-
puts message
|
256
|
-
end
|
257
|
-
|
258
|
-
def print_error(message)
|
259
|
-
$stderr.puts message
|
260
|
-
end
|
261
|
-
|
262
|
-
def args
|
263
|
-
@args
|
264
|
-
end
|
265
|
-
|
266
|
-
def file_valid?(filepath)
|
267
|
-
extension = filepath.split('.').last
|
268
|
-
ALLOWED_FILE_TYPES.include?(extension)
|
269
|
-
end
|
270
|
-
|
271
|
-
def create_locales_folder!
|
272
|
-
::FileUtils.mkdir_p("phrase/locales")
|
273
|
-
end
|
274
|
-
|
275
|
-
def check_config_available
|
276
|
-
if !@config.secret || @config.secret.empty?
|
277
|
-
print_error "No config present. You need to initialize phrase first."
|
278
|
-
exit(43)
|
279
|
-
end
|
280
|
-
end
|
281
|
-
|
282
|
-
def valid_tags_are_given?(tags)
|
283
|
-
tags.each do |tag|
|
284
|
-
return false unless TagValidator.valid?(tag)
|
285
|
-
end
|
286
|
-
true
|
287
|
-
end
|
288
|
-
|
289
|
-
def self.rails_default_locale_folder
|
290
|
-
"./config/locales/"
|
291
|
-
end
|
292
|
-
|
293
|
-
def self.rails_default_locale_folder_is_available
|
294
|
-
File.exist?(rails_default_locale_folder) && File.directory?(rails_default_locale_folder)
|
37
|
+
command.execute!
|
295
38
|
end
|
296
39
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module Phrase::Tool::Commands
|
4
|
+
end
|
5
|
+
|
6
|
+
require "phrase/tool/commands/base"
|
7
|
+
require "phrase/tool/commands/init"
|
8
|
+
require "phrase/tool/commands/push"
|
9
|
+
require "phrase/tool/commands/pull"
|
10
|
+
require "phrase/tool/commands/show_version"
|
11
|
+
require "phrase/tool/commands/show_help"
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module Phrase::Tool::Commands
|
4
|
+
class Base
|
5
|
+
def initialize(options, args=[])
|
6
|
+
@options = options
|
7
|
+
@args = args
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute!
|
11
|
+
raise "not implemented"
|
12
|
+
end
|
13
|
+
|
14
|
+
def require_auth_token!
|
15
|
+
unless config.secret and config.secret.present?
|
16
|
+
print_error "No auth token present. You need to initialize phrase first."
|
17
|
+
exit_command
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.print_error(msg)
|
22
|
+
$stderr.puts msg.red
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.print_server_error(msg, location=nil)
|
26
|
+
error_message = "#{msg}"
|
27
|
+
error_message << " (#{location})" unless location.nil?
|
28
|
+
print_error error_message
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.print_message(msg)
|
32
|
+
$stdout.puts msg
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.exit_command
|
36
|
+
exit()
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
def api_client
|
41
|
+
Phrase::Api::Client.new(config.secret)
|
42
|
+
end
|
43
|
+
|
44
|
+
def config
|
45
|
+
@config ||= get_config
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_config
|
49
|
+
config = Phrase::Tool::Config.new
|
50
|
+
config.load
|
51
|
+
end
|
52
|
+
|
53
|
+
def options
|
54
|
+
@options
|
55
|
+
end
|
56
|
+
|
57
|
+
def print_error(msg)
|
58
|
+
self.class.print_error(msg)
|
59
|
+
end
|
60
|
+
|
61
|
+
def print_server_error(message, location=nil)
|
62
|
+
self.class.print_server_error(message, location)
|
63
|
+
end
|
64
|
+
|
65
|
+
def print_message(msg)
|
66
|
+
self.class.print_message(msg)
|
67
|
+
end
|
68
|
+
|
69
|
+
def exit_command
|
70
|
+
self.class.exit_command
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Commands::Init < Phrase::Tool::Commands::Base
|
4
|
+
def initialize(options, args)
|
5
|
+
super(options, args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute!
|
9
|
+
secret = options.get(:secret)
|
10
|
+
if secret.present?
|
11
|
+
config.secret = secret
|
12
|
+
print_message "Wrote secret to config file .phrase"
|
13
|
+
default_locale_name = options.get(:default_locale)
|
14
|
+
create_locale(default_locale_name)
|
15
|
+
make_locale_default(default_locale_name)
|
16
|
+
else
|
17
|
+
print_auth_token_error
|
18
|
+
exit_command
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def create_locale(name)
|
25
|
+
begin
|
26
|
+
api_client.create_locale(name)
|
27
|
+
print_message "Created locale \"#{name}\"".green
|
28
|
+
rescue Exception => e
|
29
|
+
print_message "Notice: Locale \"#{name}\" could not be created (maybe it already exists)".light_red
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def make_locale_default(name)
|
34
|
+
begin
|
35
|
+
api_client.make_locale_default(name)
|
36
|
+
print_message "Locale \"#{name}\" is now the default locale".green
|
37
|
+
rescue Exception => e
|
38
|
+
print_message "Notice: Locale \"#{name}\" could not be made the default locale".light_red
|
39
|
+
print_server_error(e.message)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def print_auth_token_error
|
44
|
+
print_error "No auth token was given"
|
45
|
+
print_error "Please provide the --secret=YOUR_SECRET parameter."
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Commands::Pull < Phrase::Tool::Commands::Base
|
4
|
+
|
5
|
+
ALLOWED_DOWNLOAD_FORMATS = %w(yml po xml strings json resx ts qph ini plist properties xlf)
|
6
|
+
DEFAULT_DOWNLOAD_FORMAT = "yml"
|
7
|
+
DEFAULT_TARGET_FOLDER = "phrase/locales/"
|
8
|
+
|
9
|
+
def initialize(options, args)
|
10
|
+
super(options, args)
|
11
|
+
require_auth_token!
|
12
|
+
|
13
|
+
@locale = @args[1]
|
14
|
+
|
15
|
+
@format = @options.get(:format) || DEFAULT_DOWNLOAD_FORMAT
|
16
|
+
@target = @options.get(:target) || DEFAULT_TARGET_FOLDER
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute!
|
20
|
+
(print_error("Invalid format: #{@format}") and exit_command) unless format_valid?(@format)
|
21
|
+
locales_to_download.compact.each do |locale|
|
22
|
+
print_message "Downloading #{locale.name}..."
|
23
|
+
fetch_translations_for_locale(locale, @format)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def fetch_translations_for_locale(locale, format)
|
30
|
+
begin
|
31
|
+
content = api_client.download_translations_for_locale(locale.name, format)
|
32
|
+
store_content_in_locale_file(locale, content)
|
33
|
+
rescue Exception => e
|
34
|
+
print_error "Failed"
|
35
|
+
print_server_error(e.message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def store_content_in_locale_file(locale, content)
|
40
|
+
directory = Phrase::Tool::Formats.directory_for_locale_in_format(locale, @format)
|
41
|
+
filename = Phrase::Tool::Formats.filename_for_locale_in_format(locale, @format)
|
42
|
+
path = File.join(base_directory, directory)
|
43
|
+
target = File.join(path, filename)
|
44
|
+
begin
|
45
|
+
FileUtils.mkpath(path)
|
46
|
+
File.open(target, "w") do |file|
|
47
|
+
file.write(content)
|
48
|
+
end
|
49
|
+
print_message "Saved to #{target}".green
|
50
|
+
rescue
|
51
|
+
print_error("Cannot write file to target folder (#{path})")
|
52
|
+
exit_command
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch_locales
|
57
|
+
begin
|
58
|
+
Phrase::Tool::Locale.all
|
59
|
+
rescue Exception => e
|
60
|
+
print_error "Could not fetch locales from server"
|
61
|
+
print_server_error e.message
|
62
|
+
exit_command
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def format_valid?(format)
|
67
|
+
ALLOWED_DOWNLOAD_FORMATS.include?(format)
|
68
|
+
end
|
69
|
+
|
70
|
+
def base_directory
|
71
|
+
directory = @target
|
72
|
+
end
|
73
|
+
|
74
|
+
# TODO: test
|
75
|
+
def locales_to_download
|
76
|
+
if user_specified_a_locale?
|
77
|
+
[specified_locale]
|
78
|
+
else
|
79
|
+
all_locales
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# TODO: test
|
84
|
+
def specified_locale
|
85
|
+
locale = all_locales.select { |locale| locale.name == @locale }.first
|
86
|
+
(print_error("Locale #{@locale} does not exist") and exit_command) if locale.nil?
|
87
|
+
locale
|
88
|
+
end
|
89
|
+
|
90
|
+
# TODO: test
|
91
|
+
def all_locales
|
92
|
+
@all_locales_from_server ||= fetch_locales
|
93
|
+
end
|
94
|
+
|
95
|
+
# TODO: test
|
96
|
+
def user_specified_a_locale?
|
97
|
+
@locale and @locale.strip != ''
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Commands::Push < Phrase::Tool::Commands::Base
|
4
|
+
ALLOWED_FILE_TYPES = %w(yml pot po xml strings json resx ts qph ini plist properties xlf)
|
5
|
+
FORMATS_CONTAINING_LOCALE = %q(po yml qph ts xlf)
|
6
|
+
RAILS_DEFAULT_FOLDER = "./config/locales/"
|
7
|
+
|
8
|
+
def initialize(options, args)
|
9
|
+
super(options, args)
|
10
|
+
require_auth_token!
|
11
|
+
|
12
|
+
@file_name = @args[1]
|
13
|
+
@locale = @options.get(:locale)
|
14
|
+
@tags = @options.get(:tags)
|
15
|
+
@recursive = @options.get(:recursive)
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute!
|
19
|
+
unless @tags.empty? or valid_tags_are_given?(@tags)
|
20
|
+
print_error "Invalid tags: Only letters, numbers, underscores and dashes are allowed"
|
21
|
+
exit_command
|
22
|
+
end
|
23
|
+
|
24
|
+
files = choose_files_to_upload
|
25
|
+
if files.empty?
|
26
|
+
print_message "Could not find any files to upload".light_red
|
27
|
+
exit_command
|
28
|
+
else
|
29
|
+
upload_files(files)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def choose_files_to_upload
|
35
|
+
if @file_name.blank?
|
36
|
+
if rails_default_locale_folder_available?
|
37
|
+
@file_name = RAILS_DEFAULT_FOLDER
|
38
|
+
print_message "No file or directory specified, using #{RAILS_DEFAULT_FOLDER}"
|
39
|
+
else
|
40
|
+
print_error "Need either a file or directory:"
|
41
|
+
print_error "phrase push FILE"
|
42
|
+
print_error "phrase push DIRECTORY"
|
43
|
+
exit_command
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
unless File.exist?(@file_name)
|
48
|
+
print_error "The file #{@file_name} could not be found."
|
49
|
+
exit_command
|
50
|
+
end
|
51
|
+
|
52
|
+
if File.directory?(@file_name)
|
53
|
+
pattern = @recursive ? "#{File.expand_path(@file_name)}/**/*" : "#{File.expand_path(@file_name)}/**"
|
54
|
+
files = Dir.glob(pattern)
|
55
|
+
else
|
56
|
+
files = [@file_name]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def upload_files(files)
|
61
|
+
files.each { |file| upload_file(file) }
|
62
|
+
end
|
63
|
+
|
64
|
+
def upload_file(file)
|
65
|
+
valid = true
|
66
|
+
|
67
|
+
if File.directory?(file)
|
68
|
+
valid = false
|
69
|
+
else
|
70
|
+
unless file_valid?(file)
|
71
|
+
valid = false
|
72
|
+
print_error "Notice: Could not upload #{file} (type not supported)"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if valid
|
77
|
+
begin
|
78
|
+
tagged = " (tagged: #{@tags.join(", ")})" if @tags.size > 0
|
79
|
+
print_message "Uploading #{file}#{tagged}..."
|
80
|
+
locale = @locale || detect_locale_name_from_file_path(file)
|
81
|
+
api_client.upload(file, File.read(file), @tags, locale)
|
82
|
+
print_message "OK".green
|
83
|
+
rescue Exception => e
|
84
|
+
print_error "Failed"
|
85
|
+
print_server_error(e.message)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def file_valid?(filepath)
|
91
|
+
extension = filepath.split('.').last
|
92
|
+
ALLOWED_FILE_TYPES.include?(extension)
|
93
|
+
end
|
94
|
+
|
95
|
+
def valid_tags_are_given?(tags)
|
96
|
+
tags.all? { |tag| Phrase::Tool::TagValidator.valid?(tag) }
|
97
|
+
end
|
98
|
+
|
99
|
+
def rails_default_locale_folder_available?
|
100
|
+
File.exist?(RAILS_DEFAULT_FOLDER) && File.directory?(RAILS_DEFAULT_FOLDER)
|
101
|
+
end
|
102
|
+
|
103
|
+
def detect_locale_name_from_file_path(file_path)
|
104
|
+
Phrase::Tool::Formats.detect_locale_name_from_file_path(file_path)
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Commands::ShowHelp < Phrase::Tool::Commands::Base
|
4
|
+
def initialize(options, args)
|
5
|
+
super(options, args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute!
|
9
|
+
show_help
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def show_help
|
15
|
+
print_message <<USAGE
|
16
|
+
usage: phrase <command> [<args>]
|
17
|
+
|
18
|
+
phrase init --secret=<YOUR SECRET> --default-locale=<DEFAULT LOCALE>
|
19
|
+
|
20
|
+
phrase push FILE [--tags=<tags>] [--locale=<locale>]
|
21
|
+
phrase push DIRECTORY [--tags=<tags>] [--locale=<locale>]
|
22
|
+
|
23
|
+
phrase pull [LOCALE] [--target=<target-folder>] [--format=<format>]
|
24
|
+
|
25
|
+
phrase --version
|
26
|
+
USAGE
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Commands::ShowVersion < Phrase::Tool::Commands::Base
|
4
|
+
def initialize(options, args)
|
5
|
+
super(options, args)
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute!
|
9
|
+
show_version
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def show_version
|
15
|
+
print_message "phrase version #{Phrase::VERSION}"
|
16
|
+
end
|
17
|
+
end
|
data/lib/phrase/tool/config.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'json'
|
4
4
|
|
5
5
|
class Phrase::Tool::Config
|
6
|
-
def
|
6
|
+
def load
|
7
7
|
if File.exist?(".phrase")
|
8
8
|
begin
|
9
9
|
contents = File.read(".phrase")
|
@@ -12,6 +12,7 @@ class Phrase::Tool::Config
|
|
12
12
|
$stderr.puts "Could not parse config file: #{err}"
|
13
13
|
end
|
14
14
|
end
|
15
|
+
self
|
15
16
|
end
|
16
17
|
|
17
18
|
def secret
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
module Phrase::Tool::Formats
|
4
|
+
autoload :Base, 'phrase/tool/formats/base'
|
5
|
+
autoload :Yaml, 'phrase/tool/formats/yaml'
|
6
|
+
autoload :Gettext, 'phrase/tool/formats/gettext'
|
7
|
+
autoload :Xml, 'phrase/tool/formats/xml'
|
8
|
+
autoload :Strings, 'phrase/tool/formats/strings'
|
9
|
+
autoload :Xliff, 'phrase/tool/formats/xliff'
|
10
|
+
autoload :QtPhraseBook, 'phrase/tool/formats/qt_phrase_book'
|
11
|
+
autoload :QtTranslationSource, 'phrase/tool/formats/qt_translation_source'
|
12
|
+
autoload :Json, 'phrase/tool/formats/json'
|
13
|
+
autoload :Resx, 'phrase/tool/formats/resx'
|
14
|
+
autoload :Ini, 'phrase/tool/formats/ini'
|
15
|
+
autoload :Properties, 'phrase/tool/formats/properties'
|
16
|
+
autoload :Plist, 'phrase/tool/formats/plist'
|
17
|
+
|
18
|
+
SUPPORTED_FORMATS = {
|
19
|
+
yml: Phrase::Tool::Formats::Yaml,
|
20
|
+
po: Phrase::Tool::Formats::Gettext,
|
21
|
+
xml: Phrase::Tool::Formats::Xml,
|
22
|
+
strings: Phrase::Tool::Formats::Strings,
|
23
|
+
xlf: Phrase::Tool::Formats::Xliff,
|
24
|
+
qph: Phrase::Tool::Formats::QtPhraseBook,
|
25
|
+
ts: Phrase::Tool::Formats::QtTranslationSource,
|
26
|
+
json: Phrase::Tool::Formats::Json,
|
27
|
+
resx: Phrase::Tool::Formats::Resx,
|
28
|
+
ini: Phrase::Tool::Formats::Ini,
|
29
|
+
properties: Phrase::Tool::Formats::Properties,
|
30
|
+
plist: Phrase::Tool::Formats::Plist
|
31
|
+
}
|
32
|
+
|
33
|
+
def self.directory_for_locale_in_format(locale, format_name)
|
34
|
+
handler = handler_class_for_format(format_name)
|
35
|
+
handler.directory_for_locale(locale)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.filename_for_locale_in_format(locale, format_name)
|
39
|
+
handler = handler_class_for_format(format_name)
|
40
|
+
handler.filename_for_locale(locale)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.detect_locale_name_from_file_path(file_path)
|
44
|
+
format = guess_possible_file_format_from_file_path(file_path)
|
45
|
+
format.nil? ? nil : handler_class_for_format(format).extract_locale_name_from_file_path(file_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.handler_class_for_format(format_name)
|
49
|
+
SUPPORTED_FORMATS.fetch(format_name.to_sym)
|
50
|
+
end
|
51
|
+
private_class_method :handler_class_for_format
|
52
|
+
|
53
|
+
def self.guess_possible_file_format_from_file_path(file_path)
|
54
|
+
extension = file_path.split('.').last.downcase
|
55
|
+
return SUPPORTED_FORMATS.has_key?(extension.to_sym) ? extension.to_sym : nil
|
56
|
+
end
|
57
|
+
private_class_method :guess_possible_file_format_from_file_path
|
58
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Formats::Base
|
4
|
+
def self.directory_for_locale(locale)
|
5
|
+
"./"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.filename_for_locale(locale)
|
9
|
+
raise "not implemented"
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.extract_locale_name_from_file_path(file_path)
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Formats::Strings < Phrase::Tool::Formats::Base
|
4
|
+
def self.directory_for_locale(locale)
|
5
|
+
name = locale.code || locale.name
|
6
|
+
"#{formatted(name)}.lproj"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.filename_for_locale(locale)
|
10
|
+
"Localizable.strings"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.extract_locale_name_from_file_path(file_path)
|
14
|
+
file_path.scan(/\/([a-zA-Z\-_]*).lproj\//i).first.try(:first)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.formatted(name)
|
18
|
+
return name unless name.include?("-")
|
19
|
+
parts = name.split("-")
|
20
|
+
"#{parts.first}_#{parts.last.upcase}"
|
21
|
+
end
|
22
|
+
private_class_method :formatted
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Formats::Xml < Phrase::Tool::Formats::Base
|
4
|
+
def self.directory_for_locale(locale)
|
5
|
+
if locale.default?
|
6
|
+
"values"
|
7
|
+
else
|
8
|
+
name = locale.code || locale.name
|
9
|
+
"values-#{formatted(name)}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.filename_for_locale(locale)
|
14
|
+
"strings.xml"
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.extract_locale_name_from_file_path(file_path)
|
18
|
+
return default_locale_name if file_path.scan(/\/values\/strings.xml/i).first
|
19
|
+
locale_part = file_path.scan(/\/values-([a-zA-Z\-_]*)\/strings.xml/i).first.try(:first)
|
20
|
+
if locale_part and locale_part.include?("-r")
|
21
|
+
"#{locale_part.split("-r").first}-#{locale_part.split("-r").last}"
|
22
|
+
else
|
23
|
+
locale_part
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.default_locale_name
|
28
|
+
Phrase::Tool::Locale.find_default_locale.try(:name)
|
29
|
+
end
|
30
|
+
private_class_method :default_locale_name
|
31
|
+
|
32
|
+
def self.formatted(name)
|
33
|
+
return name unless name.include?("-")
|
34
|
+
parts = name.split("-")
|
35
|
+
"#{parts.first}-r#{parts.last.upcase}"
|
36
|
+
end
|
37
|
+
private_class_method :formatted
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
class Phrase::Tool::Locale
|
4
|
+
attr_accessor :id, :name, :code, :is_default
|
5
|
+
|
6
|
+
def initialize(attrs={})
|
7
|
+
attrs.each do |key,value|
|
8
|
+
self.send("#{key}=", value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def default?
|
13
|
+
self.is_default == true
|
14
|
+
end
|
15
|
+
|
16
|
+
def ==(object)
|
17
|
+
object.id == self.id
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.find_default_locale
|
21
|
+
all.select { |locale| locale.default? }.try(:first)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.all
|
25
|
+
locales = []
|
26
|
+
api_client.fetch_locales.each do |locale|
|
27
|
+
locales << Phrase::Tool::Locale.new(id: locale[:id], name: locale[:name], code: locale[:code], is_default: locale[:is_default])
|
28
|
+
end
|
29
|
+
locales
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.config
|
33
|
+
config = Phrase::Tool::Config.new
|
34
|
+
config.load
|
35
|
+
end
|
36
|
+
private_class_method :config
|
37
|
+
|
38
|
+
def self.api_client
|
39
|
+
Phrase::Api::Client.new(config.secret)
|
40
|
+
end
|
41
|
+
private_class_method :api_client
|
42
|
+
end
|
data/lib/phrase/tool/options.rb
CHANGED
@@ -64,7 +64,7 @@ private
|
|
64
64
|
end
|
65
65
|
when :pull
|
66
66
|
OptionParser.new do |opts|
|
67
|
-
opts.on("--format=yml", String, "
|
67
|
+
opts.on("--format=yml", String, "See documentation for list of allowed locales") do |format|
|
68
68
|
@data[command_name][:format] = format
|
69
69
|
end
|
70
70
|
|
data/lib/phrase/version.rb
CHANGED
data/phrase.gemspec
CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |s|
|
|
22
22
|
s.require_paths = ["lib"]
|
23
23
|
s.add_dependency('activesupport', '~> 3.0')
|
24
24
|
s.add_dependency('addressable', '~> 2.2.8')
|
25
|
+
s.add_dependency('colorize')
|
25
26
|
s.add_dependency('json')
|
26
27
|
s.add_development_dependency('rspec')
|
27
28
|
s.add_development_dependency('i18n')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phrase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement: &
|
16
|
+
requirement: &70212288992600 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70212288992600
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: addressable
|
27
|
-
requirement: &
|
27
|
+
requirement: &70212288992140 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,21 @@ dependencies:
|
|
32
32
|
version: 2.2.8
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70212288992140
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: colorize
|
38
|
+
requirement: &70212288991760 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70212288991760
|
36
47
|
- !ruby/object:Gem::Dependency
|
37
48
|
name: json
|
38
|
-
requirement: &
|
49
|
+
requirement: &70212288991300 !ruby/object:Gem::Requirement
|
39
50
|
none: false
|
40
51
|
requirements:
|
41
52
|
- - ! '>='
|
@@ -43,10 +54,10 @@ dependencies:
|
|
43
54
|
version: '0'
|
44
55
|
type: :runtime
|
45
56
|
prerelease: false
|
46
|
-
version_requirements: *
|
57
|
+
version_requirements: *70212288991300
|
47
58
|
- !ruby/object:Gem::Dependency
|
48
59
|
name: rspec
|
49
|
-
requirement: &
|
60
|
+
requirement: &70212288990880 !ruby/object:Gem::Requirement
|
50
61
|
none: false
|
51
62
|
requirements:
|
52
63
|
- - ! '>='
|
@@ -54,10 +65,10 @@ dependencies:
|
|
54
65
|
version: '0'
|
55
66
|
type: :development
|
56
67
|
prerelease: false
|
57
|
-
version_requirements: *
|
68
|
+
version_requirements: *70212288990880
|
58
69
|
- !ruby/object:Gem::Dependency
|
59
70
|
name: i18n
|
60
|
-
requirement: &
|
71
|
+
requirement: &70212288990460 !ruby/object:Gem::Requirement
|
61
72
|
none: false
|
62
73
|
requirements:
|
63
74
|
- - ! '>='
|
@@ -65,10 +76,10 @@ dependencies:
|
|
65
76
|
version: '0'
|
66
77
|
type: :development
|
67
78
|
prerelease: false
|
68
|
-
version_requirements: *
|
79
|
+
version_requirements: *70212288990460
|
69
80
|
- !ruby/object:Gem::Dependency
|
70
81
|
name: webmock
|
71
|
-
requirement: &
|
82
|
+
requirement: &70212288990040 !ruby/object:Gem::Requirement
|
72
83
|
none: false
|
73
84
|
requirements:
|
74
85
|
- - ! '>='
|
@@ -76,10 +87,10 @@ dependencies:
|
|
76
87
|
version: '0'
|
77
88
|
type: :development
|
78
89
|
prerelease: false
|
79
|
-
version_requirements: *
|
90
|
+
version_requirements: *70212288990040
|
80
91
|
- !ruby/object:Gem::Dependency
|
81
92
|
name: vcr
|
82
|
-
requirement: &
|
93
|
+
requirement: &70212288989620 !ruby/object:Gem::Requirement
|
83
94
|
none: false
|
84
95
|
requirements:
|
85
96
|
- - ! '>='
|
@@ -87,10 +98,10 @@ dependencies:
|
|
87
98
|
version: '0'
|
88
99
|
type: :development
|
89
100
|
prerelease: false
|
90
|
-
version_requirements: *
|
101
|
+
version_requirements: *70212288989620
|
91
102
|
- !ruby/object:Gem::Dependency
|
92
103
|
name: timecop
|
93
|
-
requirement: &
|
104
|
+
requirement: &70212288989200 !ruby/object:Gem::Requirement
|
94
105
|
none: false
|
95
106
|
requirements:
|
96
107
|
- - ! '>='
|
@@ -98,7 +109,7 @@ dependencies:
|
|
98
109
|
version: '0'
|
99
110
|
type: :development
|
100
111
|
prerelease: false
|
101
|
-
version_requirements: *
|
112
|
+
version_requirements: *70212288989200
|
102
113
|
description: phrase allows you to edit translations in-place on the page itself. More
|
103
114
|
information at phraseapp.com
|
104
115
|
email:
|
@@ -130,7 +141,29 @@ files:
|
|
130
141
|
- lib/phrase/engine.rb
|
131
142
|
- lib/phrase/hash_flattener.rb
|
132
143
|
- lib/phrase/tool.rb
|
144
|
+
- lib/phrase/tool/commands.rb
|
145
|
+
- lib/phrase/tool/commands/base.rb
|
146
|
+
- lib/phrase/tool/commands/init.rb
|
147
|
+
- lib/phrase/tool/commands/pull.rb
|
148
|
+
- lib/phrase/tool/commands/push.rb
|
149
|
+
- lib/phrase/tool/commands/show_help.rb
|
150
|
+
- lib/phrase/tool/commands/show_version.rb
|
133
151
|
- lib/phrase/tool/config.rb
|
152
|
+
- lib/phrase/tool/formats.rb
|
153
|
+
- lib/phrase/tool/formats/base.rb
|
154
|
+
- lib/phrase/tool/formats/gettext.rb
|
155
|
+
- lib/phrase/tool/formats/ini.rb
|
156
|
+
- lib/phrase/tool/formats/json.rb
|
157
|
+
- lib/phrase/tool/formats/plist.rb
|
158
|
+
- lib/phrase/tool/formats/properties.rb
|
159
|
+
- lib/phrase/tool/formats/qt_phrase_book.rb
|
160
|
+
- lib/phrase/tool/formats/qt_translation_source.rb
|
161
|
+
- lib/phrase/tool/formats/resx.rb
|
162
|
+
- lib/phrase/tool/formats/strings.rb
|
163
|
+
- lib/phrase/tool/formats/xliff.rb
|
164
|
+
- lib/phrase/tool/formats/xml.rb
|
165
|
+
- lib/phrase/tool/formats/yaml.rb
|
166
|
+
- lib/phrase/tool/locale.rb
|
134
167
|
- lib/phrase/tool/options.rb
|
135
168
|
- lib/phrase/tool/tag_validator.rb
|
136
169
|
- lib/phrase/version.rb
|