Skip to content

Commit

Permalink
feat: Add support for using next-intl in the app folder with Next…
Browse files Browse the repository at this point in the history
  • Loading branch information
amannn committed Dec 9, 2022
1 parent 81e5050 commit 18c94d6
Show file tree
Hide file tree
Showing 57 changed files with 3,344 additions and 248 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: 14.x
node-version: 16.x
# The Remix example unfortunately doesn't seem to support yarn workspaces
- uses: bahmutov/npm-install@v1
- run: yarn playwright install --with-deps

# The order matters here
- run: yarn workspace use-intl run build
- run: yarn workspace next-intl run build
- run: yarn workspaces run lint
- run: yarn workspaces run test --ci --maxWorkers=2
- run: yarn workspace example run build
- run: yarn workspace example-advanced run build
- run: yarn workspace example-next-13 run build

- run: yarn workspaces run lint
- run: yarn workspaces run test
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 14.x
node-version: 16.x
registry-url: 'https://registry.npmjs.org'
- uses: bahmutov/npm-install@v1
# Seems like after squash & merge the author is unknown to lerna
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
.DS_Store
node_modules
dist
.vscode
1 change: 1 addition & 0 deletions lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"packages/next-intl",
"packages/example",
"packages/example-advanced",
"packages/example-next-13",
"packages/website"
],
"useWorkspaces": true,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"packages/use-intl",
"packages/next-intl",
"packages/example",
"packages/example-next-13",
"packages/example-advanced",
"packages/website"
],
Expand Down
4 changes: 1 addition & 3 deletions packages/example-advanced/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ module.exports = {
node: true
},
rules: {
'react/react-in-jsx-scope': 'off',
'jsx-a11y/anchor-is-valid': 'off',
'react/display-name': 'off'
'react/react-in-jsx-scope': 'off'
}
};
12 changes: 12 additions & 0 deletions packages/example-next-13/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
require('eslint-config-molindo/setupPlugins');

module.exports = {
extends: [
'molindo/typescript',
'molindo/react',
'plugin:@next/next/recommended'
],
rules: {
'react/react-in-jsx-scope': 'off'
}
};
7 changes: 7 additions & 0 deletions packages/example-next-13/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/node_modules
/.next/
.DS_Store
tsconfig.tsbuildinfo
/test-results/
/playwright-report/
/playwright/.cache/
4 changes: 4 additions & 0 deletions packages/example-next-13/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"typescript.tsdk": "../../node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true
}
3 changes: 3 additions & 0 deletions packages/example-next-13/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# example-next-13

An example that showcases usage of `next-intl` in the `app` folder of Next.js 13.
12 changes: 12 additions & 0 deletions packages/example-next-13/messages/de.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Head": {
"title": "next-intl Beispiel"
},
"Index": {
"title": "Start",
"description": "Das ist die Startseite."
},
"LocaleSwitcher": {
"switchLocale": "Zu {locale, select, de {Deutsch} en {Englisch} other {Unbekannt}} wechseln"
}
}
12 changes: 12 additions & 0 deletions packages/example-next-13/messages/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"Head": {
"title": "next-intl example"
},
"Index": {
"title": "Home",
"description": "This is the home page."
},
"LocaleSwitcher": {
"switchLocale": "Switch to {locale, select, de {German} en {English} other {Unknown}}"
}
}
5 changes: 5 additions & 0 deletions packages/example-next-13/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
3 changes: 3 additions & 0 deletions packages/example-next-13/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
experimental: {appDir: true}
};
31 changes: 31 additions & 0 deletions packages/example-next-13/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "example-next-13",
"version": "2.9.1",
"private": true,
"scripts": {
"dev": "next dev",
"lint": "eslint src && tsc",
"test": "playwright test",
"build": "next build",
"start": "next start"
},
"dependencies": {
"accept-language-parser": "1.5.0",
"date-fns": "^2.16.1",
"lodash": "^4.17.21",
"next": "^13.0.5",
"next-intl": "^2.9.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^4.6.3"
},
"devDependencies": {
"@playwright/test": "1.28.1",
"@types/lodash": "^4.14.176",
"@types/node": "^17.0.23",
"@types/react": "^18.0.23",
"eslint": "^8.12.0",
"eslint-config-molindo": "^6.0.0",
"eslint-config-next": "^13.0.0"
}
}
19 changes: 19 additions & 0 deletions packages/example-next-13/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* eslint-disable import/no-extraneous-dependencies */
import type {PlaywrightTestConfig} from '@playwright/test';
import {devices} from '@playwright/test';

const config: PlaywrightTestConfig = {
testDir: './tests',
projects: [
{
name: 'chromium',
use: devices['Desktop Chrome']
}
],
webServer: {
command: 'yarn start',
port: 3000
}
};

