From 71a0c18ce85ca6a88ccf9dace3a0bab4a142236f Mon Sep 17 00:00:00 2001 From: Johnson Mao Date: Sun, 7 Jan 2024 22:38:13 +0800 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20i18n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- data/i18n/index.ts | 14 +++++++++++++ data/i18n/locales/en.json | 22 ++++++++++++++++++++ data/i18n/locales/zh.json | 22 ++++++++++++++++++++ data/metadata.ts | 6 +++--- i18n/index.ts | 15 ------------- i18n/locales/en/common.ts | 9 -------- i18n/locales/en/index.ts | 11 ---------- i18n/locales/en/metadata.ts | 6 ------ i18n/locales/en/notFound.ts | 5 ----- i18n/locales/zh/common.ts | 9 -------- i18n/locales/zh/index.ts | 11 ---------- i18n/locales/zh/metadata.ts | 6 ------ i18n/locales/zh/notFound.ts | 5 ----- src/app/[lang]/layout.tsx | 6 +++--- src/app/[lang]/page.tsx | 10 ++++----- src/app/[lang]/posts/InfiniteList.tsx | 5 +++-- src/app/[lang]/posts/page.tsx | 10 ++++----- src/app/layout.tsx | 2 +- src/app/not-found.tsx | 2 +- src/hooks/useI18n.ts | 2 +- src/middleware.ts | 2 +- src/utils/generateRSS.ts | 2 +- src/utils/getLocale.ts | 4 ++-- tests/app/[lang]/layout.test.tsx | 2 +- tests/app/[lang]/page.test.tsx | 2 +- tests/app/[lang]/posts/infiniteList.test.tsx | 4 ++-- tests/app/[lang]/posts/page.test.tsx | 2 +- tests/hooks/useI18n.test.ts | 2 +- tests/utils/generateRSS.test.ts | 2 +- tests/utils/getLocale.test.ts | 2 +- 31 files changed, 95 insertions(+), 111 deletions(-) create mode 100644 data/i18n/index.ts create mode 100644 data/i18n/locales/en.json create mode 100644 data/i18n/locales/zh.json delete mode 100644 i18n/index.ts delete mode 100644 i18n/locales/en/common.ts delete mode 100644 i18n/locales/en/index.ts delete mode 100644 i18n/locales/en/metadata.ts delete mode 100644 i18n/locales/en/notFound.ts delete mode 100644 i18n/locales/zh/common.ts delete mode 100644 i18n/locales/zh/index.ts delete mode 100644 i18n/locales/zh/metadata.ts delete mode 100644 i18n/locales/zh/notFound.ts diff --git a/README.md b/README.md index ced2c239..ce88ad0c 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Welcome to My Blog, a simple blog built with Next.js 13 and Markdown for content - Next.js 13 - React 18 +- React Spring - TypeScript - Tailwind CSS - I18n @@ -24,10 +25,10 @@ Welcome to My Blog, a simple blog built with Next.js 13 and Markdown for content ## Project Structure - `data/`: Contains data-related files. + - `i18n/`: Internationalization and localization files. - `posts/`: Markdown files for blog posts. - `giscus.ts`: Configuration for the Giscus comment system. - `metadata.ts`: Metadata and configuration for the blog. -- `i18n/`: Internationalization and localization files. - `public/static/`: Public static images and other assets. - `src/`: The main source code directory. - `app/`: Next.js pages and routing. @@ -37,6 +38,7 @@ Welcome to My Blog, a simple blog built with Next.js 13 and Markdown for content - `plugins/`: Additional plugins or extensions. - `utils/`: Utility functions and helper modules. - `middleware.ts`: Next.js middleware. +- `tests/`: Test folder contains the application's test files. - `types/`: TypeScript type definitions. diff --git a/data/i18n/index.ts b/data/i18n/index.ts new file mode 100644 index 00000000..aca93f97 --- /dev/null +++ b/data/i18n/index.ts @@ -0,0 +1,14 @@ +export const defaultLocale = 'zh'; +export const locales = [defaultLocale, 'en'] as const; +export type Locale = (typeof locales)[number]; +export type Dictionary = typeof import('./locales/zh.json'); + +const dictionaries: Record Promise> = { + zh: () => import('./locales/zh.json'), + en: () => import('./locales/en.json'), +}; + +export const getDictionary = (locale: Locale) => dictionaries[locale](); + +export const isLocale = (language: string): language is Locale => + locales.findIndex((locale) => locale === language) > -1; diff --git a/data/i18n/locales/en.json b/data/i18n/locales/en.json new file mode 100644 index 00000000..813ee065 --- /dev/null +++ b/data/i18n/locales/en.json @@ -0,0 +1,22 @@ +{ + "common": { + "home": "Home", + "posts": "Posts", + "about": "About", + "tags": "Tags", + "categories": "Categories", + "latestPosts": "Latest Posts", + "morePosts": "More Posts" + }, + "homePage": { + "title": "Mao's Corner", + "description": "Welcome to Mao's Learning Corner, where I document my study notes. Looking forward to exploring the vast realms of knowledge together with you." + }, + "postsPage": { + "title": "Mao's Corner", + "description": "Welcome to Mao's Learning Corner, where I document my study notes. Looking forward to exploring the vast realms of knowledge together with you." + }, + "notFound": { + "message": "Sorry we couldn't find this page." + } +} diff --git a/data/i18n/locales/zh.json b/data/i18n/locales/zh.json new file mode 100644 index 00000000..69196b29 --- /dev/null +++ b/data/i18n/locales/zh.json @@ -0,0 +1,22 @@ +{ + "common": { + "home": "首頁", + "posts": "文章", + "about": "關於", + "tags": "標籤", + "categories": "分類", + "latestPosts": "最新文章", + "morePosts": "更多文章" + }, + "homePage": { + "title": "Mao's Corner", + "description": "歡迎來到阿毛的學習角落,這裡記錄著我的學習筆記,期待與你一同探索廣闊的知識領域。" + }, + "postsPage": { + "title": "Mao's Corner", + "description": "歡迎來到阿毛的學習角落,這裡記錄著我的學習筆記,期待與你一同探索廣闊的知識領域。" + }, + "notFound": { + "message": "抱歉!找不到此頁面。" + } +} diff --git a/data/metadata.ts b/data/metadata.ts index b4bffddb..459fdb7b 100644 --- a/data/metadata.ts +++ b/data/metadata.ts @@ -1,6 +1,6 @@ import type { Metadata } from 'next'; import type { FeedOptions } from 'feed'; -import { Locale, defaultLocale, getDictionary, locales } from '~/i18n'; +import { Locale, defaultLocale, getDictionary, locales } from '~/data/i18n'; export const avatarUrl = '/static/mao.jpg'; export const name = 'Johnson Mao'; @@ -13,7 +13,7 @@ export async function createMetadata( locale: Locale = defaultLocale ): Promise { const { - metadata: { title, description }, + homePage: { title, description }, } = await getDictionary(locale); return { @@ -50,7 +50,7 @@ export async function createMetadata( export async function createFeedOptions(locale: Locale): Promise { const { - metadata: { title, description }, + homePage: { title, description }, } = await getDictionary(locale); return { diff --git a/i18n/index.ts b/i18n/index.ts deleted file mode 100644 index cd40d1d5..00000000 --- a/i18n/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const defaultLocale = 'zh'; -export const locales = [defaultLocale, 'en'] as const; -export type Locale = (typeof locales)[number]; - -const dictionaries = { - zh: () => import('./locales/zh').then((module) => module.default), - en: () => import('./locales/en').then((module) => module.default), -}; - -export type Dictionary = Awaited>; - -export const getDictionary = async (locale: Locale) => dictionaries[locale](); - -export const isLocale = (language: string): language is Locale => - locales.findIndex((locale) => locale === language) > -1; diff --git a/i18n/locales/en/common.ts b/i18n/locales/en/common.ts deleted file mode 100644 index a3d012c9..00000000 --- a/i18n/locales/en/common.ts +++ /dev/null @@ -1,9 +0,0 @@ -const common = { - home: 'Home', - posts: 'Posts', - about: 'About', - tags: 'Tags', - categories: 'Categories', -}; - -export default common; diff --git a/i18n/locales/en/index.ts b/i18n/locales/en/index.ts deleted file mode 100644 index d8acda00..00000000 --- a/i18n/locales/en/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import common from './common'; -import metadata from './metadata'; -import notFound from './notFound'; - -const en = { - common, - metadata, - notFound, -}; - -export default en; diff --git a/i18n/locales/en/metadata.ts b/i18n/locales/en/metadata.ts deleted file mode 100644 index 5baf2433..00000000 --- a/i18n/locales/en/metadata.ts +++ /dev/null @@ -1,6 +0,0 @@ -const metadata = { - title: "Mao's Corner", - description: "Welcome to Mao's Learning Corner, where I document my study notes. Looking forward to exploring the vast realms of knowledge together with you.", -}; - -export default metadata; diff --git a/i18n/locales/en/notFound.ts b/i18n/locales/en/notFound.ts deleted file mode 100644 index cb7a268e..00000000 --- a/i18n/locales/en/notFound.ts +++ /dev/null @@ -1,5 +0,0 @@ -const notFound = { - message: "Sorry we couldn't find this page.", -}; - -export default notFound; diff --git a/i18n/locales/zh/common.ts b/i18n/locales/zh/common.ts deleted file mode 100644 index 38b32048..00000000 --- a/i18n/locales/zh/common.ts +++ /dev/null @@ -1,9 +0,0 @@ -const common = { - home: '首頁', - posts: '文章', - about: '關於', - tags: '標籤', - categories: '分類', -}; - -export default common; diff --git a/i18n/locales/zh/index.ts b/i18n/locales/zh/index.ts deleted file mode 100644 index d4f77504..00000000 --- a/i18n/locales/zh/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import common from './common'; -import metadata from './metadata'; -import notFound from './notFound'; - -const zh = { - common, - metadata, - notFound, -}; - -export default zh; diff --git a/i18n/locales/zh/metadata.ts b/i18n/locales/zh/metadata.ts deleted file mode 100644 index 88592f52..00000000 --- a/i18n/locales/zh/metadata.ts +++ /dev/null @@ -1,6 +0,0 @@ -const metadata = { - title: "Mao's Corner", - description: '歡迎來到阿毛的學習角落,這裡記錄著我的學習筆記,期待與你一同探索廣闊的知識領域。', -}; - -export default metadata; diff --git a/i18n/locales/zh/notFound.ts b/i18n/locales/zh/notFound.ts deleted file mode 100644 index 1acad915..00000000 --- a/i18n/locales/zh/notFound.ts +++ /dev/null @@ -1,5 +0,0 @@ -const notFound = { - message: "抱歉!找不到此頁面。", -}; - -export default notFound; diff --git a/src/app/[lang]/layout.tsx b/src/app/[lang]/layout.tsx index b069d3d4..41ee45b9 100644 --- a/src/app/[lang]/layout.tsx +++ b/src/app/[lang]/layout.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next'; import { avatarUrl, name, copyright, createMetadata } from '~/data/metadata'; -import { Locale, getDictionary, locales } from '~/i18n'; +import { Locale, getDictionary, locales } from '~/data/i18n'; import ThemeSwitcher from '@/components/ThemeSwitcher'; import Header, { Avatar } from './Header'; import Footer from './Footer'; @@ -19,8 +19,8 @@ export async function generateMetadata({ params: { lang }, }: RootParams): Promise { const { alternates } = await createMetadata(lang); - const { metadata } = await getDictionary(lang); - const { title } = metadata; + const { homePage } = await getDictionary(lang); + const { title } = homePage; return { title: { diff --git a/src/app/[lang]/page.tsx b/src/app/[lang]/page.tsx index 34c55e1e..713e23e7 100644 --- a/src/app/[lang]/page.tsx +++ b/src/app/[lang]/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next'; -import { getDictionary } from '~/i18n'; +import { getDictionary } from '~/data/i18n'; import Container from '@/components/Container'; import { H1 } from '@/components/Heading'; import List from '@/components/List'; @@ -21,16 +21,16 @@ export async function generateMetadata({ async function RootPage({ params: { lang } }: RootParams) { const posts = await getAllDataFrontmatter('posts'); - const { metadata } = await getDictionary(lang); + const { homePage, common } = await getDictionary(lang); return ( <> -

