decidim-term_customizer 0.16.6 → 0.17.0

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