Merge pull request #5243 from consuldemocracy/tenant_locales

Allow different locales per tenant
This commit is contained in:
Javi Martín
2024-06-05 16:27:02 +02:00
committed by GitHub
91 changed files with 1184 additions and 299 deletions

View File

@@ -574,6 +574,7 @@ RSpec/InstanceVariable:
Enabled: true
Exclude:
- spec/controllers/concerns/has_orders_spec.rb
- spec/form_builders/translatable_form_builder_spec.rb
RSpec/LetBeforeExamples:
Enabled: true

View File

@@ -0,0 +1,43 @@
.admin .locales-form {
label {
display: table;
}
select {
width: auto;
}
.help-text {
display: block;
}
> select + fieldset,
> fieldset + fieldset {
margin-top: $line-height / 2;
}
> [type="hidden"] + fieldset,
> fieldset + fieldset {
@include admin-fieldset-separator;
padding-top: calc(#{$line-height} / 4);
}
.collection-radio-buttons,
.collection-check-boxes {
column-gap: rem-calc(map-get($grid-column-gutter, medium));
column-width: 13rem;
max-width: max-content;
@each $elements in 10, 15, 20, 25, 30 {
&:has(label:nth-of-type(#{$elements})) {
column-count: calc(#{$elements} / 5);
}
}
}
[type="submit"] {
@include regular-button;
display: block;
margin-top: $line-height;
}
}

View File

@@ -0,0 +1,39 @@
<%= form_for locales_settings, url: admin_locales_path, html: { class: "locales-form" } do |f| %>
<% if many_available_locales? %>
<%= f.select :default, locales_options, hint: sanitize(t("admin.locales.default_help_text")) %>
<% else %>
<fieldset>
<legend><%= attribute_name(:default) %></legend>
<p class="help-text"><%= sanitize(t("admin.locales.default_help_text")) %></p>
<div class="collection-radio-buttons">
<%= f.collection_radio_buttons(
:default,
available_locales,
:to_sym,
->(locale) { name_for_locale(locale) }
) do |b| %>
<%= b.label { b.radio_button + b.text } %>
<% end %>
</div>
</fieldset>
<% end %>
<fieldset>
<legend><%= attribute_name(:enabled) %></legend>
<p class="help-text"><%= t("admin.locales.enabled_help_text") %></p>
<div class="collection-check-boxes">
<%= f.collection_check_boxes(
:enabled,
available_locales,
:to_sym,
->(locale) { name_for_locale(locale) }
) do |b| %>
<%= b.label { b.check_box + b.text } %>
<% end %>
</div>
</fieldset>
<%= submit_tag %>
<% end %>

View File

@@ -0,0 +1,30 @@
class Admin::Locales::FormComponent < ApplicationComponent
attr_reader :locales_settings
use_helpers :name_for_locale
def initialize(locales_settings)
@locales_settings = locales_settings
end
private
def available_locales
I18n.available_locales
end
def many_available_locales?
available_locales.count > select_field_threshold
end
def locales_options
available_locales.map { |locale| [name_for_locale(locale), locale] }
end
def select_field_threshold
10
end
def attribute_name(...)
locales_settings.class.human_attribute_name(...)
end
end

View File

@@ -0,0 +1,4 @@
<%= header %>
<% provide :main_class, "admin-locales-show" %>
<%= render Admin::Locales::FormComponent.new(locales_settings) %>

View File

@@ -0,0 +1,12 @@
class Admin::Locales::ShowComponent < ApplicationComponent
include Header
attr_reader :locales_settings
def initialize(locales_settings)
@locales_settings = locales_settings
end
def title
t("admin.menu.locales")
end
end

View File

@@ -54,7 +54,7 @@ class Admin::MenuComponent < ApplicationComponent
end
def settings?
controllers_names = ["settings", "tenants", "tags", "geozones", "local_census_records", "imports"]
controllers_names = %w[settings tenants tags locales geozones local_census_records imports]
controllers_names.include?(controller_name)
end
@@ -462,6 +462,7 @@ class Admin::MenuComponent < ApplicationComponent
settings_link,
tenants_link,
tags_link,
locales_link,
geozones_link,
local_census_records_link
)
@@ -494,6 +495,14 @@ class Admin::MenuComponent < ApplicationComponent
]
end
def locales_link
[
t("admin.menu.locales"),
admin_locales_path,
controller_name == "locales"
]
end
def geozones_link
[
t("admin.menu.geozones"),

View File

@@ -1,12 +1,12 @@
<%= form_for [:admin, @content_block], html: { class: "edit_page" } do |f| %>
<%= form_for [:admin, content_block], html: { class: "edit_page" } do |f| %>
<%= render "shared/errors", resource: @content_block %>
<%= render "shared/errors", resource: content_block %>
<div class="small-12 medium-6 column">
<%= f.select :name, options_for_select(valid_blocks, @selected_content_block) %>
<%= f.select :name, options_for_select(valid_blocks, selected_content_block) %>
</div>
<div class="small-12 medium-6 column">
<%= f.select :locale, I18n.available_locales %>
<%= f.select :locale, Setting.enabled_locales %>
</div>
<div class="small-12 column">

View File

@@ -0,0 +1,14 @@
class Admin::SiteCustomization::ContentBlocks::FormContentBlockComponent < ApplicationComponent
attr_reader :content_block
use_helpers :valid_blocks
def initialize(content_block)
@content_block = content_block
end
private
def selected_content_block
content_block.name
end
end

View File

@@ -1,17 +1,17 @@
<%= form_tag(admin_site_customization_update_heading_content_block_path(@content_block.id), method: "put") do %>
<%= render "shared/errors", resource: @content_block %>
<%= form_tag(admin_site_customization_update_heading_content_block_path(content_block.id), method: "put") do %>
<%= render "shared/errors", resource: content_block %>
<div class="small-12 medium-6 column">
<%= label_tag :name %>
<%= select_tag :name, options_for_select(valid_blocks, @selected_content_block) %>
<%= select_tag :name, options_for_select(valid_blocks, selected_content_block) %>
</div>
<div class="small-12 medium-6 column">
<%= label_tag :locale %>
<%= select_tag :locale, options_for_select(I18n.available_locales, @content_block.locale.to_sym) %>
<%= select_tag :locale, options_for_select(Setting.enabled_locales, content_block.locale.to_sym) %>
</div>
<div class="small-12 column">
<%= label_tag :body %>
<%= text_area_tag :body, @content_block.body, rows: 10 %>
<%= text_area_tag :body, content_block.body, rows: 10 %>
<div class="small-12 medium-6 large-3">
<%= button_tag t("admin.menu.site_customization.buttons.content_block.update"), class: "button success expanded" %>
</div>

View File

@@ -0,0 +1,14 @@
class Admin::SiteCustomization::ContentBlocks::FormHeadingContentBlockComponent < ApplicationComponent
attr_reader :content_block
use_helpers :valid_blocks
def initialize(content_block)
@content_block = content_block
end
private
def selected_content_block
"hcb_#{content_block.heading_id}"
end
end

View File

@@ -1,13 +1,13 @@
<%= render "globalize_locales" %>
<%= render Shared::GlobalizeLocalesComponent.new %>
<%= form_tag admin_site_customization_information_texts_path do %>
<% I18n.available_locales.each do |l| %>
<% enabled_locales.each do |l| %>
<%= translation_enabled_tag l, site_customization_enable_translation?(l) %>
<% end %>
<% contents.each do |group| %>
<% group.each do |content| %>
<b><%= content.key %></b>
<% content.globalize_locales.each do |locale| %>
<% (content.globalize_locales & enabled_locales.map(&:to_sym)).each do |locale| %>
<%= render Admin::SiteCustomization::InformationTexts::FormFieldComponent.new(content, locale: locale) %>
<% end %>
<% end %>

View File

@@ -0,0 +1,18 @@
class Admin::SiteCustomization::InformationTexts::FormComponent < ApplicationComponent
attr_reader :contents
use_helpers :site_customization_enable_translation?
def initialize(contents)
@contents = contents
end
private
def translation_enabled_tag(locale, enabled)
hidden_field_tag("enabled_translations[#{locale}]", (enabled ? 1 : 0))
end
def enabled_locales
Setting.enabled_locales
end
end

View File

@@ -4,6 +4,6 @@
text,
rows: 5,
class: "js-globalize-attribute",
style: site_customization_display_translation_style(locale),
style: site_customization_display_translation_style,
data: { locale: locale } %>
<% end %>

View File

@@ -1,6 +1,6 @@
class Admin::SiteCustomization::InformationTexts::FormFieldComponent < ApplicationComponent
attr_reader :i18n_content, :locale
use_helpers :globalize, :site_customization_display_translation_style
use_helpers :globalize, :site_customization_enable_translation?
def initialize(i18n_content, locale:)
@i18n_content = i18n_content
@@ -22,4 +22,8 @@ class Admin::SiteCustomization::InformationTexts::FormFieldComponent < Applicati
def i18n_text
I18n.translate(i18n_content.key, locale: locale)
end
def site_customization_display_translation_style
site_customization_enable_translation?(locale) ? "" : "display: none;"
end
end

View File

@@ -12,7 +12,7 @@ class Layout::LocaleSwitcherComponent < ApplicationComponent
end
def locales
I18n.available_locales
Setting.enabled_locales
end
def label

View File

@@ -3,19 +3,19 @@
<span class="small">
<strong class="js-languages-description"
data-texts="<%= t("shared.translations.languages_in_use").to_json %>">
<%= selected_languages_description(resource) %>
<%= selected_languages_description %>
</strong>
</span>
<%= select_tag :select_language,
options_for_select_language(resource),
options_for_select_language,
prompt: t("shared.translations.select_language_prompt"),
class: "js-select-language" %>
<%= select_language_error(resource) %>
<%= select_language_error %>
<div class="margin-bottom">
<% if manage_languages %>
<% I18n.available_locales.each do |locale| %>
<% Setting.enabled_locales.each do |locale| %>
<%= link_to t("shared.translations.remove_language"), "#",
style: display_destroy_locale_style(resource, locale),
style: display_destroy_locale_style(locale),
class: "delete js-delete-language js-delete-#{locale}",
data: { locale: locale } %>
<% end %>

View File

@@ -0,0 +1,95 @@
class Shared::GlobalizeLocalesComponent < ApplicationComponent
attr_reader :resource, :manage_languages
use_helpers :first_translation, :first_marked_for_destruction_translation,
:enabled_locale?, :name_for_locale, :highlight_translation_html_class
def initialize(resource = nil, manage_languages: true)
@resource = resource
@manage_languages = manage_languages
end
private
def options_for_select_language
options_for_select(available_locales, selected_locale)
end
def available_locales
Setting.enabled_locales.select { |locale| enabled_locale?(resource, locale) }.map do |locale|
[name_for_locale(locale), locale, { data: { locale: locale }}]
end
end
def selected_locale
return first_i18n_content_locale if resource.blank?
if resource.locales_not_marked_for_destruction.any?
first_translation(resource)
elsif resource.locales_persisted_and_marked_for_destruction.any?
first_marked_for_destruction_translation(resource)
else
I18n.locale
end
end
def first_i18n_content_locale
if i18n_content_locales.empty? || i18n_content_locales.include?(I18n.locale)
I18n.locale
else
i18n_content_locales.first
end
end
def selected_languages_description
sanitize(t("shared.translations.languages_in_use", count: active_locales_count))
end
def select_language_error
return if resource.blank?
current_translation = resource.translation_for(selected_locale)
if current_translation.errors.added? :base, :translations_too_short
tag.div class: "small error" do
current_translation.errors[:base].join(", ")
end
end
end
def active_locales_count
if active_locales.size > 0
active_locales.size
else
1
end
end
def active_locales
if resource.present?
resource.locales_not_marked_for_destruction
else
i18n_content_locales
end
end
def display_destroy_locale_style(locale)
"display: none;" unless display_destroy_locale_link?(locale)
end
def display_destroy_locale_link?(locale)
selected_locale == locale
end
def options_for_add_language
options_for_select(all_language_options, nil)
end
def all_language_options
Setting.enabled_locales.map do |locale|
[name_for_locale(locale), locale]
end
end
def i18n_content_locales
I18nContentTranslation.existing_locales
end
end

View File

@@ -0,0 +1,27 @@
class Admin::LocalesController < Admin::BaseController
before_action :set_locales_settings
authorize_resource :locales_settings
def show
end
def update
@locales_settings.update!(locales_settings_params)
redirect_to admin_locales_path, notice: t("admin.locales.update.notice")
end
private
def locales_settings_params
params.require(:setting_locales_settings).permit(allowed_params)
end
def allowed_params
[:default, enabled: []]
end
def set_locales_settings
@locales_settings = Setting::LocalesSettings.new
end
end

View File

@@ -11,6 +11,9 @@ class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomizati
@headings_content_blocks = Budget::ContentBlock.all
end
def new
end
def create
if is_heading_content_block?(@content_block.name)
heading_content_block = new_heading_content_block
@@ -31,11 +34,6 @@ class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomizati
end
def edit
if @content_block.is_a? SiteCustomization::ContentBlock
@selected_content_block = @content_block.name
else
@selected_content_block = "hcb_#{@content_block.heading_id}"
end
end
def update
@@ -72,11 +70,6 @@ class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomizati
def edit_heading_content_block
@content_block = Budget::ContentBlock.find(params[:id])
if @content_block.is_a? Budget::ContentBlock
@selected_content_block = "hcb_#{@content_block.heading_id}"
else
@selected_content_block = @content_block.name
end
@is_heading_content_block = true
render :edit
end

View File

@@ -46,17 +46,17 @@ class ApplicationController < ActionController::Base
current_user.update(locale: locale)
end
session[:locale] = locale
session[:locale] = locale.to_s
I18n.with_locale(locale, &action)
end
def current_locale
if I18n.available_locales.include?(params[:locale]&.to_sym)
if Setting.enabled_locales.include?(params[:locale]&.to_sym)
params[:locale]
elsif I18n.available_locales.include?(session[:locale]&.to_sym)
elsif Setting.enabled_locales.include?(session[:locale]&.to_sym)
session[:locale]
else
I18n.default_locale
Setting.default_locale
end
end

View File

@@ -40,11 +40,11 @@ class Management::BaseController < ActionController::Base
end
def switch_locale(&action)
if params[:locale] && I18n.available_locales.include?(params[:locale].to_sym)
session[:locale] = params[:locale]
if params[:locale] && Setting.enabled_locales.include?(params[:locale].to_sym)
session[:locale] = params[:locale].to_s
end
session[:locale] ||= I18n.default_locale
session[:locale] ||= Setting.default_locale.to_s
I18n.with_locale(session[:locale], &action)
end

View File

@@ -32,8 +32,12 @@ class SubscriptionsController < ApplicationController
def set_user_locale(&action)
if params[:locale].blank?
session[:locale] = I18n.available_locales.find { |locale| locale == @user.locale&.to_sym }
session[:locale] = find_locale.to_s
end
I18n.with_locale(session[:locale], &action)
end
def find_locale
Setting.enabled_locales.find { |locale| locale == @user.locale&.to_sym } || I18n.locale
end
end

View File

View File

@@ -0,0 +1,71 @@
class TranslatableFormBuilder < ConsulFormBuilder
attr_accessor :translations
def translatable_fields(&)
@translations = {}
visible_locales.map do |locale|
@translations[locale] = translation_for(locale)
end
safe_join(visible_locales.map do |locale|
Globalize.with_locale(locale) { fields_for_locale(locale, &) }
end)
end
private
def fields_for_locale(locale)
fields_for_translation(@translations[locale]) do |translations_form|
@template.tag.div(**translations_options(translations_form.object, locale)) do
@template.concat translations_form.hidden_field(
:_destroy,
value: !@template.enabled_locale?(translations_form.object.globalized_model, locale),
data: { locale: locale }
)
@template.concat translations_form.hidden_field(:locale, value: locale)
yield translations_form
end
end
end
def fields_for_translation(translation, &)
fields_for(:translations, translation, builder: TranslationsFieldsFormBuilder, &)
end
def translation_for(locale)
existing_translation_for(locale) || new_translation_for(locale)
end
def existing_translation_for(locale)
@object.translations.find { |translation| translation.locale == locale }
end
def new_translation_for(locale)
@object.translations.new(locale: locale).tap(&:mark_for_destruction)
end
def highlight_translation_html_class
@template.highlight_translation_html_class
end
def translations_options(resource, locale)
{
class: "translatable-fields js-globalize-attribute #{highlight_translation_html_class}",
style: @template.display_translation_style(resource.globalized_model, locale),
data: { locale: locale }
}
end
def no_other_translations?(translation)
(@object.translations - [translation]).reject(&:_destroy).empty?
end
def visible_locales
if @template.translations_interface_enabled?
Setting.enabled_locales & @object.globalize_locales
else
[I18n.locale]
end
end
end

View File

@@ -0,0 +1,5 @@
class TranslationsFieldsFormBuilder < ConsulFormBuilder
def locale
@object.locale
end
end

View File

@@ -46,8 +46,8 @@ module ApplicationHelper
end
end
def content_block(name, locale = I18n.locale)
SiteCustomization::ContentBlock.block_for(name, locale)
def content_block(...)
SiteCustomization::ContentBlock.block_for(...)
end
def self.asset_data_base64(path)

View File

@@ -1,14 +1,4 @@
module GlobalizeHelper
def options_for_select_language(resource)
options_for_select(available_locales(resource), selected_locale(resource))
end
def available_locales(resource)
I18n.available_locales.select { |locale| enabled_locale?(resource, locale) }.map do |locale|
[name_for_locale(locale), locale, { data: { locale: locale }}]
end
end
def enabled_locale?(resource, locale)
return site_customization_enable_translation?(locale) if resource.blank?
@@ -21,27 +11,6 @@ module GlobalizeHelper
end
end
def selected_locale(resource)
return first_i18n_content_translation_locale if resource.blank?
if resource.locales_not_marked_for_destruction.any?
first_translation(resource)
elsif resource.locales_persisted_and_marked_for_destruction.any?
first_marked_for_destruction_translation(resource)
else
I18n.locale
end
end
def first_i18n_content_translation_locale
if I18nContentTranslation.existing_languages.count == 0 ||
I18nContentTranslation.existing_languages.include?(I18n.locale)
I18n.locale
else
I18nContentTranslation.existing_languages.first
end
end
def first_translation(resource)
if resource.locales_not_marked_for_destruction.include? I18n.locale
I18n.locale
@@ -58,40 +27,6 @@ module GlobalizeHelper
end
end
def translations_for_locale?(resource)
resource.locales_not_marked_for_destruction.any?
end
def selected_languages_description(resource)
sanitize(t("shared.translations.languages_in_use", count: active_languages_count(resource)))
end
def select_language_error(resource)
return if resource.blank?
current_translation = resource.translation_for(selected_locale(resource))
if current_translation.errors.added? :base, :translations_too_short
tag.div class: "small error" do
current_translation.errors[:base].join(", ")
end
end
end
def active_languages_count(resource)
if resource.blank?
no_resource_languages_count
elsif resource.locales_not_marked_for_destruction.size > 0
resource.locales_not_marked_for_destruction.size
else
1
end
end
def no_resource_languages_count
count = I18nContentTranslation.existing_languages.count
count > 0 ? count : 1
end
def display_translation_style(resource, locale)
"display: none;" unless display_translation?(resource, locale)
end
@@ -108,28 +43,6 @@ module GlobalizeHelper
end
end
def display_destroy_locale_style(resource, locale)
"display: none;" unless display_destroy_locale_link?(resource, locale)
end
def display_destroy_locale_link?(resource, locale)
selected_locale(resource) == locale
end
def options_for_add_language
options_for_select(all_language_options, nil)
end
def all_language_options
I18n.available_locales.map do |locale|
[name_for_locale(locale), locale]
end
end
def translation_enabled_tag(locale, enabled)
hidden_field_tag("enabled_translations[#{locale}]", (enabled ? 1 : 0))
end
def globalize(locale, &)
Globalize.with_locale(locale, &)
end

View File

@@ -1,10 +1,6 @@
module SiteCustomizationHelper
def site_customization_enable_translation?(locale)
I18nContentTranslation.existing_languages.include?(locale) || locale == I18n.locale
end
def site_customization_display_translation_style(locale)
site_customization_enable_translation?(locale) ? "" : "display: none;"
I18nContentTranslation.existing_locales.include?(locale) || locale == I18n.locale
end
def information_texts_tabs

View File

@@ -14,82 +14,4 @@ module TranslatableFormHelper
def highlight_translation_html_class
"highlight" if translations_interface_enabled?
end
class TranslatableFormBuilder < ConsulFormBuilder
attr_accessor :translations
def translatable_fields(&)
@translations = {}
visible_locales.map do |locale|
@translations[locale] = translation_for(locale)
end
safe_join(visible_locales.map do |locale|
Globalize.with_locale(locale) { fields_for_locale(locale, &) }
end)
end
private
def fields_for_locale(locale)
fields_for_translation(@translations[locale]) do |translations_form|
@template.tag.div **translations_options(translations_form.object, locale) do
@template.concat translations_form.hidden_field(
:_destroy,
value: !@template.enabled_locale?(translations_form.object.globalized_model, locale),
data: { locale: locale }
)
@template.concat translations_form.hidden_field(:locale, value: locale)
yield translations_form
end
end
end
def fields_for_translation(translation, &)
fields_for(:translations, translation, builder: TranslationsFieldsBuilder, &)
end
def translation_for(locale)
existing_translation_for(locale) || new_translation_for(locale)
end
def existing_translation_for(locale)
@object.translations.find { |translation| translation.locale == locale }
end
def new_translation_for(locale)
@object.translations.new(locale: locale).tap(&:mark_for_destruction)
end
def highlight_translation_html_class
@template.highlight_translation_html_class
end
def translations_options(resource, locale)
{
class: "translatable-fields js-globalize-attribute #{highlight_translation_html_class}",
style: @template.display_translation_style(resource.globalized_model, locale),
data: { locale: locale }
}
end
def no_other_translations?(translation)
(@object.translations - [translation]).reject(&:_destroy).empty?
end
def visible_locales
if @template.translations_interface_enabled?
@object.globalize_locales
else
[I18n.locale]
end
end
end
class TranslationsFieldsBuilder < ConsulFormBuilder
def locale
@object.locale
end
end
end

View File

@@ -37,7 +37,7 @@ module SearchDictionarySelector
private
def find_from_i18n_default
key_to_lookup = I18n.default_locale.to_s.split("-").first.to_sym
key_to_lookup = Setting.default_locale.to_s.split("-").first.to_sym
dictionary = I18N_TO_DICTIONARY[key_to_lookup]
dictionary ||= "simple"

View File

@@ -76,7 +76,7 @@ class Mailer < ApplicationMailer
def user_invite(email)
@email_to = email
I18n.with_locale(I18n.default_locale) do
I18n.with_locale(Setting.default_locale) do
mail(to: @email_to, subject: t("mailers.user_invite.subject", org_name: Setting["org_name"]))
end
end

View File

@@ -134,6 +134,8 @@ module Abilities
can [:deliver], Newsletter, hidden_at: nil
can [:manage], Dashboard::AdministratorTask
can :manage, Setting::LocalesSettings
can :manage, LocalCensusRecord
can [:create, :read], LocalCensusRecords::Import

View File

@@ -1,6 +1,6 @@
class Budget
class ContentBlock < ApplicationRecord
validates :locale, presence: true, inclusion: { in: I18n.available_locales.map(&:to_s) }
validates :locale, presence: true, inclusion: { in: ->(*) { Setting.enabled_locales.map(&:to_s) }}
validates :heading, presence: true, uniqueness: { scope: :locale }
belongs_to :heading

View File

@@ -13,10 +13,6 @@ module Globalizable
translations.reject(&:marked_for_destruction?).map(&:locale)
end
def locales_marked_for_destruction
I18n.available_locales - locales_not_marked_for_destruction
end
def locales_persisted_and_marked_for_destruction
translations.select { |t| t.persisted? && t.marked_for_destruction? }.map(&:locale)
end
@@ -86,7 +82,7 @@ module Globalizable
translation_class.instance_eval do
validates method,
length: options[:length],
if: lambda { |translation| translation.locale == I18n.default_locale }
if: lambda { |translation| translation.locale == Setting.default_locale }
end
if options.count > 1
translation_class.instance_eval do

View File

@@ -119,7 +119,7 @@ class I18nContent < ApplicationRecord
end
end
def self.update(contents, enabled_translations = I18n.available_locales)
def self.update(contents, enabled_translations = Setting.enabled_locales)
contents.each do |content|
values = content[:values].slice(*translation_params(enabled_translations))

View File

@@ -1,5 +1,5 @@
class I18nContentTranslation < ApplicationRecord
def self.existing_languages
self.select(:locale).distinct.map { |l| l.locale.to_sym }.to_a
def self.existing_locales
distinct.pluck(:locale).map(&:to_sym)
end
end

View File

@@ -95,6 +95,8 @@ class Setting < ApplicationRecord
"html.per_page_code_body": "",
# Code to be included at the top (inside <head>) of every page (useful for tracking)
"html.per_page_code_head": "",
"locales.enabled": nil,
"locales.default": nil,
"map.latitude": 51.48,
"map.longitude": 0.0,
"map.zoom": 10,
@@ -219,5 +221,20 @@ class Setting < ApplicationRecord
def archived_proposals_date_limit
Setting["months_to_archive_proposals"].to_i.months.ago
end
def enabled_locales
locales = Setting["locales.enabled"].to_s.split.map(&:to_sym)
[
default_locale,
*((locales & I18n.available_locales).presence || I18n.available_locales)
].uniq
end
def default_locale
locale = Setting["locales.default"].to_s.strip.to_sym
([locale] & I18n.available_locales).first || I18n.default_locale
end
end
end

View File

@@ -0,0 +1,23 @@
class Setting
class LocalesSettings
include ActiveModel::Model
include ActiveModel::Attributes
attribute :enabled, array: true, default: -> { Setting.enabled_locales }
attribute :default, default: -> { Setting.default_locale }
def persisted?
true
end
def update(attributes)
assign_attributes(attributes)
Setting.transaction do
Setting["locales.default"] = default
Setting["locales.enabled"] = [default, *enabled].join(" ")
end
end
alias_method :update!, :update
end
end

View File

@@ -1,11 +1,10 @@
class SiteCustomization::ContentBlock < ApplicationRecord
VALID_BLOCKS = %w[top_links footer footer_legal subnavigation_left subnavigation_right].freeze
validates :locale, presence: true, inclusion: { in: I18n.available_locales.map(&:to_s) }
validates :locale, presence: true, inclusion: { in: ->(*) { Setting.enabled_locales.map(&:to_s) }}
validates :name, presence: true, uniqueness: { scope: :locale }, inclusion: { in: ->(*) { VALID_BLOCKS }}
def self.block_for(name, locale)
locale ||= I18n.default_locale
def self.block_for(name, locale = I18n.locale)
find_by(name: name, locale: locale)&.body
end
end

View File

@@ -339,7 +339,7 @@ class User < ApplicationRecord
end
def locale
self[:locale] || I18n.default_locale.to_s
self[:locale] || Setting.default_locale.to_s
end
def confirmation_required?

View File

@@ -1,7 +1,4 @@
<%= render "shared/globalize_locales",
resource: @process,
display_style: lambda { |locale| enable_translation_style(@process, locale) },
manage_languages: false %>
<%= render "shared/globalize_locales", resource: @process, manage_languages: false %>
<%= translatable_form_for [:admin, @process], url: url do |f| %>

View File

@@ -1,7 +1,4 @@
<%= render "shared/globalize_locales",
resource: @process,
display_style: lambda { |locale| enable_translation_style(@process, locale) },
manage_languages: false %>
<%= render "shared/globalize_locales", resource: @process, manage_languages: false %>
<%= translatable_form_for [:admin, @process] do |f| %>
<div class="row">

View File

@@ -0,0 +1 @@
<%= render Admin::Locales::ShowComponent.new(@locales_settings) %>

View File

@@ -1,5 +1,5 @@
<% if @is_heading_content_block %>
<%= render "form_heading_content_block" %>
<%= render Admin::SiteCustomization::ContentBlocks::FormHeadingContentBlockComponent.new(@content_block) %>
<% else %>
<%= render "form_content_block" %>
<%= render Admin::SiteCustomization::ContentBlocks::FormContentBlockComponent.new(@content_block) %>
<% end %>

View File

@@ -1,4 +0,0 @@
<%= render "shared/common_globalize_locales",
resource: nil,
display_style: lambda { |locale| site_customization_display_translation_style(locale) },
manage_languages: defined?(manage_languages) ? manage_languages : true %>

View File

@@ -4,7 +4,6 @@
<%= render "tabs" %>
<div class="tabs-panel is-active" role="tab">
<%= render "form", contents: [@content] %>
<%= render Admin::SiteCustomization::InformationTexts::FormComponent.new([@content]) %>
</div>
</div>

View File

@@ -1,6 +1,6 @@
<% if translations_interface_enabled? %>
<%= render "shared/common_globalize_locales",
resource: resource,
display_style: lambda { |locale| enable_translation_style(resource, locale) },
manage_languages: defined?(manage_languages) ? manage_languages : true %>
<%= render Shared::GlobalizeLocalesComponent.new(
resource,
manage_languages: defined?(manage_languages) ? manage_languages : true
) %>
<% end %>

View File

@@ -136,6 +136,7 @@ module Consul
[
"app/components/custom",
"app/controllers/custom",
"app/form_builders/custom",
"app/graphql/custom",
"app/lib/custom",
"app/mailers/custom",

View File

@@ -38,6 +38,9 @@ en:
year_of_birth: "Year born"
local_census_records/import:
file: File
setting/locales_settings:
default: Default language
enabled: Enabled languages
errors:
models:
local_census_records/import:

View File

@@ -710,6 +710,11 @@ en:
milestones:
index:
title: Following
locales:
default_help_text: "This is the default language of the application, and changing it will <strong>affect every user</strong> visiting the website for the first time."
enabled_help_text: The default language, selected above, will be included automatically.
update:
notice: Languages updated successfully
managers:
index:
title: Managers
@@ -727,11 +732,12 @@ en:
menu:
activity: Moderator activity
admin: Admin menu
banner: Manage banners
banner: Banners
proposals: Proposals
proposals_topics: Proposals topics
budgets: Participatory budgets
geozones: Manage geozones
geozones: Geozones
locales: Languages
hidden_comments: Hidden comments
hidden_debates: Hidden debates
hidden_proposals: Hidden proposals
@@ -752,7 +758,7 @@ en:
polls: Polls
poll_booths: Booths location
poll_booth_assignments: Booths Assignments
poll_shifts: Manage shifts
poll_shifts: Shifts Assignments
officials: Officials
organizations: Organisations
settings: Global settings
@@ -794,7 +800,7 @@ en:
dashboard_actions: Resources and actions
debates: "Debates"
comments: "Comments"
local_census_records: Manage local census
local_census_records: Local census
machine_learning: "AI / Machine learning"
multitenancy: Multitenancy
administrators:
@@ -1232,7 +1238,7 @@ en:
show:
location: "Location"
booth:
shifts: "Manage shifts"
shifts: "Shifts Assignments"
officials:
edit:
destroy: Remove "Official" status
@@ -1713,7 +1719,7 @@ en:
empty: "There are no changes logged"
local_census_records:
index:
title: Manage local census
title: Local census
create: Create new local census record
no_local_census_records: There are no local census records.
document_type: Document type
@@ -1738,7 +1744,7 @@ en:
create:
notice: Local census records import process executed successfully!
show:
title: Manage local census
title: Local census
subtitle: Import process results
import: Import again
errored: Errored rows

View File

@@ -38,6 +38,9 @@ es:
year_of_birth: "Año de nacimiento"
local_census_records/import:
file: Archivo
setting/locales_settings:
default: Idioma por defecto
enabled: Idiomas habilitados
errors:
models:
local_census_records/import:

View File

@@ -710,6 +710,11 @@ es:
milestones:
index:
title: Seguimiento
locales:
default_help_text: "Cambiar el idioma por defecto <strong>afectará a todos los usuarios</strong> que visiten la página por primera vez."
enabled_help_text: El idioma por defecto, seleccionado con anterioridad, se habilitará automáticamente.
update:
notice: Idiomas actualizados con éxito
managers:
index:
title: Gestores
@@ -727,11 +732,12 @@ es:
menu:
activity: Actividad de moderadores
admin: Menú de administración
banner: Gestionar banners
banner: Banners
proposals: Propuestas
proposals_topics: Temas de propuestas
budgets: Presupuestos participativos
geozones: Gestionar zonas
geozones: Zonas
locales: Idiomas
hidden_comments: Comentarios ocultos
hidden_debates: Debates ocultos
hidden_proposals: Propuestas ocultas
@@ -794,7 +800,7 @@ es:
dashboard_actions: Recursos y acciones
debates: "Debates"
comments: "Comentarios"
local_census_records: Gestionar censo local
local_census_records: Censo local
machine_learning: "IA / Machine learning"
multitenancy: Multientidad
administrators:
@@ -1713,7 +1719,7 @@ es:
empty: "No hay cambios registrados"
local_census_records:
index:
title: Gestionar censo local
title: Censo local
create: Crear nuevo registro en el censo local
no_local_census_records: No hay registros de censo local
document_type: Tipo de documento
@@ -1738,7 +1744,7 @@ es:
create:
notice: ¡Proceso de import de registros del censo local ejecutado correctamente!
show:
title: Gestionar censo local
title: Censo local
subtitle: Resultados del proceso de importación
import: Importar otro fichero
errored: Filas erróneas

View File

@@ -242,6 +242,7 @@ namespace :admin do
end
resources :geozones, only: [:index, :new, :create, :edit, :update, :destroy]
resource :locales, only: [:show, :update]
namespace :site_customization do
resources :pages, except: [:show] do

View File

@@ -23,7 +23,11 @@ def log(msg)
end
def random_locales
[I18n.default_locale, *(I18n.available_locales & %i[en es]), *I18n.available_locales.sample(4)].uniq.take(5)
[
Setting.default_locale,
*(Setting.enabled_locales & %i[en es]),
*Setting.enabled_locales.sample(4)
].uniq.take(5)
end
def random_locales_attributes(**attribute_names_with_values)

View File

@@ -9,7 +9,7 @@ section "Creating banners" do
post_started_at: rand((1.week.ago)..(1.day.ago)),
post_ended_at: rand((1.day.ago)..(1.week.from_now)),
created_at: rand((1.week.ago)..Time.current))
I18n.available_locales.map do |locale|
Setting.enabled_locales.map do |locale|
Globalize.with_locale(locale) do
banner.description = "Description for locale #{locale}"
banner.title = "Title for locale #{locale}"

View File

@@ -41,7 +41,7 @@ section "Creating polls" do
Poll.find_each do |poll|
name = poll.name
I18n.available_locales.map do |locale|
Setting.enabled_locales.map do |locale|
Globalize.with_locale(locale) do
poll.name = "#{name} (#{locale})"
poll.summary = "Summary for locale #{locale}"
@@ -59,7 +59,7 @@ section "Creating Poll Questions & Answers" do
question = Poll::Question.new(author: User.sample,
title: question_title,
poll: poll)
I18n.available_locales.map do |locale|
Setting.enabled_locales.map do |locale|
Globalize.with_locale(locale) do
question.title = "#{question_title} (#{locale})"
end
@@ -71,7 +71,7 @@ section "Creating Poll Questions & Answers" do
title: title.capitalize,
description: description,
given_order: index + 1)
I18n.available_locales.map do |locale|
Setting.enabled_locales.map do |locale|
Globalize.with_locale(locale) do
answer.title = "#{title} (#{locale})"
answer.description = "#{description} (#{locale})"
@@ -235,7 +235,7 @@ section "Creating Poll Questions from Proposals" do
question = Poll::Question.new(poll: poll)
question.copy_attributes_from_proposal(proposal)
question_title = question.title
I18n.available_locales.map do |locale|
Setting.enabled_locales.map do |locale|
Globalize.with_locale(locale) do
question.title = "#{question_title} (#{locale})"
end
@@ -247,7 +247,7 @@ section "Creating Poll Questions from Proposals" do
title: title.capitalize,
description: description,
given_order: index + 1)
I18n.available_locales.map do |locale|
Setting.enabled_locales.map do |locale|
Globalize.with_locale(locale) do
answer.title = "#{title} (#{locale})"
answer.description = "#{description} (#{locale})"
@@ -265,7 +265,7 @@ section "Creating Successful Proposals" do
question = Poll::Question.new(poll: poll)
question.copy_attributes_from_proposal(proposal)
question_title = question.title
I18n.available_locales.map do |locale|
Setting.enabled_locales.map do |locale|
Globalize.with_locale(locale) do
question.title = "#{question_title} (#{locale})"
end
@@ -277,7 +277,7 @@ section "Creating Successful Proposals" do
title: title.capitalize,
description: description,
given_order: index + 1)
I18n.available_locales.map do |locale|
Setting.enabled_locales.map do |locale|
Globalize.with_locale(locale) do
answer.title = "#{title} (#{locale})"
answer.description = "#{description} (#{locale})"

