All posts
wordpress

Multilingual WordPress with Polylang: practical implementation guide

How to implement multilingual WordPress with Polylang: configuration, SEO, hreflang, automatic translations, ACF and WooCommerce integration. Real pitfalls, code, settings.

8 min readUpdated: July 9, 2026

Multilingual WordPress with Polylang

Polylang is the best multilingual plugin for WordPress. Free, open-source, actively developed. This post is a complete implementation guide β€” install, configuration, SEO, integrations, real problems.

Why Polylang, not WPML

The comparison I show clients:

| Feature | Polylang | WPML | |---------|----------|------| | Price (1 site, 1 year) | $0 / $99 (Pro) | $39-159 | | Price (1 site, 5 years) | $0 / $495 | $195-795 | | Custom post types | βœ… in free | βœ… in paid | | ACF Pro integration | βœ… in Pro | βœ… in full | | WooCommerce integration | βœ… in Pro | βœ… in full | | Automatic translations | DeepL (Pro) | Own system | | Performance | Lighter | Heavier (more query) | | PL community | Active | Smaller |

My recommendation: Polylang. WPML is 5-10x more expensive in a 5-year perspective and offers nothing Polylang does not. Exception: giant portals with >50 custom post types, where WPML has more mature integrations.

Installation and configuration

# Wp-cli (faster than GUI)
wp plugin install polylang --activate
wp plugin install polylang-pro --activate  # optional

Or through WP Admin β†’ Plugins β†’ Add New β†’ "Polylang" β†’ Install β†’ Activate.

Language settings

Languages β†’ Languages:

Languages: Polish (pl), English (en)
URL order: /pl/ for Polish (default), /en/ for English
Hide default language URL: OFF (better for SEO)
Language switcher: ON

Key decision: subdirectory (/pl/, /en/) vs subdomain (pl.example.com, en.example.com).

Subdirectory wins:

  • Easier SEO (the whole domain builds authority)
  • Easier SSL certs (one cert, not two)
  • Simpler management (one hosting, one config)

Subdomain makes sense when:

  • Different teams handle different language versions
  • Different legal requirements (GDPR, sensitive data per country)

URL structure

In Settings β†’ Permalinks:

Setting: Post name (/%postname%/)
Polylang prefix: /pl/, /en/

Result:

  • Polish version: example.com/pl/o-mnie/
  • English version: example.com/en/about-me/

Every post/page/CPT has a separate URL per language plus a second URL in the other language. Polylang links them automatically.

Translating content

Manual translation (WP editor)

For every post Polylang adds a meta box "Language" + a "+" button next to the title. Click "+", choose a language, write the content. Fields are synced (EN title ↔ PL title), but content is independent.

DeepL automatic translation

Polylang Pro + DeepL API:

// wp-config.php
define('POLYLANG_DEEPL_API_KEY', 'your-deepl-key');

Workflow:

  1. Write a post in Polish
  2. Click "Translate with DeepL" in the editor
  3. Polylang translates the content + excerpt + meta to EN
  4. Native speaker review + corrections

DeepL Pro ($8/m for 500k characters) gives 80-90% quality for EN↔PL. Worse for NO/FI/DK pairs. Always review for critical pages (pricing, terms, privacy policy).

SEO: hreflang, canonical, sitemap

Automatic hreflang

Polylang automatically adds hreflang in <head>:

<link rel="alternate" hreflang="pl" href="https://example.com/pl/o-mnie/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about-me/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/pl/o-mnie/" />

That is SEO best practice for multilingual sites. Google uses hreflang to show the correct language version in results.

Verification:

# Check hreflang on the page
curl -s https://example.com/pl/o-mnie/ | grep "hreflang"

Canonical URL

Polylang sets the canonical URL to the current language version (not to the default version). That is correct SEO β€” every language version has its own canonical.

Sitemap (with Yoast / RankMath / SEOPress)

All three popular SEO plugins integrate with Polylang. Sitemap contains all language versions with proper hreflang.

