The name `html_safe` is very confusing, and many developers (including me a few years ago) think what that method does is convert the HTML contents to safe content. It's actually quite the opposite: it marks the string as safe, so the HTML inside it isn't stripped out by Rails. In some cases we were marking strings as safe because we wanted to add some HTML. However, it meant the whole string was considered safe, and not just the contents which were under our control. In particular, some translations added by admins to the database or through crowding were marked as safe, when it wasn't necessarily the case. Although AFAIK crowdin checks for potential cross-site scripting attacks, it's a good practice to sanitize parts of a string potentially out of our control before marking the string as HTML safe.
103 lines
2.9 KiB
Ruby
103 lines
2.9 KiB
Ruby
module TranslatableFormHelper
|
|
def translatable_form_for(record, options = {})
|
|
options_full = options.merge(builder: TranslatableFormBuilder)
|
|
form_for(record, options_full) do |f|
|
|
yield(f)
|
|
end
|
|
end
|
|
|
|
def translations_interface_enabled?
|
|
Setting["feature.translation_interface"].present? || backend_translations_enabled?
|
|
end
|
|
|
|
def backend_translations_enabled?
|
|
(controller.class.parents & [Admin, Management, Valuation, Tracking]).any?
|
|
end
|
|
|
|
def highlight_translation_html_class
|
|
"highlight" if translations_interface_enabled?
|
|
end
|
|
|
|
class TranslatableFormBuilder < ConsulFormBuilder
|
|
attr_accessor :translations
|
|
|
|
def translatable_fields(&block)
|
|
@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, &block) }
|
|
end)
|
|
end
|
|
|
|
private
|
|
|
|
def fields_for_locale(locale)
|
|
fields_for_translation(@translations[locale]) do |translations_form|
|
|
@template.content_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) do |f|
|
|
yield f
|
|
end
|
|
end
|
|
|
|
def translation_for(locale)
|
|
existing_translation_for(locale) || new_translation_for(locale)
|
|
end
|
|
|
|
def existing_translation_for(locale)
|
|
@object.translations.detect { |translation| translation.locale == locale }
|
|
end
|
|
|
|
def new_translation_for(locale)
|
|
@object.translations.new(locale: locale).tap do |translation|
|
|
translation.mark_for_destruction
|
|
end
|
|
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
|