14+ years building on WordPress / Replies in under 5 hours
Getting Started 8 min read · Updated July 2026

How to Build a Multilingual WordPress Site Without Plugins

Photo of Ajay Khandal
Ajay Khandal
WordPress Developer
How to Build a Multilingual WordPress Site Without Plugins
TL;DR

Building a multilingual WordPress site without a plugin means structuring your content around subdirectory URLs (`/fr/`, `/es/`), creating translated pages manually, and handling the technical layer yourself: translating theme strings with WordPress's built-in `load_textdomain()` / `__()` / `_e()` system, adding hreflang tags via the `wp_head` action hook (always include `x-default`), and maintaining a language switcher mapped to your translated URLs. This approach works well for sites with 2-3 languages and fewer than ~20 pages per language, maintained by a developer. For larger sites or non-technical editors, Polylang (free) automates the hreflang generation, translation linking, and URL routing without meaningfully hurting performance — the plugin-free path's maintenance cost scales quickly with page count.

Building a multilingual WordPress site without a plugin is the right call for a specific type of project: a developer-maintained site with two or three languages, limited page count, and a preference for keeping the plugin stack lean. For anything larger — many languages, frequent content updates, non-technical editors — a free plugin like Polylang will save you hours of maintenance work without meaningfully hurting performance.

This guide assumes the plugin-free scenario: you want full control, you understand the tradeoffs, and you’re comfortable editing theme files. If you’re still deciding whether to go manual or plugin-based, the last section covers that honestly.

Step 1: Choose your URL structure before you write a single page

The URL structure is the foundational decision — you can’t change it after the fact without breaking every existing URL and its SEO equity. Get this right before creating any content.

The three options:

  • Subdirectories (yoursite.com/fr/, yoursite.com/es/) — the recommended approach for most sites. One domain, link equity consolidates, easier to set up in WordPress, and Google treats each language subdirectory as a clear signal without the overhead of separate domains or DNS.
  • Subdomains (fr.yoursite.com) — Google treats these as largely separate sites. Use subdomains only if you have a technical reason (different server configs per language, for example). More DNS and redirect configuration needed.
  • Separate ccTLDs (yoursite.fr, yoursite.es) — strongest geographic signal to Google, but you’re managing separate domains, separate SSL certs, and separate link profiles. Only makes sense for enterprise-level international presence.

Recommendation: subdirectories. In WordPress, implement them using Pages with a language-prefix parent page. Create a page with slug fr as the parent, then create your translated pages as children — WordPress generates yoursite.com/fr/a-propos/ automatically. The site structure decisions that affect your multilingual setup most — domain, hosting, and page hierarchy — are covered in depth in things to decide before building a WordPress site.

Step 2: Create and organize language-specific pages

With your URL structure decided, create translated versions of each page manually. The workflow:

  1. Build the page in your primary language first (English is standard)
  2. Duplicate it — WordPress doesn’t have a built-in duplicate page function, but you can copy-paste blocks in the block editor or duplicate via phpmyadmin if you’re comfortable with that
  3. Assign it to the correct parent page (your /fr/ parent page)
  4. Set a language-native slug — a-propos for French, not about-us-fr — so the full URL reads naturally: yoursite.com/fr/a-propos/
  5. Translate all content including image alt text and button labels

Keep page slugs in the target language throughout. A French URL with English slugs (/fr/about-us/) reads as English to French users and misses the localization signal. If you’re new to WordPress page creation, how to create pages in WordPress covers the basics of the block editor and page hierarchy.

Step 3: Translate theme strings with WordPress i18n

This is what most “multilingual without plugins” guides skip entirely — and it’s where the real technical work lives. Translation plugins handle this automatically. Without one, you need to use WordPress’s built-in internationalization (i18n) system to translate your theme’s hard-coded strings: navigation labels, button text, footer copy, widget labels, form placeholders.

The setup in functions.php:

function mytheme_setup() {
    load_theme_textdomain('mytheme', get_template_directory() . '/languages');
}
add_action('after_setup_theme', 'mytheme_setup');

In your theme templates, replace every hard-coded string with a translation function call:

// Instead of:
echo 'Read More';

// Use:
echo __('Read More', 'mytheme');

// For direct output:
_e('Submit', 'mytheme');

// In HTML attributes:
?> <input placeholder="<?php esc_attr_e('Search...', 'mytheme'); ?>">

Once your templates use these functions, generate a `.pot` (Portable Object Template) file that catalogues every translatable string:

wp i18n make-pot . languages/mytheme.pot --domain=mytheme

