diff --git a/Gemfile b/Gemfile
index 8c3144058..f7f9c46ad 100644
--- a/Gemfile
+++ b/Gemfile
@@ -51,6 +51,8 @@ gem 'turnout', '~> 2.4.0'
gem 'uglifier', '~> 4.1.2'
gem 'unicorn', '~> 5.4.0'
gem 'whenever', '~> 0.10.0', require: false
+gem 'globalize', '~> 5.0.0'
+gem 'globalize-accessors', '~> 0.2.1'
source 'https://rails-assets.org' do
gem 'rails-assets-leaflet'
diff --git a/Gemfile.lock b/Gemfile.lock
index 6c35b0776..aa4a00eab 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -176,6 +176,11 @@ GEM
geocoder (1.4.4)
globalid (0.4.1)
activesupport (>= 4.2.0)
+ globalize (5.0.1)
+ activemodel (>= 4.2.0, < 4.3)
+ activerecord (>= 4.2.0, < 4.3)
+ globalize-accessors (0.2.1)
+ globalize (~> 5.0, >= 5.0.0)
graphiql-rails (1.4.8)
rails
graphql (1.7.8)
@@ -518,6 +523,8 @@ DEPENDENCIES
faker (~> 1.8.7)
foundation-rails (~> 6.2.4.0)
foundation_rails_helper (~> 2.0.0)
+ globalize (~> 5.0.0)
+ globalize-accessors (~> 0.2.1)
graphiql-rails (~> 1.4.1)
graphql (~> 1.7.8)
groupdate (~> 3.2.0)
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index 7a110aa18..1c97069b0 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -77,6 +77,7 @@
//= require investment_report_alert
//= require send_newsletter_alert
//= require managers
+//= require globalize
var initialize_modules = function() {
App.Comments.initialize();
@@ -121,6 +122,7 @@ var initialize_modules = function() {
App.InvestmentReportAlert.initialize();
App.SendNewsletterAlert.initialize();
App.Managers.initialize();
+ App.Globalize.initialize();
};
$(function(){
diff --git a/app/assets/javascripts/globalize.js.coffee b/app/assets/javascripts/globalize.js.coffee
new file mode 100644
index 000000000..0de799318
--- /dev/null
+++ b/app/assets/javascripts/globalize.js.coffee
@@ -0,0 +1,46 @@
+App.Globalize =
+
+ display_locale: (locale) ->
+ $(".js-globalize-locale-link").each ->
+ if $(this).data("locale") == locale
+ $(this).show()
+ App.Globalize.highlight_locale($(this))
+ $(".js-globalize-locale option:selected").removeAttr("selected");
+ return
+
+ display_translations: (locale) ->
+ $(".js-globalize-attribute").each ->
+ if $(this).data("locale") == locale
+ $(this).show()
+ else
+ $(this).hide()
+ $('.js-delete-language').hide()
+ $('#delete-' + locale).show()
+
+ highlight_locale: (element) ->
+ $('.js-globalize-locale-link').removeClass('is-active');
+ element.addClass('is-active');
+
+ remove_language: (locale) ->
+ $(".js-globalize-attribute[data-locale=" + locale + "]").val('').hide()
+ $(".js-globalize-locale-link[data-locale=" + locale + "]").hide()
+ next = $(".js-globalize-locale-link:visible").first()
+ App.Globalize.highlight_locale(next)
+ App.Globalize.display_translations(next.data("locale"))
+ $("#delete_translations_" + locale).val(1)
+
+ initialize: ->
+ $('.js-globalize-locale').on 'change', ->
+ App.Globalize.display_translations($(this).val())
+ App.Globalize.display_locale($(this).val())
+
+ $('.js-globalize-locale-link').on 'click', ->
+ locale = $(this).data("locale")
+ App.Globalize.display_translations(locale)
+ App.Globalize.highlight_locale($(this))
+
+ $('.js-delete-language').on 'click', ->
+ locale = $(this).data("locale")
+ $(this).hide()
+ App.Globalize.remove_language(locale)
+
diff --git a/app/controllers/admin/budget_investment_milestones_controller.rb b/app/controllers/admin/budget_investment_milestones_controller.rb
index e4aebd3cf..d2fdf5700 100644
--- a/app/controllers/admin/budget_investment_milestones_controller.rb
+++ b/app/controllers/admin/budget_investment_milestones_controller.rb
@@ -1,4 +1,5 @@
class Admin::BudgetInvestmentMilestonesController < Admin::BaseController
+ include Translatable
before_action :load_budget_investment, only: [:index, :new, :create, :edit, :update, :destroy]
before_action :load_budget_investment_milestone, only: [:edit, :update, :destroy]
@@ -46,7 +47,8 @@ class Admin::BudgetInvestmentMilestonesController < Admin::BaseController
documents_attributes = [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy]
attributes = [:title, :description, :publication_date, :budget_investment_id,
image_attributes: image_attributes, documents_attributes: documents_attributes]
- params.require(:budget_investment_milestone).permit(*attributes)
+
+ params.require(:budget_investment_milestone).permit(*attributes, translation_params(params[:budget_investment_milestone]))
end
def load_budget_investment
@@ -54,7 +56,19 @@ class Admin::BudgetInvestmentMilestonesController < Admin::BaseController
end
def load_budget_investment_milestone
- @milestone = Budget::Investment::Milestone.find(params[:id])
+ @milestone = get_milestone
+ end
+
+ def get_milestone
+ Budget::Investment::Milestone.find(params[:id])
+ end
+
+ def resource_model
+ Budget::Investment::Milestone
+ end
+
+ def resource
+ get_milestone
end
end
diff --git a/app/controllers/concerns/translatable.rb b/app/controllers/concerns/translatable.rb
new file mode 100644
index 000000000..4b4b14d87
--- /dev/null
+++ b/app/controllers/concerns/translatable.rb
@@ -0,0 +1,29 @@
+module Translatable
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :set_translation_locale
+ before_action :delete_translations, only: [:update]
+ end
+
+ private
+
+ def translation_params(params)
+ resource_model.globalize_attribute_names.select { |k, v| params.include?(k.to_sym) && params[k].present? }
+ end
+
+ def set_translation_locale
+ Globalize.locale = I18n.locale
+ end
+
+ def delete_translations
+ locales = resource_model.globalize_locales.
+ select { |k, v| params[:delete_translations].include?(k.to_sym) && params[:delete_translations][k] == "1" }
+ locales.each do |l|
+ Globalize.with_locale(l) do
+ resource.translation.destroy
+ end
+ end
+ end
+
+end
diff --git a/app/helpers/globalize_helper.rb b/app/helpers/globalize_helper.rb
new file mode 100644
index 000000000..b64c1ff09
--- /dev/null
+++ b/app/helpers/globalize_helper.rb
@@ -0,0 +1,43 @@
+module GlobalizeHelper
+
+ def options_for_locale_select
+ options_for_select(locale_options, nil)
+ end
+
+ def locale_options
+ I18n.available_locales.map do |locale|
+ [name_for_locale(locale), neutral_locale(locale)]
+ end
+ end
+
+ def display_translation?(locale)
+ same_locale?(neutral_locale(I18n.locale), neutral_locale(locale)) ? "" : "display: none"
+ end
+
+ def css_to_display_translation?(resource, locale)
+ resource.translated_locales.include?(neutral_locale(locale)) || locale == I18n.locale ? "" : "display: none"
+ end
+
+ def highlight_current?(locale)
+ same_locale?(I18n.locale, locale) ? 'is-active' : ''
+ end
+
+ def show_delete?(locale)
+ display_translation?(locale)
+ end
+
+ def neutral_locale(locale)
+ locale.to_s.downcase.underscore.to_sym
+ end
+
+ def globalize(locale, &block)
+ Globalize.with_locale(locale) do
+ yield
+ end
+ end
+
+ def same_locale?(locale1, locale2)
+ locale1 == locale2
+ end
+
+end
diff --git a/app/models/budget/investment/milestone.rb b/app/models/budget/investment/milestone.rb
index 30acb5152..3f8ba4f3a 100644
--- a/app/models/budget/investment/milestone.rb
+++ b/app/models/budget/investment/milestone.rb
@@ -7,6 +7,9 @@ class Budget
max_file_size: 3.megabytes,
accepted_content_types: [ "application/pdf" ]
+ translates :title, :description, touch: true
+ globalize_accessors locales: [:en, :es, :fr, :nl, :val, :pt_br]
+
belongs_to :investment
validates :title, presence: true
diff --git a/app/views/admin/budget_investment_milestones/_form.html.erb b/app/views/admin/budget_investment_milestones/_form.html.erb
index 2b2af4b50..6643c7317 100644
--- a/app/views/admin/budget_investment_milestones/_form.html.erb
+++ b/app/views/admin/budget_investment_milestones/_form.html.erb
@@ -1,7 +1,22 @@
+<%= render "globalize_locales" %>
+
<%= form_for [:admin, @investment.budget, @investment, @milestone] do |f| %>
- <%= f.hidden_field :title, value: l(Time.current, format: :datetime), maxlength: Budget::Investment::Milestone.title_max_length %>
- <%= f.text_area :description, rows: 5 %>
+ <%= f.hidden_field :title, value: l(Time.current, format: :datetime),
+ maxlength: Budget::Investment::Milestone.title_max_length %>
+
+ <%= f.label :description, t("admin.milestones.new.description") %>
+ <% @milestone.globalize_locales.each do |locale| %>
+ <%= hidden_field_tag "delete_translations[#{locale}]", 0 %>
+ <% globalize(locale) do %>
+ <%= f.text_area "description_#{locale}", rows: 5,
+ class: "js-globalize-attribute",
+ data: { locale: locale },
+ style: display_translation?(locale),
+ label: false %>
+ <% end %>
+ <% end %>
+
<%= f.label :publication_date, t("admin.milestones.new.date") %>
<%= f.text_field :publication_date,
value: @milestone.publication_date.present? ? l(@milestone.publication_date.to_date) : nil,
diff --git a/app/views/admin/budget_investment_milestones/_globalize_locales.html.erb b/app/views/admin/budget_investment_milestones/_globalize_locales.html.erb
new file mode 100644
index 000000000..b4b789ad2
--- /dev/null
+++ b/app/views/admin/budget_investment_milestones/_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: css_to_display_translation?(@milestone, 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/budgets/investments/_milestones.html.erb b/app/views/budgets/investments/_milestones.html.erb
index 5fc984808..60b52f46e 100644
--- a/app/views/budgets/investments/_milestones.html.erb
+++ b/app/views/budgets/investments/_milestones.html.erb
@@ -9,37 +9,7 @@
<% @investment.milestones.order_by_publication_date.each do |milestone| %>
- -
-
- <% if milestone.publication_date.present? %>
-
-
- <%= t("budgets.investments.show.milestone_publication_date",
- publication_date: l(milestone.publication_date.to_date)) %>
-
-
- <% end %>
- <%= image_tag(milestone.image_url(:large),
- { alt: milestone.image.title.unicode_normalize,
- class: "margin",
- id: "image_#{milestone.id}" }) if milestone.image.present? %>
-
<%= text_with_links milestone.description %>
- <% if milestone.documents.present? %>
-
-
-
- <%= t("shared.documentation") %>
-
- <% milestone.documents.each do |document| %>
- <%= link_to document.title,
- document.attachment.url,
- target: "_blank",
- rel: "nofollow" %>
- <% end %>
-
- <% end %>
-
-
+ <%= render 'budgets/investments/milestones/milestone', milestone: milestone %>
<% end %>
diff --git a/app/views/budgets/investments/milestones/_milestone.html.erb b/app/views/budgets/investments/milestones/_milestone.html.erb
new file mode 100644
index 000000000..774819d0c
--- /dev/null
+++ b/app/views/budgets/investments/milestones/_milestone.html.erb
@@ -0,0 +1,37 @@
+
+
+
+ <% if milestone.publication_date.present? %>
+
+
+ <%= t("budgets.investments.show.milestone_publication_date",
+ publication_date: l(milestone.publication_date.to_date)) %>
+
+
+ <% end %>
+
+ <%= image_tag(milestone.image_url(:large), { id: "image_#{milestone.id}", alt: milestone.image.title, class: "margin" }) if milestone.image.present? %>
+
+ <% globalize(neutral_locale(locale)) do %>
+
+ <%= text_with_links milestone.description %>
+
+ <% end %>
+
+ <% if milestone.documents.present? %>
+
+
+
+ <%= t("shared.documentation") %>
+
+ <% milestone.documents.each do |document| %>
+ <%= link_to document.title,
+ document.attachment.url,
+ target: "_blank",
+ rel: "nofollow" %>
+ <% end %>
+
+ <% end %>
+
+
+
diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml
index c477fb875..0292df944 100644
--- a/config/locales/en/admin.yml
+++ b/config/locales/en/admin.yml
@@ -254,9 +254,13 @@ en:
image: "Image"
show_image: "Show image"
documents: "Documents"
+ form:
+ add_language: Add language
+ remove_language: Remove language
new:
creating: Create milestone
date: Date
+ description: Description
edit:
title: Edit milestone
create:
diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml
index 496596ee3..5d44c9a17 100644
--- a/config/locales/es/admin.yml
+++ b/config/locales/es/admin.yml
@@ -254,9 +254,13 @@ es:
image: "Imagen"
show_image: "Mostrar imagen"
documents: "Documentos"
+ form:
+ add_language: Añadir idioma
+ remove_language: Eliminar idioma
new:
creating: Crear hito
date: Fecha
+ description: Descripción
edit:
title: Editar hito
create:
diff --git a/db/dev_seeds/budgets.rb b/db/dev_seeds/budgets.rb
index 4740ed407..5d3294b46 100644
--- a/db/dev_seeds/budgets.rb
+++ b/db/dev_seeds/budgets.rb
@@ -109,3 +109,17 @@ section "Creating Valuation Assignments" do
Budget::Investment.all.sample.valuators << Valuator.first
end
end
+
+section "Creating investment milestones" do
+ Budget::Investment.all.each do |investment|
+ milestone = Budget::Investment::Milestone.new(investment_id: investment.id, publication_date: Date.tomorrow)
+ I18n.available_locales.map do |locale|
+ neutral_locale = locale.to_s.downcase.underscore.to_sym
+ Globalize.with_locale(neutral_locale) do
+ milestone.description = "Description for locale #{locale}"
+ milestone.title = I18n.l(Time.current, format: :datetime)
+ milestone.save!
+ end
+ end
+ end
+end
diff --git a/db/migrate/20180323190027_add_translate_milestones.rb b/db/migrate/20180323190027_add_translate_milestones.rb
new file mode 100644
index 000000000..6e27583a8
--- /dev/null
+++ b/db/migrate/20180323190027_add_translate_milestones.rb
@@ -0,0 +1,12 @@
+class AddTranslateMilestones < ActiveRecord::Migration
+ def self.up
+ Budget::Investment::Milestone.create_translation_table!({
+ title: :string,
+ description: :text
+ })
+ end
+
+ def self.down
+ Budget::Investment::Milestone.drop_translation_table!
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index c1ee88246..860378811 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: 20180320104823) do
+ActiveRecord::Schema.define(version: 20180323190027) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -118,6 +118,18 @@ ActiveRecord::Schema.define(version: 20180320104823) do
add_index "budget_headings", ["group_id"], name: "index_budget_headings_on_group_id", using: :btree
+ create_table "budget_investment_milestone_translations", force: :cascade do |t|
+ t.integer "budget_investment_milestone_id", null: false
+ t.string "locale", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "title"
+ t.text "description"
+ end
+
+ add_index "budget_investment_milestone_translations", ["budget_investment_milestone_id"], name: "index_6770e7675fe296cf87aa0fd90492c141b5269e0b", using: :btree
+ add_index "budget_investment_milestone_translations", ["locale"], name: "index_budget_investment_milestone_translations_on_locale", using: :btree
+
create_table "budget_investment_milestones", force: :cascade do |t|
t.integer "investment_id"
t.string "title", limit: 80
@@ -162,8 +174,8 @@ ActiveRecord::Schema.define(version: 20180320104823) do
t.boolean "winner", default: false
t.boolean "incompatible", default: false
t.integer "community_id"
+ t.boolean "visible_to_valuators", default: false
t.integer "valuator_group_assignments_count", default: 0
- t.boolean "visible_to_valuators", default: false
end
add_index "budget_investments", ["administrator_id"], name: "index_budget_investments_on_administrator_id", using: :btree
diff --git a/spec/features/admin/budget_investment_milestones_spec.rb b/spec/features/admin/budget_investment_milestones_spec.rb
index 5967d71f5..81b9be430 100644
--- a/spec/features/admin/budget_investment_milestones_spec.rb
+++ b/spec/features/admin/budget_investment_milestones_spec.rb
@@ -39,7 +39,7 @@ feature 'Admin budget investment milestones' do
click_link 'Create new milestone'
- fill_in 'budget_investment_milestone_description', with: 'New description milestone'
+ fill_in 'budget_investment_milestone_description_en', with: 'New description milestone'
fill_in 'budget_investment_milestone_publication_date', with: Date.current
click_button 'Create milestone'
@@ -53,7 +53,7 @@ feature 'Admin budget investment milestones' do
click_link 'Create new milestone'
- fill_in 'budget_investment_milestone_description', with: 'New description milestone'
+ fill_in 'budget_investment_milestone_description_en', with: 'New description milestone'
click_button 'Create milestone'
@@ -77,7 +77,7 @@ feature 'Admin budget investment milestones' do
expect(page).to have_css("img[alt='#{milestone.image.title}']")
- fill_in 'budget_investment_milestone_description', with: 'Changed description'
+ fill_in 'budget_investment_milestone_description_en', with: 'Changed description'
fill_in 'budget_investment_milestone_publication_date', with: Date.current
fill_in 'budget_investment_milestone_documents_attributes_0_title', with: 'New document title'
diff --git a/spec/features/budgets/investments_spec.rb b/spec/features/budgets/investments_spec.rb
index de15c44ee..b9b590a6f 100644
--- a/spec/features/budgets/investments_spec.rb
+++ b/spec/features/budgets/investments_spec.rb
@@ -995,7 +995,8 @@ feature 'Budget Investments' do
user = create(:user)
investment = create(:budget_investment)
create(:budget_investment_milestone, investment: investment,
- description: "Last milestone with a link to https://consul.dev",
+ description_en: "Last milestone with a link to https://consul.dev",
+ description_es: "Último hito con el link https://consul.dev",
publication_date: Date.tomorrow)
first_milestone = create(:budget_investment_milestone, investment: investment,
description: "First milestone",
@@ -1017,6 +1018,15 @@ feature 'Budget Investments' do
expect(page).to have_link(document.title)
expect(page).to have_link("https://consul.dev")
end
+
+ select('Español', from: 'locale-switcher')
+
+ find("#tab-milestones-label").click
+
+ within("#tab-milestones") do
+ expect(page).to have_content('Último hito con el link https://consul.dev')
+ expect(page).to have_link("https://consul.dev")
+ end
end
scenario "Show no_milestones text", :js do
diff --git a/spec/features/translations_spec.rb b/spec/features/translations_spec.rb
new file mode 100644
index 000000000..4ff482ee4
--- /dev/null
+++ b/spec/features/translations_spec.rb
@@ -0,0 +1,121 @@
+require 'rails_helper'
+
+feature "Translations" do
+
+ context "Milestones" do
+
+ let(:investment) { create(:budget_investment) }
+ let(:milestone) { create(:budget_investment_milestone,
+ investment: investment,
+ description_en: "Description in English",
+ description_es: "Descripción en Español") }
+
+ background do
+ admin = create(:administrator)
+ login_as(admin.user)
+ end
+
+ before do
+ @edit_milestone_url = edit_admin_budget_budget_investment_budget_investment_milestone_path(investment.budget, investment, milestone)
+ end
+
+ scenario "Add a translation", :js do
+ visit @edit_milestone_url
+
+ select "Français", from: "translation_locale"
+ fill_in 'budget_investment_milestone_description_fr', with: 'Description en Français'
+
+ click_button 'Update milestone'
+ expect(page).to have_content "Milestone updated successfully"
+
+ visit @edit_milestone_url
+ expect(page).to have_field('budget_investment_milestone_description_en', with: 'Description in English')
+
+ click_link "Español"
+ expect(page).to have_field('budget_investment_milestone_description_es', with: 'Descripción en Español')
+
+ click_link "Français"
+ expect(page).to have_field('budget_investment_milestone_description_fr', with: 'Description en Français')
+ end
+
+ scenario "Update a translation", :js do
+ visit @edit_milestone_url
+
+ click_link "Español"
+ fill_in 'budget_investment_milestone_description_es', with: 'Descripción correcta en Español'
+
+ click_button 'Update milestone'
+ expect(page).to have_content "Milestone updated successfully"
+
+ visit budget_investment_path(investment.budget, investment)
+
+ click_link("Milestones (1)")
+ expect(page).to have_content("Description in English")
+
+ select('Español', from: 'locale-switcher')
+ click_link("Seguimiento (1)")
+
+ expect(page).to have_content("Descripción correcta en Español")
+ end
+
+ scenario "Remove a translation", :js do
+ visit @edit_milestone_url
+
+ click_link "Español"
+ click_link "Remove language"
+
+ expect(page).not_to have_link "Español"
+
+ click_button "Update milestone"
+ visit @edit_milestone_url
+ expect(page).not_to have_link "Español"
+ end
+
+ context "Globalize javascript interface" do
+
+ scenario "Highlight current locale", :js do
+ visit @edit_milestone_url
+
+ 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
+ visit @edit_milestone_url
+
+ 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
+ visit @edit_milestone_url
+
+ expect(page).to have_field('budget_investment_milestone_description_en', with: 'Description in English')
+
+ click_link "Español"
+
+ expect(page).to have_field('budget_investment_milestone_description_es', with: 'Descripción en Español')
+ end
+
+ scenario "Select a locale and add it to the milestone form", :js do
+ visit @edit_milestone_url
+
+ select "Français", from: "translation_locale"
+
+ expect(page).to have_link "Français"
+
+ click_link "Français"
+
+ expect(page).to have_field('budget_investment_milestone_description_fr')
+ end
+ end
+
+ end
+
+end