<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brandon Casci</title>
    <description>The latest articles on DEV Community by Brandon Casci (@bcasci).</description>
    <link>https://dev.to/bcasci</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1144652%2Fdf40989b-4bb9-4ff9-a076-323802c7d92f.jpeg</url>
      <title>DEV Community: Brandon Casci</title>
      <link>https://dev.to/bcasci</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bcasci"/>
    <language>en</language>
    <item>
      <title>Inter-Process Communication With Ruby</title>
      <dc:creator>Brandon Casci</dc:creator>
      <pubDate>Tue, 29 Aug 2023 02:31:00 +0000</pubDate>
      <link>https://dev.to/bcasci/inter-process-communication-with-ruby-4360</link>
      <guid>https://dev.to/bcasci/inter-process-communication-with-ruby-4360</guid>
      <description>&lt;p&gt;&lt;small&gt;2023-08-28&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inter-Process Communication With Ruby 🔄
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wnfZFV5J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/http://www.brandoncasci.com/2023/08/28/cover.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wnfZFV5J--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/http://www.brandoncasci.com/2023/08/28/cover.png" alt="Building a hot tub" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In 2015, I needed a way to record audio streams on a scheduled basis. To accomplish this, I created a Rails app called Radioshifter that served as a UI for editing and storing schedules. In a separate process, a &lt;a href="https://github.com/jmettraux/rufus-scheduler"&gt;rufus-scheduler&lt;/a&gt; would run and start/stop various command line applications that captured online audio streams to disk and uploaded them to Dropbox.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vdiZnVcP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/http://www.brandoncasci.com/2023/08/28/radio-shifter.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vdiZnVcP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/http://www.brandoncasci.com/2023/08/28/radio-shifter.png" alt="Building a hot tub" width="512" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Radioshifter - R.I.P. 🪦☠&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing a Method for Interprocess Communication
&lt;/h2&gt;

&lt;p&gt;I looked at various methods for interprocess communication with Ruby, which included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pipes&lt;/li&gt;
&lt;li&gt;shared memory&lt;/li&gt;
&lt;li&gt;message queues&lt;/li&gt;
&lt;li&gt;sockets&lt;/li&gt;
&lt;li&gt;RPC frameworks&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ruby/drb"&gt;Ruby dRb&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  dRb: The Right Tool for the Job
&lt;/h2&gt;

&lt;p&gt;I ultimately chose dRb (distributed Ruby) for its simplicity and ease of use.&lt;/p&gt;

&lt;p&gt;The dRb fronted process didn’t have too much to worry about in terms of processing messages. All it needed to do was take a message and edit rufus-scheduler’s job list. dRb seemed capable enough for that type of message load.&lt;/p&gt;

&lt;p&gt;To enable communication between the Rails app and the scheduler, I used dRb. dRb is a distributed object system for Ruby. It allows you to expose objects to other processes and communicate with them. dRb is a part of the Ruby standard library, so it’s available out of the box. There are two main components to a dRb implementation, whatever you choose to be the server, and a client or clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  My dRb Implementation in Radioshifter
&lt;/h2&gt;

&lt;p&gt;Things to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I did this in 2015, so some of the code may be a bit dated.&lt;/li&gt;
&lt;li&gt;I did this in a hurry, so there are some things that could be improved.&lt;/li&gt;
&lt;li&gt;At this point in time, there are probably better ways to do this&lt;/li&gt;
&lt;li&gt;There’s no built in or durability or retrying with dRb, like a robust message queue would have.&lt;/li&gt;
&lt;li&gt;I’ve never looked into the performace limits of dRb.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With that being said, there is a time and place for dRb, and it’s a good tool know about.&lt;/p&gt;

&lt;p&gt;Below are the critical entry points for the dRb implementation, but you can also view the full application source code on the &lt;a href="https://bitbucket.org/bcasci/radio_shifter_web/"&gt;repository&lt;/a&gt;. I’m also no longer really fond of the -er -or naming convention that I used for certain classes, but I digress!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# The executable that starts the scheduler which is fronted by dRb

