Skip to content

fix(i18n): preserve locale across navigation and language switching#65

Merged
ViktorSvertoka merged 1 commit into
developfrom
feature/translations
Dec 19, 2025
Merged

fix(i18n): preserve locale across navigation and language switching#65
ViktorSvertoka merged 1 commit into
developfrom
feature/translations

Conversation

@TiZorii
Copy link
Copy Markdown
Collaborator

@TiZorii TiZorii commented Dec 19, 2025

Summary by CodeRabbit

  • Improvements
    • Enhanced internationalization support with improved locale handling across the application.
    • Added Ukrainian language support to improve regional accessibility.
    • Fixed language switcher navigation to correctly preserve the current page when switching languages.
    • Improved redirect behavior for authenticated users to properly maintain locale context.

✏️ Tip: You can customize this high-level summary in your review settings.

@netlify
Copy link
Copy Markdown

netlify Bot commented Dec 19, 2025

Deploy Preview for develop-devlovers ready!

Name Link
🔨 Latest commit 4e9a428
🔍 Latest deploy log https://app.netlify.com/projects/develop-devlovers/deploys/6945836eb820fd0008509871
😎 Deploy Preview https://deploy-preview-65--develop-devlovers.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 19, 2025

Walkthrough

The changes implement locale-aware routing and internationalization improvements across the frontend. A new Claude settings file enables bash commands, layout components receive explicit locale parameters, the root HTML element declares Ukrainian language, locale-prefixed URL navigation is refactored, and middleware logic is updated to handle locale extraction from request paths and redirect authentication traffic appropriately.

Changes

Cohort / File(s) Change Summary
Claude AI Settings
frontend/.claude/settings.local.json
New configuration file introducing a permissions.allow list that enumerates Bash-related commands for Claude settings, including process querying, termination, and npm dev script execution.
Layout Locale Integration
frontend/app/[locale]/layout.tsx, frontend/app/layout.tsx
Modified layout components to explicitly pass locale parameter to i18n message loader and added lang="uk" attribute to the root HTML element.
Language Switcher Navigation
frontend/components/shared/LanguageSwitcher.tsx
Refactored path resolution logic to strip locale prefixes ("/uk", "/en", "/pl") from the full pathname, enabling proper locale-aware navigation when switching languages.
Middleware Locale Handling
frontend/middleware.ts
Updated redirect logic for authenticated access to compute locale from the URL path and added root-path safeguard to redirect exact "/" requests to "/uk". Simplified internationalization header handling by always setting x-app-scope on the response.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Middleware redirect logic: Requires careful review of locale extraction from URL segments and verification that the redirect chain works correctly for both root and authenticated paths.
  • LanguageSwitcher path stripping: Verify that locale prefix removal handles edge cases correctly and that the fallback to "/" is appropriate.
  • Locale parameter propagation: Confirm that explicit locale passing in layout components integrates properly with the i18n system.

Poem

🐰 With locales now woven through every layout line,
Paths stripped of prefixes, redirects align,
Ukrainian roots set deep in the code,
Language-switchers hop down the locale road—
Middleware guards with a dash and a bound! 🌍✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the primary changes: fixing i18n locale preservation across navigation and language switching, which is reflected in updates to middleware, layout components, and the LanguageSwitcher.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/translations

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
frontend/middleware.ts (1)

52-55: Consider locale selection strategy for root path.

The middleware redirects the root path / to /uk, effectively making Ukrainian the default locale for all users. While this is functionally correct and ensures a consistent experience, it may not be ideal for international users.

Consider these alternatives:

  1. Browser language detection: Use the Accept-Language header to redirect to the user's preferred locale (next-intl middleware can handle this automatically if configured).
  2. Geolocation-based default: Determine locale based on the user's location.
  3. Remember user preference: Store the last-used locale in a cookie and redirect to that.

The current approach is valid if Ukrainian is intentionally the primary locale, but you may want to enable next-intl's automatic locale detection by adjusting the middleware configuration:

const intlMiddleware = createIntlMiddleware({
  ...routing,
  localeDetection: true, // Enable automatic locale detection
});

