phrase 0.1.4 → 0.2.0.beta.2
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/Gemfile +2 -2
- data/Gemfile.lock +6 -6
- data/bin/phrase +1 -1
- data/lib/phrase/api/client.rb +168 -0
- data/lib/phrase/api/config.rb +22 -0
- data/lib/phrase/api/exceptions.rb +9 -0
- data/lib/phrase/api.rb +6 -0
- data/lib/phrase/backend/base.rb +5 -3
- data/lib/phrase/config.rb +64 -62
- data/lib/phrase/delegate.rb +55 -0
- data/lib/phrase/engine.rb +3 -5
- data/lib/phrase/tool.rb +198 -235
- data/lib/phrase/tool_config.rb +26 -45
- data/lib/phrase.rb +1 -0
- data/phrase.gemspec +1 -1
- metadata +16 -6
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -2,7 +2,7 @@ GEM
|
|
2
2
|
remote: http://rubygems.org/
|
3
3
|
specs:
|
4
4
|
activesupport (3.0.0)
|
5
|
-
addressable (2.2.
|
5
|
+
addressable (2.2.8)
|
6
6
|
crack (0.3.1)
|
7
7
|
diff-lcs (1.1.3)
|
8
8
|
i18n (0.6.0)
|
@@ -15,9 +15,9 @@ GEM
|
|
15
15
|
rspec-expectations (2.8.0)
|
16
16
|
diff-lcs (~> 1.1.2)
|
17
17
|
rspec-mocks (2.8.0)
|
18
|
-
vcr (
|
19
|
-
webmock (1.7
|
20
|
-
addressable (
|
18
|
+
vcr (2.2.1)
|
19
|
+
webmock (1.8.7)
|
20
|
+
addressable (>= 2.2.7)
|
21
21
|
crack (>= 0.1.7)
|
22
22
|
|
23
23
|
PLATFORMS
|
@@ -28,5 +28,5 @@ DEPENDENCIES
|
|
28
28
|
i18n
|
29
29
|
json
|
30
30
|
rspec
|
31
|
-
vcr
|
32
|
-
webmock
|
31
|
+
vcr
|
32
|
+
webmock
|
data/bin/phrase
CHANGED
@@ -0,0 +1,168 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'net/https'
|
5
|
+
require 'phrase'
|
6
|
+
require 'phrase/api'
|
7
|
+
|
8
|
+
class Phrase::Api::Client
|
9
|
+
|
10
|
+
METHOD_GET = :get
|
11
|
+
METHOD_POST = :post
|
12
|
+
METHOD_PUT = :put
|
13
|
+
|
14
|
+
attr_reader :auth_token
|
15
|
+
|
16
|
+
def initialize(auth_token)
|
17
|
+
raise "No auth token specified!" if (auth_token.nil? or auth_token.blank?)
|
18
|
+
@auth_token = auth_token
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_locales
|
22
|
+
result = perform_api_request("/locales", :get)
|
23
|
+
locales = []
|
24
|
+
JSON.parse(result).map do |locale|
|
25
|
+
locales << locale['name']
|
26
|
+
end
|
27
|
+
locales
|
28
|
+
end
|
29
|
+
|
30
|
+
def translate(key)
|
31
|
+
raise "You must specify a key" if key.nil? or key.blank?
|
32
|
+
keys = {}
|
33
|
+
result = JSON.parse(perform_api_request("/translation_keys/translate", :get, {:key => key}))
|
34
|
+
if result["translate"]
|
35
|
+
keys = result["translate"]
|
36
|
+
end
|
37
|
+
keys
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_locale(name)
|
41
|
+
raise "You must specify a name" if name.nil? or name.blank?
|
42
|
+
|
43
|
+
begin
|
44
|
+
perform_api_request("/locales", :post, {
|
45
|
+
"locale[name]" => name
|
46
|
+
})
|
47
|
+
rescue Phrase::Api::Exceptions::ServerError => e
|
48
|
+
raise "Locale #{name} could not be created (Maybe it already exists)"
|
49
|
+
end
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
def make_locale_default(name)
|
54
|
+
raise "You must specify a name" if name.nil? or name.blank?
|
55
|
+
|
56
|
+
begin
|
57
|
+
perform_api_request("/locales/#{name}/make_default", :put)
|
58
|
+
rescue Phrase::Api::Exceptions::ServerError => e
|
59
|
+
raise "Locale #{name} could not be made the default locale"
|
60
|
+
end
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def download_translations_for_locale(name)
|
65
|
+
raise "You must specify a name" if name.nil? or name.blank?
|
66
|
+
|
67
|
+
begin
|
68
|
+
content = perform_api_request("/translations/download", :get, {'locale' => name})
|
69
|
+
return content
|
70
|
+
rescue Phrase::Api::Exceptions::ServerError => e
|
71
|
+
raise "Translations #{name} could not be downloaded"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def upload(filename, file_content)
|
76
|
+
begin
|
77
|
+
perform_api_request("/translation_keys/upload", :post, {
|
78
|
+
"filename" => filename,
|
79
|
+
"file_content" => file_content,
|
80
|
+
})
|
81
|
+
rescue Phrase::Api::Exceptions::ServerError => e
|
82
|
+
raise "File #{filename} could not be uploaded"
|
83
|
+
end
|
84
|
+
true
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
def display_api_error(response)
|
89
|
+
error_message = api_error_message(response)
|
90
|
+
$stderr.puts error_message
|
91
|
+
end
|
92
|
+
|
93
|
+
def api_error_message(response)
|
94
|
+
message = ""
|
95
|
+
begin
|
96
|
+
error = JSON.parse(response.body)["error"]
|
97
|
+
if error.class == String
|
98
|
+
message = error
|
99
|
+
else
|
100
|
+
message = JSON.parse(response.body)["message"]
|
101
|
+
end
|
102
|
+
rescue JSON::ParserError
|
103
|
+
end
|
104
|
+
message = "Unknown Error" if message.blank?
|
105
|
+
"Error: #{message} (#{response.code})"
|
106
|
+
end
|
107
|
+
|
108
|
+
def perform_api_request(endpoint, method=METHOD_GET, params={})
|
109
|
+
request = case method
|
110
|
+
when METHOD_GET
|
111
|
+
get_request(endpoint, params)
|
112
|
+
when METHOD_POST
|
113
|
+
post_request(endpoint, params)
|
114
|
+
when METHOD_PUT
|
115
|
+
put_request(endpoint, params)
|
116
|
+
else
|
117
|
+
raise "Invalid Request Method: #{method}"
|
118
|
+
end
|
119
|
+
response = http_client.request(request)
|
120
|
+
code = response.code.to_i
|
121
|
+
if (code == 200)
|
122
|
+
return response.body
|
123
|
+
else
|
124
|
+
error_message = api_error_message(response)
|
125
|
+
raise Phrase::Api::Exceptions::Unauthorized.new(error_message) if (code == 401)
|
126
|
+
raise Phrase::Api::Exceptions::ServerError.new(error_message)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_request(endpoint, params={})
|
131
|
+
query_string = "?auth_token=#{@auth_token}"
|
132
|
+
params.each do |key, value|
|
133
|
+
query_string << "&#{key}=#{value}"
|
134
|
+
end unless params.empty?
|
135
|
+
request = Net::HTTP::Get.new("#{api_path_for(endpoint)}#{query_string}")
|
136
|
+
request
|
137
|
+
end
|
138
|
+
|
139
|
+
def post_request(endpoint, params={})
|
140
|
+
request = Net::HTTP::Post.new("#{api_path_for(endpoint)}")
|
141
|
+
params.merge!({
|
142
|
+
'auth_token' => @auth_token
|
143
|
+
})
|
144
|
+
request.set_form_data(params)
|
145
|
+
request
|
146
|
+
end
|
147
|
+
|
148
|
+
def put_request(endpoint, params={})
|
149
|
+
request = Net::HTTP::Put.new("#{api_path_for(endpoint)}")
|
150
|
+
params.merge!({
|
151
|
+
'auth_token' => @auth_token
|
152
|
+
})
|
153
|
+
request.set_form_data(params)
|
154
|
+
request
|
155
|
+
end
|
156
|
+
|
157
|
+
def api_path_for(endpoint)
|
158
|
+
"#{Phrase::Api::Config.api_path_prefix}#{endpoint}"
|
159
|
+
end
|
160
|
+
|
161
|
+
def http_client
|
162
|
+
client = Net::HTTP.new(Phrase::Api::Config.api_host, Phrase::Api::Config.api_port)
|
163
|
+
client.use_ssl = true if Phrase::Api::Config.api_use_ssl?
|
164
|
+
client.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
165
|
+
client.ca_file = File.join(File.dirname(__FILE__), "..", "..", "cacert.pem")
|
166
|
+
client
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'phrase'
|
4
|
+
require 'phrase/api'
|
5
|
+
|
6
|
+
class Phrase::Api::Config
|
7
|
+
def self.api_port
|
8
|
+
ENV.fetch("PHRASE_API_PORT", "443")
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.api_host
|
12
|
+
ENV.fetch("PHRASE_API_HOST", "phraseapp.com")
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.api_path_prefix
|
16
|
+
"/api/#{Phrase::API_VERSION}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.api_use_ssl?
|
20
|
+
(ENV.fetch("PHRASE_API_USE_SSL", "1") == "1")
|
21
|
+
end
|
22
|
+
end
|
data/lib/phrase/api.rb
ADDED
data/lib/phrase/backend/base.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
+
require 'phrase/delegate'
|
4
|
+
|
3
5
|
module Phrase::Backend::Base
|
4
6
|
|
5
7
|
def translate(*args)
|
6
8
|
if Phrase.enabled?
|
7
9
|
key = lookup_normalized_key(*args)
|
8
|
-
|
10
|
+
phrase_delegate_for(key[:key])
|
9
11
|
else
|
10
12
|
I18n.translate_without_phrase(*args)
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
protected
|
15
|
-
def
|
17
|
+
def phrase_delegate_for(key=nil)
|
16
18
|
return nil unless key.presence
|
17
|
-
|
19
|
+
Phrase::Delegate.new(key)
|
18
20
|
end
|
19
21
|
|
20
22
|
def lookup_normalized_key(*args)
|
data/lib/phrase/config.rb
CHANGED
@@ -1,68 +1,70 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
3
|
+
class Phrase::Config
|
4
|
+
def client_version
|
5
|
+
Phrase::CLIENT_VERSION
|
6
|
+
end
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def backend=(backend)
|
32
|
-
@@backend = backend
|
33
|
-
end
|
34
|
-
|
35
|
-
def prefix
|
36
|
-
@@prefix ||= "{{__"
|
37
|
-
end
|
38
|
-
|
39
|
-
def prefix=(prefix)
|
40
|
-
@@prefix = prefix
|
41
|
-
end
|
42
|
-
|
43
|
-
def suffix
|
44
|
-
@@suffix ||= "__}}"
|
45
|
-
end
|
46
|
-
|
47
|
-
def suffix=(suffix)
|
48
|
-
@@suffix = suffix
|
49
|
-
end
|
50
|
-
|
51
|
-
def js_host
|
52
|
-
@@js_host ||= 'phraseapp.com'
|
53
|
-
end
|
54
|
-
|
55
|
-
def js_host=(js_host)
|
56
|
-
@@js_host = js_host
|
57
|
-
end
|
58
|
-
|
59
|
-
def js_use_ssl
|
60
|
-
@@js_use_ssl = true if !defined? @@js_use_ssl or @@js_use_ssl.nil?
|
61
|
-
@@js_use_ssl
|
62
|
-
end
|
8
|
+
def api_version
|
9
|
+
Phrase::API_VERSION
|
10
|
+
end
|
11
|
+
|
12
|
+
def auth_token
|
13
|
+
@@auth_token = "" if !defined? @@auth_token or @@auth_token.nil?
|
14
|
+
@@auth_token
|
15
|
+
end
|
16
|
+
|
17
|
+
def auth_token=(auth_token)
|
18
|
+
@@auth_token = auth_token
|
19
|
+
end
|
20
|
+
|
21
|
+
def enabled
|
22
|
+
@@enabled = true if !defined? @@enabled or @@enabled.nil?
|
23
|
+
@@enabled
|
24
|
+
end
|
25
|
+
|
26
|
+
def enabled=(enabled)
|
27
|
+
@@enabled = enabled
|
28
|
+
end
|
63
29
|
|
64
|
-
|
65
|
-
|
66
|
-
|
30
|
+
def backend
|
31
|
+
@@backend ||= Phrase::Backend::PhraseService.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def backend=(backend)
|
35
|
+
@@backend = backend
|
36
|
+
end
|
37
|
+
|
38
|
+
def prefix
|
39
|
+
@@prefix ||= "{{__"
|
40
|
+
end
|
41
|
+
|
42
|
+
def prefix=(prefix)
|
43
|
+
@@prefix = prefix
|
44
|
+
end
|
45
|
+
|
46
|
+
def suffix
|
47
|
+
@@suffix ||= "__}}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def suffix=(suffix)
|
51
|
+
@@suffix = suffix
|
52
|
+
end
|
53
|
+
|
54
|
+
def js_host
|
55
|
+
@@js_host ||= 'phraseapp.com'
|
56
|
+
end
|
57
|
+
|
58
|
+
def js_host=(js_host)
|
59
|
+
@@js_host = js_host
|
60
|
+
end
|
61
|
+
|
62
|
+
def js_use_ssl
|
63
|
+
@@js_use_ssl = true if !defined? @@js_use_ssl or @@js_use_ssl.nil?
|
64
|
+
@@js_use_ssl
|
65
|
+
end
|
66
|
+
|
67
|
+
def js_use_ssl=(js_use_ssl)
|
68
|
+
@@js_use_ssl = js_use_ssl
|
67
69
|
end
|
68
70
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'phrase/api'
|
2
|
+
|
3
|
+
class Phrase::Delegate
|
4
|
+
attr_accessor :key, :api_client
|
5
|
+
|
6
|
+
def initialize(key)
|
7
|
+
@key = key
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
"#{decorated_key_name}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def method_missing(*args, &block)
|
15
|
+
if @key.respond_to?(args.first)
|
16
|
+
to_s.send(*args)
|
17
|
+
else
|
18
|
+
data = translation_or_subkeys
|
19
|
+
if data.is_a?(String)
|
20
|
+
to_s
|
21
|
+
elsif data.respond_to?(args.first)
|
22
|
+
data.send(*args, &block)
|
23
|
+
else
|
24
|
+
log "You are trying to execute the method ##{args.first} on a translation key which is not supported. Please make sure you treat your translations as strings only."
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def decorated_key_name
|
32
|
+
"#{Phrase.prefix}phrase_#{@key}#{Phrase.suffix}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def translation_or_subkeys
|
36
|
+
begin
|
37
|
+
api_client.translate(@key)
|
38
|
+
rescue Exception => e
|
39
|
+
log "Server Error: #{e.message}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def api_client
|
44
|
+
@api_client ||= Phrase::Api::Client.new(Phrase.auth_token)
|
45
|
+
end
|
46
|
+
|
47
|
+
def log(message)
|
48
|
+
message = "phrase: #{message}"
|
49
|
+
if defined?(Rails)
|
50
|
+
Rails.logger.warn(message)
|
51
|
+
else
|
52
|
+
$stderr.puts message
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/phrase/engine.rb
CHANGED
@@ -4,11 +4,9 @@ require 'phrase'
|
|
4
4
|
require 'i18n'
|
5
5
|
|
6
6
|
if defined? Rails
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
ActionView::Base.send :include, Phrase::ViewHelpers
|
11
|
-
end
|
7
|
+
class Phrase::Engine < Rails::Engine
|
8
|
+
initializer 'phrase' do |app|
|
9
|
+
ActionView::Base.send :include, Phrase::ViewHelpers
|
12
10
|
end
|
13
11
|
end
|
14
12
|
end
|
data/lib/phrase/tool.rb
CHANGED
@@ -1,277 +1,240 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
-
require 'phrase/tool_config'
|
4
|
-
|
5
3
|
require 'optparse'
|
6
4
|
require 'net/http'
|
7
5
|
require 'net/https'
|
8
6
|
require 'fileutils'
|
7
|
+
require 'phrase/tool_config'
|
9
8
|
|
10
|
-
|
11
|
-
|
9
|
+
class Phrase::Tool
|
10
|
+
|
11
|
+
attr_accessor :config
|
12
|
+
|
13
|
+
def initialize(argv)
|
14
|
+
@args = argv
|
15
|
+
end
|
12
16
|
|
13
|
-
|
14
|
-
|
17
|
+
def run
|
18
|
+
@config = Phrase::ToolConfig.new
|
19
|
+
command = args.first
|
20
|
+
|
21
|
+
case command
|
22
|
+
when /init/
|
23
|
+
init
|
24
|
+
when /push/
|
25
|
+
push
|
26
|
+
when /pull/
|
27
|
+
pull
|
28
|
+
else
|
29
|
+
print_usage
|
15
30
|
end
|
31
|
+
end
|
16
32
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
when /push/
|
25
|
-
push(config)
|
26
|
-
when /pull/
|
27
|
-
pull(config)
|
28
|
-
else
|
29
|
-
print_usage
|
30
|
-
end
|
33
|
+
protected
|
34
|
+
def init
|
35
|
+
secret_param = args.find{ |arg| arg =~ /secret=/ }
|
36
|
+
unless secret_param.to_s.match(/secret=.+/)
|
37
|
+
$stderr.puts "Need a secret to init, but found none."
|
38
|
+
$stderr.puts "Please provide the --secret=YOUR_SECRET parameter."
|
39
|
+
exit(41)
|
31
40
|
end
|
32
41
|
|
33
|
-
|
42
|
+
secret = secret_param.split("=", 2).last
|
43
|
+
@config.secret = secret
|
44
|
+
puts "Wrote secret to config file .phrase"
|
34
45
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
46
|
+
default_locale_param = args.find{ |arg| arg =~ /default-locale=/ }
|
47
|
+
if default_locale_param.to_s.match(/default-locale=.+/)
|
48
|
+
locale_name = default_locale_param.split("=", 2).last
|
49
|
+
else
|
50
|
+
locale_name = "en"
|
51
|
+
end
|
52
|
+
create_locale(locale_name)
|
53
|
+
make_locale_default(locale_name)
|
54
|
+
end
|
42
55
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
else
|
51
|
-
locale_name = "en"
|
52
|
-
end
|
53
|
-
create_locale(locale_name, config)
|
54
|
-
make_locale_default(locale_name, config)
|
56
|
+
def push
|
57
|
+
check_config_available
|
58
|
+
|
59
|
+
files = choose_files_to_upload
|
60
|
+
if files.empty?
|
61
|
+
puts "Could not find any files to upload :("
|
62
|
+
exit(43)
|
55
63
|
end
|
64
|
+
upload_files(files)
|
65
|
+
end
|
56
66
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
upload_files(files, config)
|
67
|
+
def pull
|
68
|
+
check_config_available
|
69
|
+
|
70
|
+
create_locales_folder!
|
71
|
+
|
72
|
+
locale = args[1]
|
73
|
+
locales = []
|
74
|
+
if locale && locale.strip != ''
|
75
|
+
locales = [locale]
|
76
|
+
else
|
77
|
+
locales = fetch_locales
|
70
78
|
end
|
79
|
+
|
80
|
+
locales.each do |locale_name|
|
81
|
+
print "Downloading phrase.#{locale_name}.yml..."
|
82
|
+
fetch_translations_for_locale(locale_name)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def print_usage
|
87
|
+
$stderr.puts <<USAGE
|
88
|
+
Welcome to phrase!
|
71
89
|
|
72
|
-
|
73
|
-
file_name = args[1]
|
74
|
-
|
75
|
-
unless file_name
|
76
|
-
if self.class.rails_default_locale_folder_is_available
|
77
|
-
file_name = self.class.rails_default_locale_folder
|
78
|
-
puts "No file or directory specified, using #{self.class.rails_default_locale_folder}"
|
79
|
-
else
|
80
|
-
$stderr.puts "Need either a file or directory:"
|
81
|
-
$stderr.puts "phrase push FILE"
|
82
|
-
$stderr.puts "phrase push DIRECTORY"
|
83
|
-
exit(46)
|
84
|
-
end
|
85
|
-
end
|
90
|
+
phrase Prints usage
|
86
91
|
|
87
|
-
|
88
|
-
$stderr.puts "The file #{file_name} could not be found."
|
89
|
-
exit(42)
|
90
|
-
end
|
92
|
+
phrase init --secret=<YOUR SECRET> --default-locale=<YOUR DEFAULT LOCALE>
|
91
93
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
94
|
+
phrase push FILE
|
95
|
+
phrase push DIRECTORY
|
96
|
+
USAGE
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
def choose_files_to_upload
|
101
|
+
file_name = args[1]
|
102
|
+
|
103
|
+
unless file_name
|
104
|
+
if self.class.rails_default_locale_folder_is_available
|
105
|
+
file_name = self.class.rails_default_locale_folder
|
106
|
+
puts "No file or directory specified, using #{self.class.rails_default_locale_folder}"
|
107
|
+
else
|
108
|
+
$stderr.puts "Need either a file or directory:"
|
109
|
+
$stderr.puts "phrase push FILE"
|
110
|
+
$stderr.puts "phrase push DIRECTORY"
|
111
|
+
exit(46)
|
96
112
|
end
|
97
113
|
end
|
98
114
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
if File.directory?(file)
|
104
|
-
proceed_with_upload = false
|
105
|
-
end
|
106
|
-
|
107
|
-
if is_yaml_file(file)
|
108
|
-
proceed_with_upload = false
|
109
|
-
$stderr.puts "Notice: Could not upload #{file} (extension not supported - see http://phraseapp.com/help for more information)"
|
110
|
-
end
|
111
|
-
|
112
|
-
if proceed_with_upload
|
113
|
-
puts "Uploading #{file}..."
|
114
|
-
params = {
|
115
|
-
'auth_token' => config.secret,
|
116
|
-
'filename' => file,
|
117
|
-
'file_content' => File.read(file)
|
118
|
-
}
|
119
|
-
|
120
|
-
http = http_client(config)
|
121
|
-
|
122
|
-
request = Net::HTTP::Post.new("#{config.api_path_prefix}/translation_keys/upload")
|
123
|
-
request.set_form_data(params)
|
124
|
-
res = http.request(request)
|
125
|
-
|
126
|
-
unless res.code.to_s =~ /^[23]/
|
127
|
-
print_server_error(res, file)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
end
|
115
|
+
unless File.exist?(file_name)
|
116
|
+
$stderr.puts "The file #{file_name} could not be found."
|
117
|
+
exit(42)
|
131
118
|
end
|
132
119
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
if locale && locale.strip != ''
|
138
|
-
locales = [locale]
|
139
|
-
else
|
140
|
-
locales = fetch_locales(config)
|
141
|
-
end
|
142
|
-
|
143
|
-
locales.each do |locale|
|
144
|
-
fetch_translation_for_locale(config, locale)
|
145
|
-
end
|
120
|
+
if File.directory?(file_name)
|
121
|
+
files = Dir.glob("#{File.expand_path(file_name)}/**")
|
122
|
+
else
|
123
|
+
files = [file_name]
|
146
124
|
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def upload_files(files)
|
128
|
+
files.each do |file|
|
129
|
+
proceed_with_upload = true
|
147
130
|
|
148
|
-
|
149
|
-
|
150
|
-
::FileUtils.mkdir_p("phrase/locales")
|
151
|
-
|
152
|
-
http = http_client(config)
|
153
|
-
|
154
|
-
request = Net::HTTP::Get.new("#{config.api_path_prefix}/translations/download?auth_token=#{config.secret}&locale=#{locale}")
|
155
|
-
res = http.request(request)
|
156
|
-
|
157
|
-
if res.code.to_s =~ /200/
|
158
|
-
puts "OK"
|
159
|
-
File.open("phrase/locales/phrase.#{locale}.yml", "w") do |file|
|
160
|
-
file.write(res.body)
|
161
|
-
end
|
162
|
-
else
|
163
|
-
puts "Failed"
|
164
|
-
print_server_error(res)
|
131
|
+
if File.directory?(file)
|
132
|
+
proceed_with_upload = false
|
165
133
|
end
|
166
|
-
end
|
167
134
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
request = Net::HTTP::Get.new("#{config.api_path_prefix}/locales?auth_token=#{config.secret}")
|
172
|
-
res = http.request(request)
|
173
|
-
|
174
|
-
if res.code.to_s =~ /200/
|
175
|
-
puts "Fetched all locales"
|
176
|
-
return JSON.parse(res.body).map { |locale| locale['name'] }
|
177
|
-
else
|
178
|
-
puts "Failed"
|
179
|
-
print_server_error(res)
|
180
|
-
exit(47)
|
135
|
+
if is_yaml_file(file)
|
136
|
+
proceed_with_upload = false
|
137
|
+
$stderr.puts "Notice: Could not upload #{file} (extension not supported - see http://phraseapp.com/help for more information)"
|
181
138
|
end
|
182
|
-
end
|
183
139
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
request.set_form_data(params)
|
194
|
-
res = http.request(request)
|
195
|
-
|
196
|
-
if res.code.to_s =~ /200/
|
197
|
-
puts "Created locale \"#{locale_name}\""
|
198
|
-
else
|
199
|
-
puts "Notice: Locale \"#{locale_name}\" could not be created (maybe it already exists)"
|
140
|
+
if proceed_with_upload
|
141
|
+
begin
|
142
|
+
puts "Uploading #{file}..."
|
143
|
+
api_client.upload(file, File.read(file))
|
144
|
+
puts "OK"
|
145
|
+
rescue Exception => e
|
146
|
+
puts "Failed"
|
147
|
+
print_server_error(e.message, file)
|
148
|
+
end
|
200
149
|
end
|
201
150
|
end
|
202
|
-
|
203
|
-
def make_locale_default(locale_name, config)
|
204
|
-
params = {
|
205
|
-
'auth_token' => config.secret,
|
206
|
-
}
|
151
|
+
end
|
207
152
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
else
|
217
|
-
puts "Notice: Locale \"#{locale_name}\" could not be made the default locale"
|
218
|
-
print_server_error(res)
|
219
|
-
end
|
153
|
+
def fetch_translations_for_locale(name)
|
154
|
+
begin
|
155
|
+
content = api_client.download_translations_for_locale(name)
|
156
|
+
puts "OK"
|
157
|
+
store_translations_file(name, content)
|
158
|
+
rescue Exception => e
|
159
|
+
puts "Failed"
|
160
|
+
print_server_error(e.message)
|
220
161
|
end
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
162
|
+
end
|
163
|
+
|
164
|
+
def store_translations_file(name, content)
|
165
|
+
File.open("phrase/locales/phrase.#{name}.yml", "w") do |file|
|
166
|
+
file.write(content)
|
225
167
|
end
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
168
|
+
end
|
169
|
+
|
170
|
+
def fetch_locales
|
171
|
+
begin
|
172
|
+
locales = api_client.fetch_locales
|
173
|
+
puts "Fetched all locales"
|
174
|
+
return locales
|
175
|
+
rescue Exception => e
|
176
|
+
puts "Failed"
|
177
|
+
print_server_error(e.message)
|
178
|
+
exit(47)
|
233
179
|
end
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
phrase push FILE
|
244
|
-
phrase push DIRECTORY
|
245
|
-
USAGE
|
180
|
+
end
|
181
|
+
|
182
|
+
def create_locale(name)
|
183
|
+
begin
|
184
|
+
api_client.create_locale(name)
|
185
|
+
puts "Created locale \"#{name}\""
|
186
|
+
rescue Exception => e
|
187
|
+
puts "Notice: Locale \"#{name}\" could not be created (maybe it already exists)"
|
246
188
|
end
|
247
|
-
|
248
|
-
|
249
|
-
|
189
|
+
end
|
190
|
+
|
191
|
+
def make_locale_default(name)
|
192
|
+
begin
|
193
|
+
api_client.make_locale_default(name)
|
194
|
+
puts "Locale \"#{name}\" is now the default locale"
|
195
|
+
rescue Exception => e
|
196
|
+
puts "Notice: Locale \"#{name}\" could not be made the default locale"
|
197
|
+
print_server_error(e.message)
|
250
198
|
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def api_client
|
202
|
+
Phrase::Api::Client.new(@config.secret)
|
203
|
+
end
|
251
204
|
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
205
|
+
def print_server_error(message, filename=nil)
|
206
|
+
$stderr.puts "#{message} (#{filename})"
|
207
|
+
end
|
208
|
+
|
209
|
+
def args
|
210
|
+
@args
|
211
|
+
end
|
212
|
+
|
213
|
+
def puts_debug_info
|
214
|
+
puts "ARGS: #{args.join(",")}"
|
215
|
+
puts "Dir.pwd: #{Dir.pwd}"
|
216
|
+
end
|
217
|
+
|
218
|
+
def is_yaml_file(filepath)
|
219
|
+
!File.directory?(filepath) && filepath.split('.').last != 'yml'
|
220
|
+
end
|
221
|
+
|
222
|
+
def create_locales_folder!
|
223
|
+
::FileUtils.mkdir_p("phrase/locales")
|
224
|
+
end
|
225
|
+
|
226
|
+
def check_config_available
|
227
|
+
if !@config.secret || @config.secret.empty?
|
228
|
+
$stderr.puts "No config present. You need to initialize phrase first."
|
229
|
+
exit(43)
|
275
230
|
end
|
276
231
|
end
|
277
|
-
|
232
|
+
|
233
|
+
def self.rails_default_locale_folder
|
234
|
+
"./config/locales/"
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.rails_default_locale_folder_is_available
|
238
|
+
File.exist?(rails_default_locale_folder) && File.directory?(rails_default_locale_folder)
|
239
|
+
end
|
240
|
+
end
|
data/lib/phrase/tool_config.rb
CHANGED
@@ -1,56 +1,37 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
+
require 'phrase'
|
3
4
|
require 'json'
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
rescue JSON::ParserError => err
|
14
|
-
$stderr.puts "Could not parse config file: #{err}"
|
15
|
-
end
|
6
|
+
class Phrase::ToolConfig
|
7
|
+
def initialize
|
8
|
+
if File.exist?(".phrase")
|
9
|
+
begin
|
10
|
+
contents = File.read(".phrase")
|
11
|
+
@config = JSON.parse(contents)
|
12
|
+
rescue JSON::ParserError => err
|
13
|
+
$stderr.puts "Could not parse config file: #{err}"
|
16
14
|
end
|
17
15
|
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def secret
|
19
|
+
config["secret"]
|
20
|
+
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
def api_port
|
29
|
-
ENV.fetch("PHRASE_API_PORT", "443")
|
30
|
-
end
|
31
|
-
|
32
|
-
def api_host
|
33
|
-
ENV.fetch("PHRASE_API_HOST", "phraseapp.com")
|
34
|
-
end
|
35
|
-
|
36
|
-
def api_path_prefix
|
37
|
-
"/api/v1"
|
38
|
-
end
|
39
|
-
|
40
|
-
def api_use_ssl
|
41
|
-
ENV.fetch("PHRASE_API_USE_SSL", "1")
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def config
|
47
|
-
@config ||= {}
|
48
|
-
end
|
22
|
+
def secret=(new_secret)
|
23
|
+
config["secret"] = new_secret
|
24
|
+
save_config!
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def config
|
29
|
+
@config ||= {}
|
30
|
+
end
|
49
31
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
32
|
+
def save_config!
|
33
|
+
File.open(".phrase", "w+") do |file|
|
34
|
+
file.write(JSON.pretty_generate(config))
|
54
35
|
end
|
55
36
|
end
|
56
37
|
end
|
data/lib/phrase.rb
CHANGED
data/phrase.gemspec
CHANGED
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: phrase
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0.beta.2
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Dynport GmbH
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-06-
|
12
|
+
date: 2012-06-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
16
|
-
requirement:
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,7 +21,12 @@ dependencies:
|
|
21
21
|
version: '3.0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements:
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.0'
|
25
30
|
description: phrase allows you to edit translations inline, on the page itself. More
|
26
31
|
information at phraseapp.com
|
27
32
|
email:
|
@@ -38,10 +43,15 @@ files:
|
|
38
43
|
- bin/phrase
|
39
44
|
- cacert.pem
|
40
45
|
- lib/phrase.rb
|
46
|
+
- lib/phrase/api.rb
|
47
|
+
- lib/phrase/api/client.rb
|
48
|
+
- lib/phrase/api/config.rb
|
49
|
+
- lib/phrase/api/exceptions.rb
|
41
50
|
- lib/phrase/backend.rb
|
42
51
|
- lib/phrase/backend/base.rb
|
43
52
|
- lib/phrase/backend/phrase_service.rb
|
44
53
|
- lib/phrase/config.rb
|
54
|
+
- lib/phrase/delegate.rb
|
45
55
|
- lib/phrase/engine.rb
|
46
56
|
- lib/phrase/tool.rb
|
47
57
|
- lib/phrase/tool_config.rb
|
@@ -67,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
67
77
|
version: 1.3.6
|
68
78
|
requirements: []
|
69
79
|
rubyforge_project: phrase
|
70
|
-
rubygems_version: 1.8.
|
80
|
+
rubygems_version: 1.8.24
|
71
81
|
signing_key:
|
72
82
|
specification_version: 3
|
73
83
|
summary: The best way to manage i18n.
|