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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/decidim/term_customizer/admin/translations_admin_bulk.js.es6 +113 -0
- data/app/commands/decidim/term_customizer/admin/destroy_translations.rb +40 -0
- data/app/commands/decidim/term_customizer/admin/import_set_translations.rb +139 -0
- data/app/controllers/decidim/term_customizer/admin/translation_sets_controller.rb +11 -0
- data/app/controllers/decidim/term_customizer/admin/translations_controller.rb +23 -0
- data/app/controllers/decidim/term_customizer/admin/translations_destroys_controller.rb +54 -0
- data/app/forms/decidim/term_customizer/admin/translations_destroy_form.rb +46 -0
- data/app/forms/decidim/term_customizer/admin/translations_import_form.rb +48 -0
- data/app/helpers/decidim/term_customizer/admin/translations_helper.rb +21 -0
- data/app/jobs/decidim/term_customizer/admin/export_job.rb +19 -0
- data/app/permissions/decidim/term_customizer/admin/permissions.rb +11 -1
- data/app/views/decidim/term_customizer/admin/add_translations/index.html.erb +26 -6
- data/app/views/decidim/term_customizer/admin/translation_sets/_form.html.erb +25 -1
- data/app/views/decidim/term_customizer/admin/translation_sets/index.html.erb +39 -33
- data/app/views/decidim/term_customizer/admin/translations/_export_dropdown.html.erb +8 -0
- data/app/views/decidim/term_customizer/admin/translations/_form.html.erb +27 -1
- data/app/views/decidim/term_customizer/admin/translations/bulk_actions/_destroy.html.erb +13 -0
- data/app/views/decidim/term_customizer/admin/translations/bulk_actions/_dropdown.html.erb +26 -0
- data/app/views/decidim/term_customizer/admin/translations/index.html.erb +66 -38
- data/app/views/decidim/term_customizer/admin/translations/new_import.html.erb +40 -0
- data/app/views/decidim/term_customizer/admin/translations_destroys/new.html.erb +36 -0
- data/config/locales/ca.yml +72 -26
- data/config/locales/en.yml +109 -26
- data/config/locales/es.yml +72 -26
- data/config/locales/fi.yml +72 -26
- data/config/locales/fr.yml +72 -26
- data/config/locales/sv.yml +72 -26
- data/lib/decidim/term_customizer.rb +4 -0
- data/lib/decidim/term_customizer/admin_engine.rb +11 -1
- data/lib/decidim/term_customizer/import.rb +12 -0
- data/lib/decidim/term_customizer/import/importer.rb +69 -0
- data/lib/decidim/term_customizer/import/importer_factory.rb +17 -0
- data/lib/decidim/term_customizer/import/parser.rb +49 -0
- data/lib/decidim/term_customizer/import/readers.rb +39 -0
- data/lib/decidim/term_customizer/import/readers/base.rb +36 -0
- data/lib/decidim/term_customizer/import/readers/csv.rb +23 -0
- data/lib/decidim/term_customizer/import/readers/json.rb +25 -0
- data/lib/decidim/term_customizer/import/readers/xls.rb +25 -0
- data/lib/decidim/term_customizer/translation_import_collection.rb +71 -0
- data/lib/decidim/term_customizer/translation_parser.rb +13 -0
- data/lib/decidim/term_customizer/translation_serializer.rb +28 -0
- data/lib/decidim/term_customizer/version.rb +2 -2
- 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
|
-
|
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
|