Skip to main content

09. Multilingual Support (Korean · English)

1. Overview​

Documents are provided in Korean and English, and the language can be selected from the top menu.

ApproachPros and cons
Korean and English on the same pageNo 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:

AreaKoreanEnglish
IT · Security · AI docsYesYes
AboutYesYes
NotesYesNo (Korean only)

2. Structure​

ItemKorean (default)English
URLwiki.securenomad.kr/... (unchanged)wiki.securenomad.kr/en/...
Docsdocs/i18n/en/docusaurus-plugin-content-docs/current/
Pages (About)src/pages/i18n/en/docusaurus-plugin-content-pages/
Category labels_category_.jsoni18n/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​

docusaurus.config.js
i18n: {
defaultLocale: 'ko',
locales: ['ko', 'en'],
localeConfigs: {
ko: {label: '한국어', htmlLang: 'ko-KR'},
en: {label: 'English', htmlLang: 'en-US'},
},
},
FieldMeaning
defaultLocaleDefault language, served without a prefix (/)
localesSupported languages. Non-default languages get a prefix such as /en/
labelName shown in the language menu
htmlLangValue for <html lang> and hreflang

Add a language menu to the navbar:

docusaurus.config.js (navbar.items)
{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.

docusaurus.config.js
// 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'}] : []),
ResultKoreanEnglish
NavbarIT · Security · AI · NotesIT · Security · AI
Notes pages/notes generated/en/notes not generated
caution

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.

src/theme/DocItem/Content/index.js
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:

SituationDoc 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:

FileContentsAction
docusaurus-plugin-content-docs/current.jsonCategory names and descriptionsKept, Korean descriptions translated into English
docusaurus-theme-classic/navbar.jsonNavbar labelsDeleted (menus are already in English)
docusaurus-theme-classic/footer.jsonFooter copyrightDeleted (the year is saved as a fixed value and would not update each year)
code.jsonDefault theme strings (Next, Previous, etc.)Deleted (built-in English theme strings are used)
docusaurus-plugin-content-blog/options.jsonBlog stringsDeleted (no English Notes)
i18n/en/docusaurus-plugin-content-docs/current.json (excerpt)
"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."
}
tip

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​

LanguageSitemap
Koreanhttps://wiki.securenomad.kr/sitemap.xml
Englishhttps://wiki.securenomad.kr/en/sitemap.xml

Added the English sitemap to static/robots.txt:

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​

CommandBehavior
npm startPreview the Korean site only
npm start -- --locale enPreview the English site only
npm run buildBuild both Korean (build/) and English (build/en/)
info

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​

  1. Write the Korean doc: docs/security/xss.md
  2. Write the English doc at the same path with the same name: i18n/en/docusaurus-plugin-content-docs/current/security/xss.md
  3. If you added a new category (_category_.json), run npm run write-translations -- --locale en and translate the Korean description in current.json
  4. Run npm run build to check both languages → Commit → Push
tip

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​

  • i18n configuration (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