View File

@@ -104,7 +104,7 @@ end
if SiteCustomization::Page.find_by(slug: "accessibility").nil?
page = SiteCustomization::Page.new(slug: "accessibility", status: "published")
I18n.available_locales.each do |locale|
Setting.enabled_locales.each do |locale|
I18n.with_locale(locale) { generate_content(page) }
end
end

View File

@@ -7,7 +7,7 @@ end
if SiteCustomization::Page.find_by(slug: "census_terms").nil?
page = SiteCustomization::Page.new(slug: "census_terms", status: "published")
page.print_content_flag = true
I18n.available_locales.each do |locale|
Setting.enabled_locales.each do |locale|
I18n.with_locale(locale) { generate_content(page) }
end
end

View File

@@ -8,7 +8,7 @@ end
if SiteCustomization::Page.find_by(slug: "conditions").nil?
page = SiteCustomization::Page.new(slug: "conditions", status: "published")
page.print_content_flag = true
I18n.available_locales.each do |locale|
Setting.enabled_locales.each do |locale|
I18n.with_locale(locale) { generate_content(page) }
end
end

View File

@@ -5,7 +5,7 @@ def generate_content(page)
end
if SiteCustomization::Page.find_by(slug: "faq").nil?
page = SiteCustomization::Page.new(slug: "faq", status: "published")
I18n.available_locales.each do |locale|
Setting.enabled_locales.each do |locale|
I18n.with_locale(locale) { generate_content(page) }
end
end

