phrase 0.0.0 → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ *.gem
2
+ .phrase
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gem 'json'
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ gem 'webmock', '~>1.7.0'
8
+ gem 'vcr'
9
+ end
@@ -0,0 +1,26 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.2.7)
5
+ crack (0.3.1)
6
+ diff-lcs (1.1.3)
7
+ rspec (2.8.0)
8
+ rspec-core (~> 2.8.0)
9
+ rspec-expectations (~> 2.8.0)
10
+ rspec-mocks (~> 2.8.0)
11
+ rspec-core (2.8.0)
12
+ rspec-expectations (2.8.0)
13
+ diff-lcs (~> 1.1.2)
14
+ rspec-mocks (2.8.0)
15
+ vcr (1.11.3)
16
+ webmock (1.7.10)
17
+ addressable (~> 2.2, > 2.2.5)
18
+ crack (>= 0.1.7)
19
+
20
+ PLATFORMS
21
+ ruby
22
+
23
+ DEPENDENCIES
24
+ rspec
25
+ vcr
26
+ webmock (~> 1.7.0)
@@ -0,0 +1,15 @@
1
+ # Phrase - the Gem
2
+
3
+ ### Installation and usage
4
+
5
+ gem install phrase
6
+
7
+ phrase Prints usage
8
+
9
+ phrase init --secret=<YOUR SECRET>
10
+ phrase push [FILE_NAME]
11
+ phrase pull LOCALE
12
+
13
+ ### More Information
14
+
15
+ http://phraseapp.com
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'phrase/tool'
4
+ PhraseGem::Tool.new(ARGV).run
@@ -1,5 +1,34 @@
1
- class Phrase
2
- def self.readme
3
- puts "please visit phraseapp.com for more information!"
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module Phrase
4
+ autoload :Config, 'phrase/config'
5
+
6
+ class << self
7
+ def config
8
+ Thread.current[:phrase_config] ||= Phrase::Config.new
9
+ end
10
+
11
+ def config=(value)
12
+ Thread.current[:phrase_config] = value
13
+ end
14
+
15
+ %w(backend default_locale available_locales).each do |method|
16
+ module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
17
+ def #{method}
18
+ config.#{method}
19
+ end
20
+ DELEGATORS
21
+
22
+ module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
23
+ def #{method}=(value)
24
+ config.#{method} = (value)
25
+ end
26
+ DELEGATORS
27
+ end
4
28
  end
