http_accept_language_v2 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 [](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: []
|