diff --git a/app/controllers/admin/site_customization/information_texts_controller.rb b/app/controllers/admin/site_customization/information_texts_controller.rb
new file mode 100644
index 000000000..25594fc4a
--- /dev/null
+++ b/app/controllers/admin/site_customization/information_texts_controller.rb
@@ -0,0 +1,90 @@
+class Admin::SiteCustomization::InformationTextsController < Admin::SiteCustomization::BaseController
+ include Translatable
+
+ def index
+ fetch_existing_keys
+ append_or_create_keys
+ @content = @content[@tab.to_s]
+ end
+
+ def update
+ content_params.each do |content|
+ values = content[:values].slice(*translation_params(content[:values]))
+
+ unless values.empty?
+ values.each do |key, value|
+ locale = key.split("_").last
+
+ if value == t(content[:id], locale: locale) || value.match(/translation missing/)
+ next
+ else
+ text = I18nContent.find_or_create_by(key: content[:id])
+ Globalize.locale = locale
+ text.update(value: value)
+ end
+ end
+ end
+
+ end
+
+ redirect_to admin_site_customization_information_texts_path,
+ notice: t('flash.actions.update.translation')
+ end
+
+ private
+
+ def i18n_content_params
+ attributes = [:key, :value]
+ params.require(:information_texts).permit(*attributes, translation_params(params[:information_texts]))
+ end
+
+ def resource_model
+ I18nContent
+ end
+
+ def resource
+ resource_model.find(content_params[:id])
+ end
+
+ def content_params
+ params.require(:contents).values
+ end
+
+ def delete_translations
+ languages_to_delete = params[:delete_translations].select { |k, v| params[:delete_translations][k] == '1' }.keys
+ languages_to_delete.each do |locale|
+ I18nContentTranslation.destroy_all(locale: locale)
+ end
+ end
+
+ def fetch_existing_keys
+ @existing_keys = {}
+ @tab = params[:tab] || :debates
+
+ I18nContent.begins_with_key(@tab)
+ .all
+ .map{ |content| @existing_keys[content.key] = content }
+ end
+
+ def append_or_create_keys
+ @content = {}
+ I18n.backend.send(:init_translations) unless I18n.backend.initialized?
+
+ locale = params[:locale] || I18n.locale
+ translations = I18n.backend.send(:translations)[locale.to_sym]
+
+ translations.each do |k, v|
+ @content[k.to_s] = flat_hash(v).keys
+ .map { |s| @existing_keys["#{k.to_s}.#{s}"].nil? ?
+ I18nContent.new(key: "#{k.to_s}.#{s}") :
+ @existing_keys["#{k.to_s}.#{s}"] }
+ end
+ end
+
+ def flat_hash(h, f = nil, g = {})
+ return g.update({ f => h }) unless h.is_a? Hash
+ h.each { |k, r| flat_hash(r, [f,k].compact.join('.'), g) }
+ return g
+ end
+
+end
diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb
index 8a32e5630..772dcb9d3 100644
--- a/app/helpers/admin_helper.rb
+++ b/app/helpers/admin_helper.rb
@@ -49,7 +49,7 @@ module AdminHelper
end
def menu_customization?
- ["pages", "banners"].include?(controller_name) || menu_homepage?
+ ["pages", "banners", "information_texts"].include?(controller_name) || menu_homepage?
end
def menu_homepage?
diff --git a/app/helpers/site_customization_helper.rb b/app/helpers/site_customization_helper.rb
new file mode 100644
index 000000000..d9318fd9b
--- /dev/null
+++ b/app/helpers/site_customization_helper.rb
@@ -0,0 +1,5 @@
+module SiteCustomizationHelper
+ def site_customization_display_translation?(locale)
+ I18nContentTranslation.existing_languages.include?(neutral_locale(locale)) || locale == I18n.locale ? "" : "display: none"
+ end
+end
diff --git a/app/models/i18n_content.rb b/app/models/i18n_content.rb
new file mode 100644
index 000000000..c291e696c
--- /dev/null
+++ b/app/models/i18n_content.rb
@@ -0,0 +1,11 @@
+class I18nContent < ActiveRecord::Base
+
+ scope :by_key, -> (key){ where(key: key) }
+ scope :begins_with_key, -> (key){ where("key ILIKE ?", "#{key}?%") }
+
+ validates :key, uniqueness: true
+
+ translates :value, touch: true
+ globalize_accessors locales: [:en, :es, :fr, :nl]
+
+end
diff --git a/app/models/i18n_content_translation.rb b/app/models/i18n_content_translation.rb
new file mode 100644
index 000000000..76447d83e
--- /dev/null
+++ b/app/models/i18n_content_translation.rb
@@ -0,0 +1,5 @@
+class I18nContentTranslation < ActiveRecord::Base
+ def self.existing_languages
+ self.select(:locale).uniq.map{ |l| l.locale.to_sym }.to_a
+ end
+end
diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb
index 8385b8742..911bc0713 100644
--- a/app/views/admin/_menu.html.erb
+++ b/app/views/admin/_menu.html.erb
@@ -121,6 +121,10 @@
>
<%= link_to t("admin.menu.banner"), admin_banners_path %>
+
+ >
+ <%= link_to t("admin.menu.site_customization.information_texts"), admin_site_customization_information_texts_path %>
+
diff --git a/app/views/admin/site_customization/information_texts/_form.html.erb b/app/views/admin/site_customization/information_texts/_form.html.erb
new file mode 100644
index 000000000..861315691
--- /dev/null
+++ b/app/views/admin/site_customization/information_texts/_form.html.erb
@@ -0,0 +1,16 @@
+<%= render "globalize_locales" %>
+
+<%= form_tag admin_site_customization_information_texts_path do %>
+ <% I18n.available_locales.each do |l| %>
+ <%= hidden_field_tag "delete_translations[#{l}]", 0 %>
+ <% end %>
+ <% contents.each do |group| %>
+ <% group.each do |content| %>
+ <%= content.key %>
+ <% content.globalize_locales.each do |locale| %>
+ <%= render "form_field", content: content, locale: locale %>
+ <% end %>
+ <% end %>
+ <% end %>
+ <%= submit_tag t("admin.menu.site_customization.buttons.save"), class: "button" %>
+<% end %>
diff --git a/app/views/admin/site_customization/information_texts/_form_field.html.erb b/app/views/admin/site_customization/information_texts/_form_field.html.erb
new file mode 100644
index 000000000..54945dfb6
--- /dev/null
+++ b/app/views/admin/site_customization/information_texts/_form_field.html.erb
@@ -0,0 +1,19 @@
+<% globalize(locale) do %>
+
+ <% i18n_content = I18nContent.where(key: content.key).first %>
+ <% if i18n_content.present? %>
+ <% i18n_content_translation = I18nContentTranslation.where(i18n_content_id: i18n_content.id, locale: locale).first.try(:value) %>
+ <% else %>
+ <% i18n_content_translation = false %>
+ <% end %>
+
+ <%= hidden_field_tag "contents[content_#{content.key}][id]", content.key %>
+ <%= text_area_tag "contents[content_#{content.key}]values[value_#{locale}]",
+ i18n_content_translation ||
+ t(content.key, locale: locale),
+ { rows: 5,
+ class: "js-globalize-attribute",
+ style: display_translation?(locale),
+ data: { locale: locale }
+ } %>
+<% end %>
diff --git a/app/views/admin/site_customization/information_texts/_globalize_locales.html.erb b/app/views/admin/site_customization/information_texts/_globalize_locales.html.erb
new file mode 100644
index 000000000..9e90283cd
--- /dev/null
+++ b/app/views/admin/site_customization/information_texts/_globalize_locales.html.erb
@@ -0,0 +1,27 @@
+<% I18n.available_locales.each do |locale| %>
+ <%= link_to t("admin.milestones.form.remove_language"), "#",
+ id: "delete-#{neutral_locale(locale)}",
+ style: show_delete?(locale),
+ class: 'float-right delete js-delete-language',
+ data: { locale: neutral_locale(locale) } %>
+
+<% end %>
+
+
+ <% I18n.available_locales.each do |locale| %>
+ -
+ <%= link_to name_for_locale(locale), "#",
+ style: site_customization_display_translation?(locale),
+ class: "js-globalize-locale-link #{highlight_current?(locale)}",
+ data: { locale: neutral_locale(locale) },
+ remote: true %>
+
+ <% end %>
+
+
+
+ <%= select_tag :translation_locale,
+ options_for_locale_select,
+ prompt: t("admin.milestones.form.add_language"),
+ class: "js-globalize-locale" %>
+
diff --git a/app/views/admin/site_customization/information_texts/_tabs.html.erb b/app/views/admin/site_customization/information_texts/_tabs.html.erb
new file mode 100644
index 000000000..ca5061bc3
--- /dev/null
+++ b/app/views/admin/site_customization/information_texts/_tabs.html.erb
@@ -0,0 +1,7 @@
+
+ <% [:debates, :community, :proposals, :polls, :layouts, :mailers, :management, :guides, :welcome].each do |tab| %>
+ - ">
+ <%= link_to t("admin.menu.site_customization.information_texts_menu.#{tab}"), admin_site_customization_information_texts_path(tab: tab) %>
+
+ <% end %>
+
diff --git a/app/views/admin/site_customization/information_texts/index.html.erb b/app/views/admin/site_customization/information_texts/index.html.erb
new file mode 100644
index 000000000..f41102d97
--- /dev/null
+++ b/app/views/admin/site_customization/information_texts/index.html.erb
@@ -0,0 +1,10 @@
+<%= t("admin.menu.site_customization.information_texts") %>
+
+
+ <%= render 'tabs' %>
+
+
+ <%= render "form", contents: [@content] %>
+
+
+
diff --git a/app/views/welcome/welcome.html.erb b/app/views/welcome/welcome.html.erb
index ec51e225f..d647ba8e6 100644
--- a/app/views/welcome/welcome.html.erb
+++ b/app/views/welcome/welcome.html.erb
@@ -1,9 +1,7 @@
<%= t("welcome.welcome.title") %>
-
<%= t("welcome.welcome.user_permission_info") %>
-
- <%= t("welcome.welcome.user_permission_debates") %>
- <%= t("welcome.welcome.user_permission_proposal") %>
diff --git a/config/initializers/i18n_translation.rb b/config/initializers/i18n_translation.rb
new file mode 100644
index 000000000..c01199a08
--- /dev/null
+++ b/config/initializers/i18n_translation.rb
@@ -0,0 +1,23 @@
+require 'i18n/exceptions'
+require 'action_view/helpers/tag_helper'
+
+module ActionView
+ module Helpers
+ module TranslationHelper
+ include TagHelper
+
+ def t(key, options = {})
+ current_locale = options[:locale].present? ? options[:locale] : I18n.locale
+
+ i18_content = I18nContent.by_key(key).first
+ translation = I18nContentTranslation.where(i18n_content_id: i18_content&.id,
+ locale: current_locale).first&.value
+ if translation.present?
+ translation
+ else
+ translate(key, options)
+ end
+ end
+ end
+ end
+end
diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml
index f2154da67..a1f692fc2 100644
--- a/config/locales/en/admin.yml
+++ b/config/locales/en/admin.yml
@@ -548,9 +548,22 @@ en:
signature_sheets: Signature Sheets
site_customization:
homepage: Homepage
- pages: Custom Pages
- images: Custom Images
+ pages: Custom pages
+ images: Custom images
content_blocks: Custom content blocks
+ information_texts: Custom information texts
+ information_texts_menu:
+ debates: "Debates"
+ community: "Community"
+ proposals: "Proposals"
+ polls: "Polls"
+ layouts: "Layouts"
+ mailers: "Emails"
+ management: "Management"
+ guides: "Guides"
+ welcome: "Welcome"
+ buttons:
+ save: "Save"
title_moderated_content: Moderated content
title_budgets: Budgets
title_polls: Polls
diff --git a/config/locales/en/responders.yml b/config/locales/en/responders.yml
index f91cb807c..e11b071f2 100644
--- a/config/locales/en/responders.yml
+++ b/config/locales/en/responders.yml
@@ -29,6 +29,7 @@ en:
budget_investment: "Investment project updated succesfully."
topic: "Topic updated successfully."
valuator_group: "Valuator group updated successfully"
+ translation: "Translation updated successfully"
destroy:
spending_proposal: "Spending proposal deleted succesfully."
budget_investment: "Investment project deleted succesfully."
diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml
index 0365f9b57..22a328823 100644
--- a/config/locales/es/admin.yml
+++ b/config/locales/es/admin.yml
@@ -552,6 +552,19 @@ es:
pages: Personalizar páginas
images: Personalizar imágenes
content_blocks: Personalizar bloques
+ information_texts: Personalizar textos
+ information_texts_menu:
+ debates: "Debates"
+ community: "Comunidad"
+ proposals: "Propuestas"
+ polls: "Votaciones"
+ layouts: "Plantillas"
+ mailers: "Correos"
+ management: "Gestión"
+ guides: "Guías"
+ welcome: "Bienvenido/a"
+ buttons:
+ save: "Guardar cambios"
title_moderated_content: Contenido moderado
title_budgets: Presupuestos
title_polls: Votaciones
diff --git a/config/locales/es/responders.yml b/config/locales/es/responders.yml
index 3674a0290..1910acd95 100644
--- a/config/locales/es/responders.yml
+++ b/config/locales/es/responders.yml
@@ -29,6 +29,7 @@ es:
budget_investment: "Proyecto de gasto actualizado correctamente"
topic: "Tema actualizado correctamente."
valuator_group: "Grupo de evaluadores actualizado correctamente"
+ translation: "Traducción actualizada correctamente"
destroy:
spending_proposal: "Propuesta de inversión eliminada."
budget_investment: "Proyecto de gasto eliminado."
diff --git a/config/locales/fr/admin.yml b/config/locales/fr/admin.yml
index 7c2d63e03..0b1f7acc3 100644
--- a/config/locales/fr/admin.yml
+++ b/config/locales/fr/admin.yml
@@ -225,6 +225,19 @@ fr:
pages: Pages personnalisées
images: Images personnalisées
content_blocks: Blocs de contenu personnalisés
+ information_texts: "Textes d'information personnalisés"
+ information_texts_menu:
+ debates: "Débats"
+ community: "Communauté"
+ proposals: "Propositions"
+ polls: "Votes"
+ layouts: "Dessins"
+ mailers: "Courriels"
+ management: "Gestion"
+ guides: "Guides"
+ welcome: "Bienvenue"
+ buttons:
+ save: "Enregistrer"
title_categories: Catégories
title_moderated_content: Contenu modéré
title_budgets: Budgets
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index ee6c0f050..335d389d0 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -209,6 +209,9 @@ namespace :admin do
resources :pages, except: [:show]
resources :images, only: [:index, :update, :destroy]
resources :content_blocks, except: [:show]
+ resources :information_texts, only: [:index] do
+ post :update, on: :collection
+ end
end
resource :homepage, controller: :homepage, only: [:show]
diff --git a/db/migrate/20180718115545_create_i18n_content_translations.rb b/db/migrate/20180718115545_create_i18n_content_translations.rb
new file mode 100644
index 000000000..e472f0622
--- /dev/null
+++ b/db/migrate/20180718115545_create_i18n_content_translations.rb
@@ -0,0 +1,17 @@
+class CreateI18nContentTranslations < ActiveRecord::Migration
+ def change
+ create_table :i18n_contents do |t|
+ t.string :key
+ end
+
+ reversible do |dir|
+ dir.up do
+ I18nContent.create_translation_table! :value => :text
+ end
+
+ dir.down do
+ I18nContent.drop_translation_table!
+ end
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index b7ed9d5c3..9b5c93c8e 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180711224810) do
+ActiveRecord::Schema.define(version: 20180718115545) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -453,6 +453,21 @@ ActiveRecord::Schema.define(version: 20180711224810) do
add_index "geozones_polls", ["geozone_id"], name: "index_geozones_polls_on_geozone_id", using: :btree
add_index "geozones_polls", ["poll_id"], name: "index_geozones_polls_on_poll_id", using: :btree
+ create_table "i18n_content_translations", force: :cascade do |t|
+ t.integer "i18n_content_id", null: false
+ t.string "locale", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.text "value"
+ end
+
+ add_index "i18n_content_translations", ["i18n_content_id"], name: "index_i18n_content_translations_on_i18n_content_id", using: :btree
+ add_index "i18n_content_translations", ["locale"], name: "index_i18n_content_translations_on_locale", using: :btree
+
+ create_table "i18n_contents", force: :cascade do |t|
+ t.string "key"
+ end
+
create_table "identities", force: :cascade do |t|
t.integer "user_id"
t.string "provider"
diff --git a/spec/factories.rb b/spec/factories.rb
index eadd26a9d..dbf6f8b34 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -1056,4 +1056,10 @@ LOREM_IPSUM
factory :widget_feed, class: 'Widget::Feed' do
end
+ factory :i18n_content, class: 'I18nContent' do
+ key 'debates.index.section_footer.description'
+ value_es 'Texto en español'
+ value_en 'Text in english'
+ end
+
end
diff --git a/spec/features/admin/site_customization/images_spec.rb b/spec/features/admin/site_customization/images_spec.rb
index cd8c0c065..7e0f24f01 100644
--- a/spec/features/admin/site_customization/images_spec.rb
+++ b/spec/features/admin/site_customization/images_spec.rb
@@ -11,7 +11,7 @@ feature "Admin custom images" do
visit admin_root_path
within("#side_menu") do
- click_link "Custom Images"
+ click_link "Custom images"
end
within("tr.logo_header") do
@@ -27,7 +27,7 @@ feature "Admin custom images" do
visit admin_root_path
within("#side_menu") do
- click_link "Custom Images"
+ click_link "Custom images"
end
within("tr.icon_home") do
@@ -43,7 +43,7 @@ feature "Admin custom images" do
visit admin_root_path
within("#side_menu") do
- click_link "Custom Images"
+ click_link "Custom images"
end
within("tr.social_media_icon") do
diff --git a/spec/features/admin/site_customization/information_texts_spec.rb b/spec/features/admin/site_customization/information_texts_spec.rb
new file mode 100644
index 000000000..b5ed5496b
--- /dev/null
+++ b/spec/features/admin/site_customization/information_texts_spec.rb
@@ -0,0 +1,172 @@
+require 'rails_helper'
+
+feature "Admin custom information texts" do
+
+ background do
+ admin = create(:administrator)
+ login_as(admin.user)
+ end
+
+ scenario 'page is correctly loaded' do
+ visit admin_site_customization_information_texts_path
+
+ click_link 'Debates'
+ expect(page).to have_content 'Help about debates'
+
+ click_link 'Community'
+ expect(page).to have_content 'Access the community'
+
+ click_link 'Proposals'
+ expect(page).to have_content 'Create proposal'
+
+ within "#information-texts-tabs" do
+ click_link "Polls"
+ end
+
+ expect(page).to have_content 'Results'
+
+ click_link 'Layouts'
+ expect(page).to have_content 'Accessibility'
+
+ click_link 'Emails'
+ expect(page).to have_content 'Confirm your email'
+
+ within "#information-texts-tabs" do
+ click_link "Management"
+ end
+
+ expect(page).to have_content 'This user account is already verified.'
+
+ click_link 'Guides'
+ expect(page).to have_content 'Choose what you want to create'
+
+ click_link 'Welcome'
+ expect(page).to have_content 'See all debates'
+ end
+
+ context "Globalization" do
+
+ scenario "Add a translation", :js do
+ key = "debates.form.debate_title"
+
+ visit admin_site_customization_information_texts_path
+
+ select "Français", from: "translation_locale"
+ fill_in "contents_content_#{key}values_value_fr", with: 'Titre personalise du débat'
+
+ click_button "Save"
+
+ expect(page).to have_content 'Translation updated successfully'
+
+ select "Français", from: "translation_locale"
+
+ expect(page).to have_content 'Titre personalise du débat'
+ expect(page).not_to have_content 'Titre du débat'
+ end
+
+ scenario "Update a translation", :js do
+ key = "debates.form.debate_title"
+ content = create(:i18n_content, key: key, value_fr: 'Titre personalise du débat')
+
+ visit admin_site_customization_information_texts_path
+
+ select "Français", from: "translation_locale"
+ fill_in "contents_content_#{key}values_value_fr", with: 'Titre personalise again du débat'
+
+ click_button 'Save'
+ expect(page).to have_content 'Translation updated successfully'
+
+ click_link 'Français'
+
+ expect(page).to have_content 'Titre personalise again du débat'
+ expect(page).not_to have_content 'Titre personalise du débat'
+ end
+
+ scenario "Remove a translation", :js do
+ first_key = "debates.form.debate_title"
+ debate_title = create(:i18n_content, key: first_key,
+ value_en: 'Custom debate title',
+ value_es: 'Título personalizado de debate')
+
+ second_key = "debates.form.debate_text"
+ debate_text = create(:i18n_content, key: second_key,
+ value_en: 'Custom debate text',
+ value_es: 'Texto personalizado de debate')
+
+ visit admin_site_customization_information_texts_path
+
+ click_link "Español"
+ click_link "Remove language"
+ click_button "Save"
+
+ expect(page).not_to have_link "Español"
+
+ click_link 'English'
+ expect(page).to have_content 'Custom debate text'
+ expect(page).to have_content 'Custom debate title'
+
+ debate_title.reload
+ debate_text.reload
+
+ expect(debate_text.value_es).to be(nil)
+ expect(debate_title.value_es).to be(nil)
+ expect(debate_text.value_en).to eq('Custom debate text')
+ expect(debate_title.value_en).to eq('Custom debate title')
+ end
+
+ context "Javascript interface" do
+
+ scenario "Highlight current locale", :js do
+ visit admin_site_customization_information_texts_path
+
+ expect(find("a.js-globalize-locale-link.is-active")).to have_content "English"
+
+ select('Español', from: 'locale-switcher')
+
+ expect(find("a.js-globalize-locale-link.is-active")).to have_content "Español"
+ end
+
+ scenario "Highlight selected locale", :js do
+ key = "debates.form.debate_title"
+ content = create(:i18n_content, key: key, value_es: 'Título')
+
+ visit admin_site_customization_information_texts_path
+
+ expect(find("a.js-globalize-locale-link.is-active")).to have_content "English"
+
+ click_link "Español"
+
+ expect(find("a.js-globalize-locale-link.is-active")).to have_content "Español"
+ end
+
+ scenario "Show selected locale form", :js do
+ key = "debates.form.debate_title"
+ content = create(:i18n_content, key: key,
+ value_en: 'Title',
+ value_es: 'Título')
+
+ visit admin_site_customization_information_texts_path
+
+ expect(page).to have_field("contents_content_#{key}values_value_en", with: 'Title')
+
+ click_link "Español"
+
+ expect(page).to have_field("contents_content_#{key}values_value_es", with: 'Título')
+ end
+
+ scenario "Select a locale and add it to the form", :js do
+ key = "debates.form.debate_title"
+
+ visit admin_site_customization_information_texts_path
+ select "Français", from: "translation_locale"
+
+ expect(page).to have_link "Français"
+
+ click_link "Français"
+ expect(page).to have_field("contents_content_#{key}values_value_fr")
+ end
+ end
+
+ end
+
+end
diff --git a/spec/features/admin/site_customization/pages_spec.rb b/spec/features/admin/site_customization/pages_spec.rb
index d4873be9d..7b586c042 100644
--- a/spec/features/admin/site_customization/pages_spec.rb
+++ b/spec/features/admin/site_customization/pages_spec.rb
@@ -19,7 +19,7 @@ feature "Admin custom pages" do
visit admin_root_path
within("#side_menu") do
- click_link "Custom Pages"
+ click_link "Custom pages"
end
expect(page).not_to have_content "An example custom page"
@@ -44,7 +44,7 @@ feature "Admin custom pages" do
visit admin_root_path
within("#side_menu") do
- click_link "Custom Pages"
+ click_link "Custom pages"
end
click_link "An example custom page"