View File

@@ -7,7 +7,7 @@ end
if SiteCustomization::Page.find_by(slug: "privacy").nil?
page = SiteCustomization::Page.new(slug: "privacy", status: "published")
page.print_content_flag = true
I18n.available_locales.each do |locale|
Setting.enabled_locales.each do |locale|
I18n.with_locale(locale) { generate_content(page) }
end
end

View File

@@ -17,7 +17,7 @@ end
if SiteCustomization::Page.find_by(slug: "welcome_level_three_verified").nil?
page = SiteCustomization::Page.new(slug: "welcome_level_three_verified", status: "published")
I18n.available_locales.each do |locale|
Setting.enabled_locales.each do |locale|
I18n.with_locale(locale) { generate_content(page) }
end
end

View File

@@ -22,7 +22,7 @@ def generate_content(page)
end
if SiteCustomization::Page.find_by(slug: "welcome_level_two_verified").nil?
page = SiteCustomization::Page.new(slug: "welcome_level_two_verified", status: "published")
I18n.available_locales.each do |locale|
Setting.enabled_locales.each do |locale|
I18n.with_locale(locale) { generate_content(page) }
end
end

View File

@@ -22,7 +22,7 @@ def generate_content(page)
end
if SiteCustomization::Page.find_by(slug: "welcome_not_verified").nil?
page = SiteCustomization::Page.new(slug: "welcome_not_verified", status: "published")
I18n.available_locales.each do |locale|
Setting.enabled_locales.each do |locale|
I18n.with_locale(locale) { generate_content(page) }
end
end

