decidim-term_customizer 0.16.6 → 0.17.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/decidim/term_customizer/admin/translations_admin_bulk.js.es6 +113 -0
  3. data/app/commands/decidim/term_customizer/admin/destroy_translations.rb +40 -0
  4. data/app/commands/decidim/term_customizer/admin/import_set_translations.rb +139 -0
  5. data/app/controllers/decidim/term_customizer/admin/translation_sets_controller.rb +11 -0
  6. data/app/controllers/decidim/term_customizer/admin/translations_controller.rb +23 -0
  7. data/app/controllers/decidim/term_customizer/admin/translations_destroys_controller.rb +54 -0
  8. data/app/forms/decidim/term_customizer/admin/translations_destroy_form.rb +46 -0
  9. data/app/forms/decidim/term_customizer/admin/translations_import_form.rb +48 -0
  10. data/app/helpers/decidim/term_customizer/admin/translations_helper.rb +21 -0
  11. data/app/jobs/decidim/term_customizer/admin/export_job.rb +19 -0
  12. data/app/permissions/decidim/term_customizer/admin/permissions.rb +11 -1
  13. data/app/views/decidim/term_customizer/admin/add_translations/index.html.erb +26 -6
  14. data/app/views/decidim/term_customizer/admin/translation_sets/_form.html.erb +25 -1
  15. data/app/views/decidim/term_customizer/admin/translation_sets/index.html.erb +39 -33
  16. data/app/views/decidim/term_customizer/admin/translations/_export_dropdown.html.erb +8 -0
  17. data/app/views/decidim/term_customizer/admin/translations/_form.html.erb +27 -1
  18. data/app/views/decidim/term_customizer/admin/translations/bulk_actions/_destroy.html.erb +13 -0
  19. data/app/views/decidim/term_customizer/admin/translations/bulk_actions/_dropdown.html.erb +26 -0
  20. data/app/views/decidim/term_customizer/admin/translations/index.html.erb +66 -38
  21. data/app/views/decidim/term_customizer/admin/translations/new_import.html.erb +40 -0
  22. data/app/views/decidim/term_customizer/admin/translations_destroys/new.html.erb +36 -0
  23. data/config/locales/ca.yml +72 -26
  24. data/config/locales/en.yml +109 -26
  25. data/config/locales/es.yml +72 -26
  26. data/config/locales/fi.yml +72 -26
  27. data/config/locales/fr.yml +72 -26
  28. data/config/locales/sv.yml +72 -26
  29. data/lib/decidim/term_customizer.rb +4 -0
  30. data/lib/decidim/term_customizer/admin_engine.rb +11 -1
  31. data/lib/decidim/term_customizer/import.rb +12 -0
  32. data/lib/decidim/term_customizer/import/importer.rb +69 -0
  33. data/lib/decidim/term_customizer/import/importer_factory.rb +17 -0
  34. data/lib/decidim/term_customizer/import/parser.rb +49 -0
  35. data/lib/decidim/term_customizer/import/readers.rb +39 -0
  36. data/lib/decidim/term_customizer/import/readers/base.rb +36 -0
  37. data/lib/decidim/term_customizer/import/readers/csv.rb +23 -0
  38. data/lib/decidim/term_customizer/import/readers/json.rb +25 -0
  39. data/lib/decidim/term_customizer/import/readers/xls.rb +25 -0
  40. data/lib/decidim/term_customizer/translation_import_collection.rb +71 -0
  41. data/lib/decidim/term_customizer/translation_parser.rb +13 -0
  42. data/lib/decidim/term_customizer/translation_serializer.rb +28 -0
  43. data/lib/decidim/term_customizer/version.rb +2 -2
  44. metadata +39 -14
@@ -10,7 +10,17 @@ module Decidim
10
10
 
11
11
  routes do
12
12
  resources :translation_sets, path: :sets, except: [:show] do
13
- resources :translations, except: [:show]
13
+ member do
14
+ post :export
15
+ end
16
+
17
+ resources :translations, except: [:show] do
18
+ collection do
19
+ get :import, action: :new_import
20
+ post :import
21
+ resource :translations_destroy, only: [:new, :destroy]
22
+ end
23
+ end
14
24
  resources :add_translations, only: [:index, :create] do
15
25
  collection do
