phrase 0.3.1 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +4 -1
  2. data/Guardfile +29 -0
  3. data/README.md +1 -1
  4. data/lib/phrase.rb +8 -9
  5. data/lib/phrase/adapters/fast_gettext.rb +22 -0
  6. data/lib/phrase/adapters/i18n.rb +11 -0
  7. data/lib/phrase/api/client.rb +1 -1
  8. data/lib/phrase/backend/phrase_service.rb +2 -2
  9. data/lib/phrase/delegate.rb +13 -165
  10. data/lib/phrase/delegate/fast_gettext.rb +27 -0
  11. data/lib/phrase/delegate/i18n.rb +159 -0
  12. data/lib/phrase/tool.rb +1 -1
  13. data/lib/phrase/tool/commands/base.rb +1 -1
  14. data/lib/phrase/tool/commands/init.rb +13 -5
  15. data/lib/phrase/tool/commands/pull.rb +11 -8
  16. data/lib/phrase/tool/commands/push.rb +11 -2
  17. data/lib/phrase/tool/config.rb +55 -1
  18. data/lib/phrase/tool/encoding_detector.rb +4 -2
  19. data/lib/phrase/tool/formats.rb +33 -4
  20. data/lib/phrase/tool/formats/base.rb +23 -1
  21. data/lib/phrase/tool/formats/custom.rb +38 -0
  22. data/lib/phrase/tool/formats/gettext.rb +14 -2
  23. data/lib/phrase/tool/formats/gettext_pot.rb +11 -0
  24. data/lib/phrase/tool/formats/ini.rb +4 -0
  25. data/lib/phrase/tool/formats/json.rb +4 -0
  26. data/lib/phrase/tool/formats/plist.rb +5 -1
  27. data/lib/phrase/tool/formats/properties.rb +4 -0
  28. data/lib/phrase/tool/formats/qt_phrase_book.rb +4 -0
  29. data/lib/phrase/tool/formats/qt_translation_source.rb +4 -0
  30. data/lib/phrase/tool/formats/resx.rb +4 -0
  31. data/lib/phrase/tool/formats/strings.rb +9 -1
  32. data/lib/phrase/tool/formats/xliff.rb +4 -0
  33. data/lib/phrase/tool/formats/xml.rb +7 -4
  34. data/lib/phrase/tool/formats/yaml.rb +4 -0
  35. data/lib/phrase/tool/options.rb +13 -52
  36. data/lib/phrase/tool/options_factory.rb +77 -0
  37. data/lib/phrase/version.rb +1 -1
  38. metadata +74 -21
data/.gitignore CHANGED
@@ -19,4 +19,7 @@ tmp
19
19
  .rvmrc
20
20
  .DS_Store
21
21
  /phrase
22
- .rspec
22
+ .rspec
23
+ /locales
24
+ /ru.lproj
25
+ /res
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
@@ -4,4 +4,4 @@ Powerful in-place translation management for your web applications.
4
4
 
5
5
  ## More Information
6
6
 
7
- https://phraseapp.com
7
+ https://phraseapp.com
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
- module I18n
53
- class << self
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
@@ -0,0 +1,11 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module I18n
4
+ class << self
5
+ def translate_with_phrase(*args)
6
+ Phrase.backend.translate(*args)
7
+ end
8
+ alias_method_chain :translate, :phrase
9
+ alias_method :t, :translate
10
+ end
11
+ end if defined?(I18n)
@@ -218,4 +218,4 @@ private
218
218
  def escaped(value)
219
219
  CGI::escape(value)
220
220
  end
221
- end
221
+ end
@@ -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)
@@ -1,175 +1,23 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
- require 'phrase/api'
4
- require 'phrase/cache'
5
- require 'phrase/hash_flattener'
6
- require 'set'
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
- end
54
-
55
- def find_keys_within_phrase(key_names)
56
- key_names_to_check_against_api = key_names - pre_fetched(key_names)
57
- pre_cached(key_names) | key_names_returned_from_api_for(key_names_to_check_against_api)
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
- fallback_items << @options[:default]
14
+ $stderr.puts message
99
15
  end
100
16
  end
101
17
 
102
- fallback_items.each do |item|
103
- process_fallback_item(item)
104
- end
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