View File

@@ -0,0 +1,37 @@
require "rails_helper"
describe Admin::Locales::FormComponent do
let(:default_locale) { :nl }
let(:enabled_locales) { %i[en nl] }
let(:locales_settings) { Setting::LocalesSettings.new(default: default_locale, enabled: enabled_locales) }
let(:component) { Admin::Locales::FormComponent.new(locales_settings) }
describe "default language selector" do
before { allow(I18n).to receive(:available_locales).and_return(%i[de en es nl]) }
it "renders radio buttons when there are only a few locales" do
render_inline component
page.find(:fieldset, "Default language") do |fieldset|
expect(fieldset).to have_checked_field "Nederlands", type: :radio
expect(fieldset).to have_unchecked_field "English", type: :radio
expect(fieldset).to have_unchecked_field "Español", type: :radio
expect(fieldset).to have_unchecked_field "Deutsch", type: :radio
end
expect(page).not_to have_select
end
it "renders a select when there are many locales" do
allow(component).to receive(:select_field_threshold).and_return(3)
render_inline component
expect(page).not_to have_field type: :radio
expect(page).to have_select "Default language",
options: %w[English Español Deutsch Nederlands],
selected: "Nederlands"
end
end
end

View File

@@ -0,0 +1,19 @@
require "rails_helper"
describe Admin::SiteCustomization::ContentBlocks::FormContentBlockComponent do
describe "locale selector" do
let(:content_block) { create(:site_customization_content_block, locale: "de") }
let(:component) do
Admin::SiteCustomization::ContentBlocks::FormContentBlockComponent.new(content_block)
end
it "only includes enabled settings" do
Setting["locales.default"] = "de"
Setting["locales.enabled"] = "de fr"
render_inline component
expect(page).to have_select "locale", options: ["de", "fr"]
end
end
end