#!/usr/bin/env ruby
require 'drb'
require File.expand_path('../../config/environment', __FILE__ )

ENV['RAILS_ENV'] ||= 'development'
ENV['RUFUS_SCHEDULER_SERVER_PORT'] ||= '9000'
ENV['RECORDINGS_LOG_FOLDER'] ||= File.expand_path('../../log/recordings', __FILE__ )

trap("INT") { exit }

RUFUS_SCHEDULER = Rufus::Scheduler.new

server = Scheduler::Server.new(RUFUS_SCHEDULER)
bootstrapper = Scheduler::ServerBootstrapper.new(server, RecordingSchedule)
bootstrapper.bootstrap

DRb.start_service("druby://localhost:#{ENV['RUFUS_SCHEDULER_SERVER_PORT']}",
  server
)
DRb.thread.join  


# The server class that exposes the scheduler

module Scheduler
  class Server
    include ::Scheduler::JobTransformations

    def initialize(scheduler)
      @scheduler = scheduler
      @job_editor = Scheduler::JobEditor.new(@scheduler)
      @lock = Mutex.new
    end

    def cron(recording_schedule)
      @lock.synchronize do
        @job_editor.cron(recording_schedule)
      end
    end

    def at(recording_schedule)
      @lock.synchronize do
        @job_editor.at(recording_schedule)
      end
    end

    def unschedule(job_id)
      @lock.synchronize do
        @job_editor.unschedule(job_id)
      end
    end

    def job(job_id)
      begin
        job = @scheduler.job(job_id)
        job_to_hash(job) if job
      rescue ArgumentError =&amp;gt; e
        nil
      end
    end

    def jobs(job_ids)
      found_jobs = job_ids.map do |id|
        job(id)
      end

      found_jobs.compact
    end
  end
end


# The dRb client initializer.
# In my app this is located at: config/initializers/rufus-scheduler.rb

require 'drb'
RUFUS_SCHEDULER_CLIENT = DRbObject.new(nil, 'druby://localhost:9000')

# Note: this must be called at least once per process to take any effect.
# This is particularly important if your application forks.
DRb.start_service


# Calling the dRb Client.

class RecordingSchedulesController &amp;lt; ApplicationController
  before_action :authenticate_user!
  before_action :redirect_to_time_zone, if: Proc.new { current_user.time_zone.blank? }

  def index
    @recording_schedules = RecordingSchedule.where(user_id: current_user.id).order(:name).decorate
  end

  def new
    @recording_schedule = RecordingSchedule.new
  end

  def create
    @recording_schedule = RecordingSchedule.new(recording_schedule_params).decorate
    @recording_schedule.user = current_user
    notifier = Services::ScheduleNotifier.new(@recording_schedule, RUFUS_SCHEDULER_CLIENT)

    if notifier.save
      redirect_to recording_schedules_path, flash: { success: 'Recording schedule added.' }
    else
      flash.now[:error] = "There were some problems with this recording schedule."
      render :new
    end
  end

  def destroy
    @recording_schedule = RecordingSchedule.find(params[:id]).decorate
    notifier = Services::ScheduleNotifier.new(@recording_schedule, RUFUS_SCHEDULER_CLIENT)
    flash_options = {}

    if notifier.destroy
      flash_options[:success] = 'Recording schedule removed.'
    else
      flash_options[:success] = 'Recording schedule not removed.'
    end

    redirect_to recording_schedules_path, flash_options
  end

  def edit
    @recording_schedule = RecordingSchedule.where(id: params[:id], user_id: current_user.id).first
  end

  def update
    @recording_schedule = RecordingSchedule.where(id: params[:id], user_id: current_user.id)
      .first
      .decorate
    notifier = Services::ScheduleNotifier.new(@recording_schedule, RUFUS_SCHEDULER_CLIENT)

    if notifier.save(recording_schedule_params)
      redirect_to recording_schedules_path, flash: { success: 'Changes saved.' }
    else
      flash.now[:error] = "There were some problems with this recording schedule."
      render :edit
    end
  end

  private

  def recording_schedule_params
    strip_blank_from_days_of_week(
      params
        .require(:recording_schedule)
        .permit(:duration, :stop_time, :name, :recurring,
         :start_on, :start_time, :stream_url, days_of_week: []
        )
    )
  end

  def redirect_to_time_zone
    redirect_to time_zone_path
  end

  def strip_blank_from_days_of_week(permittied_params)
    if permittied_params[:days_of_week].present?
      permittied_params[:days_of_week]
        .reject! {|i|i.blank?}
        .collect!(&amp;amp;:to_i)
    end
    permittied_params
  end
