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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d0adf060c3c243ebc5ac49b06b470a5ca036d6fe04ecee991775e5da5d464a65
4
- data.tar.gz: eb46370b1bf5186def7e3aa10b7b93c50ac5704f858b665fe4dffb62de877261
3
+ metadata.gz: 16eec8f526df47ea82ef79a644e4afd290f64e7e1f7aa2fea64723ad7a30d6b9
4
+ data.tar.gz: 93c4464c8c3e2b3437df94b25e0f457e60ad1f18240c1fbc6609421600888805
5
5
  SHA512:
6
- metadata.gz: 42bff42d6fe263926813ed5f3e56822be28d4a483784d8097572a891e9a86152ff3ae3e432f4475fab443bb4a01f6ac65d6ae33d5474f155cbc3f095604c9ea6
7
- data.tar.gz: 8e8c1cee2e2350183ee9bbf5e6f3f185f56eb5a720742526c3acd367ad10bf7d7bcfe5aa437c3aadad9d32e70cbb4ced5712cefdf00eccba68327cf988002f66
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