Skip to content

Commit

Permalink
nits:
Browse files Browse the repository at this point in the history
 - A little bit less usage of callout in the docs (we already have so many)
 - Minor wording fixes in middleware docs
 - Fix mock behavior in middleware
 - Add tests for alternate links in middleware
  • Loading branch information
amannn committed Jul 18, 2023
1 parent 1bc6fe1 commit d90b1dc
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 45 deletions.
31 changes: 14 additions & 17 deletions docs/pages/docs/routing/middleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,13 @@ import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// ... other config

localePrefix: 'always' // Defaults to 'as-needed'
localePrefix: 'always'
});
```

In this case, requests without a prefix will be redirected accordingly (e.g. `/about` to `/en/about`).

<Callout>
Note that this will affect both prefix-based as well as domain-based routing.
</Callout>
Note that this will affect both prefix-based as well as domain-based routing.

### Never use a locale prefix

Expand All @@ -158,19 +156,17 @@ import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// ... other config

localePrefix: 'never' // Defaults to 'as-needed'
localePrefix: 'never'
});
```

<Callout>
In this case all requests for all locales will be rewritten to have the locale
prefixed internally. You still need to place all your pages inside a
`[locale]` folder.
</Callout>
In this case all requests for all locales will be rewritten to have the locale
prefixed internally. You still need to place all your pages inside a
`[locale]` folder for the routes to be able to receive the `locale` param.

