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