Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: createNavigation #1316

Draft
wants to merge 38 commits into
base: canary
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7df9378
wip
amannn Sep 3, 2024
4898793
Fix some types
amannn Sep 3, 2024
faedffa
set up size limits
amannn Sep 3, 2024
978c560
keep legacy logic to fix test
amannn Sep 3, 2024
73d29ab
Fix redirect edge case
amannn Sep 3, 2024
243c41d
More tests, fix for `as-necessary`
amannn Sep 4, 2024
b3a81a2
got that initial render for link working 😏
amannn Sep 4, 2024
00fb58f
Some cleanup
amannn Sep 4, 2024
690fe46
Add another test
amannn Sep 5, 2024
4660545
Mandatory locale for getPathname, don't allow to pass a locale to red…
amannn Sep 5, 2024
5a6f7a1
initial react-client implementation
amannn Sep 5, 2024
c800f84
usePathname
amannn Sep 5, 2024
177528c
test usePathname with custom prefix
amannn Sep 5, 2024
43a0b12
strictly typed return type for usePathname
amannn Sep 5, 2024
4e6f19d
more tests for link
amannn Sep 5, 2024
5f01ba4
more tests for link
amannn Sep 5, 2024
9775157
Merge branch 'canary' into feat/create-navigation
amannn Sep 5, 2024
45bc9fc
Basic useRouter implementation
amannn Sep 6, 2024
d9f388a
docs: Add robots.txt (#1338)
amannn Sep 16, 2024
172e535
docs: Reorder content in expandable section for tags with attributes
amannn Sep 17, 2024
72c1731
fix: Handle overlapping custom locale prefixes correctly (#1343)
amannn Sep 17, 2024
ea69a82
v3.19.2
amannn Sep 17, 2024
7958659
fix: Handle overlapping locale prefixes correctly pt. 2 (#1344)
amannn Sep 17, 2024
aa60dde
v3.19.3
amannn Sep 17, 2024
129eff8
Merge remote-tracking branch 'origin/main' into feat/create-navigation
amannn Sep 17, 2024
4aa524d
Some simplification
amannn Sep 17, 2024
6ad0167
Few more tests
amannn Sep 17, 2024
a4c6975
migrate app router playground, add base path test
amannn Sep 17, 2024
6a8c958
comment
amannn Sep 17, 2024
52aaeeb
Migrate `getPathname` in playground
amannn Sep 17, 2024
b801833
no jsdoc
amannn Sep 17, 2024
99a144a
Fix invocation
amannn Sep 17, 2024
92f181d
fix test
amannn Sep 18, 2024
ee7e2eb
refactor playwright tests to use cases
amannn Sep 18, 2024
ba7acf4
first bit of domains support
amannn Sep 20, 2024
604d56c
domain progress with hydrating link
amannn Sep 20, 2024
4301e9b
fix lint
amannn Sep 20, 2024
49a75ef
fix test script
amannn Sep 20, 2024
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.

## 3.19.3 (2024-09-17)

### Bug Fixes

* Handle overlapping locale prefixes correctly pt. 2 ([#1344](https://github.com/amannn/next-intl/issues/1344)) ([7958659](https://github.com/amannn/next-intl/commit/7958659f858bb5df19203ec3c1a8701e029ed2c4)) – by @amannn

## 3.19.2 (2024-09-17)

### Bug Fixes

* Handle overlapping custom locale prefixes correctly ([#1343](https://github.com/amannn/next-intl/issues/1343)) ([72c1731](https://github.com/amannn/next-intl/commit/72c1731892db6e7d0470cefcea2b1f22a5f37ce2)), closes [#1329](https://github.com/amannn/next-intl/issues/1329) – by @amannn

## 3.19.1 (2024-09-05)

### Bug Fixes
Expand Down
19 changes: 19 additions & 0 deletions docs/app/robots.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {MetadataRoute} from 'next';

export default function robots(): MetadataRoute.Robots {
if (process.env.VERCEL_ENV !== 'production') {
return {
rules: {
userAgent: '*',
disallow: '/'
}
};
} else {
return {
rules: {
userAgent: '*',
allow: '/'
}
};
}
}
4 changes: 2 additions & 2 deletions docs/pages/docs/usage/messages.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,6 @@ t.rich('message', {
});
```

For the use case of localizing pathnames, consider using [`createLocalizedPathnamesNavigation`](/docs/routing/navigation).

In case you have attribute values that are required to be configured as part of your messages, you can retrieve them from a separate message and pass them to another one as an attribute:

```json filename="en.json"
Expand All @@ -410,6 +408,8 @@ t.rich('message', {
});
```

For the use case of localizing pathnames, consider using [`createLocalizedPathnamesNavigation`](/docs/routing/navigation).

</Details>

## HTML markup
Expand Down
3 changes: 2 additions & 1 deletion examples/example-app-router-playground/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const withMdx = mdxPlugin();

export default withMdx(
withNextIntl({
trailingSlash: process.env.TRAILING_SLASH === 'true',
trailingSlash: process.env.USE_CASE === 'trailing-slash',
basePath: process.env.USE_CASE === 'base-path' ? '/base/path' : undefined,
experimental: {
staleTimes: {
// Next.js 14.2 broke `locale-prefix-never.spec.ts`.
Expand Down
6 changes: 2 additions & 4 deletions examples/example-app-router-playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
"scripts": {
"dev": "next dev",
"lint": "eslint src && tsc",
"test": "pnpm run test:playwright:main && pnpm run test:playwright:locale-prefix-never && pnpm run test:playwright:trailing-slash && pnpm run test:jest",
"test:playwright:main": "TEST_MATCH=main.spec.ts playwright test",
"test:playwright:locale-prefix-never": "NEXT_PUBLIC_LOCALE_PREFIX=never pnpm build && TEST_MATCH=locale-prefix-never.spec.ts playwright test",
"test:playwright:trailing-slash": "TRAILING_SLASH=true pnpm build && TEST_MATCH=trailing-slash.spec.ts playwright test",
"test": "pnpm test:jest && node runPlaywright.mjs",
"test:playwright": "node runPlaywright.mjs",
"test:jest": "jest",
"build": "next build",
"start": "next start",
Expand Down
11 changes: 11 additions & 0 deletions examples/example-app-router-playground/runPlaywright.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {execSync} from 'child_process';

const useCases = ['main', 'locale-prefix-never', 'trailing-slash', 'base-path'];

for (const useCase of useCases) {
// eslint-disable-next-line no-console
console.log(`Running tests for use case: ${useCase}`);

const command = `USE_CASE=${useCase} pnpm build && USE_CASE=${useCase} TEST_MATCH=${useCase}.spec.ts playwright test`;
execSync(command, {stdio: 'inherit'});
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Metadata} from 'next';
import {useTranslations} from 'next-intl';
import {getPathname, routing, Locale} from '@/i18n/routing';
import {getPathname, Locale} from '@/i18n/routing';

type Props = {
params: {
Expand All @@ -10,19 +10,17 @@ type Props = {
};

export async function generateMetadata({params}: Props): Promise<Metadata> {
let canonical = getPathname({
href: {
pathname: '/news/[articleId]',
params: {articleId: params.articleId}
},
locale: params.locale
});

if (params.locale !== routing.defaultLocale) {
canonical = '/' + params.locale + canonical;
}

return {alternates: {canonical}};
return {
alternates: {
canonical: getPathname({
href: {
pathname: '/news/[articleId]',
params: {articleId: params.articleId}
},
locale: params.locale
})
}
};
}

export default function NewsArticle({params}: Props) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
'use client';

import {ComponentProps} from 'react';
import {Link, Pathnames} from '@/i18n/routing';
import {Link} from '@/i18n/routing';

export default function NavigationLink<Pathname extends Pathnames>(
props: ComponentProps<typeof Link<Pathname>>
) {
export default function NavigationLink(props: ComponentProps<typeof Link>) {
return <Link {...props} />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

import {useSelectedLayoutSegment} from 'next/navigation';
import {ComponentProps} from 'react';
import {Link, Pathnames} from '@/i18n/routing';
import {Link} from '@/i18n/routing';

export default function NavigationLink<Pathname extends Pathnames>({
export default function NavigationLink({
href,
...rest
}: ComponentProps<typeof Link<Pathname>>) {
}: ComponentProps<typeof Link>) {
const selectedLayoutSegment = useSelectedLayoutSegment();
const pathname = selectedLayoutSegment ? `/${selectedLayoutSegment}` : '/';
const isActive = pathname === href;
Expand Down
6 changes: 3 additions & 3 deletions examples/example-app-router-playground/src/i18n/routing.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {createLocalizedPathnamesNavigation} from 'next-intl/navigation';
import {createNavigation} from 'next-intl/navigation';
import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting({
locales: ['en', 'de', 'es', 'ja'],
defaultLocale: 'en',
localePrefix:
process.env.NEXT_PUBLIC_LOCALE_PREFIX === 'never'
process.env.USE_CASE === 'locale-prefix-never'
? 'never'
: {
mode: 'as-needed',
Expand Down Expand Up @@ -44,4 +44,4 @@ export type Pathnames = keyof typeof routing.pathnames;
export type Locale = (typeof routing.locales)[number];

export const {Link, getPathname, redirect, usePathname, useRouter} =
createLocalizedPathnamesNavigation(routing);
createNavigation(routing);
9 changes: 7 additions & 2 deletions examples/example-app-router-playground/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import {routing} from './i18n/routing';
export default createMiddleware(routing);

export const config = {
// Skip all paths that should not be internationalized
matcher: ['/((?!_next|.*\\..*).*)']
matcher: [
// Skip all paths that should not be internationalized
'/((?!_next|.*\\..*).*)',

// Necessary for base path to work
'/'
]
};
22 changes: 22 additions & 0 deletions examples/example-app-router-playground/tests/base-path.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {test as it, expect} from '@playwright/test';
import {assertLocaleCookieValue} from './utils';

it('can use the router', async ({page}) => {
await page.goto('/base/path');
await assertLocaleCookieValue(page, 'en', {path: '/base/path'});

await page.getByRole('button', {name: 'Go to nested page'}).click();
await expect(page).toHaveURL('/base/path/nested');
await page.getByRole('link', {name: 'Home'}).click();
await page.getByRole('link', {name: 'Switch to German'}).click();

await expect(page).toHaveURL('/base/path/de');
assertLocaleCookieValue(page, 'de', {path: '/base/path'});
await page.getByRole('button', {name: 'Go to nested page'}).click();
await expect(page).toHaveURL('/base/path/de/verschachtelt');
await page.getByRole('link', {name: 'Start'}).click();
await page.getByRole('link', {name: 'Zu Englisch wechseln'}).click();

await expect(page).toHaveURL('/base/path');
assertLocaleCookieValue(page, 'en', {path: '/base/path'});
});
13 changes: 0 additions & 13 deletions examples/example-app-router-playground/tests/getAlternateLinks.ts

This file was deleted.

19 changes: 2 additions & 17 deletions examples/example-app-router-playground/tests/main.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
import {test as it, expect, Page, BrowserContext} from '@playwright/test';
import getAlternateLinks from './getAlternateLinks';
import {test as it, expect, BrowserContext} from '@playwright/test';
import {getAlternateLinks, assertLocaleCookieValue} from './utils';

const describe = it.describe;

async function assertLocaleCookieValue(
page: Page,
value: string,
otherProps?: Record<string, unknown>
) {
const cookie = (await page.context().cookies()).find(
(cur) => cur.name === 'NEXT_LOCALE'
);
expect(cookie).toMatchObject({
name: 'NEXT_LOCALE',
value,
...otherProps
});
}

function getPageLoadTracker(context: BrowserContext) {
const state = {numPageLoads: 0};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {test as it, expect} from '@playwright/test';
import getAlternateLinks from './getAlternateLinks';
import {getAlternateLinks} from './utils';

it('redirects to a locale prefix correctly', async ({request}) => {
const response = await request.get('/', {
Expand Down
28 changes: 28 additions & 0 deletions examples/example-app-router-playground/tests/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {APIResponse, expect, Page} from '@playwright/test';

export async function getAlternateLinks(response: APIResponse) {
return (
response
.headers()
.link.split(', ')
// On CI, Playwright uses a different host somehow
.map((cur) => cur.replace(/0\.0\.0\.0/g, 'localhost'))
// Normalize ports
.map((cur) => cur.replace(/localhost:\d{4}/g, 'localhost:3000'))
);
}

export async function assertLocaleCookieValue(
page: Page,
value: string,
otherProps?: Record<string, unknown>
) {
const cookie = (await page.context().cookies()).find(
(cur) => cur.name === 'NEXT_LOCALE'
);
expect(cookie).toMatchObject({
name: 'NEXT_LOCALE',
value,
...otherProps
});
}
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "node_modules/@lerna-lite/cli/schemas/lerna-schema.json",
"version": "3.19.1",
"version": "3.19.3",
"packages": [
"packages/*"
],
Expand Down
38 changes: 37 additions & 1 deletion packages/next-intl/.size-limit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,79 @@ import type {SizeLimitConfig} from 'size-limit';

const config: SizeLimitConfig = [
{
name: 'import * from \'next-intl\' (react-client)',
path: 'dist/production/index.react-client.js',
limit: '14.095 KB'
},
{
name: 'import * from \'next-intl\' (react-server)',
path: 'dist/production/index.react-server.js',
limit: '14.665 KB'
},
{
name: 'import {createSharedPathnamesNavigation} from \'next-intl/navigation\' (react-client)',
path: 'dist/production/navigation.react-client.js',
import: '{createSharedPathnamesNavigation}',
limit: '3.155 KB'
},
{
name: 'import {createLocalizedPathnamesNavigation} from \'next-intl/navigation\' (react-client)',
path: 'dist/production/navigation.react-client.js',
import: '{createLocalizedPathnamesNavigation}',
limit: '3.155 KB'
},
{
name: 'import {createNavigation} from \'next-intl/navigation\' (react-client)',
path: 'dist/production/navigation.react-client.js',
import: '{createNavigation}',
limit: '3.155 KB'
},
{
name: 'import {createSharedPathnamesNavigation} from \'next-intl/navigation\' (react-server)',
path: 'dist/production/navigation.react-server.js',
import: '{createSharedPathnamesNavigation}',
limit: '15.845 KB'
},
{
name: 'import {createLocalizedPathnamesNavigation} from \'next-intl/navigation\' (react-server)',
path: 'dist/production/navigation.react-server.js',
import: '{createLocalizedPathnamesNavigation}',
limit: '15.845 KB'
},
{
name: 'import {createNavigation} from \'next-intl/navigation\' (react-server)',
path: 'dist/production/navigation.react-server.js',
import: '{createNavigation}',
limit: '15.91 KB'
},
{
name: 'import * from \'next-intl/server\' (react-client)',
path: 'dist/production/server.react-client.js',
limit: '1 KB'
},
{
name: 'import * from \'next-intl/server\' (react-server)',
path: 'dist/production/server.react-server.js',
limit: '13.865 KB'
},
{
name: 'import createMiddleware from \'next-intl/middleware\'',
path: 'dist/production/middleware.js',
limit: '9.595 KB'
limit: '9.61 KB'
},
{
name: 'import * from \'next-intl/routing\'',
path: 'dist/production/routing.js',
limit: '1 KB'
},
{
name: 'import * from \'next-intl\' (react-client, ESM)',
path: 'dist/esm/index.react-client.js',
import: '*',
limit: '14.265 kB'
},
{
name: 'import {NextIntlProvider} from \'next-intl\' (react-client, ESM)',
path: 'dist/esm/index.react-client.js',
import: '{NextIntlClientProvider}',
limit: '1.425 kB'
Expand Down
Loading
Loading