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