decidim-term_customizer 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
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