16watts-fluently 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|