decidim-term_customizer 0.16.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 (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +180 -0
  4. data/Rakefile +48 -0
  5. data/app/assets/config/translation_sets_admin_manifest.js +2 -0
  6. data/app/assets/javascripts/decidim/term_customizer/admin/constraint_fields.js.es6 +45 -0
  7. data/app/assets/javascripts/decidim/term_customizer/admin/multifield/component.js.es6 +103 -0
  8. data/app/assets/javascripts/decidim/term_customizer/admin/multifield.js.es6 +5 -0
  9. data/app/assets/javascripts/decidim/term_customizer/admin/translation_sets_admin.js.es6 +19 -0
  10. data/app/assets/javascripts/decidim/term_customizer/admin/translations_admin.js.es6 +72 -0
  11. data/app/commands/decidim/term_customizer/admin/create_translation.rb +53 -0
  12. data/app/commands/decidim/term_customizer/admin/create_translation_set.rb +68 -0
  13. data/app/commands/decidim/term_customizer/admin/import_translation_keys.rb +59 -0
  14. data/app/commands/decidim/term_customizer/admin/update_translation.rb +65 -0
  15. data/app/commands/decidim/term_customizer/admin/update_translation_set.rb +61 -0
  16. data/app/controllers/decidim/term_customizer/admin/add_translations_controller.rb +72 -0
  17. data/app/controllers/decidim/term_customizer/admin/application_controller.rb +15 -0
  18. data/app/controllers/decidim/term_customizer/admin/translation_sets_controller.rb +107 -0
  19. data/app/controllers/decidim/term_customizer/admin/translations_controller.rb +102 -0
  20. data/app/forms/decidim/term_customizer/admin/translation_form.rb +39 -0
  21. data/app/forms/decidim/term_customizer/admin/translation_key_import_form.rb +15 -0
  22. data/app/forms/decidim/term_customizer/admin/translation_set_constraint_form.rb +58 -0
  23. data/app/forms/decidim/term_customizer/admin/translation_set_form.rb +18 -0
  24. data/app/forms/decidim/term_customizer/admin/translation_set_subject_component_form.rb +12 -0
  25. data/app/forms/decidim/term_customizer/admin/translation_set_subject_form.rb +61 -0
  26. data/app/helpers/decidim/term_customizer/admin/application_helper.rb +13 -0
  27. data/app/models/decidim/term_customizer/application_record.rb +10 -0
  28. data/app/models/decidim/term_customizer/constraint.rb +36 -0
  29. data/app/models/decidim/term_customizer/translation.rb +23 -0
  30. data/app/models/decidim/term_customizer/translation_set.rb +19 -0
  31. data/app/permissions/decidim/term_customizer/admin/permissions.rb +66 -0
  32. data/app/queries/decidim/term_customizer/organization_translation_sets.rb +37 -0
  33. data/app/queries/decidim/term_customizer/set_translations.rb +21 -0
  34. data/app/views/decidim/term_customizer/admin/add_translations/index.html.erb +59 -0
  35. data/app/views/decidim/term_customizer/admin/translation_sets/_constraint_fields.html.erb +58 -0
  36. data/app/views/decidim/term_customizer/admin/translation_sets/_form.html.erb +29 -0
  37. data/app/views/decidim/term_customizer/admin/translation_sets/edit.html.erb +9 -0
  38. data/app/views/decidim/term_customizer/admin/translation_sets/index.html.erb +44 -0
  39. data/app/views/decidim/term_customizer/admin/translation_sets/new.html.erb +9 -0
  40. data/app/views/decidim/term_customizer/admin/translations/_form.html.erb +15 -0
  41. data/app/views/decidim/term_customizer/admin/translations/edit.html.erb +7 -0
  42. data/app/views/decidim/term_customizer/admin/translations/index.html.erb +52 -0
  43. data/app/views/decidim/term_customizer/admin/translations/new.html.erb +7 -0
  44. data/config/locales/en.yml +65 -0
  45. data/config/locales/fi.yml +65 -0
  46. data/config/locales/sv.yml +65 -0
  47. data/lib/decidim/term_customizer/admin.rb +10 -0
  48. data/lib/decidim/term_customizer/admin_engine.rb +46 -0
  49. data/lib/decidim/term_customizer/engine.rb +35 -0
  50. data/lib/decidim/term_customizer/i18n_backend.rb +82 -0
  51. data/lib/decidim/term_customizer/resolver.rb +108 -0
  52. data/lib/decidim/term_customizer/test/factories.rb +43 -0
  53. data/lib/decidim/term_customizer/translation_directory.rb +50 -0
  54. data/lib/decidim/term_customizer/translation_store.rb +67 -0
  55. data/lib/decidim/term_customizer/version.rb +8 -0
  56. data/lib/decidim/term_customizer.rb +21 -0
  57. metadata +170 -0
@@ -0,0 +1,65 @@
1
+ sv:
2
+ decidim:
3
+ admin:
4
+ actions:
5
+ add_multiple_translations: Add multiple
6
+ back: Back
7
+ new_translation_set: New translation set
8
+ new_translation: New translation
9
+ models:
10
+ translation_sets:
11
+ fields:
12
+ name: Name
13
+ translations:
14
+ fields:
15
+ key: Key
16
+ term: Term
17
+ titles:
18
+ translation_sets: Translation sets
19
+ translations: Translations
20
+ term_customizer:
21
+ admin:
22
+ add_translations:
23
+ index:
24
+ add: Add
25
+ search: Search
26
+ translation_sets:
27
+ constraint_fields:
28
+ constraint: Constraint
29
+ remove: Remove
30
+ create:
31
+ success: Translation set successfully created.
32
+ error: Error creating translation set.
33
+ destroy:
34
+ success: Translation set successfully deleted.
35
+ edit:
36
+ title: Translation set
37
+ save: Save
38
+ form:
39
+ add_constraint: Add constraint
40
+ title: Translation set
41
+ new:
42
+ create: Create
43
+ title: Translation sets
44
+ update:
45
+ success: Translation set successfully updated.
46
+ error: Error updating translation set.
47
+ translations:
48
+ create:
49
+ success: Translation successfully created.
50
+ error: Error creating translation.
51
+ destroy:
52
+ success: Translation successfully deleted.
53
+ edit:
54
+ title: Translation
55
+ save: Save
56
+ form:
57
+ title: Translation
58
+ new:
59
+ create: Create
60
+ title: Translations
61
+ update:
62
+ success: Translation successfully updated.
63
+ error: Error updating translation.
64
+ menu:
65
+ term_customizer: Term customizer
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ # This module contains all the domain logic associated to Decidim Term
6
+ # Customizer's admin panel.
7
+ module Admin
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ class AdminEngine < ::Rails::Engine
6
+ isolate_namespace Decidim::TermCustomizer::Admin
7
+
8
+ paths["db/migrate"] = nil
9
+ paths["lib/tasks"] = nil
10
+
11
+ routes do
12
+ resources :translation_sets, path: :sets, except: [:show] do
13
+ resources :translations, except: [:show]
14
+ resources :add_translations, only: [:index, :create] do
15
+ collection do
16
+ get :search
17
+ end
18
+ end
19
+ end
20
+
21
+ root to: "translation_sets#index"
22
+ end
23
+
24
+ initializer "decidim_term_customizer.admin_mount_routes" do
25
+ Decidim::Core::Engine.routes do
26
+ mount Decidim::TermCustomizer::AdminEngine, at: "/admin/term_customizer", as: "decidim_admin_term_customizer"
27
+ end
28
+ end
29
+
30
+ initializer "decidim_term_customizer.admin_assets" do |app|
31
+ app.config.assets.precompile += %w(translation_sets_admin_manifest.js)
32
+ end
33
+
34
+ initializer "decidim_term_customizer.admin_menu" do
35
+ Decidim.menu :admin_menu do |menu|
36
+ menu.item I18n.t("menu.term_customizer", scope: "decidim.term_customizer"),
37
+ decidim_admin_term_customizer.translation_sets_path,
38
+ icon_name: "text",
39
+ position: 7.1,
40
+ active: :inclusive,
41
+ if: allowed_to?(:update, :organization, organization: current_organization)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace Decidim::TermCustomizer
7
+
8
+ initializer "decidim_term_customizer.setup" do
9
+ customizer_backend = Decidim::TermCustomizer::I18nBackend.new
10
+ I18n.backend = I18n::Backend::Chain.new(
11
+ customizer_backend,
12
+ I18n.backend
13
+ )
14
+
15
+ # Setup a controller hook to setup the term customizer before the
16
+ # request is processed and the translations are printed out. This is
17
+ # done through a notification to get access to the `current_*`
18
+ # environment variables within Decidim.
19
+ ActiveSupport::Notifications.subscribe "start_processing.action_controller" do |_name, _started, _finished, _unique_id, data|
20
+ env = data[:headers].env
21
+
22
+ # Create a new resolver instance within the current request scope
23
+ TermCustomizer.resolver = Resolver.new(
24
+ env["decidim.current_organization"],
25
+ env["decidim.current_participatory_space"],
26
+ env["decidim.current_component"]
27
+ )
28
+
29
+ # Force the backend to reload the translations for the current request
30
+ customizer_backend.reload!
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "i18n/backend/base"
4
+ require "i18n/backend/flatten"
5
+
6
+ module Decidim
7
+ module TermCustomizer
8
+ class I18nBackend
9
+ using I18n::HashRefinements
10
+
11
+ (class << self; self; end).class_eval { public :include }
12
+
13
+ module Implementation
14
+ include I18n::Backend::Base
15
+
16
+ # Get available locales from the translations hash
17
+ def available_locales
18
+ Translation.available_locales
19
+ rescue ::ActiveRecord::StatementInvalid
20
+ []
21
+ end
22
+
23
+ def initialized?
24
+ !@translations.nil?
25
+ end
26
+
27
+ # Clean up translations hash on reload!
28
+ def reload!
29
+ @translations = nil
30
+ super
31
+ end
32
+
33
+ def translations
34
+ return @translations if @translations
35
+ return {} unless TermCustomizer.resolver
36
+
37
+ @translations = {}
38
+
39
+ TermCustomizer.resolver.translations.each do |tr|
40
+ keyparts = [tr.locale] + tr.key.split(".")
41
+ lastkey = keyparts.pop.to_sym
42
+
43
+ current = @translations
44
+ keyparts.each do |key|
45
+ current[key.to_sym] ||= {}
46
+ current = current[key.to_sym]
47
+ end
48
+
49
+ current[lastkey] = tr.value
50
+ end
51
+
52
+ @translations
53
+ end
54
+
55
+ protected
56
+
57
+ # Looks up a translation from the translations hash. Returns nil if
58
+ # either key is nil, or locale, scope or key do not exist as a key in
59
+ # the nested translations hash. Splits keys or scopes containing dots
60
+ # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same
61
+ # as <tt>%w(currency format)</tt>.
62
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
63
+ keys = I18n.normalize_keys(locale, key, scope, options[:separator])
64
+
65
+ keys.inject(translations) do |result, inner_key|
66
+ return nil unless result.is_a?(Hash)
67
+
68
+ unless result.has_key?(inner_key)
69
+ inner_key = inner_key.to_s.to_sym
70
+ return nil unless result.has_key?(inner_key)
71
+ end
72
+ result = result[inner_key]
73
+ result = resolve(locale, inner_key, result, options.merge(scope: nil)) if result.is_a?(Symbol)
74
+ result
75
+ end
76
+ end
77
+ end
78
+
79
+ include Implementation
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ class Resolver
6
+ def initialize(organization, space, component)
7
+ @organization = organization
8
+ @space = space
9
+ @component = component
10
+ end
11
+
12
+ def translations
13
+ @translations ||= resolve_translations_query
14
+ end
15
+
16
+ def constraints
17
+ @constraints ||= resolve_constraints_query
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :organization, :space, :component
23
+
24
+ def resolve_translations_query
25
+ query = translations_base_query
26
+ translations_add_constraints_query(query)
27
+
28
+ query
29
+ end
30
+
31
+ def translations_base_query
32
+ # All translations without any constraints
33
+ Translation.where.not(id: Translation.joins(:constraints))
34
+ end
35
+
36
+ def translations_add_constraints_query(query)
37
+ return unless constraints
38
+
39
+ query.or!(
40
+ Translation.where(
41
+ id: Translation.joins(:constraints).where(
42
+ decidim_term_customizer_constraints: {
43
+ id: constraints
44
+ }
45
+ )
46
+ )
47
+ )
48
+ end
49
+
50
+ def resolve_constraints_query
51
+ return nil unless organization
52
+
53
+ query = constraints_base_query
54
+ constraints_add_organization_query(query)
55
+ constraints_add_space_query(query)
56
+ constraints_add_component_query(query)
57
+
58
+ query
59
+ end
60
+
61
+ def constraints_base_query
62
+ # All constraints that are NOT attached with any organization
63
+ TermCustomizer::Constraint.where(organization: nil)
64
+ end
65
+
66
+ def constraints_add_organization_query(query)
67
+ return unless organization
68
+
69
+ query.or!(
70
+ TermCustomizer::Constraint.where(
71
+ organization: organization,
72
+ subject_type: nil,
73
+ subject_id: nil
74
+ )
75
+ )
76
+ end
77
+
78
+ def constraints_add_space_query(query)
79
+ return unless space
80
+
81
+ query.or!(
82
+ TermCustomizer::Constraint.where(
83
+ organization: organization,
84
+ subject: space
85
+ )
86
+ )
87
+ query.or!(
88
+ TermCustomizer::Constraint.where(
89
+ organization: organization,
90
+ subject_type: space.class.name,
91
+ subject_id: nil
92
+ )
93
+ )
94
+ end
95
+
96
+ def constraints_add_component_query(query)
97
+ return unless component
98
+
99
+ query.or!(
100
+ TermCustomizer::Constraint.where(
101
+ organization: organization,
102
+ subject: component
103
+ )
104
+ )
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ # Create as a sequence to avoid key collisions
5
+ sequence :translation_key do |n|
6
+ "#{::Faker::Lorem.words(rand(1..3)).join(".").downcase}.tr#{n}"
7
+ end
8
+
9
+ factory :translation_set, class: Decidim::TermCustomizer::TranslationSet do
10
+ transient do
11
+ organization { nil }
12
+ end
13
+
14
+ name do
15
+ {
16
+ en: generate(:title),
17
+ fi: generate(:title),
18
+ sv: generate(:title)
19
+ }
20
+ end
21
+
22
+ after(:create) do |set, evaluator|
23
+ if evaluator.organization
24
+ set.constraints.create!(
25
+ organization: evaluator.organization
26
+ )
27
+ end
28
+ end
29
+ end
30
+
31
+ factory :translation, class: Decidim::TermCustomizer::Translation do
32
+ locale { :en }
33
+ key { generate(:translation_key) }
34
+ value { ::Faker::Lorem.words(rand(1..10)).join(" ") }
35
+ translation_set { create(:translation_set) }
36
+ end
37
+
38
+ factory :translation_set_constraint, class: Decidim::TermCustomizer::Constraint do
39
+ organization { create(:organization) }
40
+ translation_set { create(:translation_set) }
41
+ subject { create(:participatory_process, organization: organization) }
42
+ end
43
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module TermCustomizer
5
+ class TranslationDirectory
6
+ attr_reader :locale
7
+
8
+ def initialize(locale)
9
+ @locale = locale.to_sym
10
+ end
11
+
12
+ def backend
13
+ @backend ||= original_backend
14
+ end
15
+
16
+ def translations
17
+ @translations ||= TranslationStore.new(backend_translations)
18
+ end
19
+
20
+ def translations_search(search)
21
+ translations_by_key(search).merge(translations_by_term(search))
22
+ end
23
+
24
+ def translations_by_key(search)
25
+ translations.by_key(search)
26
+ end
27
+
28
+ def translations_by_term(search, case_sensitive: false)
29
+ translations.by_term(search, case_sensitive: case_sensitive)
30
+ end
31
+
32
+ private
33
+
34
+ def original_backend
35
+ if I18n.backend.class == I18n::Backend::Chain
36
+ return I18n.backend.backends.find do |be|
37
+ be.class == I18n::Backend::Simple
38
+ end
39
+ end
40
+
41
+ I18n.backend
42
+ end
43
+
44
+ def backend_translations
45
+ list = backend.translations(do_init: true)
46
+ list[locale]
47
+ end
48
+ end
49
+ end
50
+ end