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 %> + + + +
+ <%= 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 @@
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? %> + + <% 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