lit 0.3.3 → 0.4.0.pre.alpha

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 (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