425 lines
9.2 KiB
Markdown
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)
|