phrase 0.3.1 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -1
- data/Guardfile +29 -0
- data/README.md +1 -1
- data/lib/phrase.rb +8 -9
- data/lib/phrase/adapters/fast_gettext.rb +22 -0
- data/lib/phrase/adapters/i18n.rb +11 -0
- data/lib/phrase/api/client.rb +1 -1
- data/lib/phrase/backend/phrase_service.rb +2 -2
- data/lib/phrase/delegate.rb +13 -165
- data/lib/phrase/delegate/fast_gettext.rb +27 -0
- data/lib/phrase/delegate/i18n.rb +159 -0
- data/lib/phrase/tool.rb +1 -1
- data/lib/phrase/tool/commands/base.rb +1 -1
- data/lib/phrase/tool/commands/init.rb +13 -5
- data/lib/phrase/tool/commands/pull.rb +11 -8
- data/lib/phrase/tool/commands/push.rb +11 -2
- data/lib/phrase/tool/config.rb +55 -1
- data/lib/phrase/tool/encoding_detector.rb +4 -2
- data/lib/phrase/tool/formats.rb +33 -4
- data/lib/phrase/tool/formats/base.rb +23 -1
- data/lib/phrase/tool/formats/custom.rb +38 -0
- data/lib/phrase/tool/formats/gettext.rb +14 -2
- data/lib/phrase/tool/formats/gettext_pot.rb +11 -0
- data/lib/phrase/tool/formats/ini.rb +4 -0
- data/lib/phrase/tool/formats/json.rb +4 -0
- data/lib/phrase/tool/formats/plist.rb +5 -1
- data/lib/phrase/tool/formats/properties.rb +4 -0
- data/lib/phrase/tool/formats/qt_phrase_book.rb +4 -0
- data/lib/phrase/tool/formats/qt_translation_source.rb +4 -0
- data/lib/phrase/tool/formats/resx.rb +4 -0
- data/lib/phrase/tool/formats/strings.rb +9 -1
- data/lib/phrase/tool/formats/xliff.rb +4 -0
- data/lib/phrase/tool/formats/xml.rb +7 -4
- data/lib/phrase/tool/formats/yaml.rb +4 -0
- data/lib/phrase/tool/options.rb +13 -52
- data/lib/phrase/tool/options_factory.rb +77 -0
- data/lib/phrase/version.rb +1 -1
- metadata +74 -21
data/.gitignore
CHANGED
data/Guardfile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
# Uncomment next line if Gemfile contain `gemspec' command
|
7
|
+
# watch(/^.+\.gemspec/)
|
8
|
+
end
|
9
|
+
|
10
|
+
guard 'rspec' do
|
11
|
+
watch(%r{^spec/.+_spec\.rb$})
|
12
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
14
|
+
|
15
|
+
# Rails example
|
16
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
17
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
18
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
19
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
20
|
+
watch('config/routes.rb') { "spec/routing" }
|
21
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
22
|
+
|
23
|
+
# Capybara features specs
|
24
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
|
25
|
+
|
26
|
+
# Turnip features and steps
|
27
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
28
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
29
|
+
end
|
data/README.md
CHANGED
data/lib/phrase.rb
CHANGED
@@ -42,19 +42,18 @@ module Phrase
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def self.configure
|
46
|
+
yield(self.config)
|
47
|
+
end
|
48
|
+
|
45
49
|
autoload :ViewHelpers, 'phrase/view_helpers'
|
46
50
|
|
51
|
+
require 'phrase/api'
|
47
52
|
require 'phrase/version'
|
48
53
|
require 'phrase/engine'
|
49
54
|
require 'phrase/backend'
|
55
|
+
require 'phrase/delegate'
|
50
56
|
end
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
def translate_with_phrase(*args)
|
55
|
-
Phrase.backend.translate(*args)
|
56
|
-
end
|
57
|
-
alias_method_chain :translate, :phrase
|
58
|
-
alias_method :t, :translate
|
59
|
-
end
|
60
|
-
end
|
58
|
+
require 'phrase/adapters/i18n'
|
59
|
+
require 'phrase/adapters/fast_gettext'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'phrase/delegate/fast_gettext'
|
4
|
+
|
5
|
+
module FastGettext
|
6
|
+
module Translation
|
7
|
+
def __with_phrase(*args)
|
8
|
+
Phrase::Delegate::FastGettext.new(:_, *args)
|
9
|
+
end
|
10
|
+
alias_method_chain :_, :phrase
|
11
|
+
|
12
|
+
def n__with_phrase(*args)
|
13
|
+
Phrase::Delegate::FastGettext.new(:n_, *args)
|
14
|
+
end
|
15
|
+
alias_method_chain :n_, :phrase
|
16
|
+
|
17
|
+
def s__with_phrase(*args)
|
18
|
+
Phrase::Delegate::FastGettext.new(:s_, *args)
|
19
|
+
end
|
20
|
+
alias_method_chain :s_, :phrase
|
21
|
+
end
|
22
|
+
end if defined? FastGettext::Translation
|
data/lib/phrase/api/client.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
-
require 'phrase/delegate'
|
3
|
+
require 'phrase/delegate/i18n'
|
4
4
|
|
5
5
|
class Phrase::Backend::PhraseService
|
6
6
|
|
@@ -55,7 +55,7 @@ protected
|
|
55
55
|
key = given_key_from_args(args)
|
56
56
|
return nil unless present?(key)
|
57
57
|
options = args[1].nil? ? {} : args[1]
|
58
|
-
Phrase::Delegate.new(key, options)
|
58
|
+
Phrase::Delegate::I18n.new(key, options)
|
59
59
|
end
|
60
60
|
|
61
61
|
def extract_normalized_key_from_args(args)
|
data/lib/phrase/delegate.rb
CHANGED
@@ -1,175 +1,23 @@
|
|
1
1
|
# -*- encoding : utf-8 -*-
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
class Phrase::Delegate < String
|
9
|
-
attr_accessor :key, :display_key, :options, :api_client, :fallback_keys
|
10
|
-
|
11
|
-
def initialize(key, options={})
|
12
|
-
@display_key = @key = key
|
13
|
-
@options = options
|
14
|
-
|
15
|
-
@fallback_keys = []
|
16
|
-
|
17
|
-
extract_fallback_keys
|
18
|
-
identify_key_to_display if @fallback_keys.any?
|
19
|
-
super(decorated_key_name)
|
20
|
-
end
|
21
|
-
|
22
|
-
def to_s
|
23
|
-
"#{decorated_key_name}"
|
24
|
-
end
|
25
|
-
|
26
|
-
def method_missing(*args, &block)
|
27
|
-
if @key.respond_to?(args.first)
|
28
|
-
to_s.send(*args)
|
29
|
-
else
|
30
|
-
data = translation_or_subkeys
|
31
|
-
if data.is_a?(String)
|
32
|
-
to_s
|
33
|
-
elsif data.respond_to?(args.first)
|
34
|
-
data.send(*args, &block)
|
35
|
-
else
|
36
|
-
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."
|
37
|
-
nil
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
private
|
43
|
-
def identify_key_to_display
|
44
|
-
key_names = [@key] | @fallback_keys
|
45
|
-
available_key_names = find_keys_within_phrase(key_names)
|
46
|
-
@display_key = @key
|
47
|
-
key_names.each do |item|
|
48
|
-
if available_key_names.include?(item)
|
49
|
-
@display_key = item
|
50
|
-
break
|
51
|
-
end
|
3
|
+
module Phrase::Delegate
|
4
|
+
class Base < String
|
5
|
+
def to_s
|
6
|
+
"#{decorated_key_name}"
|
52
7
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
def pre_cached(key_names)
|
61
|
-
warm_translation_key_names_cache unless cache.cached?(:translation_key_names)
|
62
|
-
pre_cached_key_names = key_names.select { |key_name| key_name_precached?(key_name) }
|
63
|
-
pre_cached_key_names
|
64
|
-
end
|
65
|
-
|
66
|
-
def pre_fetched(key_names)
|
67
|
-
key_names.select { |key_name| covered_by_initial_caching?(key_name) }
|
68
|
-
end
|
69
|
-
|
70
|
-
def key_name_precached?(key_name)
|
71
|
-
covered = covered_by_initial_caching?(key_name)
|
72
|
-
in_cache = key_name_is_in_cache?(key_name)
|
73
|
-
covered && in_cache
|
74
|
-
end
|
75
|
-
|
76
|
-
def key_names_returned_from_api_for(key_names)
|
77
|
-
if key_names.size > 0
|
78
|
-
api_client.find_keys_by_name(key_names).map { |key| key["name"] }
|
79
|
-
else
|
80
|
-
[]
|
81
|
-
end
|
82
|
-
end
|
83
|
-
|
84
|
-
def key_name_is_in_cache?(key_name)
|
85
|
-
cache.get(:translation_key_names).include?(key_name)
|
86
|
-
end
|
87
|
-
|
88
|
-
def covered_by_initial_caching?(key_name)
|
89
|
-
key_name.start_with?(*Phrase.cache_key_segments_initial)
|
90
|
-
end
|
91
|
-
|
92
|
-
def extract_fallback_keys
|
93
|
-
fallback_items = []
|
94
|
-
if @options.has_key?(:default)
|
95
|
-
if @options[:default].kind_of?(Array)
|
96
|
-
fallback_items = @options[:default]
|
8
|
+
|
9
|
+
def self.log(message)
|
10
|
+
message = "phrase: #{message}"
|
11
|
+
if defined?(Rails)
|
12
|
+
Rails.logger.warn(message)
|
97
13
|
else
|
98
|
-
|
14
|
+
$stderr.puts message
|
99
15
|
end
|
100
16
|
end
|
101
17
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
|
-
def scoped(item)
|
108
|
-
@options.has_key?(:scope) ? "#{@options[:scope]}.#{item}" : item
|
109
|
-
end
|
110
|
-
|
111
|
-
def process_fallback_item(item)
|
112
|
-
if item.kind_of?(Symbol)
|
113
|
-
entry = scoped(item.to_s)
|
114
|
-
@fallback_keys << entry
|
115
|
-
if @key == "helpers.label.#{entry}" # http://apidock.com/rails/v3.1.0/ActionView/Helpers/FormHelper/label
|
116
|
-
@fallback_keys << "activerecord.attributes.#{entry}"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def decorated_key_name
|
122
|
-
"#{Phrase.prefix}phrase_#{@display_key}#{Phrase.suffix}"
|
123
|
-
end
|
124
|
-
|
125
|
-
def translation_or_subkeys
|
126
|
-
begin
|
127
|
-
api_client.translate(@key)
|
128
|
-
rescue Exception => e
|
129
|
-
log "Server Error: #{e.message}"
|
18
|
+
protected
|
19
|
+
def decorated_key_name
|
20
|
+
"#{Phrase.prefix}phrase_#{@display_key}#{Phrase.suffix}"
|
130
21
|
end
|
131
22
|
end
|
132
|
-
|
133
|
-
def api_client
|
134
|
-
@api_client ||= Phrase::Api::Client.new(Phrase.auth_token)
|
135
|
-
end
|
136
|
-
|
137
|
-
def log(message)
|
138
|
-
message = "phrase: #{message}"
|
139
|
-
if defined?(Rails)
|
140
|
-
Rails.logger.warn(message)
|
141
|
-
else
|
142
|
-
$stderr.puts message
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def cache
|
147
|
-
Thread.current[:phrase_cache] ||= build_cache
|
148
|
-
end
|
149
|
-
|
150
|
-
def build_cache
|
151
|
-
cache = Phrase::Cache.new
|
152
|
-
end
|
153
|
-
|
154
|
-
def warm_translation_key_names_cache
|
155
|
-
cache.set(:translation_key_names, prefetched_key_names)
|
156
|
-
end
|
157
|
-
|
158
|
-
def prefetched_key_names
|
159
|
-
prefetched = Set.new
|
160
|
-
Phrase.cache_key_segments_initial.each do |segment|
|
161
|
-
result = api_client.translate(segment)
|
162
|
-
prefetched.add(segment) if result.is_a?(String)
|
163
|
-
prefetched = prefetched.merge(key_names_from_nested(segment, result))
|
164
|
-
end
|
165
|
-
prefetched
|
166
|
-
end
|
167
|
-
|
168
|
-
def key_names_from_nested(segment, data)
|
169
|
-
key_names = Set.new
|
170
|
-
Phrase::HashFlattener.flatten(data, nil) do |key, value|
|
171
|
-
key_names.add("#{segment}.#{key}") unless value.is_a?(Hash)
|
172
|
-
end unless (data.is_a?(String) || data.nil?)
|
173
|
-
key_names
|
174
|
-
end
|
175
23
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'phrase/delegate'
|
4
|
+
|
5
|
+
class Phrase::Delegate::FastGettext < Phrase::Delegate::Base
|
6
|
+
def initialize(method, *args)
|
7
|
+
@method = method
|
8
|
+
params = params_from_args(args)
|
9
|
+
@display_key = params[:msgid]
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def params_from_args(args)
|
14
|
+
params = case @method
|
15
|
+
when :_
|
16
|
+
{msgid: args.first}
|
17
|
+
when :n_
|
18
|
+
{msgid: args.first, msgid_plural: args.second, count: args.last}
|
19
|
+
when :s_
|
20
|
+
{msgid: args.first}
|
21
|
+
else
|
22
|
+
self.class.log("Unsupported FastGettext method #{@method}")
|
23
|
+
{}
|
24
|
+
end
|
25
|
+
params
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'phrase/api'
|
4
|
+
require 'phrase/cache'
|
5
|
+
require 'phrase/hash_flattener'
|
6
|
+
require 'phrase/delegate'
|
7
|
+
require 'set'
|
8
|
+
|
9
|
+
class Phrase::Delegate::I18n < Phrase::Delegate::Base
|
10
|
+
attr_accessor :key, :display_key, :options, :api_client, :fallback_keys
|
11
|
+
|
12
|
+
def initialize(key, options={})
|
13
|
+
@display_key = @key = key
|
14
|
+
@options = options
|
15
|
+
|
16
|
+
@fallback_keys = []
|
17
|
+
|
18
|
+
extract_fallback_keys
|
19
|
+
identify_key_to_display if @fallback_keys.any?
|
20
|
+
super(decorated_key_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(*args, &block)
|
24
|
+
if @key.respond_to?(args.first)
|
25
|
+
to_s.send(*args)
|
26
|
+
else
|
27
|
+
data = translation_or_subkeys
|
28
|
+
if data.is_a?(String)
|
29
|
+
to_s
|
30
|
+
elsif data.respond_to?(args.first)
|
31
|
+
data.send(*args, &block)
|
32
|
+
else
|
33
|
+
self.class.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."
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def identify_key_to_display
|
41
|
+
key_names = [@key] | @fallback_keys
|
42
|
+
available_key_names = find_keys_within_phrase(key_names)
|
43
|
+
@display_key = @key
|
44
|
+
key_names.each do |item|
|
45
|
+
if available_key_names.include?(item)
|
46
|
+
@display_key = item
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_keys_within_phrase(key_names)
|
53
|
+
key_names_to_check_against_api = key_names - pre_fetched(key_names)
|
54
|
+
pre_cached(key_names) | key_names_returned_from_api_for(key_names_to_check_against_api)
|
55
|
+
end
|
56
|
+
|
57
|
+
def pre_cached(key_names)
|
58
|
+
warm_translation_key_names_cache unless cache.cached?(:translation_key_names)
|
59
|
+
pre_cached_key_names = key_names.select { |key_name| key_name_precached?(key_name) }
|
60
|
+
pre_cached_key_names
|
61
|
+
end
|
62
|
+
|
63
|
+
def pre_fetched(key_names)
|
64
|
+
key_names.select { |key_name| covered_by_initial_caching?(key_name) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def key_name_precached?(key_name)
|
68
|
+
covered = covered_by_initial_caching?(key_name)
|
69
|
+
in_cache = key_name_is_in_cache?(key_name)
|
70
|
+
covered && in_cache
|
71
|
+
end
|
72
|
+
|
73
|
+
def key_names_returned_from_api_for(key_names)
|
74
|
+
if key_names.size > 0
|
75
|
+
api_client.find_keys_by_name(key_names).map { |key| key["name"] }
|
76
|
+
else
|
77
|
+
[]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def key_name_is_in_cache?(key_name)
|
82
|
+
cache.get(:translation_key_names).include?(key_name)
|
83
|
+
end
|
84
|
+
|
85
|
+
def covered_by_initial_caching?(key_name)
|
86
|
+
key_name.start_with?(*Phrase.cache_key_segments_initial)
|
87
|
+
end
|
88
|
+
|
89
|
+
def extract_fallback_keys
|
90
|
+
fallback_items = []
|
91
|
+
if @options.has_key?(:default)
|
92
|
+
if @options[:default].kind_of?(Array)
|
93
|
+
fallback_items = @options[:default]
|
94
|
+
else
|
95
|
+
fallback_items << @options[:default]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
fallback_items.each do |item|
|
100
|
+
process_fallback_item(item)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def scoped(item)
|
105
|
+
@options.has_key?(:scope) ? "#{@options[:scope]}.#{item}" : item
|
106
|
+
end
|
107
|
+
|
108
|
+
def process_fallback_item(item)
|
109
|
+
if item.kind_of?(Symbol)
|
110
|
+
entry = scoped(item.to_s)
|
111
|
+
@fallback_keys << entry
|
112
|
+
if @key == "helpers.label.#{entry}" # http://apidock.com/rails/v3.1.0/ActionView/Helpers/FormHelper/label
|
113
|
+
@fallback_keys << "activerecord.attributes.#{entry}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def translation_or_subkeys
|
119
|
+
begin
|
120
|
+
api_client.translate(@key)
|
121
|
+
rescue Exception => e
|
122
|
+
self.class.log "Server Error: #{e.message}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def api_client
|
127
|
+
@api_client ||= Phrase::Api::Client.new(Phrase.auth_token)
|
128
|
+
end
|
129
|
+
|
130
|
+
def cache
|
131
|
+
Thread.current[:phrase_cache] ||= build_cache
|
132
|
+
end
|
133
|
+
|
134
|
+
def build_cache
|
135
|
+
cache = Phrase::Cache.new
|
136
|
+
end
|
137
|
+
|
138
|
+
def warm_translation_key_names_cache
|
139
|
+
cache.set(:translation_key_names, prefetched_key_names)
|
140
|
+
end
|
141
|
+
|
142
|
+
def prefetched_key_names
|
143
|
+
prefetched = Set.new
|
144
|
+
Phrase.cache_key_segments_initial.each do |segment|
|
145
|
+
result = api_client.translate(segment)
|
146
|
+
prefetched.add(segment) if result.is_a?(String)
|
147
|
+
prefetched = prefetched.merge(key_names_from_nested(segment, result))
|
148
|
+
end
|
149
|
+
prefetched
|
150
|
+
end
|
151
|
+
|
152
|
+
def key_names_from_nested(segment, data)
|
153
|
+
key_names = Set.new
|
154
|
+
Phrase::HashFlattener.flatten(data, nil) do |key, value|
|
155
|
+
key_names.add("#{segment}.#{key}") unless value.is_a?(Hash)
|
156
|
+
end unless (data.is_a?(String) || data.nil?)
|
157
|
+
key_names
|
158
|
+
end
|
159
|
+
end
|