Skip to content

Commit be53b82

Browse files
Remove the http-accept dependency.
Assisted-By: devx/b3414b50-d642-461b-95a8-8f773c4d087b
1 parent 409acc8 commit be53b82

9 files changed

Lines changed: 88 additions & 13 deletions

File tree

lib/utopia/http.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,11 @@
33
# Released under the MIT License.
44
# Copyright, 2010-2026, by Samuel Williams.
55

6-
require "http/accept"
76
require "protocol/http/status"
87

98
module Utopia
109
# HTTP protocol implementation.
1110
module HTTP
12-
# Pull in {::HTTP::Accept} for parsing.
13-
Accept = ::HTTP::Accept
14-
1511
# A list of commonly used HTTP status codes.
1612
# For help choosing the right status code, see http://racksburg.com/choosing-an-http-status-code/
1713
STATUS_CODES = {

lib/utopia/localization.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# Copyright, 2009-2025, by Samuel Williams.
55

66
require_relative "localization/preferences"
7+
require_relative "localization/locales"
78
require_relative "localization/resolver"
89
require_relative "localization/middleware"
910

lib/utopia/localization/locales.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2026, by Samuel Williams.
5+
6+
module Utopia
7+
module Localization
8+
# Matches configured locales against language ranges.
9+
class Locales
10+
# Expand a locale into progressively less specific language ranges.
11+
# @parameter locale [String] The locale to expand.
12+
# @parameter patterns [Hash] The destination language-range mapping.
13+
def self.expand(locale, patterns)
14+
parts = locale.split("-")
15+
16+
while parts.any?
17+
pattern = parts.join("-")
18+
patterns[pattern] ||= locale
19+
parts.pop
20+
end
21+
end
22+
23+
# Initialize the configured locales.
24+
# @parameter names [Array(String)] The locale names, in preference order.
25+
def initialize(names)
26+
@names = names
27+
@patterns = {}
28+
29+
@names.each do |name|
30+
self.class.expand(name, @patterns)
31+
end
32+
33+
freeze
34+
end
35+
36+
# Freeze this object and its internal state.
37+
# @returns [self] This object.
38+
def freeze
39+
return self if frozen?
40+
41+
@names.freeze
42+
@patterns.freeze
43+
44+
return super
45+
end
46+
47+
attr :names
48+
attr :patterns
49+
50+
# Select configured locales matching the given language ranges.
51+
# @parameter languages [Enumerable] Preferred language ranges.
52+
# @returns [Array(String)] Matching locale names in language preference order.
53+
def match(languages)
54+
languages.filter_map do |language|
55+
@patterns[language.name]
56+
end
57+
end
58+
end
59+
end
60+
end

lib/utopia/localization/middleware.rb

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
# Copyright, 2025-2026, by Samuel Williams.
55

66
require_relative "preferences"
7+
require_relative "locales"
78
require_relative "../middleware"
89
require_relative "../request"
910
require_relative "../response"
1011

1112
require "set"
13+
require "protocol/http/header/accept_language"
1214

1315
module Utopia
1416
module Localization
@@ -22,7 +24,7 @@ class Middleware < Protocol::HTTP::Middleware
2224
def initialize(app, locales:, default_locale: nil, default_locales: nil, hosts: {}, ignore: [])
2325
super(app)
2426

25-
@all_locales = HTTP::Accept::Languages::Locales.new(locales)
27+
@all_locales = Locales.new(locales)
2628

2729
# Locales here are represented as an array of strings, e.g. ['en', 'ja', 'cn', 'de'] and are used in order if no locale is specified by the user.
2830
unless @default_locales = default_locales
@@ -125,17 +127,17 @@ def extract_path_locale(request)
125127
# @parameter request [Utopia::Request] The application request.
126128
# @returns [Array(String)] Supported locales accepted by the browser, in preference order.
127129
def browser_preferred_locales(request)
128-
accept_languages = request.headers["accept-language"]&.to_s
130+
accept_languages = request.headers["accept-language"]
129131

130132
# No user prefered languages:
131133
return [] unless accept_languages
132134

133135
# Extract the ordered list of languages:
134-
languages = HTTP::Accept::Languages.parse(accept_languages)
136+
languages = accept_languages.preferred_languages
135137

136138
# Returns available languages based on the order languages:
137-
return @all_locales & languages
138-
rescue HTTP::Accept::ParseError
139+
return @all_locales.match(languages)
140+
rescue Protocol::HTTP::Header::AcceptLanguage::ParseError
139141
# If we fail to parse the browser Accept-Language header, we ignore it (silently).
140142
return []
141143
end

readme.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ Please see the [project releases](https://socketry.github.io/utopia/releases/ind
5757
- [Utopia::Gallery](https://github.com/ioquatix/utopia-gallery) — A fast photo gallery based on [libvips](https://github.com/jcupitt/libvips).
5858
- [Utopia::Project](https://github.com/socketry/utopia-project) — A Ruby project documentation tool.
5959
- [Utopia::Analytics](https://github.com/ioquatix/utopia-analytics) — Simple integration with Google Analytics.
60-
- [HTTP::Accept](https://github.com/ioquatix/http-accept) — RFC compliant header parser.
6160
- [Samovar](https://github.com/ioquatix/samovar) — Command line parser used by Utopia.
6261
- [Mapping](https://github.com/ioquatix/mapping) — Provide structured conversions for web interfaces.
6362

releases.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Unreleasd
44

55
- **Security** Fix handling of redirects that start with `//` to prevent open redirect vulnerabilities.
6-
- Use `protocol-media` for response negotiation.
6+
- Use `protocol-media` and `protocol-http` for response and language negotiation, removing the `http-accept` dependency.
77

88
## v2.31.0
99

setup/site/pages/welcome/index.xnode

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<div>
1818
<i class="fa fa-code"></i>
1919
<h2>Well tested and maintained</h2>
20-
<p>Utopia comprises a <a href="https://github.com/ioquatix/utopia">core gem</a> and several supporting libraries, the main ones being <a href="https://github.com/ioquatix/trenni">trenni</a> for templates and parsing, and <a href="https://github.com/ioquatix/http-accept">http-accept</a> for HTTP header processing. Together, these gems have over 90% test coverage.</p>
20+
<p>Utopia comprises a <a href="https://github.com/ioquatix/utopia">core gem</a> and several supporting libraries, including <a href="https://github.com/socketry/xrb">XRB</a> for templates and markup parsing and <a href="https://github.com/socketry/protocol-http">Protocol HTTP</a> for HTTP protocol handling.</p>
2121
</div>
2222

2323
<div>

test/utopia/localization.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,24 @@
7676
expect(last_response.headers["content-location"].to_s).to be == "/ja/localized.txt"
7777
end
7878

79+
it "matches less specific accepted languages" do
80+
application = Utopia::Application.build(Protocol::HTTP::Middleware.for do |request|
81+
Utopia::Response.text(request.localization.preferred_locales.compact.join(","))
82+
end) do
83+
use Utopia::Localization, locales: ["en-NZ", "en-US"], default_locale: "en-NZ"
84+
end
85+
86+
response = application.call(Protocol::HTTP::Request["GET", "/", {"accept-language" => "en"}])
87+
88+
expect(response.read).to be == "en-NZ"
89+
end
90+
91+
it "ignores malformed accepted languages" do
92+
client.get "/localized.txt", {"accept-language" => "not a language"}
93+
94+
expect(last_response.read).to be == "localized.en.txt"
95+
end
96+
7997
it "resolves localized content templates" do
8098
client.get "/page", {"accept-language" => "ja,en"}
8199

utopia.gemspec

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ Gem::Specification.new do |spec|
2828
spec.add_dependency "bake", "~> 0.20"
2929
spec.add_dependency "concurrent-ruby", "~> 1.2"
3030
spec.add_dependency "console", "~> 1.24"
31-
spec.add_dependency "http-accept", "~> 2.1"
3231
spec.add_dependency "irb"
3332
spec.add_dependency "mail", "~> 2.6"
3433
spec.add_dependency "mime-types", "~> 3.0"

0 commit comments

Comments
 (0)