Files
chill_hugoplate/themes/chill-theme/docs/INTERNATIONALIZATION.md
2026-01-28 09:40:37 +01:00

738 lines
22 KiB
Markdown

# Internationalization (i18n) Guide
Complete guide for implementing multilingual support in Hugo Saasify Theme.
## Table of Contents
- [Overview](#overview)
- [Quick Start](#quick-start)
- [Configuration](#configuration)
- [Language Preference Persistence](#language-preference-persistence)
- [Translation Keys](#translation-keys)
- [Content Organization](#content-organization)
- [Date Formatting](#date-formatting)
- [Common Issues](#common-issues)
- [Adding a New Language](#adding-a-new-language)
## Overview
Hugo Saasify Theme provides full multilingual support with:
- Automatic language switcher in the header
- Persistent language preference (remembers user's choice)
- 40+ translation keys for UI elements
- Language-specific content directories
- Language-specific menus and parameters
- Date format localization
- Translated blog sidebar, post navigation, and more
## Quick Start
The theme comes with **three fully implemented language examples** in the demo site:
- **English** (`en`) - Default language
- **Chinese** (`zh-cn`) - Complete translation
- **German** (`de`) - Complete translation
You can use these as references when adding your own languages.
### Basic Setup
1. **Configure languages in `hugo.toml`:**
```toml
defaultContentLanguage = "en"
[languages]
[languages.en]
languageCode = "en-us"
languageName = "English"
title = "Your Site"
weight = 1
contentDir = "content"
[languages.zh-cn]
languageCode = "zh-cn"
languageName = "简体中文"
title = "Your Site"
weight = 2
contentDir = "content/zh-cn"
```
**Tip:** Check the demo site's `hugo.toml` for complete English, Chinese, and German configurations you can copy and adapt.
2. **Copy translation files:**
```bash
# Copy English translations (already included in theme)
cp themes/chill-theme/i18n/en.toml i18n/en.toml
# Copy Chinese example and rename
cp themes/chill-theme/docs/zh-cn.toml.example i18n/zh-cn.toml
```
3. **Create language-specific content:**
```
content/
├── _index.md # English homepage
├── blog/
│ └── my-post.md # English blog post
└── zh-cn/
├── _index.md # Chinese homepage
└── blog/
└── my-post.md # Chinese blog post
```
4. **Configure language-specific menus:**
```toml
# English menu
[[languages.en.menu.main]]
name = "Blog"
url = "/blog"
weight = 1
# Chinese menu
[[languages.zh-cn.menu.main]]
name = "博客"
url = "/zh-cn/blog"
weight = 1
```
The language switcher will automatically appear in the header!
## Configuration
### Language Preference Persistence
**New Feature:** The theme now automatically remembers the user's language preference using browser localStorage. This provides a seamless multilingual experience.
#### How It Works
1. **First Visit**: User sees the default language (no redirect)
2. **User Selects Language**: When clicking a language in the switcher:
- Their preference is saved to localStorage
- They navigate to the selected language
3. **Return Visits**: On subsequent page loads:
- The system checks for a saved preference
- If found, automatically redirects to their preferred language
- This happens once per session to avoid interfering with manual navigation
#### User Experience Examples
**Scenario 1: New User**
```
1. User lands on https://example.com/ (English, default)
2. Clicks "简体中文" in language switcher
3. Navigates to https://example.com/zh-cn/
4. Preference saved: zh-cn
5. Next visit: automatically redirects to Chinese version
```
**Scenario 2: Deep Links**
```
1. User has preference: zh-cn
2. Clicks link to https://example.com/blog/my-post
3. Auto-redirects to https://example.com/zh-cn/blog/my-post
4. Original path preserved, just language changed
```
**Scenario 3: Manual Navigation**
```
1. User has preference: zh-cn (currently on Chinese site)
2. Types https://example.com/en/contact directly in address bar
3. Sees English contact page (no forced redirect)
4. Can still use language switcher to change languages
```
#### Technical Details
- **Storage**: Uses browser `localStorage` with key `hugo_preferred_language`
- **Fallback**: Works gracefully without JavaScript (standard links still function)
- **Privacy**: Stored locally in user's browser only
- **Session Safety**: Uses `sessionStorage` flag to prevent redirect loops
- **Progressive Enhancement**: Site works perfectly with JavaScript disabled
#### Disabling the Feature
If you want to disable language preference persistence:
1. Remove the script include from `layouts/_default/baseof.html`:
```html
<!-- Remove or comment out these lines: -->
{{ if .Site.IsMultiLingual }}
<script src="{{ "js/language-preference.js" | relURL }}"></script>
{{ end }}
```
2. Or delete the file: `static/js/language-preference.js`
The language switcher will still work; it just won't remember the user's choice.
#### Automatic Language Detection with VisitorAPI
**New Feature:** The theme supports automatic language detection using [VisitorAPI](https://visitorapi.com/) to detect the visitor's language based on their location.
##### How It Works
1. **Every Page Load (No Stored Preference)**: If the user has no stored language preference:
- The system calls VisitorAPI on every page load to detect the visitor's language
- If a matching language is available, the user is automatically redirected
- The detected language is stored as their preference for future visits
2. **After Preference is Stored**: Once a language preference is saved (either from auto-detection or manual selection):
- Uses the stored preference (no API call)
- Fast redirect based on localStorage
3. **Manual Selection**: User can always override by selecting a different language
##### Setup Instructions
1. **Sign up for VisitorAPI**:
- Visit [visitorapi.com](https://visitorapi.com/) and create a free account
- Create a new project to get your Project ID
2. **Add the Project ID to your configuration**:
```toml
[params]
# VisitorAPI Project ID for automatic language detection
# Set this to enable auto-detection of visitor's language
# If not set, language detection will be disabled
visitorapi_pid = "YOUR_PROJECT_ID_HERE"
```
3. **That's it!** The feature is automatically enabled when the Project ID is configured.
##### How Language Matching Works
The system uses **dynamic language matching** based on the first two characters of language codes. This means it automatically works for any language you add to Hugo without requiring any code changes.
**Matching Logic:**
- VisitorAPI returns language codes like `"en"`, `"zh"`, `"de"`, `"fr"`, `"es"`, `"pt"`, etc.
- The system extracts the first 2 characters from the VisitorAPI response
- It compares these with the first 2 characters of your Hugo language codes
- When they match, the user is redirected to that language
**Examples:**
- VisitorAPI returns `"en"` → Matches Hugo language `"en"`
- VisitorAPI returns `"zh"` or `"zho"` → Matches Hugo language `"zh-cn"` ✓ (both start with "zh")
- VisitorAPI returns `"de"` or `"deu"` → Matches Hugo language `"de"`
- VisitorAPI returns `"pt"` or `"por"` → Matches Hugo language `"pt"` or `"pt-br"`
- VisitorAPI returns `"es"` or `"spa"` → Matches Hugo language `"es"`
- VisitorAPI returns `"fr"` or `"fra"` → Matches Hugo language `"fr"` or `"fr-ca"`
**Adding a New Language:**
When you add a new language to your Hugo site, VisitorAPI auto-detection will automatically work for it:
1. Add the language to your `hugo.toml`:
```toml
[languages.ja]
languageCode = "ja-jp"
languageName = "日本語"
# ... rest of config
```
2. That's it! The system will automatically detect visitors from Japan and redirect them to `/ja/` because:
- VisitorAPI will return `"ja"` or `"jpn"` for Japanese visitors
- The first 2 characters (`"ja"`) match your Hugo language code `"ja"`
- No code changes needed
**Technical Details:**
The matching happens in `language-preference.js`:
```javascript
// Extract first 2 characters from VisitorAPI response (e.g., "zh" from "zh" or "zho")
var langPrefix = visitorLang.substring(0, 2);
// Get all available Hugo languages from the language switcher
var languageLinks = document.querySelectorAll('.language-switch-link');
// Compare first 2 characters with each Hugo language
for (var j = 0; j < languageLinks.length; j++) {
var hugoLang = languageLinks[j].getAttribute('data-lang');
var hugoPrefix = hugoLang.substring(0, 2);
if (langPrefix === hugoPrefix) {
// Match found! Redirect to this language
detectedLang = hugoLang;
break;
}
}
```
This approach ensures:
- No hardcoded language mappings to maintain
- Automatic support for any language you add
- Works with both 2-character codes (`"en"`) and extended codes (`"zh-cn"`)
- Handles VisitorAPI's various language code formats (ISO 639-1 and ISO 639-2)
##### Privacy & Performance
- **API Call**: Called on every page load when no language preference is stored
- **Caching**: Once a language is detected and stored, no more API calls are made
- **Efficient**: After first detection, uses localStorage for instant redirects
- **Fallback**: Works gracefully if API is unavailable
- **No Tracking**: VisitorAPI detects language, doesn't track user behavior
- **User Control**: Users can clear localStorage to reset and re-detect
##### Disabling Auto-Detection
To disable automatic language detection while keeping manual preference persistence:
1. Simply remove or comment out the `visitorapi_pid` parameter from your `hugo.toml`
2. Or set it to an empty string: `visitorapi_pid = ""`
The language preference system will continue to work; it just won't auto-detect on first visit.
### Basic Setup
```toml
# Set the default language
defaultContentLanguage = "en"
# Whether to include default language in URL path
# false: English at / (example.com/)
# true: English at /en/ (example.com/en/)
defaultContentLanguageInSubdir = false
```
### Language-Specific Parameters
Each language can have its own configuration for CTAs, headers, etc:
```toml
[languages.en.params.cta]
enable = true
title = "Ready to Get Started?"
description = "Join companies using our platform"
[languages.en.params.cta.primary_button]
text = "Get Started Free"
url = "/get-started"
[languages.zh-cn.params.cta]
enable = true
title = "准备好开始了吗?"
description = "加入使用我们平台的公司"
[languages.zh-cn.params.cta.primary_button]
text = "免费开始"
url = "/zh-cn/get-started"
```
### Sidebar Configuration
**Important:** Do NOT set title/description/text in global sidebar config, as this overrides i18n translations:
```toml
# ❌ DON'T: This prevents translation
[params.blog.sidebar.recent]
enable = true
title = "Recent Articles" # This overrides i18n
# ✅ DO: Let i18n handle the translations
[params.blog.sidebar.recent]
enable = true
count = 5 # Only set non-text configuration
```
## Translation Keys
### Complete List
The theme includes 40+ translation keys. Here are the most important ones:
#### Blog & Content
- `readMore` - "Read More" link text
- `readTime` - Reading time suffix
- `categories` - Categories heading
- `tags` - Tags heading
#### Blog Post
- `tableOfContents` - TOC heading
- `minRead` - Reading time text
- `previousPost` - Previous navigation
- `nextPost` - Next navigation
- `dateFormat` - Date format string
#### Blog Sidebar
- `recentArticles` - Recent articles heading
- `popularTags` - Popular tags heading
- `subscribeNewsletter` - Newsletter heading
- `subscribeDescription` - Newsletter description
- `emailPlaceholder` - Email input placeholder
- `subscribe` - Subscribe button
#### Navigation
- `previous` - Previous page
- `next` - Next page
- `documentation` - Documentation heading
- `language` - Language label
#### Features & Pricing
- `seeItInAction` - Feature demo heading
- `popular` - Popular badge
- `mostPopular` - Most popular badge
- `perMonth` - Pricing suffix
#### Homepage
- `trustedByCompanies` - Company logos heading
- `lovedByTeams` - Testimonials heading
For the complete list with default values, see `themes/chill-theme/i18n/en.toml`
### Example Translation File
**i18n/en.toml:**
```toml
[readMore]
other = "Read More"
[minRead]
other = "min read"
[previousPost]
other = "Previous Post"
[nextPost]
other = "Next Post"
[dateFormat]
other = "January 2, 2006"
```
**i18n/zh-cn.toml:**
```toml
[readMore]
other = "阅读更多"
[minRead]
other = "分钟阅读"
[previousPost]
other = "上一篇"
[nextPost]
other = "下一篇"
[dateFormat]
other = "2006年1月2日"
```
## Content Organization
### Directory Structure
```
content/
├── _index.md # English homepage
├── blog/
│ ├── post-1.md # English blog posts
│ └── post-2.md
├── features/
│ └── performance.md # English feature page
└── zh-cn/ # Chinese content
├── _index.md # Chinese homepage
├── blog/
│ ├── post-1.md # Chinese blog posts
│ └── post-2.md
└── features/
└── performance.md # Chinese feature page
```
### Creating Translated Content
1. **Duplicate the structure** from your default language
2. **Translate the content** including front matter
3. **Keep the same file names** for better URL consistency
**Example - English post:**
```yaml
---
title: "Getting Started with Hugo"
date: 2024-01-15
categories: ["Tutorial"]
tags: ["hugo", "web development"]
---
Content in English...
```
**Example - Chinese post:**
```yaml
---
title: "Hugo入门指南"
date: 2024-01-15
categories: ["教程"]
tags: ["hugo", "网站开发"]
---
中文内容...
```
## Date Formatting
The theme uses the `dateFormat` i18n key for locale-specific date formatting:
```toml
# English format
[dateFormat]
other = "January 2, 2006" # Output: July 20, 2023
# Chinese format
[dateFormat]
other = "2006年1月2日" # Output: 2023年7月20日
# French format example
[dateFormat]
other = "2 January 2006" # Output: 20 juillet 2023
```
Hugo's date format reference:
- `2006` - Year (4 digits)
- `06` - Year (2 digits)
- `January` - Month (full name)
- `Jan` - Month (abbreviated)
- `01` - Month (2 digits)
- `1` - Month (1-2 digits)
- `02` - Day (2 digits)
- `2` - Day (1-2 digits)
- `Monday` - Weekday (full name)
- `Mon` - Weekday (abbreviated)
## Common Issues
### Issue: Sidebar Still Showing English
**Problem:** Blog sidebar shows English text even in Chinese version.
**Solution:** Remove hardcoded titles from global `[params.blog.sidebar]` configuration. The theme's i18n will handle the translations automatically.
```toml
# Remove these from global config:
[params.blog.sidebar.recent]
enable = true
# title = "Recent Articles" ← Remove this
count = 5
```
### Issue: Language Switcher Not Appearing
**Problem:** Language switcher doesn't show in header.
**Solution:** Ensure you have defined multiple languages in your `hugo.toml`:
```toml
[languages]
[languages.en]
# ...
[languages.zh-cn]
# ...
```
The language switcher automatically appears when 2+ languages are configured.
### Issue: Wrong Language on Home Link
**Problem:** Logo/home link goes to wrong language homepage.
**Solution:** This is already fixed in the theme. The header uses `{{ "/" | relLangURL }}` which automatically links to the correct language homepage.
## Adding a New Language
Let's add French as an example.
### Quick Reference (For Experienced Users)
If you're familiar with Hugo i18n, here's what you need:
1. **Config:** `hugo.toml` - Add `languages.fr` section with `languageCode`, `languageName`, `title`, `weight`, `contentDir`
2. **Translations:** `i18n/fr.toml` - Copy from `i18n/en.toml` and translate all keys
3. **Content:** `content/fr/` - Create directory structure and translate content
4. **Menus:** `languages.fr.menu.main` + 3 footer menus
5. **Params:** `languages.fr.params` - **Must include**: `cta`, `footer` (with uppercase column titles), `header`, `blog`
**Critical:** Don't forget the `footer` configuration - it's required for footer to display correctly!
### Detailed Step-by-Step Guide
### 1. Add Language Configuration
```toml
[languages.fr]
languageCode = "fr-fr"
languageName = "Français"
title = "Votre Site"
weight = 3
contentDir = "content/fr"
```
### 2. Create Translation File
Copy the example and translate:
```bash
cp themes/chill-theme/docs/zh-cn.toml.example i18n/fr.toml
```
Edit `i18n/fr.toml`:
```toml
[readMore]
other = "Lire la suite"
[minRead]
other = "min de lecture"
[previousPost]
other = "Article précédent"
[nextPost]
other = "Article suivant"
[dateFormat]
other = "2 January 2006"
[recentArticles]
other = "Articles récents"
[categories]
other = "Catégories"
[popularTags]
other = "Tags populaires"
# ... translate all keys
```
### 3. Create French Content
```
content/fr/
├── _index.md
├── blog/
└── features/
```
### 4. Configure French Menu
```toml
[[languages.fr.menu.main]]
name = "Fonctionnalités"
url = "/fr/features"
weight = 1
[[languages.fr.menu.main]]
name = "Blog"
url = "/fr/blog"
weight = 2
```
### 5. Language-Specific Parameters (Required)
Configure language-specific parameters for your new language. **Important:** These parameters are required for proper navigation and UI display.
```toml
# French language specific params
[languages.fr.params]
# Global CTA Configuration
[languages.fr.params.cta]
enable = true
title = "Prêt à commencer?"
description = "Rejoignez les entreprises qui utilisent notre plateforme"
gradient_from = "#2563eb"
gradient_to = "#7c3aed"
gradient_angle = 30
[languages.fr.params.cta.primary_button]
text = "Commencer"
url = "/fr/get-started"
[languages.fr.params.cta.secondary_button]
text = "Démo"
url = "/fr/demo"
# Footer Configuration (IMPORTANT - Required for footer to display correctly)
[languages.fr.params.footer]
column_1_title = "FONCTIONNALITÉS"
column_2_title = "ENTREPRISE"
column_3_title = "LÉGAL"
# Header Configuration
[languages.fr.params.header]
[languages.fr.params.header.buttons]
[languages.fr.params.header.buttons.signIn]
text = "Connexion"
url = "/fr/signin"
[languages.fr.params.header.buttons.getStarted]
text = "Commencer"
url = "/fr/get-started"
# Blog configuration
[languages.fr.params.blog]
enable = true
title = "Derniers Articles"
subtitle = "Apprenez-en plus sur le développement web et les meilleures pratiques"
```
**Important Notes:**
- **Footer Configuration:** The `footer` section is required. Without it, footer column titles may not display correctly in the new language.
- **Use uppercase** for footer column titles to maintain consistency with other languages
- **URL paths:** Include the language prefix (e.g., `/fr/`) in all button URLs
- All parameters should mirror the structure used in other languages (check English or Chinese examples)
### 6. Complete Checklist
Use this checklist to ensure you've configured everything correctly:
- [ ] **Language Config:** Added language configuration in `hugo.toml` with `languageCode`, `languageName`, `title`, `weight`, and `contentDir`
- [ ] **Translation File:** Created `i18n/[lang].toml` with all UI translations (50+ keys)
- [ ] **Content Directory:** Created `content/[lang]/` directory structure
- [ ] **Homepage:** Created `content/[lang]/_index.md` with translated homepage content
- [ ] **Navigation Menus:** Configured `languages.[lang].menu.main`, `footer_column_1`, `footer_column_2`, `footer_column_3`
- [ ] **Language Params - CTA:** Added `languages.[lang].params.cta` configuration
- [ ] **Language Params - Footer:** Added `languages.[lang].params.footer` with all three column titles (UPPERCASE)
- [ ] **Language Params - Header:** Added `languages.[lang].params.header.buttons` configuration
- [ ] **Language Params - Blog:** Added `languages.[lang].params.blog` configuration
- [ ] **URL Prefixes:** All menu URLs include the language prefix (e.g., `/fr/features`)
- [ ] **Test:** Verified language switcher appears in header with new language
- [ ] **Test:** Verified homepage displays correctly in new language
- [ ] **Test:** Verified top navigation menu shows translated items
- [ ] **Test:** Verified footer navigation shows translated items
- [ ] **Test:** Verified footer column titles display in new language
### Common Mistakes to Avoid
1. **Missing Footer Config:** Forgetting to add `languages.[lang].params.footer` will cause footer titles to not display
2. **Inconsistent Capitalization:** Footer column titles should be UPPERCASE to match the theme's style
3. **Missing URL Prefixes:** All language-specific URLs must include the language code (e.g., `/fr/`, not just `/`)
4. **Incomplete Menus:** Ensure all three footer menus (`footer_column_1`, `footer_column_2`, `footer_column_3`) are configured
5. **Missing i18n Keys:** Make sure all 50+ translation keys are present in your translation file
## Best Practices
1. **Consistent Structure:** Mirror your content directory structure across all languages
2. **Translation Keys:** Use descriptive i18n keys like `readMore` instead of abbreviations
3. **Avoid Hardcoding:** Don't set text parameters in global config; let i18n handle it
4. **Test Switching:** Verify language switching works correctly on all pages
5. **Date Formats:** Define appropriate date formats for each language
6. **Menus:** Configure language-specific menus with translated labels
7. **URLs:** Include language code in URLs for non-default languages
## Resources
- **Hugo i18n Documentation:** https://gohugo.io/content-management/multilingual/
- **Theme i18n Files:** `themes/chill-theme/i18n/`
- **Demo Site Configuration:** See complete trilingual setup in demo site's `hugo.toml`
- **Translation Examples:**
- English: `i18n/en.toml` (reference for all keys)
- Chinese: `i18n/zh-cn.toml` (complete translation)
- German: `i18n/de.toml` (complete translation)
- **Content Structure Examples:**
- `content/` (English)
- `content/zh-cn/` (Chinese)
- `content/de/` (German)
## Support
For issues or questions about i18n:
1. Check [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
2. Review the demo site's working multilingual setup
3. Open an issue on GitHub with details about your configuration