Skip to content

Commit

Permalink
Split up responsibilities
Browse files Browse the repository at this point in the history
  • Loading branch information
iain committed Jul 13, 2012
1 parent 0503fc8 commit 3977c5b
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 193 deletions.
10 changes: 3 additions & 7 deletions Rakefile
@@ -1,10 +1,6 @@
require "bundler/gem_tasks"

require 'rake/testtask'
desc 'Test the http_accept_language plugin.'
Rake::TestTask.new(:test) do |t|
t.pattern = 'test/**/*_test.rb'
end
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)

desc 'Default: run unit tests.'
task :default => :test
task :default => :spec
97 changes: 2 additions & 95 deletions lib/http_accept_language.rb
@@ -1,96 +1,3 @@
module HttpAcceptLanguage

# Returns a sorted array based on user preference in HTTP_ACCEPT_LANGUAGE.
# Browsers send this HTTP header, so don't think this is holy.
#
# Example:
#
# request.user_preferred_languages
# # => [ 'nl-NL', 'nl-BE', 'nl', 'en-US', 'en' ]
#
def user_preferred_languages
@user_preferred_languages ||= env['HTTP_ACCEPT_LANGUAGE'].split(/\s*,\s*/).collect do |l|
l += ';q=1.0' unless l =~ /;q=\d+\.\d+$/
l.split(';q=')
end.sort do |x,y|
raise "Not correctly formatted" unless x.first =~ /^[a-z\-0-9]+$/i
y.last.to_f <=> x.last.to_f
end.collect do |l|
l.first.downcase.gsub(/-[a-z0-9]+$/i) { |x| x.upcase }
end
rescue # Just rescue anything if the browser messed up badly.
[]
end

# Sets the user languages preference, overiding the browser
#
def user_preferred_languages=(languages)
@user_preferred_languages = languages
end

# Finds the locale specifically requested by the browser.
#
# Example:
#
# request.preferred_language_from I18n.available_locales
# # => 'nl'
#
def preferred_language_from(array)
(user_preferred_languages & array.collect { |i| i.to_s }).first
end

# Returns the first of the user_preferred_languages that is compatible
# with the available locales. Ignores region.
#
# Example:
#
# request.compatible_language_from I18n.available_locales
#
def compatible_language_from(available_languages)
user_preferred_languages.map do |x| #en-US
available_languages.find do |y| # en
y = y.to_s
x == y || x.split('-', 2).first == y.split('-', 2).first
end
end.compact.first
end

# Returns a supplied list of available locals without any extra application info
# that may be attached to the locale for storage in the application.
#
# Example:
# [ja_JP-x1, en-US-x4, en_UK-x5, fr-FR-x3] => [ja-JP, en-US, en-UK, fr-FR]
#
def sanitize_available_locales(available_languages)
available_languages.map do |avail|
split_locale = avail.split(/[_-]/)

split_locale.map do |e|
e unless e.match(/x|[0-9*]/)
end.compact.join("-")
end
end

# Returns the first of the user preferred languages that is
# also found in available languages. Finds best fit by matching on
# primary language first and secondarily on region. If no matching region is
# found, return the first language in the group matching that primary language.
#
# Example:
#
# request.language_region_compatible(available_languages)
#
def language_region_compatible_from(available_languages)
available_languages = sanitize_available_locales(available_languages)
user_preferred_languages.map do |x| #en-US
lang_group = available_languages.select do |y| # en
y = y.to_s
x.split('-', 2).first == y.split('-', 2).first
end
lang_group.find{|l| l == x} || lang_group.first #en-US, en-UK
end.compact.first
end

end

require 'http_accept_language/parser'
require 'http_accept_language/rack'
require 'http_accept_language/rails' if defined?(ActionPack)
104 changes: 104 additions & 0 deletions lib/http_accept_language/parser.rb
@@ -0,0 +1,104 @@
module HttpAcceptLanguage

class Parser

attr_reader :env

def initialize(env)
@env = env
end

# Returns a sorted array based on user preference in HTTP_ACCEPT_LANGUAGE.
# Browsers send this HTTP header, so don't think this is holy.
#
# Example:
#
# request.user_preferred_languages
# # => [ 'nl-NL', 'nl-BE', 'nl', 'en-US', 'en' ]
#
def user_preferred_languages
@user_preferred_languages ||= env['HTTP_ACCEPT_LANGUAGE'].split(/\s*,\s*/).collect do |l|
l += ';q=1.0' unless l =~ /;q=\d+\.\d+$/
l.split(';q=')
end.sort do |x,y|
raise "Not correctly formatted" unless x.first =~ /^[a-z\-0-9]+$/i
y.last.to_f <=> x.last.to_f
end.collect do |l|
l.first.downcase.gsub(/-[a-z0-9]+$/i) { |x| x.upcase }
end
rescue # Just rescue anything if the browser messed up badly.
[]
end

# Sets the user languages preference, overiding the browser
#
def user_preferred_languages=(languages)
@user_preferred_languages = languages
end

# Finds the locale specifically requested by the browser.
#
# Example:
#
# request.preferred_language_from I18n.available_locales
# # => 'nl'
#
def preferred_language_from(array)
(user_preferred_languages & array.collect { |i| i.to_s }).first
end

