Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
adrai committed Dec 19, 2022
0 parents commit 319c21e
Show file tree
Hide file tree
Showing 49 changed files with 7,915 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "next/core-web-vitals",
"rules": {
"@next/next/no-img-element": 0
}
}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.next
node_modules
.vscode
.next
.DS_Store
30 changes: 30 additions & 0 deletions @types/i18next.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* If you want to enable locale keys typechecking and enhance IDE experience.
*
* Requires `resolveJsonModule:true` in your tsconfig.json.
*
* @link https://www.i18next.com/overview/typescript
*/
import 'i18next'

import type translation from '../app/i18n/locales/en/translation.json'
import type secondPage from '../app/i18n/locales/en/second-page.json'
import type footer from '../app/i18n/locales/en/footer.json'
import type clientPage from '../app/i18n/locales/en/client-page.json'
import type secondClientPage from '../app/i18n/locales/en/second-client-page.json'

interface I18nNamespaces {
translation: typeof translation
'second-page': typeof secondPage
footer: typeof footer
'client-page': typeof clientPage
'second-client-page': typeof secondClientPage
}

declare module 'i18next' {
interface CustomTypeOptions {
// returnNull: false
// defaultNS: 'translation'
resources: I18nNamespaces
}
}
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Next.js 13 app directory feature in combination with i18next

