phrase 0.0.0 → 0.0.1

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.
@@ -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
-