lit 0.3.3 → 0.4.0.pre.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +40 -6
  3. data/app/assets/javascripts/lit/lit_frontend.js +20 -12
  4. data/app/assets/stylesheets/lit/application.css +3 -0
  5. data/app/controllers/lit/api/v1/base_controller.rb +1 -1
  6. data/app/controllers/lit/api/v1/locales_controller.rb +1 -1
  7. data/app/controllers/lit/api/v1/localization_keys_controller.rb +12 -4
  8. data/app/controllers/lit/api/v1/localizations_controller.rb +25 -9
  9. data/app/controllers/lit/application_controller.rb +11 -8
  10. data/app/controllers/lit/concerns/request_info_store.rb +1 -0
  11. data/app/controllers/lit/incomming_localizations_controller.rb +22 -15
  12. data/app/controllers/lit/locales_controller.rb +1 -1
  13. data/app/controllers/lit/localization_keys_controller.rb +44 -18
  14. data/app/controllers/lit/localizations_controller.rb +16 -11
  15. data/app/controllers/lit/sources_controller.rb +9 -13
  16. data/app/helpers/lit/frontend_helper.rb +28 -16
  17. data/app/helpers/lit/localizations_helper.rb +2 -1
  18. data/app/jobs/lit/synchronize_source_job.rb +1 -1
  19. data/app/models/lit/incomming_localization.rb +71 -41
  20. data/app/models/lit/locale.rb +11 -13
  21. data/app/models/lit/localization.rb +26 -24
  22. data/app/models/lit/localization_key.rb +46 -55
  23. data/app/models/lit/source.rb +17 -74
  24. data/app/queries/localization_key_search_query.rb +80 -0
  25. data/app/services/remote_interactor_service.rb +45 -0
  26. data/app/services/synchronize_source_service.rb +63 -0
  27. data/app/views/kaminari/lit/_gap.html.erb +1 -1
  28. data/app/views/layouts/lit/_navigation.html.erb +1 -1
  29. data/app/views/lit/dashboard/index.html.erb +2 -2
  30. data/app/views/lit/incomming_localizations/index.html.erb +10 -6
  31. data/app/views/lit/localization_keys/_localization_row.html.erb +1 -1
  32. data/app/views/lit/localization_keys/_localizations_list.html.erb +84 -0
  33. data/app/views/lit/localization_keys/_sidebar.html.erb +66 -0
  34. data/app/views/lit/localization_keys/change_completed.js.erb +2 -0
  35. data/app/views/lit/localization_keys/index.html.erb +3 -121
  36. data/app/views/lit/localization_keys/not_translated.html.erb +9 -0
  37. data/app/views/lit/localization_keys/restore_deleted.js.erb +1 -0
  38. data/app/views/lit/localization_keys/visited_again.html.erb +9 -0
  39. data/app/views/lit/localizations/_previous_versions_rows.html.erb +2 -2
  40. data/app/views/lit/localizations/change_completed.js.erb +2 -0
  41. data/app/views/lit/localizations/update.js.erb +2 -0
  42. data/app/views/lit/sources/_form.html.erb +1 -1
  43. data/app/views/lit/sources/index.html.erb +1 -1
  44. data/config/routes.rb +5 -0
  45. data/db/migrate/20181017123839_lit_add_is_deleted_to_localization_keys.rb +8 -0
  46. data/db/migrate/20181018075955_lit_add_localization_key_is_deleted_to_localization_keys.rb +8 -0
  47. data/db/migrate/20181030111522_lit_add_is_visited_again_to_localization_keys.rb +8 -0
  48. data/lib/generators/lit/install/templates/initializer.rb +5 -1
  49. data/lib/lit.rb +1 -0
  50. data/lib/lit/adapters/redis_storage.rb +1 -1
  51. data/lib/lit/cache.rb +32 -54
  52. data/lib/lit/export.rb +80 -0
  53. data/lib/lit/i18n_backend.rb +12 -9
  54. data/lib/lit/import.rb +179 -0
  55. data/lib/lit/loader.rb +2 -0
  56. data/lib/lit/version.rb +1 -1
  57. data/lib/tasks/lit_tasks.rake +78 -13
  58. metadata +21 -6
@@ -3,11 +3,14 @@ module Lit
3
3
  attr_accessor :interpolated_key
4
4
 
5
5
  ## SCOPES
