Skip to content

Commit

Permalink
♻️ refactor i18n
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnsonMao committed Jan 7, 2024
1 parent 26dfc9c commit 71a0c18
Show file tree
Hide file tree
Showing 31 changed files with 95 additions and 111 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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.

<!-- ## License -->
Expand Down
14 changes: 14 additions & 0 deletions data/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -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<Locale, () => Promise<Dictionary>> = {
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;
22 changes: 22 additions & 0 deletions data/i18n/locales/en.json
Original file line number Diff line number Diff line change
@@ -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."
}
}
22 changes: 22 additions & 0 deletions data/i18n/locales/zh.json
Original file line number Diff line number Diff line change
@@ -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": "抱歉!找不到此頁面。"
}
}
6 changes: 3 additions & 3 deletions data/metadata.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -13,7 +13,7 @@ export async function createMetadata(
locale: Locale = defaultLocale
): Promise<Metadata> {
const {
metadata: { title, description },
homePage: { title, description },
} = await getDictionary(locale);

return {
Expand Down Expand Up @@ -50,7 +50,7 @@ export async function createMetadata(

export async function createFeedOptions(locale: Locale): Promise<FeedOptions> {
const {
metadata: { title, description },
homePage: { title, description },
} = await getDictionary(locale);

return {
Expand Down
15 changes: 0 additions & 15 deletions i18n/index.ts

This file was deleted.

9 changes: 0 additions & 9 deletions i18n/locales/en/common.ts

This file was deleted.

11 changes: 0 additions & 11 deletions i18n/locales/en/index.ts

This file was deleted.

6 changes: 0 additions & 6 deletions i18n/locales/en/metadata.ts

This file was deleted.

5 changes: 0 additions & 5 deletions i18n/locales/en/notFound.ts

This file was deleted.

9 changes: 0 additions & 9 deletions i18n/locales/zh/common.ts

This file was deleted.

11 changes: 0 additions & 11 deletions i18n/locales/zh/index.ts

This file was deleted.

6 changes: 0 additions & 6 deletions i18n/locales/zh/metadata.ts

This file was deleted.

5 changes: 0 additions & 5 deletions i18n/locales/zh/notFound.ts

This file was deleted.

6 changes: 3 additions & 3 deletions src/app/[lang]/layout.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -19,8 +19,8 @@ export async function generateMetadata({
params: { lang },
}: RootParams): Promise<Metadata> {
const { alternates } = await createMetadata(lang);
const { metadata } = await getDictionary(lang);
const { title } = metadata;
const { homePage } = await getDictionary(lang);
const { title } = homePage;

return {
title: {
Expand Down
10 changes: 5 additions & 5 deletions src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
<>
<Container className="pb-8">
<H1 className="mb-4 text-3xl font-bold">{metadata.title}</H1>
<p className="text-xl">{metadata.description}</p>
<H1 className="mb-4 text-3xl font-bold">{homePage.title}</H1>
<p className="text-xl">{homePage.description}</p>
</Container>
<Container as="main" className="py-8">
<p className="mb-4 text-lg">近期文章</p>
<p className="mb-4 text-lg">{common.latestPosts}</p>
<List Item={Article} items={posts.slice(0, 4)} />
</Container>
</>
Expand Down
5 changes: 3 additions & 2 deletions src/app/[lang]/posts/InfiniteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -24,7 +25,7 @@ function InfiniteList({ items }: InfiniteListProps) {
<List Item={MemoArticle} items={items.slice(0, limit)} />
{limit < total && (
<Link href={`?limit=${clampLimit(limit + 10)}`} replace scroll={false}>
更多文章
{morePostsText}
</Link>
)}
</>
Expand Down
10 changes: 5 additions & 5 deletions src/app/[lang]/posts/page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
<>
<Container className="pb-8">
<H1 className="mb-4 text-3xl font-bold">{metadata.title}</H1>
<p className="text-xl">{metadata.description}</p>
<H1 className="mb-4 text-3xl font-bold">{postsPage.title}</H1>
<p className="text-xl">{postsPage.description}</p>
</Container>
<Container as="main" className="py-8">
<InfiniteList items={posts} />
<InfiniteList items={posts} morePostsText={common.morePosts} />
</Container>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion src/app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const metadata: Metadata = {

function NotFoundPage() {
return (
<div className="flex h-screen items-center justify-center">
<div className="flex h-screen flex-col items-center justify-center">
<H2>Page not found</H2>
<p>Sorry, the page you are looking for does not exist.</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useI18n.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { usePathname } from 'next/navigation';
import { defaultLocale } from '~/i18n';
import { defaultLocale } from '~/data/i18n';
import getLocale from '@/utils/getLocale';

/**
Expand Down
2 changes: 1 addition & 1 deletion src/middleware.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/generateRSS.ts
Original file line number Diff line number Diff line change
@@ -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');

Expand Down
4 changes: 2 additions & 2 deletions src/utils/getLocale.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
2 changes: 1 addition & 1 deletion tests/app/[lang]/layout.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
2 changes: 1 addition & 1 deletion tests/app/[lang]/page.test.tsx
Original file line number Diff line number Diff line change
@@ -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', () => ({
Expand Down
4 changes: 2 additions & 2 deletions tests/app/[lang]/posts/infiniteList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ describe('InfiniteList component', () => {
tags: [],
}));
mockNavigation.searchParams.mockReturnValue(new URLSearchParams());
render(<InfiniteList items={generateMockPosts(20)} />);
const link = screen.getByRole('link', { name: '更多文章' });
render(<InfiniteList items={generateMockPosts(20)} morePostsText='MorePost' />);
const link = screen.getByRole('link', { name: 'MorePost' });
const list = screen.getByRole('list');
expect(link).toBeInTheDocument();
expect(list).toBeInTheDocument();
Expand Down
2 changes: 1 addition & 1 deletion tests/app/[lang]/posts/page.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
2 changes: 1 addition & 1 deletion tests/hooks/useI18n.test.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down
2 changes: 1 addition & 1 deletion tests/utils/generateRSS.test.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand Down
Loading

0 comments on commit 71a0c18

Please sign in to comment.