<Callout>
Alternate links will be disabled, regardless of the [`alternateLinks`
configuration setting](#disable-alternate-links).
Note that [alternate links](#disable-alternate-links) are disabled in this
mode since there are no distinct URLs per language.
</Callout>

### Disable automatic locale detection
Expand All @@ -183,11 +179,11 @@ import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// ... other config

localeDetection: false // Defaults to `true`
localeDetection: false
});
```

Note that in this case other detection mechanisms will remain in place regardless (e.g. based on a locale prefix in the pathname or a matched domain).
In this case, only the locale prefix and a potentially [matching domain](#domain-based-routing) are used to determine the locale.

### Disable alternate links

Expand All @@ -209,7 +205,7 @@ export default createMiddleware({

If you want to localize the pathnames of your app, you can accomplish this by using appropriate [rewrites](https://nextjs.org/docs/api-reference/next.config.js/rewrites).

```js filename="next.config.js" {8-9}
```js filename="next.config.js" {7-8}
const withNextIntl = require('next-intl/plugin')();

module.exports = withNextIntl({
Expand Down Expand Up @@ -349,8 +345,9 @@ export default function RootPage() {

<Callout type="warning">
Note that this is currently limited to the usage of `next-intl` in [Client
Components](/docs/getting-started/app-router-client-components) (no [Server
Components](/docs/getting-started/app-router-server-components)).
Components](/docs/getting-started/app-router-client-components) ([Server
Components](/docs/getting-started/app-router-server-components) are not
supported).
</Callout>

You can explore a working demo by building [the Next.js 13 example](/examples/app-router) after enabling the static export:
Expand Down
79 changes: 51 additions & 28 deletions packages/next-intl/test/middleware/middleware.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@ import {it, describe, vi, beforeEach, expect, Mock} from 'vitest';
import createIntlMiddleware from '../../src/middleware';
import {COOKIE_LOCALE_NAME} from '../../src/shared/constants';

type MockResponse = NextResponse & {
args: Array<any>;
};

vi.mock('next/server', () => {
const response = {
headers: new Headers(),
cookies: new RequestCookies(new Headers())
};
type MiddlewareResponseInit = Parameters<(typeof NextResponse)['next']>[0];

function createResponse(init: MiddlewareResponseInit) {
const response = new Response(null, init);
(response as any).cookies = new RequestCookies(
init?.request?.headers || new Headers()
);
return response as NextResponse;
}
return {
NextResponse: {
next: vi.fn(() => response),
rewrite: vi.fn(() => response),
redirect: vi.fn(() => response)
next: vi.fn((init: ResponseInit) => createResponse(init)),
rewrite: vi.fn((_destination: string, init: ResponseInit) =>
createResponse(init)
),
redirect: vi.fn((_url: string, init: ResponseInit) =>
createResponse(init)
)
}
};
});
Expand Down Expand Up @@ -53,29 +58,19 @@ function createMockRequest(
} as NextRequest;
}

function createMockMiddleware(
...args: Parameters<typeof createIntlMiddleware>
) {
return createIntlMiddleware(...args) as (
request: NextRequest
) => MockResponse;
}

const MockedNextResponse = NextResponse as unknown as {
next: Mock<Parameters<(typeof NextResponse)['next']>>;
rewrite: Mock<Parameters<(typeof NextResponse)['rewrite']>>;
redirect: Mock<Parameters<(typeof NextResponse)['redirect']>>;
};

beforeEach(() => {
MockedNextResponse.next.mockClear();
MockedNextResponse.rewrite.mockClear();
MockedNextResponse.redirect.mockClear();
vi.clearAllMocks();
});

describe('prefix-based routing', () => {
describe('localePrefix: as-needed', () => {
const middleware = createMockMiddleware({
const middleware = createIntlMiddleware({
defaultLocale: 'en',
locales: ['en', 'de']
});
Expand Down Expand Up @@ -213,10 +208,21 @@ describe('prefix-based routing', () => {
)
).toBe('test');
});

it('returns alternate links', () => {
const response = middleware(createMockRequest('/'));
expect(response.headers.get('link')).toBe(
[
'<http://localhost:3000/>; rel="alternate"; hreflang="en"',
'<http://localhost:3000/de>; rel="alternate"; hreflang="de"',
'<http://localhost:3000/>; rel="alternate"; hreflang="x-default"'
].join(', ')
);
});
});

describe('localePrefix: as-needed, localeDetection: false', () => {
const middleware = createMockMiddleware({
const middleware = createIntlMiddleware({
defaultLocale: 'en',
locales: ['en', 'de'],
localePrefix: 'as-needed',
Expand Down Expand Up @@ -244,7 +250,7 @@ describe('prefix-based routing', () => {
});

describe('localePrefix: always', () => {
const middleware = createMockMiddleware({
const middleware = createIntlMiddleware({
defaultLocale: 'en',
locales: ['en', 'de'],
localePrefix: 'always'
Expand Down Expand Up @@ -303,7 +309,7 @@ describe('prefix-based routing', () => {
});

describe('localePrefix: never', () => {
const middleware = createMockMiddleware({
const middleware = createIntlMiddleware({
defaultLocale: 'en',
locales: ['en', 'de'],
localePrefix: 'never'
Expand Down Expand Up @@ -459,12 +465,17 @@ describe('prefix-based routing', () => {
)
).toBe('test');
});

it('disables the alternate links', () => {
const response = middleware(createMockRequest('/'));
expect(response.headers.get('link')).toBe(null);
});
});
});

describe('domain-based routing', () => {
describe('localePrefix: as-needed', () => {
const middleware = createMockMiddleware({
const middleware = createIntlMiddleware({
defaultLocale: 'en',
locales: ['en', 'fr'],
domains: [
Expand Down Expand Up @@ -526,6 +537,18 @@ describe('domain-based routing', () => {
expect(MockedNextResponse.next).toHaveBeenCalled();
});

it('returns alternate links', () => {
const response = middleware(createMockRequest('/'));
expect(response.headers.get('link')).toBe(
[
'<http://en.example.com/>; rel="alternate"; hreflang="en"',
'<http://ca.example.com/>; rel="alternate"; hreflang="en"',
'<http://ca.example.com/fr>; rel="alternate"; hreflang="fr"',
'<http://fr.example.com/>; rel="alternate"; hreflang="fr"'
].join(', ')
);
});

describe('unknown hosts', () => {
it('serves requests for unknown hosts at the root', () => {
middleware(createMockRequest('/', 'en', 'http://localhost'));
Expand Down Expand Up @@ -671,7 +694,7 @@ describe('domain-based routing', () => {
});

describe("localePrefix: 'always'", () => {
const middleware = createMockMiddleware({
const middleware = createIntlMiddleware({
defaultLocale: 'en',
locales: ['en', 'fr'],
localePrefix: 'always',
Expand Down

0 comments on commit d90b1dc

Please sign in to comment.