16watts-fluently 0.3.0
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/.document +5 -0
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +59 -0
- data/Rakefile +54 -0
- data/VERSION.yml +4 -0
- data/fluently.gemspec +62 -0
- data/lib/fluently/cocoa_marshal.rb +46 -0
- data/lib/fluently/connection.rb +149 -0
- data/lib/fluently/i18n/backend.rb +123 -0
- data/lib/fluently/i18n.rb +7 -0
- data/lib/fluently/rails_marshal.rb +17 -0
- data/lib/fluently/rake/base/task.rb +40 -0
- data/lib/fluently/rake/cocoa/task.rb +31 -0
- data/lib/fluently/rake/rails/task.rb +31 -0
- data/lib/fluently/reader.rb +85 -0
- data/lib/fluently.rb +2 -0
- data/spec/fluently_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +92 -0
data/.document
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 16watts
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
= fluently
|
2
|
+
|
3
|
+
Fluently (http://fluent.ly/) helps you translate your application
|
4
|
+
into other languages. The fluently gem provides a rake task that
|
5
|
+
will sync your project's default locale up to the Fluently servers,
|
6
|
+
and conversely sync any translations on the Fluently servers in to
|
7
|
+
your local codebase.
|
8
|
+
|
9
|
+
== Install
|
10
|
+
|
11
|
+
$ gem sources -a http://gems.github.com (you only have to do this once)
|
12
|
+
$ sudo gem install 16watts-fluently
|
13
|
+
|
14
|
+
|
15
|
+
== Setup
|
16
|
+
|
17
|
+
Put the following in a Rakefile in the root of your project.
|
18
|
+
|
19
|
+
=== Cocoa project
|
20
|
+
|
21
|
+
require 'rubygems'
|
22
|
+
require 'fluently'
|
23
|
+
require 'fluently/rake/cocoa/task'
|
24
|
+
Fluently::Rake::Task.new do |t|
|
25
|
+
t.api_key = "abc123"
|
26
|
+
t.api_secret = "xyz456"
|
27
|
+
end
|
28
|
+
|
29
|
+
=== Ruby on Rails project
|
30
|
+
|
31
|
+
require 'rubygems'
|
32
|
+
require 'fluently'
|
33
|
+
require 'fluently/rake/rails/task'
|
34
|
+
Fluently::Rake::Task.new do |t|
|
35
|
+
t.api_key = "abc123"
|
36
|
+
t.api_secret = "xyz456"
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
== Usage
|
41
|
+
|
42
|
+
$ rake fluently:sync
|
43
|
+
|
44
|
+
or simply
|
45
|
+
|
46
|
+
$ rake fluently
|
47
|
+
|
48
|
+
|
49
|
+
== Other project types
|
50
|
+
|
51
|
+
Usually this is as easy as examining the Marshal objects included with
|
52
|
+
this gem. We'll incorporate Marshals written by the community in
|
53
|
+
future versions -- just fork this project on github and issue a pull
|
54
|
+
request.
|
55
|
+
|
56
|
+
|
57
|
+
== Copyright
|
58
|
+
|
59
|
+
Copyright (c) 2009 16watts. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "fluently"
|
8
|
+
gem.summary = %Q{Fluently helps you translate your appliation. http://fluent.ly/}
|
9
|
+
gem.email = "bob@zoller.us"
|
10
|
+
gem.homepage = "http://github.com/bzoller/fluently"
|
11
|
+
gem.authors = ["Bob Zoller"]
|
12
|
+
|
13
|
+
gem.add_dependency('json', '>= 1.1.4')
|
14
|
+
gem.add_dependency('ezcrypto', '>= 0.7.2')
|
15
|
+
|
16
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
17
|
+
end
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'spec/rake/spectask'
|
24
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
25
|
+
spec.libs << 'lib' << 'spec'
|
26
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
27
|
+
end
|
28
|
+
|
29
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
30
|
+
spec.libs << 'lib' << 'spec'
|
31
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
32
|
+
spec.rcov = true
|
33
|
+
end
|
34
|
+
|
35
|
+
task :default => :spec
|
36
|
+
rescue LoadError
|
37
|
+
# rspec not available
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'rake/rdoctask'
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
if File.exist?('VERSION.yml')
|
43
|
+
config = YAML.load(File.read('VERSION.yml'))
|
44
|
+
version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
|
45
|
+
else
|
46
|
+
version = ""
|
47
|
+
end
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "fluently #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
54
|
+
|
data/VERSION.yml
ADDED
data/fluently.gemspec
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{fluently}
|
5
|
+
s.version = "0.3.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Bob Zoller"]
|
9
|
+
s.date = %q{2009-06-07}
|
10
|
+
s.email = %q{bob@zoller.us}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"LICENSE",
|
13
|
+
"README.rdoc"
|
14
|
+
]
|
15
|
+
s.files = [
|
16
|
+
".document",
|
17
|
+
".gitignore",
|
18
|
+
"LICENSE",
|
19
|
+
"README.rdoc",
|
20
|
+
"Rakefile",
|
21
|
+
"VERSION.yml",
|
22
|
+
"fluently.gemspec",
|
23
|
+
"lib/fluently.rb",
|
24
|
+
"lib/fluently/cocoa_marshal.rb",
|
25
|
+
"lib/fluently/connection.rb",
|
26
|
+
"lib/fluently/i18n.rb",
|
27
|
+
"lib/fluently/i18n/backend.rb",
|
28
|
+
"lib/fluently/rails_marshal.rb",
|
29
|
+
"lib/fluently/rake/base/task.rb",
|
30
|
+
"lib/fluently/rake/cocoa/task.rb",
|
31
|
+
"lib/fluently/rake/rails/task.rb",
|
32
|
+
"lib/fluently/reader.rb",
|
33
|
+
"spec/fluently_spec.rb",
|
34
|
+
"spec/spec_helper.rb"
|
35
|
+
]
|
36
|
+
s.has_rdoc = true
|
37
|
+
s.homepage = %q{http://github.com/bzoller/fluently}
|
38
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.3.2}
|
41
|
+
s.summary = %q{Fluently helps you translate your appliation. http://fluent.ly/}
|
42
|
+
s.test_files = [
|
43
|
+
"spec/fluently_spec.rb",
|
44
|
+
"spec/spec_helper.rb"
|
45
|
+
]
|
46
|
+
|
47
|
+
if s.respond_to? :specification_version then
|
48
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_runtime_dependency(%q<json>, [">= 1.1.4"])
|
53
|
+
s.add_runtime_dependency(%q<ezcrypto>, [">= 0.7.2"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<json>, [">= 1.1.4"])
|
56
|
+
s.add_dependency(%q<ezcrypto>, [">= 0.7.2"])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<json>, [">= 1.1.4"])
|
60
|
+
s.add_dependency(%q<ezcrypto>, [">= 0.7.2"])
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'iconv'
|
2
|
+
# cocoa string files are assumed to be UTF-16. the cmess gem
|
3
|
+
# can guess at encoding, which might be helpful.
|
4
|
+
#require 'cmess/guess_encoding'
|
5
|
+
#charset = CMess::GuessEncoding::Automatic.guess(input)
|
6
|
+
|
7
|
+
module Fluently
|
8
|
+
class CocoaMarshal
|
9
|
+
def self.read(filename)
|
10
|
+
hash = {}
|
11
|
+
File.open(filename, 'r') do |f|
|
12
|
+
content = Iconv.conv('UTF-8', 'UTF-16', f.read)
|
13
|
+
content.gsub!(%r{//.*$}, '') # ignore comments
|
14
|
+
content.scan(/"(.*?)"\s*=\s*"(.*?)"\s*;/) do |key, value|
|
15
|
+
key = self.read_string(key)
|
16
|
+
value = self.read_string(value)
|
17
|
+
hash[key] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
hash
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.write(filename, hash)
|
24
|
+
File.open(filename, 'w') do |f|
|
25
|
+
f.puts("/* DO NOT EDIT THIS FILE - YOUR CHANGES WILL BE LOST */\n")
|
26
|
+
lines = hash.map do |key, value|
|
27
|
+
%{"#{self.write_string(key)}" = "#{self.write_string(value)}";}
|
28
|
+
end
|
29
|
+
f.write Iconv.conv('UTF-16', 'UTF-8', lines.join("\n"))
|
30
|
+
end
|
31
|
+
File.chmod(0444, filename)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
# unquote quoted quotes
|
37
|
+
def self.read_string(str)
|
38
|
+
return str.to_s.gsub(/([^\\])[\\]"/, '\\1"')
|
39
|
+
end
|
40
|
+
|
41
|
+
# quote quotes
|
42
|
+
def self.write_string(str)
|
43
|
+
return str.to_s.gsub(/"/, '\\"')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'uri'
|
3
|
+
require 'net/https'
|
4
|
+
require 'net/http'
|
5
|
+
require 'benchmark'
|
6
|
+
|
7
|
+
# other gems
|
8
|
+
require 'json'
|
9
|
+
require 'ezcrypto'
|
10
|
+
|
11
|
+
module Fluently
|
12
|
+
class Connection
|
13
|
+
|
14
|
+
# Base URL of the API
|
15
|
+
attr_accessor :site
|
16
|
+
|
17
|
+
# Fluently API key
|
18
|
+
attr_accessor :api_key
|
19
|
+
|
20
|
+
# Fluently API secret
|
21
|
+
attr_accessor :api_secret
|
22
|
+
|
23
|
+
# HTTP Timeout
|
24
|
+
attr_accessor :timeout
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_accessor :site
|
28
|
+
|
29
|
+
def site=(new_site)
|
30
|
+
@site = URI.parse(new_site)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
self.site = "http://fluent.ly/api/v1"
|
35
|
+
|
36
|
+
def initialize(api_key, api_secret)
|
37
|
+
self.api_key = api_key
|
38
|
+
self.api_secret = api_secret
|
39
|
+
self.site = self.class.site
|
40
|
+
end
|
41
|
+
|
42
|
+
def locale
|
43
|
+
@locale ||= self.get("#{self.prefix}locale.json")['response']['message']
|
44
|
+
end
|
45
|
+
|
46
|
+
def sync!(translations)
|
47
|
+
self.post("#{self.prefix}sync.json?#{self.options_query}", JSON.generate({:translations => translations[self.locale]}))['response']['translations']
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def http
|
53
|
+
http = Net::HTTP.new(@site.host, @site.port)
|
54
|
+
http.use_ssl = @site.is_a?(URI::HTTPS)
|
55
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
|
56
|
+
http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used.
|
57
|
+
http
|
58
|
+
end
|
59
|
+
|
60
|
+
def get(path, headers = {})
|
61
|
+
JSON.parse(request(:get, path, self.build_request_headers(headers)).body)
|
62
|
+
end
|
63
|
+
|
64
|
+
def post(path, body = '', headers = {})
|
65
|
+
result = request(:post, path, body.to_s, self.build_request_headers(headers))
|
66
|
+
data = JSON.parse(result.body)
|
67
|
+
if result.code.to_i == 200
|
68
|
+
data
|
69
|
+
else
|
70
|
+
raise "ERROR: #{data['response']['message']}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def request(method, path, *arguments)
|
75
|
+
path = self.sign_url(path)
|
76
|
+
|
77
|
+
# do we need to encrypt the body?
|
78
|
+
if [:put, :post].include? method
|
79
|
+
body = arguments.shift
|
80
|
+
key = EzCrypto::Key.with_password(self.api_key, self.api_secret)
|
81
|
+
body = key.encrypt64(body)
|
82
|
+
arguments.unshift(body)
|
83
|
+
end
|
84
|
+
|
85
|
+
logger.info "#{method.to_s.upcase} #{@site.scheme}://#{@site.host}:#{@site.port}#{path}" if logger
|
86
|
+
result = nil
|
87
|
+
time = Benchmark.realtime { result = http.send(method, path, *arguments) }
|
88
|
+
logger.info "--> #{result.code} #{result.message} (#{result.body.length}b %.2fs)" % time if logger
|
89
|
+
handle_response(result)
|
90
|
+
end
|
91
|
+
|
92
|
+
def handle_response(result)
|
93
|
+
case result.code.to_i
|
94
|
+
when 401
|
95
|
+
raise 'ERROR: invalid authentication. check api_key and api_secret'
|
96
|
+
else
|
97
|
+
begin
|
98
|
+
key = EzCrypto::Key.with_password(self.api_key, self.api_secret)
|
99
|
+
result.send(:instance_variable_set, '@body', key.decrypt64(result.body))
|
100
|
+
rescue OpenSSL::CipherError
|
101
|
+
raise "ERROR: response was not encrypted:\n\n#{result.body}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
result
|
105
|
+
end
|
106
|
+
|
107
|
+
def build_request_headers(headers={})
|
108
|
+
headers.update('Content-Type' => 'application/json', 'Accept' => 'application/json', 'X-Signed-Resource' => '1.0')
|
109
|
+
end
|
110
|
+
|
111
|
+
def prefix
|
112
|
+
"#{@site.path}/"
|
113
|
+
end
|
114
|
+
|
115
|
+
def options_query
|
116
|
+
hash = {}
|
117
|
+
hash[:force] = 1 if ENV['FORCE']
|
118
|
+
hash_to_query(:options => hash)
|
119
|
+
end
|
120
|
+
|
121
|
+
def hash_to_query(hash, namespace = nil)
|
122
|
+
hash.collect do |key, value|
|
123
|
+
ns_key = namespace ? "#{namespace}[#{key}]" : key
|
124
|
+
if value.kind_of?(Hash)
|
125
|
+
hash_to_query(value, ns_key)
|
126
|
+
else
|
127
|
+
"#{CGI.escape(ns_key.to_s)}=#{CGI.escape(value.to_s)}"
|
128
|
+
end
|
129
|
+
end.sort * '&'
|
130
|
+
end
|
131
|
+
|
132
|
+
def sign_url(path)
|
133
|
+
uri = URI.parse(path)
|
134
|
+
uri.query = (uri.query.nil? ? '' : "#{uri.query}&") << "ts=#{Time.now.to_i}&rnd=#{self.random_string(16)}&key=#{self.api_key}"
|
135
|
+
hash = Digest::SHA1.hexdigest("#{uri.query}#{api_secret}")
|
136
|
+
uri.query << "&sig=#{hash}"
|
137
|
+
uri.to_s
|
138
|
+
end
|
139
|
+
|
140
|
+
def random_string(length = 8)
|
141
|
+
chars = ("a".."z").to_a + ("0".."9").to_a + ("A".."Z").to_a
|
142
|
+
Array.new(length, '').collect{chars[rand(chars.size)]}.join
|
143
|
+
end
|
144
|
+
|
145
|
+
def logger
|
146
|
+
nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'fluently/rails_marshal'
|
2
|
+
|
3
|
+
module Fluently
|
4
|
+
module I18n
|
5
|
+
|
6
|
+
class InheritableTranslations
|
7
|
+
MAX_LOCALES_SAFEGUARD = 1000
|
8
|
+
|
9
|
+
def initialize(hash)
|
10
|
+
@base = hash
|
11
|
+
@expanded = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](locale)
|
15
|
+
@expanded[locale] ||= build_expanded_locale(locale)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
def build_expanded_locale(locale)
|
20
|
+
raise 'hit MAX_LOCALES_SAFEGUARD' if @expanded.size == MAX_LOCALES_SAFEGUARD
|
21
|
+
expanded = {}
|
22
|
+
self.locale_lookup_chain(locale).reverse.each do |locale|
|
23
|
+
expanded = deep_hash_merge(expanded, (@base[locale] || {}))
|
24
|
+
end
|
25
|
+
expanded
|
26
|
+
end
|
27
|
+
|
28
|
+
# From: http://www.ruby-forum.com/topic/142809
|
29
|
+
# Author: Stefan Rusterholz
|
30
|
+
def deep_hash_merge(first, second)
|
31
|
+
merger = proc { |key,v1,v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
|
32
|
+
first.merge(second, &merger)
|
33
|
+
end
|
34
|
+
|
35
|
+
def root_locale(locale)
|
36
|
+
locale.to_s.split(/[_-]/)[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
def locale_lookup_chain(locale)
|
40
|
+
@i18n_fallback_locales ||= {}
|
41
|
+
unless @i18n_fallback_locales[locale.to_sym]
|
42
|
+
base_locale = (root_locale(locale) || locale).to_sym
|
43
|
+
locales = [locale.to_sym, base_locale]
|
44
|
+
@base.keys.each do |l|
|
45
|
+
current_base_locale = root_locale(l)
|
46
|
+
locales << l if current_base_locale && current_base_locale.to_sym == base_locale
|
47
|
+
end
|
48
|
+
@i18n_fallback_locales[locale.to_sym] = locales.uniq
|
49
|
+
end
|
50
|
+
@i18n_fallback_locales[locale.to_sym]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Backend < ::I18n::Backend::Simple
|
55
|
+
|
56
|
+
# the path to locale directories
|
57
|
+
attr_accessor :path
|
58
|
+
|
59
|
+
# the format of locale directory names
|
60
|
+
attr_accessor :dirname_format
|
61
|
+
|
62
|
+
# the format of string file names
|
63
|
+
attr_accessor :filename_format
|
64
|
+
|
65
|
+
# the marshal that will read and write string files
|
66
|
+
attr_accessor :marshal
|
67
|
+
|
68
|
+
def initialize(*args)
|
69
|
+
super
|
70
|
+
yield self if block_given?
|
71
|
+
self.path ||= File.join(RAILS_ROOT, 'config', 'locales')
|
72
|
+
self.dirname_format ||= '*'
|
73
|
+
self.filename_format ||= '*.yml'
|
74
|
+
self.marshal = Fluently::RailsMarshal
|
75
|
+
end
|
76
|
+
|
77
|
+
def load_translations(*args)
|
78
|
+
super
|
79
|
+
self.reader.translations.each do |locale, translations|
|
80
|
+
self.store_translations(locale, translations)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def reload!
|
85
|
+
super
|
86
|
+
@interhitable_translations = nil
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
protected
|
91
|
+
def inheritable_translations
|
92
|
+
@inheritable_translations ||= InheritableTranslations.new(self.translations)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Looks up a translation from the translations hash. Returns nil if
|
96
|
+
# eiher key is nil, or locale, scope or key do not exist as a key in the
|
97
|
+
# nested translations hash. Splits keys or scopes containing dots
|
98
|
+
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
|
99
|
+
# <tt>%w(currency format)</tt>.
|
100
|
+
def lookup(locale, key, scope = [])
|
101
|
+
return unless key
|
102
|
+
init_translations unless initialized?
|
103
|
+
keys = ::I18n.send(:normalize_translation_keys, locale, key, scope)
|
104
|
+
keys.inject(inheritable_translations) do |result, k|
|
105
|
+
if (x = result[k.to_sym]).nil?
|
106
|
+
return nil
|
107
|
+
else
|
108
|
+
x
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def reader
|
114
|
+
Fluently::Reader.new do |reader|
|
115
|
+
reader.path = self.path
|
116
|
+
reader.dirname_format = self.dirname_format
|
117
|
+
reader.filename_format = self.filename_format
|
118
|
+
reader.marshal = self.marshal
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Fluently
|
4
|
+
class RailsMarshal
|
5
|
+
def self.read(filename)
|
6
|
+
YAML.load_file(filename)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.write(filename, hash)
|
10
|
+
File.open(filename, 'w') do |f|
|
11
|
+
f.puts("# DO NOT EDIT THIS FILE - YOUR CHANGES WILL BE LOST #\n")
|
12
|
+
YAML.dump(hash, f)
|
13
|
+
end
|
14
|
+
File.chmod(0444, filename)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Fluently
|
2
|
+
module Rake
|
3
|
+
class BaseTask
|
4
|
+
|
5
|
+
# Fluently API key
|
6
|
+
attr_accessor :api_key
|
7
|
+
# Fluently API secret
|
8
|
+
attr_accessor :api_secret
|
9
|
+
|
10
|
+
# Define a Rake
|
11
|
+
def initialize(namespace = "fluently")
|
12
|
+
@namespace = namespace
|
13
|
+
yield self if block_given?
|
14
|
+
define_task
|
15
|
+
end
|
16
|
+
|
17
|
+
def define_task # :nodoc:
|
18
|
+
namespace @namespace do
|
19
|
+
desc "Perform a sync with Fluently"
|
20
|
+
task :sync do
|
21
|
+
reader = self.build_reader
|
22
|
+
connection = Fluently::Connection.new(self.api_key, self.api_secret)
|
23
|
+
new_translations = connection.sync!(reader.translations)
|
24
|
+
if !new_translations || new_translations.empty?
|
25
|
+
puts "no translations yet."
|
26
|
+
else
|
27
|
+
reader.translations = new_translations
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
task @namespace => ["#{@namespace}:sync"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def build_reader
|
36
|
+
Reader.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'fluently/rake/base/task'
|
2
|
+
require 'fluently/cocoa_marshal'
|
3
|
+
|
4
|
+
module Fluently
|
5
|
+
module Rake
|
6
|
+
# Defines a Rake task for syncing string files.
|
7
|
+
#
|
8
|
+
# The simplest use of it goes something like:
|
9
|
+
#
|
10
|
+
# Fluently::Rake::Task.new do |t|
|
11
|
+
# t.api_key = "abc123"
|
12
|
+
# t.api_secret = "xyz456"
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# This will create a task named 'fluently' described as 'Perform a sync with
|
16
|
+
# Fluently'. It assumes string files all end in ".strings" and live underneath
|
17
|
+
# directories called "<lang>.lproj" , ex: "en.lproj"
|
18
|
+
#
|
19
|
+
# See the attributes for additional configuration possibilities.
|
20
|
+
class Task < BaseTask
|
21
|
+
def build_reader
|
22
|
+
Reader.new do |r|
|
23
|
+
r.path = '.'
|
24
|
+
r.dirname_format = '*.lproj'
|
25
|
+
r.filename_format = '*.strings'
|
26
|
+
r.marshal = CocoaMarshal
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'fluently/rake/base/task'
|
2
|
+
require 'fluently/rails_marshal'
|
3
|
+
|
4
|
+
module Fluently
|
5
|
+
module Rake
|
6
|
+
# Defines a Rake task for syncing string files.
|
7
|
+
#
|
8
|
+
# The simplest use of it goes something like:
|
9
|
+
#
|
10
|
+
# Fluently::Rake::Task.new do |t|
|
11
|
+
# t.api_key = "abc123"
|
12
|
+
# t.api_secret = "xyz456"
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# This will create a task named 'fluently' described as 'Perform a sync with
|
16
|
+
# Fluently'. It assumes string files all end in ".yml" and live underneath
|
17
|
+
# directories called "<locale>" , ex: "en-US"
|
18
|
+
#
|
19
|
+
# See the attributes for additional configuration possibilities.
|
20
|
+
class Task < BaseTask
|
21
|
+
def build_reader
|
22
|
+
Reader.new do |r|
|
23
|
+
r.path = File.join(RAILS_ROOT, 'config', 'locales')
|
24
|
+
r.dirname_format = '*'
|
25
|
+
r.filename_format = '*.yml'
|
26
|
+
r.marshal = RailsMarshal
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Fluently
|
4
|
+
class Reader
|
5
|
+
|
6
|
+
# the path to locale directories
|
7
|
+
attr_accessor :path
|
8
|
+
|
9
|
+
# the format of locale directory names
|
10
|
+
attr_accessor :dirname_format
|
11
|
+
|
12
|
+
# the format of string file names
|
13
|
+
attr_accessor :filename_format
|
14
|
+
|
15
|
+
# the marshal that will read and write string files
|
16
|
+
attr_accessor :marshal
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
yield self if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def translations
|
23
|
+
@translations ||= read_translations
|
24
|
+
end
|
25
|
+
|
26
|
+
def translations=(new_translations)
|
27
|
+
@translations = new_translations
|
28
|
+
write_translations
|
29
|
+
end
|
30
|
+
|
31
|
+
%w{path dirname_format filename_format marshal}.each do |method|
|
32
|
+
class_eval <<-EOF
|
33
|
+
def #{method}=(new_value)
|
34
|
+
@translations = nil # reset memo
|
35
|
+
@#{method} = new_value
|
36
|
+
end
|
37
|
+
EOF
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def read_translations
|
44
|
+
data = {}
|
45
|
+
self.read_locale_files do |locale, section, translations|
|
46
|
+
data[locale] ||= {}
|
47
|
+
raise "duplicate section #{section} for locale #{locale}" if data[locale][section]
|
48
|
+
data[locale][section] = translations
|
49
|
+
end
|
50
|
+
data
|
51
|
+
end
|
52
|
+
|
53
|
+
def write_translations
|
54
|
+
self.translations.each do |locale, sections|
|
55
|
+
locale_dir = File.join(self.path, self.dirname_format.sub('*', locale))
|
56
|
+
FileUtils.mkdir_p(locale_dir) unless File.exist?(locale_dir)
|
57
|
+
puts "#{File.basename(locale_dir)}#{File::SEPARATOR}"
|
58
|
+
sections.each do |section, translations|
|
59
|
+
section_file = File.join(locale_dir, self.filename_format.sub('*', section))
|
60
|
+
self.marshal.write(section_file, translations)
|
61
|
+
puts " #{File.basename(section_file)}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def good_config?
|
67
|
+
!!(self.path && self.dirname_format && self.filename_format && self.marshal)
|
68
|
+
end
|
69
|
+
|
70
|
+
def read_locale_files
|
71
|
+
raise 'bad config' unless self.good_config?
|
72
|
+
Dir.glob(File.join(self.path, self.dirname_format)).each do |locale_path|
|
73
|
+
next unless File.directory?(locale_path)
|
74
|
+
locale = File.basename(locale_path).match(/^#{self.dirname_format.sub('*', '(.*)')}$/)[1]
|
75
|
+
Dir.glob(File.join(locale_path, self.filename_format)).each do |locale_file|
|
76
|
+
next unless File.file?(locale_file)
|
77
|
+
section = File.basename(locale_file).match(/^#{self.filename_format.sub('*', '(.*)')}$/)[1]
|
78
|
+
data = self.marshal.read(locale_file)
|
79
|
+
yield [locale, section, data]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
data/lib/fluently.rb
ADDED
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: 16watts-fluently
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bob Zoller
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-07 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.1.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ezcrypto
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.7.2
|
34
|
+
version:
|
35
|
+
description:
|
36
|
+
email: bob@zoller.us
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- VERSION.yml
|
51
|
+
- fluently.gemspec
|
52
|
+
- lib/fluently.rb
|
53
|
+
- lib/fluently/cocoa_marshal.rb
|
54
|
+
- lib/fluently/connection.rb
|
55
|
+
- lib/fluently/i18n.rb
|
56
|
+
- lib/fluently/i18n/backend.rb
|
57
|
+
- lib/fluently/rails_marshal.rb
|
58
|
+
- lib/fluently/rake/base/task.rb
|
59
|
+
- lib/fluently/rake/cocoa/task.rb
|
60
|
+
- lib/fluently/rake/rails/task.rb
|
61
|
+
- lib/fluently/reader.rb
|
62
|
+
- spec/fluently_spec.rb
|
63
|
+
- spec/spec_helper.rb
|
64
|
+
has_rdoc: true
|
65
|
+
homepage: http://github.com/bzoller/fluently
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options:
|
68
|
+
- --charset=UTF-8
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "0"
|
82
|
+
version:
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.2.0
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: Fluently helps you translate your appliation. http://fluent.ly/
|
90
|
+
test_files:
|
91
|
+
- spec/fluently_spec.rb
|
92
|
+
- spec/spec_helper.rb
|