folderweb/docs/how-to/multi-language.md
2025-11-02 13:46:47 +01:00

425 lines
9.2 KiB
Markdown

# How to Create a Multi-Language Site
This guide shows you how to set up and manage a multi-language website with FolderWeb.
## Overview
FolderWeb supports multiple languages through:
- Language prefixes in URLs
- Language-specific content files
- Translated slugs and metadata
- Translation files for UI strings
## Configuration
### Step 1: Configure Available Languages
Create or edit `custom/config.ini`:
```ini
[languages]
default = "en"
available = "en,no,fr"
```
- **default**: The primary language (no URL prefix)
- **available**: Comma-separated list of all supported languages
### Step 2: Create Translation Files
Create translation files for each language in `custom/languages/`:
```bash
mkdir -p custom/languages
```
**English** (`custom/languages/en.ini`):
```ini
home = "Home"
read_more = "Read more"
categories = "Categories"
tags = "Tags"
footer_text = "Made with FolderWeb"
footer_handcoded = "Generated in"
footer_page_time = "ms"
```
**Norwegian** (`custom/languages/no.ini`):
```ini
home = "Hjem"
read_more = "Les mer"
categories = "Kategorier"
tags = "Stikkord"
footer_text = "Laget med FolderWeb"
footer_handcoded = "Generert på"
footer_page_time = "ms"
```
**French** (`custom/languages/fr.ini`):
```ini
home = "Accueil"
read_more = "Lire la suite"
categories = "Catégories"
tags = "Étiquettes"
footer_text = "Créé avec FolderWeb"
footer_handcoded = "Généré en"
footer_page_time = "ms"
```
## URL Structure
With the configuration above:
- **English** (default): `yoursite.com/about/`
- **Norwegian**: `yoursite.com/no/about/`
- **French**: `yoursite.com/fr/about/`
The default language never has a URL prefix.
## Creating Language-Specific Content
### Method 1: Separate Files Per Language
Use language suffixes in filenames: `filename.{lang}.ext`
**Example structure**:
```
content/about/
├── index.md # Default language (English)
├── index.no.md # Norwegian version
└── index.fr.md # French version
```
**Rules**:
- Language-specific files (`.lang.ext`) show only for that language
- Default files (no language suffix) show only if no language variant exists
- Files are automatically filtered based on current language
### Example Content
**content/about/index.md** (English):
```markdown
# About Us
We are a company dedicated to simplicity.
```
**content/about/index.no.md** (Norwegian):
```markdown
# Om Oss
Vi er et selskap dedikert til enkelhet.
```
**content/about/index.fr.md** (French):
```markdown
# À Propos
Nous sommes une entreprise dédiée à la simplicité.
```
Now when users visit:
- `/about/` → Shows English (index.md)
- `/no/about/` → Shows Norwegian (index.no.md)
- `/fr/about/` → Shows French (index.fr.md)
### Method 2: Language-Specific Folders
For blog posts and articles, you can create separate folders:
```
content/blog/
├── 2025-11-01-english-post/
│ └── index.md
├── 2025-11-01-norsk-innlegg/
│ └── index.no.md
└── 2025-11-01-article-francais/
└── index.fr.md
```
## Translated Slugs and Titles
Use `metadata.ini` to provide translated slugs and metadata:
**content/about/metadata.ini**:
```ini
; Default (English)
title = "About Us"
slug = "about"
[no]
title = "Om Oss"
slug = "om-oss"
[fr]
title = "À Propos"
slug = "a-propos"
```
Now URLs become:
- English: `/about/`
- Norwegian: `/no/om-oss/`
- French: `/fr/a-propos/`
The actual folder is still named `about/`, but FolderWeb maps the translated slug to the real folder.
## Blog Posts with Translations
**Structure**:
```
content/blog/
└── 2025-11-02-my-post/
├── index.md
├── index.no.md
├── index.fr.md
├── cover.jpg
└── metadata.ini
```
**metadata.ini**:
```ini
; Default language
title = "My First Post"
summary = "An introduction to multilingual blogging."
date = "2025-11-02"
[no]
title = "Mitt Første Innlegg"
summary = "En introduksjon til flerspråklig blogging."
[fr]
title = "Mon Premier Article"
summary = "Une introduction au blogging multilingue."
```
**Important**: Date is global, cover image is shared across languages.
## Navigation and Menus
Navigation is automatically built with translations. In `metadata.ini` for each top-level directory:
**content/blog/metadata.ini**:
```ini
menu = true
menu_order = 1
title = "Blog"
[no]
title = "Blogg"
[fr]
title = "Blog"
```
**content/about/metadata.ini**:
```ini
menu = true
menu_order = 2
title = "About"
[no]
title = "Om"
[fr]
title = "À Propos"
```
Navigation automatically includes language prefix in URLs.
## Using Translations in Templates
### In Default Templates
Translations are automatically available as `$translations` array:
```php
<a href="<?= $ctx->langPrefix ?>/">
<?= $translations['home'] ?>
</a>
<p><?= $translations['footer_text'] ?></p>
```
### In Custom Templates
Access translations the same way:
```php
<button><?= $translations['read_more'] ?></button>
```
### Access Current Language
```php
<?php if ($ctx->currentLang === 'no'): ?>
<p>Dette er norsk innhold.</p>
<?php else: ?>
<p>This is English content.</p>
<?php endif; ?>
```
## Language Switcher
Create a language switcher in your custom base template:
```php
<nav class="language-switcher">
<?php foreach ($ctx->availableLangs as $lang): ?>
<?php
$url = $lang === $ctx->defaultLang
? '/' . trim($ctx->requestPath, '/')
: '/' . $lang . '/' . trim($ctx->requestPath, '/');
$current = $lang === $ctx->currentLang;
?>
<a href="<?= $url ?>"
<?= $current ? 'aria-current="true"' : '' ?>
class="<?= $current ? 'active' : '' ?>">
<?= strtoupper($lang) ?>
</a>
<?php endforeach; ?>
</nav>
```
Style it:
```css
.language-switcher {
display: flex;
gap: 0.5rem;
}
.language-switcher a {
padding: 0.25rem 0.75rem;
border-radius: var(--border-radius);
text-decoration: none;
}
.language-switcher a.active {
background: var(--color-primary);
color: white;
}
```
## List Views with Multiple Languages
When displaying blog listings, FolderWeb automatically filters items by language:
```
content/blog/
├── 2025-11-01-english-article/
│ └── index.md # Shows in English
├── 2025-11-02-norsk-artikkel/
│ └── index.no.md # Shows only in Norwegian
└── 2025-11-03-universal/
├── index.md # Shows in English
├── index.no.md # Shows in Norwegian
└── index.fr.md # Shows in French
```
When viewing `/blog/`:
- Shows "english-article" and "universal"
When viewing `/no/blog/`:
- Shows "norsk-artikkel" and "universal"
When viewing `/fr/blog/`:
- Shows only "universal"
## Handling Missing Translations
### Default Fallback
If a translation is missing, FolderWeb uses the default language automatically.
### Show Different Content
You can use PHP in your content files:
```php
<?php if ($ctx->currentLang === 'en'): ?>
# Welcome
This page is only in English.
<?php else: ?>
# Under Construction
This page is not yet translated.
<?php endif; ?>
```
## SEO Considerations
### Add hreflang Tags
In your custom base template:
```php
<head>
<!-- ... other head content ... -->
<?php foreach ($ctx->availableLangs as $lang): ?>
<?php
$url = $lang === $ctx->defaultLang
? 'https://yoursite.com/' . trim($ctx->requestPath, '/')
: 'https://yoursite.com/' . $lang . '/' . trim($ctx->requestPath, '/');
?>
<link rel="alternate" hreflang="<?= $lang ?>" href="<?= $url ?>">
<?php endforeach; ?>
<link rel="alternate" hreflang="x-default"
href="https://yoursite.com/<?= trim($ctx->requestPath, '/') ?>">
</head>
```
### Language-Specific Metadata
Add language attributes:
```php
<html lang="<?= $ctx->currentLang ?>">
```
## Testing Your Multi-Language Site
1. **Visit default language**: `http://localhost:8000/about/`
2. **Visit Norwegian**: `http://localhost:8000/no/about/`
3. **Visit French**: `http://localhost:8000/fr/about/`
4. **Check navigation**: Ensure links include language prefix
5. **Test translation strings**: Verify UI text changes per language
6. **Check blog listings**: Confirm language-specific posts appear correctly
## Common Patterns
### Blog in Multiple Languages
Structure:
```
content/blog/
├── metadata.ini # List template config
└── [date]-[slug]/
├── index.{lang}.md # One file per language
├── cover.jpg # Shared assets
└── metadata.ini # Translated metadata
```
### Documentation in Multiple Languages
Structure:
```
content/docs/
├── metadata.ini # Template config
├── 00-intro.md # Default language
├── 00-intro.no.md # Norwegian
├── 01-setup.md
├── 01-setup.no.md
└── ...
```
### Mixed Content Strategy
Not everything needs translation. You can have:
- English-only blog posts (no language suffix)
- Multi-language main pages (with language suffixes)
- Shared images and assets
## Related
- [Metadata Reference](../reference/metadata.md)
- [Configuration Reference](../reference/configuration.md)
- [Template Variables Reference](../reference/templates.md)