translation_engine 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +37 -0
  4. data/app/assets/javascripts/translation_engine/screenshots.js.coffee +225 -0
  5. data/app/assets/stylesheets/translation_engine/screenshots.css +23 -0
  6. data/app/middlewares/translation_engine/catcher_middleware.rb +64 -0
  7. data/app/middlewares/translation_engine/connection_exception_middleware.rb +18 -0
  8. data/app/middlewares/translation_engine/keys_middleware.rb +31 -0
  9. data/app/middlewares/translation_engine/screenshots_middleware.rb +31 -0
  10. data/app/models/translation_engine/backend.rb +79 -0
  11. data/app/models/translation_engine/connection.rb +75 -0
  12. data/app/models/translation_engine/connection_error.rb +2 -0
  13. data/app/models/translation_engine/downloader.rb +72 -0
  14. data/app/models/translation_engine/error.rb +2 -0
  15. data/app/models/translation_engine/release.rb +26 -0
  16. data/app/models/translation_engine/translation.rb +67 -0
  17. data/config/initializers/translation_engine.rb +8 -0
  18. data/lib/tasks/translation_engine_tasks.rake +83 -0
  19. data/lib/translation_engine.rb +10 -0
  20. data/lib/translation_engine/engine.rb +32 -0
  21. data/lib/translation_engine/version.rb +3 -0
  22. data/test/dummy/README.rdoc +28 -0
  23. data/test/dummy/Rakefile +6 -0
  24. data/test/dummy/app/assets/javascripts/application.js +15 -0
  25. data/test/dummy/app/assets/javascripts/root.js +2 -0
  26. data/test/dummy/app/assets/stylesheets/application.css +16 -0
  27. data/test/dummy/app/assets/stylesheets/root.css +4 -0
  28. data/test/dummy/app/controllers/application_controller.rb +5 -0
  29. data/test/dummy/app/controllers/root_controller.rb +4 -0
  30. data/test/dummy/app/helpers/application_helper.rb +2 -0
  31. data/test/dummy/app/helpers/root_helper.rb +2 -0
  32. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  33. data/test/dummy/app/views/root/index.html.erb +8 -0
  34. data/test/dummy/bin/bundle +3 -0
  35. data/test/dummy/bin/rails +4 -0
  36. data/test/dummy/bin/rake +4 -0
  37. data/test/dummy/bin/setup +29 -0
  38. data/test/dummy/config.ru +4 -0
  39. data/test/dummy/config/application.rb +26 -0
  40. data/test/dummy/config/boot.rb +5 -0
  41. data/test/dummy/config/database.yml +25 -0
  42. data/test/dummy/config/environment.rb +5 -0
  43. data/test/dummy/config/environments/development.rb +41 -0
  44. data/test/dummy/config/environments/production.rb +79 -0
  45. data/test/dummy/config/environments/test.rb +42 -0
  46. data/test/dummy/config/initializers/assets.rb +11 -0
  47. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  48. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  49. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  50. data/test/dummy/config/initializers/inflections.rb +16 -0
  51. data/test/dummy/config/initializers/mime_types.rb +4 -0
  52. data/test/dummy/config/initializers/session_store.rb +3 -0
  53. data/test/dummy/config/initializers/translation_engine.rb +7 -0
  54. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  55. data/test/dummy/config/locales/en.yml +23 -0
  56. data/test/dummy/config/routes.rb +58 -0
  57. data/test/dummy/config/secrets.yml +22 -0
  58. data/test/dummy/db/development.sqlite3 +0 -0
  59. data/test/dummy/db/test.sqlite3 +0 -0
  60. data/test/dummy/log/development.log +20917 -0
  61. data/test/dummy/log/test.log +13 -0
  62. data/test/dummy/public/404.html +67 -0
  63. data/test/dummy/public/422.html +67 -0
  64. data/test/dummy/public/500.html +66 -0
  65. data/test/dummy/public/favicon.ico +0 -0
  66. data/test/dummy/test/controllers/root_controller_test.rb +9 -0
  67. data/test/dummy/tmp/cache/assets/development/sprockets/00744134f1b8c820c724a9d6207f3a7a +0 -0
  68. data/test/dummy/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  69. data/test/dummy/tmp/cache/assets/development/sprockets/25e260f6a1dc0c4484c200da624af92e +0 -0
  70. data/test/dummy/tmp/cache/assets/development/sprockets/2c59dca85b9e8c86af73e35e031fbbdc +0 -0
  71. data/test/dummy/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  72. data/test/dummy/tmp/cache/assets/development/sprockets/3065ce12758c77c2a4921568b7e742a0 +0 -0
  73. data/test/dummy/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  74. data/test/dummy/tmp/cache/assets/development/sprockets/379aa3c595d4aa45885d1db5fbcb6e3a +0 -0
  75. data/test/dummy/tmp/cache/assets/development/sprockets/3824972f4a7973916302b2649b498d5c +0 -0
  76. data/test/dummy/tmp/cache/assets/development/sprockets/3c856723c34a3a47f181661bc3499592 +0 -0
  77. data/test/dummy/tmp/cache/assets/development/sprockets/41448ad2a85326b721f1e060b86dc3dc +0 -0
  78. data/test/dummy/tmp/cache/assets/development/sprockets/4387b22d35f85d5eb8336a8ed1381c5c +0 -0
  79. data/test/dummy/tmp/cache/assets/development/sprockets/48388ecb825f9d2b3ac4af78175993b2 +0 -0
  80. data/test/dummy/tmp/cache/assets/development/sprockets/55588f5ab129d88af0eef8e14f0f57aa +0 -0
  81. data/test/dummy/tmp/cache/assets/development/sprockets/6c9de7f2c8ef919141331199520c6890 +0 -0
  82. data/test/dummy/tmp/cache/assets/development/sprockets/6ef9396e711eaa215cf2fee6f0102551 +0 -0
  83. data/test/dummy/tmp/cache/assets/development/sprockets/7c8b616251089af5cf0b4bddc02c2cf1 +0 -0
  84. data/test/dummy/tmp/cache/assets/development/sprockets/83e8ae6039d56661189e1fb4812c89f0 +0 -0
  85. data/test/dummy/tmp/cache/assets/development/sprockets/91ae3870a5845130aa888fbe093d1554 +0 -0
  86. data/test/dummy/tmp/cache/assets/development/sprockets/9b474687caa91b30ec2214bdd9f54f33 +0 -0
  87. data/test/dummy/tmp/cache/assets/development/sprockets/b6c894a4da0fd8d4cd152a353d826045 +0 -0
  88. data/test/dummy/tmp/cache/assets/development/sprockets/c17e2e13afcf1d3570e8a857ccede2c1 +0 -0
  89. data/test/dummy/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  90. data/test/dummy/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  91. data/test/dummy/tmp/cache/assets/development/sprockets/efb95a602af8237a4970b7c683dae187 +0 -0
  92. data/test/dummy/tmp/cache/assets/development/sprockets/f603eeacf1822560ceb9ac4102ff8430 +0 -0
  93. data/test/dummy/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  94. data/test/dummy/tmp/cache/assets/test/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  95. data/test/dummy/tmp/cache/assets/test/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  96. data/test/dummy/tmp/cache/assets/test/sprockets/48388ecb825f9d2b3ac4af78175993b2 +0 -0
  97. data/test/dummy/tmp/cache/assets/test/sprockets/7c8b616251089af5cf0b4bddc02c2cf1 +0 -0
  98. data/test/dummy/tmp/cache/assets/test/sprockets/83e8ae6039d56661189e1fb4812c89f0 +0 -0
  99. data/test/dummy/tmp/cache/assets/test/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  100. data/test/dummy/tmp/cache/assets/test/sprockets/efb95a602af8237a4970b7c683dae187 +0 -0
  101. data/test/test_helper.rb +18 -0
  102. data/test/translation_engine_test.rb +7 -0
  103. metadata +310 -0
