diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c87090f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: Ruby + +on: + push: + branches: + - main + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + ruby-version: + - '3.0' + - '3.1' + - '3.2' + - '3.3' + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + bundler-cache: true + + - name: Run specs + run: | + bin/rspec + + - name: Lint with standard + run: | + bin/standardrb diff --git a/.gitignore b/.gitignore index b8479b7..87a5af7 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,8 @@ pkg/* .rvmrc log/*.log tmp/ + +# rspec failure tracking +.rspec_status + +/coverage/ diff --git a/.rspec b/.rspec index 5f16476..2559e39 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1,3 @@ --color --format progress +--require spec_helper diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8e1084c..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: ruby -cache: bundler -sudo: false -rvm: - - 2.0.0 - - 2.1 - - 2.2.5 - - 2.3.1 -script: bundle exec rspec spec diff --git a/Gemfile b/Gemfile index b4e2a20..60001ee 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,12 @@ source "https://rubygems.org" gemspec + +gem "combustion" +gem "rack-test" +gem "rails", "> 6" +gem "rake" +gem "rspec-rails" +gem "rspec" +gem "simplecov", require: false +gem "standard" diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 135f0e6..0000000 --- a/Guardfile +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby - -guard 'rspec', :cli => "-fd", :version => 2 do - watch(%r{^spec/.+_spec\.rb$}) - watch(%r{^lib/(.+)\.rb$}) { "spec" } - watch('spec/spec_helper.rb') { "spec" } -end - diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..10f2327 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md index c5103d3..f5de189 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ -# HttpAcceptLanguage [![Build Status](https://travis-ci.org/iain/http_accept_language.svg?branch=master)](https://travis-ci.org/iain/http_accept_language) +# HttpAcceptLanguage -A gem which helps you detect the users preferred language, as sent by the "Accept-Language" HTTP header. +A gem which helps you detect the users preferred language, as sent by the +"Accept-Language" HTTP header. -The algorithm is based on [RFC 2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html), with one exception: -when a user requests "en-US" and "en" is an available language, "en" is deemed compatible with "en-US". -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. +The algorithm is based on [RFC 2616], with one exception: when a user requests +`en-US` and `en` is an available language, `en` is deemed compatible with +`en-US`. 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. Since version 2.0, this gem is Rack middleware. @@ -30,11 +35,11 @@ class SomeController < ApplicationController end ``` -You can easily set the locale used for i18n in a before-filter: +You can easily set the locale used for i18n with a filter: ```ruby class SomeController < ApplicationController - before_filter :set_locale + before_action :set_locale private def set_locale @@ -43,12 +48,11 @@ class SomeController < ApplicationController end ``` -If you want to enable this behavior by default in your controllers, you can just include the provided concern: +To enable this behavior by default, include the provided concern: ```ruby class ApplicationController < ActionController::Base include HttpAcceptLanguage::AutoLocale - #... end ``` @@ -88,21 +92,21 @@ end ## Available methods -* **user_preferred_languages**: - Returns a sorted array based on user preference in HTTP_ACCEPT_LANGUAGE, sanitized and all. -* **preferred_language_from(languages)**: - Finds the locale specifically requested by the browser -* **compatible_language_from(languages)**: - Returns the first of the user_preferred_languages that is compatible with the available locales. +* **user_preferred_languages**: Returns a sorted array based on user preference + in `HTTP_ACCEPT_LANGUAGE`, sanitized and all. +* **preferred_language_from(languages)**: Finds the locale specifically + requested by the browser. +* **compatible_language_from(languages)**: Returns the first of the + `user_preferred_languages` that is compatible with the available locales. Ignores region. -* **sanitize_available_locales(languages)**: - Returns a supplied list of available locals without any extra application info - that may be attached to the locale for storage in the application. -* **language_region_compatible_from(languages)**: - 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. +* **sanitize_available_locales(languages)**: Returns a supplied list of + available locales without any extra application info that may be attached to + the locale for storage in the application. +* **language_region_compatible_from(languages)**: 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. ## Installation @@ -122,4 +126,6 @@ Run `bundle install` to install it. --- -Released under the MIT license +Released under the MIT license. + +[RFC 2616]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html \ No newline at end of file diff --git a/Rakefile b/Rakefile index 1b92817..c5c746e 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,6 @@ require "bundler/gem_tasks" -require 'rspec/core/rake_task' +require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) -require 'cucumber/rake/task' -Cucumber::Rake::Task.new(:cucumber) - -Cucumber::Rake::Task.new(:wip, "Run features tagged with @wip") do |t| - t.profile = "wip" -end - -task :default => [:spec, :cucumber, :wip] +task default: [:spec] diff --git a/bin/rspec b/bin/rspec new file mode 100755 index 0000000..cb53ebe --- /dev/null +++ b/bin/rspec @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'rspec' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rspec-core", "rspec") diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/bin/standardrb b/bin/standardrb new file mode 100755 index 0000000..b329561 --- /dev/null +++ b/bin/standardrb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# +# This file was generated by Bundler. +# +# The application 'standardrb' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) + +bundle_binstub = File.expand_path("bundle", __dir__) + +if File.file?(bundle_binstub) + if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") + load(bundle_binstub) + else + abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. +Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") + end +end + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("standard", "standardrb") diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..1265db9 --- /dev/null +++ b/config.ru @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "rubygems" +require "bundler" + +Bundler.require :default, :development + +Combustion.initialize! :all +run Combustion::Application diff --git a/cucumber.yml b/cucumber.yml deleted file mode 100644 index 0c12ef1..0000000 --- a/cucumber.yml +++ /dev/null @@ -1,2 +0,0 @@ -default: --format progress --strict --tags ~@wip -wip: --format pretty --wip --tags @wip diff --git a/features/rails_integration.feature b/features/rails_integration.feature deleted file mode 100644 index 99d8433..0000000 --- a/features/rails_integration.feature +++ /dev/null @@ -1,29 +0,0 @@ -@rails -Feature: Rails Integration - - To use http_accept_language inside a Rails application, just add it to your - Gemfile and run `bundle install`. - - It is automatically added to your middleware. - - Scenario: Installing - When I generate a new Rails app - And I add http_accept_language to my Gemfile - And I run `rake middleware` - Then the output should contain "use HttpAcceptLanguage::Middleware" - - Scenario: Using - Given I have installed http_accept_language - When I generate the following controller: - """ - class LanguagesController < ApplicationController - - def index - languages = http_accept_language.user_preferred_languages - render :text => "Languages: #{languages.join(' : ')}" - end - - end - """ - When I access that action with the HTTP_ACCEPT_LANGUAGE header "en-us,en-gb;q=0.8,en;q=0.6,es-419" - Then the response should contain "Languages: en-US : es-419 : en-GB : en" diff --git a/features/steps/rails.rb b/features/steps/rails.rb deleted file mode 100644 index bba48fc..0000000 --- a/features/steps/rails.rb +++ /dev/null @@ -1,37 +0,0 @@ -Before "@rails" do - @rails = RailsDriver.new -end - -When /^I generate a new Rails app$/ do - @rails.generate_rails -end - -When /^I add http_accept_language to my Gemfile$/ do - @rails.append_gemfile -end - -Given /^I have installed http_accept_language$/ do - @rails.install_gem -end - -When /^I generate the following controller:$/ do |string| - @rails.generate_controller "languages", string -end - -When /^I access that action with the HTTP_ACCEPT_LANGUAGE header "(.*?)"$/ do |header| - @rails.with_rails_running do - @rails.request_with_http_accept_language_header(header, "/languages") - end -end - -Then /^the response should contain "(.*?)"$/ do |output| - @rails.output_should_contain(output) -end - -When /^I run `rake middleware`$/ do - @rails.bundle_exec("rake middleware") -end - -Then /^the output should contain "(.*?)"$/ do |expected| - @rails.assert_partial_output(expected, @rails.all_output) -end diff --git a/features/support/rails_driver.rb b/features/support/rails_driver.rb deleted file mode 100644 index eaf2b37..0000000 --- a/features/support/rails_driver.rb +++ /dev/null @@ -1,93 +0,0 @@ -require 'aruba/api' - -class RailsDriver - include Aruba::Api - - def initialize - @aruba_io_wait_seconds = 10 - # @announce_stdout = true - # @announce_stderr = true - # @announce_cmd = true - # @announce_dir = true - # @announce_env = true - end - - def app_name - "foobar" - end - - def install_gem - if app_exists? - cd app_name - else - generate_rails - append_gemfile - end - end - - def app_exists? - in_current_dir do - File.exist?("#{app_name}/Gemfile") - end - end - - def bundle_exec(cmd) - run_simple "bundle exec #{cmd}" - end - - def generate_rails - # install rails with as few things as possible, for speed! - bundle_exec "rails new #{app_name} --force --skip-git --skip-active-record --skip-sprockets --skip-javascript --skip-test-unit --old-style-hash" - cd app_name - end - - def append_gemfile - # Specifiy a path so cucumber will use the unreleased version of the gem - append_to_file "Gemfile", "gem 'http_accept_language', :path => '#{gem_path}'" - end - - def gem_path - File.expand_path('../../../', __FILE__) - end - - def generate_controller(name, content) - bundle_exec "rails generate resource #{name} --force" - write_file "app/controllers/#{name}_controller.rb", content - end - - def request_with_http_accept_language_header(header, path) - run_simple "curl --retry 10 -H 'Accept-language: #{header}' #{File.join(host, path)} -o #{response}" - run_simple "cat out.html" - end - - def host - "http://localhost:13000" - end - - def with_rails_running - start_rails - yield - ensure - stop_rails - end - - def start_rails - bundle_exec "rails server -p 13000 -d" - end - - def stop_rails - in_current_dir do - `cat tmp/pids/server.pid | xargs kill -9` - end - end - - def response - File.expand_path(File.join(current_dir, 'out.html')) - end - - def output_should_contain(expected) - actual = File.open(response, 'r:utf-8').read - actual.should include expected - end - -end diff --git a/http_accept_language.gemspec b/http_accept_language.gemspec index 5885f8a..d777008 100644 --- a/http_accept_language.gemspec +++ b/http_accept_language.gemspec @@ -1,30 +1,22 @@ -# -*- encoding: utf-8 -*- -$:.push File.expand_path("../lib", __FILE__) -require "http_accept_language/version" +require_relative "lib/http_accept_language/version" -Gem::Specification.new do |s| - s.name = "http_accept_language" - s.version = HttpAcceptLanguage::VERSION - s.authors = ["iain"] - s.email = ["iain@iain.nl"] - s.homepage = "https://github.com/iain/http_accept_language" - s.summary = %q{Find out which locale the user preferes by reading the languages they specified in their browser} - s.description = %q{Find out which locale the user preferes by reading the languages they specified in their browser} - s.license = "MIT" +Gem::Specification.new do |spec| + spec.authors = ["iain"] + spec.email = ["iain@iain.nl"] + spec.homepage = "https://github.com/iain/http_accept_language" + spec.license = "MIT" + spec.name = "http_accept_language" + spec.required_ruby_version = ">= 3.0.0" + spec.summary = "Find out which locale the user prefers by reading the languages they specified in their browser." + spec.version = HttpAcceptLanguage::VERSION - s.rubyforge_project = "http_accept_language" + spec.files = Dir[ + "lib/**/*", + "README.md", + "LICENSE.txt", + ] - s.files = `git ls-files`.split("\n") - s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } - s.require_paths = ["lib"] - - s.add_development_dependency 'rake' - s.add_development_dependency 'rspec' - s.add_development_dependency 'rack-test' - s.add_development_dependency 'guard-rspec' - s.add_development_dependency 'listen', '< 3.1.0' if RUBY_VERSION < '2.2.5' - s.add_development_dependency 'rails', ['>= 3.2.6', *('< 5' if RUBY_VERSION < '2.2.2')] - s.add_development_dependency 'cucumber' - s.add_development_dependency 'aruba' + spec.bindir = "exe" + spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] end diff --git a/lib/http_accept_language.rb b/lib/http_accept_language.rb index 0e8a0a6..a91f21a 100644 --- a/lib/http_accept_language.rb +++ b/lib/http_accept_language.rb @@ -1,4 +1,4 @@ -require 'http_accept_language/auto_locale' -require 'http_accept_language/parser' -require 'http_accept_language/middleware' -require 'http_accept_language/railtie' if defined?(Rails::Railtie) +require "http_accept_language/auto_locale" +require "http_accept_language/parser" +require "http_accept_language/middleware" +require "http_accept_language/railtie" if defined?(Rails::Railtie) diff --git a/lib/http_accept_language/auto_locale.rb b/lib/http_accept_language/auto_locale.rb index 82490eb..73cf45c 100644 --- a/lib/http_accept_language/auto_locale.rb +++ b/lib/http_accept_language/auto_locale.rb @@ -1,15 +1,11 @@ -require 'active_support/concern' +require "active_support/concern" module HttpAcceptLanguage module AutoLocale extend ActiveSupport::Concern included do - if respond_to?(:prepend_before_action) - prepend_before_action :set_locale - else - prepend_before_filter :set_locale - end + prepend_before_action :set_locale end private diff --git a/lib/http_accept_language/middleware.rb b/lib/http_accept_language/middleware.rb index 6f42e1a..3998bf8 100644 --- a/lib/http_accept_language/middleware.rb +++ b/lib/http_accept_language/middleware.rb @@ -6,11 +6,11 @@ def initialize(app) def call(env) env["http_accept_language.parser"] = Parser.new(env["HTTP_ACCEPT_LANGUAGE"]) - + def env.http_accept_language self["http_accept_language.parser"] end - + @app.call(env) end end diff --git a/lib/http_accept_language/parser.rb b/lib/http_accept_language/parser.rb index f7e4086..ecb67bf 100644 --- a/lib/http_accept_language/parser.rb +++ b/lib/http_accept_language/parser.rb @@ -1,5 +1,8 @@ module HttpAcceptLanguage class Parser + DEFAULT_QUALITY = 1.0 + LOCALE_FORMAT = /^[a-z\-0-9]+|\*$/i + attr_accessor :header def initialize(header) @@ -7,7 +10,6 @@ def initialize(header) 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: # @@ -16,31 +18,29 @@ def initialize(header) # def user_preferred_languages @user_preferred_languages ||= begin - header.to_s.gsub(/\s+/, '').split(',').map do |language| - locale, quality = language.split(';q=') - raise ArgumentError, 'Not correctly formatted' unless locale =~ /^[a-z\-0-9]+|\*$/i + languages_from_header.map do |language| + locale, quality = language.split(";q=") + raise ArgumentError, "Not correctly formatted" unless LOCALE_FORMAT.match?(locale) - locale = locale.downcase.gsub(/-[a-z0-9]+$/i, &:upcase) # Uppercase territory - locale = nil if locale == '*' # Ignore wildcards + locale = locale.downcase.gsub(/-[a-z0-9]+$/i, &:upcase) # Uppercase territory + locale = nil if locale == "*" # Ignore wildcards - quality = quality ? quality.to_f : 1.0 + quality = quality ? quality.to_f : DEFAULT_QUALITY [locale, quality] end.sort do |(_, left), (_, right)| right <=> left end.map(&:first).compact - rescue ArgumentError # Just rescue anything if the browser messed up badly. + rescue ArgumentError # Rescue from malformed language strings. [] end end # Sets the user languages preference, overriding the browser # - def user_preferred_languages=(languages) - @user_preferred_languages = languages - end + attr_writer :user_preferred_languages - # Finds the locale specifically requested by the browser. + # Finds the first locale specifically requested by the browser. # # Example: # @@ -59,22 +59,22 @@ def preferred_language_from(array) # request.compatible_language_from I18n.available_locales # def compatible_language_from(available_languages) - user_preferred_languages.map do |preferred| #en-US + user_preferred_languages.map do |preferred| # en-US preferred = preferred.downcase - preferred_language = preferred.split('-', 2).first + preferred_language = preferred.split("-", 2).first available_languages.find do |available| # en available = available.to_s.downcase - preferred == available || preferred_language == available.split('-', 2).first + preferred == available || preferred_language == available.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. + # Returns a supplied list of available locales 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] + # 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 |available| @@ -82,10 +82,10 @@ def sanitize_available_locales(available_languages) 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. + # 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 then on region. If no matching region is found, return the first + # language in the group matching that primary language. # # Example: # @@ -93,16 +93,25 @@ def sanitize_available_locales(available_languages) # def language_region_compatible_from(available_languages) available_languages = sanitize_available_locales(available_languages) - user_preferred_languages.map do |preferred| #en-US + user_preferred_languages.map do |preferred| # en-US preferred = preferred.downcase - preferred_language = preferred.split('-', 2).first + preferred_language = preferred.split("-", 2).first lang_group = available_languages.select do |available| # en - preferred_language == available.downcase.split('-', 2).first + preferred_language == available.downcase.split("-", 2).first end - - lang_group.find { |lang| lang.downcase == preferred } || lang_group.first #en-US, en-UK + + lang_group.find { |lang| lang.downcase == preferred } || lang_group.first # en-US, en-UK end.compact.first end + + private + + def languages_from_header + header + .to_s + .gsub(/\s+/, "") + .split(",") + end end end diff --git a/lib/http_accept_language/version.rb b/lib/http_accept_language/version.rb index 36a2f9b..805da0b 100644 --- a/lib/http_accept_language/version.rb +++ b/lib/http_accept_language/version.rb @@ -1,3 +1,3 @@ module HttpAcceptLanguage - VERSION = '2.1.1' + VERSION = "2.1.1" end diff --git a/spec/auto_locale_spec.rb b/spec/auto_locale_spec.rb index 8679f25..ee0f306 100644 --- a/spec/auto_locale_spec.rb +++ b/spec/auto_locale_spec.rb @@ -1,24 +1,11 @@ -require 'i18n' -require 'http_accept_language/auto_locale' -require 'http_accept_language/parser' -require 'http_accept_language/middleware' - -describe HttpAcceptLanguage::AutoLocale do +RSpec.describe HttpAcceptLanguage::AutoLocale do let(:controller_class) do - Class.new do + Class.new(ActionController::Base) do def initialize(header = nil) super() @header = header end - def self.prepend_before_action(dummy) - # dummy method - end - - def self.prepend_before_filter(dummy) - # dummy method - end - def http_accept_language @http_accept_language ||= HttpAcceptLanguage::Parser.new(@header) end @@ -54,7 +41,7 @@ def http_accept_language end end - let(:no_accept_language_controller) { controller_class.new() } + let(:no_accept_language_controller) { controller_class.new } context "default locale is ja" do before do diff --git a/spec/internal/app/controllers/application_controller.rb b/spec/internal/app/controllers/application_controller.rb new file mode 100644 index 0000000..09705d1 --- /dev/null +++ b/spec/internal/app/controllers/application_controller.rb @@ -0,0 +1,2 @@ +class ApplicationController < ActionController::Base +end diff --git a/spec/internal/app/controllers/languages_controller.rb b/spec/internal/app/controllers/languages_controller.rb new file mode 100644 index 0000000..d909cde --- /dev/null +++ b/spec/internal/app/controllers/languages_controller.rb @@ -0,0 +1,11 @@ +class LanguagesController < ApplicationController + def index + render plain: "Languages: #{languages.join(" : ")}" + end + + private + + def languages + http_accept_language.user_preferred_languages + end +end diff --git a/spec/internal/config/routes.rb b/spec/internal/config/routes.rb new file mode 100644 index 0000000..1502766 --- /dev/null +++ b/spec/internal/config/routes.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Rails.application.routes.draw do + root to: "languages#index" +end diff --git a/spec/internal/log/.gitignore b/spec/internal/log/.gitignore new file mode 100644 index 0000000..bf0824e --- /dev/null +++ b/spec/internal/log/.gitignore @@ -0,0 +1 @@ +*.log \ No newline at end of file diff --git a/spec/middleware_spec.rb b/spec/middleware_spec.rb index 931de7c..aaefbe6 100644 --- a/spec/middleware_spec.rb +++ b/spec/middleware_spec.rb @@ -1,24 +1,18 @@ -require 'http_accept_language' -require 'rack/test' -require 'json' - class TestRackApp - def call(env) request = Rack::Request.new(env) http_accept_language = env.http_accept_language result = { - :user_preferred_languages => http_accept_language.user_preferred_languages, + user_preferred_languages: http_accept_language.user_preferred_languages } - if request.params['preferred'] - result[:preferred_language_from] = http_accept_language.preferred_language_from(request.params['preferred']) + if request.params["preferred"] + result[:preferred_language_from] = http_accept_language.preferred_language_from(request.params["preferred"]) end - [ 200, {}, [ JSON.generate(result) ]] + [200, {}, [JSON.generate(result)]] end - end -describe "Rack integration" do +RSpec.describe "Rack integration" do include Rack::Test::Methods def app @@ -29,32 +23,31 @@ def app end it "handles reuse of the env instance" do - env = { "HTTP_ACCEPT_LANGUAGE" => "en" } + env = {"HTTP_ACCEPT_LANGUAGE" => "en"} app = lambda { |env| env } middleware = HttpAcceptLanguage::Middleware.new(app) middleware.call(env) - expect(env.http_accept_language.user_preferred_languages).to eq %w{en} + expect(env.http_accept_language.user_preferred_languages).to eq %w[en] env["HTTP_ACCEPT_LANGUAGE"] = "de" middleware.call(env) - expect(env.http_accept_language.user_preferred_languages).to eq %w{de} + expect(env.http_accept_language.user_preferred_languages).to eq %w[de] end it "decodes the HTTP_ACCEPT_LANGUAGE header" do - request_with_header 'en-us,en-gb;q=0.8,en;q=0.6,es-419' - expect(r['user_preferred_languages']).to eq %w{en-US es-419 en-GB en} + request_with_header "en-us,en-gb;q=0.8,en;q=0.6,es-419" + expect(body_as_json["user_preferred_languages"]).to eq %w[en-US es-419 en-GB en] end it "finds the first available language" do - request_with_header 'en-us,en-gb;q=0.8,en;q=0.6,es-419', :preferred => %w(en en-GB) - expect(r['preferred_language_from']).to eq 'en-GB' + request_with_header "en-us,en-gb;q=0.8,en;q=0.6,es-419", preferred: %w[en en-GB] + expect(body_as_json["preferred_language_from"]).to eq "en-GB" end def request_with_header(header, params = {}) - get "/", params, 'HTTP_ACCEPT_LANGUAGE' => header + get "/", params, "HTTP_ACCEPT_LANGUAGE" => header end - def r + def body_as_json JSON.parse(last_response.body) end - end diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 15677e2..0ed638c 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -1,9 +1,6 @@ -require 'http_accept_language/parser' - -describe HttpAcceptLanguage::Parser do - +RSpec.describe HttpAcceptLanguage::Parser do def parser - @parser ||= HttpAcceptLanguage::Parser.new('en-us,en-gb;q=0.8,en;q=0.6,es-419') + @parser ||= HttpAcceptLanguage::Parser.new("en-us,en-gb;q=0.8,en;q=0.6,es-419") end it "should return empty array" do @@ -12,45 +9,45 @@ def parser end it "should properly split" do - expect(parser.user_preferred_languages).to eq %w{en-US es-419 en-GB en} + expect(parser.user_preferred_languages).to eq %w[en-US es-419 en-GB en] end it "should ignore jambled header" do - parser.header = 'odkhjf89fioma098jq .,.,' + parser.header = "odkhjf89fioma098jq .,.," expect(parser.user_preferred_languages).to eq [] end it "should properly respect whitespace" do - parser.header = 'en-us, en-gb; q=0.8,en;q = 0.6,es-419' - expect(parser.user_preferred_languages).to eq %w{en-US es-419 en-GB en} + parser.header = "en-us, en-gb; q=0.8,en;q = 0.6,es-419" + expect(parser.user_preferred_languages).to eq %w[en-US es-419 en-GB en] end it "should find first available language" do - expect(parser.preferred_language_from(%w{en en-GB})).to eq "en-GB" + expect(parser.preferred_language_from(%w[en en-GB])).to eq "en-GB" end it "should find first compatible language" do - expect(parser.compatible_language_from(%w{en-hk})).to eq "en-hk" - expect(parser.compatible_language_from(%w{en})).to eq "en" + expect(parser.compatible_language_from(%w[en-hk])).to eq "en-hk" + expect(parser.compatible_language_from(%w[en])).to eq "en" end it "should find first compatible from user preferred" do - parser.header = 'en-us,de-de' - expect(parser.compatible_language_from(%w{de en})).to eq 'en' + parser.header = "en-us,de-de" + expect(parser.compatible_language_from(%w[de en])).to eq "en" end it "should accept symbols as available languages" do - parser.header = 'en-us' + parser.header = "en-us" expect(parser.compatible_language_from([:"en-HK"])).to eq :"en-HK" end it "should accept and ignore wildcards" do - parser.header = 'en-US,en,*' + parser.header = "en-US,en,*" expect(parser.compatible_language_from([:"en-US"])).to eq :"en-US" end it "should sanitize available language names" do - 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"] + 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"] end it "should accept available language names as symbols and return them as strings" do @@ -58,8 +55,7 @@ def parser end it "should find most compatible language from user preferred" do - parser.header = 'ja,en-gb,en-us,fr-fr' - expect(parser.language_region_compatible_from(%w{en-UK en-US ja-JP})).to eq "ja-JP" + parser.header = "ja,en-gb,en-us,fr-fr" + expect(parser.language_region_compatible_from(%w[en-UK en-US ja-JP])).to eq "ja-JP" end - end diff --git a/spec/rails_integration_spec.rb b/spec/rails_integration_spec.rb new file mode 100644 index 0000000..7de2552 --- /dev/null +++ b/spec/rails_integration_spec.rb @@ -0,0 +1,19 @@ +RSpec.describe "Rails integration" do + describe "Controller Helper", type: :request do + it "returns correct language values from header" do + headers = {"HTTP_ACCEPT_LANGUAGE" => "en-us,en-gb;q=0.8,en;q=0.6,es-419"} + + get root_path, headers: headers + + expect(response.body) + .to include("Languages: en-US : es-419 : en-GB : en") + end + end + + describe "Middleware" do + it "inserts middleware into the Rails application" do + expect(Rails.application.middleware.middlewares) + .to include(HttpAcceptLanguage::Middleware) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..74eedce --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,23 @@ +if ENV.fetch("COVERAGE", false) + require "simplecov" + SimpleCov.start do + load_profile "test_frameworks" + end +end + +require "action_controller/railtie" + +Bundler.require :default, :development + +Combustion.initialize! :action_controller do + config.load_defaults Rails::VERSION::STRING.to_f +end + +require "rspec/rails" + +RSpec.configure do |config| + config.disable_monkey_patching! + config.example_status_persistence_file_path = ".rspec_status" + config.infer_spec_type_from_file_location! + config.use_transactional_fixtures = true +end