09. Multilingual Support (Korean · English)
1. Overview
Documents are provided in Korean and English, and the language can be selected from the top menu.
| Approach | Pros and cons |
|---|---|
| Korean and English on the same page | No setup needed, but pages become twice as long and harder to read |
| Separate pages per language (Docusaurus i18n) (chosen) | Clean separation, switching languages opens the same document, search engines get language information |
Scope:
| Area | Korean | English |
|---|---|---|
| IT · Security · AI docs | Yes | Yes |
| About | Yes | Yes |
| Notes | Yes | No (Korean only) |
2. Structure
| Item | Korean (default) | English |
|---|---|---|
| URL | wiki.securenomad.kr/... (unchanged) | wiki.securenomad.kr/en/... |
| Docs | docs/ | i18n/en/docusaurus-plugin-content-docs/current/ |
| Pages (About) | src/pages/ | i18n/en/docusaurus-plugin-content-pages/ |
| Category labels | _category_.json | i18n/en/docusaurus-plugin-content-docs/current.json |
SecureNomad/
├── docs/
│ └── security/xss.md ← Korean
└── i18n/en/
├── docusaurus-plugin-content-docs/
│ ├── current.json ← category label translations
│ └── current/
│ └── security/xss.md ← English (same path, same file name)
└── docusaurus-plugin-content-pages/
└── about.md ← About in English
- URLs for Korean, the default language, do not change, so existing search engine registrations and links keep working
- English docs must use the same path and file name as the Korean doc to be linked to it
3. Basic configuration
i18n: {
defaultLocale: 'ko',
locales: ['ko', 'en'],
localeConfigs: {
ko: {label: '한국어', htmlLang: 'ko-KR'},
en: {label: 'English', htmlLang: 'en-US'},
},
},
| Field | Meaning |
|---|---|
defaultLocale | Default language, served without a prefix (/) |
locales | Supported languages. Non-default languages get a prefix such as /en/ |
label | Name shown in the language menu |
htmlLang | Value for <html lang> and hreflang |
Add a language menu to the navbar:
{type: 'localeDropdown', position: 'right'},
4. Korean-only Notes
In English, the Notes menu is hidden and its pages are not generated.
Docusaurus builds each language separately and exposes the language being built in the DOCUSAURUS_CURRENT_LOCALE environment variable.
// Language being built / served (ko: Korean, en: English)
const currentLocale = process.env.DOCUSAURUS_CURRENT_LOCALE ?? 'ko';
const isKorean = currentLocale === 'ko';
blog: isKorean && { // false in the English build → blog (Notes) disabled
path: 'notes',
// ...
},
// Show Notes only in Korean
...(isKorean ? [{to: '/notes', label: 'Notes', position: 'left'}] : []),
| Result | Korean | English |
|---|---|---|
| Navbar | IT · Security · AI · Notes | IT · Security · AI |
| Notes pages | /notes generated | /en/notes not generated |
Linking to /notes from a doc that has no English translation can break the English build, because the link is treated as broken. Avoid linking to Notes from docs where possible.
5. "Korean only" notice for untranslated pages
When a doc has no English translation, the Korean original is shown instead on the English site (default Docusaurus behavior). The doc content component is wrapped to show a notice above the original in that case.
import React from 'react';
import Content from '@theme-original/DocItem/Content';
import Admonition from '@theme/Admonition';
import {useDoc} from '@docusaurus/plugin-content-docs/client';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
export default function ContentWrapper(props) {
const {metadata} = useDoc();
const {
i18n: {currentLocale, defaultLocale},
} = useDocusaurusContext();
// Translated: source is '@site/i18n/...'; untranslated: original '@site/docs/...'
const isFallback =
currentLocale !== defaultLocale && metadata.source.startsWith('@site/docs/');
return (
<>
{isFallback && (
<Admonition type="info" title="Korean only">
This page is available in Korean only. An English version will be added later.
</Admonition>
)}
<Content {...props} />
</>
);
}
How it works:
| Situation | Doc source (metadata.source) | Notice |
|---|---|---|
| Korean site | @site/docs/... | No (default language) |
| English site + translated | @site/i18n/en/... | No |
| English site + not translated | @site/docs/... | Yes |
:::info Theme customization (swizzling)
Creating a file under src/theme/ at the same path as a theme component replaces or wraps the default component.
Wrapping, which imports the original from @theme-original/..., keeps the default behavior and only adds features, so it is relatively safe across updates.
:::
6. Translating menu and category labels
This command extracts UI strings that need translation into JSON files:
npm run write-translations -- --locale en
Generated files and how they were handled:
| File | Contents | Action |
|---|---|---|
docusaurus-plugin-content-docs/current.json | Category names and descriptions | Kept, Korean descriptions translated into English |
docusaurus-theme-classic/navbar.json | Navbar labels | Deleted (menus are already in English) |
docusaurus-theme-classic/footer.json | Footer copyright | Deleted (the year is saved as a fixed value and would not update each year) |
code.json | Default theme strings (Next, Previous, etc.) | Deleted (built-in English theme strings are used) |
docusaurus-plugin-content-blog/options.json | Blog strings | Deleted (no English Notes) |
"sidebar.itSidebar.category.Docusaurus Setup.link.generated-index.description": {
"message": "A step-by-step record of building a Docusaurus site on a Windows PC and deploying it to GitHub Pages."
}
When a translation file is missing, the original strings are used. Keeping only files that contain actual translations makes maintenance simpler.
7. Search engines
Automatic hreflang
Each page gets alternate-language information automatically, so search engines show the page in the right language.
<link rel="alternate" href="https://wiki.securenomad.kr/it/docusaurus-setup/environment" hreflang="ko-KR" />
<link rel="alternate" href="https://wiki.securenomad.kr/en/it/docusaurus-setup/environment" hreflang="en-US" />
<link rel="alternate" href="https://wiki.securenomad.kr/it/docusaurus-setup/environment" hreflang="x-default" />
Sitemap per language
| Language | Sitemap |
|---|---|
| Korean | https://wiki.securenomad.kr/sitemap.xml |
| English | https://wiki.securenomad.kr/en/sitemap.xml |
Added the English sitemap to static/robots.txt:
User-agent: *
Allow: /
Sitemap: https://wiki.securenomad.kr/sitemap.xml
Sitemap: https://wiki.securenomad.kr/en/sitemap.xml
After deployment, the English sitemap is also submitted to Google Search Console and Naver Search Advisor.
8. Preview and build
| Command | Behavior |
|---|---|
npm start | Preview the Korean site only |
npm start -- --locale en | Preview the English site only |
npm run build | Build both Korean (build/) and English (build/en/) |
The development server (npm start) runs one language at a time, so the language menu does not switch languages in the preview. It works normally on the deployed site.
The build runs once per language, so build time roughly doubles.
9. Adding English docs in the future
- Write the Korean doc:
docs/security/xss.md - Write the English doc at the same path with the same name:
i18n/en/docusaurus-plugin-content-docs/current/security/xss.md - If you added a new category (
_category_.json), runnpm run write-translations -- --locale enand translate the Korean description incurrent.json - Run
npm run buildto check both languages → Commit → Push
English docs do not have to be written right away. Without a translation, the English site shows the Korean original with a "Korean only" notice, so translations can be added later.
10. Checklist
-
i18nconfiguration (ko default, en added) and language menu - Korean-only Notes (no English menu or pages)
- "Korean only" notice for untranslated docs
- Existing docs translated into English (home, overviews, About, Docusaurus Setup, Troubleshooting)
- Category descriptions translated (
current.json) - English sitemap added to robots.txt
- Both languages build successfully
- English sitemap submitted to Search Console and Search Advisor