@@ -0,0 +1,79 @@
1
+ class TranslationEngine::Backend < I18n::Backend::Simple
2
+
3
+ IGNORE_KEYS = %i(faker order)
4
+
5
+ def reload!
6
+ super
7
+ init_translations
8
+ release == 'master' ? download_master : download_release
9
+ end
10
+
11
+ def release=(release)
12
+ @release = release
13
+ reload!
14
+ release
15
+ end
16
+
17
+ def release
18
+ @release ||= 'master'
19
+ end
20
+
21
+ def releases
22
+ [TranslationEngine::Release.master] | connection.get_releases
23
+ end
24
+
25
+ def current_locale_releases
26
+ releases.select(&:current_locale?)
27
+ end
28
+
29
+ protected
30
+
31
+ def connection
32
+ @connection ||= TranslationEngine::Connection.new
33
+ end
34
+
35
+ def download_master
36
+ if (yml_data = connection.get_translations.body)
37
+ Rails.logger.info { 'Updating from translation server - master' }
38
+ YAML.load(yml_data).each do |locale, translations|
39
+ store_translations locale, translations
40
+ end
41
+ end
42
+ end
43
+
44
+ def download_release
45
+ if (yml_data = connection.get_release(release).body)
46
+ Rails.logger.info { "Updating from translation server - #{release}" }
47
+ YAML.load(yml_data).each do |locale, translations|
48
+ store_translations locale, translations
49
+ end
50
+ end
51
+ end
52
+
53
+ def lookup(locale, key, scope = [], options = {})
54
+ keys = I18n.normalize_keys(locale, key, scope, options[:separator])
55
+ resolved = super
56
+ if resolved
57
+ enhance_translation(resolved, keys)
58
+ else
59
+ resolved
60
+ end
61
+ end
62
+
63
+ def enhance_translation(value, main_keys, catch_translation = true)
64
+ return value if (main_keys & IGNORE_KEYS).any?
65
+
66
+ case value
67
+ when Array
68
+ TranslationEngine::Translation.catch(value, main_keys)
69
+ value.map { |v| enhance_translation(v, main_keys, false) }
70
+ when Hash
71
+ Hash[value.map { |k, v| [k, enhance_translation(v, main_keys + [k])] }]
72
+ when String
73
+ TranslationEngine::Translation.new(value, main_keys).to_s
74
+ else
75
+ TranslationEngine::Translation.catch(value, main_keys) if catch_translation
76
+ value
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,75 @@
1
+ require 'faraday'
2
+
3
+ class TranslationEngine::Connection
4
+
5
+ NotFound = Class.new(Exception)
6
+
7
+ def send_images(data)
8
+ connection.post do |req|
9
+ req.url '/api/v1/images'
10
+ req.headers['Content-Type'] = 'application/json'
11
+ req.headers['Authorization'] = api_token
12
+ req.body = data.to_json
13
+ end
14
+ end
15
+
16
+ def send_translations(data)
17
+ connection.post do |req|
18
+ req.url '/api/v1/translations'
19
+ req.headers['Content-Type'] = 'application/json'
20
+ req.headers['Authorization'] = api_token
21
+ req.body = data.to_json
22
+ end
23
+ end
24
+
25
+ def get_releases
26
+ response = connection.get do |req|
27
+ req.url '/api/v1/releases.json'
28
+ req.headers['Authorization'] = api_token
29
+ end
30
+
31
+ JSON.parse(response.body).with_indifferent_access[:releases].map do |args|
32
+ TranslationEngine::Release.new args
33
+ end
34
+ end
35
+
36
+ def get_release(version)
37
+ response = connection.get do |req|
38
+ req.url "/api/v1/releases/#{version}.yaml"
39
+ req.headers['Authorization'] = api_token
40
+ end
41
+
42
+ raise NotFound, "Release #{version} Not found" if response.status != 200
43
+
44
+ response
45
+ end
46
+
47
+ def get_translations
48
+ connection.get do |req|
49
+ req.url '/api/v1/translations.yaml'
50
+ req.headers['Authorization'] = api_token
51
+ end
52
+ end
53
+
54
+ def get_translations_head
55
+ connection.head do |req|
56
+ req.url '/api/v1/translations.yaml'
57
+ req.headers['Authorization'] = api_token
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def connection
64
+ @connection ||= Faraday.new(:url => TranslationEngine.api_host) do |faraday|
65
+ faraday.use TranslationEngine::ConnectionExceptionMiddleware
66
+ faraday.adapter Faraday.default_adapter
67
+ faraday.options.timeout = TranslationEngine.timeout
68
+ faraday.options.open_timeout = TranslationEngine.timeout * 4
69
+ end
70
+ end
71
+
72
+ def api_token
73
+ "Token token=#{TranslationEngine.api_key}"
74
+ end
75
+ end
@@ -0,0 +1,2 @@
1
+ class TranslationEngine::ConnectionError < TranslationEngine::Error
2
+ end
@@ -0,0 +1,72 @@
1
+ require 'fileutils'
2
+
3
+ class TranslationEngine::Downloader
4
+
5
+ def self.etag?(etag)
6
+ response = @current_etag == etag
7
+ Rails.logger.info { "Translations are up to date" } if response
8
+ @current_etag = etag
9
+ response
10
+ end
11
+
12
+ def update
13
+ return nil if self.class.etag?(connection.get_translations_head[:etag])
14
+ I18n.backend.reload!
15
+ end
16
+
17
+ def store
18
+ if (yml_data = receive_translations)
19
+ Rails.logger.info { "Storing translations to #{locale_file}" }
20
+ write(locale_file, yml_data)
21
+ add_translation_file_to_i18n
22
+ I18n.backend.reload!
23
+ end
24
+ end
25
+
26
+ def store_release(release)
27
+ locale = release.split('_')[0...-1].join('_')
28
+ filename = releases_dir.join("#{locale.downcase}.yml")
29
+ if (yml_data = connection.get_release(release).body)
30
+ Rails.logger.info { "Storing release #{release} to #{filename}" }
31
+ write(filename, yml_data)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ def add_translation_file_to_i18n
38
+ unless I18n.load_path.include?(locale_file)
39
+ I18n.load_path += [locale_file]
40
+ end
41
+ end
42
+
43
+ def connection
44
+ @connection ||= TranslationEngine::Connection.new
45
+ end
46
+
47
+ def locale_dir
48
+ Rails.root.join('config', 'locales')
49
+ end
50
+
51
+ def locale_file
52
+ locale_dir.join('z_translation_engine.yml')
53
+ end
54
+
55
+ def receive_translations
56
+ return if self.class.etag?(connection.get_translations_head[:etag])
57
+
58
+ connection.get_translations.body
59
+ end
60
+
61
+ def releases_dir
62
+ dir = Rails.root.join('config', 'locales', 'z_releases')
63
+ FileUtils.mkdir_p(dir) unless File.exists?(dir)
64
+ dir
65
+ end
66
+
67
+ def write(filename, data)
68
+ File.open(filename, 'wb') do |f|
69
+ f.write data
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,2 @@
1
+ class TranslationEngine::Error < StandardError
2
+ end
@@ -0,0 +1,26 @@
1
+ class TranslationEngine::Release
2
+
3
+ attr_accessor :version, :locale, :created_at
4
+
5
+ def self.master
6
+ new(
7
+ version: 'master',
8
+ locale: 'all',
9
+ created_at: Time.now.to_s
10
+ )
11
+ end
12
+
13
+ def initialize(args)
14
+ @version = args[:version]
15
+ @locale = args[:locale]
16
+ @created_at = Time.parse(args[:created_at])
17
+ end
18
+
19
+ def current_locale?
20
+ I18n.locale.to_s == @locale || @locale == 'all'
21
+ end
22
+
23
+ def current?
24
+ I18n.backend.release == @version
25
+ end
26
+ end
@@ -0,0 +1,67 @@
1
+ class TranslationEngine::Translation
2
+
3
+ def self.catched
4
+ @catched ||= []
5
+ end
6
+
7
+ def self.clear_catched
8
+ @catched = []
9
+ end
10
+
11
+ def self.catch(text, keys)
12
+ if text.is_a?(Hash)
13
+ text.each do |key, value|
14
+ TranslationEngine::Translation.catch(value, keys + [key])
15
+ end
16
+ else
17
+ catch_basic_value(text, keys)
18
+ end
19
+ end
20
+
21
+ def self.catch_basic_value(text, keys)
22
+ catched << {
23
+ data_type: data_type(text),
24
+ key: keys.join('.'),
25
+ text: normalize_for_translation_server(text)
26
+ }
27
+ end
28
+
29
+ def self.normalize_for_translation_server(value)
30
+ if value.is_a?(Array)
31
+ YAML.dump(value).gsub("---\n", '')
32
+ else
33
+ value
34
+ end
35
+ end
36
+
37
+ def self.data_type(text)
38
+ case text
39
+ when Array then 'array'
40
+ when String then 'string'
41
+ when Float then 'float'
42
+ when Integer then 'integer'
43
+ when TrueClass then 'boolean'
44
+ when FalseClass then 'boolean'
45
+ when Symbol then 'symbol'
46
+ else nil
47
+ end
48
+ end
49
+
50
+ def initialize(text, keys)
51
+ @text = text
52
+ @keys = keys
53
+ self.class.catch(@text, @keys)
54
+ end
55
+
56
+ def full_key
57
+ @keys.join('.') rescue ''
58
+ end
59
+
60
+ def to_s
61
+ if TranslationEngine.use_screenshots && @text.is_a?(String)
62
+ "--TRANSLATION--#{full_key}--#{@text}"
63
+ else
64
+ @text
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,8 @@
1
+ TranslationEngine.config do |config|
2
+ config.api_key = '4e75310553e4523c0e015606343e01c5'
3
+ config.api_host = 'http://127.0.0.1:3000'
4
+ config.use_screenshots = false
5
+ config.use_catcher = false
6
+ config.timeout = 5
7
+ config.raise_exceptions = Rails.env.development?
8
+ end
@@ -0,0 +1,83 @@
1
+ namespace :translation_engine do
2
+ namespace :pull do
3
+
4
+ desc 'Download all master translations from Translations server and ' +
5
+ 'store them into config/locales/z_translation_engine.yml'
6
+ task master: :environment do
7
+ TranslationEngine::Downloader.new.store
8
+ puts 'Downloaded translations to: config/locales/z_translation_engine.yml'
9
+ end
10
+
11
+ desc 'Download released translations from Translations server and ' +
12
+ 'store them into config/locales/z_releases/LOCALE.yml, ' +
13
+ 'will overwrite previous locale release, leaving allways only one.'
14
+ task release: :environment do
15
+ Rails.logger = Logger.new(STDOUT)
16
+ TranslationEngine::Downloader.new.store_release(ENV['RELEASE'])
17
+ end
18
+ end
19
+
20
+ namespace :list do
21
+ desc 'List all available releases from Translations Server'
22
+ task releases: :environment do
23
+ puts "Listing releases from: #{TranslationEngine.api_host}"
24
+ releases = TranslationEngine::Connection.new.get_releases
25
+
26
+ releases_hash = Hash.new { |h, k| h[k] = [] }
27
+
28
+ releases.each_with_object(releases_hash) do |release, hash|
29
+ hash[release.locale] << release
30
+ end
31
+
32
+ releases_hash.each do |locale, releases|
33
+ puts "LOCALE: #{locale}"
34
+ releases.each do |release|
35
+ print " #{release.version} - #{release.created_at}"
36
+ print ' -> `rake translation_engine:pull:release '
37
+ puts "RELEASE=#{release.version}`"
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ desc 'Send all translations from directory config/locales/LOCALE ' +
44
+ 'to translations server'
45
+ task push: :environment do
46
+ locale = ENV['LOCALE'] || 'default'
47
+ puts "\nLOCALE: #{locale}"
48
+
49
+ TranslationEngine.timeout = 1000
50
+
51
+ def to_dotted_hash(hash, recursive_key = [])
52
+ hash.each_with_object({}) do |(k, v), ret|
53
+ key = recursive_key + [k]
54
+ if v.is_a? Hash
55
+ ret.merge! to_dotted_hash(v, key)
56
+ else
57
+ ret[key] = v
58
+ end
59
+ end
60
+ end
61
+ dir = Rails.root.join('config', 'locales')
62
+
63
+ Dir[dir.join "#{locale}/**/*.{yml}"].each do |file|
64
+ unless file.include?('faker')
65
+ hash = YAML.load_file(file) || {}
66
+ to_dotted_hash(hash).each do |keys, text|
67
+ Translation.catch text, keys
68
+ end
69
+ end
70
+ end
71
+
72
+ if Translation.catched.any?
73
+ data = {
74
+ locale: locale,
75
+ translations: Translation.catched.uniq
76
+ }
77
+ puts "Send #{Translation.catched.size} translations"
78
+ Connection.new.send_translations(data)
79
+ else
80
+ puts "No translations was found and send"
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,10 @@
1
+ require "translation_engine/engine"
2
+
3
+ module TranslationEngine
4
+ mattr_accessor :api_key, :api_host, :timeout,
5
+ :use_screenshots, :use_catcher, :raise_exceptions
6
+
7
+ def self.config
8
+ yield self
9
+ end
10
+ end
@@ -0,0 +1,32 @@
1
+ require 'rails-assets-html2canvas'
2
+
3
+ module TranslationEngine
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace TranslationEngine
6
+
7
+ initializer "translation_engine.middlewares" do |app|
8
+ app.config.app_middleware.use TranslationEngine::CatcherMiddleware
9
+ app.config.app_middleware.use TranslationEngine::KeysMiddleware
10
+ app.config.app_middleware.use TranslationEngine::ScreenshotsMiddleware
11
+ end
12
+
13
+ initializer "translation_engine.assets.precompile" do |app|
14
+ app.config.assets.precompile += %w(
15
+ translation_engine/screenshots.css
16
+ translation_engine/screenshots.js
17
+ )
18
+ %w(stylesheets javascripts).each do |sub|
19
+ app.config.assets.paths << root.join('app', 'assets', sub).to_s
20
+ end
21
+ end
22
+
23
+ if !Rails.const_defined?('Console')
24
+ config.after_initialize do |app|
25
+ if TranslationEngine.use_catcher || TranslationEngine.use_screenshots
26
+ I18n.backend = TranslationEngine::Backend.new
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+