Open the `.pot` file in Poedit, create a new translation for French (fr_FR), translate each string, and save — Poedit generates both fr_FR.po (readable source) and fr_FR.mo (compiled binary that WordPress reads). Place both files in your theme’s /languages/ directory.

WordPress picks the correct .mo file based on get_locale(), which reads from the site’s language setting. For a manual multilingual setup without a plugin, you’d set the site’s locale to match the language of the page being served — this requires a custom locale filter based on the URL, which is the point at which the manual approach becomes genuinely complex to maintain. Full details on WordPress theme i18n setup are in the custom WordPress theme build guide.

Step 4: Add hreflang tags in WordPress

Hreflang tags tell Google which language and region each page targets, and how pages in different languages relate to each other. Without them, Google may treat your translated pages as duplicate content rather than intentional language variants.

Add them via the wp_head action in functions.php. The simplest maintainable approach: a lookup array mapping page slugs to their translated URLs.

function mytheme_hreflang_tags() {
    global $post;
    if (!is_singular() || !$post) {
        return;
    }

    // Add your page translations here
    $translations = [
        'about-us' => [
            ['en', home_url('/about-us/')],
            ['fr', home_url('/fr/a-propos/')],
            ['es', home_url('/es/sobre-nosotros/')],
        ],
        'services' => [
            ['en', home_url('/services/')],
            ['fr', home_url('/fr/services/')],
        ],
        // ... continue for each translated page
    ];

    $slug = $post->post_name;

    if (isset($translations[$slug])) {
        foreach ($translations[$slug] as [$lang, $url]) {
            printf(
                '<link rel="alternate" hreflang="%s" href="%s">' . "
",
                esc_attr($lang),
                esc_url($url)
            );
        }
        // x-default: points to your primary language version
        printf(
            '<link rel="alternate" hreflang="x-default" href="%s">' . "
",
            esc_url($translations[$slug][0][1])
        );
    }
}
add_action('wp_head', 'mytheme_hreflang_tags');

The x-default tag is not optional — Google uses it for users whose language isn’t covered by any of your specific hreflang values. It should point to your default language version (typically English).

Important: every page referenced in a hreflang set must also link back to all the others. If your English page lists the French URL and the Spanish URL, your French page must list the English and Spanish URLs, and your Spanish page must list English and French. A one-directional hreflang set is treated as invalid by Google. For the full SEO setup around hreflang, structured data, and per-page meta, see the WordPress SEO plugins guide — Rank Math and Yoast both have dedicated hreflang management if you want to handle it at the plugin layer instead of in code.

Step 5: Add a language switcher

The simplest approach: a custom WordPress navigation menu (Appearance → Menus) with links to each language version of your key pages. This works without any PHP code and is easy for a client to maintain — they can update links in the menu editor without touching theme files.

For a more dynamic switcher that updates automatically based on the current page, add a function to your theme:

function mytheme_language_switcher() {
    // Map current page slug to its translations
    global $post;
    $slug = $post ? $post->post_name : '';

    $switcher_map = [
        'about-us'        => ['EN' => '/about-us/',       'FR' => '/fr/a-propos/'],
        'a-propos'        => ['EN' => '/about-us/',       'FR' => '/fr/a-propos/'],
        'services'        => ['EN' => '/services/',        'FR' => '/fr/services/'],
    ];

    if (!isset($switcher_map[$slug])) {
        return; // no switcher for untranslated pages
    }

    echo '<ul class="lang-switcher">';
    foreach ($switcher_map[$slug] as $label => $path) {
        printf(
            '<li><a href="%s">%s</a></li>',
            esc_url(home_url($path)),
            esc_html($label)
        );
    }
    echo '</ul>';
}

// Call in your header template:
// mytheme_language_switcher();

The maintenance cost is visible here: every time you add a translated page, you update this map (and the hreflang array from Step 4). For a site with 8-10 pages in 2 languages, this is manageable. For 30 pages in 4 languages, a plugin handles this bookkeeping automatically.

Step 6: Configure SEO for each language

Every language version of a page needs its own meta title and meta description. If you’re using Rank Math or Yoast, these are set per-page in the SEO meta box — just edit each translated page and fill in the SEO fields in the target language. Don’t leave them in English on your French pages.

Other per-language SEO considerations:

  • XML sitemaps: Rank Math and Yoast generate sitemaps that include all pages regardless of language — your translated pages will be included automatically as long as they’re published
  • The lang attribute: WordPress’s get_language_attributes() function outputs the correct lang attribute on the <html> element. Ensure your theme’s header template uses it: <html <?php language_attributes(); ?>>. For a manual multilingual site, you’d need a filter that sets the locale per page based on the URL prefix, so this outputs correctly per language
  • Translated alt text: Every image on a translated page should have alt text in that page’s language — not the English version copied over

