Skip to content

Feature/LocaleFilter - #81

Merged
AntonAhlqvist merged 4 commits into
mainfrom
feature/locale-filter
Feb 25, 2026
Merged

Feature/LocaleFilter#81
AntonAhlqvist merged 4 commits into
mainfrom
feature/locale-filter

Conversation

@AntonAhlqvist

@AntonAhlqvist AntonAhlqvist commented Feb 23, 2026

Copy link
Copy Markdown

Summary by CodeRabbit

  • New Features

    • Automatic per-request locale detection from Accept-Language headers with a safe fallback to English (US), exposed via a global accessor for use during request handling.
  • Tests

    • Added unit tests validating header parsing, selection of the first language, case-insensitive header handling, and safe defaults for missing, blank, or null request/header scenarios.

@coderabbitai

coderabbitai Bot commented Feb 23, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4bdf984 and 97c501d.

📒 Files selected for processing (2)
  • src/main/java/org/example/filter/LocaleFilter.java
  • src/test/java/org/example/filter/LocaleFilterTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/org/example/filter/LocaleFilter.java
  • src/test/java/org/example/filter/LocaleFilterTest.java

📝 Walkthrough

Walkthrough

Adds a new LocaleFilter that derives a request-scoped locale from the Accept-Language header (defaults to "en-US"), stores it in a ThreadLocal for the request, exposes a static accessor, and includes unit tests for header parsing and null/blank/case scenarios.

Changes

Cohort / File(s) Summary
LocaleFilter implementation
src/main/java/org/example/filter/LocaleFilter.java
New servlet-style Filter that resolves the Accept-Language header (first language tag), sets a per-request ThreadLocal locale, exposes getCurrentLocale(), delegates to the filter chain, and always removes the ThreadLocal in a finally block.
LocaleFilter tests
src/test/java/org/example/filter/LocaleFilterTest.java
New JUnit tests verifying: first-language selection, defaulting to "en-US" when header/request/headers are missing or blank, and case-insensitive header lookup.

Sequence Diagram

sequenceDiagram
    participant Client
    participant LocaleFilter
    participant ThreadLocal
    participant FilterChain
    participant Backend

    Client->>LocaleFilter: doFilter(request, response, chain)
    activate LocaleFilter
    LocaleFilter->>LocaleFilter: resolveLocale(request)
    alt Accept-Language header present
        LocaleFilter->>ThreadLocal: set(parsed_locale)
    else missing/blank/malformed
        LocaleFilter->>ThreadLocal: set("en-US")
    end
    LocaleFilter->>FilterChain: chain.doFilter(request, response)
    activate FilterChain
    FilterChain->>Backend: process request
    Backend-->>FilterChain: response
    deactivate FilterChain
    LocaleFilter->>ThreadLocal: remove()
    LocaleFilter-->>Client: return response
    deactivate LocaleFilter
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 I hop through headers, sniffing languages true,
I tuck each request's tongue into a ThreadLocal view,
If headers hide, I hum "en-US" with cheer,
I pass the chain along, then vanish — no trace here,
Hooray, locales found — the rabbit celebrates near!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/LocaleFilter' is vague and uses a generic 'Feature/' prefix without clearly describing what the LocaleFilter does or why it's being added. Replace with a descriptive title like 'Add LocaleFilter to derive user locale from Accept-Language header' to clearly communicate the change's purpose.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/locale-filter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/main/java/org/example/filter/LocaleFilter.java (1)

62-64: Redundant null check on headers.

HttpRequest.getHeaders() always returns a non-null map (Collections.emptyMap() when the constructor received null), so headers == null can never be true. The headers.isEmpty() guard is sufficient.

