-
Notifications
You must be signed in to change notification settings - Fork 7
Real World Examples
|
This page presents practical patterns and complete examples for common use cases. Each example is production-ready and addresses real-world requirements.
An online store serving customers across Europe with region-specific pricing and content.
- Support English, French, German, Spanish, Italian
- Regional variants for pricing (EUR vs GBP)
- Fallback chain for similar languages
# config/locales.rb
module StoreLocales
SUPPORTED = {
en: { currency: "GBP", region: "UK" },
"en-US": { currency: "USD", region: "US" },
fr: { currency: "EUR", region: "FR" },
"fr-BE": { currency: "EUR", region: "BE" },
de: { currency: "EUR", region: "DE" },
"de-AT": { currency: "EUR", region: "AT" },
"de-CH": { currency: "CHF", region: "CH" },
es: { currency: "EUR", region: "ES" },
it: { currency: "EUR", region: "IT" }
}.freeze
AVAILABLE = SUPPORTED.keys.freeze
DEFAULT = :en
def self.config_for(locale)
SUPPORTED[locale] || SUPPORTED[DEFAULT]
end
end# app/controllers/concerns/store_locale.rb
module StoreLocale
extend ActiveSupport::Concern
included do
before_action :set_store_locale
helper_method :current_locale, :current_currency, :current_region
end
private
def set_store_locale
@current_locale = detect_locale
@locale_config = StoreLocales.config_for(@current_locale)
I18n.locale = @current_locale
end
def detect_locale
locale_from_params ||
locale_from_session ||
locale_from_header ||
StoreLocales::DEFAULT
end
def locale_from_params
locale = params[:locale]&.to_sym
return locale if StoreLocales::AVAILABLE.include?(locale)
nil
end
def locale_from_session
locale = session[:locale]&.to_sym
return locale if StoreLocales::AVAILABLE.include?(locale)
nil
end
def locale_from_header
header = request.headers["HTTP_ACCEPT_LANGUAGE"]
return if header.nil?
AcceptLanguage.parse(header).match(*StoreLocales::AVAILABLE)
end
def current_locale
@current_locale
end
def current_currency
@locale_config[:currency]
end
def current_region
@locale_config[:region]
end
end<!-- app/views/products/show.html.erb -->
<h1><%= @product.name %></h1>
<p class="price">
<%= number_to_currency(@product.price, unit: current_currency) %>
</p>
<p><%= t("shipping_info", region: current_region) %></p>A SaaS application where users can set their preferred language, with browser detection as fallback for new users.
- Logged-in users: use saved preference
- New visitors: detect from browser
- Support for team-wide default language
# app/models/user.rb
class User < ApplicationRecord
belongs_to :team
def effective_locale
preferred_locale || team&.default_locale || I18n.default_locale
end
end# app/models/team.rb
class Team < ApplicationRecord
has_many :users
validates :default_locale, inclusion: { in: I18n.available_locales.map(&:to_s) },
allow_nil: true
end# app/controllers/concerns/saas_locale.rb
module SaasLocale
extend ActiveSupport::Concern
included do
before_action :set_locale
end
private
def set_locale
I18n.locale = determine_locale
end
def determine_locale
if user_signed_in?
current_user.effective_locale
else
locale_from_header || I18n.default_locale
end
end
def locale_from_header
header = request.headers["HTTP_ACCEPT_LANGUAGE"]
return if header.nil?
AcceptLanguage.parse(header).match(*I18n.available_locales)
end
end# app/controllers/user_preferences_controller.rb
class UserPreferencesController < ApplicationController
def update
if current_user.update(user_params)
redirect_to settings_path, notice: t("preferences.updated")
else
render :edit
end
end
private
def user_params
params.require(:user).permit(:preferred_locale)
end
endA REST API that returns localized content based on the Accept-Language header.
- Return localized error messages
- Include content language in response
- Support quality values for nuanced preferences
# app/controllers/api/v1/base_controller.rb
module Api
module V1
class BaseController < ActionController::API
before_action :set_locale
after_action :set_content_language_header
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::ParameterMissing, with: :bad_request
private
def set_locale
I18n.locale = detect_locale || I18n.default_locale
end
def detect_locale
header = request.headers["HTTP_ACCEPT_LANGUAGE"]
return if header.nil?
AcceptLanguage.parse(header).match(*I18n.available_locales)
end
def set_content_language_header
response.headers["Content-Language"] = I18n.locale.to_s
end
def not_found
render json: { error: t("api.errors.not_found") }, status: :not_found
end
def bad_request(exception)
render json: { error: t("api.errors.bad_request", param: exception.param) },
status: :bad_request
end
end
end
end# app/controllers/api/v1/articles_controller.rb
module Api
module V1
class ArticlesController < BaseController
def index
@articles = Article.published.includes(:translations)
render json: serialize_articles(@articles)
end
def show
@article = Article.find(params[:id])
render json: serialize_article(@article)
end
private
def serialize_articles(articles)
articles.map { |a| serialize_article(a) }
end
def serialize_article(article)
{
id: article.id,
title: article.title_for(I18n.locale),
body: article.body_for(I18n.locale),
locale: I18n.locale.to_s,
available_locales: article.available_locales
}
end
end
end
endRequest:
GET /api/v1/articles/42 Accept-Language: fr-CH, fr;q=0.9, en;q=0.8
Response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Language: fr
{
"id": 42,
"title": "Introduction à Ruby",
"body": "Ruby est un langage de programmation...",
"locale": "fr",
"available_locales": ["en", "fr", "de"]
}
Pre-generate locale-specific redirects for a static site.
# scripts/generate_redirects.rb
require "accept_language"
SUPPORTED_LOCALES = [:en, :fr, :de, :ja]
DEFAULT_LOCALE = :en
# Common Accept-Language patterns to pre-compute
COMMON_PATTERNS = [
"en-US,en;q=0.9",
"en-GB,en;q=0.9",
"fr-FR,fr;q=0.9,en;q=0.8",
"fr-CA,fr;q=0.9,en;q=0.8",
"de-DE,de;q=0.9,en;q=0.8",
"de-AT,de;q=0.9,en;q=0.8",
"de-CH,de;q=0.9,fr;q=0.8,en;q=0.7",
"ja-JP,ja;q=0.9,en;q=0.8",
"zh-CN,zh;q=0.9,en;q=0.8",
"es-ES,es;q=0.9,en;q=0.8"
]
results = COMMON_PATTERNS.map do |pattern|
locale = AcceptLanguage.parse(pattern).match(*SUPPORTED_LOCALES) || DEFAULT_LOCALE
{ pattern: pattern, locale: locale }
end
puts "Redirect mappings:"
results.each do |r|
puts " #{r[:pattern]} => /#{r[:locale]}/"
endOutput:
Redirect mappings: en-US,en;q=0.9 => /en/ en-GB,en;q=0.9 => /en/ fr-FR,fr;q=0.9,en;q=0.8 => /fr/ fr-CA,fr;q=0.9,en;q=0.8 => /fr/ de-DE,de;q=0.9,en;q=0.8 => /de/ de-AT,de;q=0.9,en;q=0.8 => /de/ de-CH,de;q=0.9,fr;q=0.8,en;q=0.7 => /de/ ja-JP,ja;q=0.9,en;q=0.8 => /ja/ zh-CN,zh;q=0.9,en;q=0.8 => /en/ es-ES,es;q=0.9,en;q=0.8 => /en/
A gaming platform with region-locked content and language preferences.
- Match language with available game localizations
- Fall back gracefully for partially localized games
- Track language for analytics
# app/models/game.rb
class Game < ApplicationRecord
serialize :supported_locales, Array
def best_locale_for(header)
return default_locale if header.nil?
AcceptLanguage.parse(header).match(*supported_locales.map(&:to_sym)) ||
default_locale
end
def default_locale
supported_locales.first&.to_sym || :en
end
end# app/controllers/games_controller.rb
class GamesController < ApplicationController
def show
@game = Game.find(params[:id])
@game_locale = @game.best_locale_for(accept_language_header)
track_locale_selection(@game, @game_locale)
end
private
def accept_language_header
request.headers["HTTP_ACCEPT_LANGUAGE"]
end
def track_locale_selection(game, locale)
Analytics.track(
event: "game_locale_selected",
properties: {
game_id: game.id,
selected_locale: locale,
available_locales: game.supported_locales,
accept_language: accept_language_header
}
)
end
end<!-- app/views/games/show.html.erb -->
<h1><%= @game.title(@game_locale) %></h1>
<p><%= @game.description(@game_locale) %></p>
<% if @game_locale != @game.supported_locales.first.to_sym %>
<div class="notice">
<%= t("games.partial_localization", locale: @game_locale) %>
</div>
<% end %>A documentation site with multiple versions and languages.
# lib/docs_locale.rb
require "accept_language"
class DocsLocale
VERSIONS = {
"v3" => [:en, :fr, :de, :ja, :zh],
"v2" => [:en, :fr, :de],
"v1" => [:en]
}.freeze
DEFAULT_VERSION = "v3"
DEFAULT_LOCALE = :en
def initialize(version: nil, accept_language: nil)
@version = version || DEFAULT_VERSION
@accept_language = accept_language
end
def available_locales
VERSIONS[@version] || VERSIONS[DEFAULT_VERSION]
end
def best_locale
return DEFAULT_LOCALE if @accept_language.nil?
AcceptLanguage.parse(@accept_language).match(*available_locales) ||
DEFAULT_LOCALE
end
def locale_available?(locale)
available_locales.include?(locale.to_sym)
end
end# app/controllers/docs_controller.rb
class DocsController < ApplicationController
before_action :set_docs_locale
def show
@doc = Doc.find_by!(
version: params[:version],
slug: params[:slug],
locale: @docs_locale.best_locale
)
rescue ActiveRecord::RecordNotFound
# Try English fallback
@doc = Doc.find_by!(
version: params[:version],
slug: params[:slug],
locale: :en
)
end
private
def set_docs_locale
@docs_locale = DocsLocale.new(
version: params[:version],
accept_language: request.headers["HTTP_ACCEPT_LANGUAGE"]
)
end
endSend emails in the user's preferred language.
# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
def mail_with_locale(user:, **args)
I18n.with_locale(locale_for(user)) do
mail(**args)
end
end
private
def locale_for(user)
# Priority: user preference > registration locale > default
user.preferred_locale ||
user.registration_locale ||
I18n.default_locale
end
end# app/models/user.rb
class User < ApplicationRecord
before_create :set_registration_locale
private
def set_registration_locale
return if registration_locale.present?
return if signup_accept_language.nil?
self.registration_locale = AcceptLanguage
.parse(signup_accept_language)
.match(*I18n.available_locales)
end
end# app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
def create
@user = User.new(user_params)
@user.signup_accept_language = request.headers["HTTP_ACCEPT_LANGUAGE"]
if @user.save
WelcomeMailer.welcome(@user).deliver_later
redirect_to dashboard_path
else
render :new
end
end
end# spec/support/shared_examples/locale_detection.rb
RSpec.shared_examples "locale detection" do
context "with Accept-Language header" do
it "detects simple locale" do
get endpoint, headers: { "HTTP_ACCEPT_LANGUAGE" => "fr" }
expect(I18n.locale).to eq(:fr)
end
it "respects quality values" do
get endpoint, headers: { "HTTP_ACCEPT_LANGUAGE" => "de;q=0.8, fr;q=0.9" }
expect(I18n.locale).to eq(:fr)
end
it "handles regional variants" do
get endpoint, headers: { "HTTP_ACCEPT_LANGUAGE" => "en-GB, en;q=0.9" }
expect(I18n.locale).to eq(:en)
end
it "falls back for unsupported locale" do
get endpoint, headers: { "HTTP_ACCEPT_LANGUAGE" => "xx" }
expect(I18n.locale).to eq(I18n.default_locale)
end
end
context "without Accept-Language header" do
it "uses default locale" do
get endpoint
expect(I18n.locale).to eq(I18n.default_locale)
end
end
end# spec/requests/home_spec.rb
RSpec.describe "Home" do
let(:endpoint) { root_path }
it_behaves_like "locale detection"
endThese patterns demonstrate AcceptLanguage in various contexts:
| Use Case | Key Pattern |
|---|---|
| E-Commerce | Locale + currency + region mapping |
| SaaS | User preference > team default > browser |
| REST API | Content-Language response header |
| Static Site | Pre-computed redirects |
| Gaming | Per-resource locale availability |
| Documentation | Version-specific locale support |
| Capture locale at registration |
- Understanding Quality Values — How preferences are ranked
- Basic Filtering Explained — The matching algorithm
- BCP 47 Language Tags — Language tag structure
- Rails Integration Guide — Rails-specific patterns
- Rack Integration Guide — Middleware patterns