16
26
  get :search
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ module Import
6
+ autoload :ImporterFactory, "decidim/term_customizer/import/importer_factory"
7
+ autoload :Importer, "decidim/term_customizer/import/importer"
8
+ autoload :Parser, "decidim/term_customizer/import/parser"
9
+ autoload :Readers, "decidim/term_customizer/import/readers"
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ module Import
6
+ # Class providing the interface and implementation of an importer. Needs
7
+ # a reader to be passed to the constructor which handles the import file
8
+ # reading depending on its type.
9
+ #
10
+ # You can also use the ImporterFactory class to create an Importer
11
+ # instance.
12
+ class Importer
13
+ # Public: Initializes an Importer.
14
+ #
15
+ # file - A file with the data to be imported.
16
+ # reader - A Reader to be used to read the data from the file.
17
+ # parser - A Parser to be used during the import.
18
+ def initialize(file, reader = Readers::Base, parser = Parser)
19
+ @file = file
20
+ @reader = reader
21
+ @parser = parser
22
+ end
23
+
24
+ # Public: Imports a spreadsheet/JSON to the data collection provided by
25
+ # the parser. The parsed data objects are saved one by one or the data
26
+ # collection is yielded in case block is given in which case the saving
27
+ # should happen outside of this class.
28
+ def import
29
+ parser.resource_klass.transaction do
30
+ if block_given?
31
+ yield collection
32
+ else
33
+ collection.each(&:save!)
34
+ end
35
+ end
36
+ end
37
+
38
+ # Returns a data collection of the target data.
39
+ def collection
40
+ @collection ||= collection_data.map { |item| parser.new(item).parse }
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :file, :reader, :parser
46
+
47
+ def collection_data
48
+ return @collection_data if @collection_data
49
+
50
+ @collection_data = []
51
+ data_headers = []
52
+ reader.new(file).read_rows do |rowdata, index|
53
+ if index.zero?
54
+ data_headers = rowdata.map(&:to_sym)
55
+ else
56
+ @collection_data << Hash[
57
+ rowdata.each_with_index.map do |val, ind|
58
+ [data_headers[ind], val]
59
+ end
60
+ ]
61
+ end
62
+ end
63
+
64
+ @collection_data
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ module Import
6
+ # A factory class providing easier way to create new importers.
7
+ class ImporterFactory
8
+ def self.build(file, mime_type, *args)
9
+ reader = Readers.find_by_mime_type(mime_type)
10
+ raise NotImplementedError, "No reader implemented for mime type: #{mime_type}" if reader.nil?
11
+
12
+ Importer.new(file, reader, *args)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ module Import
6
+ # This is an abstract class with a very naive default implementation
7
+ # for the importers to use. It can also serve as a superclass of your
8
+ # own implementation.
9
+ #
10
+ # It is used to be run against each element of an importable collection
11
+ # in order to parse relevant fields. Every import should specify their
12
+ # own parser or this default will be used.
13
+ class Parser
14
+ attr_reader :data
15
+
16
+ # Initializes the parser with a resource.
17
+ #
18
+ # data - The data hash to parse.
19
+ def initialize(data)
20
+ @data = data.except(:id, "id")
21
+ end
22
+
23
+ # Retuns the resource class to be created with the provided data.
24
+ def self.resource_klass
25
+ raise NotImplementedError, "#{self.class.name} does not define resource class"
26
+ end
27
+
28
+ # Can be used to convert the data hash to the resource attributes in
29
+ # case the data hash to be imported has different column names than the
30
+ # resource object to be created of it.
31
+ #
32
+ # By default returns the data hash but can be implemented by each parser
33
+ # implementation.
34
+ #
35
+ # Returns the resource attributes to be passed for the constructor.
36
+ def resource_attributes
37
+ @data
38
+ end
39
+
40
+ # Public: Returns a parsed object with the parsed data.
41
+ #
42
+ # Returns a target object.
43
+ def parse
44
+ self.class.resource_klass.new(resource_attributes)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ module Import
6
+ module Readers
7
+ autoload :Base, "decidim/term_customizer/import/readers/base"
8
+ autoload :CSV, "decidim/term_customizer/import/readers/csv"
9
+ autoload :JSON, "decidim/term_customizer/import/readers/json"
10
+ autoload :XLS, "decidim/term_customizer/import/readers/xls"
11
+
12
+ # Accepted mime types
13
+ # keys: are used for dynamic help text on admin form.
14
+ # values: are used to validate the file format of imported document.
15
+ ACCEPTED_MIME_TYPES = {
16
+ json: Readers::JSON::MIME_TYPE,
17
+ csv: Readers::CSV::MIME_TYPE,
18
+ xls: Readers::XLS::MIME_TYPE
19
+ }.freeze
20
+
21
+ def self.all
22
+ [
23
+ Readers::CSV,
24
+ Readers::JSON,
25
+ Readers::XLS
26
+ ]
27
+ end
28
+
29
+ def self.find_by_mime_type(mime_type)
30
+ all.each do |reader_klass|
31
+ return reader_klass if mime_type == reader_klass::MIME_TYPE
32
+ end
33
+
34
+ nil
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ module Import
6
+ module Readers
7
+ # Imports an exported file to data arrays that can be processed during
8
+ # yields.
9
+ class Base
10
+ def initialize(file)
11
+ @file = file
12
+ end
13
+
14
+ # The read_rows method should iterate over each row of the data and
15
+ # yield the data array of each row with the row's index. For example,
16
+ # this could look like following:
17
+ # Parser.read(file).rows.each_with_index do |row, index|
18
+ # yield row.to_a, index
19
+ # end
20
+ #
21
+ # The first row yielded with index 0 needs to contain the data headers
22
+ # which can be later used to map the data to correct attributes.
23
+ #
24
+ # This needs to be implemented by the extending classes.
25
+ def read_rows
26
+ raise NotImplementedError
27
+ end
28
+
29
+ protected
30
+
31
+ attr_reader :file
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module Decidim
6
+ module TermCustomizer
7
+ module Import
8
+ module Readers
9
+ # Imports any exported CSV file to local objects. It transforms the
10
+ # import data using the parser into the final target objects.
11
+ class CSV < Base
12
+ MIME_TYPE = "text/csv"
13
+
14
+ def read_rows
15
+ ::CSV.read(file, col_sep: ";").each_with_index do |row, index|
16
+ yield row, index
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Decidim
6
+ module TermCustomizer
7
+ module Import
8
+ module Readers
9
+ # Imports any exported JSON file to local objects. It transforms the
10
+ # import data using the parser into the final target objects.
11
+ class JSON < Base
12
+ MIME_TYPE = "application/json"
13
+
14
+ def read_rows
15
+ json_string = File.read(file)
16
+ ::JSON.parse(json_string).each_with_index do |row, index|
17
+ yield row.keys, index if index.zero?
18
+ yield row.values, index + 1
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spreadsheet"
4
+
5
+ module Decidim
6
+ module TermCustomizer
7
+ module Import
8
+ module Readers
9
+ # Imports any exported XLS file to local objects. It transforms the
10
+ # import data using the parser into the final target objects.
11
+ class XLS < Base
12
+ MIME_TYPE = "application/vnd.ms-excel"
13
+
14
+ def read_rows
15
+ book = ::Spreadsheet.open(file)
16
+ sheet = book.worksheet(0)
17
+ sheet.each_with_index do |row, index|
18
+ yield row.to_a, index
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ class TranslationImportCollection
6
+ def initialize(translation_set, records, locales)
7
+ @translation_set = translation_set
8
+ @records = records
9
+ @locales = locales
10
+ end
11
+
12
+ def import_attributes
13
+ attributes_for_all_locales
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :translation_set, :records, :locales
19
+
20
+ def collection_attributes
21
+ @collection_attributes ||= records.map do |translation|
22
+ # Skip all translation keys that already exists
23
+ next if translation_set.translations.where(
24
+ key: translation.key
25
+ ).present?
26
+
27
+ {
28
+ locale: translation.locale,
29
+ key: translation.key,
30
+ value: translation.value
31
+ }
32
+ end.compact
33
+ end
34
+
35
+ def unique_attributes
36
+ @unique_attributes ||= collection_attributes.uniq do |attr|
37
+ "#{attr[:locale]}.#{attr[:key]}"
38
+ end
39
+ end
40
+
41
+ def attribute_keys
42
+ @attribute_keys ||= unique_attributes.map { |attr| attr[:key] }.uniq
43
+ end
44
+
45
+ def attributes_for_all_locales
46
+ @attributes_for_all_locales ||= attribute_keys.map do |key|
47
+ locales.map do |locale|
48
+ # Find if the item with the locale already exists in the
49
+ # unique collection.
50
+ item = unique_attributes.find do |attr|
51
+ attr[:key] == key && attr[:locale] == locale.to_s
52
+ end
53
+
54
+ # In case the item does not exist for the key and locale, create
55
+ # a new item with the default I18n translation. Otherwise, return
56
+ # the found item to the final array.
57
+ if item.nil?
58
+ {
59
+ key: key,
60
+ locale: locale.to_s,
61
+ value: I18n.t(key, locale: locale, default: "")
62
+ }
63
+ else
64
+ item
65
+ end
66
+ end
67
+ end.flatten
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ # This class serializes a Proposal so can be exported to CSV, JSON or other
6
+ # formats.
7
+ class TranslationParser < Import::Parser
8
+ def self.resource_klass
9
+ Decidim::TermCustomizer::Translation
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ # This class serializes a Proposal so can be exported to CSV, JSON or other
6
+ # formats.
7
+ class TranslationSerializer < Decidim::Exporters::Serializer
8
+ # Public: Initializes the serializer with a proposal.
9
+ def initialize(translation)
10
+ @translation = translation
11
+ end
12
+
13
+ # Public: Exports a hash with the serialized data for this proposal.
14
+ def serialize
15
+ {
16
+ id: translation.id,
17
+ locale: translation.locale,
18
+ key: translation.key,
19
+ value: translation.value
20
+ }
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :translation
26
+ end
27
+ end
28
+ end