♻️ Proposed simplification
-        if (headers == null || headers.isEmpty()) {
+        if (headers.isEmpty()) {
             return DEFAULT_LOCALE;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/filter/LocaleFilter.java` around lines 62 - 64, The
null check on headers in LocaleFilter is redundant because
HttpRequest.getHeaders() always returns a non-null Map; update the guard in the
method that uses headers (look for the headers variable in LocaleFilter, likely
in a getLocale or doFilter method) to remove the `headers == null` check and
only check `headers.isEmpty()` before returning DEFAULT_LOCALE, keeping the rest
of the logic unchanged.
src/test/java/org/example/filter/LocaleFilterTest.java (1)

14-29: Missing test for a first-entry quality weight — would catch the parts[0].trim() bug.

There is no test where the first locale tag carries a ;q= suffix (e.g., "sv-SE;q=0.9,en-US"). Adding one would directly exercise — and currently expose — the stripping bug flagged in LocaleFilter.resolveLocale.

✅ Suggested additional test
`@Test`
void shouldStripQualityWeightFromFirstLocale() {
    Map<String, String> headers = new HashMap<>();
    headers.put("Accept-Language", "sv-SE;q=0.9,en-US");

    HttpRequest request = new HttpRequest("GET", "/", "HTTP/1.1", headers, null);
    HttpResponseBuilder response = new HttpResponseBuilder();

    LocaleFilter filter = new LocaleFilter();

    filter.doFilter(request, response, (req, res) -> {
        assertEquals("sv-SE", LocaleFilter.getCurrentLocale());
    });
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/java/org/example/filter/LocaleFilterTest.java` around lines 14 - 29,
Add a test that verifies LocaleFilter correctly strips a leading quality weight
from the first Accept-Language entry: create a test named
shouldStripQualityWeightFromFirstLocale that builds headers with
"Accept-Language" = "sv-SE;q=0.9,en-US", constructs HttpRequest and
HttpResponseBuilder, instantiates LocaleFilter, calls filter.doFilter(request,
response, (req, res) -> { assertEquals("sv-SE",
LocaleFilter.getCurrentLocale()); }), and ensure the assertion checks the locale
inside the filter invocation to catch the parts[0].trim() bug in
LocaleFilter.resolveLocale.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/example/filter/LocaleFilter.java`:
- Around line 80-85: In LocaleFilter (method handling the acceptLanguage string)
remove the dead check for parts.length == 0 and instead parse the first token to
strip any quality weight; after splitting acceptLanguage by ',' take parts[0],
trim it, then split that token on ';' (or strip everything after the first ';' /
match up to the first ';' with a regex) and return the cleaned locale tag; keep
returning DEFAULT_LOCALE when the acceptLanguage is blank/null or the cleaned
token is empty.

In `@src/test/java/org/example/filter/LocaleFilterTest.java`:
- Around line 85-95: The test name is incorrect because HttpRequest normalizes a
null headers argument to an empty map, so the test exercises the empty-headers
path rather than a null-headers guard; update the test method name in
LocaleFilterTest from shouldUseDefaultWhenHeadersAreNull to
shouldUseDefaultWhenHeadersAreEmpty and keep the body as-is (it constructs new
HttpRequest(..., null, null) and asserts LocaleFilter.getCurrentLocale() inside
the filter.doFilter callback) so the name accurately describes the scenario
being tested.

---

Nitpick comments:
In `@src/main/java/org/example/filter/LocaleFilter.java`:
- Around line 62-64: The null check on headers in LocaleFilter is redundant
because HttpRequest.getHeaders() always returns a non-null Map; update the guard
in the method that uses headers (look for the headers variable in LocaleFilter,
likely in a getLocale or doFilter method) to remove the `headers == null` check
and only check `headers.isEmpty()` before returning DEFAULT_LOCALE, keeping the
rest of the logic unchanged.

In `@src/test/java/org/example/filter/LocaleFilterTest.java`:
- Around line 14-29: Add a test that verifies LocaleFilter correctly strips a
leading quality weight from the first Accept-Language entry: create a test named
shouldStripQualityWeightFromFirstLocale that builds headers with
"Accept-Language" = "sv-SE;q=0.9,en-US", constructs HttpRequest and
HttpResponseBuilder, instantiates LocaleFilter, calls filter.doFilter(request,
response, (req, res) -> { assertEquals("sv-SE",
LocaleFilter.getCurrentLocale()); }), and ensure the assertion checks the locale
inside the filter invocation to catch the parts[0].trim() bug in
LocaleFilter.resolveLocale.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 86f2ba7 and e19ed97.

📒 Files selected for processing (2)
  • src/main/java/org/example/filter/LocaleFilter.java
  • src/test/java/org/example/filter/LocaleFilterTest.java

Comment thread src/main/java/org/example/filter/LocaleFilter.java Outdated
Comment thread src/test/java/org/example/filter/LocaleFilterTest.java
@AntonAhlqvist AntonAhlqvist changed the title Re-commit LocaleFilter + tests to clean branch for PR Feature/LocaleFilter Feb 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main/java/org/example/filter/LocaleFilter.java (1)

72-80: Consider simplifying the case-insensitive header lookup.

HttpRequest.getHeaders() returns a plain Map (from Map.copyOf), so the manual iteration is functionally necessary for case-insensitive matching. However, you could tighten this with a stream one-liner or, better, push a case-insensitive getHeader(String name) method onto HttpRequest itself so every filter/handler doesn't have to re-implement this pattern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/example/filter/LocaleFilter.java` around lines 72 - 80, The
manual loop in LocaleFilter that searches headers for "Accept-Language" should
be replaced with a case-insensitive lookup: either add a helper method
HttpRequest.getHeader(String name) that performs case-insensitive matching over
HttpRequest.getHeaders() and returns the first matching value, then call that
from LocaleFilter to set acceptLanguage, or replace the loop in LocaleFilter
with a concise stream-based one-liner that finds an entry whose key
equalsIgnoreCase("Accept-Language") and maps to its value; update references to
acceptLanguage and the LocaleFilter header lookup to use the new helper or
stream one-liner so the code is clearer and reusable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/org/example/filter/LocaleFilter.java`:
- Around line 86-92: The current parsing in LocaleFilter (using acceptLanguage,
parts and parts[0].split(";")[0].trim()) can yield an empty string for malformed
headers; after extracting and trimming the language token from parts[0], add a
final emptiness guard that returns DEFAULT_LOCALE when the token is empty (i.e.,
check the result of parts[0].split(";")[0].trim() and return DEFAULT_LOCALE if
it is blank) so the method never returns an empty locale string.

---

Nitpick comments:
In `@src/main/java/org/example/filter/LocaleFilter.java`:
- Around line 72-80: The manual loop in LocaleFilter that searches headers for
"Accept-Language" should be replaced with a case-insensitive lookup: either add
a helper method HttpRequest.getHeader(String name) that performs
case-insensitive matching over HttpRequest.getHeaders() and returns the first
matching value, then call that from LocaleFilter to set acceptLanguage, or
replace the loop in LocaleFilter with a concise stream-based one-liner that
finds an entry whose key equalsIgnoreCase("Accept-Language") and maps to its
value; update references to acceptLanguage and the LocaleFilter header lookup to
use the new helper or stream one-liner so the code is clearer and reusable.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e19ed97 and 4bdf984.

📒 Files selected for processing (1)
  • src/main/java/org/example/filter/LocaleFilter.java

Comment thread src/main/java/org/example/filter/LocaleFilter.java

@FeFFe1996 FeFFe1996 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snyggt jobbat!

@viktorlindell12 viktorlindell12 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bra jobbat

@eeebbaandersson eeebbaandersson linked an issue Feb 24, 2026 that may be closed by this pull request
@AntonAhlqvist
AntonAhlqvist merged commit 7652687 into main Feb 25, 2026
3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Feb 25, 2026
Boppler12 pushed a commit that referenced this pull request Feb 25, 2026
* Re-commit LocaleFilter + tests to clean branch for PR

* Update LocaleFilter to handle quality weights and improve javadoc

* Fix: rename test method to reflect actual headers scenario

* Fix: ensure resolveLocale never returns empty string; strip quality weights
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement LocaleFilter to read Accept-Language header

4 participants