Merge pull request #2809 from wairbut-m2c/backport/translatable-content-for-admin

Basic content for I18n on Admin panel
This commit is contained in:
Raimond Garcia
2018-07-27 18:01:00 +02:00
committed by GitHub
25 changed files with 480 additions and 11 deletions

View File

@@ -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

View File

@@ -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?

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -121,6 +121,10 @@
<li <%= "class=is-active" if controller_name == "banners" %>>
<%= link_to t("admin.menu.banner"), admin_banners_path %>
</li>
<li <%= "class=is-active" if controller_name == "information_texts" %>>
<%= link_to t("admin.menu.site_customization.information_texts"), admin_site_customization_information_texts_path %>
</li>
</ul>
</li>

View File

@@ -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| %>
<b><%= content.key %></b>
<% 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 %>

View File

@@ -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 %>

View File

@@ -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 %>
<ul class="tabs" data-tabs id="globalize_locale">
<% I18n.available_locales.each do |locale| %>
<li class="tabs-title">
<%= 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 %>
</li>
<% end %>
</ul>
<div class="small-12 medium-6">
<%= select_tag :translation_locale,
options_for_locale_select,
prompt: t("admin.milestones.form.add_language"),
class: "js-globalize-locale" %>
</div>

View File

@@ -0,0 +1,7 @@
<ul id="information-texts-tabs" class="tabs" >
<% [:debates, :community, :proposals, :polls, :layouts, :mailers, :management, :guides, :welcome].each do |tab| %>
<li class="tabs-title <%= "is-active" if tab.to_s == @tab %>">
<%= link_to t("admin.menu.site_customization.information_texts_menu.#{tab}"), admin_site_customization_information_texts_path(tab: tab) %>
</li>
<% end %>
</ul>

View File

@@ -0,0 +1,10 @@
<h2><%= t("admin.menu.site_customization.information_texts") %></h2>
<div class="tabs-content" data-tabs-content="information-texts-tabs" role="tablist">
<%= render 'tabs' %>
<div class="tabs-panel is-active" role="tab">
<%= render "form", contents: [@content] %>
</div>
</div>

View File

@@ -1,9 +1,7 @@
<h2><%= t("welcome.welcome.title") %></h2>
<div class="user-permissions">
<p><%= t("welcome.welcome.user_permission_info") %></p>
<ul>
<li><span class="icon-check"></span>&nbsp;<%= t("welcome.welcome.user_permission_debates") %></li>
<li><span class="icon-check"></span>&nbsp;<%= t("welcome.welcome.user_permission_proposal") %></li>

View File

@@ -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

View File

@@ -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

View File

@@ -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."

View File

@@ -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

View File

@@ -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."

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"