export default config;
Binary file added packages/example-next-13/public/favicon.ico
Binary file not shown.
29 changes: 29 additions & 0 deletions packages/example-next-13/src/app/[locale]/head.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {createTranslator} from 'next-intl';
import {notFound} from 'next/navigation';

type Props = {
params: {
locale: string;
};
};

export default async function Head({params: {locale}}: Props) {
let messages;
try {
messages = (await import(`../../../messages/${locale}.json`)).default;
} catch (error) {
notFound();
}

// Currently you can use the core (non-React) APIs when you
// have to use next-intl in a Server Component like `Head`.
// In the future you'll be able to use the React APIs here as
// well (see https://next-intl-docs.vercel.app/docs/next-13).
const t = createTranslator({locale, messages});

return (
<>
<title>{t('Head.title')}</title>
</>
);
}
26 changes: 26 additions & 0 deletions packages/example-next-13/src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {NextIntlClientProvider} from 'next-intl/client';
import {notFound} from 'next/navigation';
import {ReactNode} from 'react';

type Props = {
children: ReactNode;
params: {locale: string};
};

export default async function LocaleLayout({
children,
params: {locale}
}: Props) {
let messages;
try {
messages = (await import(`../../../messages/${locale}.json`)).default;
} catch (error) {
notFound();
}

return (
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
);
}
16 changes: 16 additions & 0 deletions packages/example-next-13/src/app/[locale]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

import {useTranslations} from 'next-intl';
import LocaleSwitcher from '../../components/LocaleSwitcher';
import PageLayout from '../../components/PageLayout';

export default function Index() {
const t = useTranslations('Index');

return (
<PageLayout title={t('title')}>
<p>{t('description')}</p>
<LocaleSwitcher />
</PageLayout>
);
}
20 changes: 20 additions & 0 deletions packages/example-next-13/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {ReactNode} from 'react';

type Props = {
children: ReactNode;
};

export default function RootLayout({children}: Props) {
// TODO: Here's a problem: When there's no cookie yet, we can't use the locale
// from the URL. Something like `require('next/navigation').usePathname()`
// would be great, but that uses non-server context currently, so not an
// option in server components. `params` is also not an option, since they are
// only available from matched segments downwards.
const lang = undefined;

return (
<html lang={lang}>
<body>{children}</body>
</html>
);
}
14 changes: 14 additions & 0 deletions packages/example-next-13/src/components/LocaleSwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {useLocale, useTranslations} from 'next-intl';
import Link from 'next/link';

export default function LocaleSwitcher() {
const t = useTranslations('LocaleSwitcher');
const locale = useLocale();
const otherLocale = locale === 'en' ? 'de' : 'en';

return (
<Link href={'/' + otherLocale} prefetch={false}>
{t('switchLocale', {locale: otherLocale})}
</Link>
);
}
25 changes: 25 additions & 0 deletions packages/example-next-13/src/components/PageLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import {ReactNode} from 'react';

type Props = {
children?: ReactNode;
title: string;
};

export default function PageLayout({children, title}: Props) {
return (
<>
<div
style={{
padding: 24,
fontFamily: 'system-ui, sans-serif',
lineHeight: 1.5
}}
>
<div style={{maxWidth: 510}}>
<h1>{title}</h1>
{children}
</div>
</div>
</>
);
}
6 changes: 6 additions & 0 deletions packages/example-next-13/src/middleware.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {createIntlMiddleware} from 'next-intl/server';

export default createIntlMiddleware({
locales: ['en', 'de'],
defaultLocale: 'en'
});
29 changes: 29 additions & 0 deletions packages/example-next-13/tests/main.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {test as it, expect} from '@playwright/test';

it('handles i18n routing', async ({page}) => {
await page.goto('/');
await expect(page).toHaveURL(/\/en/);

// A cookie remembers the last locale
await page.goto('/de');
await page.goto('/');
await expect(page).toHaveURL(/\/de/);

await page.goto('/unknown');
});

it('can be used in the head', async ({page}) => {
await page.goto('/en');
await expect(page).toHaveTitle('next-intl example');

await page.goto('/de');
await expect(page).toHaveTitle('next-intl Beispiel');
});

it('can be used to localize the page', async ({page}) => {
await page.goto('/en');
page.locator('text=This is the home page.');

await page.goto('/de');
page.locator('text=Das ist die Startseite.');
});
35 changes: 35 additions & 0 deletions packages/example-next-13/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"extends": "eslint-config-molindo/tsconfig.json",
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}

1 comment on commit 18c94d6

@vercel
Copy link

@vercel vercel bot commented on 18c94d6 Dec 9, 2022

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

next-intl – ./

next-intl-docs.vercel.app
next-intl-amann.vercel.app
next-intl-git-main-amann.vercel.app

Please sign in to comment.