end

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Overall, using dRb for interprocess communication in Ruby proved to be a reliable and efficient solution for my audio recording system. While there are other methods available for interprocess communication in Ruby, dRb’s simplicity and ease of use made it a good choice for my use case.&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>rails</category>
      <category>interprocess</category>
    </item>
    <item>
      <title>Internationalize and Humanize your Ruby on Rails application</title>
      <dc:creator>Brandon Casci</dc:creator>
      <pubDate>Wed, 23 Aug 2023 13:52:47 +0000</pubDate>
      <link>https://dev.to/bcasci/internationalize-and-humanize-your-ruby-on-rails-application-1g7c</link>
      <guid>https://dev.to/bcasci/internationalize-and-humanize-your-ruby-on-rails-application-1g7c</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhze0555g02a8yqtfu2g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhze0555g02a8yqtfu2g.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Internationalization is the process of designing and developing web applications that can support multiple languages and locales. This is an important consideration for any web application that needs to reach a global audience, as it allows users to interact with the application in their preferred language and format.&lt;/p&gt;

&lt;p&gt;Ruby on Rails provides built-in support for &lt;a href="https://github.com/ruby-i18n/i18n" rel="noopener noreferrer"&gt;I18n&lt;/a&gt;, making it easy to create and manage translations for different languages. The humanization helpers of ActiveRecord and ActiveModel I18n, and you can leverage this to make you application support multiple languages, and to also humanize your object names and attributes to generate human readable content and forms.&lt;/p&gt;

&lt;p&gt;Take the following two example screens from &lt;a href="https://www.cboflow.com/" rel="noopener noreferrer"&gt;www.cboflow.com&lt;/a&gt;, a Rails application that I maintain. The first is in English, and the second is in Spanish. Using the strategies outlined in this article, I was able to make this application support multiple languages with minimal effort.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupglqq1c95pzfdvl7tju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fupglqq1c95pzfdvl7tju.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwbiepxbtpu496ktg05j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwbiepxbtpu496ktg05j.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I18n and Rails
&lt;/h2&gt;

&lt;p&gt;A new Rails application is configured to use I18n by default.&lt;/p&gt;

&lt;p&gt;All of the translations are stored in resource files within the project's &lt;code&gt;config/locales&lt;/code&gt; directory. Each file has to end with a language code, and &lt;code&gt;.yml&lt;/code&gt;. So for example, you might have a file named &lt;code&gt;config/locales/en.yml&lt;/code&gt;, with the following content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

  en:
    hello: "Hello, world!"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In this folder, you can have as many subfolder as you like, and prefix the files as you wish. Some people like to have one large locale file for each supported language, while others like to have one file per object or view. For example, keeping all content in &lt;code&gt;config/locales/en.yml&lt;/code&gt;, or breaking the content up into several files like &lt;code&gt;config/locales/author.yml&lt;/code&gt; and &lt;code&gt;config/locales/post.yml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://guides.rubyonrails.org/i18n.html" rel="noopener noreferrer"&gt;Rails Guide&lt;/a&gt; has a lot of information about using this feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I use I18n in my own Rails applications
&lt;/h2&gt;

&lt;p&gt;I try to use the same lingo across my applications.&lt;/p&gt;

&lt;p&gt;I will create one locale file for each object that I intent to humanize in a view.&lt;/p&gt;

