How to do IP Geolocation in Ruby on Rails

Last Updated Jan 15, 2021
Emma Jagger

Engineer, maker, Google alumna, CMU grad

Rails include many mechanisms based on the location of visitors to display information that corresponds to their geographical context without requiring too much effort on the part of developers. The I18n Internationalization API, an integral part of Rails since version 2.2, provides a powerful framework for creating multilingual applications. I18n manages translations, date and time formats, names of days and months, pluralization rules, and much more.  This is also a common requirement on web apps, where due to regulations like GDPR in the European Union you need the user's explicit consent via cookie banner.

For example, the helper method number_to_currency, which takes an integer or decimal number as input, returns a string representing a price formatted according to the requested locale.


number_to_currency 1234.50
# => $1,234.50

number_to_currency 1234.50, locale: :fr
# => 1 234,50 €

Displaying a price formatted in the visitor's locale is very easy in Rails, but it remains the developer's responsibility to convert currency values, which is out of the scope of this article.

Setting up the localization in your Rails project via I18n

To translate all the string used by core Rails, you need only to install the rails-i18n gem, which provides the translation in more than a hundred languages. You can also write your own translation in the files located in the config/locales directory.

Add one of the following lines to your Gemfile:


gem 'rails-i18n', '~> 6.0.0' # For 6.0.0 or higher
gem 'rails-i18n', '~> 5.1' # For 5.0.x, 5.1.x and 5.2.x
gem 'rails-i18n', '~> 4.0' # For 4.0.x
gem 'rails-i18n', '~> 3.0' # For 3.x

Then update your bundle:


bundle update

You must then set up a mechanism that allows the controller to determine which locale to use for the current request. Here is the list of elements to consider:

  • the locale is saved in the user's session,
  • a user can manually change the locale if he prefers to use your site in another language
  • the system falls back to the default locale.

You can implement this function in a before_action of all your controllers:


class ApplicationController < ActionController::Base
  before_action :set_locale

  protected def set_locale
    I18n.locale = params[:locale] || # 1: use request parameter, if available
      session[:locale] ||            # 2: use the value saved in iurrent session
      I18n.default_locale            # last: fallback to default locale
  end
end

You also need to save the selected locale in the user's session:


class ApplicationController < ActionController::Base
  before_action :set_locale
  after_action :save_locale

  protected def set_locale
    I18n.locale = params[:locale] || # 1: use request parameter, if available
      session[:locale] ||            # 2: use the value saved in iurrent session
      I18n.default_locale            # last: fallback to default locale
  end

  protected def save_locale
    session[:locale] = I18n.locale
  end
end

Add automatic locale detection with IP geolocation

One of the most convenient features for a website is to automatically detect new visitors' geographical location and display itself in the locality corresponding to their region. This can be implemented through geolocation.

To do so, it is possible to use the geoip gem, which searches through a database to match an IP address to its geographical location.

Install the geoip gem in your Gemfile:


gem 'geoip'

Then update your bundle:


bundle update

Download the geolocation database from the MaxMind website, and extract the files in your Rails directory.

You can then use the gem's API to obtain the geographical location of your visitor from their IP address:


require 'geoip'

maxmind_db_location = '/path/to/GeoLite2-City.mmdb'
ip_address = '173.194.112.35'

g = GeoIP.new maxmind_db_location
loc = g.city ip_address
puts loc.country_code
# => US

However, this is not the easiest method, mainly because it requires a lot of maintenance as you will have to regularly download every MaxMind database updates in order to keep your geolocation accurate. It may not contain all the information you need, such as whether the IP address is using a VPN or proxy service.

Using the free Abstract geolocation service

A simple and very effective alternative is to use the free Abstract API geolocation service. A simple HTTP call allows determining a user's locale, and the Abstract database always remains up-to-date without requiring any maintenance from you. This is how to integrate it in your controllers:


class ApplicationController < ActionController::Base
  before_action :set_locale
  after_action :save_locale

  protected def set_locale
    I18n.locale = params[:locale] || # 1: use request parameter, if available
      session[:locale] ||            # 2: use the value saved in iurrent session
      geo_locate_from_ip ||          # 3: gets locale from geolocation
      I18n.default_locale            # last: fallback to default locale
  end

  protected def save_locale
    session[:locale] = I18n.locale
  end

  private def geo_locate_from_ip
    api_key = ENV['ABSTRACT_API_KEY'] # You need to secretly set your api key
    ip_address = request.remote_ip # Get IP address

    # Request geolocation from Abstract end-point
    uri = URI("https://ipgeolocation.abstractapi.com/v1/?api_key=#{api_key}&ip_address=#{ip_address}")

    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER

    request =  Net::HTTP::Get.new(uri)

    response = JSON.parse http.request(request)

    # Return locale code from country code (as a Symbol)
    response['country_code'].downcase.to_sym
  end
end

Abstract's IP Geolocation API comes with Ruby on Rails libraries, code snippets, guides, and more.
Get started