Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions dev-packages/e2e-tests/test-applications/nextjs-15-intl/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# env files (can opt-in for commiting if needed)
.env*

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# Sentry
.sentryclirc

pnpm-lock.yaml
.tmp_dev_server_logs
.tmp_build_stdout
.tmp_build_stderr
event-dumps
test-results

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@sentry:registry=http://localhost:4873
@sentry-internal:registry=http://localhost:4873
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default async function I18nTestPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
return (
<div>
<h1>I18n Test Page</h1>
<p>Current locale: {locale}</p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default async function LocaleRootPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
return (
<div>
<h1>Locale Root</h1>
<p>Current locale: {locale}</p>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const metadata = {
title: 'Next.js 15 i18n Test',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { getRequestConfig } from 'next-intl/server';
import { hasLocale } from 'next-intl';
import { routing } from './routing';

export default getRequestConfig(async ({ requestLocale }) => {
// Typically corresponds to the `[locale]` segment
const requested = await requestLocale;
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale;

return {
locale,
messages: {},
};
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';

export const routing = defineRouting({
locales: ['en', 'ar', 'fr'],
defaultLocale: 'en',
localePrefix: 'as-needed',
});

export const { Link, redirect, usePathname, useRouter } = createNavigation(routing);
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import * as Sentry from '@sentry/nextjs';

Sentry.init({
environment: 'qa',
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`,
tracesSampleRate: 1.0,
sendDefaultPii: true,
});

export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as Sentry from '@sentry/nextjs';

export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('./sentry.server.config');
}

if (process.env.NEXT_RUNTIME === 'edge') {
await import('./sentry.edge.config');
}
}

export const onRequestError = Sentry.captureRequestError;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import createMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';

export default createMiddleware(routing);

export const config = {
matcher: ['/((?!api|_next|.*\\..*).*)'],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { withSentryConfig } = require('@sentry/nextjs');
const createNextIntlPlugin = require('next-intl/plugin');

const withNextIntl = createNextIntlPlugin('./i18n/request.ts');

/** @type {import('next').NextConfig} */
const nextConfig = {};

module.exports = withSentryConfig(withNextIntl(nextConfig), {
silent: true,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "nextjs-15-intl",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "next build > .tmp_build_stdout 2> .tmp_build_stderr || (cat .tmp_build_stdout && cat .tmp_build_stderr && exit 1)",
"clean": "npx rimraf node_modules pnpm-lock.yaml .tmp_dev_server_logs",
"test:prod": "TEST_ENV=production playwright test",
"test:dev": "TEST_ENV=development playwright test",
"test:build": "pnpm install && pnpm build",
"test:assert": "pnpm test:prod && pnpm test:dev"
},
"dependencies": {
"@sentry/nextjs": "latest || *",
"@types/node": "^18.19.1",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.9",
"next": "15.5.4",
"next-intl": "^4.3.12",
"react": "latest",
"react-dom": "latest",
"typescript": "~5.0.0"
},
"devDependencies": {
"@playwright/test": "~1.53.2",
"@sentry-internal/test-utils": "link:../../../test-utils"
},
"volta": {
"extends": "../../package.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getPlaywrightConfig } from '@sentry-internal/test-utils';
const testEnv = process.env.TEST_ENV;

if (!testEnv) {
throw new Error('No test env defined');
}

const getStartCommand = () => {
if (testEnv === 'development') {
return 'pnpm next dev -p 3030 2>&1 | tee .tmp_dev_server_logs';
}

if (testEnv === 'production') {
return 'pnpm next start -p 3030';
}

throw new Error(`Unknown test env: ${testEnv}`);
};

const config = getPlaywrightConfig({
startCommand: getStartCommand(),
port: 3030,
});

export default config;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as Sentry from '@sentry/nextjs';

Sentry.init({
environment: 'qa',
dsn: process.env.SENTRY_DSN,
tunnel: `http://localhost:3031/`,
tracesSampleRate: 1.0,
sendDefaultPii: true,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Sentry from '@sentry/nextjs';

Sentry.init({
environment: 'qa',
dsn: process.env.NEXT_PUBLIC_E2E_TEST_DSN,
tunnel: `http://localhost:3031/`,
tracesSampleRate: 1.0,
sendDefaultPii: true,
transportOptions: {
bufferSize: 1000,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as fs from 'fs';
import * as path from 'path';
import { startEventProxyServer } from '@sentry-internal/test-utils';

const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json')));

startEventProxyServer({
port: 3031,
proxyServerName: 'nextjs-15-intl',
envelopeDumpPath: path.join(
process.cwd(),
`event-dumps/nextjs-15-intl-v${packageJson.dependencies.next}-${process.env.TEST_ENV}.dump`,
),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { expect, test } from '@playwright/test';
import { waitForTransaction } from '@sentry-internal/test-utils';

test('should create consistent parameterized transaction for default locale without prefix', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-15-intl', async transactionEvent => {
return transactionEvent.transaction === '/:locale/i18n-test' && transactionEvent.contexts?.trace?.op === 'pageload';
});

await page.goto(`/i18n-test`);

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
transaction: '/:locale/i18n-test',
transaction_info: { source: 'route' },
contexts: {
trace: {
data: {
'sentry.source': 'route',
},
},
},
});
});

test('should create consistent parameterized transaction for non-default locale with prefix', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-15-intl', async transactionEvent => {
return transactionEvent.transaction === '/:locale/i18n-test' && transactionEvent.contexts?.trace?.op === 'pageload';
});

await page.goto(`/ar/i18n-test`);

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
transaction: '/:locale/i18n-test',
transaction_info: { source: 'route' },
contexts: {
trace: {
data: {
'sentry.source': 'route',
},
},
},
});
});

test('should parameterize locale root page correctly for default locale without prefix', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-15-intl', async transactionEvent => {
return transactionEvent.transaction === '/:locale' && transactionEvent.contexts?.trace?.op === 'pageload';
});

await page.goto(`/`);

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
transaction: '/:locale',
transaction_info: { source: 'route' },
contexts: {
trace: {
data: {
'sentry.source': 'route',
},
},
},
});
});

test('should parameterize locale root page correctly for non-default locale with prefix', async ({ page }) => {
const transactionPromise = waitForTransaction('nextjs-15-intl', async transactionEvent => {
return transactionEvent.transaction === '/:locale' && transactionEvent.contexts?.trace?.op === 'pageload';
});

await page.goto(`/fr`);

const transaction = await transactionPromise;

expect(transaction).toMatchObject({
transaction: '/:locale',
transaction_info: { source: 'route' },
contexts: {
trace: {
data: {
'sentry.source': 'route',
},
},
},
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"]
},
"target": "ES2017"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export default async function I18nTestPage({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
return (
<div>
<h1>I18n Test Page</h1>
<p>Current locale: {locale || 'default'}</p>
<p>This page tests i18n route parameterization</p>
</div>
);
}
Loading
Loading