This example shows a basic way to use [i18next](https://www.i18next.com) (and [react-i18next](https://react.i18next.com)) in a [Next.js 13](https://beta.nextjs.org/) app with the new app directory features.
[next-i18next](https://next.i18next.com) is not needed anymore for this setup.

It shows i18next integration on some server side pages and some client side pages.

There is also an example middleware with language detection and persistence via cookie.

*This example has been created out of [this discussion](https://github.com/i18next/next-i18next/discussions/1993).*

## There's also a [blog post](https://locize.com/blog/next-13-app-dir-i18n) describing this with more detail information.

[![](https://locize.com/blog/next-13-app-dir-i18n/next-13-app-dir-i18n.jpg)](https://locize.com/blog/next-13-app-dir-i18n)
15 changes: 15 additions & 0 deletions app/[lng]/client-page/head.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useTranslation } from '../../i18n'

export default async function Head({ params: { lng } }) {
const { t } = await useTranslation(lng, 'client-page')

return (
<>
<title>{t('title')}</title>
<meta
name="description"
content="A playground to explore new Next.js 13 app directory features such as nested layouts, instant loading states, streaming, and component level data fetching."
/>
</>
)
}
37 changes: 37 additions & 0 deletions app/[lng]/client-page/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client'

import Link from 'next/link'
import { useTranslation } from '../../i18n/client'
import { Header } from '../components/Header'
import { Footer } from '../components/Footer/client'
import { useState } from 'react'

export default function Page({ params: { lng } }: {
params: {
lng: string;
};
}) {
const { t } = useTranslation(lng, 'client-page')
const [counter, setCounter] = useState(0)
return (
<>
<main>
<Header heading={t('h1')} />
<p>{t('counter', { count: counter })}</p>
<div>
<button onClick={() => setCounter(Math.max(0, counter - 1))}>-</button>
<button onClick={() => setCounter(Math.min(10, counter + 1))}>+</button>
</div>
<Link href={`/${lng}/second-client-page`}>
{t('to-second-client-page')}
</Link>
<Link href={`/${lng}`}>
<button type="button">
{t('back-to-home')}
</button>
</Link>
</main>
<Footer lng={lng} path="/client-page" />
</>
)
}
46 changes: 46 additions & 0 deletions app/[lng]/components/Footer/FooterBase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { i18n } from 'i18next'
import Link from 'next/link'
import { Trans } from 'react-i18next/TransWithoutContext'
import { languages } from '../../../i18n/settings'

export const FooterBase = ({ i18n, lng, path = '' }: { i18n: i18n, lng: string, path: string }) => {
const t = i18n.getFixedT(lng, 'footer')
return (
<footer>
<Trans i18nKey="languageSwitcher" t={t}>
{/* @ts-expect-error Trans interpolation */}
Switch from <strong>{{lng}}</strong> to:{' '}
</Trans>
{languages.filter((l) => lng !== l).map((l, index) => {
return (
<span key={l}>
{index > 0 && (' or ')}
<Link href={`/${l}${path}`}>
{l}
</Link>
</span>
)
})}
<p>{t('description')}</p>
<p
style={{
fontSize: 'smaller',
fontStyle: 'italic',
marginTop: 20,
}}
>
<Trans i18nKey="helpLocize" t={t}>
With using
<a href="https://locize.com" target="_new">
locize
</a>
you directly support the future of
<a href="https://www.i18next.com" target="_new">
i18next
</a>
.
</Trans>
</p>
</footer>
)
}
9 changes: 9 additions & 0 deletions app/[lng]/components/Footer/client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
'use client'

import { FooterBase } from './FooterBase'
import { useTranslation } from '../../../i18n/client'

export const Footer = ({ lng, path }) => {
const { i18n } = useTranslation(lng, 'footer')
return <FooterBase i18n={i18n} lng={lng} path={path} />
}
10 changes: 10 additions & 0 deletions app/[lng]/components/Footer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { useTranslation } from '../../../i18n'
import { FooterBase } from './FooterBase'

export const Footer = async ({ lng, path }: {
lng: string;
path?: string;
}) => {
const { t, i18n } = await useTranslation(lng, 'footer')
return <FooterBase i18n={i18n} lng={lng} path={path} />
}
12 changes: 12 additions & 0 deletions app/[lng]/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const Header = ({ heading }) => (
<>
<h2>
Next.js 13 <small>(app directory)</small> - i18next
<hr />
</h2>
<h1>{heading}</h1>
<a className="github" href="//github.com/i18next/i18next">
<i className="typcn typcn-social-github-circular" />
</a>
</>
)
99 changes: 99 additions & 0 deletions app/[lng]/global.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
body {
font-family: 'Open Sans', sans-serif;
text-align: center;
background-image: linear-gradient(
to left top,
#ffffff,
#f5f5f5,
#eaeaea,
#e0e0e0,
#d6d6d6
);
display: flex;
flex-direction: column;
margin: 0;
min-height: 100vh;
min-width: 100vw;
}

h1,
h2 {
font-family: 'Oswald', sans-serif;
}

h1 {
font-size: 3rem;
/* margin: 5rem 0; */
}
h2 {
min-width: 18rem;
font-size: 2rem;
opacity: 0.3;
}
h3 {
font-size: 1.5rem;
opacity: 0.5;
}

p {
line-height: 1.65em;
}
p:nth-child(2) {
font-style: italic;
opacity: 0.65;
margin-top: 1rem;
}

a.github {
position: fixed;
top: 0.5rem;
right: 0.75rem;
font-size: 4rem;
color: #888;
opacity: 0.8;
}
a.github:hover {
opacity: 1;
}

button {
display: inline-block;
vertical-align: bottom;
outline: 0;
text-decoration: none;
cursor: pointer;
background-color: rgba(255, 255, 255, 0.5);
box-sizing: border-box;
font-size: 1em;
font-family: inherit;
border-radius: 3px;
transition: box-shadow 0.2s ease;
user-select: none;
line-height: 2.5em;
min-height: 40px;
padding: 0 0.8em;
border: 0;
color: inherit;
position: relative;
transform: translateZ(0);
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
margin: 0.8rem;
}

button:hover,
button:focus {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4);
}

main {
display: flex;
flex-direction: column;
flex: 1;
justify-content: center;
align-items: center;
}
footer {
background-color: rgba(255, 255, 255, 0.5);
width: 100vw;
padding: 3rem 0;
}
17 changes: 17 additions & 0 deletions app/[lng]/head.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { languages, fallbackLng } from '../i18n/settings'
import { useTranslation } from '../i18n'

export default async function Head({ params: { lng } }) {
if (languages.indexOf(lng) < 0) lng = fallbackLng
const { t } = await useTranslation(lng)

return (
<>
<title>{t('title')}</title>
<meta
name="description"
content="A playground to explore new Next.js 13 app directory features such as nested layouts, instant loading states, streaming, and component level data fetching."
/>
</>
)
}
24 changes: 24 additions & 0 deletions app/[lng]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import './global.css'

import { dir } from 'i18next'
import { languages } from '../i18n/settings'

export async function generateStaticParams() {
return languages.map((lng) => ({ lng }))
}

export default function RootLayout({
children,
params: {
lng
}
}) {
return (
<html lang={lng} dir={dir(lng)}>
<head />
<body>
{children}
</body>
</html>
)
}
53 changes: 53 additions & 0 deletions app/[lng]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Link from 'next/link'
import { Trans } from 'react-i18next/TransWithoutContext'
import { languages, fallbackLng } from '../i18n/settings'
import { useTranslation } from '../i18n'
import { Header } from './components/Header'
import { Footer } from './components/Footer'

export default async function Page({ params: { lng } }: {
params: {
lng: string;
};
}) {
if (languages.indexOf(lng) < 0) lng = fallbackLng
const { t } = await useTranslation(lng)

return (
<>
<main>
<Header heading={t('h1')} />
<h2>
<Trans t={t} i18nKey="welcome">
Welcome to Next.js v13 <small>appDir</small> and i18next
</Trans>
</h2>
<div style={{ width: '100%' }}>
<p>
<Trans t={t} i18nKey="blog.text">
Check out the corresponding <a href={t('blog.link')}>blog post</a> describing this example.
</Trans>
</p>
<a href={t('blog.link')}>
<img
style={{ width: '50%' }}
alt="next 13 blog post"
src="https://locize.com/blog/next-13-app-dir-i18n/next-13-app-dir-i18n.jpg"
/>
</a>
</div>
<hr style={{ marginTop: 20, width: '90%' }} />
<div>
<Link href={`/${lng}/second-page`}>
<button type="button">{t('to-second-page')}</button>
</Link>
<Link href={`/${lng}/client-page`}>
<button type="button">{t('to-client-page')}</button>
</Link>
</div>
</main>
{/* @ts-expect-error Server Component */}
<Footer lng={lng}/>
</>
)
}
Loading

0 comments on commit 319c21e

Please sign in to comment.