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.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +180 -0
- data/Rakefile +48 -0
- data/app/assets/config/translation_sets_admin_manifest.js +2 -0
- data/app/assets/javascripts/decidim/term_customizer/admin/constraint_fields.js.es6 +45 -0
- data/app/assets/javascripts/decidim/term_customizer/admin/multifield/component.js.es6 +103 -0
- data/app/assets/javascripts/decidim/term_customizer/admin/multifield.js.es6 +5 -0
- data/app/assets/javascripts/decidim/term_customizer/admin/translation_sets_admin.js.es6 +19 -0
- data/app/assets/javascripts/decidim/term_customizer/admin/translations_admin.js.es6 +72 -0
- data/app/commands/decidim/term_customizer/admin/create_translation.rb +53 -0
- data/app/commands/decidim/term_customizer/admin/create_translation_set.rb +68 -0
- data/app/commands/decidim/term_customizer/admin/import_translation_keys.rb +59 -0
- data/app/commands/decidim/term_customizer/admin/update_translation.rb +65 -0
- data/app/commands/decidim/term_customizer/admin/update_translation_set.rb +61 -0
- data/app/controllers/decidim/term_customizer/admin/add_translations_controller.rb +72 -0
- data/app/controllers/decidim/term_customizer/admin/application_controller.rb +15 -0
- data/app/controllers/decidim/term_customizer/admin/translation_sets_controller.rb +107 -0
- data/app/controllers/decidim/term_customizer/admin/translations_controller.rb +102 -0
- data/app/forms/decidim/term_customizer/admin/translation_form.rb +39 -0
- data/app/forms/decidim/term_customizer/admin/translation_key_import_form.rb +15 -0
- data/app/forms/decidim/term_customizer/admin/translation_set_constraint_form.rb +58 -0
- data/app/forms/decidim/term_customizer/admin/translation_set_form.rb +18 -0
- data/app/forms/decidim/term_customizer/admin/translation_set_subject_component_form.rb +12 -0
- data/app/forms/decidim/term_customizer/admin/translation_set_subject_form.rb +61 -0
- data/app/helpers/decidim/term_customizer/admin/application_helper.rb +13 -0
- data/app/models/decidim/term_customizer/application_record.rb +10 -0
- data/app/models/decidim/term_customizer/constraint.rb +36 -0
- data/app/models/decidim/term_customizer/translation.rb +23 -0
- data/app/models/decidim/term_customizer/translation_set.rb +19 -0
- data/app/permissions/decidim/term_customizer/admin/permissions.rb +66 -0
- data/app/queries/decidim/term_customizer/organization_translation_sets.rb +37 -0
- data/app/queries/decidim/term_customizer/set_translations.rb +21 -0
- data/app/views/decidim/term_customizer/admin/add_translations/index.html.erb +59 -0
- data/app/views/decidim/term_customizer/admin/translation_sets/_constraint_fields.html.erb +58 -0
- data/app/views/decidim/term_customizer/admin/translation_sets/_form.html.erb +29 -0
- data/app/views/decidim/term_customizer/admin/translation_sets/edit.html.erb +9 -0
- data/app/views/decidim/term_customizer/admin/translation_sets/index.html.erb +44 -0
- data/app/views/decidim/term_customizer/admin/translation_sets/new.html.erb +9 -0
- data/app/views/decidim/term_customizer/admin/translations/_form.html.erb +15 -0
- data/app/views/decidim/term_customizer/admin/translations/edit.html.erb +7 -0
- data/app/views/decidim/term_customizer/admin/translations/index.html.erb +52 -0
- data/app/views/decidim/term_customizer/admin/translations/new.html.erb +7 -0
- data/config/locales/en.yml +65 -0
- data/config/locales/fi.yml +65 -0
- data/config/locales/sv.yml +65 -0
- data/lib/decidim/term_customizer/admin.rb +10 -0
- data/lib/decidim/term_customizer/admin_engine.rb +46 -0
- data/lib/decidim/term_customizer/engine.rb +35 -0
- data/lib/decidim/term_customizer/i18n_backend.rb +82 -0
- data/lib/decidim/term_customizer/resolver.rb +108 -0
- data/lib/decidim/term_customizer/test/factories.rb +43 -0
- data/lib/decidim/term_customizer/translation_directory.rb +50 -0
- data/lib/decidim/term_customizer/translation_store.rb +67 -0
- data/lib/decidim/term_customizer/version.rb +8 -0
- data/lib/decidim/term_customizer.rb +21 -0
- 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,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
|