View File

@@ -0,0 +1,19 @@
require "rails_helper"
describe Admin::SiteCustomization::ContentBlocks::FormHeadingContentBlockComponent do
describe "locale selector" do
let(:content_block) { create(:heading_content_block, locale: "de") }
let(:component) do
Admin::SiteCustomization::ContentBlocks::FormHeadingContentBlockComponent.new(content_block)
end
it "only includes enabled settings" do
Setting["locales.default"] = "de"
Setting["locales.enabled"] = "de fr"
render_inline component
expect(page).to have_select "locale", options: ["de", "fr"]
end
end
end

View File

@@ -0,0 +1,29 @@
require "rails_helper"
describe Admin::SiteCustomization::InformationTexts::FormComponent do
describe "enabled_translations fields" do
it "renders fields for enabled locales" do
Setting["locales.enabled"] = "en es"
content = create(:i18n_content)
render_inline Admin::SiteCustomization::InformationTexts::FormComponent.new([[content]])
expect(page).to have_css "input[name^='enabled_translations']", count: 2, visible: :all
expect(page).to have_css "input[name='enabled_translations[en]']", visible: :hidden
expect(page).to have_css "input[name='enabled_translations[es]']", visible: :hidden
end
end
describe "text fields" do
it "renders fields for enabled locales" do
Setting["locales.enabled"] = "en es"
content = create(:i18n_content, key: "system.failure")
render_inline Admin::SiteCustomization::InformationTexts::FormComponent.new([[content]])
expect(page).to have_css "textarea[name^='contents[content_system.failure]']", count: 2, visible: :all
expect(page).to have_field "contents[content_system.failure]values[value_en]"
expect(page).to have_field "contents[content_system.failure]values[value_es]"
end
end
end

