2026年7月22日

2026年7月22日

Polylangのエラーを解決する方法

はじめに

Polylangで翻訳済みの投稿URLにアクセスすると404エラーになる・フロントエンドに言語切替ウィジェットが表示されない・投稿を更新しても翻訳ページに変更が反映されない・WooCommerceで商品の翻訳が作れないといった問題は、パーマリンク設定・言語ページのパス設定・Polylangの翻訳同期設定が原因です。

症状・原因

  • 日本語投稿は表示されるが/en/パスの英語翻訳にアクセスすると404になる
  • ウィジェットエリアに言語切替(Language Switcher)を追加したが表示されない
  • 日本語投稿のカスタムフィールドを更新しても英語翻訳には反映されない
  • WooCommerce商品の翻訳を作ろうとしても翻訳追加ボタンが表示されない

解決手順

ステップ1:Polylangの状態を確認する

# Polylangの設定と言語を確認
wp eval "
// バージョン確認
if (defined('POLYLANG_VERSION')) {
    echo 'Polylang version: ' . POLYLANG_VERSION . PHP_EOL;
}

// 登録済み言語を確認
\$languages = pll_languages_list(['fields' => []]);
echo 'Languages: ' . PHP_EOL;
foreach (\$languages as \$lang) {
    printf('  %s (%s) | Slug: %s | Home URL: %s' . PHP_EOL,
        \$lang->name, \$lang->locale, \$lang->slug, pll_home_url(\$lang->slug));
}

// 設定を確認
\$options = get_option('polylang', []);
echo 'Hide default language: ' . (\$options['hide_default'] ?? '0') . PHP_EOL;
echo 'Rewrite: '               . (\$options['rewrite'] ?? '0') . PHP_EOL;
echo 'Default language: '      . (\$options['default_lang'] ?? 'not set') . PHP_EOL;

// 翻訳済みの投稿数を確認
\$post = get_posts(['post_type' => 'post', 'numberposts' => 1]);
if (\$post) {
    \$translations = pll_get_post_translations(\$post[0]->ID);
    printf('Sample post #%d translations: %s' . PHP_EOL, \$post[0]->ID, implode(', ', array_keys(\$translations)));
}
"

# パーマリンクをリセット
wp rewrite flush --hard
echo "Rewrite rules flushed"

ステップ2:翻訳の取得と管理をカスタマイズする

// functions.php: Polylang翻訳管理

// ① 現在の言語で翻訳投稿を取得
function get_translated_post(int $post_id, string $lang = ''): int {
    $lang = $lang ?: pll_current_language();
    return (int) pll_get_post($post_id, $lang);
}

// 使用例: 英語版の投稿IDを取得
$en_post_id = get_translated_post($ja_post_id, 'en');

// ② カスタムフィールドを翻訳間で同期
add_action('pll_save_post', function(int $post_id, \WP_Post $post, array $translations): void {
    // 同期するカスタムフィールドのキー
    $sync_metas = ['_product_sku', '_custom_price', 'seo_noindex'];

    foreach ($translations as $lang => $translated_id) {
        if ($translated_id === $post_id) continue; // 自身はスキップ

        foreach ($sync_metas as $meta_key) {
            $value = get_post_meta($post_id, $meta_key, true);
            if ($value !== '') {
                update_post_meta($translated_id, $meta_key, $value);
            }
        }
    }
}, 10, 3);

// ③ 言語切替URLを手動で生成
function get_language_switcher_urls(): array {
    $languages = pll_languages_list(['fields' => []]);
    $urls       = [];

    foreach ($languages as $lang) {
        $translated_id = pll_get_post(get_the_ID(), $lang->slug);
        $urls[$lang->slug] = [
            'name'   => $lang->name,
            'url'    => $translated_id ? get_permalink($translated_id) : pll_home_url($lang->slug),
            'active' => ($lang->slug === pll_current_language()),
            'flag'   => $lang->flag_url,
        ];
    }
    return $urls;
}

ステップ3:プログラムで文字列と言語を登録する

// functions.php: Polylang文字列翻訳

// ① カスタム文字列を翻訳可能に登録
add_action('init', function(): void {
    if (!function_exists('pll_register_string')) return;

    // テーマ固有の文字列を登録
    pll_register_string('theme', 'ご購入はこちら', 'My Theme');
    pll_register_string('theme', 'もっと見る', 'My Theme');
    pll_register_string('theme', 'カートに追加', 'My Theme');
    pll_register_string('theme', 'お問い合わせ', 'My Theme');
});

// テンプレートでの使用
// echo pll__('ご購入はこちら');

// ② 特定のパスで言語を強制設定
add_filter('pll_get_post_language', function(string $lang, \WP_Post $post): string {
    // /en/ パスで始まるURLは英語強制
    if (str_starts_with($_SERVER['REQUEST_URI'] ?? '', '/en/')) {
        return 'en';
    }
    return $lang;
}, 10, 2);