&lt;p&gt;So if I have a Person model, I'll have a &lt;code&gt;person.en.yml&lt;/code&gt; file, and more files for other languages like &lt;code&gt;person.es.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The default humanization for an attribute name is to the same as &lt;code&gt;String#humanize&lt;/code&gt;, so &lt;code&gt;Person#first_name&lt;/code&gt; will become &lt;code&gt;First name&lt;/code&gt; by default, and if I am happy with that I probably will not make a translation entry.&lt;/p&gt;

&lt;p&gt;I also tend to use gems like &lt;a href="https://github.com/heartcombo/simple_form" rel="noopener noreferrer"&gt;simple_form&lt;/a&gt; to generate my form HTML, and this saves me from having to maintain a lot of view code to outputting translated content onto forms. Also simple_form has it's own i18n convention that compliments the Rails default pretty well.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

 # person.en.yml
  en:
    activerecord:
      models:
        person:
          one: Person
          other: People
    attributes:
      person/hispanic:
        true: 'Yes'
        false: 'No'               
      errors:
        models:
          person:
            attributes:
              birth_date:
                must_be_in_the_past: "can't be before today"
                must_be_formatted_correctly: "must be formatted like yyyy-mm-dd"
              full_name:
                blank: "can't be blank"
              email:
                invalid: "is not valid. It should look like name@domain.com"
              phone_number:
                invalid: "is not valid. It should look like 123-456-7890"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# person.es.yml
  es:
    activerecord:
      models:
        person:
          one: "Persona"
          other: "Personas"
      attributes:
        person:
          birth_date: "Fecha de nacimiento"
          email: "Correo electrónico"
          full_name: "Nombre completo"
          hispanic: "Hispano"
          personal_information: "Información personal"
          phone_number: "Número de teléfono"
          self_described_gender: "Género autodescrito"
          self_described_race: "Raza autodescrita" 
        person/hispanic:
          true: "Sí"
          false: "No"
      errors:
        models:
          person:
            attributes:
              birth_date:
                must_be_in_the_past: "no puede ser antes de hoy"
                must_be_formatted_correctly: "debe tener el formato yyyy-mm-dd"
              full_name:
                blank: "no puede estar en blanco"
              email:
                invalid: "no es válido. Debe parecerse a nombre@dominio.com"
              phone_number:
                invalid: "no es válido. Debe parecerse a 123-456-7890"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Gems like simple_form will label your forms with the correct languages, but you will have to call the model humanization helpers when outputting other types of content. You can also clean this up with your pwn helpers or presentation objets.&lt;/p&gt;

&lt;p&gt;You manually pull translations like this:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;Person.human_attribute_name(:first_name) # -&amp;gt; First name&lt;br&gt;
  Person.model_name.human # -&amp;gt; Person&lt;br&gt;
  Person.model_name.human(count: 2) # -&amp;gt; People&lt;br&gt;
  Person.human_attribute_name("hispanic.#{person.hispanic}") %&amp;gt; # -&amp;gt; Yes&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Humanizing Context and Service Objects with I18n.&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Sometimes it will make sense to humanize one object's names and attributes with a totally new name in different parts of an app. For example, maybe it makes sense on one screen to refer to Person ans Profile, or Household Member.&lt;/p&gt;

&lt;p&gt;Sometimes it also makes sense to wrap a sequence of steps into a service object that someone can initiate by filling out a form and clicking a button.&lt;/p&gt;

&lt;p&gt;I'll use something like what's below to accomplish that.&lt;/p&gt;

&lt;p&gt;Now you can do things like &lt;code&gt;Clients::ChangeProfile.model_name.human&lt;/code&gt; or &lt;code&gt;Clients::ChangeProfile.human_attribute_name(:first_name)&lt;/code&gt;.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;module Clients&lt;br&gt;
  class ChangeProfile &amp;lt; BaseCommand&lt;br&gt;
    attribute :full_name, :string&lt;br&gt;
    attribute :birth_date, :date&lt;br&gt;
    attribute :email, :string&lt;br&gt;
    attribute :phone_number, :string&lt;br&gt;
    attribute :hispanic, :boolean, default: false&lt;br&gt;
    attribute :race_list, default: []&lt;br&gt;
    attribute :self_described_race, :string&lt;br&gt;
    attribute :gender_list, default: []&lt;br&gt;
    attribute :self_described_gender, :string&lt;br&gt;
    attribute :user&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;validates :user, presence: true