When to use a plugin instead

The manual approach described here scales to a site with roughly 2-3 languages and under 20 pages per language, maintained by a developer. Beyond that, the maintenance burden — updating hreflang arrays, switcher maps, and locale filters every time a page is added or renamed — outweighs the performance benefit of skipping a plugin.

Two plugins that don’t have the performance profile of heavier options:

  • Polylang (free): Handles translation linking, automatic hreflang generation, language switcher widget, and URL routing without the weight of WPML. The free version covers basic multilingual needs for most small-to-medium sites. It works with Rank Math and Yoast for per-language SEO fields.
  • WPML ($39-199/year): The enterprise standard. Handles WooCommerce, complex translation workflows, professional translation service integrations, and advanced URL routing. Worth it for eCommerce multilingual sites or sites with non-technical editors managing translations.

The performance argument against plugins is real but smaller than commonly stated. Polylang in particular is lightweight — the meaningful performance gap is between a well-configured site and a poorly-configured one, not between manual and Polylang specifically. For context on complex WordPress sites that need specialized setups, see how building a WordPress membership site handles similar “non-default” WordPress configuration decisions.

Frequently asked questions

Subdirectories are the recommended URL structure for most multilingual WordPress sites: `yoursite.com/fr/`, `yoursite.com/es/`. One domain means link equity consolidates, it's straightforward to configure with WordPress's page hierarchy, and Google treats each language subdirectory as a clear language signal. Subdomains (`fr.yoursite.com`) are treated more like separate sites by Google and require more DNS and redirect configuration. Country-code TLDs (`yoursite.fr`) are strongest for geographic targeting but require managing separate domains. Unless you have a specific reason for subdomains or ccTLDs, subdirectories are the practical default.

Add hreflang tags via the `wp_head` action hook in your theme's `functions.php`. Create a lookup array mapping each page slug to its translated URLs, loop through them to output `` tags, and always include an `x-default` tag pointing to your primary language version. The `x-default` tag tells Google which page to serve users whose language isn't covered by your specific hreflang values. Every page in a hreflang set must link to all the others — a one-directional hreflang implementation is treated as invalid by Google.

`x-default` is a hreflang value (not a language code) that designates the fallback page for users whose language isn't covered by any of your specific hreflang tags. For example, if you have English and French versions but a German user visits, Google serves the `x-default` URL. Set it to your primary language version — usually English. The tag format is: ``. Including `x-default` is not strictly required by Google, but omitting it means Google will make its own fallback choice, which may not match your intent.

Use WordPress's built-in i18n system. First, load the text domain in `functions.php` with `load_theme_textdomain('mytheme', get_template_directory() . '/languages')` inside an `after_setup_theme` action. Then replace hard-coded strings in your theme templates with `__('String', 'mytheme')` (returns the translation) or `_e('String', 'mytheme')` (echoes it directly). Generate a `.pot` file with WP-CLI: `wp i18n make-pot . languages/mytheme.pot`. Open the `.pot` in Poedit, create a `.po` translation for each locale (e.g. `fr_FR.po`), save — Poedit compiles the `.mo` file WordPress reads. Place both `.po` and `.mo` files in your theme's `/languages/` directory.

For most sites, yes — especially anything beyond 2-3 languages or 20 pages per language. The manual approach requires maintaining a hreflang lookup array, a language switcher map, and a URL-based locale filter every time a page is added or renamed. Polylang (free tier) handles all of this automatically: translation linking, hreflang generation, URL routing, and language switcher widget — with a minimal performance footprint. WPML ($39-199/year) is the enterprise option for WooCommerce multilingual, professional translation workflows, and complex routing. The plugin-free path makes sense for a developer-maintained site with a small, stable page count where you want zero plugin overhead.

Not necessarily — but each language version of a page must appear in your sitemap. If you use Rank Math or Yoast, their XML sitemap generation includes all published pages regardless of language, so your translated pages are included automatically as long as they're published. Google's recommended approach is a single sitemap that lists all language variants, with hreflang tags to signal the relationships — not a separate sitemap per language. If you're on a custom setup without an SEO plugin generating sitemaps, you'd need to build a sitemap that includes all language versions and reference it in your `robots.txt`.

Photo of Ajay Khandal

Written by Ajay Khandal

I'm a freelance WordPress developer with 14+ years of experience building, fixing, and speeding up sites for businesses, agencies, and store owners across the US, UK, Europe, and Australia. I specialize in custom themes, WooCommerce, and performance — the kind of work that shows up as faster load times and fewer support tickets. No account managers, no outsourced tickets — you work directly with me, with replies typically inside 5 hours.

Work with me →