Skip to content

Using Mobility with Forms

dvodvo edited this page Nov 8, 2024 · 13 revisions

A common use case involves translating content using a form (in Rails). Here we look at some issues with this use case and ways to solve them.

Translations as Form Fields

To show translations of a given attribute as separate fields in a form, you will first need to enable the LocaleAccessors plugin:

Mobility.configure do
  plugins do
    locale_accessors
  end
end

Assuming you have set I18n.available_locales, you can then create a form like this:

<%= form_for @post do |f| %>
  <% I18n.available_locales.each do |locale| %>
    <div>
      <% name = "name_#{Mobility.normalize_locale(locale)}" %>
      <%= f.label name %>
      <%= f.text_field name %>
    </div>
  <% end %>

  <%= f.submit %>
<% end %>

Mobility.normalize_locale transforms locales like :pt-BR into the form-friendly pt_br, so they can be appended to the attribute name to get title_pt_br.

In your controller, you will need to permit these translated attributes otherwise they will not get updated. You can do this like this:

class PostsController < ApplicationController
  def update
    # ...
    @post.update(post_params)
    # ...
  end

  # ...

  private

  def post_params
    translated_params = (I18n.available_locales.map do |l|
      [:"name_#{Mobility.normalize_locale(l)}", :"description_#{Mobility.normalize_locale(l)}"]
      end.flatten)
    params.require(:post).permit(translated_params, :date, [...], pictures[] )
  end


end

note: translated params being an array, needs to be before keyword arguments, particularly if one is loading arrays for the object

Fallbacks / Default

If you define fallbacks or a default value on your model, you will see the fallback locale value or default value of an attribute in the form if the value in the current locale is not available. Submitting the form will then save this fallback or default value, which is not generally what you would want. (See this issue for reference).

To solve this, you can pass a fallback and/or default option to the attribute reader to disable using fallbacks/default:

post.title(fallback: false, default: nil)
#=> returns the actual value of the title, in the current locale, or nil if it is nil

In your form, you can set this as the default value for every translatable attribute:

<%= form_for @post do |f| %>

  ...
  <%= f.label :title %>:
  <%= f.text_field :title, value: @post.title(fallback: false, default: nil) %><br />
  ...

  <%= f.submit %>
<% end %>

This may become cumbersome if you have many translated attributes and many forms. To make this default, use this form builder, which will automatically disable fallbacks on all translated attributes:

class FallbacksDisabledFormBuilder < ActionView::Helpers::FormBuilder
  %w[text_field text_area].each do |field_name|
    define_method field_name do |attribute, options={}|
      if @object.translated_attribute_names.include?(attribute)
        super(attribute, options.merge(value: @object.send(attribute, fallback: false, default: nil)))
      else
        super(attribute, options)
      end
    end
  end
end

You can use the builder in your form with builder: FallbacksDisabledFormBuilder, like this:

<%= form_for @post, builder: FallbacksDisabledFormBuilder do |f| %>

  ...
  <%= f.label :title %>:
  <%= f.text_field :title %><br />
  ...

  <%= f.submit %>
<% end %>

For more on form builders, see the rails docs.

SimpleForm

By default, SimpleForm will not use textarea form fields for text-valued translated attributes. To get around this, use SipleForm's as argument to specify the attribute's type:

= f.input :content, as: :text

See also: https://github.com/shioyama/mobility/issues/109