EN | ES

Internationalization (i18n)

Wordless handles multiple languages through a clean, directory-based approach. Each language lives in its own folder, and content automatically inherits language metadata.

Directory Structure

content/
  en/
    index.php              → /en
    about.php              → /en/about
    blog/
      index.php            → /en/blog
      post.php             → /en/blog/post

  es/
    index.php              → /es
    about.php              → /es/about
    blog/
      index.php            → /es/blog

Language Inheritance

Each language folder's index.php declares metadata inherited by all child pages:

<?php $meta = [
    'lang'   => 'es',
    'locale' => 'es-ES',
    'dir'    => 'ltr',
]; ?>

<h1>Español</h1>

Every page under /es/ automatically inherits lang: 'es', but can override other metadata:

<?php $meta = [
    'title' => 'Acerca de',
    'date'  => '2026-05-05',
]; ?>

<h1>Acerca de Wordless</h1>

Using Locale Data in Templates

Access language information in your templates:

<html lang="<?= e($content->get('locale', 'en-US')) ?>">

<?php if ($content->get('language') === 'ar'): ?>
    <!-- Right-to-left styles for Arabic -->
<?php endif; ?>

Querying Language-Specific Content

Fetch all content in a specific language:

$repo = $container->get(ContentRepositoryInterface::class);

// All Spanish pages
$spanish = $repo->all('es');

// All English blog posts
$englishBlog = $repo->all('en/blog');

foreach ($spanish as $page) {
    echo $page->get('lang'); // 'es'
}

Language Switching Navigation

Build language switchers by deriving alternate URLs:

<?php
// Peers are resolved at runtime from content structure
// and passed into $pageMeta['peers'] by ContentController
foreach ($pageMeta['peers'] as $lang => $path):
    $isActive = $lang === $pageMeta['lang'];
?>
    <a href="<?= e($path) ?>" class="<?= $isActive ? 'active' : '' ?>">
        <?= e(strtoupper($lang)) ?>
    </a>
<?php endforeach; ?>

SEO Considerations

Language Meta Tag

Always set the lang attribute on the <html> element:

<html lang="<?= e($content->get('locale')) ?>">

Alternate Links

Help search engines understand language variations with hreflang links:

<link rel="alternate" hreflang="es" href="/es/acerca" />
<link rel="alternate" hreflang="en" href="/en/about" />
<link rel="alternate" hreflang="x-default" href="/en/about" />

Sitemaps

The automatic sitemap includes all language variants, helping search engines discover them all.

Scaling to More Languages

Adding a new language is as simple as creating a new folder:

mkdir content/fr
echo '<?php $meta = ["lang" => "fr", "locale" => "fr-FR"]; ?>' > content/fr/index.php

All French content now inherits the language metadata automatically.

Best Practices

  • Use BCP 47 locale codes: en-US, es-ES, fr-FR
  • Organize by language first: Makes permissions and deployment easier
  • Translate slugs naturally: /en/about pairs with /es/acerca — use the peers key in $meta to declare the link explicitly
  • Translate all navigation: Link switchers and menus for every language
  • Maintain parity: Keep content across languages reasonably synchronized

← Back to Features