Next.js Blog (8): i18n Content & Toggle
Contents
Notice
This text was originally translated using AI tools.
Introduction#
In the previous post, we reorganized the project structure to pages/[lang]/...
and worked on routing by language through dynamic paths.
Currently, while the URL in the address bar changes, the actual content does not yet change according to the language.
In this post, I will implement the data binding logic to load the appropriate Markdown file
based on the current access path (lang)
and a 'Language Setting Toggle Button' to select the language in real-time.
Implementation#
1. Matching Markdown Files by Language
The first task is to dynamically load files based on the lang value received from getStaticProps.
I spent some time deliberating between two differentiation methods:
- Suffix Differentiation:
post-name.en.md - Folder Differentiation:
posts/ko/post-name.md
Ultimately, I decided that separating by folder is clearer for management than adding suffixes to filenames, so I chose the second method.
The modified content structure is as follows:
contents
├── ko
│ ├── blog
│ └── data
└── en
├── blog
└── dataNow, it is necessary to modify the utility functions in markdown-utils.ts that fetch the section, slug, and Markdown body.
The modifications are simple: just insert the lang parameter at the starting point of every file exploration path.
// markdown-utils.ts
export const getAllSections = ({
lang, // Added lang parameter
sectionRoot = 'contents',
}: {
lang: LangType;
sectionRoot?: SectionType;
}): string[] => {
// Add lang to the path to look at the corresponding language folder.
const contentRoot = path.join(process.cwd(), sectionRoot, lang);
if (!fs.existsSync(contentRoot)) return [];
return fs
.readdirSync(contentRoot, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
.map((entry) => entry.name);
};The remaining functions, such as getAllPostIds or getMarkdown, can be modified using the same principle.
Now, by passing the lang value received from getStaticProps to these functions,
the body data corresponding to each language environment will appear correctly on the screen.
2. Language Toggle Button
I implemented a LangSwitch component using a Switch component.
It saves the selected language and redirects the user to the corresponding path.
const LangSwitch = ({ lang }: { lang: LangType }) => {
const router = useRouter();
const pathname = usePathname();
const toggleHandler = (checked: boolean) => {
// 'en' if checked, 'ko' if false
const nextLang = checked ? 'en' : 'ko';
// Save language preference in localStorage
localStorage.setItem('lang', nextLang);
// Change path
const pathParts = pathname.split('/');
pathParts[1] = nextLang;
const nextPath = pathParts.join('/');
// Navigate smoothly without a page refresh
router.push(nextPath);
};
return <Switch checked={lang === 'en'} onChange={toggleHandler} />;
};
export default LangSwitch;Clicking the button now triggers the language switch correctly.
3. Meta Data Optimization for SEO
Finally, use the lang data passed to each page to improve SEO.
<Head>
<title>{pageTitle}</title>
<meta name='description' content={description} />
<meta property='og:title' content={pageTitle} />
<meta property='og:description' content={description} />
{/* 1. Basic lang setting (for search engines) */}
<meta name='language' content={lang} />
{/* 2. Open Graph setting (for SNS sharing) */}
<meta property='og:locale' content={lang === 'ko' ? 'ko_KR' : 'en_US'} />
</Head>Conclusion#
This completes the implementation of internationalization features. Fortunately, the blog does not have many posts yet. I am using AI tools for most of the English translations, but it is still a demanding task. It might be useful to build a script that checks Git changes and automatically generates translation files.