translation_engine 0.0.1

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.
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
+