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

9.2 KiB

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:

[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/:

mkdir -p custom/languages

English (custom/languages/en.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):

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

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

# About Us

We are a company dedicated to simplicity.

content/about/index.no.md (Norwegian):

# Om Oss

Vi er et selskap dedikert til enkelhet.

content/about/index.fr.md (French):

# À 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:

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

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

menu = true
menu_order = 1

title = "Blog"
[no]
title = "Blogg"
[fr]
title = "Blog"

content/about/metadata.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:

<a href="<?= $ctx->langPrefix ?>/">
    <?= $translations['home'] ?>
</a>

<p><?= $translations['footer_text'] ?></p>

In Custom Templates

Access translations the same way:

<button><?= $translations['read_more'] ?></button>

Access Current Language

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

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

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

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

<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