5
- end
29
+
30
+ autoload :Extensions, 'phrase/extensions'
31
+ autoload :Helpers, 'phrase/helpers'
32
+ require 'phrase/engine'
33
+ require 'phrase/backend'
34
+ end
@@ -0,0 +1,134 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module Phrase::Backend
4
+ # require_relative 'backend/base'
5
+ module Phrase::Backend::Base
6
+ PREFIX = "{{__phrase_"
7
+ SUFFIX = "__}}"
8
+
9
+ # attr_accessor :config
10
+
11
+ def translate(*args)
12
+ key = lookup_normalized_key(*args)
13
+ case key[:translation]
14
+ when String, nil, "" then decorate_translation(key[:key])
15
+ else key[:translation]
16
+ end
17
+ end
18
+
19
+ # def load_translations(*args)
20
+ # raise NotImplementedError
21
+ # end
22
+ #
23
+ # def raw_lookup(locale, key, scope=[], options={})
24
+ # I18n.backend.load_translations unless I18n.backend.initialized?
25
+ # keys = I18n.normalize_keys(locale, key, scope, options[:separator])
26
+ # keys.inject(I18n.backend.send(:translations)) do |result, _key|
27
+ # _key = _key.to_sym
28
+ # return nil unless result.is_a?(Hash) && result.has_key?(_key)
29
+ # result[_key]
30
+ # end
31
+ # end
32
+
33
+ protected
34
+ def decorate_translation(key=nil)
35
+ return nil unless key.presence
36
+ "#{PREFIX}#{key}#{SUFFIX}"
37
+ end
38
+
39
+ def translation_presence(*args)
40
+ I18n.translate!(*args)
41
+ rescue I18n::MissingTranslationData => e
42
+ nil
43
+ end
44
+
45
+ def lookup_normalized_key(*args)
46
+ translation = translation_presence(*args)
47
+ caller = identify_caller
48
+
49
+ if (translation.nil? || translation.is_a?(Hash)) && caller && args.first =~ /^\./
50
+ args = transform_args_for_caller(caller, *args)
51
+ translation = translation_presence(*args)
52
+ end
53
+
54
+ new_args = split_args(*args)
55
+
56
+ normalized_key = I18n::Backend::Flatten.normalize_flat_keys(*new_args)
57
+ normalized_key.gsub!("..", ".")
58
+
59
+ {:key => normalized_key, :translation => translation}
60
+ end
61
+
62
+ def identify_caller
63
+ caller = nil
64
+ send(:caller)[0..6].each { |string| caller = match_caller(string) unless caller }
65
+ caller.present? ? find_lookup_scope(caller) : nil
66
+ end
67
+
68
+ def match_caller(string)
69
+ string.match(/(views)(\/.+)(?>:[0-9]+:in)/)
70
+ end
71
+
72
+ # def extract_required_vars(*args)
73
+ # excluded = ["scope", "locale"]
74
+ # required_vars = args.last.is_a?(Hash) ? args.pop.keys.sort { |a, b| a.to_s <=> b.to_s } : []
75
+ # required_vars.delete_if { |var| excluded.include?(var.to_s) }
76
+ # end
77
+
78
+ def split_args(*args)
79
+ options = args.last.is_a?(Hash) ? args.pop : {}
80
+ key ||= args.shift
81
+ locale = options.delete(:locale) || I18n.locale
82
+ return [locale, key, options[:scope], nil]
83
+ end
84
+
85
+ def transform_args_for_caller(caller, *args)
86
+ _scope = caller
87
+
88
+ options = args.last.is_a?(Hash) ? args.pop : {}
89
+
90
+ if !options[:scope].presence && _scope.presence
91
+ options[:scope] = _scope
92
+ end
93
+
94
+ args.push(options)
95
+ parts = args.first.to_s.split(".").select { |e| !e.blank? }
96
+ args[0] = parts[0] if parts.size == 1
97
+
98
+ return args
99
+ end
100
+
101
+ def find_lookup_scope(caller)
102
+ split_path = caller[2][1..-1].split(".")[0].split("/")
103
+
104
+ template_or_partial = remove_underscore_form_partial(split_path[-1])
105
+ split_path[-1] = template_or_partial
106
+
107
+ split_path.map!(&:to_sym)
108
+ end
109
+
110
+ def remove_underscore_form_partial(template_or_partial)
111
+ if template_or_partial.to_s[0,1] == "_"
112
+ template_or_partial.to_s[1..-1]
113
+ else
114
+ template_or_partial.to_s
115
+ end
116
+ end
117
+ end
118
+
119
+ # require_relative 'backend/phrase_service'
120
+ module Phrase::Backend
121
+ class PhraseService
122
+ include Base
123
+
124
+ def initialize(args = {})
125
+ # self.config = { "locales" => {}, "strings" => {} }
126
+ # Phrase.available_locales = self.config["locales"].keys.map(&:to_sym)
127
+ self
128
+ end
129
+
130
+ end
131
+ end
132
+ # LOCALE in keys mit schreiben, hilft der GUI!? -> manuel
133
+
134
+ end
@@ -0,0 +1,120 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module Phrase::Backend::Base
4
+ PREFIX = "{{__phrase_"
5
+ SUFFIX = "__}}"
6
+
7
+ attr_accessor :config
8
+
9
+ def translate(*args)
10
+ key = lookup_normalized_key(*args)
11
+ case key[:translation]
12
+ when String, nil, "" then decorate_translation(key[:key])
13
+ else key[:translation]
14
+ end
15
+ end
16
+
17
+ def load_translations(*args)
18
+ raise NotImplementedError
19
+ end
20
+
21
+ def store_translation(locale, key, translation, options = {})
22
+ I18n.backend.load_translations unless I18n.backend.initialized?
23
+ I18n.backend.store_translations(locale, key.expand_to_hash(translation))
24
+ end
25
+
26
+ def raw_lookup(locale, key, scope=[], options={})
27
+ I18n.backend.load_translations unless I18n.backend.initialized?
28
+ keys = I18n.normalize_keys(locale, key, scope, options[:separator])
29
+ keys.inject(I18n.backend.send(:translations)) do |result, _key|
30
+ _key = _key.to_sym
31
+ return nil unless result.is_a?(Hash) && result.has_key?(_key)
32
+ result[_key]
33
+ end
34
+ end
35
+
36
+ protected
37
+ def decorate_translation(key=nil)
38
+ return nil unless key.presence
39
+ "#{PREFIX}#{key}#{SUFFIX}"
40
+ end
41
+
42
+ def translation_presence(*args)
43
+ I18n.translate!(*args)
44
+ rescue I18n::MissingTranslationData => e
45
+ nil
46
+ end
47
+
48
+ def lookup_normalized_key(*args)
49
+ translation = translation_presence(*args)
50
+ caller = identify_caller
51
+
52
+ if (translation.nil? || translation.is_a?(Hash)) && caller && args.first =~ /^\./
53
+ args = transform_args_for_caller(caller, *args)
54
+ translation = translation_presence(*args)
55
+ end
56
+
57
+ new_args = split_args(*args)
58
+
59
+ normalized_key = I18n::Backend::Flatten.normalize_flat_keys(*new_args)
60
+ normalized_key.gsub!("..", ".")
61
+
62
+ {:key => normalized_key, :translation => translation}
63
+ end
64
+
65
+ def identify_caller
66
+ caller = nil
67
+ send(:caller)[0..6].each { |string| caller = match_caller(string) unless caller }
68
+ caller.present? ? find_lookup_scope(caller) : nil
69
+ end
70
+
71
+ def match_caller(string)
72
+ string.match(/(views)(\/.+)(?>:[0-9]+:in)/)
73
+ end
74
+
75
+ def extract_required_vars(*args)
76
+ excluded = ["scope", "locale"]
77
+ required_vars = args.last.is_a?(Hash) ? args.pop.keys.sort { |a, b| a.to_s <=> b.to_s } : []
78
+ required_vars.delete_if { |var| excluded.include?(var.to_s) }
79
+ end
80
+
81
+ def split_args(*args)
82
+ options = args.last.is_a?(Hash) ? args.pop : {}
83
+ key ||= args.shift
84
+ locale = options.delete(:locale) || I18n.locale
85
+ return [locale, key, options[:scope], nil]
86
+ end
87
+
88
+ def transform_args_for_caller(caller, *args)
89
+ _scope = caller
90
+
91
+ options = args.last.is_a?(Hash) ? args.pop : {}
92
+
93
+ if !options[:scope].presence && _scope.presence
94
+ options[:scope] = _scope
95
+ end
96
+
97
+ args.push(options)
98
+ parts = args.first.to_s.split(".").select { |e| !e.blank? }
99
+ args[0] = parts[0] if parts.size == 1
100
+
101
+ return args
102
+ end
103
+
104
+ def find_lookup_scope(caller)
105
+ split_path = caller[2][1..-1].split(".")[0].split("/")
106
+
107
+ template_or_partial = remove_underscore_form_partial(split_path[-1])
108
+ split_path[-1] = template_or_partial
109
+
110
+ split_path.map!(&:to_sym)
111
+ end
112
+
113
+ def remove_underscore_form_partial(template_or_partial)
114
+ if template_or_partial.to_s[0,1] == "_"
115
+ template_or_partial.to_s[1..-1]
116
+ else
117
+ template_or_partial.to_s
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,14 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ module Phrase::Backend
4
+ class PhraseService
5
+ include Base
6
+
7
+ def initialize(args = {})
8
+ self.config = { "locales" => {}, "strings" => {} }
9
+ Phrase.available_locales = self.config["locales"].keys.map(&:to_sym)
10
+ self
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,34 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Phrase
3
+ class Config
4
+ def enabled?
5
+ ENV['TRANSLATABLE'] == "true" || (defined?(Rails) == true && Rails.env == "staging")
6
+ end
7
+
8
+ def locale
9
+ I18n.locale
10
+ end
11
+
12
+ def default_locale
13
+ @@default_locale ||= I18n.default_locale
14
+ end
15
+
16
+ def backend
17
+ @@backend ||= Backend::PhraseService.new
18
+ end
19
+
20
+ def backend=(backend)
21
+ @@backend = backend
22
+ end
23
+
24
+ def available_locales
25
+ @@available_locales ||= nil
26
+ @@available_locales ||= I18n.available_locales
27
+ end
28
+
29
+ def available_locales=(locales)
30
+ @@available_locales = Array(locales).map { |locale| locale.to_sym }
31
+ @@available_locales = nil if @@available_locales.empty?
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,18 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'phrase'
3
+ require 'rails'
4
+
5
+ module Phrase
6
+ class Engine < Rails::Engine
7
+
8
+ initializer 'phrase' do |app|
9
+ ActiveSupport.on_load(:action_view) do
10
+ ::ActionView::Base.send :include, Phrase::Extensions::Base
11
+ end
12
+
13
+ ActiveSupport.on_load(:action_controller) do
14
+ ::ActionController::Base.send :include, Phrase::Extensions::Base
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module Phrase::Extensions
2
+ end
3
+
4
+ require_relative 'extensions/hash'
5
+ require_relative 'extensions/string'
6
+ require_relative 'extensions/base'
7
+
@@ -0,0 +1,11 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Phrase::Extensions::Base
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+ def translate(*args)
7
+ Phrase.backend.translate(*args)
8
+ end
9
+ alias_method :t, :translate
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Phrase::Extensions::Hash
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+ def to_shallow_hash
7
+ self.inject({}) do |shallow_hash, (key, value)|
8
+ if value.is_a?(Hash)
9
+ value.to_shallow_hash.each do |sub_key, sub_value|
10
+ shallow_hash[[key, sub_key].join(".")] = sub_value
11
+ end
12
+ else
13
+ shallow_hash[key.to_s] = value
14
+ end
15
+ shallow_hash
16
+ end
17
+ end
18
+
19
+ def deep_stringify_keys
20
+ inject({}) { |result, (key, value)|
21
+ value = value.deep_stringify_keys if value.is_a?(Hash)
22
+ unless value.is_a? Proc
23
+ result[(key.to_s rescue key) || key] = value
24
+ else
25
+ result[(key.to_s rescue key) || key] = nil
26
+ end
27
+ result
28
+ }
29
+ end
30
+ end
31
+ end
32
+
33
+ Hash.send(:include, Phrase::Extensions::Hash)
@@ -0,0 +1,14 @@
1
+ # -*- encoding : utf-8 -*-
2
+ module Phrase::Extensions::String
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+ def expand_to_hash(value=nil)
7
+ self.split('.').reverse.inject(value) do |hash, item|
8
+ {item => hash}
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ String.send(:include, Phrase::Extensions::String)
@@ -0,0 +1,166 @@
1
+ require 'phrase/tool_config'
2
+
3
+ require 'optparse'
4
+ require 'net/http'
5
+ require 'fileutils'
6
+
7
+ module PhraseGem
8
+ class Tool
9
+
10
+ def initialize(argv)
11
+ @args = argv
12
+ end
13
+
14
+ def run
15
+ config = ToolConfig.new
16
+ command = args.first
17
+
18
+ case command
19
+ when /init/
20
+ init(config)
21
+ when /push/
22
+ push(config)
23
+ when /pull/
24
+ pull(config)
25
+ else
26
+ print_usage
27
+ end
28
+ end
29
+
30
+ private
31
+ def init(config)
32
+ secret_param = args.find{ |arg| arg =~ /secret=/ }
33
+ unless secret_param.to_s.match(/secret=.+/)
34
+ $stderr.puts "Need a secret to init, but found none."
35
+ $stderr.puts "Please provide the --secret=YOUR_SECRET parameter."
36
+ exit(41)
37
+ end
38
+
39
+ secret = secret_param.split("=", 2).last
40
+ config.secret = secret
41
+ puts "Wrote secret to config file .phrase"
42
+ end
43
+
44
+ def push(config)
45
+ files = choose_files_to_upload
46
+ if files.empty?
47
+ puts "Could not find any files to upload :("
48
+ exit(43)
49
+ end
50
+
51
+ if !config.secret || config.secret.empty?
52
+ puts "No config present. Please initialize phrase before pushing or pulling."
53
+ exit(43)
54
+ end
55
+
56
+ files.each do |file|
57
+ if file.split('.').last != 'yml'
58
+ $stderr.puts "Currently only .yml files are supported."
59
+ exit(43)
60
+ end
61
+ end
62
+
63
+ upload_files(files, config)
64
+ end
65
+
66
+ def choose_files_to_upload
67
+ file_name = args[1]
68
+ unless file_name
69
+ $stderr.puts "Need either a file or directory:"
70
+ $stderr.puts " phrase push FILE"
71
+ $stderr.puts " phrase push DIRECTORY"
72
+ exit(46)
73
+ end
74
+
75
+ unless File.exist?(file_name)
76
+ $stderr.puts "The file #{file_name} could not be found."
77
+ exit(42)
78
+ end
79
+
80
+ if File.directory?(file_name)
81
+ files = Dir.glob("#{File.expand_path(file_name)}/**")
82
+ else
83
+ files = [file_name]
84
+ end
85
+ end
86
+
87
+ def upload_files(files, config)
88
+ files.each do |file|
89
+ puts "Uploading #{file}..."
90
+ params = {
91
+ 'auth_token'=>config.secret,
92
+ 'filename'=> file,
93
+ 'file_content' => File.read(file)
94
+ }
95
+ res = Net::HTTP.post_form(URI.parse("#{config.api_endpoint}/translation_keys/upload"),params)
96
+
97
+ dump_summary_to_file(res, file)
98
+ unless res.code.to_s =~ /^[23]/
99
+ print_server_error(res, file)
100
+ end
101
+ end
102
+ end
103
+
104
+ def pull(config)
105
+ locale = args[1]
106
+ uri = "#{config.api_endpoint}/translations/download?auth_token=#{config.secret}&locale=#{locale}"
107
+ print "Downloading phrase.#{locale}.yml..."
108
+ ::FileUtils.mkdir_p("phrase/locales")
109
+ res = Net::HTTP.get_response(URI.parse(uri))
110
+ if res.code.to_s =~ /200/
111
+ puts " OK"
112
+ File.open("phrase/locales/phrase.#{locale}.yml", "w") do |file|
113
+ file.write(res.body)
114
+ end
115
+ else
116
+ puts " Failed"
117
+ print_server_error(res)
118
+ end
119
+ end
120
+
121
+ def print_server_error(res, filename=nil)
122
+ error_message = server_error_message(res.body)
123
+ $stderr.puts "Server error: #{res.code} - #{error_message} (#{filename})"
124
+ end
125
+
126
+ def server_error_message(body)
127
+ begin
128
+ JSON.parse(body)["error"]
129
+ rescue JSON::ParserError
130
+ "Unkown error"
131
+ end
132
+ end
133
+
134
+ def print_usage
135
+ $stderr.puts <<USAGE
136
+ Welcome to phrase!
137
+ phrase Prints usage
138
+
139
+ phrase init --secret=<YOUR SECRET>
140
+
141
+ phrase push FILE
142
+ phrase push DIRECTORY
143
+ USAGE
144
+ end
145
+
146
+ def args
147
+ @args
148
+ end
149
+
150
+ def puts_debug_info
151
+ puts "ARGS: #{args.join(",")}"
152
+ puts "Dir.pwd: #{Dir.pwd}"
153
+ end
154
+
155
+ def dump_summary_to_file(res, upload_file)
156
+ ::FileUtils.mkdir_p("phrase/uploads/")
157
+ timestamp = Time.now.strftime("%Y%m%m-%H%M%S")
158
+ file_name = "#{File.basename(upload_file)}-#{timestamp}"
159
+ summary_file = "phrase/uploads/#{file_name}.json"
160
+ File.open(summary_file, "w") do |file|
161
+ file.write(res.body)
162
+ end
163
+ puts "Summary saved in #{summary_file}"
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,41 @@
1
+ require 'json'
2
+
3
+ module PhraseGem
4
+ class ToolConfig
5
+
6
+ def initialize
7
+ if File.exist?(".phrase")
8
+ begin
9
+ contents = File.read(".phrase")
10
+ @config = JSON.parse(contents)
11
+ rescue JSON::ParserError => err
12
+ $stderr.puts "Could not parse config file: #{err}"
13
+ end
14
+ end
15
+ end
16
+
17
+ def secret
18
+ config["secret"]
19
+ end
20
+
21
+ def secret=(new_secret)
22
+ config["secret"] = new_secret
23
+ save_config!
24
+ end
25
+
26
+ def api_endpoint
27
+ ENV.fetch("PHRASE_API_ENDPOINT", "https://phraseapp.com/api/v1")
28
+ end
29
+
30
+ private
31
+ def config
32
+ @config ||= {}
33
+ end
34
+
35
+ def save_config!
36
+ File.open(".phrase", "w+") do |file|
37
+ file.write(JSON.pretty_generate(config))
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib/', __FILE__)
3
+ $:.unshift lib unless $:.include?(lib)
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "phrase"
7
+ s.version = "0.0.1"
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Dynport GmbH"]
10
+ s.email = ["info@phraseapp.com"]
11
+ s.homepage = "http://phraseapp.com"
12
+ s.summary = %q{The best way to manage i18n.}
13
+ s.description = s.summary
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+ s.rubyforge_project = "phrase"
16
+ git_files = `git ls-files | grep -v spec/`.split("\n") rescue ''
17
+ s.files = git_files
18
+ s.executables = %w(phrase)
19
+ s.require_paths = ["lib"]
20
+ end
metadata CHANGED
@@ -1,65 +1,64 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: phrase
3
- version: !ruby/object:Gem::Version
4
- hash: 31
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
5
  prerelease:
