http_accept_language_v2 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/tests.yml +25 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/Gemfile +3 -0
- data/Guardfile +8 -0
- data/README.md +125 -0
- data/Rakefile +13 -0
- data/cucumber.yml +2 -0
- data/features/rails_integration.feature +29 -0
- data/features/steps/rails.rb +37 -0
- data/features/support/rails_driver.rb +93 -0
- data/http_accept_language.gemspec +29 -0
- data/lib/http_accept_language.rb +4 -0
- data/lib/http_accept_language/auto_locale.rb +21 -0
- data/lib/http_accept_language/middleware.rb +17 -0
- data/lib/http_accept_language/parser.rb +108 -0
- data/lib/http_accept_language/railtie.rb +17 -0
- data/lib/http_accept_language/version.rb +3 -0
- data/spec/auto_locale_spec.rb +71 -0
- data/spec/middleware_spec.rb +60 -0
- data/spec/parser_spec.rb +65 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3674378e35da2c8cb7ff2f7dff176ab8d1037158b3c2024690143064b4cfed72
|
4
|
+
data.tar.gz: d57a9880c727af8b8b084de1818f656fd516561f7b03b7397775ac066dab9400
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4e7d7b7d180be9f80e1e4637235624070203d0514a1b57d0e58b3cd01c1a0b0c9881f7b238716c25a1d47435f53555e81886d19af5af82bb6f44df0e6704a2ac
|
7
|
+
data.tar.gz: e768458d391b2a5cfe628ddd62fcffe52bb5ce95be6fbc888841ef56c6433001981bee3d8e8e94f2a499a37cada119841431d3ba8d2633046c8358e907038d05
|
@@ -0,0 +1,25 @@
|
|
1
|
+
on:
|
2
|
+
push
|
3
|
+
|
4
|
+
jobs:
|
5
|
+
rspec:
|
6
|
+
name: Rspec Ruby ${{ matrix.ruby }} Rails ${{ matrix.rails }}
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
rails: [5.2, 6.0, 6.1]
|
11
|
+
ruby: [2.6, 2.7, 3.0]
|
12
|
+
runs-on: ubuntu-latest
|
13
|
+
steps:
|
14
|
+
- uses: actions/checkout@v2
|
15
|
+
- uses: actions/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: ${{ matrix.ruby }}
|
18
|
+
- name: Specs
|
19
|
+
env:
|
20
|
+
RAILS_VERSION: ${{ matrix.rails }}
|
21
|
+
run: |
|
22
|
+
gem install bundler
|
23
|
+
bundle install --jobs 4 --retry 3
|
24
|
+
gem install rails --version "=$RAILS_VERSION"
|
25
|
+
bundle exec rspec spec
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
# HttpAcceptLanguage [![Build Status](https://travis-ci.org/iain/http_accept_language.svg?branch=master)](https://travis-ci.org/iain/http_accept_language)
|
2
|
+
|
3
|
+
A gem which helps you detect the users preferred language, as sent by the "Accept-Language" HTTP header.
|
4
|
+
|
5
|
+
The algorithm is based on [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html), with one exception:
|
6
|
+
when a user requests "en-US" and "en" is an available language, "en" is deemed compatible with "en-US".
|
7
|
+
The RFC specifies that the requested language must either exactly match the available language or must exactly match a prefix of the available language. This means that when the user requests "en" and "en-US" is available, "en-US" would be compatible, but not the other way around. This is usually not what you're looking for.
|
8
|
+
|
9
|
+
Since version 2.0, this gem is Rack middleware.
|
10
|
+
|
11
|
+
## Example
|
12
|
+
|
13
|
+
The `http_accept_language` method is available in any controller:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class SomeController < ApplicationController
|
17
|
+
def some_action
|
18
|
+
http_accept_language.user_preferred_languages # => %w(nl-NL nl-BE nl en-US en)
|
19
|
+
available = %w(en en-US nl-BE)
|
20
|
+
http_accept_language.preferred_language_from(available) # => 'nl-BE'
|
21
|
+
|
22
|
+
http_accept_language.user_preferred_languages # => %w(en-GB)
|
23
|
+
available = %w(en-US)
|
24
|
+
http_accept_language.compatible_language_from(available) # => 'en-US'
|
25
|
+
|
26
|
+
http_accept_language.user_preferred_languages # => %w(nl-NL nl-BE nl en-US en)
|
27
|
+
available = %w(en nl de) # This could be from I18n.available_locales
|
28
|
+
http_accept_language.preferred_language_from(available) # => 'nl'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
You can easily set the locale used for i18n in a before-filter:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class SomeController < ApplicationController
|
37
|
+
before_filter :set_locale
|
38
|
+
|
39
|
+
private
|
40
|
+
def set_locale
|
41
|
+
I18n.locale = http_accept_language.compatible_language_from(I18n.available_locales)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
If you want to enable this behavior by default in your controllers, you can just include the provided concern:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class ApplicationController < ActionController::Base
|
50
|
+
include HttpAcceptLanguage::AutoLocale
|
51
|
+
|
52
|
+
#...
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
Then set available locales in `config/application.rb`:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
config.i18n.available_locales = %w(en nl de fr)
|
60
|
+
```
|
61
|
+
|
62
|
+
To use the middleware in any Rack application, simply add the middleware:
|
63
|
+
|
64
|
+
``` ruby
|
65
|
+
require 'http_accept_language'
|
66
|
+
use HttpAcceptLanguage::Middleware
|
67
|
+
run YourAwesomeApp
|
68
|
+
```
|
69
|
+
|
70
|
+
Then you can access it from `env`:
|
71
|
+
|
72
|
+
``` ruby
|
73
|
+
class YourAwesomeApp
|
74
|
+
|
75
|
+
def initialize(app)
|
76
|
+
@app = app
|
77
|
+
end
|
78
|
+
|
79
|
+
def call(env)
|
80
|
+
available = %w(en en-US nl-BE)
|
81
|
+
language = env.http_accept_language.preferred_language_from(available)
|
82
|
+
|
83
|
+
[200, {}, ["Oh, you speak #{language}!"]]
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
```
|
88
|
+
|
89
|
+
## Available methods
|
90
|
+
|
91
|
+
* **user_preferred_languages**:
|
92
|
+
Returns a sorted array based on user preference in HTTP_ACCEPT_LANGUAGE, sanitized and all.
|
93
|
+
* **preferred_language_from(languages)**:
|
94
|
+
Finds the locale specifically requested by the browser
|
95
|
+
* **compatible_language_from(languages)**:
|
96
|
+
Returns the first of the user_preferred_languages that is compatible with the available locales.
|
97
|
+
Ignores region.
|
98
|
+
* **sanitize_available_locales(languages)**:
|
99
|
+
Returns a supplied list of available locals without any extra application info
|
100
|
+
that may be attached to the locale for storage in the application.
|
101
|
+
* **language_region_compatible_from(languages)**:
|
102
|
+
Returns the first of the user preferred languages that is
|
103
|
+
also found in available languages. Finds best fit by matching on
|
104
|
+
primary language first and secondarily on region. If no matching region is
|
105
|
+
found, return the first language in the group matching that primary language.
|
106
|
+
|
107
|
+
## Installation
|
108
|
+
|
109
|
+
### Without Bundler
|
110
|
+
|
111
|
+
Install the gem `http_accept_language`
|
112
|
+
|
113
|
+
### With Bundler
|
114
|
+
|
115
|
+
Add the gem to your Gemfile:
|
116
|
+
|
117
|
+
``` ruby
|
118
|
+
gem 'http_accept_language'
|
119
|
+
```
|
120
|
+
|
121
|
+
Run `bundle install` to install it.
|
122
|
+
|
123
|
+
---
|
124
|
+
|
125
|
+
Released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
5
|
+
|
6
|
+
require 'cucumber/rake/task'
|
7
|
+
Cucumber::Rake::Task.new(:cucumber)
|
8
|
+
|
9
|
+
Cucumber::Rake::Task.new(:wip, "Run features tagged with @wip") do |t|
|
10
|
+
t.profile = "wip"
|
11
|
+
end
|
12
|
+
|
13
|
+
task :default => [:spec, :cucumber, :wip]
|
data/cucumber.yml
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
@rails
|
2
|
+
Feature: Rails Integration
|
3
|
+
|
4
|
+
To use http_accept_language inside a Rails application, just add it to your
|
5
|
+
Gemfile and run `bundle install`.
|
6
|
+
|
7
|
+
It is automatically added to your middleware.
|
8
|
+
|
9
|
+
Scenario: Installing
|
10
|
+
When I generate a new Rails app
|
11
|
+
And I add http_accept_language to my Gemfile
|
12
|
+
And I run `rake middleware`
|
13
|
+
Then the output should contain "use HttpAcceptLanguage::Middleware"
|
14
|
+
|
15
|
+
Scenario: Using
|
16
|
+
Given I have installed http_accept_language
|
17
|
+
When I generate the following controller:
|
18
|
+
"""
|
19
|
+
class LanguagesController < ApplicationController
|
20
|
+
|
21
|
+
def index
|
22
|
+
languages = http_accept_language.user_preferred_languages
|
23
|
+
render :text => "Languages: #{languages.join(' : ')}"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
"""
|
28
|
+
When I access that action with the HTTP_ACCEPT_LANGUAGE header "en-us,en-gb;q=0.8,en;q=0.6,es-419"
|
29
|
+
Then the response should contain "Languages: en-US : es-419 : en-GB : en"
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Before "@rails" do
|
2
|
+
@rails = RailsDriver.new
|
3
|
+
end
|
4
|
+
|
5
|
+
When /^I generate a new Rails app$/ do
|
6
|
+
@rails.generate_rails
|
7
|
+
end
|
8
|
+
|
9
|
+
When /^I add http_accept_language to my Gemfile$/ do
|
10
|
+
@rails.append_gemfile
|
11
|
+
end
|
12
|
+
|
13
|
+
Given /^I have installed http_accept_language$/ do
|
14
|
+
@rails.install_gem
|
15
|
+
end
|
16
|
+
|
17
|
+
When /^I generate the following controller:$/ do |string|
|
18
|
+
@rails.generate_controller "languages", string
|
19
|
+
end
|
20
|
+
|
21
|
+
When /^I access that action with the HTTP_ACCEPT_LANGUAGE header "(.*?)"$/ do |header|
|
22
|
+
@rails.with_rails_running do
|
23
|
+
@rails.request_with_http_accept_language_header(header, "/languages")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Then /^the response should contain "(.*?)"$/ do |output|
|
28
|
+
@rails.output_should_contain(output)
|
29
|
+
end
|
30
|
+
|
31
|
+
When /^I run `rake middleware`$/ do
|
32
|
+
@rails.bundle_exec("rake middleware")
|
33
|
+
end
|
34
|
+
|
35
|
+
Then /^the output should contain "(.*?)"$/ do |expected|
|
36
|
+
@rails.assert_partial_output(expected, @rails.all_output)
|
37
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'aruba/api'
|
2
|
+
|
3
|
+
class RailsDriver
|
4
|
+
include Aruba::Api
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@aruba_io_wait_seconds = 10
|
8
|
+
# @announce_stdout = true
|
9
|
+
# @announce_stderr = true
|
10
|
+
# @announce_cmd = true
|
11
|
+
# @announce_dir = true
|
12
|
+
# @announce_env = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def app_name
|
16
|
+
"foobar"
|
17
|
+
end
|
18
|
+
|
19
|
+
def install_gem
|
20
|
+
if app_exists?
|
21
|
+
cd app_name
|
22
|
+
else
|
23
|
+
generate_rails
|
24
|
+
append_gemfile
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def app_exists?
|
29
|
+
in_current_dir do
|
30
|
+
File.exist?("#{app_name}/Gemfile")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def bundle_exec(cmd)
|
35
|
+
run_simple "bundle exec #{cmd}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def generate_rails
|
39
|
+
# install rails with as few things as possible, for speed!
|
40
|
+
bundle_exec "rails new #{app_name} --force --skip-git --skip-active-record --skip-sprockets --skip-javascript --skip-test-unit --old-style-hash"
|
41
|
+
cd app_name
|
42
|
+
end
|
43
|
+
|
44
|
+
def append_gemfile
|
45
|
+
# Specifiy a path so cucumber will use the unreleased version of the gem
|
46
|
+
append_to_file "Gemfile", "gem 'http_accept_language', :path => '#{gem_path}'"
|
47
|
+
end
|
48
|
+
|
49
|
+
def gem_path
|
50
|
+
File.expand_path('../../../', __FILE__)
|
51
|
+
end
|
52
|
+
|
53
|
+
def generate_controller(name, content)
|
54
|
+
bundle_exec "rails generate resource #{name} --force"
|
55
|
+
write_file "app/controllers/#{name}_controller.rb", content
|
56
|
+
end
|
57
|
+
|
58
|
+
def request_with_http_accept_language_header(header, path)
|
59
|
+
run_simple "curl --retry 10 -H 'Accept-language: #{header}' #{File.join(host, path)} -o #{response}"
|
60
|
+
run_simple "cat out.html"
|
61
|
+
end
|
62
|
+
|
63
|
+
def host
|
64
|
+
"http://localhost:13000"
|
65
|
+
end
|
66
|
+
|
67
|
+
def with_rails_running
|
68
|
+
start_rails
|
69
|
+
yield
|
70
|
+
ensure
|
71
|
+
stop_rails
|
72
|
+
end
|
73
|
+
|
74
|
+
def start_rails
|
75
|
+
bundle_exec "rails server -p 13000 -d"
|
76
|
+
end
|
77
|
+
|
78
|
+
def stop_rails
|
79
|
+
in_current_dir do
|
80
|
+
`cat tmp/pids/server.pid | xargs kill -9`
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def response
|
85
|
+
File.expand_path(File.join(current_dir, 'out.html'))
|
86
|
+
end
|
87
|
+
|
88
|
+
def output_should_contain(expected)
|
89
|
+
actual = File.open(response, 'r:utf-8').read
|
90
|
+
actual.should include expected
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "http_accept_language/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "http_accept_language_v2"
|
7
|
+
s.version = HttpAcceptLanguage::VERSION
|
8
|
+
s.authors = ["iain", "yknx4"]
|
9
|
+
s.email = ["iain@iain.nl", "me@ale.world"]
|
10
|
+
s.homepage = "https://github.com/yknx4/http_accept_language"
|
11
|
+
s.summary = %q{Find out which locale the user preferes by reading the languages they specified in their browser}
|
12
|
+
s.description = %q{Find out which locale the user preferes by reading the languages they specified in their browser}
|
13
|
+
s.license = "MIT"
|
14
|
+
|
15
|
+
s.rubyforge_project = "http_accept_language"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency 'rake'
|
23
|
+
s.add_development_dependency 'rspec'
|
24
|
+
s.add_development_dependency 'rack-test'
|
25
|
+
s.add_development_dependency 'guard-rspec'
|
26
|
+
s.add_development_dependency 'rails', ['>= 5.2', '< 7.0']
|
27
|
+
s.add_development_dependency 'cucumber'
|
28
|
+
s.add_development_dependency 'aruba'
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module HttpAcceptLanguage
|
4
|
+
module AutoLocale
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
if respond_to?(:prepend_before_action)
|
9
|
+
prepend_before_action :set_locale
|
10
|
+
else
|
11
|
+
prepend_before_filter :set_locale
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def set_locale
|
18
|
+
I18n.locale = http_accept_language.compatible_language_from(I18n.available_locales) || I18n.default_locale
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module HttpAcceptLanguage
|
2
|
+
class Middleware
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
env["http_accept_language.parser"] = Parser.new(env["HTTP_ACCEPT_LANGUAGE"])
|
9
|
+
|
10
|
+
def env.http_accept_language
|
11
|
+
self["http_accept_language.parser"]
|
12
|
+
end
|
13
|
+
|
14
|
+
@app.call(env)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module HttpAcceptLanguage
|
2
|
+
class Parser
|
3
|
+
attr_accessor :header
|
4
|
+
|
5
|
+
def initialize(header)
|
6
|
+
@header = header
|
7
|
+
end
|
8
|
+
|
9
|
+
# Returns a sorted array based on user preference in HTTP_ACCEPT_LANGUAGE.
|
10
|
+
# Browsers send this HTTP header, so don't think this is holy.
|
11
|
+
#
|
12
|
+
# Example:
|
13
|
+
#
|
14
|
+
# request.user_preferred_languages
|
15
|
+
# # => [ 'nl-NL', 'nl-BE', 'nl', 'en-US', 'en' ]
|
16
|
+
#
|
17
|
+
def user_preferred_languages
|
18
|
+
@user_preferred_languages ||= begin
|
19
|
+
header.to_s.gsub(/\s+/, '').split(',').map do |language|
|
20
|
+
locale, quality = language.split(';q=')
|
21
|
+
raise ArgumentError, 'Not correctly formatted' unless locale =~ /^[a-z\-0-9]+|\*$/i
|
22
|
+
|
23
|
+
locale = locale.downcase.gsub(/-[a-z0-9]+$/i, &:upcase) # Uppercase territory
|
24
|
+
locale = nil if locale == '*' # Ignore wildcards
|
25
|
+
|
26
|
+
quality = quality ? quality.to_f : 1.0
|
27
|
+
|
28
|
+
[locale, quality]
|
29
|
+
end.sort do |(_, left), (_, right)|
|
30
|
+
right <=> left
|
31
|
+
end.map(&:first).compact
|
32
|
+
rescue ArgumentError # Just rescue anything if the browser messed up badly.
|
33
|
+
[]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Sets the user languages preference, overriding the browser
|
38
|
+
#
|
39
|
+
def user_preferred_languages=(languages)
|
40
|
+
@user_preferred_languages = languages
|
41
|
+
end
|
42
|
+
|
43
|
+
# Finds the locale specifically requested by the browser.
|
44
|
+
#
|
45
|
+
# Example:
|
46
|
+
#
|
47
|
+
# request.preferred_language_from I18n.available_locales
|
48
|
+
# # => 'nl'
|
49
|
+
#
|
50
|
+
def preferred_language_from(array)
|
51
|
+
(user_preferred_languages & array.map(&:to_s)).first
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the first of the user_preferred_languages that is compatible
|
55
|
+
# with the available locales. Ignores region.
|
56
|
+
#
|
57
|
+
# Example:
|
58
|
+
#
|
59
|
+
# request.compatible_language_from I18n.available_locales
|
60
|
+
#
|
61
|
+
def compatible_language_from(available_languages)
|
62
|
+
user_preferred_languages.map do |preferred| #en-US
|
63
|
+
preferred = preferred.downcase
|
64
|
+
preferred_language = preferred.split('-', 2).first
|
65
|
+
|
66
|
+
available_languages.find do |available| # en
|
67
|
+
available = available.to_s.downcase
|
68
|
+
preferred == available || preferred_language == available.split('-', 2).first
|
69
|
+
end
|
70
|
+
end.compact.first
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns a supplied list of available locals without any extra application info
|
74
|
+
# that may be attached to the locale for storage in the application.
|
75
|
+
#
|
76
|
+
# Example:
|
77
|
+
# [ja_JP-x1, en-US-x4, en_UK-x5, fr-FR-x3] => [ja-JP, en-US, en-UK, fr-FR]
|
78
|
+
#
|
79
|
+
def sanitize_available_locales(available_languages)
|
80
|
+
available_languages.map do |available|
|
81
|
+
available.to_s.split(/[_-]/).reject { |part| part.start_with?("x") }.join("-")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the first of the user preferred languages that is
|
86
|
+
# also found in available languages. Finds best fit by matching on
|
87
|
+
# primary language first and secondarily on region. If no matching region is
|
88
|
+
# found, return the first language in the group matching that primary language.
|
89
|
+
#
|
90
|
+
# Example:
|
91
|
+
#
|
92
|
+
# request.language_region_compatible(available_languages)
|
93
|
+
#
|
94
|
+
def language_region_compatible_from(available_languages)
|
95
|
+
available_languages = sanitize_available_locales(available_languages)
|
96
|
+
user_preferred_languages.map do |preferred| #en-US
|
97
|
+
preferred = preferred.downcase
|
98
|
+
preferred_language = preferred.split('-', 2).first
|
99
|
+
|
100
|
+
lang_group = available_languages.select do |available| # en
|
101
|
+
preferred_language == available.downcase.split('-', 2).first
|
102
|
+
end
|
103
|
+
|
104
|
+
lang_group.find { |lang| lang.downcase == preferred } || lang_group.first #en-US, en-UK
|
105
|
+
end.compact.first
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module HttpAcceptLanguage
|
2
|
+
class Railtie < ::Rails::Railtie
|
3
|
+
initializer "http_accept_language.add_middleware" do |app|
|
4
|
+
app.middleware.use Middleware
|
5
|
+
|
6
|
+
ActiveSupport.on_load :action_controller do
|
7
|
+
include EasyAccess
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module EasyAccess
|
13
|
+
def http_accept_language
|
14
|
+
@http_accept_language ||= request.env["http_accept_language.parser"] || Parser.new(request.env["HTTP_ACCEPT_LANGUAGE"])
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'i18n'
|
2
|
+
require 'http_accept_language/auto_locale'
|
3
|
+
require 'http_accept_language/parser'
|
4
|
+
require 'http_accept_language/middleware'
|
5
|
+
|
6
|
+
describe HttpAcceptLanguage::AutoLocale do
|
7
|
+
let(:controller_class) do
|
8
|
+
Class.new do
|
9
|
+
def initialize(header = nil)
|
10
|
+
super()
|
11
|
+
@header = header
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.prepend_before_action(dummy)
|
15
|
+
# dummy method
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.prepend_before_filter(dummy)
|
19
|
+
# dummy method
|
20
|
+
end
|
21
|
+
|
22
|
+
def http_accept_language
|
23
|
+
@http_accept_language ||= HttpAcceptLanguage::Parser.new(@header)
|
24
|
+
end
|
25
|
+
|
26
|
+
include HttpAcceptLanguage::AutoLocale
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:controller) { controller_class.new("ja,en-us;q=0.7,en;q=0.3") }
|
31
|
+
|
32
|
+
context "available languages includes accept_languages" do
|
33
|
+
before do
|
34
|
+
I18n.available_locales = [:en, :ja]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "take a suitable locale" do
|
38
|
+
controller.send(:set_locale)
|
39
|
+
|
40
|
+
expect(I18n.locale).to eq(:ja)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "available languages do not include accept_languages" do
|
45
|
+
before do
|
46
|
+
I18n.available_locales = [:es]
|
47
|
+
I18n.default_locale = :es
|
48
|
+
end
|
49
|
+
|
50
|
+
it "set the locale to default" do
|
51
|
+
no_accept_language_controller.send(:set_locale)
|
52
|
+
|
53
|
+
expect(I18n.locale).to eq(:es)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
let(:no_accept_language_controller) { controller_class.new() }
|
58
|
+
|
59
|
+
context "default locale is ja" do
|
60
|
+
before do
|
61
|
+
I18n.available_locales = [:en, :ja]
|
62
|
+
I18n.default_locale = :ja
|
63
|
+
end
|
64
|
+
|
65
|
+
it "set the locale to default" do
|
66
|
+
no_accept_language_controller.send(:set_locale)
|
67
|
+
|
68
|
+
expect(I18n.locale).to eq(:ja)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'http_accept_language'
|
2
|
+
require 'rack/test'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class TestRackApp
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
request = Rack::Request.new(env)
|
9
|
+
http_accept_language = env.http_accept_language
|
10
|
+
result = {
|
11
|
+
:user_preferred_languages => http_accept_language.user_preferred_languages,
|
12
|
+
}
|
13
|
+
if request.params['preferred']
|
14
|
+
result[:preferred_language_from] = http_accept_language.preferred_language_from(request.params['preferred'])
|
15
|
+
end
|
16
|
+
[ 200, {}, [ JSON.generate(result) ]]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "Rack integration" do
|
22
|
+
include Rack::Test::Methods
|
23
|
+
|
24
|
+
def app
|
25
|
+
Rack::Builder.new do
|
26
|
+
use HttpAcceptLanguage::Middleware
|
27
|
+
run TestRackApp.new
|
28
|
+
end.to_app
|
29
|
+
end
|
30
|
+
|
31
|
+
it "handles reuse of the env instance" do
|
32
|
+
env = { "HTTP_ACCEPT_LANGUAGE" => "en" }
|
33
|
+
app = lambda { |env| env }
|
34
|
+
middleware = HttpAcceptLanguage::Middleware.new(app)
|
35
|
+
middleware.call(env)
|
36
|
+
expect(env.http_accept_language.user_preferred_languages).to eq %w{en}
|
37
|
+
env["HTTP_ACCEPT_LANGUAGE"] = "de"
|
38
|
+
middleware.call(env)
|
39
|
+
expect(env.http_accept_language.user_preferred_languages).to eq %w{de}
|
40
|
+
end
|
41
|
+
|
42
|
+
it "decodes the HTTP_ACCEPT_LANGUAGE header" do
|
43
|
+
request_with_header 'en-us,en-gb;q=0.8,en;q=0.6,es-419'
|
44
|
+
expect(r['user_preferred_languages']).to eq %w{en-US es-419 en-GB en}
|
45
|
+
end
|
46
|
+
|
47
|
+
it "finds the first available language" do
|
48
|
+
request_with_header 'en-us,en-gb;q=0.8,en;q=0.6,es-419', :preferred => %w(en en-GB)
|
49
|
+
expect(r['preferred_language_from']).to eq 'en-GB'
|
50
|
+
end
|
51
|
+
|
52
|
+
def request_with_header(header, params = {})
|
53
|
+
get "/", params, 'HTTP_ACCEPT_LANGUAGE' => header
|
54
|
+
end
|
55
|
+
|
56
|
+
def r
|
57
|
+
JSON.parse(last_response.body)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'http_accept_language/parser'
|
2
|
+
|
3
|
+
describe HttpAcceptLanguage::Parser do
|
4
|
+
|
5
|
+
def parser
|
6
|
+
@parser ||= HttpAcceptLanguage::Parser.new('en-us,en-gb;q=0.8,en;q=0.6,es-419')
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should return empty array" do
|
10
|
+
parser.header = nil
|
11
|
+
expect(parser.user_preferred_languages).to eq []
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should properly split" do
|
15
|
+
expect(parser.user_preferred_languages).to eq %w{en-US es-419 en-GB en}
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should ignore jambled header" do
|
19
|
+
parser.header = 'odkhjf89fioma098jq .,.,'
|
20
|
+
expect(parser.user_preferred_languages).to eq []
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should properly respect whitespace" do
|
24
|
+
parser.header = 'en-us, en-gb; q=0.8,en;q = 0.6,es-419'
|
25
|
+
expect(parser.user_preferred_languages).to eq %w{en-US es-419 en-GB en}
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should find first available language" do
|
29
|
+
expect(parser.preferred_language_from(%w{en en-GB})).to eq "en-GB"
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should find first compatible language" do
|
33
|
+
expect(parser.compatible_language_from(%w{en-hk})).to eq "en-hk"
|
34
|
+
expect(parser.compatible_language_from(%w{en})).to eq "en"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should find first compatible from user preferred" do
|
38
|
+
parser.header = 'en-us,de-de'
|
39
|
+
expect(parser.compatible_language_from(%w{de en})).to eq 'en'
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should accept symbols as available languages" do
|
43
|
+
parser.header = 'en-us'
|
44
|
+
expect(parser.compatible_language_from([:"en-HK"])).to eq :"en-HK"
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should accept and ignore wildcards" do
|
48
|
+
parser.header = 'en-US,en,*'
|
49
|
+
expect(parser.compatible_language_from([:"en-US"])).to eq :"en-US"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should sanitize available language names" do
|
53
|
+
expect(parser.sanitize_available_locales(%w{en_UK-x3 en-US-x1 ja_JP-x2 pt-BR-x5 es-419-x4})).to eq ["en-UK", "en-US", "ja-JP", "pt-BR", "es-419"]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should accept available language names as symbols and return them as strings" do
|
57
|
+
expect(parser.sanitize_available_locales([:en, :"en-US", :ca, :"ca-ES"])).to eq ["en", "en-US", "ca", "ca-ES"]
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should find most compatible language from user preferred" do
|
61
|
+
parser.header = 'ja,en-gb,en-us,fr-fr'
|
62
|
+
expect(parser.language_region_compatible_from(%w{en-UK en-US ja-JP})).to eq "ja-JP"
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: http_accept_language_v2
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- iain
|
8
|
+
- yknx4
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-06-24 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rspec
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rack-test
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: guard-rspec
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rails
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '5.2'
|
77
|
+
- - "<"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '7.0'
|
80
|
+
type: :development
|
81
|
+
prerelease: false
|
82
|
+
version_requirements: !ruby/object:Gem::Requirement
|
83
|
+
requirements:
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '5.2'
|
87
|
+
- - "<"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '7.0'
|
90
|
+
- !ruby/object:Gem::Dependency
|
91
|
+
name: cucumber
|
92
|
+
requirement: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
type: :development
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: aruba
|
106
|
+
requirement: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
description: Find out which locale the user preferes by reading the languages they
|
119
|
+
specified in their browser
|
120
|
+
email:
|
121
|
+
- iain@iain.nl
|
122
|
+
- me@ale.world
|
123
|
+
executables: []
|
124
|
+
extensions: []
|
125
|
+
extra_rdoc_files: []
|
126
|
+
files:
|
127
|
+
- ".github/workflows/tests.yml"
|
128
|
+
- ".gitignore"
|
129
|
+
- ".rspec"
|
130
|
+
- Gemfile
|
131
|
+
- Guardfile
|
132
|
+
- README.md
|
133
|
+
- Rakefile
|
134
|
+
- cucumber.yml
|
135
|
+
- features/rails_integration.feature
|
136
|
+
- features/steps/rails.rb
|
137
|
+
- features/support/rails_driver.rb
|
138
|
+
- http_accept_language.gemspec
|
139
|
+
- lib/http_accept_language.rb
|
140
|
+
- lib/http_accept_language/auto_locale.rb
|
141
|
+
- lib/http_accept_language/middleware.rb
|
142
|
+
- lib/http_accept_language/parser.rb
|
143
|
+
- lib/http_accept_language/railtie.rb
|
144
|
+
- lib/http_accept_language/version.rb
|
145
|
+
- spec/auto_locale_spec.rb
|
146
|
+
- spec/middleware_spec.rb
|
147
|
+
- spec/parser_spec.rb
|
148
|
+
homepage: https://github.com/yknx4/http_accept_language
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ">="
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubygems_version: 3.2.15
|
168
|
+
signing_key:
|
169
|
+
specification_version: 4
|
170
|
+
summary: Find out which locale the user preferes by reading the languages they specified
|
171
|
+
in their browser
|
172
|
+
test_files: []
|