def run
  context = {}
  validate!
  context[:person] = person

  # While the implementation is not shown in this code sample, the attributes
  # for this ActiveModel based Command/Service Object are written across
  # several models within a transaction
  save!

  result(true, **context)
rescue ActiveRecord::RecordInvalid, ActiveModel::ValidationError
  rollup_errors(person) if person
  result(false, **context)
end

# overriding model name here, so this command can be bound to a form
# with ActionView and humanized with full I18n support

def self.model_name
  ActiveModel::Name.new(self, nil, 'Profile')
end

# other implementation details and methods
# ...
# ...
# ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;end&lt;br&gt;
end&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;en:&lt;br&gt;
  activerecord:&lt;br&gt;
    models:&lt;br&gt;
      person:&lt;br&gt;
        one: Person&lt;br&gt;
        other: People&lt;br&gt;
      clients/profile:&lt;br&gt;
        one: Profile&lt;br&gt;
        other: Profiles&lt;br&gt;
      household_member:&lt;br&gt;
        one: Household Member&lt;br&gt;
        other:  Household Members&lt;br&gt;&lt;br&gt;
    attributes:&lt;br&gt;
      person/hispanic:&lt;br&gt;
        true: 'Yes'&lt;br&gt;
        false: 'No'&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;errors:
  models:
    person:
      attributes:  &amp;amp;amp;person_error_attributes
        birth_date:
          must_be_in_the_past: "can't be before today"
          must_be_formatted_correctly: "must be formatted like yyyy-mm-dd"
        full_name:
          blank: "can't be blank"
        email:
          invalid: "is not valid. It should look like name@domain.com"
        phone_number:
          invalid: "is not valid. It should look like 123-456-7890"
    household_member:
      attributes:
        &amp;amp;lt;&amp;amp;lt;: *person_error_attributes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Navigational links and contextual information&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;I may place all of my navigational and contextual information into a locale file like what's below.&lt;/p&gt;

&lt;p&gt;This enables me to do things like this:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;&amp;lt;%= link_to t(:add_action, subject: Person.model_name.human), new_manage_client_care_distribution_path, class: 'btn btn-info' %&amp;gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;p&gt;en:&lt;br&gt;
  add: Add&lt;br&gt;
  add_action: "Add %{subject}"&lt;br&gt;
  all: All&lt;br&gt;
  back: Back&lt;br&gt;
  basic_information: Basic Information&lt;br&gt;
  client_profile: Client Profile&lt;br&gt;
  confirm: Are you sure?&lt;br&gt;
  empty_search: "No %{subject} found"&lt;br&gt;
  delete: Delete&lt;br&gt;
  edit: Edit&lt;br&gt;
  hello: "Hello world"&lt;br&gt;
  history_action: "%{subject} History"&lt;br&gt;&lt;br&gt;
  insights: Insights&lt;br&gt;
  invite_action: "Invite %{subject}"&lt;br&gt;&lt;br&gt;
  loading: Loading...&lt;br&gt;
  provide_action: "Provide %{subject}"&lt;br&gt;
  missing_object: "No %{subject}"&lt;br&gt;
  new: New&lt;br&gt;
  new_action: "New %{subject}"&lt;br&gt;
  register_action: "Register %{subject}"&lt;br&gt;
  summary: Summary&lt;br&gt;
  view: View&lt;br&gt;
  view_action: "View %{subject}"&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;I18n is powerful and flexible, and really makes it easy to humanize your applications. There are other complexities that I have not covered here, like right-to-left languages, supporting many languages at once, and long form content, but I hope this article has given you a good starting point.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
    </item>
  </channel>
</rss>
