localeapp 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +4 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +2 -0
- data/README.textile +147 -0
- data/Rakefile +11 -0
- data/bin/localeapp +61 -0
- data/cucumber.yml +8 -0
- data/features/localeapp_binary.feature +116 -0
- data/features/step_definitions/cli_steps.rb +27 -0
- data/features/support/env.rb +21 -0
- data/features/support/hooks.rb +3 -0
- data/init.rb +1 -0
- data/lib/locale_app/api_call.rb +9 -0
- data/lib/locale_app/api_caller.rb +77 -0
- data/lib/locale_app/cli/install.rb +41 -0
- data/lib/locale_app/cli/pull.rb +34 -0
- data/lib/locale_app/cli/push.rb +49 -0
- data/lib/locale_app/cli/update.rb +19 -0
- data/lib/locale_app/configuration.rb +93 -0
- data/lib/locale_app/exception_handler.rb +21 -0
- data/lib/locale_app/key_checker.rb +43 -0
- data/lib/locale_app/missing_translations.rb +36 -0
- data/lib/locale_app/poller.rb +61 -0
- data/lib/locale_app/rails/2_3_translation_helper_monkeypatch.rb +36 -0
- data/lib/locale_app/rails/controller.rb +34 -0
- data/lib/locale_app/rails/flatten.rb +113 -0
- data/lib/locale_app/rails.rb +53 -0
- data/lib/locale_app/routes.rb +80 -0
- data/lib/locale_app/sender.rb +49 -0
- data/lib/locale_app/tasks/locale_app.rake +20 -0
- data/lib/locale_app/updater.rb +63 -0
- data/lib/locale_app/version.rb +3 -0
- data/lib/locale_app.rb +98 -0
- data/lib/localeapp.rb +1 -0
- data/localeapp.gemspec +35 -0
- data/run_ci +5 -0
- data/spec/fixtures/en.yml +6 -0
- data/spec/fixtures/es.yml +6 -0
- data/spec/locale_app/api_call_spec.rb +15 -0
- data/spec/locale_app/api_caller_spec.rb +157 -0
- data/spec/locale_app/cli/install_spec.rb +42 -0
- data/spec/locale_app/cli/pull_spec.rb +45 -0
- data/spec/locale_app/cli/push_spec.rb +30 -0
- data/spec/locale_app/cli/update_spec.rb +18 -0
- data/spec/locale_app/configuration_spec.rb +119 -0
- data/spec/locale_app/exception_handler_spec.rb +21 -0
- data/spec/locale_app/key_checker_spec.rb +19 -0
- data/spec/locale_app/missing_translations_spec.rb +28 -0
- data/spec/locale_app/poller_spec.rb +61 -0
- data/spec/locale_app/rails/controller_spec.rb +117 -0
- data/spec/locale_app/routes_spec.rb +134 -0
- data/spec/locale_app/sender_spec.rb +49 -0
- data/spec/locale_app/updater_spec.rb +89 -0
- data/spec/spec_helper.rb +24 -0
- data/spec/support/locale_app_integration_data.rb +33 -0
- data/spec/support/locale_app_synchronization_data.rb +21 -0
- metadata +300 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
module LocaleApp
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
# The API key for your project, found on the project edit form
|
5
|
+
attr_accessor :api_key
|
6
|
+
|
7
|
+
# The host to connect to (defaults to api.localeapp.com)
|
8
|
+
attr_accessor :host
|
9
|
+
|
10
|
+
# The port to connect to (defaults to 80)
|
11
|
+
attr_accessor :port
|
12
|
+
|
13
|
+
attr_accessor :http_auth_username
|
14
|
+
attr_accessor :http_auth_password
|
15
|
+
|
16
|
+
# The name of the environment the application is running in
|
17
|
+
attr_accessor :environment_name
|
18
|
+
|
19
|
+
# The path to the project in which the translation occurred, such as the
|
20
|
+
# RAILS_ROOT
|
21
|
+
attr_accessor :project_root
|
22
|
+
|
23
|
+
# The names of environments where notifications aren't sent (defaults to
|
24
|
+
# 'test', 'cucumber', 'production')
|
25
|
+
attr_accessor :disabled_sending_environments
|
26
|
+
|
27
|
+
# The names of environments where I18n.reload isn't called for each request
|
28
|
+
# (defaults to 'test', 'cucumber', 'production')
|
29
|
+
attr_accessor :disabled_reloading_environments
|
30
|
+
|
31
|
+
# The names of environments where updates aren't pulled (defaults to
|
32
|
+
# 'test', 'cucumber', 'production')
|
33
|
+
attr_accessor :disabled_polling_environments
|
34
|
+
|
35
|
+
# The logger used by LocaleApp
|
36
|
+
attr_accessor :logger
|
37
|
+
|
38
|
+
# The number of seconds to wait before asking the service for new
|
39
|
+
# translations (defaults to 0 - every request).
|
40
|
+
attr_accessor :poll_interval
|
41
|
+
|
42
|
+
# The complete path to the data file where we store synchronization
|
43
|
+
# information (defaults to ./locale_app.yml) local_app/rails overwrites
|
44
|
+
# this to RAILS_ROOT/log/locale_app.yml
|
45
|
+
attr_accessor :synchronization_data_file
|
46
|
+
|
47
|
+
# The complete path to the directory where translations are stored
|
48
|
+
attr_accessor :translation_data_directory
|
49
|
+
|
50
|
+
def initialize
|
51
|
+
@host = 'api.localeapp.com'
|
52
|
+
@port = 80
|
53
|
+
@disabled_sending_environments = %w(test cucumber production)
|
54
|
+
@disabled_reloading_environments = %w(test cucumber production)
|
55
|
+
@disabled_polling_environments = %w(test cucumber production)
|
56
|
+
@poll_interval = 0
|
57
|
+
@synchronization_data_file = File.join('log', 'locale_app.yml')
|
58
|
+
@translation_data_directory = File.join('config', 'locales')
|
59
|
+
if ENV['DEBUG']
|
60
|
+
require 'logger'
|
61
|
+
@logger = Logger.new(STDOUT)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def polling_disabled?
|
66
|
+
disabled_polling_environments.include?(environment_name)
|
67
|
+
end
|
68
|
+
|
69
|
+
def reloading_disabled?
|
70
|
+
disabled_reloading_environments.include?(environment_name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def sending_disabled?
|
74
|
+
disabled_sending_environments.include?(environment_name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def write_initial(path)
|
78
|
+
dir = File.dirname(path)
|
79
|
+
FileUtils.mkdir_p(dir)
|
80
|
+
File.open(path, 'w+') do |file|
|
81
|
+
file.write <<-CONTENT
|
82
|
+
require 'locale_app/rails'
|
83
|
+
|
84
|
+
LocaleApp.configure do |config|
|
85
|
+
config.api_key = '#{@api_key}'
|
86
|
+
config.host = '#{@host}'
|
87
|
+
config.port = #{@port}
|
88
|
+
end
|
89
|
+
CONTENT
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module LocaleApp
|
2
|
+
class ExceptionHandler
|
3
|
+
def self.call(exception, locale, key, options)
|
4
|
+
LocaleApp.log(exception.message)
|
5
|
+
if I18n::MissingTranslationData === exception
|
6
|
+
LocaleApp.log("Detected missing translation for key(s) #{key.inspect}")
|
7
|
+
|
8
|
+
[*key].each do |key|
|
9
|
+
LocaleApp.missing_translations.add(locale, key, options)
|
10
|
+
end
|
11
|
+
|
12
|
+
[locale, key].join(', ')
|
13
|
+
else
|
14
|
+
LocaleApp.log('Raising exception')
|
15
|
+
raise
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
I18n.exception_handler = LocaleApp::ExceptionHandler
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module LocaleApp
|
6
|
+
class KeyChecker
|
7
|
+
include ::LocaleApp::ApiCall
|
8
|
+
|
9
|
+
def check(key)
|
10
|
+
if LocaleApp.configuration.nil? # no config file yet
|
11
|
+
LocaleApp.configuration = LocaleApp::Configuration.new
|
12
|
+
LocaleApp.configuration.host = ENV['LA_TEST_HOST'] if ENV['LA_TEST_HOST']
|
13
|
+
end
|
14
|
+
LocaleApp.configuration.api_key = key
|
15
|
+
api_call :project,
|
16
|
+
:success => :handle_success,
|
17
|
+
:failure => :handle_failure,
|
18
|
+
:max_connection_attempts => 1
|
19
|
+
|
20
|
+
if @checked
|
21
|
+
[@ok, @data]
|
22
|
+
else
|
23
|
+
[false, "Error communicating with server"]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_success(response)
|
28
|
+
@checked = true
|
29
|
+
@ok = true
|
30
|
+
@data = JSON.parse(response)
|
31
|
+
end
|
32
|
+
|
33
|
+
def handle_failure(response)
|
34
|
+
if response.code.to_i == 404
|
35
|
+
@checked = true
|
36
|
+
@ok = false
|
37
|
+
@data = {}
|
38
|
+
else
|
39
|
+
@checked = false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module LocaleApp
|
2
|
+
MissingTranslationRecord = Struct.new(:key, :locale, :options)
|
3
|
+
|
4
|
+
class MissingTranslations
|
5
|
+
def initialize
|
6
|
+
@translations = Hash.new { |h, k| h[k] = {} }
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(locale, key, options = {})
|
10
|
+
record = MissingTranslationRecord.new(key, locale, options)
|
11
|
+
@translations[locale][key] = record
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](locale)
|
15
|
+
@translations[locale]
|
16
|
+
end
|
17
|
+
|
18
|
+
# This method will get cleverer so we don't resend keys we've
|
19
|
+
# already sent, or send multiple times for the same locale etc.
|
20
|
+
# For now it's pretty dumb
|
21
|
+
def to_send
|
22
|
+
data = []
|
23
|
+
# need the sort to make specs work under 1.8
|
24
|
+
@translations.sort { |a, b| a.to_s <=> b.to_s }.each do |locale, records|
|
25
|
+
records.each do |key, record|
|
26
|
+
missing_data = {}
|
27
|
+
missing_data[:key] = key
|
28
|
+
missing_data[:locale] = locale
|
29
|
+
missing_data[:options] = record.options
|
30
|
+
data << missing_data
|
31
|
+
end
|
32
|
+
end
|
33
|
+
data
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'rest-client'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module LocaleApp
|
6
|
+
class Poller
|
7
|
+
include ::LocaleApp::ApiCall
|
8
|
+
|
9
|
+
# when we last asked the service for updates
|
10
|
+
attr_accessor :polled_at
|
11
|
+
|
12
|
+
# the last time the service had updates for us
|
13
|
+
attr_accessor :updated_at
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@polled_at = synchronization_data[:polled_at] || 0
|
17
|
+
@updated_at = synchronization_data[:updated_at] || 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def synchronization_data
|
21
|
+
if File.exists?(LocaleApp.configuration.synchronization_data_file)
|
22
|
+
YAML.load_file(LocaleApp.configuration.synchronization_data_file)
|
23
|
+
else
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def write_synchronization_data!(polled_at, updated_at)
|
29
|
+
File.open(LocaleApp.configuration.synchronization_data_file, 'w+') do |f|
|
30
|
+
f.write({:polled_at => polled_at, :updated_at => updated_at}.to_yaml)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def needs_polling?
|
35
|
+
synchronization_data[:polled_at] < (Time.now.to_i - LocaleApp.configuration.poll_interval)
|
36
|
+
end
|
37
|
+
|
38
|
+
def needs_reloading?
|
39
|
+
synchronization_data[:updated_at] != @updated_at
|
40
|
+
end
|
41
|
+
|
42
|
+
def poll!
|
43
|
+
api_call :translations,
|
44
|
+
:url_options => { :query => { :updated_at => updated_at }},
|
45
|
+
:success => :handle_success,
|
46
|
+
:failure => :handle_failure,
|
47
|
+
:max_connection_attempts => 1
|
48
|
+
@success
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle_success(response)
|
52
|
+
@success = true
|
53
|
+
LocaleApp.updater.update(JSON.parse(response))
|
54
|
+
write_synchronization_data!(Time.now.to_i, Time.parse(response.headers[:date]).to_i)
|
55
|
+
end
|
56
|
+
|
57
|
+
def handle_failure(response)
|
58
|
+
@success = false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# We're replacing the original method with one that doesn't set the :raise option.
|
2
|
+
# This means the exception handler will be called and missing translations get sent to
|
3
|
+
# localeapp. It's ugly but there's no other way to do it :(
|
4
|
+
|
5
|
+
module LocaleApp::TranslationHelperMonkeyPatch
|
6
|
+
# Delegates to I18n#translate but also performs two additional functions. First, it'll catch MissingTranslationData exceptions
|
7
|
+
# and turn them into inline spans that contains the missing key, such that you can see in a view what is missing where.
|
8
|
+
#
|
9
|
+
# Second, it'll scope the key by the current partial if the key starts with a period. So if you call translate(".foo") from the
|
10
|
+
# people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo"). This makes it less repetitive
|
11
|
+
# to translate many keys within the same partials and gives you a simple framework for scoping them consistently. If you don't
|
12
|
+
# prepend the key with a period, nothing is converted.
|
13
|
+
def translate(keys, options = {})
|
14
|
+
if multiple_keys = keys.is_a?(Array)
|
15
|
+
ActiveSupport::Deprecation.warn "Giving an array to translate is deprecated, please give a symbol or a string instead", caller
|
16
|
+
end
|
17
|
+
|
18
|
+
keys = scope_keys_by_partial(keys)
|
19
|
+
|
20
|
+
translations = I18n.translate(keys, options)
|
21
|
+
translations = [translations] if !multiple_keys && translations.size > 1
|
22
|
+
translations = html_safe_translation_keys(keys, translations)
|
23
|
+
|
24
|
+
if multiple_keys || translations.size > 1
|
25
|
+
translations
|
26
|
+
else
|
27
|
+
translations.first
|
28
|
+
end
|
29
|
+
rescue I18n::MissingTranslationData => e
|
30
|
+
keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope])
|
31
|
+
content_tag('span', keys.join(', '), :class => 'translation_missing')
|
32
|
+
end
|
33
|
+
alias :t :translate
|
34
|
+
end
|
35
|
+
|
36
|
+
ActionView::Base.send(:include, ::LocaleApp::TranslationHelperMonkeyPatch)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module LocaleApp
|
2
|
+
module Rails
|
3
|
+
module Controller
|
4
|
+
def self.included(base)
|
5
|
+
base.before_filter :handle_translation_updates
|
6
|
+
base.after_filter :send_missing_translations
|
7
|
+
end
|
8
|
+
|
9
|
+
def handle_translation_updates
|
10
|
+
unless ::LocaleApp.configuration.polling_disabled?
|
11
|
+
::LocaleApp.log Time.now.to_i.to_s << '-- Handling translation updates'
|
12
|
+
if ::LocaleApp.poller.needs_polling?
|
13
|
+
::LocaleApp.log Time.now.to_i.to_s << ' - polling'
|
14
|
+
::LocaleApp.poller.poll!
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
unless ::LocaleApp.configuration.reloading_disabled?
|
19
|
+
if ::LocaleApp.poller.needs_reloading?
|
20
|
+
::LocaleApp.log Time.now.to_i.to_s << '- reloading I18n'
|
21
|
+
I18n.reload!
|
22
|
+
::LocaleApp.poller.updated_at = ::LocaleApp.poller.synchronization_data[:updated_at]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_missing_translations
|
28
|
+
return if ::LocaleApp.configuration.sending_disabled?
|
29
|
+
|
30
|
+
::LocaleApp.sender.post_missing_translations
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module I18n
|
2
|
+
module Backend
|
3
|
+
# This module contains several helpers to assist flattening translations.
|
4
|
+
# You may want to flatten translations for:
|
5
|
+
#
|
6
|
+
# 1) speed up lookups, as in the Memoize backend;
|
7
|
+
# 2) In case you want to store translations in a data store, as in ActiveRecord backend;
|
8
|
+
#
|
9
|
+
# You can check both backends above for some examples.
|
10
|
+
# This module also keeps all links in a hash so they can be properly resolved when flattened.
|
11
|
+
module Flatten
|
12
|
+
SEPARATOR_ESCAPE_CHAR = "\001"
|
13
|
+
FLATTEN_SEPARATOR = "."
|
14
|
+
|
15
|
+
# normalize_keys the flatten way. This method is significantly faster
|
16
|
+
# and creates way less objects than the one at I18n.normalize_keys.
|
17
|
+
# It also handles escaping the translation keys.
|
18
|
+
def self.normalize_flat_keys(locale, key, scope, separator)
|
19
|
+
keys = [scope, key].flatten.compact
|
20
|
+
separator ||= I18n.default_separator
|
21
|
+
|
22
|
+
if separator != FLATTEN_SEPARATOR
|
23
|
+
keys.map! do |k|
|
24
|
+
k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}",
|
25
|
+
"#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
keys.join(".")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Receives a string and escape the default separator.
|
33
|
+
def self.escape_default_separator(key) #:nodoc:
|
34
|
+
key.to_s.tr(FLATTEN_SEPARATOR, SEPARATOR_ESCAPE_CHAR)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Shortcut to I18n::Backend::Flatten.normalize_flat_keys
|
38
|
+
# and then resolve_links.
|
39
|
+
def normalize_flat_keys(locale, key, scope, separator)
|
40
|
+
key = I18n::Backend::Flatten.normalize_flat_keys(locale, key, scope, separator)
|
41
|
+
resolve_link(locale, key)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Store flattened links.
|
45
|
+
def links
|
46
|
+
@links ||= Hash.new { |h,k| h[k] = {} }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Flatten keys for nested Hashes by chaining up keys:
|
50
|
+
#
|
51
|
+
# >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind
|
52
|
+
# => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
|
53
|
+
#
|
54
|
+
def flatten_keys(hash, escape, prev_key=nil, &block)
|
55
|
+
hash.each_pair do |key, value|
|
56
|
+
key = escape_default_separator(key) if escape
|
57
|
+
curr_key = [prev_key, key].compact.join(FLATTEN_SEPARATOR).to_sym
|
58
|
+
yield curr_key, value
|
59
|
+
flatten_keys(value, escape, curr_key, &block) if value.is_a?(Hash)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Receives a hash of translations (where the key is a locale and
|
64
|
+
# the value is another hash) and return a hash with all
|
65
|
+
# translations flattened.
|
66
|
+
#
|
67
|
+
# Nested hashes are included in the flattened hash just if subtree
|
68
|
+
# is true and Symbols are automatically stored as links.
|
69
|
+
def flatten_translations(locale, data, escape, subtree)
|
70
|
+
hash = {}
|
71
|
+
flatten_keys(data, escape) do |key, value|
|
72
|
+
if value.is_a?(Hash)
|
73
|
+
hash[key] = value if subtree
|
74
|
+
else
|
75
|
+
store_link(locale, key, value) if value.is_a?(Symbol)
|
76
|
+
hash[key] = value
|
77
|
+
end
|
78
|
+
end
|
79
|
+
hash
|
80
|
+
end
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
def store_link(locale, key, link)
|
85
|
+
links[locale.to_sym][key.to_s] = link.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def resolve_link(locale, key)
|
89
|
+
key, locale = key.to_s, locale.to_sym
|
90
|
+
links = self.links[locale]
|
91
|
+
|
92
|
+
if links.key?(key)
|
93
|
+
links[key]
|
94
|
+
elsif link = find_link(locale, key)
|
95
|
+
store_link(locale, key, key.gsub(*link))
|
96
|
+
else
|
97
|
+
key
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_link(locale, key) #:nodoc:
|
102
|
+
links[locale].each do |from, to|
|
103
|
+
return [from, to] if key[0, from.length] == from
|
104
|
+
end && nil
|
105
|
+
end
|
106
|
+
|
107
|
+
def escape_default_separator(key) #:nodoc:
|
108
|
+
I18n::Backend::Flatten.escape_default_separator(key)
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module LocaleApp
|
2
|
+
module Rails
|
3
|
+
def self.initialize
|
4
|
+
if defined?(::Rails.logger)
|
5
|
+
rails_logger = ::Rails.logger
|
6
|
+
elsif defined?(RAILS_DEFAULT_LOGGER)
|
7
|
+
rails_logger = RAILS_DEFAULT_LOGGER
|
8
|
+
end
|
9
|
+
|
10
|
+
if defined?(::Rails.env)
|
11
|
+
rails_env = ::Rails.env
|
12
|
+
elsif defined?(RAILS_ENV)
|
13
|
+
rails_env = RAILS_ENV
|
14
|
+
end
|
15
|
+
|
16
|
+
if defined?(::Rails.root)
|
17
|
+
rails_root = ::Rails.root
|
18
|
+
elsif defined?(RAILS_ROOT)
|
19
|
+
rails_root = RAILS_ROOT
|
20
|
+
end
|
21
|
+
|
22
|
+
ActionController::Base.send(:include, LocaleApp::Rails::Controller)
|
23
|
+
|
24
|
+
if ::Rails::VERSION::MAJOR == 2 && ::Rails::VERSION::MINOR >= 3 # TODO: Check previous rails versions if required
|
25
|
+
require 'locale_app/rails/2_3_translation_helper_monkeypatch'
|
26
|
+
end
|
27
|
+
|
28
|
+
LocaleApp.configure do |config|
|
29
|
+
config.logger = rails_logger
|
30
|
+
config.environment_name = rails_env
|
31
|
+
config.project_root = rails_root
|
32
|
+
config.synchronization_data_file = File.join([rails_root, 'log', 'locale_app.yml'])
|
33
|
+
config.translation_data_directory = File.join([rails_root, 'config', 'locales'])
|
34
|
+
end
|
35
|
+
initialize_synchronization_data_file
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.initialize_synchronization_data_file
|
39
|
+
if !File.exists?(LocaleApp.configuration.synchronization_data_file)
|
40
|
+
File.open(LocaleApp.configuration.synchronization_data_file, 'w') do |f|
|
41
|
+
f.write({:polled_at => Time.now.to_i, :updated_at => Time.now.to_i}.to_yaml)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
if defined?(Rails)
|
49
|
+
require 'locale_app/rails/controller'
|
50
|
+
require 'locale_app/exception_handler'
|
51
|
+
LocaleApp::Rails.initialize
|
52
|
+
LocaleApp.log('Loaded locale_app/rails')
|
53
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module LocaleApp
|
2
|
+
module Routes
|
3
|
+
VERSION = 'v1'
|
4
|
+
|
5
|
+
def project_endpoint(options = {})
|
6
|
+
[:get, project_url(options)]
|
7
|
+
end
|
8
|
+
|
9
|
+
def project_url(options = {})
|
10
|
+
options[:format] ||= 'json'
|
11
|
+
URI::HTTP.build(base_options.merge(:path => project_path(options[:format]))).to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
def translations_url(options={})
|
15
|
+
options[:format] ||= 'json'
|
16
|
+
url = URI::HTTP.build(base_options.merge(:path => translations_path(options[:format])))
|
17
|
+
url.query = options[:query].map { |k,v| "#{k}=#{v}" }.join('&') if options[:query]
|
18
|
+
url.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def translations_endpoint(options = {})
|
22
|
+
[:get, translations_url(options)]
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_translation_endpoint(options = {})
|
26
|
+
[:post, translations_url(options)]
|
27
|
+
end
|
28
|
+
|
29
|
+
def missing_translations_endpoint(options = {})
|
30
|
+
[:post, missing_translations_url(options)]
|
31
|
+
end
|
32
|
+
|
33
|
+
def missing_translations_url(options={})
|
34
|
+
options[:format] ||= 'json'
|
35
|
+
url = URI::HTTP.build(base_options.merge(:path => missing_translations_path(options[:format])))
|
36
|
+
url.query = options[:query].map { |k,v| "#{k}=#{v}" }.join('&') if options[:query]
|
37
|
+
url.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def import_endpoint(options = {})
|
41
|
+
[:post, import_url(options)]
|
42
|
+
end
|
43
|
+
|
44
|
+
def import_url(options={})
|
45
|
+
URI::HTTP.build(base_options.merge(:path => import_path)).to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def base_options
|
51
|
+
options = {:host => LocaleApp.configuration.host, :port => LocaleApp.configuration.port}
|
52
|
+
if LocaleApp.configuration.http_auth_username
|
53
|
+
options[:userinfo] = "#{LocaleApp.configuration.http_auth_username}:#{LocaleApp.configuration.http_auth_password}"
|
54
|
+
end
|
55
|
+
options
|
56
|
+
end
|
57
|
+
|
58
|
+
def project_path(format = nil)
|
59
|
+
path = "/#{VERSION}/projects/#{LocaleApp.configuration.api_key}"
|
60
|
+
path << ".#{format}" if format
|
61
|
+
path
|
62
|
+
end
|
63
|
+
|
64
|
+
def translations_path(format = nil)
|
65
|
+
path = project_path << '/translations'
|
66
|
+
path << ".#{format}" if format
|
67
|
+
path
|
68
|
+
end
|
69
|
+
|
70
|
+
def missing_translations_path(format = nil)
|
71
|
+
path = project_path << '/translations/missing'
|
72
|
+
path << ".#{format}" if format
|
73
|
+
path
|
74
|
+
end
|
75
|
+
|
76
|
+
def import_path(format = nil)
|
77
|
+
project_path << '/import/'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module LocaleApp
|
5
|
+
class Sender
|
6
|
+
include ::LocaleApp::ApiCall
|
7
|
+
include ::LocaleApp::Routes
|
8
|
+
|
9
|
+
def post_translation(locale, key, options, value = nil)
|
10
|
+
options ||= {}
|
11
|
+
translation = { :key => key, :locale => locale, :substitutions => options.keys, :description => value}
|
12
|
+
@data = { :translation => translation }
|
13
|
+
api_call :create_translation,
|
14
|
+
:payload => @data.to_json,
|
15
|
+
:request_options => { :content_type => :json },
|
16
|
+
:success => :handle_single_translation_success,
|
17
|
+
:failure => :handle_single_translation_failure,
|
18
|
+
:max_connection_attempts => 1
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle_single_translation_success(response)
|
22
|
+
LocaleApp.log([translations_url, response.code, @data.inspect].join(' - '))
|
23
|
+
end
|
24
|
+
|
25
|
+
def handle_single_translation_failure(response)
|
26
|
+
LocaleApp.log([translations_url, response.code, @data.inspect].join(' - '))
|
27
|
+
end
|
28
|
+
|
29
|
+
def post_missing_translations
|
30
|
+
to_send = LocaleApp.missing_translations.to_send
|
31
|
+
return if to_send.empty?
|
32
|
+
@data = { :translations => to_send }
|
33
|
+
api_call :missing_translations,
|
34
|
+
:payload => @data.to_json,
|
35
|
+
:request_options => { :content_type => :json },
|
36
|
+
:success => :handle_missing_translation_success,
|
37
|
+
:failure => :handle_missing_translation_failure,
|
38
|
+
:max_connection_attempts => 1
|
39
|
+
end
|
40
|
+
|
41
|
+
def handle_missing_translation_success(response)
|
42
|
+
LocaleApp.log([translations_url, response.code, @data.inspect].join(' - '))
|
43
|
+
end
|
44
|
+
|
45
|
+
def handle_missing_translation_failure(response)
|
46
|
+
LocaleApp.log([translations_url, response.code, @data.inspect].join(' - '))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
namespace :locale_app do
|
2
|
+
desc 'Imports the en.yml file to the LocaleServer'
|
3
|
+
task :import => :environment do
|
4
|
+
require 'flatten'
|
5
|
+
include I18n::Backend::Flatten
|
6
|
+
yml = YAML.load_file(ENV['LOCALE_FILE'])
|
7
|
+
|
8
|
+
yml.each do |locale, translations|
|
9
|
+
flatten_translations(
|
10
|
+
locale,
|
11
|
+
translations,
|
12
|
+
I18n::Backend::Flatten::SEPARATOR_ESCAPE_CHAR,
|
13
|
+
nil
|
14
|
+
).each do |key, value|
|
15
|
+
puts "#{key} => #{value}"
|
16
|
+
LocaleApp.sender.post_translation(locale, key, {}, value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|