How can I make Rails 3 localize my date formats?

前端 未结 5 1332
终归单人心
终归单人心 2021-02-01 02:03

I am working on a Rails 3 project where there is place for date input within a form. The text field with the date uses a date picker so there is no concern about the date being

相关标签:
5条回答
  • 2021-02-01 02:22

    You could override your getters for the :publish_date attribute.

    i.e. in your model:

    def date( *format)
        self[:publish_date].strftime(format.first || default)
    end
    

    and in your view you could do either

    @income.date("%d/%m/%Y")
    

    or

    @income.date
    

    This would cause strftime to use the passed format string unless it was nil, in which case it would fall back to your default string.

    Note: I used the splat operator to add support for getting the date without an argument. There may be a cleaner way to do that.

    0 讨论(0)
  • 2021-02-01 02:24

    When displaying a date, you can use I18n.l

    So you would do :

    I18n.l @entry.created_at
    

    And if you want to change it's format :

    I18n.l @entry.created_at, :format => :short
    

    The internationalization rails guide is documenting that.

    0 讨论(0)
  • 2021-02-01 02:25

    check out the delocalize gem, it might help you out some.

    https://github.com/clemens/delocalize

    http://www.railway.at/articles/2009/05/03/new-plugin-delocalize/

    0 讨论(0)
  • 2021-02-01 02:27

    @damien-mathieu has a solid answer for displaying localized dates with I18n.localize, and his comment raises an important caveat: this breaks form text inputs. Since then, rails gives us a nice solution.

    As of Rails 5, you can use the Rails attributes API to customize how user input is transformed into a model or database value. Actually, it was available in Rails 4.2, just not fully documented.

    Through Sean Griffin's considerable efforts, all models' types are now defined as ActiveRecord::Type objects. This defines a single source of truth for how an attribute is handled. The type defines how the attribute is serialized (from a ruby type to a database type), deserialized (from a database type to a ruby type) and cast (from user input to a ruby type). This is a big deal, because messing with this used to be a minefield of special cases developers should avoid.

    First, skim the attribute docs to understand how to override an attribute's type. You probably need to read the docs to understand this answer.

    How Rails Transforms Attributes

    Here's a quick tour of the Rails Attributes API. You can skip this section, but then you won't know how this stuff works. What fun is that?

    Understanding how Rails handles user input for your attribute will let us override only one method instead of making a more complete custom type. It will also help you write better code, since rails' code is pretty good.

    Since you didn't mention a model, I'll assume you have a Post with a :publish_date attribute (some would prefer the name :published_on, but I digress).

    What is your type?

    Find out what type :publish_date is. We don't care that it is an instance of Date, we need to know what type_for_attribute returns:

    This method is the only valid source of information for anything related to the types of a model's attributes.

    $ rails c
    > post = Post.where.not(publish_date: nil).first
    > post.publish_date.class
    => Date
    > Post.type_for_attribute('publish_date').type
    => :date
    

    Now we know the :publish_date attribute is a :date type. This is defined by ActiveRecord::Type::Date, which extends ActiveModel::Type::Date, which extends ActiveModel::Type::Value. I've linked to rails 5.1.3, but you'll want to read the source for your version.

    How is user input transformed by ActiveRecord::Type::Date?

    So, when you set :publish_date, the value is passed to cast, which calls cast_value. Since form input is a String, it will try a fast_string_to_date then fallback_string_to_date which uses Date._parse.

    If you're getting lost, don't worry. You don't need to understand rails' code to customize an attribute.

    Defining a Custom Type

    Now that we understand how Rails uses the attributes API, we can easily make our own. Just create a custom type to override cast_value to expect localized date strings:

    class LocalizedDate < ActiveRecord::Type::Date
    
      private
    
        # Convert localized date string to Date object. This takes I18n formatted date strings
        # from user input and casts them back to Date objects.
        def cast_value(value)
          if value.is_a?(::String)
            return if value.empty?
            format = I18n.translate("date.formats.short")
            Date.strptime(value, format) rescue nil
          elsif value.respond_to?(:to_date)
            value.to_date
          else
            value
          end
        end
    end
    

    See how I just copied rails' code and made a small tweak. Easy. You might want to improve on this with a call to super and move the :short format to an option or constant.

    Register your type so it can be referenced by a symbol:

    # config/initializers/types.rb
    ActiveRecord::Type.register(:localized_date, LocalizedDate)
    

    Override the :publish_date type with your custom type:

    class Post < ApplicationRecord
      attribute :publish_date, :localized_date
    end
    

    Now you can use localized values in your form inputs:

    # app/views/posts/_form.html.erb
    <%= form_for(@post) do |f| %>
      <%= f.label :publish_date %>
      <%= f.text_field :publish_date, value: (I18n.localize(value, format: :short) if value.present?) %>
    <% end %>
    
    0 讨论(0)
  • 2021-02-01 02:29

    What I found to be the best solution is this:

    • I localize date formats in my locale file like you do
    • In my forms I localize the date by setting the value directly

    <%= f.text_field :publish_date, :value => (@model.publish_date.nil? ? nil : l(@model.publish_date)) %>

    It is not perfect sadly, but at least this way I can use my form for both new and existing records. Also the app will stay compatible with multiple locales compared to changing the default format with initializers. If you fully want to comply with DRY you could always write a custom helper.

    0 讨论(0)
提交回复
热议问题