<!-- Sitemap example -->
<url>
  <loc>https://example.com/pl/o-mnie/</loc>
  <xhtml:link rel="alternate" hreflang="pl" href="https://example.com/pl/o-mnie/" />
  <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about-me/" />
</url>
<url>
  <loc>https://example.com/en/about-me/</loc>
  <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about-me/" />
  <xhtml:link rel="alternate" hreflang="pl" href="https://example.com/pl/o-mnie/" />
</url>

Check whether SEO works

# 1. hreflang in <head>
curl -s https://example.com/pl/o-mnie/ | grep hreflang

# 2. Canonical per language
curl -s https://example.com/pl/o-mnie/ | grep canonical
curl -s https://example.com/en/about-me/ | grep canonical

# 3. Search Console β†’ International Targeting
# Check whether hreflang is recognized (can take 2-4 weeks)

Integration with ACF (custom fields)

Free Polylang: ACF custom fields are per post, not per language. That means: the field value in EN you have to enter manually in PL (or vice versa).

Polylang Pro + ACF Pro solves this: custom fields per language.

// functions.php β€” Polylang Pro + ACF Pro
add_filter('acf/settings/l10n', function () {
    return true;  // enables ACF translations
});

add_filter('acf/settings/l10n_textdomain', function () {
    return 'your-textdomain';
});

Then in ACF:

// Field definition with translation
acf_add_local_field_group([
    'key' => 'group_clients',
    'title' => 'Clients',
    'fields' => [
        [
            'key' => 'field_client_name',
            'label' => 'Client name',
            'name' => 'client_name',
            'type' => 'text',
        ],
    ],
    'location' => [
        [
            [
                'param' => 'post_type',
                'operator' => '==',
                'value' => 'project',
            ],
        ],
    ],
]);

From now client_name has separate values per language. In pl you have "Jan Kowalski", in en you have "John Smith".

Integration with WooCommerce

Free Polylang: WooCommerce products are shared (one product, two language versions). Problem: price, attributes, variants are shared β€” which is wrong for many stores.

Polylang Pro for WooCommerce ($99/year) gives:

  • Prices per language (e.g. 99 PLN / €22)
  • Attributes per language (color "czerwony" / "red")
  • Categories per language
  • Shipping zones per language

Without Pro: either you use a multi-currency plugin (e.g. WOOCS), or you accept a shared price (conversion at display rate).

Language switcher in menu

Add to Appearance β†’ Menus:

Menu: Header
Items:
  - Home (PL)              β†’ /pl/
  - About (PL)             β†’ /pl/o-mnie/
  - Blog (PL)              β†’ /pl/blog/
  - [Polylang Language Switcher] ← add as "Language Switcher"

The language switcher shows active language + flags (optionally). You can pick the style: dropdown, list, flags.

My recommendation: dropdown without flags (flags are problematic β€” e.g. Polish page has no single "official" flag, code-switch PL/EN with flags is politically incorrect for some markets).

Production hardening

1. Check for duplicate URLs

Bug: the same page is accessible under two URLs (/o-mnie/ and /pl/o-mnie/). Fix:

// functions.php β€” redirect non-prefixed URL to default language
add_action('template_redirect', function () {
    if (strpos($_SERVER['REQUEST_URI'], '/pl/') === 0 ||
        strpos($_SERVER['REQUEST_URI'], '/en/') === 0) {
        return; // OK
    }
    // No prefix β€” redirect to default
    $default = pll_default_language();
    wp_redirect(home_url("/{$default}" . $_SERVER['REQUEST_URI']), 301);
    exit;
});

2. Cache per language

If you use WP Super Cache or W3 Total Cache, you must disable cache for the page or cache per language. Polylang integrates with most cache plugins β€” enable "Don't cache pages for visitors with cookies" (Polylang sets the cookie pll_language).

For full control (Cloudflare + APO) β€” see my Cloudflare post. HTML cache per language is a separate URL, so cache works automatically.

3. Pre-production testing

