phrase 0.3.1 → 0.3.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/.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
|