localeapp 0.0.7
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/.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
|