6
- segments:
7
- - 0
8
- - 0
9
- - 0
10
- version: 0.0.0
11
6
  platform: ruby
12
- authors:
13
- - Manuel Boy
7
+ authors:
8
+ - Dynport GmbH
14
9
  autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
-
18
- date: 2012-03-12 00:00:00 Z
12
+ date: 2012-03-13 00:00:00.000000000Z
19
13
  dependencies: []
20
-
21
- description: Easy i18n for websites and apps
22
- email: info@phraseapp.com
23
- executables: []
24
-
14
+ description: The best way to manage i18n.
15
+ email:
16
+ - info@phraseapp.com
17
+ executables:
18
+ - phrase
25
19
  extensions: []
26
-
27
20
  extra_rdoc_files: []
28
-
29
- files:
21
+ files:
22
+ - .gitignore
23
+ - Gemfile
24
+ - Gemfile.lock
25
+ - README.md
26
+ - bin/phrase
30
27
  - lib/phrase.rb
28
+ - lib/phrase/backend.rb
29
+ - lib/phrase/backend/base.rb
30
+ - lib/phrase/backend/phrase_service.rb
31
+ - lib/phrase/config.rb
32
+ - lib/phrase/engine.rb
33
+ - lib/phrase/extensions.rb
34
+ - lib/phrase/extensions/base.rb
35
+ - lib/phrase/extensions/hash.rb
36
+ - lib/phrase/extensions/string.rb
37
+ - lib/phrase/tool.rb
38
+ - lib/phrase/tool_config.rb
39
+ - phrase.gemspec
31
40
  homepage: http://phraseapp.com
32
41
  licenses: []
33
-
34
42
  post_install_message:
35
43
  rdoc_options: []
36
-
37
- require_paths:
44
+ require_paths:
38
45
  - lib
39
- required_ruby_version: !ruby/object:Gem::Requirement
46
+ required_ruby_version: !ruby/object:Gem::Requirement
40
47
  none: false
41
- requirements:
42
- - - ">="
43
- - !ruby/object:Gem::Version
44
- hash: 3
45
- segments:
46
- - 0
47
- version: "0"
48
- required_rubygems_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
53
  none: false
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- hash: 3
54
- segments:
55
- - 0
56
- version: "0"
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: 1.3.6
57
58
  requirements: []
58
-
59
- rubyforge_project:
59
+ rubyforge_project: phrase
60
60
  rubygems_version: 1.8.10
61
61
  signing_key:
62
62
  specification_version: 3
63
- summary: phrase i18n
63
+ summary: The best way to manage i18n.
64
64
  test_files: []
65
-