This is my first tutorial, the intention of this one is to make life easier and save time for those who need to implement these technologies in the same project, it took me a while to implement it and I want to share it with whoever may need it.
I hope you find it useful...
1. We create a new project as indicated in the documentation.
I'm going to do it with Typescript but with Javascript the steps are the same.
npx create-next-app@latest --typescript
You type the name of your project and wait for the dependencies to be installed, then you run VSCode or your code editor in the project folder.
Now in the terminal type.
npm run dev
This will start the development server on port 3000.
2. Install and configure next-i18next
As indicated in the project repository we run
yarn add next-i18next
# Si usas NPM ejecuta:
npm i --save next-i18next
Now we create two folders with json files to test the translations:
└─ public
└ locales
├─en
| └─ common.json
└─es
└─ common.json
Then we create a next-i18next.config.js
file in the root of the project with the following content:
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es'],
},
};
Then in the next.config.js
file we import the configuration:
/** @type {import('next').NextConfig} */
const { i18n } = require("./next-i18next.config");
const nextConfig = {
reactStrictMode: true,
i18n,
};
module.exports = nextConfig;
Finally we edit the pages/_app.tsx
file to run the application with translations, the change is made in the last line.
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { appWithTranslation } from 'next-i18next';
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default appWithTranslation(MyApp);
3. Add and test translations.
In the files public/locales/en/common.json
and public/locales/en/common.json
we add the translations in this way:
public/locales/en/common.json
{
"welcome": "Welcome to the world of Next.js!",
"content": "This is the blog index page"
}
public/locales/en/common.json
{
"welcome": "Bienvenido al mundo de Next.js!",
"content": "Esta es la página de inicio del blog"
}
Now, this step is important, for the translations to work correctly, they must be called from the server in all the components at page level for it to perform the "hydration" correctly, in this case we edit our pages/index.tsx
file to pass the translations, so we can call each translation individually we use
const { t } = useTranslation('common');
The file in this case looks like this:
import type { NextPage, NextPageContext } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';
const Home: NextPage = () => {
const { t } = useTranslation('common');
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>{t('welcome')}</h1>
<p className={styles.description}>{t('content')}</p>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
);
};
export default Home;
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';
export async function getStaticProps({ locale }: NextPageContext) {
return {
props: {
...(await serverSideTranslations(locale || 'en', ['common'])),
},
};
}
The result is the translated page with the language detected in the system.
If we want to manually test the language change we have to change the configuration, I will also add another line that will serve to upload our project to Vercel, the file looks like this:
const path = require('path');
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'es'],
localeDetection: false,
localePath: path.resolve('./public/locales'), // for deployment on Vercel
},
};
We will also create a button to change the language manually, the pages/index.tsx
file would look like this:
import type { NextPage, NextPageContext } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useRouter } from 'next/router';
const Home: NextPage = () => {
const router = useRouter();
const { pathname, asPath, query } = router;
const { t } = useTranslation('common');
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>{t('welcome')}</h1>
{/* Language Change Button */}
<button
type="button"
onClick={() => {
router.push({ pathname, query }, asPath, {
locale: router.locale === 'es' ? 'en' : 'es',
});
}}
>
{router.locale === 'es' ? 'English' : 'Español'}
</button>
<p className={styles.description}>{t('content')}</p>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
);
};
export default Home;
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';
export async function getStaticProps({ locale }: NextPageContext) {
return {
props: {
...(await serverSideTranslations(locale || 'en', ['common'])),
},
};
}
In this way we already have our application working perfectly with two languages based on the route
4. We configure authentication with next-auth
First we install the package:
npm i --save next-auth
For testing purposes we will use credentials (email and password), also we will do a simple validation, doing it correctly depends on what you want in your project, if you comment I can do a tutorial explaining how to do the authentication with different providers and using a customizable home page. That said we continue...
We create a pages/api/auth/[...nextauth].ts
file.
With the following content:
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
export default NextAuth({
providers: [
CredentialsProvider({
name: 'credentials',
credentials: {
email: {
label: 'Email',
type: 'email',
placeholder: 'email@example.com',
},
password: {
label: 'Password',
type: 'password',
placeholder: '********',
},
},
async authorize(credentials) {
if (credentials && credentials.email && credentials.password) {
if (
credentials.email === 'test@test.com' &&
credentials.password === 'test'
) {
return {
email: credentials.email,
image: 'https://i.pravatar.cc/500',
name: 'Test User',
};
}
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token }) => {
return token;
},
session: ({ session, token }) => {
if (token) {
session.id = token.id;
}
return session;
},
},
secret: 'my-secret',
jwt: {
secret: 'my-secret',
maxAge: 60 * 60 * 24 * 30,
},
});
Then we add the pages/index.tsx
file to have an authentication or logout button in addition to the language change.
import type { NextPage, NextPageContext } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import styles from '../styles/Home.module.css';
import { useRouter } from 'next/router';
import { useSession, signIn, signOut } from 'next-auth/react';
const Home: NextPage = () => {
const router = useRouter();
const { pathname, asPath, query } = router;
const { t } = useTranslation('common');
const { data: session } = useSession();
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1 className={styles.title}>{t('welcome')}</h1>
{/* Language Change Button */}
<button
type="button"
onClick={() => {
router.push({ pathname, query }, asPath, {
locale: router.locale === 'es' ? 'en' : 'es',
});
}}
>
{router.locale === 'es' ? 'English' : 'Español'}
</button>
{/* Authentication Button */}
{session ? (
<>
<h4>
{t('welcome')} {JSON.stringify(session.user)}
</h4>
<button
type="button"
onClick={() => {
signOut();
}}
>
{t('signOut')}
</button>
</>
) : (
<button
type="button"
onClick={() => {
signIn();
}}
>
{t('signIn')}
</button>
)}
<p className={styles.description}>{t('content')}</p>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
);
};
export default Home;
import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import { useTranslation } from 'next-i18next';
export async function getStaticProps({ locale }: NextPageContext) {
return {
props: {
...(await serverSideTranslations(locale || 'en', ['common'])),
},
};
}
Now the most important thing, probably the reason why you come to this post, and personally what I found most difficult to solve, just make a small change in the pages/_app.tsx
file so that the SessionProvider does not collide with the Translation Provider:
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { appWithTranslation } from 'next-i18next';
import { SessionProvider } from 'next-auth/react';
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
const AppWithI18n = appWithTranslation(MyApp);
const AppWithAuth = (props: AppProps) => (
<SessionProvider session={props.pageProps.session}>
<AppWithI18n {...props} />
</SessionProvider>
);
export default AppWithAuth;
And that's it, now we can change the language of our page and have the authentication in the same NextJS application.
I hope I helped you, if you have any comments or questions leave them in the comments to help more developers.
Top comments (2)
Thank you Ángel. This is a great article. I was wondering if you are planning to write something similar for NextJs 13, using the experimental App folder.
Yes, I have a app example, In this week I'm going to post it