Skip to content

Commit 225995d

Browse files
committed
feat: initial i18n configuration added
1 parent 7ebb9a6 commit 225995d

31 files changed

+276
-123
lines changed

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"lint": "next lint"
1010
},
1111
"dependencies": {
12+
"@formatjs/intl-localematcher": "^0.5.4",
1213
"@heroicons/react": "^2.1.1",
1314
"@splinetool/react-spline": "^2.2.6",
1415
"@splinetool/runtime": "^1.2.8",
@@ -17,6 +18,7 @@
1718
"eslint-config-next": "13.2.3",
1819
"framer-motion": "^11.0.3",
1920
"lodash": "^4.17.21",
21+
"negotiator": "^0.6.3",
2022
"next": "^14.1.0",
2123
"react": "18.2.0",
2224
"react-dom": "18.2.0",
@@ -31,6 +33,7 @@
3133
},
3234
"devDependencies": {
3335
"@types/lodash": "^4.14.202",
36+
"@types/negotiator": "^0.6.3",
3437
"@types/node": "18.14.6",
3538
"@types/react": "^18.2.48",
3639
"@types/react-dom": "18.2.18",

src/app/(routes)/posts/layout.tsx

-27
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
'use client'
22

3+
import { NavItem } from '@/app/[locale]/_components/NavItem'
4+
import { NavModal } from '@/app/[locale]/_components/NavModal'
35
import ChatBox from '@/components/common/chats/ChatBox'
4-
import { NavItem } from '@/components/main/NavItem'
5-
import { NavModal } from '@/components/main/NavModal'
6+
import LoadingCircle from '@/components/common/loadings/LoadingCircle'
67
import { ChatBubbleLeftEllipsisIcon, DocumentIcon } from '@heroicons/react/24/solid'
78
import { Application, SPEObject } from '@splinetool/runtime'
89
import { motion } from 'framer-motion'
@@ -12,27 +13,7 @@ import { useRef, useState } from 'react'
1213

1314
const Spline = dynamic(() => import('@splinetool/react-spline'), {
1415
ssr: false,
15-
loading: () => (
16-
<div role="status">
17-
<svg
18-
aria-hidden="true"
19-
className="w-8 h-8 text-light/40 animate-spin fill-primary"
20-
viewBox="0 0 100 101"
21-
fill="none"
22-
xmlns="http://www.w3.org/2000/svg"
23-
>
24-
<path
25-
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
26-
fill="currentColor"
27-
/>
28-
<path
29-
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
30-
fill="currentFill"
31-
/>
32-
</svg>
33-
<span className="sr-only">Loading...</span>
34-
</div>
35-
)
16+
loading: () => <LoadingCircle />
3617
})
3718

3819
const inter = Inter({ variable: '--font-inter', subsets: ['latin'] })
@@ -78,7 +59,7 @@ const chat = {
7859
const SPLINE_SCENE = 'https://prod.spline.design/W83XdmrQbaQnPMlJ/scene.splinecode'
7960
const SPLINE_BOT_ID = '7a1937ee-e0ec-4da1-bca9-10b1ff105490'
8061

81-
const Home = () => {
62+
const MainContainer = () => {
8263
const [selected, setSelected] = useState<string | null>(null)
8364
const [isBotChatOpened, setIsBotChatOpened] = useState<boolean>(false)
8465

@@ -109,74 +90,72 @@ const Home = () => {
10990
}
11091

11192
return (
112-
<main className={inter.variable}>
113-
<div className="w-full h-[100vh] flex justify-center relative overflow-hidden touch-none">
114-
{/* Background Component */}
93+
<div className="w-full h-[100vh] flex justify-center relative overflow-hidden touch-none">
94+
{/* Background Component */}
11595

116-
<div
117-
className="fixed w-full h-full
96+
<div
97+
className="fixed w-full h-full
11898
flex justify-center items-center z-30 pointer-events-auto bg-gradient-to-tl from-primary to-primary/60"
119-
>
120-
<Spline scene={SPLINE_SCENE} onLoad={onLoad} />
121-
</div>
99+
>
100+
<Spline scene={SPLINE_SCENE} onLoad={onLoad} />
101+
</div>
122102

123-
<div
124-
className={`fixed w-full h-full max-w-[1020px] px-[24px] pt-4 pb-[40px] md:pb-[100px]
103+
<div
104+
className={`fixed w-full h-full max-w-[1020px] px-[24px] pt-4 pb-[40px] md:pb-[100px]
125105
flex justify-end items-center z-30 pointer-events-auto`}
126-
>
127-
<ChatBox isOpen={isBotChatOpened} />
128-
</div>
106+
>
107+
<ChatBox isOpen={isBotChatOpened} />
108+
</div>
109+
110+
{navList.map((nav, i) => (
111+
<NavModal selected={selected === nav.name} {...nav} key={`nav-modal-${nav}-${i}`} />
112+
))}
129113

130-
{navList.map((nav, i) => (
131-
<NavModal selected={selected === nav.name} {...nav} key={`nav-modal-${nav}-${i}`} />
132-
))}
133-
134-
{/* Navigation */}
135-
<div className="fixed bottom-[30px] md:bottom-[60px] w-full max-w-[900px] h-full justify-start items-end z-50 pointer-events-none">
136-
<div className="w-full h-full flex flex-col-reverse bottom-[200px]">
137-
<nav className="flex justify-center gap-[16px]">
138-
{navList.map((nav, i) => (
139-
<div id={nav.name} key={`${nav.name}-${i}`} className="pointer-events-auto">
140-
<motion.div
141-
initial={{ y: 40, opacity: 0 }}
142-
animate={{ y: 0, opacity: 100 }}
143-
transition={{ ease: 'easeInOut', duration: 0.25 * i }}
144-
>
145-
<NavItem
146-
{...nav}
147-
selected={selected === nav.name}
148-
onClick={() => {
149-
if (!!isBotChatOpened) {
150-
toggleChat()
151-
}
152-
handleNavSelect(nav.name)
153-
}}
154-
/>
155-
</motion.div>
156-
</div>
157-
))}
158-
<div id={chat.name} className="pointer-events-auto">
114+
{/* Navigation */}
115+
<div className="fixed bottom-[30px] md:bottom-[60px] w-full max-w-[900px] h-full justify-start items-end z-50 pointer-events-none">
116+
<div className="w-full h-full flex flex-col-reverse bottom-[200px]">
117+
<nav className="flex justify-center gap-[16px]">
118+
{navList.map((nav, i) => (
119+
<div id={nav.name} key={`${nav.name}-${i}`} className="pointer-events-auto">
159120
<motion.div
160121
initial={{ y: 40, opacity: 0 }}
161122
animate={{ y: 0, opacity: 100 }}
162-
transition={{ ease: 'easeInOut', duration: 0.25 * navList.length }}
123+
transition={{ ease: 'easeInOut', duration: 0.25 * i }}
163124
>
164125
<NavItem
165-
selected={!!isBotChatOpened}
166-
{...chat}
126+
{...nav}
127+
selected={selected === nav.name}
167128
onClick={() => {
168-
setSelected(null)
169-
toggleChat()
129+
if (!!isBotChatOpened) {
130+
toggleChat()
131+
}
132+
handleNavSelect(nav.name)
170133
}}
171134
/>
172135
</motion.div>
173136
</div>
174-
</nav>
175-
</div>
137+
))}
138+
<div id={chat.name} className="pointer-events-auto">
139+
<motion.div
140+
initial={{ y: 40, opacity: 0 }}
141+
animate={{ y: 0, opacity: 100 }}
142+
transition={{ ease: 'easeInOut', duration: 0.25 * navList.length }}
143+
>
144+
<NavItem
145+
selected={!!isBotChatOpened}
146+
{...chat}
147+
onClick={() => {
148+
setSelected(null)
149+
toggleChat()
150+
}}
151+
/>
152+
</motion.div>
153+
</div>
154+
</nav>
176155
</div>
177156
</div>
178-
</main>
157+
</div>
179158
)
180159
}
181160

182-
export default Home
161+
export default MainContainer
File renamed without changes.

src/components/main/NavModal.tsx src/app/[locale]/_components/NavModal.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { HTMLAttributes, ReactNode, useEffect, useState } from 'react'
44

55
import { motion } from 'framer-motion'
66
import Link from 'next/link'
7-
import useResizeHandler from '../hooks/useResizeHandler'
7+
import useResizeHandler from '../../../components/hooks/useResizeHandler'
88
interface Params extends HTMLAttributes<HTMLButtonElement> {
99
name: string
1010
href: string

src/app/(routes)/about/layout.tsx src/app/[locale]/about/layout.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ interface Params {
1616
const PostsLayouts = ({ children }: Params) => {
1717
return (
1818
<div className="w-full h-screen flex flex-col bg-background transition-colors duration-200 ease-linear overflow-hidden">
19-
<Header />
2019
<main className="relative w-full overflow-y-auto">
2120
<div className="w-full h-fit flex justify-center">{children}</div>
2221
</main>
File renamed without changes.
File renamed without changes.
File renamed without changes.

src/app/(routes)/layout.tsx src/app/[locale]/layout.tsx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { i18n, Locale } from '@/i18n.config'
12
import { Metadata } from 'next'
23
import 'src/app/styles/globals.css'
34

@@ -6,13 +7,18 @@ export const metadata: Metadata = {
67
description: 'Generated by create next app'
78
}
89

10+
export async function generateStaticParams() {
11+
return i18n.locales.map((locale) => ({ lang: locale }))
12+
}
13+
914
interface Params {
1015
children: React.ReactNode
16+
params: { locale: Locale }
1117
}
1218

13-
const RootLayout = ({ children }: Params) => {
19+
const RootLayout = ({ children, params }: Params) => {
1420
return (
15-
<html lang="en">
21+
<html lang={params.locale}>
1622
<head>
1723
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
1824
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />

src/app/[locale]/page.tsx

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Locale } from '@/i18n.config'
2+
import { Inter } from 'next/font/google'
3+
import MainContainer from './_components/MainContainer'
4+
5+
const inter = Inter({ variable: '--font-inter', subsets: ['latin'] })
6+
7+
interface Params {
8+
params: {
9+
locale: Locale
10+
}
11+
}
12+
13+
const Home = async ({ params }: Params) => {
14+
return (
15+
<main className={inter.variable}>
16+
<MainContainer />
17+
</main>
18+
)
19+
}
20+
21+
export default Home
File renamed without changes.

src/app/[locale]/posts/layout.tsx

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
export const metadata: Metadata = {
2+
title: {
3+
default: 'Posts',
4+
template: '%s'
5+
}
6+
}
7+
8+
import Header from '@/components/common/layouts/header/Header'
9+
import { Locale } from '@/i18n.config'
10+
import { getDictionary } from '@/lib/dictionaries'
11+
import { Metadata } from 'next'
12+
import { ReactNode } from 'react'
13+
14+
interface Params {
15+
children: ReactNode
16+
params: { locale: Locale }
17+
}
18+
19+
const PostsLayouts = async ({ children, params }: Params) => {
20+
const { navigation } = await getDictionary(params.locale)
21+
22+
const navOption: Array<{ label: string; href: string; locale: Locale; external?: boolean }> = [
23+
{ label: navigation.home, href: '/', locale: params.locale },
24+
{ label: navigation.posts, href: '/posts', locale: params.locale },
25+
{ label: navigation.github, href: 'https://www.github.com/henrynoowah', locale: params.locale, external: true }
26+
]
27+
28+
return (
29+
<div className="w-full h-screen flex flex-col bg-background transition-colors duration-200 ease-linear overflow-hidden">
30+
<Header navOption={navOption} />
31+
<main className="relative w-full overflow-y-auto">
32+
<div className="w-full h-fit flex justify-center">{children}</div>
33+
</main>
34+
</div>
35+
)
36+
}
37+
38+
export default PostsLayouts
File renamed without changes.

src/app/(routes)/posts/page.tsx src/app/[locale]/posts/page.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getPosts } from '@/services/posts'
2-
import PostsContainer from './PostsContainer'
2+
import PostsContainer from './_components/PostsContainer'
33

44
export const dynamic = 'force-dynamic'
55

File renamed without changes.

src/app/works/layout.tsx src/app/[locale]/works/layout.tsx

-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export const metadata: Metadata = {
55
}
66
}
77

8-
import Header from '@/components/common/layouts/header/Header'
98
import { Metadata } from 'next'
109
import { ReactNode } from 'react'
1110

@@ -16,7 +15,6 @@ interface Params {
1615
const PostsLayouts = ({ children }: Params) => {
1716
return (
1817
<div className="w-full min-h-[100vh] flex flex-col bg-background transition-colors duration-200 ease-linear">
19-
<Header />
2018
<main className="relative">
2119
<div className="w-full h-fit flex justify-center py-2">{children}</div>
2220
</main>
File renamed without changes.

src/components/common/layouts/header/Header.tsx

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
1+
import { Locale } from '@/i18n.config'
2+
import dynamic from 'next/dynamic'
13
import Link from 'next/link'
24
import packageData from 'package.json'
5+
import { Suspense } from 'react'
36
import Button from '../../Button'
47
import Nav_mobile from './Nav_mobile'
58
import ShowSearchButton from './ShowSearchButton'
6-
import { Suspense } from 'react'
7-
import ThemeToggle from '../../themeToggle/ThemeToggle'
9+
const ThemeToggle = dynamic(() => import('../../themeToggle/ThemeToggle'))
810

911
const HEADER_HEIGHT = 72
1012

11-
const navOption: Array<{ label: string; href: string }> = [
12-
{ label: 'Posts', href: '/posts' },
13-
{ label: 'About', href: '/about' },
14-
{ label: 'Works', href: '/works' }
15-
]
16-
17-
const Header = () => {
13+
interface Params {
14+
navOption: Array<{ label: string; href: string; locale: Locale; external?: boolean }>
15+
}
16+
const Header = ({ navOption }: Params) => {
1817
return (
1918
<>
2019
<div
@@ -42,7 +41,10 @@ const Header = () => {
4241
<ul className="flex gap-[20px] items-center">
4342
{navOption.map((nav) => (
4443
<li key={nav.label}>
45-
<Link href={nav.href}>
44+
<Link
45+
href={!nav.external && nav.locale ? `/${nav.locale}/${nav.href}` : nav.href}
46+
target={nav.external ? '_blank' : undefined}
47+
>
4648
<Button className="bg-transparent !text-light !text-md">{nav.label}</Button>
4749
</Link>
4850
</li>

0 commit comments

Comments
 (0)