View File

@@ -97,4 +97,21 @@ describe Layout::LocaleSwitcherComponent do
expect(page).to have_css "[href='/?locale=en'][data-turbolinks=true]"
end
end
context "when not all available locales are enabled" do
before do
allow(I18n).to receive(:available_locales).and_return(%i[en es fr])
Setting["locales.default"] = "es"
Setting["locales.enabled"] = "es fr"
end
it "displays the enabled locales" do
render_inline component
expect(page).to have_link count: 2
expect(page).to have_link "Español"
expect(page).to have_link "Français"
expect(page).not_to have_link "English"
end
end
end

View File

@@ -0,0 +1,49 @@
require "rails_helper"
describe Shared::GlobalizeLocalesComponent do
describe "Language selector" do
it "only includes enabled locales" do
Setting["locales.enabled"] = "en nl"
I18n.with_locale(:en) do
render_inline Shared::GlobalizeLocalesComponent.new
expect(page).to have_select options: ["Choose language", "English"]
end
I18n.with_locale(:es) do
render_inline Shared::GlobalizeLocalesComponent.new
expect(page).to have_select options: ["Seleccionar idioma"]
end
end
end
describe "links to destroy languages" do
it "only includes enabled locales" do
Setting["locales.enabled"] = "en nl"
I18n.with_locale(:en) do
render_inline Shared::GlobalizeLocalesComponent.new
expect(page).to have_css "a[data-locale]", count: 1
end
I18n.with_locale(:es) do
render_inline Shared::GlobalizeLocalesComponent.new
expect(page).not_to have_css "a[data-locale]"
end
end
end
describe "Add language selector" do
it "only includes enabled locales" do
Setting["locales.enabled"] = "en nl"
render_inline Shared::GlobalizeLocalesComponent.new
expect(page).to have_select options: ["Add language", "English", "Nederlands"]
end
end
end

View File

@@ -1,6 +1,14 @@
require "rails_helper"
describe ApplicationController do
controller do
skip_authorization_check
def index
render plain: I18n.locale
end
end
describe "#current_budget" do
it "returns the last budget that is not in draft phase" do
create(:budget, :finished, created_at: 2.years.ago, name: "Old")
@@ -12,4 +20,68 @@ describe ApplicationController do
expect(budget.name).to eq("Current")
end
end
describe "#switch_locale" do
it "uses the default locale by default" do
Setting["locales.default"] = "pt-BR"
get :index
expect(response.body).to eq "pt-BR"
end
it "uses the locale in the parameters when it's there" do
get :index, params: { locale: :es }
expect(response.body).to eq "es"
end
it "uses the locale in the session if there are no parameters" do
get :index, params: { locale: :es }
expect(response.body).to eq "es"
get :index
expect(response.body).to eq "es"
end
it "uses the locale in the parameters even when it's in the session" do
get :index
expect(response.body).to eq "en"
get :index, params: { locale: :es }
expect(response.body).to eq "es"
end
it "only accepts enabled locales" do
Setting["locales.enabled"] = "en es fr"
get :index, params: { locale: :es }
expect(response.body).to eq "es"
get :index, params: { locale: :de }
expect(response.body).to eq "es"
get :index, params: { locale: :fr }
expect(response.body).to eq "fr"
end
context "authenticated user" do
let(:user) { create(:user) }
before { sign_in(user) }
it "updates the prefered locale when it's in the parameters" do
get :index, params: { locale: :es }
expect(user.reload.locale).to eq "es"
expect(response.body).to eq "es"
end
end
end
end