{metadata.title}

-

{metadata.description}

+

{homePage.title}

+

{homePage.description}

-

近期文章

+

{common.latestPosts}

diff --git a/src/app/[lang]/posts/InfiniteList.tsx b/src/app/[lang]/posts/InfiniteList.tsx index 745249b2..97c6d3e7 100644 --- a/src/app/[lang]/posts/InfiniteList.tsx +++ b/src/app/[lang]/posts/InfiniteList.tsx @@ -9,11 +9,12 @@ import Article from '../Article'; type InfiniteListProps = { items: DataFrontmatter[]; + morePostsText: string; }; const MemoArticle = memo(Article); -function InfiniteList({ items }: InfiniteListProps) { +function InfiniteList({ items, morePostsText }: InfiniteListProps) { const searchParams = useSearchParams(); const total = items.length; const clampLimit = clamp(1, total); @@ -24,7 +25,7 @@ function InfiniteList({ items }: InfiniteListProps) { {limit < total && ( - 更多文章 + {morePostsText} )} diff --git a/src/app/[lang]/posts/page.tsx b/src/app/[lang]/posts/page.tsx index 13d1d08b..7b611e17 100644 --- a/src/app/[lang]/posts/page.tsx +++ b/src/app/[lang]/posts/page.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next'; -import { getDictionary } from '~/i18n'; +import { getDictionary } from '~/data/i18n'; import Container from '@/components/Container'; import { H1 } from '@/components/Heading'; import { getAllDataFrontmatter } from '@/utils/mdx'; @@ -20,16 +20,16 @@ export async function generateMetadata({ async function RootPage({ params: { lang } }: RootParams) { const posts = await getAllDataFrontmatter('posts'); - const { metadata } = await getDictionary(lang); + const { postsPage, common } = await getDictionary(lang); return ( <> -

{metadata.title}

-

{metadata.description}

+

{postsPage.title}

+

{postsPage.description}

- + ); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f4afae7c..234dfa55 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,6 @@ import type { Metadata } from 'next'; -import { locales } from '~/i18n'; +import { locales } from '~/data/i18n'; import { createMetadata, createFeedOptions } from '~/data/metadata'; import Container from '@/components/Container'; import generateRSS from '@/utils/generateRSS'; diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 99678784..83d1b7a4 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -7,7 +7,7 @@ export const metadata: Metadata = { function NotFoundPage() { return ( -
+

Page not found

Sorry, the page you are looking for does not exist.

diff --git a/src/hooks/useI18n.ts b/src/hooks/useI18n.ts index 2402f2b5..a874359b 100644 --- a/src/hooks/useI18n.ts +++ b/src/hooks/useI18n.ts @@ -1,5 +1,5 @@ import { usePathname } from 'next/navigation'; -import { defaultLocale } from '~/i18n'; +import { defaultLocale } from '~/data/i18n'; import getLocale from '@/utils/getLocale'; /** diff --git a/src/middleware.ts b/src/middleware.ts index 81a8d644..d6ad6676 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { defaultLocale, locales } from '~/i18n'; +import { defaultLocale, locales } from '~/data/i18n'; import getLocale from '@/utils/getLocale'; export function middleware(request: NextRequest) { diff --git a/src/utils/generateRSS.ts b/src/utils/generateRSS.ts index 74526601..3f093246 100644 --- a/src/utils/generateRSS.ts +++ b/src/utils/generateRSS.ts @@ -1,7 +1,7 @@ import path from 'path'; import fs from 'fs'; import { Feed, FeedOptions } from 'feed'; -import { defaultLocale } from '~/i18n'; +import { defaultLocale } from '~/data/i18n'; export const PUBLIC_FEED_PATH = path.join(process.cwd(), 'public', 'feed'); diff --git a/src/utils/getLocale.ts b/src/utils/getLocale.ts index 62fa28be..e37c46b2 100644 --- a/src/utils/getLocale.ts +++ b/src/utils/getLocale.ts @@ -1,9 +1,9 @@ -import { Locale, isLocale } from '~/i18n'; +import { Locale, isLocale } from '~/data/i18n'; /** * The function get language locale code from the input. * - * Ensures that the selected locale code matches one of the supported locale codes in the ~/i18n file. + * Ensures that the selected locale code matches one of the supported locale codes in the ~/data/i18n file. * * @example * import getLocale from '@/utils/getLocale'; diff --git a/tests/app/[lang]/layout.test.tsx b/tests/app/[lang]/layout.test.tsx index a74ec674..b580d95a 100644 --- a/tests/app/[lang]/layout.test.tsx +++ b/tests/app/[lang]/layout.test.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react'; -import { locales } from '~/i18n'; +import { locales } from '~/data/i18n'; import mockNavigation from '~/tests/navigation'; import Layout, { generateMetadata, generateStaticParams } from '@/app/[lang]/layout'; diff --git a/tests/app/[lang]/page.test.tsx b/tests/app/[lang]/page.test.tsx index 43361caa..16d3e0b6 100644 --- a/tests/app/[lang]/page.test.tsx +++ b/tests/app/[lang]/page.test.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react'; -import en from '~/i18n/locales/en'; +import en from '~/data/i18n/locales/en.json'; import Page, { generateMetadata } from '@/app/[lang]/page'; jest.mock('@/utils/mdx', () => ({ diff --git a/tests/app/[lang]/posts/infiniteList.test.tsx b/tests/app/[lang]/posts/infiniteList.test.tsx index 3d4b50c8..e1691230 100644 --- a/tests/app/[lang]/posts/infiniteList.test.tsx +++ b/tests/app/[lang]/posts/infiniteList.test.tsx @@ -14,8 +14,8 @@ describe('InfiniteList component', () => { tags: [], })); mockNavigation.searchParams.mockReturnValue(new URLSearchParams()); - render(); - const link = screen.getByRole('link', { name: '更多文章' }); + render(); + const link = screen.getByRole('link', { name: 'MorePost' }); const list = screen.getByRole('list'); expect(link).toBeInTheDocument(); expect(list).toBeInTheDocument(); diff --git a/tests/app/[lang]/posts/page.test.tsx b/tests/app/[lang]/posts/page.test.tsx index ed562fd7..3b2cfbf3 100644 --- a/tests/app/[lang]/posts/page.test.tsx +++ b/tests/app/[lang]/posts/page.test.tsx @@ -1,5 +1,5 @@ import { render, screen } from '@testing-library/react'; -import en from '~/i18n/locales/en'; +import en from '~/data/i18n/locales/en.json'; import mockNavigation from '~/tests/navigation'; import Page, { generateMetadata } from '@/app/[lang]/posts/page'; diff --git a/tests/hooks/useI18n.test.ts b/tests/hooks/useI18n.test.ts index 4e29e905..c6e2ec22 100644 --- a/tests/hooks/useI18n.test.ts +++ b/tests/hooks/useI18n.test.ts @@ -1,5 +1,5 @@ import { renderHook } from '@testing-library/react'; -import { defaultLocale } from '~/i18n'; +import { defaultLocale } from '~/data/i18n'; import mockNavigation from '~/tests/navigation'; import useI18n from '@/hooks/useI18n'; diff --git a/tests/utils/generateRSS.test.ts b/tests/utils/generateRSS.test.ts index 48304a99..4586c5bf 100644 --- a/tests/utils/generateRSS.test.ts +++ b/tests/utils/generateRSS.test.ts @@ -1,6 +1,6 @@ import path from 'path'; import type { FeedOptions } from 'feed'; -import { defaultLocale } from '~/i18n'; +import { defaultLocale } from '~/data/i18n'; import generateRSS, { PUBLIC_FEED_PATH } from '@/utils/generateRSS'; const mockWriteFile = jest.fn(); diff --git a/tests/utils/getLocale.test.ts b/tests/utils/getLocale.test.ts index 189cba72..53306ffd 100644 --- a/tests/utils/getLocale.test.ts +++ b/tests/utils/getLocale.test.ts @@ -1,4 +1,4 @@ -import { defaultLocale } from '~/i18n'; +import { defaultLocale } from '~/data/i18n'; import getLocale from '@/utils/getLocale'; describe('get locale function', () => {