Conversation
There were a bunch of leftover from experiments. e.g. _lingoConfig was a hack, a field with a config copy added to the config object which can be read by the translation-server cli to parse the arguments. But bundlers were always complaining about it, plus in next with our async setup it would not work. So there is no way to parse the config now. We could add some though if needed, but honestly parsing the file and finding our config section.
📝 WalkthroughWalkthroughThis pull request introduces a custom locale resolver system for Next.js 16 that enables path-based locale routing instead of cookie-based persistence. It includes new server and client-side locale resolver utilities, a locale-aware link component, middleware for Accept-Language header parsing and locale detection, and updates to the compiler's virtual module generation and type system. Changes
Sequence DiagramsequenceDiagram
actor Browser
participant Middleware
participant Server as Server<br/>(Layout)
participant Provider as LingoProvider
participant Client as Client<br/>(LocaleLink)
Browser->>Middleware: GET /
Middleware->>Middleware: Extract locale from path<br/>or Accept-Language header
Middleware->>Browser: Redirect to /en/test
Browser->>Middleware: GET /en/test
Middleware->>Server: Request with<br/>x-lingo-locale header
Server->>Server: Read locale<br/>from header
Server->>Provider: Wrap with LingoProvider<br/>initialLocale="en"
Provider->>Provider: Initialize context<br/>with locale
Browser->>Client: Hover/click LocaleLink
Client->>Client: Get current locale<br/>from context
Client->>Client: Prefix href with<br/>/${locale}
Client->>Browser: Navigate to prefixed URL
Browser->>Middleware: Follow new locale path
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
| @@ -1 +1,2 @@ | |||
| export type { PartialLingoConfig } from "./types"; | |||
| export type { LocaleCode } from "lingo.dev/spec"; | |||
There was a problem hiding this comment.
Makes sense to reexport I think, because it's used in many places, and compiler could be the single dependency.
|
|
||
| export function getLocalePathname(locale) { | ||
| return null; // Not used for cookie-based routing | ||
| } |
There was a problem hiding this comment.
| export function getLocalePathname(locale) { | |
| return null; // Not used for cookie-based routing | |
| } |
| if (router) { | ||
| router.refresh(); | ||
| if (newUrl) { | ||
| router.push(newUrl); |
There was a problem hiding this comment.
I missed that for the path based i18n we will have to do navigation. I don't think we need to add a separate method for that, just returning newUrl form persistLocale if redirect is needed sounds reasonable to me.
Next needs a push, because we have to trigger a middleware so that server components get a correct locale
| } { | ||
| const baseDir = path.join(projectRoot, sourceRoot, lingoDir); | ||
|
|
||
| const serverPath = resolveResolverPath("locale-resolver-server", baseDir); |
There was a problem hiding this comment.
The whole file is a way to check that the file are present where we expect them to be. In this PR I expect them to be in the lingo directory, but we are free to come up with any pattern we want.
Also a nice error if one of the files is missing when custom locale resolver is chosen in settings would be good
| process.cwd(), | ||
| ); | ||
|
|
||
| customResolverAliases = { |
There was a problem hiding this comment.
For vite and unplugin there should already be a similar thing.
See
function tryLocalOrReturnVirtual(
config: LingoConfig,
fileName: string,
virtualName: string,
) {
const customPath = path.join(config.sourceRoot, config.lingoDir, fileName);
if (fs.existsSync(customPath)) {
return customPath;
}
return virtualName;
}
I don't remember if I checked it working
| @@ -0,0 +1,4 @@ | |||
| import type { LocaleCode } from "@lingo.dev/compiler" | |||
There was a problem hiding this comment.
Just a way to avoid repeating these
| @@ -0,0 +1,107 @@ | |||
| import { NextRequest, NextResponse } from "next/server"; | |||
There was a problem hiding this comment.
It's 2026 why the hell Next asks users to configure all this hell when they want path based i18n
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (5)
demo/new-compiler-next16/app/.lingo/locale-resolver-server.ts (1)
25-29: Avoid hardcoded fallback locale in server resolver.At Line 28,
"en"can diverge from configured defaults. Prefer pulling fallback from shared locale config to keep resolver behavior consistent.Proposed refactor
import { headers } from "next/headers"; +import { sourceLocale } from "../../supported-locales"; ... export async function getServerLocale(): Promise<string> { const headersList = await headers(); const locale = headersList.get("x-lingo-locale"); - return locale || "en"; + return locale || sourceLocale; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@demo/new-compiler-next16/app/.lingo/locale-resolver-server.ts` around lines 25 - 29, getServerLocale currently falls back to a hardcoded "en" which can diverge from app defaults; update getServerLocale to pull the fallback from the shared locale configuration (e.g. use the exported DEFAULT_LOCALE constant or getDefaultLocale() function) instead of the literal "en", and ensure the module imports that symbol and returns it when headers().get("x-lingo-locale") is falsy.demo/new-compiler-next16/app/[locale]/layout.tsx (1)
32-32: Consider removing or guardingconsole.debugstatement.This debug log will appear in production server logs. Consider removing it or using a conditional/logger utility that respects environment.
🔧 Proposed fix
- console.debug("LocaleLayout", { locale });Or if debugging is needed:
- console.debug("LocaleLayout", { locale }); + if (process.env.NODE_ENV === "development") { + console.debug("LocaleLayout", { locale }); + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@demo/new-compiler-next16/app/`[locale]/layout.tsx at line 32, Remove or guard the console.debug call so it doesn't leak debug output to production logs: either delete the line console.debug("LocaleLayout", { locale }) in the layout (or LocaleLayout component) or wrap it behind an environment check/logger that only emits debug in development (e.g., process.env.NODE_ENV === "development" or using the project's logger utility) so production servers won't log the locale debug message.packages/new-compiler/src/plugin/resolve-locale-resolver.ts (1)
26-41: Silent fallback when resolver file doesn't exist.When no resolver file is found,
resolveResolverPathreturns the base path without extension (line 40). This will cause a confusing Turbopack error downstream rather than a clear "resolver file not found" message.Consider adding validation or a clearer error:
🔧 Proposed improvement
function resolveResolverPath(basePath: string, projectRoot: string): string { // Try with the provided extension first const absolutePath = path.resolve(projectRoot, basePath); if (fs.existsSync(absolutePath)) { return absolutePath; } for (const ext of EXTENSIONS) { const pathWithExt = absolutePath + ext; if (fs.existsSync(pathWithExt)) { return pathWithExt; } } - return absolutePath; + throw new Error( + `Custom locale resolver not found: ${basePath}\n` + + `Expected one of: ${EXTENSIONS.map(ext => basePath + ext).join(', ')}\n` + + `Searched in: ${projectRoot}` + ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/new-compiler/src/plugin/resolve-locale-resolver.ts` around lines 26 - 41, The resolveResolverPath function currently returns the untranslated absolutePath when no file exists, causing unclear downstream errors; update resolveResolverPath to validate that a resolver file was found and throw a clear Error (or return a distinct failure) if none of the checked paths exist. Specifically, after trying absolutePath and the loop over EXTENSIONS (referencing resolveResolverPath and EXTENSIONS), construct a descriptive error message that includes the attempted basePath, projectRoot, and the candidate paths checked, then throw that Error so callers get an explicit "resolver file not found" message instead of a confusing Turbopack error.demo/new-compiler-next16/proxy.ts (1)
28-32: Type narrowing may be needed forincludes()check.
SUPPORTED_LOCALESisLocaleCode[]whilecodeisstring. Depending on TypeScript strictness settings,includes(code)might not properly narrow the type. This works at runtime but could benefit from explicit typing.🔧 Optional type-safe alternative
// Find first supported locale for (const { code } of languages) { - if (SUPPORTED_LOCALES.includes(code)) { + if ((SUPPORTED_LOCALES as readonly string[]).includes(code)) { return code; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@demo/new-compiler-next16/proxy.ts` around lines 28 - 32, The includes check compares a string `code` from `languages` against `SUPPORTED_LOCALES: LocaleCode[]` and may not narrow types under strict TS; update the check to be type-safe by either (A) narrowing `code` to `LocaleCode` with a type guard before returning, or (B) cast/assert `code as LocaleCode` when calling `SUPPORTED_LOCALES.includes`, or better yet adjust the `languages` item type so `code` is typed as `LocaleCode`; locate the loop using `for (const { code } of languages)` and change the check to use one of these approaches so TypeScript understands the result is a valid `LocaleCode`.packages/new-compiler/src/virtual/code-generator.ts (1)
74-77: Remove unusedgetLocalePathnameexport or clarify intent with TODO comment.This function is exported but never imported anywhere in the codebase. The existing comment ("Not used for cookie-based routing") suggests it's an intentional placeholder, but either remove it to avoid dead code or add a
TODOcomment if it's reserved for future use.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/new-compiler/src/virtual/code-generator.ts` around lines 74 - 77, The getLocalePathname function is exported but unused; either delete the exported function entirely or keep it but clarify intent: if removing, remove the export and function declaration for getLocalePathname; if reserving for future use, add a clear TODO/JSDoc above getLocalePathname explaining why it is a placeholder for cookie-based routing and retain the export to avoid linter noise (e.g., "TODO: reserved for future cookie-based routing — keep exported until feature implemented"); update the signature or comment accordingly so the purpose is explicit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@demo/new-compiler-next16/app/.lingo/locale-resolver-client.ts`:
- Around line 29-36: The resolver currently returns the first path segment
(potentialLocale) without checking it against the supported list; update the
logic in locale-resolver-client.ts to validate potentialLocale with
SUPPORTED_LOCALES (e.g., if (potentialLocale &&
SUPPORTED_LOCALES.includes(potentialLocale))) before returning it as LocaleCode,
otherwise fall back to the existing fallback/undefined behavior so the client
matches the server's locale validation.
In `@demo/new-compiler-next16/app/layout.tsx`:
- Around line 7-13: The RootLayout component currently returns children directly
which causes missing-root-layout-tags errors; update the export default function
RootLayout to return a root HTML structure by wrapping children in <html> and
<body> elements (e.g., return <html><body>{children}</body></html>), ensuring
the component still accepts the existing Readonly<{ children: ReactNode }> props
and exports as before.
In `@demo/new-compiler-next16/components/LocaleLink.tsx`:
- Around line 13-16: The locale-prefix check in LocaleLink.tsx (the
localizedHref logic) only prevents adding the current locale and can produce
double prefixes like "/es/en/about"; update the condition to detect any
supported locale prefix before prepending: replace the
!href.startsWith(`/${locale}`) check with a test against the app's supported
locales (e.g., supportedLocales or i18n.locales) so you only prepend
`/${locale}` when href does not start with "/" + anySupportedLocale (handle both
"/{locale}" and "/{locale}/" cases) and still skip external URLs starting with
"http".
In `@packages/new-compiler/src/plugin/next.ts`:
- Around line 262-287: The webpack config is missing the same alias wiring
applied to Turbopack: when lingoConfig.localePersistence.type === "custom" the
customResolverAliases are merged into mergedResolveAlias but that merged map
isn't applied to webpack's resolve.alias, causing imports like
'@lingo.dev/compiler/virtual/locale-server' to fail; fix by assigning
mergedResolveAlias to the webpack configuration's resolve.alias (or merging it
into any existing webpackResolveAlias) where the webpack config is
built/returned so webpack uses the same aliases as Turbopack (reference
existingResolveAlias, customResolverAliases, mergedResolveAlias).
In `@packages/new-compiler/src/react/shared/LingoProvider.tsx`:
- Around line 41-43: The router type in LingoProvider.tsx was changed to require
push which is a breaking API; make push optional on the router property (change
router?: { refresh: () => void; push?: (path: string) => void }) and update all
usages inside the LingoProvider component (and any helper functions referenced
there) to guard calls to push (use optional chaining or an existence check
before calling) so existing consumers that only provide refresh won't break.
---
Nitpick comments:
In `@demo/new-compiler-next16/app/.lingo/locale-resolver-server.ts`:
- Around line 25-29: getServerLocale currently falls back to a hardcoded "en"
which can diverge from app defaults; update getServerLocale to pull the fallback
from the shared locale configuration (e.g. use the exported DEFAULT_LOCALE
constant or getDefaultLocale() function) instead of the literal "en", and ensure
the module imports that symbol and returns it when
headers().get("x-lingo-locale") is falsy.
In `@demo/new-compiler-next16/app/`[locale]/layout.tsx:
- Line 32: Remove or guard the console.debug call so it doesn't leak debug
output to production logs: either delete the line console.debug("LocaleLayout",
{ locale }) in the layout (or LocaleLayout component) or wrap it behind an
environment check/logger that only emits debug in development (e.g.,
process.env.NODE_ENV === "development" or using the project's logger utility) so
production servers won't log the locale debug message.
In `@demo/new-compiler-next16/proxy.ts`:
- Around line 28-32: The includes check compares a string `code` from
`languages` against `SUPPORTED_LOCALES: LocaleCode[]` and may not narrow types
under strict TS; update the check to be type-safe by either (A) narrowing `code`
to `LocaleCode` with a type guard before returning, or (B) cast/assert `code as
LocaleCode` when calling `SUPPORTED_LOCALES.includes`, or better yet adjust the
`languages` item type so `code` is typed as `LocaleCode`; locate the loop using
`for (const { code } of languages)` and change the check to use one of these
approaches so TypeScript understands the result is a valid `LocaleCode`.
In `@packages/new-compiler/src/plugin/resolve-locale-resolver.ts`:
- Around line 26-41: The resolveResolverPath function currently returns the
untranslated absolutePath when no file exists, causing unclear downstream
errors; update resolveResolverPath to validate that a resolver file was found
and throw a clear Error (or return a distinct failure) if none of the checked
paths exist. Specifically, after trying absolutePath and the loop over
EXTENSIONS (referencing resolveResolverPath and EXTENSIONS), construct a
descriptive error message that includes the attempted basePath, projectRoot, and
the candidate paths checked, then throw that Error so callers get an explicit
"resolver file not found" message instead of a confusing Turbopack error.
In `@packages/new-compiler/src/virtual/code-generator.ts`:
- Around line 74-77: The getLocalePathname function is exported but unused;
either delete the exported function entirely or keep it but clarify intent: if
removing, remove the export and function declaration for getLocalePathname; if
reserving for future use, add a clear TODO/JSDoc above getLocalePathname
explaining why it is a placeholder for cookie-based routing and retain the
export to avoid linter noise (e.g., "TODO: reserved for future cookie-based
routing — keep exported until feature implemented"); update the signature or
comment accordingly so the purpose is explicit.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 17389229-3324-4733-9de9-60737c78f642
📒 Files selected for processing (18)
demo/new-compiler-next16/app/.lingo/locale-resolver-client.tsdemo/new-compiler-next16/app/.lingo/locale-resolver-server.tsdemo/new-compiler-next16/app/[locale]/layout.tsxdemo/new-compiler-next16/app/[locale]/page.tsxdemo/new-compiler-next16/app/[locale]/test/page.tsxdemo/new-compiler-next16/app/layout.tsxdemo/new-compiler-next16/components/LocaleLink.tsxdemo/new-compiler-next16/next.config.tsdemo/new-compiler-next16/proxy.tsdemo/new-compiler-next16/supported-locales.tspackages/new-compiler/src/index.tspackages/new-compiler/src/plugin/next.tspackages/new-compiler/src/plugin/resolve-locale-resolver.tspackages/new-compiler/src/react/shared/LingoProvider.tsxpackages/new-compiler/src/translation-server/translation-server.tspackages/new-compiler/src/types.tspackages/new-compiler/src/virtual/code-generator.tspackages/new-compiler/src/virtual/locale/client.ts
| try { | ||
| const pathname = window.location.pathname; | ||
| const segments = pathname.split("/").filter(Boolean); | ||
| const potentialLocale = segments[0]; | ||
|
|
||
| if (potentialLocale) { | ||
| return potentialLocale as LocaleCode; | ||
| } |
There was a problem hiding this comment.
Missing validation against supported locales.
Unlike proxy.ts which validates SUPPORTED_LOCALES.includes(potentialLocale), this function returns any first path segment as the locale without validation. This could cause client/server mismatch if an invalid locale somehow reaches the URL.
Consider validating against the supported locales list:
🛡️ Proposed fix
import type { LocaleCode } from "@lingo.dev/compiler"
-import { sourceLocale } from "../../supported-locales";
+import { sourceLocale, supportedLocales } from "../../supported-locales";
// ... in getClientLocale():
const potentialLocale = segments[0];
- if (potentialLocale) {
- return potentialLocale as LocaleCode;
+ if (potentialLocale && supportedLocales.includes(potentialLocale as LocaleCode)) {
+ return potentialLocale as LocaleCode;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@demo/new-compiler-next16/app/.lingo/locale-resolver-client.ts` around lines
29 - 36, The resolver currently returns the first path segment (potentialLocale)
without checking it against the supported list; update the logic in
locale-resolver-client.ts to validate potentialLocale with SUPPORTED_LOCALES
(e.g., if (potentialLocale && SUPPORTED_LOCALES.includes(potentialLocale)))
before returning it as LocaleCode, otherwise fall back to the existing
fallback/undefined behavior so the client matches the server's locale
validation.
| export default function RootLayout({ | ||
| children, | ||
| }: Readonly<{ | ||
| children: React.ReactNode; | ||
| children: ReactNode; | ||
| }>) { | ||
| return ( | ||
| <LingoProvider> | ||
| <html> | ||
| <body | ||
| className={`${geistSans.variable} ${geistMono.variable} antialiased`} | ||
| > | ||
| {children} | ||
| </body> | ||
| </html> | ||
| </LingoProvider> | ||
| ); | ||
| return children; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Next.js 16 App Router: does the top-level app/layout.tsx root layout still require both <html> and <body> tags?
💡 Result:
Yes, the top-level app/layout.tsx root layout in Next.js 16 App Router still requires both <html> and <body> tags.
Citations:
- 1: https://nextjs.org/docs/messages/missing-root-layout-tags
- 2: https://nextjs.org/docs/app/getting-started/layouts-and-pages
- 3: https://beta.nextjs.org/docs/api-reference/file-conventions/layout
- 4: https://beta.nextjs.org/docs/routing/fundamentals
- 5: https://nextjs.org/blog/next-16
- 6: https://nextjs.org/docs/app/guides/upgrading/version-16
- 7: https://www.knrestaurants.com
- 8: https://nextjs.org/docs/13/app/building-your-application/routing/pages-and-layouts
- 9: https://nextjs.org/docs/14/app/api-reference/file-conventions/layout
Add <html> and <body> tags to the root layout.
The top-level app/layout.tsx must render <html> and <body> elements. Returning children directly will cause missing-root-layout-tags errors at runtime.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@demo/new-compiler-next16/app/layout.tsx` around lines 7 - 13, The RootLayout
component currently returns children directly which causes
missing-root-layout-tags errors; update the export default function RootLayout
to return a root HTML structure by wrapping children in <html> and <body>
elements (e.g., return <html><body>{children}</body></html>), ensuring the
component still accepts the existing Readonly<{ children: ReactNode }> props and
exports as before.
| // If href is already locale-prefixed or external, use as-is | ||
| const localizedHref = typeof href === "string" && !href.startsWith("http") && !href.startsWith(`/${locale}`) | ||
| ? `/${locale}${href.startsWith("/") ? "" : "/"}${href}` | ||
| : href; |
There was a problem hiding this comment.
Locale prefix check only guards against current locale, not all supported locales.
If locale is "es" and href is "/en/about" (already prefixed with a different locale), this would incorrectly produce "/es/en/about".
Consider checking against all supported locales:
🐛 Proposed fix
+"use client";
+
+import NextLink from "next/link";
+import { useLingoContext } from "@lingo.dev/compiler/react";
+import type { ComponentProps } from "react";
+import { supportedLocales } from "@/supported-locales";
+
+/**
+ * Check if href already starts with a supported locale prefix
+ */
+function hasLocalePrefix(href: string): boolean {
+ return supportedLocales.some(
+ (loc) => href === `/${loc}` || href.startsWith(`/${loc}/`)
+ );
+}
+
/**
* Link component that automatically prefixes hrefs with current locale
*/
export function Link({ href, ...props }: ComponentProps<typeof NextLink>) {
const { locale } = useLingoContext();
- // If href is already locale-prefixed or external, use as-is
- const localizedHref = typeof href === "string" && !href.startsWith("http") && !href.startsWith(`/${locale}`)
+ // If href is already locale-prefixed or external, use as-is
+ const localizedHref = typeof href === "string" && !href.startsWith("http") && !hasLocalePrefix(href)
? `/${locale}${href.startsWith("/") ? "" : "/"}${href}`
: href;
return <NextLink href={localizedHref} {...props} />;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@demo/new-compiler-next16/components/LocaleLink.tsx` around lines 13 - 16, The
locale-prefix check in LocaleLink.tsx (the localizedHref logic) only prevents
adding the current locale and can produce double prefixes like "/es/en/about";
update the condition to detect any supported locale prefix before prepending:
replace the !href.startsWith(`/${locale}`) check with a test against the app's
supported locales (e.g., supportedLocales or i18n.locales) so you only prepend
`/${locale}` when href does not start with "/" + anySupportedLocale (handle both
"/{locale}" and "/{locale}/" cases) and still skip external URLs starting with
"http".
| const existingResolveAlias = existingTurbopackConfig.resolveAlias; | ||
| let customResolverAliases = {}; | ||
|
|
||
| // Custom locale resolvers: | ||
| // When using custom resolvers (localePersistence.type === "custom"), | ||
| // we map abstract module paths to the user's actual files via Turbopack resolveAlias. | ||
| // This allows virtual modules to import from '@lingo.dev/compiler/virtual/locale-*' | ||
| // which Turbopack resolves to the user's actual locale resolver files. | ||
| // | ||
| // Convention: Resolver files must be at <sourceRoot>/<lingoDir>/locale-resolver-{server|client}.ts | ||
| if (lingoConfig.localePersistence.type === "custom") { | ||
| const resolvedPaths = resolveCustomResolverPaths( | ||
| lingoConfig.sourceRoot, | ||
| lingoConfig.lingoDir, | ||
| process.cwd(), | ||
| ); | ||
|
|
||
| customResolverAliases = { | ||
| "@lingo.dev/compiler/virtual/locale-server": resolvedPaths.serverResolver, | ||
| "@lingo.dev/compiler/virtual/locale-client": resolvedPaths.clientResolver, | ||
| }; | ||
| } | ||
|
|
||
| const mergedResolveAlias = { | ||
| ...existingResolveAlias, | ||
| // TODO (AleksandrSl 08/12/2025): Describe what have to be done to support custom resolvers | ||
| ...customResolverAliases, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify whether webpack path has equivalent custom resolver handling.
# Expected: either webpack resolve.alias is configured, or locale loaders directly resolve user custom resolver files.
rg -n "resolveCustomResolverPaths|virtual/locale-server|virtual/locale-client|locale-resolver" packages/new-compiler/src
fd 'next-locale-.*loader\.ts$' packages/new-compiler/src --exec sed -n '1,260p' {}
rg -n "webpack\\s*:\\s*\\(|resolve\\s*:\\s*\\{\\s*alias|resolveAlias" packages/new-compiler/src/plugin/next.tsRepository: lingodotdev/lingo.dev
Length of output: 3468
🏁 Script executed:
cat -n packages/new-compiler/src/plugin/next.ts | sed -n '260,320p'Repository: lingodotdev/lingo.dev
Length of output: 2410
🏁 Script executed:
# Check if webpack configuration exists in the file
rg -n "webpack" packages/new-compiler/src/plugin/next.ts | head -20Repository: lingodotdev/lingo.dev
Length of output: 493
🏁 Script executed:
# Understand the unplugin approach and how it handles resolvers
cat -n packages/new-compiler/src/plugin/unplugin.ts | sed -n '1,100p'Repository: lingodotdev/lingo.dev
Length of output: 3892
🏁 Script executed:
cat -n packages/new-compiler/src/plugin/next.ts | sed -n '340,400p'Repository: lingodotdev/lingo.dev
Length of output: 1324
🏁 Script executed:
# Check if webpack resolve.alias is set anywhere in the file
rg -n "resolve.*alias|webpack.*alias" packages/new-compiler/src/plugin/next.ts -A 3Repository: lingodotdev/lingo.dev
Length of output: 47
🏁 Script executed:
# Understand the relationship between unplugin and webpack/turbopack
cat -n packages/new-compiler/src/plugin/next.ts | sed -n '1,50p'Repository: lingodotdev/lingo.dev
Length of output: 2083
🏁 Script executed:
# Search for webpack resolve.alias in entire plugin directory
rg -n "resolve.*alias" packages/new-compiler/src/plugin/ -A 2Repository: lingodotdev/lingo.dev
Length of output: 47
🏁 Script executed:
# Check what the loaders return - do they provide resolve.alias?
cat -n packages/new-compiler/src/plugin/next.ts | sed -n '22,120p'Repository: lingodotdev/lingo.dev
Length of output: 3061
🏁 Script executed:
# Understand the issue better: search for how virtual module imports are handled in webpack context
rg -n "@lingo.dev/compiler/virtual" packages/new-compiler/src --type ts -B 2 -A 2 | head -60Repository: lingodotdev/lingo.dev
Length of output: 5491
🏁 Script executed:
# Check if Next.js webpack automatically resolves `@lingo.dev/`* packages
rg -n "resolve.*modules|moduleNameMapper|alias" packages/new-compiler/src/plugin/next.tsRepository: lingodotdev/lingo.dev
Length of output: 47
🏁 Script executed:
# Verify: Can webpack loaders alone handle virtual imports like `@lingo.dev/compiler/virtual/`*, or is alias needed?
# Check if there's any webpack resolve configuration that might handle this
cat -n packages/new-compiler/src/plugin/next.ts | sed -n '345,376p'Repository: lingodotdev/lingo.dev
Length of output: 1240
Add webpack resolve.alias configuration for custom resolver paths.
Custom resolver aliasing is configured for Turbopack (lines 280–281, injected at 296 and 308), but webpack has no equivalent resolve.alias wiring. When localePersistence.type === "custom", webpack will fail to resolve the @lingo.dev/compiler/virtual/locale-* imports to the user's custom resolver files. Apply mergedResolveAlias to the webpack config's resolve.alias to maintain feature parity across bundlers.
Also applies to: 290–311
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/new-compiler/src/plugin/next.ts` around lines 262 - 287, The webpack
config is missing the same alias wiring applied to Turbopack: when
lingoConfig.localePersistence.type === "custom" the customResolverAliases are
merged into mergedResolveAlias but that merged map isn't applied to webpack's
resolve.alias, causing imports like '@lingo.dev/compiler/virtual/locale-server'
to fail; fix by assigning mergedResolveAlias to the webpack configuration's
resolve.alias (or merging it into any existing webpackResolveAlias) where the
webpack config is built/returned so webpack uses the same aliases as Turbopack
(reference existingResolveAlias, customResolverAliases, mergedResolveAlias).
| * For path-based routing, also needs push() for navigation | ||
| */ | ||
| router?: { refresh: () => void }; | ||
| router?: { refresh: () => void; push: (path: string) => void }; |
There was a problem hiding this comment.
Breaking change: router interface now requires push method.
Adding push to the required interface is a breaking change for existing consumers that only provided { refresh: () => void }. This will cause TypeScript errors for current integrations.
Consider making push optional if backward compatibility is needed:
🔧 Proposed fix for backward compatibility
- router?: { refresh: () => void; push: (path: string) => void };
+ router?: { refresh: () => void; push?: (path: string) => void };Then update the usage:
if (newUrl) {
- router.push(newUrl);
+ router.push?.(newUrl) ?? router.refresh();
} else {
router.refresh();
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/new-compiler/src/react/shared/LingoProvider.tsx` around lines 41 -
43, The router type in LingoProvider.tsx was changed to require push which is a
breaking API; make push optional on the router property (change router?: {
refresh: () => void; push?: (path: string) => void }) and update all usages
inside the LingoProvider component (and any helper functions referenced there)
to guard calls to push (use optional chaining or an existence check before
calling) so existing consumers that only provide refresh won't break.
Summary
It's an example of having custom resolvers and what we have to do to support them. Ofc we have to make path based i18n support internally along with the cookies, without custom resolvers.
There are both changes on the user side (demo) and our side (in the compiler, because I removed the custom resolver part for simplicity until we have requests, but PR also demonstrates how we can support path based i18n, probably it can also be done a bit cleaner, and we can provide middleware configuration as exported function, so users don't have to do it, similar how next-intl does this. suprisingly they have a ton of code around these)
Changes
Testing
Business logic tests added:
Visuals
Required for UI/UX changes:
Checklist
Closes #[issue-number]
Summary by CodeRabbit