View File

@@ -1,6 +1,16 @@
require "rails_helper"
describe Management::BaseController do
before { session[:manager] = double }
controller do
skip_authorization_check
def index
render plain: I18n.locale
end
end
describe "managed_user" do
it "returns existent user with session document info if present" do
session[:document_type] = "1"
@@ -21,4 +31,56 @@ describe Management::BaseController do
expect(managed_user.document_number).to eq "333333333E"
end
end
describe "#switch_locale" do
it "uses the default locale by default" do
Setting["locales.default"] = "pt-BR"
get :index
expect(response.body).to eq "pt-BR"
end
it "uses the locale in the parameters when it's there" do
get :index, params: { locale: :es }
expect(response.body).to eq "es"
end
it "uses the locale in the session if there are no parameters" do
get :index, params: { locale: :es }
expect(response.body).to eq "es"
get :index
expect(response.body).to eq "es"
end
it "uses the locale in the parameters even when it's in the session" do
get :index
expect(response.body).to eq "en"
get :index, params: { locale: :es }
expect(response.body).to eq "es"
end
it "only accepts enabled locales" do
Setting["locales.enabled"] = "en es fr"
get :index, params: { locale: :es }
expect(response.body).to eq "es"
get :index, params: { locale: :de }
expect(response.body).to eq "es"
get :index, params: { locale: :fr }
expect(response.body).to eq "fr"
end
end
end

View File

@@ -19,5 +19,24 @@ describe SubscriptionsController do
expect(response).to redirect_to "/"
expect(flash[:alert]).to eq "No tienes permiso para acceder a esta página."
end
it "uses the user locale where there's no locale in the parameters" do
create(:user, locale: "es", subscriptions_token: "mytoken")
get :edit, params: { token: "mytoken" }
expect(session[:locale]).to eq "es"
end
it "only accepts enabled locales" do
Setting["locales.default"] = "fr"
Setting["locales.enabled"] = "fr nl"
create(:user, locale: "es", subscriptions_token: "mytoken")
get :edit, params: { token: "mytoken" }
expect(session[:locale]).to eq "fr"
end
end
end

View File

@@ -0,0 +1,46 @@
require "rails_helper"
describe TranslatableFormBuilder do
before do
dummy_banner = Class.new(ApplicationRecord) do
def self.name
"DummyBanner"
end
self.table_name = "banners"
translates :title, touch: true
include Globalizable
has_many :translations, class_name: "DummyBanner::Translation", foreign_key: "banner_id"
end
stub_const("DummyBanner", dummy_banner)
end
let(:builder) do
TranslatableFormBuilder.new(:dummy, DummyBanner.new, ApplicationController.new.view_context, {})
end
describe "#translatable_fields" do
it "renders fields for the enabled locales when the translation interface is enabled" do
Setting["feature.translation_interface"] = true
Setting["locales.enabled"] = "en fr"
builder.translatable_fields do |translations_builder|
render translations_builder.text_field :title
end
expect(page).to have_field "Title", count: 2
end
end
attr_reader :content
def render(content)
@content ||= ""
@content << content
end
def page
Capybara::Node::Simple.new(content)
end
end

View File

@@ -3,27 +3,23 @@ require "rails_helper"
describe SearchDictionarySelector do
context "from I18n default locale" do
before { allow(subject).to receive(:call).and_call_original }
around do |example|
original_i18n_default = I18n.default_locale
begin
example.run
ensure
I18n.default_locale = original_i18n_default
end
end
it "returns correct dictionary for simple locale" do
I18n.default_locale = :es
Setting["locales.default"] = "es"
expect(subject.call).to eq("spanish")
end
it "returns correct dictionary for compound locale" do
I18n.default_locale = :"pt-BR"
Setting["locales.default"] = "pt-BR"
expect(subject.call).to eq("portuguese")
end
it "returns simple for unsupported locale" do
expect(I18n).to receive(:default_locale).and_return(:pl) # avoiding I18n::InvalidLocale
allow(I18n).to receive(:available_locales).and_return(%i[en pl])
Setting["locales.default"] = "pl"
expect(subject.call).to eq("simple")
end
end

View File

@@ -30,6 +30,16 @@ describe Mailer do
end
end
describe "#user_invite" do
it "uses the default locale setting" do
Setting["locales.default"] = "es"
Mailer.user_invite("invited@consul.dev").deliver_now
expect(ActionMailer::Base.deliveries.last.body.to_s).to match "<html lang=\"es\""
end
end
describe "#manage_subscriptions_token" do
let(:user) { create(:user) }
let(:proposal) { create(:proposal, author: user) }

View File

@@ -20,6 +20,15 @@ describe Budget::ContentBlock do
expect(valid_block).to be_valid
end
it "is not valid with a disabled locale" do
Setting["locales.default"] = "pt-BR"
Setting["locales.enabled"] = "nl pt-BR"
block.locale = "en"
expect(block).not_to be_valid
end
describe "#name" do
it "uses the heading name" do
block = Budget::ContentBlock.new(heading: Budget::Heading.new(name: "Central"))

View File

@@ -0,0 +1,47 @@
require "rails_helper"
describe Globalizable do
before do
dummy_banner = Class.new(ApplicationRecord) do
def self.name
"DummyBanner"
end
self.table_name = "banners"
translates :title, touch: true
include Globalizable
has_many :translations, class_name: "DummyBanner::Translation", foreign_key: "banner_id"
validates_translation :title, length: { minimum: 7 }
end
stub_const("DummyBanner", dummy_banner)
end
describe ".validates_translation" do
it "validates length for the default locale" do
Setting["locales.default"] = "es"
dummy = DummyBanner.new
dummy.translations.build(locale: "es", title: "Short")
dummy.translations.build(locale: "fr", title: "Long enough")
I18n.with_locale(:fr) do
expect(dummy).not_to be_valid
end
end
it "does not validate length for other locales" do
Setting["locales.default"] = "es"
dummy = DummyBanner.new
dummy.translations.build(locale: "es", title: "Long enough")
dummy.translations.build(locale: "fr", title: "Long enough")
dummy.translations.build(locale: "en", title: "Short")
I18n.with_locale(:fr) do
expect(dummy).to be_valid
end
end
end
end

View File

@@ -160,6 +160,17 @@ RSpec.describe I18nContent do
end
it "does not store new keys for disabled translations" do
Setting["locales.default"] = "es"
Setting["locales.enabled"] = "es"
I18nContent.update([{ id: "shared.yes", values: { "value_en" => "Oh, yeah" }}])
expect(I18nContent.all).to be_empty
end
it "uses different enabled translations when given a parameter" do
Setting["locales.enabled"] = "en es"
I18nContent.update([{ id: "shared.yes", values: { "value_en" => "Oh, yeah" }}], [:es])
expect(I18nContent.all).to be_empty

View File

@@ -0,0 +1,13 @@
require "rails_helper"
describe Setting::LocalesSettings do
describe "#update!" do
it "saves the default locale in the enabled ones when nothing is enabled" do
Setting::LocalesSettings.new.update!(default: "es", enabled: %w[])
updated_locales_settings = Setting::LocalesSettings.new
expect(updated_locales_settings.default).to eq :es
expect(updated_locales_settings.enabled).to match_array [:es]
end
end
end

