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