This would allow next-intl to automatically detect and redirect based on the Accept-Language header instead of always defaulting to /uk.

frontend/components/shared/LanguageSwitcher.tsx (1)

15-16: Refactor locale pattern to use dynamic configuration.

The hardcoded locale list (uk|en|pl) appears in multiple locations (LanguageSwitcher.tsx line 16, middleware.ts lines 23 and 46, and the matcher config on line 72). While currently synchronized with the config, this creates maintenance risk—adding or removing locales requires manual updates in each location.

The suggested refactor remains valid. For LanguageSwitcher.tsx specifically:

  const fullPathname = usePathname();
  const params = useParams();
  const currentLocale = params.locale as Locale;

- // Remove locale prefix from pathname: /uk/q&a -> /q&a, /uk -> /
- const pathname = fullPathname.replace(/^\/(uk|en|pl)/, '') || '/';
+ // Remove locale prefix from pathname: /uk/q&a -> /q&a, /uk -> /
+ const localePattern = new RegExp(`^/(${locales.join('|')})`);
+ const pathname = fullPathname.replace(localePattern, '') || '/';

Similar refactoring should be applied to middleware.ts to keep all locale patterns in sync with the config.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 933a1a5 and 4e9a428.

📒 Files selected for processing (5)
  • frontend/.claude/settings.local.json (1 hunks)
  • frontend/app/[locale]/layout.tsx (1 hunks)
  • frontend/app/layout.tsx (1 hunks)
  • frontend/components/shared/LanguageSwitcher.tsx (1 hunks)
  • frontend/middleware.ts (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/components/shared/LanguageSwitcher.tsx (1)
frontend/i18n/config.ts (1)
  • Locale (2-2)
🔇 Additional comments (3)
frontend/app/[locale]/layout.tsx (1)

30-30: LGTM! Explicit locale parameter improves clarity.

Passing the locale parameter explicitly to getMessages({ locale }) is a good improvement. This ensures that the message loading is directly tied to the locale extracted from the URL, making the code more explicit and maintainable.

frontend/components/shared/LanguageSwitcher.tsx (1)

4-4: Minor: Import consolidation is good.

Nice cleanup consolidating the imports from next/navigation into a single line.

frontend/middleware.ts (1)

63-66: LGTM! Simplified header handling improves code clarity.

The refactored header handling is cleaner and more straightforward. Always setting x-app-scope on the intlResponse and returning it directly eliminates unnecessary conditional logic.

Comment on lines +1 to +12
{
"permissions": {
"allow": [
"Bash(grep:*)",
"Bash(Get-Process -Id 9420 -ErrorAction SilentlyContinue)",
"Bash(Stop-Process -Force)",
"Bash(taskkill //F //PID 9420)",
"Bash(npm run dev:*)",
"Bash(taskkill //F //PID 8344)"
]
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove local configuration file from repository.

This file appears to be a local Claude Code editor configuration with machine-specific settings (specific PIDs like 9420 and 8344). It's unrelated to the i18n locale preservation functionality of this PR and shouldn't be committed to the repository.

Files with .local in the name are typically meant to be excluded from version control and listed in .gitignore. This file contains development environment settings that are specific to your local machine and won't be useful to other developers.

Please remove this file from the PR and ensure it's added to .gitignore:

+ # Claude local settings
+ .claude/settings.local.json

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
frontend/.claude/settings.local.json lines 1-12: this is a machine-specific
local Claude editor config (contains PIDs) that should not be committed; remove
the file from the PR and repository history (git rm --cached
frontend/.claude/settings.local.json or git rm
frontend/.claude/settings.local.json and commit), add an entry for
/.claude/*.local or specifically frontend/.claude/settings.local.json to
.gitignore, and commit the .gitignore change so the file is no longer tracked in
future commits.

Comment thread frontend/app/layout.tsx
}>) {
return (
<html suppressHydrationWarning>
<html lang="uk" suppressHydrationWarning>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

# First, explore the frontend app structure
find frontend/app -type f -name "*.tsx" | head -20

Repository: DevLoversTeam/devlovers.net


Repository: DevLoversTeam/devlovers.net
Exit code: 0

stdout:

frontend/app/[locale]/about/page.tsx
frontend/app/[locale]/blog/[slug]/PostDetails.tsx
frontend/app/[locale]/blog/[slug]/page.tsx
frontend/app/[locale]/blog/page.tsx
frontend/app/[locale]/contacts/page.tsx
frontend/app/[locale]/dashboard/page.tsx
frontend/app/[locale]/leaderboard/page.tsx
frontend/app/[locale]/login/page.tsx
frontend/app/[locale]/not-found.tsx
frontend/app/[locale]/page.tsx
frontend/app/[locale]/privacy-policy/page.tsx
frontend/app/[locale]/q&a/page.tsx
frontend/app/[locale]/quiz/[slug]/page.tsx
frontend/app/[locale]/shop/admin/layout.tsx
frontend/app/[locale]/shop/admin/orders/[id]/page.tsx
frontend/app/[locale]/shop/admin/orders/page.tsx
frontend/app/[locale]/shop/admin/page.tsx
frontend/app/[locale]/shop/admin/products/[id]/edit/page.tsx
frontend/app/[locale]/shop/admin/products/_components/product-form.tsx
frontend/app/[locale]/shop/admin/products/new/page.tsx

Script executed:

# Look at the root layout file
cat -n frontend/app/layout.tsx

Repository: DevLoversTeam/devlovers.net


Repository: DevLoversTeam/devlovers.net
Exit code: 0

stdout:

     1	import { Geist, Geist_Mono } from 'next/font/google';
     2	import './globals.css';
     3	
     4	const geistSans = Geist({
     5	  variable: '--font-geist-sans',
     6	  subsets: ['latin'],
     7	});
     8	
     9	const geistMono = Geist_Mono({
    10	  variable: '--font-geist-mono',
    11	  subsets: ['latin'],
    12	});
    13	
    14	export default function RootLayout({
    15	  children,
    16	}: Readonly<{
    17	  children: React.ReactNode;
    18	}>) {
    19	  return (
    20	    <html lang="uk" suppressHydrationWarning>
    21	      <body
    22	        suppressHydrationWarning
    23	        className={`${geistSans.variable} ${geistMono.variable} antialiased bg-gray-50 text-gray-900 dark:bg-neutral-950 dark:text-gray-100 transition-colors duration-300`}
    24	      >
    25	        {children}
    26	      </body>
    27	    </html>
    28	  );
    29	}

Script executed:

# Check if there's a locale-specific layout
cat -n frontend/app/\[locale\]/layout.tsx

Repository: DevLoversTeam/devlovers.net


Repository: DevLoversTeam/devlovers.net
Exit code: 0

stdout:

     1	import { Toaster } from 'sonner';
     2	import { NextIntlClientProvider } from 'next-intl';
     3	import { getMessages } from 'next-intl/server';
     4	import { notFound } from 'next/navigation';
     5	import { locales } from '@/i18n/config';
     6	import { Link } from '@/i18n/routing';
     7	import Footer from '@/components/shared/Footer';
     8	import { ThemeProvider } from '@/components/theme/ThemeProvider';
     9	import { LogoutButton } from '@/components/auth/logoutButton';
    10	import { getCurrentUser } from '@/lib/auth';
    11	import LanguageSwitcher from '@/components/shared/LanguageSwitcher';
    12	import { headers } from 'next/headers';
    13	import { ShopShell } from '@/components/shop/shop-shell';
    14	
    15	export const dynamic = 'force-dynamic';
    16	
    17	export default async function LocaleLayout({
    18	  children,
    19	  params,
    20	}: Readonly<{
    21	  children: React.ReactNode;
    22	  params: Promise<{ locale: string }>;
    23	}>) {
    24	  const { locale } = await params;
    25	
    26	  if (!locales.includes(locale as any)) {
    27	    notFound();
    28	  }
    29	
    30	  const messages = await getMessages({ locale });
    31	  const user = await getCurrentUser();
    32	  const scope = (await headers()).get('x-app-scope') ?? 'site';
    33	  const showAdminNavLink = process.env.NEXT_PUBLIC_ENABLE_ADMIN === 'true';
    34	
    35	  return (
    36	    <NextIntlClientProvider messages={messages}>
    37	      <ThemeProvider
    38	        attribute="class"
    39	        defaultTheme="system"
    40	        enableSystem
    41	        disableTransitionOnChange
    42	      >
    43	        {scope === 'shop' ? (
    44	          <ShopShell showAdminLink={showAdminNavLink}>
    45	            <main className="mx-auto px-6 min-h-[80vh]">{children}</main>
    46	          </ShopShell>
    47	        ) : (
    48	          <>
    49	            <header className="bg-white dark:bg-neutral-900 border-b border-gray-200 dark:border-neutral-800 shadow-sm sticky top-0 z-50 transition-colors">
    50	              <div className="max-w-5xl mx-auto flex items-center justify-between px-6 py-4">
    51	                <Link href="/" className="flex items-center gap-2">
    52	                  <span className="text-xl font-bold text-blue-600 dark:text-blue-500">
    53	                    DevLovers
    54	                  </span>
    55	                </Link>
    56	
    57	                <nav className="flex items-center gap-6 text-gray-700 dark:text-gray-300 font-medium">
    58	                  <Link
    59	                    href="/q&a"
    60	                    className="hover:text-blue-600 dark:hover:text-blue-400 transition"
    61	                  >
    62	                    Q&A
    63	                  </Link>
    64	                  <Link
    65	                    href="/quiz/react-fundamentals"
    66	                    className="hover:text-blue-600 dark:hover:text-blue-400 transition"
    67	                  >
    68	                    Quiz
    69	                  </Link>
    70	                  <Link
    71	                    href="/leaderboard"
    72	                    className="hover:text-blue-600 dark:hover:text-blue-400 transition"
    73	                  >
    74	                    Leaderboard
    75	                  </Link>
    76	                  <Link
    77	                    href="/blog"
    78	                    className="hover:text-blue-600 dark:hover:text-blue-400 transition"
    79	                  >
    80	                    Blog
    81	                  </Link>
    82	                  <Link
    83	                    href="/about"
    84	                    className="hover:text-blue-600 dark:hover:text-blue-400 transition"
    85	                  >
    86	                    About
    87	                  </Link>
    88	                  <Link
    89	                    href="/contacts"
    90	                    className="hover:text-blue-600 dark:hover:text-blue-400 transition"
    91	                  >
    92	                    Contacts
    93	                  </Link>
    94	                  <Link
    95	                    href="/shop"
    96	                    className="hover:text-blue-600 dark:hover:text-blue-400 transition"
    97	                  >
    98	                    Shop
    99	                  </Link>
   100	
   101	                  {!user ? (
   102	                    <Link
   103	                      href="/login"
   104	                      className="hover:text-blue-600 dark:hover:text-blue-400 transition"
   105	                    >
   106	                      Log in
   107	                    </Link>
   108	                  ) : (
   109	                    <LogoutButton />
   110	                  )}
   111	
   112	                  <LanguageSwitcher />
   113	                </nav>
   114	              </div>
   115	            </header>
   116	
   117	            <main className="mx-auto px-6 min-h-[80vh]">{children}</main>
   118	          </>
   119	        )}
   120	        <Footer />
   121	
   122	        <Toaster position="top-right" richColors expand />
   123	      </ThemeProvider>
   124	    </NextIntlClientProvider>
   125	  );
   126	}

Script executed:

# Check for middleware configuration
git ls-files | grep -E "(middleware|next\.config)"

Repository: DevLoversTeam/devlovers.net


Repository: DevLoversTeam/devlovers.net
Exit code: 0

stdout:

frontend/middleware.ts
frontend/next.config.ts

Next.js 15 i18n lang attribute best practices multiple locales


Here are concise best-practice recommendations for handling the HTML lang attribute and multiple locales in Next.js (v15 / App + Pages routing patterns):

  1. Configure Next.js i18n in next.config.js (locales + defaultLocale). Next will manage locale routing and add the html lang automatically for Pages router; for App router you can set in your root layout. [1][2]

  2. Use canonical UTS locale identifiers (e.g., en, en-US, pt-BR). Include region variants only when you actually support them (they act as fallbacks). [1]

  3. Static generation: generate locale-specific routes (generateStaticParams or getStaticPaths) so each locale renders with the correct lang attribute at build time. [2]

  4. SEO: add hreflang links for alternate locale/region variants and canonical tags (Next.js does not auto-create hreflang for page variants). [1]

  5. Locale detection & persistence: use Next’s localeDetection config and the NEXT_LOCALE cookie to control redirects and persist user choice; set localeDetection:false if you want full manual control. [1][3]

  6. Domain routing: if using domain-per-locale, declare domain mappings in i18n config so the correct defaultLocale and html lang are applied per domain. [1]

  7. Right-to-left (RTL) languages: set dir on (dir="rtl") when appropriate (App router root layout or custom Document) alongside lang. [2][7]

  8. Accessibility & correctness: ensure the lang value exactly matches the content language on the page (don’t set a single site-wide lang if pages contain different languages). [1][2]

References

  • Next.js i18n (Pages / guides). [1]
  • Next.js App Router internationalization (app/[lang] layouts, generateStaticParams, setting ). [2]
  • NEXT_LOCALE cookie & locale detection notes. [3]

Script executed:

# Check if [locale] layout uses metadata export
grep -n "export const metadata\|export async function generateMetadata" frontend/app/\[locale\]/layout.tsx

Repository: DevLoversTeam/devlovers.net


Repository: DevLoversTeam/devlovers.net
Exit code: 1


Script executed:

# Check i18n config to understand locale setup
cat -n frontend/i18n/config.ts 2>/dev/null || find frontend -name "config.ts" -o -name "config.js" | grep i18n | head -5

Repository: DevLoversTeam/devlovers.net


Repository: DevLoversTeam/devlovers.net
Exit code: 0

stdout:

     1	export const locales = ['uk', 'en', 'pl'] as const;
     2	export type Locale = (typeof locales)[number];
     3	
     4	export const defaultLocale: Locale = 'uk';
     5	
     6	export const localeNames: Record<Locale, string> = {
     7	  uk: 'Українська',
     8	  en: 'English',
     9	  pl: 'Polski',
    10	};

Script executed:

# Check middleware to see if it handles locale routing
head -50 frontend/middleware.ts

Repository: DevLoversTeam/devlovers.net


Repository: DevLoversTeam/devlovers.net
Exit code: 0

stdout:

import { NextRequest, NextResponse } from 'next/server';
import createIntlMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';

const AUTH_COOKIE_NAME = 'auth_session';

const _AUTH_SECRET = process.env.AUTH_SECRET;

if (!_AUTH_SECRET) {
  throw new Error('AUTH_SECRET is not defined');
}

function isAuthenticated(req: NextRequest): boolean {
  return Boolean(req.cookies.get(AUTH_COOKIE_NAME)?.value);
}

const intlMiddleware = createIntlMiddleware(routing);

function authMiddleware(req: NextRequest) {
  const { pathname } = req.nextUrl;
  const authenticated = isAuthenticated(req);

  const pathnameWithoutLocale = pathname.replace(/^\/(uk|en|pl)/, '') || '/';

  if (
    (pathnameWithoutLocale === '/login' || pathnameWithoutLocale === '/signup') && authenticated
  ) {
    const locale = pathname.split('/')[1] || 'uk'; 
    return NextResponse.redirect(new URL(`/${locale}/`, req.url)); 
  }

  if (
    pathnameWithoutLocale.startsWith('/leaderboard') ||
    pathnameWithoutLocale.startsWith('/quiz') ||
    pathnameWithoutLocale.startsWith('/dashboard')
  ) {
    if (!authenticated) {
      const locale = pathname.split('/')[1];
      return NextResponse.redirect(new URL(`/${locale}/login`, req.url));
    }
  }

  return null;
}
function getScopeFromPathname(pathname: string): "shop" | "site" {
  const pathnameWithoutLocale = pathname.replace(/^\/(uk|en|pl)(?=\/|$)/, "") || "/"
  return pathnameWithoutLocale.startsWith("/shop") ? "shop" : "site"
}



Hard-coded lang="uk" prevents correct language declaration for non-Ukrainian locales.

The lang attribute is hard-coded to "uk" in the root layout, but the application supports multiple locales (uk, en, pl). This causes all routes—including /en/* and /pl/*—to declare Ukrainian as the page language, creating accessibility issues (screen readers won't apply correct pronunciation) and SEO problems (search engines misidentify content language).

Since the root layout cannot access the [locale] segment parameter in Next.js 15's App Router hierarchy, the lang attribute must be handled in the locale-specific layout. Remove lang from the root layout and set it dynamically in frontend/app/[locale]/layout.tsx via metadata or a lang provider component that passes the correct language to the HTML element.

🤖 Prompt for AI Agents
In frontend/app/layout.tsx around line 20, remove the hard-coded lang="uk" from
the root <html> element so the root layout no longer declares a fixed language;
then add the language declaration in the locale-specific layout at
frontend/app/[locale]/layout.tsx by setting the HTML lang dynamically (either
via Next.js metadata export or by rendering the <html lang={locale}> in that
file or using a small provider component that injects the correct lang into the
HTML element) so each locale route (uk, en, pl) sets the appropriate lang
attribute.

Comment thread frontend/middleware.ts
Comment on lines +26 to 30
(pathnameWithoutLocale === '/login' || pathnameWithoutLocale === '/signup') && authenticated
) {
return NextResponse.redirect(new URL(pathname.split('/').slice(0, 2).join('/') || '/', req.url));
const locale = pathname.split('/')[1] || 'uk';
return NextResponse.redirect(new URL(`/${locale}/`, req.url));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify pathname structure in authentication routes

# Find all redirects and route definitions related to authentication
echo "Authentication route patterns:"
rg -n "login|signup" frontend/app --type ts 2>/dev/null || echo "No matches in frontend/app"

# Check auth-related paths in middleware
echo -e "\nAuthentication-related middleware logic:"
rg -n "login|signup" frontend/middleware.ts -A 3 -B 3

# Look for other places where locale is extracted from pathname
echo -e "\nOther locale extraction patterns:"
rg -n "pathname\.split" frontend --type ts

# Also check the exact middleware file structure
echo -e "\nFull middleware.ts context around lines 26-30:"
cat -n frontend/middleware.ts | sed -n '20,35p'

Repository: DevLoversTeam/devlovers.net

Length of output: 2671


Address inconsistent locale fallback in middleware redirects.

The locale extraction pattern pathname.split('/')[1] is used in two places with different fallback handling:

  • Line 28: Has fallback || 'uk' when redirecting authenticated users away from login/signup—this is appropriate.
  • Line 38: Same extraction without fallback when redirecting unauthenticated users—if a request somehow lacks a valid locale prefix, this would extract an invalid value like 'login' and create malformed redirects like /{login}/login.

Given the Next.js [locale] dynamic segment routing, valid requests should always include a locale. However, add the fallback on line 38 to match line 28 for consistency and resilience: const locale = pathname.split('/')[1] || 'uk';

🤖 Prompt for AI Agents
In frontend/middleware.ts around lines 26-30 (and also update the similar logic
near line ~38), the locale extraction uses pathname.split('/')[1] without a safe
fallback in one place causing malformed redirects; update the second occurrence
to use const locale = pathname.split('/')[1] || 'uk'; so both redirect branches
consistently fall back to 'uk' when no valid locale segment is present.

@ViktorSvertoka ViktorSvertoka merged commit 57cda44 into develop Dec 19, 2025
5 checks passed
@ViktorSvertoka ViktorSvertoka deleted the feature/translations branch December 19, 2025 18:13
liudmylasovetovs pushed a commit that referenced this pull request Jan 9, 2026
fix(i18n): preserve locale across navigation and language switching
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.

2 participants