View File

@@ -258,4 +258,96 @@ describe Setting do
expect(Setting.force_presence_postal_code?).to be true
end
end
describe ".available_locales" do
before { allow(I18n).to receive_messages(default_locale: :de, available_locales: %i[de en es pt-BR]) }
it "uses I18n available locales by default" do
Setting["locales.enabled"] = ""
expect(Setting.enabled_locales).to eq %i[de en es pt-BR]
end
it "defines available locales with a space-separated list" do
Setting["locales.enabled"] = "de es"
expect(Setting.enabled_locales).to eq %i[de es]
end
it "handles locales which include a dash" do
Setting["locales.enabled"] = "de en pt-BR"
expect(Setting.enabled_locales).to eq %i[de en pt-BR]
end
it "adds the default locale to the list of available locales" do
Setting["locales.enabled"] = "en es"
expect(Setting.enabled_locales).to eq %i[de en es]
end
it "ignores extra whitespace between locales" do
Setting["locales.enabled"] = " de en pt-BR "
expect(Setting.enabled_locales).to eq %i[de en pt-BR]
end
it "ignores locales which aren't available" do
Setting["locales.enabled"] = "de es en-US fr zh-CN"
expect(Setting.enabled_locales).to eq %i[de es]
end
it "ignores words that don't make sense in this context" do
Setting["locales.enabled"] = "yes de 1234 en SuperCool"
expect(Setting.enabled_locales).to eq %i[de en]
end
it "uses I18n available locales when no locale is available" do
Setting["locales.enabled"] = "nl fr zh-CN"
expect(Setting.enabled_locales).to eq %i[de en es pt-BR]
end
end
describe ".default_locale" do
before { allow(I18n).to receive_messages(default_locale: :en, available_locales: %i[de en es pt-BR]) }
it "uses I18n default locale by default" do
Setting["locales.default"] = ""
expect(Setting.default_locale).to eq :en
end
it "allows defining the default locale" do
Setting["locales.default"] = "de"
expect(Setting.default_locale).to eq :de
end
it "handles locales which include a dash" do
Setting["locales.default"] = "pt-BR"
expect(Setting.default_locale).to eq :"pt-BR"
end
it "ignores extra whitespace in the locale name" do
Setting["locales.default"] = " es "
expect(Setting.default_locale).to eq :es
end
it "ignores locales which aren't available" do
Setting["locales.default"] = "fr"
expect(Setting.default_locale).to eq :en
end
it "ignores an array of several locales" do
Setting["locales.default"] = "de es"
expect(Setting.default_locale).to eq :en
end
end
end

View File

@@ -27,4 +27,13 @@ RSpec.describe SiteCustomization::ContentBlock do
block.name = "top_links"
expect(block).not_to be_valid
end
it "is not valid with a disabled locale" do
Setting["locales.default"] = "nl"
Setting["locales.enabled"] = "nl pt-BR"
block.locale = "en"
expect(block).not_to be_valid
end
end

View File

@@ -81,6 +81,16 @@ describe User do
end
end
describe "#locale" do
it "defaults to the default locale setting" do
Setting["locales.default"] = "nl"
user = build(:user, locale: nil)
expect(user.locale).to eq "nl"
end
end
describe "preferences" do
describe "email_on_comment" do
it "is false by default" do

View File

@@ -65,7 +65,7 @@ describe "Admin banners magement", :admin do
within("#side_menu") do
click_button "Site content"
click_link "Manage banners"
click_link "Banners"
end
click_link "Create banner"
@@ -134,7 +134,7 @@ describe "Admin banners magement", :admin do
within("#side_menu") do
click_button "Site content"
click_link "Manage banners"
click_link "Banners"
end
click_link "Edit"

View File

@@ -16,7 +16,7 @@ describe "Admin geozones", :admin do
within("#side_menu") do
click_button "Settings"
click_link "Manage geozones"
click_link "Geozones"
end
click_link "Create geozone"

View File

@@ -0,0 +1,54 @@
require "rails_helper"
describe "Locales management", :admin do
scenario "Navigate to languages page and update them" do
allow(I18n).to receive(:available_locales).and_return(%i[de en es fr])
Setting["locales.default"] = "en"
Setting["locales.enabled"] = "en de"
visit admin_root_path
within ".locale" do
expect(page).to have_css "[aria-current]", exact_text: "English"
expect(page).to have_link "English"
expect(page).to have_link "Deutsch"
expect(page).not_to have_link "Français"
expect(page).not_to have_link "Español"
end
within "#admin_menu" do
expect(page).not_to have_link "Languages"
click_button "Settings"
click_link "Languages"
expect(page).to have_css "[aria-current]", exact_text: "Languages"
expect(page).to have_link "Languages"
end
within_fieldset "Default language" do
expect(page).to have_checked_field "English"
choose "Español"
end
within_fieldset "Enabled languages" do
uncheck "English"
check "Français"
end
click_button "Save changes"
expect(page).to have_content "Languages updated successfully"
within ".locale" do
expect(page).to have_css "[aria-current]", exact_text: "Español"
expect(page).to have_link "Français"
expect(page).to have_link "Español"
expect(page).to have_link "Deutsch"
expect(page).not_to have_link "English"
end
end
end

View File

@@ -39,7 +39,7 @@ describe "Admin booths", :admin do
within("#side_menu") do
click_button "Voting booths"
click_link "Manage shifts"
click_link "Shifts Assignments"
end
expect(page).to have_css(".booth", count: 1)
@@ -80,7 +80,7 @@ describe "Admin booths", :admin do
visit admin_booths_path
within("#booth_#{booth.id}") do
expect(page).not_to have_link "Manage shifts"
expect(page).not_to have_link "Shifts Assignments"
click_link "Edit"
end
@@ -105,7 +105,7 @@ describe "Admin booths", :admin do
visit available_admin_booths_path
within("#booth_#{booth.id}") do
click_link "Manage shifts"
click_link "Shifts Assignments"
end
click_link "Go back"

View File

@@ -44,7 +44,7 @@ describe "Admin shifts", :admin do
visit available_admin_booths_path
within("#booth_#{booth.id}") do
click_link "Manage shifts"
click_link "Shifts Assignments"
end
expect(page).to have_content "This booth has no shifts"
@@ -71,7 +71,7 @@ describe "Admin shifts", :admin do
visit available_admin_booths_path
within("#booth_#{booth.id}") do
click_link "Manage shifts"
click_link "Shifts Assignments"
end
expect(page).to have_css(".shift", count: 1)
@@ -117,7 +117,7 @@ describe "Admin shifts", :admin do
visit available_admin_booths_path
within("#booth_#{booth.id}") do
click_link "Manage shifts"
click_link "Shifts Assignments"
end
expect(page).to have_css(".shift", count: 2)
@@ -159,7 +159,7 @@ describe "Admin shifts", :admin do
visit available_admin_booths_path
within("#booth_#{booth.id}") do
click_link "Manage shifts"
click_link "Shifts Assignments"
end
expect(page).to have_content "This booth has no shifts"
@@ -182,7 +182,7 @@ describe "Admin shifts", :admin do
visit available_admin_booths_path
within("#booth_#{booth.id}") do
click_link "Manage shifts"
click_link "Shifts Assignments"
end
expect(page).to have_css(".shift", count: 1)
@@ -208,7 +208,7 @@ describe "Admin shifts", :admin do
visit available_admin_booths_path
within("#booth_#{booth.id}") do
click_link "Manage shifts"
click_link "Shifts Assignments"
end
expect(page).to have_css(".shift", count: 1)
@@ -237,7 +237,7 @@ describe "Admin shifts", :admin do
visit available_admin_booths_path
within("#booth_#{booth.id}") do
click_link "Manage shifts"
click_link "Shifts Assignments"
end
expect(page).to have_css(".shift", count: 1)