# Returns the first of the user_preferred_languages that is compatible
# with the available locales. Ignores region.
#
# Example:
#
# request.compatible_language_from I18n.available_locales
#
def compatible_language_from(available_languages)
user_preferred_languages.map do |x| #en-US
available_languages.find do |y| # en
y = y.to_s
x == y || x.split('-', 2).first == y.split('-', 2).first
end
end.compact.first
end

# Returns a supplied list of available locals without any extra application info
# that may be attached to the locale for storage in the application.
#
# Example:
# [ja_JP-x1, en-US-x4, en_UK-x5, fr-FR-x3] => [ja-JP, en-US, en-UK, fr-FR]
#
def sanitize_available_locales(available_languages)
available_languages.map do |avail|
split_locale = avail.split(/[_-]/)

split_locale.map do |e|
e unless e.match(/x|[0-9*]/)
end.compact.join("-")
end
end

# Returns the first of the user preferred languages that is
# also found in available languages. Finds best fit by matching on
# primary language first and secondarily on region. If no matching region is
# found, return the first language in the group matching that primary language.
#
# Example:
#
# request.language_region_compatible(available_languages)
#
def language_region_compatible_from(available_languages)
available_languages = sanitize_available_locales(available_languages)
user_preferred_languages.map do |x| #en-US
lang_group = available_languages.select do |y| # en
y = y.to_s
x.split('-', 2).first == y.split('-', 2).first
end
lang_group.find{|l| l == x} || lang_group.first #en-US, en-UK
end.compact.first
end

end

end
16 changes: 1 addition & 15 deletions lib/http_accept_language/rack.rb
@@ -1,5 +1,3 @@
require 'http_accept_language'

module HttpAcceptLanguage
class Rack

Expand All @@ -9,22 +7,10 @@ def initialize(app)

def call(env)
def env.http_accept_language
@http_accept_language ||= HttpAcceptLanguage.new(self)
@http_accept_language ||= Parser.new(self)
end
@app.call(env)
end

class HttpAcceptLanguage
include ::HttpAcceptLanguage

attr_reader :env

def initialize(env)
@env = env
end

end

end
end

22 changes: 21 additions & 1 deletion lib/http_accept_language/rails.rb
@@ -1,7 +1,27 @@
require 'forwardable'

module HttpAcceptLanguage
module Rails

extend Forwardable

def http_accept_language_parser
@http_accept_language_parser ||= Parser.new(env)
end

def_delegators :http_accept_language_parser,
:user_preferred_languages, :user_preferred_languages=,
:preferred_language_from, :compatible_language_from,
:sanitize_available_locales, :language_region_compatible_from

end

end

classes = if ActionPack::VERSION::MAJOR == 2
[ActionController::Request, ActionController::CgiRequest]
else
[ActionDispatch::Request]
end

classes.each { |c| c.send :include, HttpAcceptLanguage }
classes.each { |c| c.send :include, HttpAcceptLanguage::Rails }
58 changes: 0 additions & 58 deletions spec/http_accept_language_spec.rb

This file was deleted.

51 changes: 51 additions & 0 deletions spec/parser_spec.rb
@@ -0,0 +1,51 @@
require 'http_accept_language/parser'

describe HttpAcceptLanguage::Parser do

def parser
@parser ||= HttpAcceptLanguage::Parser.new('HTTP_ACCEPT_LANGUAGE' => 'en-us,en-gb;q=0.8,en;q=0.6,es-419')
end

it "should return empty array" do
parser.env['HTTP_ACCEPT_LANGUAGE'] = nil
parser.user_preferred_languages.should eq []
end

it "should properly split" do
parser.user_preferred_languages.should eq %w{en-US es-419 en-GB en}
end

it "should ignore jambled header" do
parser.env['HTTP_ACCEPT_LANGUAGE'] = 'odkhjf89fioma098jq .,.,'
parser.user_preferred_languages.should eq []
end

it "should find first available language" do
parser.preferred_language_from(%w{en en-GB}).should eq "en-GB"
end

it "should find first compatible language" do
parser.compatible_language_from(%w{en-hk}).should eq "en-hk"
parser.compatible_language_from(%w{en}).should eq "en"
end

it "should find first compatible from user preferred" do
parser.env['HTTP_ACCEPT_LANGUAGE'] = 'en-us,de-de'
parser.compatible_language_from(%w{de en}).should eq 'en'
end

it "should accept symbols as available languages" do
parser.env['HTTP_ACCEPT_LANGUAGE'] = 'en-us'
parser.compatible_language_from([:"en-HK"]).should eq :"en-HK"
end

it "should sanitize available language names" do
parser.sanitize_available_locales(%w{en_UK-x3 en-US-x1 ja_JP-x2 pt-BR-x5}).should eq ["en-UK", "en-US", "ja-JP", "pt-BR"]
end

it "should find most compatible language from user preferred" do
parser.env['HTTP_ACCEPT_LANGUAGE'] = 'ja,en-gb,en-us,fr-fr'
parser.language_region_compatible_from(%w{en-UK en-US ja-JP}).should eq "ja-JP"
end

end
2 changes: 1 addition & 1 deletion spec/rack_spec.rb
@@ -1,4 +1,4 @@
require 'http_accept_language/rack'
require 'http_accept_language'
require 'rack/test'
require 'json'

Expand Down

0 comments on commit 3977c5b

Please sign in to comment.