6
- scope :completed, proc { where(is_completed: true) }
7
- scope :not_completed, proc { where(is_completed: false) }
8
- scope :starred, proc { where(is_starred: true) }
9
- scope :ordered, proc { order('localization_key asc') }
10
- scope :after, proc { |dt|
6
+ scope :completed, -> { where(is_completed: true) }
7
+ scope :not_completed, -> { where(is_completed: false) }
8
+ scope :starred, -> { where(is_starred: true) }
9
+ scope :ordered, -> { order(localization_key: :asc) }
10
+ scope :active, -> { where(is_deleted: false) }
11
+ scope :not_active, -> { where(is_deleted: true) }
12
+ scope :visited_again, -> { where(is_visited_again: true) }
13
+ scope :after, lambda { |dt|
11
14
  joins(:localizations)
12
15
  .where('lit_localization_keys.updated_at >= ?', dt)
13
16
  .where('lit_localizations.is_changed = true')
@@ -21,11 +24,14 @@ module Lit
21
24
  presence: true,
22
25
  uniqueness: { if: :localization_key_changed? }
23
26
 
27
+ ## ACCESSORS
24
28
  unless defined?(::ActionController::StrongParameters)
25
- ## ACCESSIBLE
26
29
  attr_accessible :localization_key
27
30
  end
28
31
 
32
+ ## BEFORE AND AFTER
33
+ after_commit :check_completed, on: :update
34
+
29
35
  def to_s
30
36
  localization_key
31
37
  end
@@ -38,70 +44,55 @@ module Lit
38
44
  new_created = true
39
45
  end
40
46
  end
41
- if new_created
42
- Lit::LocalizationKey.update_all ['is_completed=?', false], ['id=? and is_completed=?', id, false]
43
- end
44
- end
45
-
46
- def mark_completed
47
- self.is_completed = localizations.changed.count(:id) == localizations.count
48
- end
49
-
50
- def mark_completed!
51
- save if mark_completed
52
- end
53
-
54
- def mark_all_completed!
55
- localizations.update_all(['is_changed=?', true])
56
- mark_completed!
47
+ return unless new_created
48
+ Lit::LocalizationKey.update_all ['is_completed=?', false],
49
+ ['id=? and is_completed=?', id, false]
57
50
  end
58
51
 
59
52
  def self.order_options
60
- ['localization_key asc', 'localization_key desc', 'created_at asc', 'created_at desc', 'updated_at asc', 'updated_at desc']
53
+ ['localization_key asc', 'localization_key desc', 'created_at asc',
54
+ 'created_at desc', 'updated_at asc', 'updated_at desc']
61
55
  end
62
56
 
63
- # it can be overridden in parent application, for example: {:order => "created_at desc"}
57
+ # it can be overridden in parent application,
58
+ # for example: {:order => "created_at desc"}
64
59
  def self.default_search_options
65
60
  {}
66
61
  end
67
62
 
68
63
  def self.search(options = {})
69
- options = options.to_h.reverse_merge(default_search_options).with_indifferent_access
70
- s = self
71
- if options[:order] && order_options.include?(options[:order])
72
- column, order = options[:order].split(' ')
73
- s = s.order(FakeLocalizationKey.arel_table[column.to_sym].send(order.to_sym))
74
- else
75
- s = s.ordered
76
- end
77
- localization_key_col = FakeLocalizationKey.arel_table[:localization_key]
78
- default_value_col = FakeLocalization.arel_table[:default_value]
79
- translated_value_col = FakeLocalization.arel_table[:translated_value]
80
- if options[:key_prefix].present?
81
- q = "#{options[:key_prefix]}%"
82
- s = s.where(localization_key_col.matches(q))
83
- end
84
- if options[:key].present?
85
- q = "%#{options[:key]}%"
86
- q_underscore = "%#{options[:key].parameterize.underscore}%"
87
- cond = localization_key_col.matches(q).or(
88
- default_value_col.matches(q).or(
89
- translated_value_col.matches(q)
90
- )
91
- ).or(localization_key_col.matches(q_underscore))
92
- s = s.joins([:localizations]).where(cond)
64
+ LocalizationKeySearchQuery.new(self, options).perform
65
+ end
66
+
67
+ def change_all_completed
68
+ self.class.transaction do
69
+ toggle(:is_completed).save!
70
+ localizations.update_all is_changed: is_completed
93
71
  end
94
- unless options[:include_completed].to_i == 1
95
- s = s.not_completed
72
+ end
73
+
74
+ def soft_destroy
75
+ ActiveRecord::Base.transaction do
76
+ update is_deleted: true
77
+ change_all_completed
78
+ I18n.backend.available_locales.each do |l|
79
+ Lit.init.cache.delete_key "#{l}.#{localization_key}"
80
+ end
96
81
  end
97
- s
98
82
  end
99
83
 
100
- class FakeLocalizationKey < ActiveRecord::Base
101
- self.table_name = 'lit_localization_keys'
84
+ def restore
85
+ ActiveRecord::Base.transaction do
86
+ update is_deleted: false, is_completed: false, is_visited_again: false
87
+ localizations.update_all is_changed: false
88
+ end
102
89
  end
103
- class FakeLocalization < ActiveRecord::Base
104
- self.table_name = 'lit_localizations'
90
+
91
+ private
92
+
93
+ def check_completed
94
+ self.is_completed = localizations.changed.count == localizations.count
95
+ save! if is_completed_changed?
105
96
  end
106
97
  end
107
98
  end
@@ -1,10 +1,10 @@
1
1
  require 'net/http'
2
2
  module Lit
3
3
  class Source < ActiveRecord::Base
4
- LOCALES_PATH = '/api/v1/locales.json'
5
- LOCALIZATION_KEYS_PATH = '/api/v1/localization_keys.json'
6
- LOCALIZATIONS_PATH = '/api/v1/localizations.json'
7
- LAST_CHANGE_PATH = '/api/v1/last_change.json'
4
+ LOCALES_PATH = '/api/v1/locales.json'.freeze
5
+ LOCALIZATION_KEYS_PATH = '/api/v1/localization_keys.json'.freeze
6
+ LOCALIZATIONS_PATH = '/api/v1/localizations.json'.freeze
7
+ LAST_CHANGE_PATH = '/api/v1/last_change.json'.freeze
8
8
 
9
9
  ## ASSOCIATIONS
10
10
  has_many :incomming_localizations
@@ -13,8 +13,9 @@ module Lit
13
13
  validates :api_key, :identifier, :url,
14
14
  presence: true
15
15
  validates :url,
16
- format: { with: /\Ahttps?:\/\/.*\/.*[^\/]\Z/i }
16
+ format: { with: %r{\Ahttps?://.*/.*[^/]\Z}i }
17
17
 
18
+ ## ACCESSORS
18
19
  unless defined?(::ActionController::StrongParameters)
19
20
  attr_accessible :api_key, :identifier, :url
20
21
  end
@@ -23,89 +24,31 @@ module Lit
23
24
  before_create :set_last_updated_at_upon_creation
24
25
  after_validation :check_if_url_is_valid
25
26
 
26
- def get_last_change
27
- result = get_from_remote(LAST_CHANGE_PATH)
27
+ def last_change
28
+ result = RemoteInteractorService.new(self).send_request(LAST_CHANGE_PATH)
28
29
  result['last_change'] unless result.nil?
29
30
  end
30
31
 
31
- def synchronize
32
- after = last_updated_at.nil? ? nil : last_updated_at.to_s(:db)
33
- result = get_from_remote(LOCALIZATIONS_PATH, after: after)
34
- unless result.nil?
35
- if result.is_a?(Array)
36
- result.each do |r|
37
- il = IncommingLocalization.new
38
- if ::Rails::VERSION::MAJOR < 4
39
- il = IncommingLocalization.where(incomming_id: r['id']).first_or_initialize
40
- else
41
- il = IncommingLocalization.find_or_initialize_by(incomming_id: r['id'])
42
- end
43
- il.source = self
44
- il.locale_str = r['locale_str']
45
- il.locale = Locale.where(locale: il.locale_str).first
46
- il.localization_key_str = r['localization_key_str']
47
- il.localization_key = LocalizationKey.where(localization_key: il.localization_key_str).first
48
- unless il.is_duplicate?(r['value'])
49
- il.save!
50
- IncommingLocalization.where(id: il.id).
51
- update_all(translated_value: r['value'])
52
- end
53
- end
54
- last_change = get_last_change
55
- last_change = DateTime.parse(last_change) unless last_change.nil?
56
- touch_last_updated_at(last_change)
57
- update_column(:sync_complete, true)
58
- save
59
- end
60
- end
61
- end
62
-
63
32
  def touch_last_updated_at!
64
- touch_last_updated_at
33
+ assign_last_updated_at
65
34
  save
66
35
  end
67
36
 
68
- private
69
-
70
- def touch_last_updated_at(time = nil)
37
+ def assign_last_updated_at(time = nil)
71
38
  self.last_updated_at = time || Time.now
72
39
  end
73
40
 
74
- def check_if_url_is_valid
75
- if errors.empty? && (self.new_record? || self.url_changed?)
76
- errors.add(:url, 'is not accessible') if get_last_change.nil?
77
- end
78
- end
41
+ private
79
42
 
80
- def get_from_remote(path, query_values = {})
81
- result = nil
82
- begin
83
- uri = URI(url + path)
84
- query_values.each do |k, v|
85
- params = URI.decode_www_form(uri.query || '') << [k, v]
86
- uri.query = URI.encode_www_form(params)
87
- end
88
- req = Net::HTTP::Get.new(uri.request_uri)
89
- req.add_field('Authorization', %(Token token="#{api_key}"))
90
- http = Net::HTTP.new(uri.host, uri.port)
91
- http.use_ssl = (uri.port == 443)
92
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
93
- res = http.start do |http_started|
94
- http_started.request(req)
95
- end
96
- if res.is_a?(Net::HTTPSuccess)
97
- result = JSON.parse(res.body)
98
- end
99
- rescue => e
100
- ::Rails.logger.error { "Lit remote error: #{e}" } if defined?(Rails)
101
- end
102
- result
43
+ def check_if_url_is_valid
44
+ return if errors.present? || !(new_record? || url_changed?) ||
45
+ last_change.present?
46
+ errors.add :url, 'is not accessible'
103
47
  end
104
48
 
105
49
  def set_last_updated_at_upon_creation
106
- if last_updated_at.blank?
107
- touch_last_updated_at if Lit.set_last_updated_at_upon_creation
108
- end
50
+ return if last_updated_at.blank? && !Lit.set_last_updated_at_upon_creation
51
+ assign_last_updated_at
109
52
  end
110
53
  end
111
54
  end
@@ -0,0 +1,80 @@
1
+ class LocalizationKeySearchQuery
2
+ def initialize(scope, params = {})
3
+ @scope = scope
4
+ @params = params.to_h
5
+ .reverse_merge(@scope.default_search_options)
6
+ .with_indifferent_access
7
+ end
8
+
9
+ def perform
10
+ order_data
11
+ search_key_prefix
12
+ search_key
13
+ search_completed
14
+ @scope
15
+ end
16
+
17
+ private
18
+
19
+ def order_data
20
+ if @params[:order] && order_options.include?(@params[:order])
21
+ column, order = @params[:order].split(' ')
22
+ @scope = @scope.order(
23
+ localization_key_arel[column.to_sym].send(order.to_sym)
24
+ )
25
+ else
26
+ @scope = @scope.ordered
27
+ end
28
+ end
29
+
30
+ def search_key_prefix
31
+ return if @params[:key_prefix].blank?
32
+ q = Arel::Nodes.build_quoted("#{@params[:key_prefix]}%")
33
+ @scope = @scope.where(localization_key_col.matches(q))
34
+ end
35
+
36
+ def search_key
37
+ return if @params[:key].blank?
38
+ q = Arel::Nodes.build_quoted("%#{@params[:key]}%")
39
+ q_underscore = Arel::Nodes.build_quoted("%#{@params[:key].parameterize.underscore}%")
40
+ cond = search_key_conditions(q, q_underscore)
41
+ @scope = @scope.joins([:localizations]).where(cond)
42
+ end
43
+
44
+ def search_key_conditions(query, q_underscore)
45
+ localization_key_col.matches(query).or(
46
+ default_value_col.matches(query).or(
47
+ translated_value_col.matches(query)
48
+ )
49
+ ).or(localization_key_col.matches(q_underscore))
50
+ end
51
+
52
+ def search_completed
53
+ return if @params[:include_completed].to_i != 1
54
+ @scope = @scope.not_completed
55
+ end
56
+
57
+ def localization_key_col
58
+ localization_key_arel[:localization_key]
59
+ end
60
+
61
+ def default_value_col
62
+ localization_arel[:default_value]
63
+ end
64
+
65
+ def translated_value_col
66
+ localization_arel[:translated_value]
67
+ end
68
+
69
+ def localization_key_arel
70
+ Arel::Table.new 'lit_localization_keys'
71
+ end
72
+
73
+ def localization_arel
74
+ Arel::Table.new 'lit_localizations'
75
+ end
76
+
77
+ def order_options
78
+ Lit::LocalizationKey.order_options
79
+ end
80
+ end
@@ -0,0 +1,45 @@
1
+ class RemoteInteractorService
2
+ def initialize(source)
3
+ @source = source
4
+ end
5
+
6
+ def send_request(path, query_values = {})
7
+ uri = initialize_uri(path, query_values)
8
+ req = initialize_request(uri)
9
+ connection = initialize_connection(uri)
10
+ perform_request(connection, req)
11
+ rescue => e
12
+ return unless defined?(Rails)
13
+ ::Rails.logger.error { "Lit remote error: #{e}" }
14
+ end
15
+
16
+ private
17
+
18
+ def initialize_uri(path, query_values)
19
+ uri = URI(@source.url + path)
20
+ query_values.each do |k, v|
21
+ params = URI.decode_www_form(uri.query || '') << [k, v]
22
+ uri.query = URI.encode_www_form(params)
23
+ end
24
+ uri
25
+ end
26
+
27
+ def initialize_request(uri)
28
+ req = Net::HTTP::Get.new(uri.request_uri)
29
+ req.add_field('Authorization', %(Token token="#{@source.api_key}"))
30
+ req
31
+ end
32
+
33
+ def initialize_connection(uri)
34
+ http = Net::HTTP.new(uri.host, uri.port)
35
+ http.use_ssl = (uri.port == 443)
36
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
37
+ http
38
+ end
39
+
40
+ def perform_request(connection, request)
41
+ res = connection.start { |http| http.request(request) }
42
+ return res unless res.is_a?(Net::HTTPSuccess)
43
+ JSON.parse(res.body)
44
+ end
45
+ end
@@ -0,0 +1,63 @@
1
+ class SynchronizeSourceService
2
+ def initialize(source)
3
+ @source = source
4
+ end
5
+
6
+ def execute
7
+ synchronize_localizations
8
+ update_timestamps
9
+ end
10
+
11
+ private
12
+
13
+ def synchronize_localizations
14
+ after_date = @source.last_updated_at&.to_s(:db)
15
+ result = interactor.send_request Lit::Source::LOCALIZATIONS_PATH,
16
+ after: after_date
17
+ return unless result&.is_a?(Array)
18
+ result.each { |loc| synchronize_localization loc }
19
+ end
20
+
21
+ def synchronize_localization(loc)
22
+ inc_loc = find_incomming_localization(loc)
23
+ inc_loc.source = @source
24
+ inc_loc.locale_str = loc['locale_str']
25
+ inc_loc.locale = Lit::Locale.find_by(locale: loc['locale_str'])
26
+ inc_loc.localization_key_str = loc['localization_key_str']
27
+ inc_loc.localization_key_is_deleted = localization_key_deleted?(loc)
28
+ inc_loc.localization_key = find_localization_key(inc_loc)
29
+ inc_loc.translated_value = loc['value']
30
+ return if inc_loc.duplicated?(loc['value'])
31
+ inc_loc.save!
32
+ end
33
+
34
+ def find_incomming_localization(localization)
35
+ Lit::IncommingLocalization.find_or_initialize_by(
36
+ incomming_id: localization['id']
37
+ )
38
+ end
39
+
40
+ def find_localization_key(inc_loc)
41
+ Lit::LocalizationKey.find_by(
42
+ localization_key: inc_loc.localization_key_str
43
+ )
44
+ end
45
+
46
+ def localization_key_deleted?(loc)
47
+ loc['localization_key_is_deleted'] || false
48
+ end
49
+
50
+ def update_timestamps
51
+ @source.assign_last_updated_at(fetch_last_change)
52
+ @source.sync_complete = true
53
+ @source.save!
54
+ end
55
+
56
+ def fetch_last_change
57
+ interactor.send_request(Lit::Source::LAST_CHANGE_PATH)['last_change']
58
+ end
59
+
60
+ def interactor
61
+ RemoteInteractorService.new(@source)
62
+ end
63
+ end