Multi-language Support

Add multiple language support to your blog

jet-w
8分钟

Multi-language Support

Add support for multiple languages to reach a global audience.

Overview

The i18n system provides:

  • Multiple languages - Support any number of languages
  • URL prefixes - /en/posts, /zh-CN/posts
  • Per-language config - Different menus, titles, etc.
  • Language switcher - Built-in UI component
  • SEO - Proper hreflang tags

Basic Setup

1. Configure Languages

In astro.config.mjs:

import { astroBlog, defineI18nConfig } from '@jet-w/astro-blog';
import { enConfig } from './src/config/locales/en';
import { zhCNConfig } from './src/config/locales/zh-CN';

const i18nConfig = defineI18nConfig({
  defaultLocale: 'en',
  locales: [
    { code: 'en', name: 'English', htmlLang: 'en', dateLocale: 'en-US' },
    { code: 'zh-CN', name: '中文', htmlLang: 'zh-CN', dateLocale: 'zh-CN' },
  ],
  routing: {
    prefixDefaultLocale: false,
  },
  localeConfigs: {
    'en': enConfig,
    'zh-CN': zhCNConfig,
  },
});

export default defineConfig({
  integrations: [astroBlog({ i18n: i18nConfig })],
});

2. Create Locale Configs

Create folders for each language:

src/config/locales/
├── en/
│   ├── index.ts
│   ├── site.ts
│   ├── menu.ts
│   ├── footer.ts
│   ├── sidebar.ts
│   └── ui.ts        # UI translations
└── zh-CN/
    ├── index.ts
    ├── site.ts
    ├── menu.ts
    ├── footer.ts
    ├── sidebar.ts
    └── ui.ts        # UI translations

3. Configure Each Language

English (en/index.ts):

import { site } from './site';
import { menu } from './menu';
import { footer } from './footer';
import { sidebar } from './sidebar';
import { ui } from './ui';

export const enConfig = {
  site,
  menu,
  footer,
  sidebar,
  ui,
};

English site (en/site.ts):

export const site = {
  title: 'My Blog',
  description: 'A blog about technology',
};

English menu (en/menu.ts):

export const menu = [
  { name: 'Home', href: '/', icon: 'home' },
  { name: 'Posts', href: '/posts', icon: 'posts' },
  { name: 'About', href: '/about', icon: 'about' },
];

URL Structure

With prefixDefaultLocale: false:

LanguageURL
English (default)/posts
Chinese/zh-CN/posts

With prefixDefaultLocale: true:

LanguageURL
English/en/posts
Chinese/zh-CN/posts

Content Organization

Option 1: Separate Folders

Create separate content folders per language:

content/posts/
├── blog_docs_en/    # English docs
│   └── ...
└── blog_docs/       # Chinese docs
    └── ...

Option 2: Same Content

Use the same content with language-aware frontmatter:

---
title: Hello World
lang: en
---

Language Switcher

The language switcher appears in the header automatically when multiple languages are configured.

It shows:

  • Current language flag/name
  • Dropdown with all languages
  • Links to the same page in other languages

Locale Configuration Options

Locale Definition

{
  code: 'en',           // URL prefix
  name: 'English',      // Display name
  htmlLang: 'en',       // HTML lang attribute
  dateLocale: 'en-US',  // Date formatting
  direction: 'ltr',     // Text direction (ltr/rtl)
}

Per-Locale Config

Each locale can override:

ConfigPurpose
siteTitle, description, author
menuNavigation links
footerFooter links and text
sidebarDocument tree groups
uiUI translation strings

UI Translations

The blog includes built-in translations for 50+ UI strings in English and Chinese. You can override any string in your locale’s ui.ts file.

Creating UI Translations

English (en/ui.ts):

import type { UITranslations } from '@jet-w/astro-blog';

export const ui: Partial<UITranslations> = {
  // Override any UI strings here
  browsePosts: 'Browse Posts',
  aboutMe: 'About Me',
  searchPlaceholder: 'Search articles...',
  readMore: 'Continue reading',
};

Available UI Keys

CategoryKeys
Navigationhome, blog, about, search
Postsposts, postList, noPostsFound, readMore, readingTime, minuteRead
Tags & Categoriestags, categories, allTags, allCategories, taggedWith, inCategory
Archivesarchives, postsInArchive
SidebarrecentPosts, popularTags, friendLinks, documentTree
FooterquickLinks, contact
SearchsearchPlaceholder, searchResults, noResults, searching, searchArticles, searchTips, etc.
HerobrowsePosts, aboutMe
PaginationpreviousPage, nextPage, page, of
ArticlepublishedOn, updatedOn, author, tableOfContents, relatedPosts, sharePost
MiscbackToTop, copyCode, copied, expand, collapse, viewMode, sortBy, draft
Slidesslides, slidesList
RSSrssFeed
Tip

You only need to override strings you want to customize. The library provides sensible defaults for all strings.

Adding a New Language

  1. Add to locales array:
locales: [
  { code: 'en', name: 'English', htmlLang: 'en', dateLocale: 'en-US' },
  { code: 'zh-CN', name: '中文', htmlLang: 'zh-CN', dateLocale: 'zh-CN' },
  { code: 'ja', name: '日本語', htmlLang: 'ja', dateLocale: 'ja-JP' },  // New
],
  1. Create config folder:
src/config/locales/ja/
├── index.ts
├── site.ts
├── menu.ts
├── footer.ts
├── sidebar.ts
└── ui.ts          # UI translations
  1. Create ui.ts with translations:
import type { UITranslations } from '@jet-w/astro-blog';

export const ui: Partial<UITranslations> = {
  // Navigation
  home: 'ホーム',
  posts: '記事',
  tags: 'タグ',
  categories: 'カテゴリー',
  search: '検索',

  // Hero section
  browsePosts: '記事を見る',
  aboutMe: '私について',

  // Search
  searchPlaceholder: '記事を検索...',
  noResults: '結果が見つかりません',

  // Add more translations as needed...
};
  1. Create index.ts:
import { site } from './site';
import { menu } from './menu';
import { footer } from './footer';
import { sidebar } from './sidebar';
import { ui } from './ui';

export const jaConfig = {
  site,
  menu,
  footer,
  sidebar,
  ui,
  contentPathPrefix: 'blog_docs_ja',
};
  1. Add to localeConfigs in astro.config.mjs:
import { jaConfig } from './src/config/locales/ja';

localeConfigs: {
  'en': enConfig,
  'zh-CN': zhCNConfig,
  'ja': jaConfig,  // New
},

RTL Languages

For right-to-left languages (Arabic, Hebrew):

{
  code: 'ar',
  name: 'العربية',
  htmlLang: 'ar',
  dateLocale: 'ar-SA',
  direction: 'rtl',  // Enable RTL
}

Best Practices

tip i18n Tips

  1. Start with two languages - Don’t add more until needed
  2. Keep content in sync - Update all translations together
  3. Use consistent URLs - Match URL structure across languages
  4. Test thoroughly - Check all pages in all languages
Path Consistency

When changing defaultLocale, update all menu and footer links to match the new URL structure.


Congratulations! You’ve completed the configuration guide.

Return to the Documentation Home for more topics.