# Test 1: all language versions are reachable
for lang in pl en; do
  echo "=== /${lang}/ ==="
  curl -s -o /dev/null -w "%{http_code}\n" https://example.com/${lang}/
done
# Expected: 200, 200

# Test 2: hreflang in <head>
for lang in pl en; do
  echo "=== hreflang /${lang}/ ==="
  curl -s https://example.com/${lang}/ | grep hreflang | head -5
done

# Test 3: canonical per language
for lang in pl en; do
  echo "=== canonical /${lang}/ ==="
  curl -s https://example.com/${lang}/ | grep canonical
done

# Test 4: language switcher works
curl -s https://example.com/pl/ | grep "pll-switcher" || echo "MISSING!"

Common problems

1. After WP update some translations disappear

Polylang keeps translations in wp_term_relationships. After large WP updates this table sometimes gets cleared. Fix: backup the database before update, restore if something disappears.

2. SEO plugin does not recognize multilingual URLs

Yoast / RankMath require manual configuration per language. Check in SEO settings whether "Permalink" is correct per language (Polylang should do this automatically, but check manually).

3. Sitemap.xml does not have all language versions

Enable "Include all languages in sitemap" in SEO settings. Check the sitemap in /sitemap.xml β€” it should have all versions with hreflang in <xhtml:link>.

4. ACF custom fields are empty after switching language

Free Polylang + ACF (without Pro) β†’ custom fields are shared. A value entered in PL is visible in EN. Fix: Polylang Pro + ACF Pro, or manual management per language (switch language β†’ enter different value β†’ save).

Real case studies

Case 1: IT company site (PL + EN, 30 subpages)

Stack: Polylang (free) + Elementor + ACF Deployment: 2 days Cost: 2000 PLN SEO: After 3 months EN keyword traffic +120% (most clients foreign)

Case 2: WooCommerce store (PL + EN + DE, 500 products)

Stack: Polylang Pro + WooCommerce + ACF Pro Deployment: 5 days Cost: 6500 PLN Features: Prices per language (PLN, EUR, GBP), attributes per language, shipping zones per country

Case 3: Educational portal (PL + UA, 1000+ articles)

Stack: Polylang Pro + custom post types + DeepL Deployment: 7 days (including WPML migration) Cost: 12000 PLN Effect: translation of 1000 articles from PL to UA in 2 weeks (DeepL + native speaker review)

What is next

If you need a multilingual WordPress implementation with Polylang β€” get in touch. Delivery: 2-5 days, starting from 2000 PLN.

Tags:#wordpress#polylang#i18n#multilingual#seo

NajczΔ™Ε›ciej zadawane pytania

Polylang Pro or free Polylang?
Free Polylang is enough for 90% of sites. Polylang Pro ($99/year) gives: integration with WooCommerce (correct prices and attributes per language), integration with ACF Pro (custom fields per language), better support for URL slugs. For a typical company site (PL + EN) free = OK. For a WooCommerce store or a site with many custom fields β€” Pro is worth it.
Can I use Polylang with WPML import?
Yes. Polylang has a WPML importer β€” the WPML to Polylang tool in the Tools menu. Direct migration is not perfect (different structure), but for a typical company site with 50-100 subpages it works. For large migrations (>1000 strings) recommendation: a script exporting WPML XML β†’ import to Polylang. Real migration time: 1-3 days for an average site.
How does SEO hreflang work with Polylang?
Polylang automatically generates hreflang links in <head> for every language version of a page. You need to enable 'SEO' in Polylang settings + configure URL format (subdirectory /pl/ /en/ is recommended for SEO, subdomains pl.example.com are less optimal). Polylang also adds x-default hreflang (default language for unknown browsers).
Can I translate automatically via DeepL / Google Translate?
Yes, Polylang Pro has built-in DeepL API integration. Quality: 80-90% for EN↔PL, less for less supported pairs (e.g. NO, FI). Workflow: manual translation of key strings, DeepL for the rest, native speaker review for critical pages (pricing, terms). DeepL Pro: $8/m for 500k characters, usually enough for an average site.

Related posts