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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16eec8f526df47ea82ef79a644e4afd290f64e7e1f7aa2fea64723ad7a30d6b9
|
4
|
+
data.tar.gz: 93c4464c8c3e2b3437df94b25e0f457e60ad1f18240c1fbc6609421600888805
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4efdea5c3cca3ec8cd9f1e4d068b63f587b84f1ec9c175f9be2d1ff5832b688c467655596fcc85be7d2c2d6f22acffcf3c07913910c6a74151a4b5e4bb35aff6
|
7
|
+
data.tar.gz: 8864a676c3562377cc114556c058cbe50983d4b4691f9007130de45d3addbc8bb0ab4dd670c11e2a1412e53b748f06a06b78d23f652b379808ee499be24ce99c
|
@@ -0,0 +1,113 @@
|
|
1
|
+
// = require_self
|
2
|
+
|
3
|
+
/* eslint-disable no-invalid-this */
|
4
|
+
$(document).ready(function () {
|
5
|
+
let selectedTranslationsCount = function() {
|
6
|
+
return $(".table-list .js-check-all-translation:checked").length
|
7
|
+
}
|
8
|
+
|
9
|
+
window.selectedTranslationsCountUpdate = function() {
|
10
|
+
if (selectedTranslationsCount() === 0) {
|
11
|
+
$("#js-selected-translation-count").text("")
|
12
|
+
} else {
|
13
|
+
$("#js-selected-translation-count").text(selectedTranslationsCount());
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
let showBulkActionsButton = function() {
|
18
|
+
if (selectedTranslationsCount() > 0) {
|
19
|
+
$("#js-bulk-actions-button").removeClass("hide");
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
let hideBulkActionsButton = function(force = false) {
|
24
|
+
if (selectedTranslationsCount() === 0 || force === true) {
|
25
|
+
$("#js-bulk-actions-button").addClass("hide");
|
26
|
+
$("#js-bulk-actions-dropdown").removeClass("is-open");
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
window.showOtherActionsButtons = function() {
|
31
|
+
$("#js-other-actions-wrapper").removeClass("hide");
|
32
|
+
}
|
33
|
+
|
34
|
+
const hideOtherActionsButtons = function() {
|
35
|
+
$("#js-other-actions-wrapper").addClass("hide");
|
36
|
+
}
|
37
|
+
|
38
|
+
window.hideBulkActionForms = function() {
|
39
|
+
return $(".js-bulk-action-form").addClass("hide");
|
40
|
+
}
|
41
|
+
|
42
|
+
if ($(".js-bulk-action-form").length) {
|
43
|
+
window.hideBulkActionForms();
|
44
|
+
$("#js-bulk-actions-button").addClass("hide");
|
45
|
+
|
46
|
+
$("#js-bulk-actions-dropdown ul li button").click(function(ev) {
|
47
|
+
ev.preventDefault();
|
48
|
+
let action = $(ev.target).data("action");
|
49
|
+
|
50
|
+
if (action) {
|
51
|
+
$(`#js-form-${action}`).submit(function() {
|
52
|
+
$(".layout-content > .callout-wrapper").html("");
|
53
|
+
})
|
54
|
+
|
55
|
+
$(`#js-${action}-actions`).removeClass("hide");
|
56
|
+
hideBulkActionsButton(true);
|
57
|
+
hideOtherActionsButtons();
|
58
|
+
}
|
59
|
+
})
|
60
|
+
|
61
|
+
// select all checkboxes
|
62
|
+
$(".js-check-all").change(function() {
|
63
|
+
$(".js-check-all-translation").prop("checked", $(this).prop("checked"));
|
64
|
+
|
65
|
+
if ($(this).prop("checked")) {
|
66
|
+
$(".js-check-all-translation").closest("tr").addClass("selected");
|
67
|
+
showBulkActionsButton();
|
68
|
+
} else {
|
69
|
+
$(".js-check-all-translation").closest("tr").removeClass("selected");
|
70
|
+
hideBulkActionsButton();
|
71
|
+
}
|
72
|
+
|
73
|
+
window.selectedTranslationsCountUpdate();
|
74
|
+
});
|
75
|
+
|
76
|
+
// translation checkbox change
|
77
|
+
$(".table-list").on("change", ".js-check-all-translation", function () {
|
78
|
+
let translationId = $(this).val()
|
79
|
+
let checked = $(this).prop("checked")
|
80
|
+
|
81
|
+
// uncheck "select all", if one of the listed checkbox item is unchecked
|
82
|
+
if ($(this).prop("checked") === false) {
|
83
|
+
$(".js-check-all").prop("checked", false);
|
84
|
+
}
|
85
|
+
// check "select all" if all checkbox translations are checked
|
86
|
+
if ($(".js-check-all-translation:checked").length === $(".js-check-all-translation").length) {
|
87
|
+
$(".js-check-all").prop("checked", true);
|
88
|
+
showBulkActionsButton();
|
89
|
+
}
|
90
|
+
|
91
|
+
if ($(this).prop("checked")) {
|
92
|
+
showBulkActionsButton();
|
93
|
+
$(this).closest("tr").addClass("selected");
|
94
|
+
} else {
|
95
|
+
hideBulkActionsButton();
|
96
|
+
$(this).closest("tr").removeClass("selected");
|
97
|
+
}
|
98
|
+
|
99
|
+
if ($(".js-check-all-translation:checked").length === 0) {
|
100
|
+
hideBulkActionsButton();
|
101
|
+
}
|
102
|
+
|
103
|
+
$(".js-bulk-action-form").find(`.js-translation-id-${translationId}`).prop("checked", checked);
|
104
|
+
window.selectedTranslationCountUpdate();
|
105
|
+
});
|
106
|
+
|
107
|
+
$(".js-cancel-bulk-action").on("click", function () {
|
108
|
+
window.hideBulkActionForms()
|
109
|
+
showBulkActionsButton();
|
110
|
+
window.showOtherActionsButtons();
|
111
|
+
});
|
112
|
+
}
|
113
|
+
});
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module TermCustomizer
|
5
|
+
module Admin
|
6
|
+
# A command with all the business logic when an admin destroys
|
7
|
+
# translations from a translation set.
|
8
|
+
class DestroyTranslations < Rectify::Command
|
9
|
+
# Public: Initializes the command.
|
10
|
+
#
|
11
|
+
# form - A form object with the params.
|
12
|
+
def initialize(form)
|
13
|
+
@form = form
|
14
|
+
end
|
15
|
+
|
16
|
+
# Executes the command. Broadcasts these events:
|
17
|
+
#
|
18
|
+
# - :ok when everything is valid.
|
19
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
20
|
+
#
|
21
|
+
# Returns nothing.
|
22
|
+
def call
|
23
|
+
return broadcast(:invalid) unless form.valid?
|
24
|
+
|
25
|
+
destroy_translations
|
26
|
+
|
27
|
+
broadcast(:ok)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :form
|
33
|
+
|
34
|
+
def destroy_translations
|
35
|
+
form.translations.destroy_all
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zip"
|
4
|
+
|
5
|
+
module Decidim
|
6
|
+
module TermCustomizer
|
7
|
+
module Admin
|
8
|
+
# A command with all the business logic when importing translations to a
|
9
|
+
# set from a file submitted through the form. The file may be one of the
|
10
|
+
# supported import formats or a ZIP file containing a supported import
|
11
|
+
# file.
|
12
|
+
class ImportSetTranslations < Rectify::Command
|
13
|
+
# Public: Initializes the command.
|
14
|
+
#
|
15
|
+
# form - A form object with the params.
|
16
|
+
# translation_set - The translation set to which the import is
|
17
|
+
# performed.
|
18
|
+
def initialize(form, translation_set)
|
19
|
+
@form = form
|
20
|
+
@translation_set = translation_set
|
21
|
+
end
|
22
|
+
|
23
|
+
# Executes the command. Broadcasts these events:
|
24
|
+
#
|
25
|
+
# - :ok when everything is valid.
|
26
|
+
# - :invalid if the form wasn't valid and we couldn't proceed.
|
27
|
+
#
|
28
|
+
# Returns nothing.
|
29
|
+
def call
|
30
|
+
return broadcast(:invalid) if form.invalid?
|
31
|
+
|
32
|
+
@translations = import_translations
|
33
|
+
|
34
|
+
if @translations.length.positive?
|
35
|
+
broadcast(:ok, @translations)
|
36
|
+
else
|
37
|
+
broadcast(:invalid)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :form, :translation_set
|
44
|
+
|
45
|
+
# Private: Handles the import of either a zip file or a regular import
|
46
|
+
# file, one of the supported reader formats based on the file provided
|
47
|
+
# by the import form.
|
48
|
+
#
|
49
|
+
# Returns Array or nil. The returned value is an array of the imported
|
50
|
+
# translations when the import is successful, otherwise nil.
|
51
|
+
def import_translations
|
52
|
+
return import_zip(form.file_path) if form.zip_file?
|
53
|
+
|
54
|
+
import_file(form.file_path, form.mime_type)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Private: Handles the import of a regular import file, one of the
|
58
|
+
# supported reader formats. Will iterate over the whole imported data
|
59
|
+
# array and save all records to the database.
|
60
|
+
#
|
61
|
+
# filepath - A filepath with the data to be imported.
|
62
|
+
# mime_type - The mime type of the provided file.
|
63
|
+
#
|
64
|
+
# Returns Array or nil. The returned value is an array of the imported
|
65
|
+
# translations when the import is successful, otherwise nil.
|
66
|
+
def import_file(filepath, mime_type)
|
67
|
+
importer_for(filepath, mime_type).import do |records|
|
68
|
+
import = TranslationImportCollection.new(
|
69
|
+
translation_set,
|
70
|
+
records,
|
71
|
+
form.current_organization.available_locales
|
72
|
+
)
|
73
|
+
|
74
|
+
return translation_set.translations.create(import.import_attributes)
|
75
|
+
end
|
76
|
+
|
77
|
+
nil
|
78
|
+
end
|
79
|
+
|
80
|
+
# Private: Parses through the provided zip file and searches for the
|
81
|
+
# first file with one of the supported import formats. Once found,
|
82
|
+
# creates an extracted temp file of that file and passes that back to
|
83
|
+
# the import method for the final import to be executed on.
|
84
|
+
#
|
85
|
+
# If no supported import file is found at the root of the zip archive,
|
86
|
+
# nothing will be done and false will be returned.
|
87
|
+
#
|
88
|
+
# filepath - A filepath with the zip file containing the actual import
|
89
|
+
# file.
|
90
|
+
#
|
91
|
+
# Returns Array or nil. The returned value is an array of the imported
|
92
|
+
# translations when the import is successful, otherwise nil.
|
93
|
+
def import_zip(filepath)
|
94
|
+
Zip::File.open(filepath) do |zip_file|
|
95
|
+
zip_file.each do |entry|
|
96
|
+
next unless entry.file?
|
97
|
+
|
98
|
+
ext = File.extname(entry.name)[1..-1]
|
99
|
+
mime_type = TranslationsImportForm::ACCEPTED_MIME_TYPES[ext.to_sym]
|
100
|
+
next if mime_type.nil?
|
101
|
+
|
102
|
+
collection = nil
|
103
|
+
|
104
|
+
file = Tempfile.new("translations_import.#{ext}")
|
105
|
+
begin
|
106
|
+
content = entry.get_input_stream.read.force_encoding("UTF-8")
|
107
|
+
file.write(content)
|
108
|
+
file.close
|
109
|
+
|
110
|
+
collection = import_file(file.path, mime_type)
|
111
|
+
ensure
|
112
|
+
file.unlink
|
113
|
+
end
|
114
|
+
|
115
|
+
return collection
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# Private: Creates a new imported for the provided file with the given
|
123
|
+
# mime type.
|
124
|
+
#
|
125
|
+
# filepath - A filepath with the data to be imported.
|
126
|
+
# mime_type - The mime type of the provided file.
|
127
|
+
#
|
128
|
+
# Returns Decidim::TermCustomizer::Import::Importer.
|
129
|
+
def importer_for(filepath, mime_type)
|
130
|
+
Import::ImporterFactory.build(
|
131
|
+
filepath,
|
132
|
+
mime_type,
|
133
|
+
TranslationParser
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -75,6 +75,17 @@ module Decidim
|
|
75
75
|
redirect_to translation_sets_path
|
76
76
|
end
|
77
77
|
|
78
|
+
def export
|
79
|
+
enforce_permission_to :export, :translation_set, translation_set: set
|
80
|
+
name = "set-translations"
|
81
|
+
|
82
|
+
ExportJob.perform_later(current_user, set, name, params[:format] || "json")
|
83
|
+
|
84
|
+
flash[:notice] = I18n.t("exports.notice", scope: "decidim.admin")
|
85
|
+
|
86
|
+
redirect_to translation_set_translations_path(set)
|
87
|
+
end
|
88
|
+
|
78
89
|
private
|
79
90
|
|
80
91
|
def sets
|
@@ -79,6 +79,29 @@ module Decidim
|
|
79
79
|
redirect_to translation_set_translations_path(set)
|
80
80
|
end
|
81
81
|
|
82
|
+
def new_import
|
83
|
+
enforce_permission_to :import, :translation_set, translation_set: set
|
84
|
+
|
85
|
+
@import = form(Admin::TranslationsImportForm).instance
|
86
|
+
end
|
87
|
+
|
88
|
+
def import
|
89
|
+
enforce_permission_to :import, :translation_set, translation_set: set
|
90
|
+
|
91
|
+
@import = form(Admin::TranslationsImportForm).from_params(params)
|
92
|
+
ImportSetTranslations.call(@import, set) do
|
93
|
+
on(:ok) do
|
94
|
+
flash[:notice] = I18n.t("translations.import.success", scope: "decidim.term_customizer.admin")
|
95
|
+
redirect_to translation_set_translations_path(set)
|
96
|
+
end
|
97
|
+
|
98
|
+
on(:invalid) do
|
99
|
+
flash.now[:alert] = I18n.t("translations.import.error", scope: "decidim.term_customizer.admin")
|
100
|
+
render action: "new_import"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
82
105
|
private
|
83
106
|
|
84
107
|
def translation_set
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module TermCustomizer
|
5
|
+
module Admin
|
6
|
+
class TranslationsDestroysController < Admin::ApplicationController
|
7
|
+
helper_method :set
|
8
|
+
|
9
|
+
before_action :set_form
|
10
|
+
|
11
|
+
def new
|
12
|
+
enforce_permission_to :destroy, :translations, translation_set: set
|
13
|
+
end
|
14
|
+
|
15
|
+
def destroy
|
16
|
+
enforce_permission_to :destroy, :translations, translation_set: set
|
17
|
+
|
18
|
+
Admin::DestroyTranslations.call(@form) do
|
19
|
+
on(:ok) do
|
20
|
+
flash[:notice] = I18n.t("translations_destroys.destroy.success", scope: "decidim.term_customizer.admin")
|
21
|
+
redirect_to translation_set_translations_path(set)
|
22
|
+
end
|
23
|
+
|
24
|
+
on(:invalid) do
|
25
|
+
if @form.translations.count < 1
|
26
|
+
flash[:alert] = I18n.t("translations_destroys.destroy.error", scope: "decidim.term_customizer.admin")
|
27
|
+
redirect_to translation_set_translations_path(set)
|
28
|
+
else
|
29
|
+
flash.now[:alert] = I18n.t("translations_destroys.destroy.error", scope: "decidim.term_customizer.admin")
|
30
|
+
render action: "new"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def set_form
|
39
|
+
@form = form(Admin::TranslationsDestroyForm).from_params(
|
40
|
+
params
|
41
|
+
).with_context(translation_set: set)
|
42
|
+
end
|
43
|
+
|
44
|
+
def translation_set
|
45
|
+
@translation_set ||= OrganizationTranslationSets.new(
|
46
|
+
current_organization
|
47
|
+
).query.find(params[:translation_set_id])
|
48
|
+
end
|
49
|
+
|
50
|
+
alias set translation_set
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module TermCustomizer
|
5
|
+
module Admin
|
6
|
+
# A form object to be used when admin users wants to destroy multiple
|
7
|
+
# translations at once.
|
8
|
+
class TranslationsDestroyForm < Decidim::Form
|
9
|
+
mimic :translations_destroy
|
10
|
+
|
11
|
+
delegate :translation_set, to: :context, prefix: false, allow_nil: true
|
12
|
+
|
13
|
+
attribute :translation_ids, Array
|
14
|
+
validates :translation_set, :translations, presence: true
|
15
|
+
|
16
|
+
# Translations for all locales corresponding the translations passed
|
17
|
+
# to the form.
|
18
|
+
def translations
|
19
|
+
return [] unless translation_set
|
20
|
+
|
21
|
+
@translations ||= translation_set.translations.where(
|
22
|
+
key: translation_keys
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Only the translations passed with the IDs (current locale).
|
27
|
+
def translations_current
|
28
|
+
return [] unless translation_set
|
29
|
+
|
30
|
+
@translations_current ||= translation_set.translations.where(
|
31
|
+
id: translation_ids
|
32
|
+
).uniq
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Because we want to delete all locales for the translations to be
|
38
|
+
# deleted, find the corresponding keys for the translation IDs passed
|
39
|
+
# from the UI (current locale).
|
40
|
+
def translation_keys
|
41
|
+
@translation_keys ||= translations_current.map(&:key).uniq
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|