// ③ 言語別にメニューを切り替え
add_filter('wp_nav_menu_args', function(array $args): array {
    $lang = pll_current_language();

    // 言語別のメニュースラッグを設定
    $menu_map = [
        'ja' => 'main-menu-ja',
        'en' => 'main-menu-en',
    ];

    if (isset($menu_map[$lang]) && $args['theme_location'] === 'primary') {
        $args['menu'] = $menu_map[$lang];
    }
    return $args;
});

ステップ4:WooCommerceとの連携を設定する

// functions.php: Polylang + WooCommerce連携

// ① 商品翻訳のURLを言語別に生成
add_filter('woocommerce_get_cart_url', function(string $url): string {
    $lang    = pll_current_language();
    $cart_id = pll_get_post(wc_get_page_id('cart'), $lang);
    return $cart_id ? get_permalink($cart_id) : $url;
});

// ② WooCommerceメールを言語別に送信
add_filter('woocommerce_email_subject_new_order', function(string $subject, \WC_Order $order): string {
    $customer_lang = get_user_meta($order->get_customer_id(), 'pll_filter_lang', true);
    if ($customer_lang === 'en') {
        return 'New Order #' . $order->get_id();
    }
    return $subject; // デフォルト(日本語)
}, 10, 2);

// ③ 言語別のホームURLを取得
function get_localized_home_url(string $lang = ''): string {
    $lang = $lang ?: pll_current_language();
    return pll_home_url($lang);
}

// ④ REST APIで言語フィルターを追加
add_filter('rest_post_query', function(array $args, \WP_REST_Request $request): array {
    $lang = $request->get_param('lang');
    if ($lang && function_exists('pll_get_post_language')) {
        $args['lang'] = sanitize_key($lang);
    }
    return $args;
}, 10, 2);

ステップ5:翻訳の一括操作とデバッグ

# 翻訳の整合性を確認
wp eval "
// 翻訳が欠けている投稿を確認
\$all_posts = get_posts([
    'post_type'      => 'post',
    'posts_per_page' => 50,
    'post_status'    => 'publish',
]);

\$languages = pll_languages_list(['fields' => 'slug']);
\$missing   = [];

foreach (\$all_posts as \$post) {
    \$translations = pll_get_post_translations(\$post->ID);
    foreach (\$languages as \$lang) {
        if (!isset(\$translations[\$lang])) {
            \$missing[] = ['post_id' => \$post->ID, 'lang' => \$lang, 'title' => \$post->post_title];
        }
    }
}

echo 'Posts missing translations: ' . count(\$missing) . PHP_EOL;
foreach (array_slice(\$missing, 0, 10) as \$m) {
    printf('  Post #%d | Missing: %s | Title: %s' . PHP_EOL, \$m['post_id'], \$m['lang'], \$m['title']);
}
"

# 言語割り当てなしの投稿を確認
wp eval "
global \$wpdb;
\$posts_without_lang = \$wpdb->get_results(\"
    SELECT p.ID, p.post_title
    FROM {\$wpdb->posts} p
    LEFT JOIN {\$wpdb->term_relationships} tr
        ON p.ID = tr.object_id
    LEFT JOIN {\$wpdb->term_taxonomy} tt
        ON tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = 'language'
    WHERE p.post_type = 'post'
      AND p.post_status = 'publish'
      AND tt.taxonomy IS NULL
    LIMIT 10
\");
echo 'Posts without language: ' . count(\$posts_without_lang) . PHP_EOL;
"

注意事項

  • Polylangで翻訳ページが404になる場合、「設定 → パーマリンク」を一度保存するだけで解決することがほとんどです。Polylangはリライトルールを動的に追加するため、プラグインの有効化後や設定変更後は必ずパーマリンクの再保存が必要です
  • WooCommerce + Polylangの組み合わせでは「Polylang for WooCommerce」(有料)プラグインを使用することを強く推奨します。無料版のPolylangでもWooCommerceの基本的な多言語化は可能ですが、カート・チェックアウト・注文完了ページの言語別URLは有料版が必要です
  • pll_register_string()で登録した文字列の翻訳は「言語 → 文字列の翻訳」画面で行います。登録後にこの画面に表示されない場合は一度ページをリロードしてください

まとめ

Polylang修復は①pll_languages_list()で言語一覧・URLを確認・wp rewrite flushでパーマリンクリセット、②pll_get_post()で翻訳投稿IDを取得・pll_save_postフックでカスタムフィールドを翻訳間同期、③pll_register_string()でテーマ文字列を翻訳可能に登録・wp_nav_menu_argsフィルターで言語別メニュー切替、④woocommerce_get_cart_urlフィルターで言語別カートURL・rest_post_queryフィルターでREST APIの言語フィルタリング、⑤翻訳欠けチェッククエリ・言語未割当投稿の診断SQLで整合性を管理する手順で解決します。

お気軽にご相談ください

お見積りへ お問い合わせへ