2026年10月22日

2026年10月22日

WordPressで多言語サイトを構築する方法【WPML・Polylang・手動実装】

はじめに

WordPressを多言語対応にすることで、グローバルな読者層へリーチできます。WPML・Polylangなどのプラグインを使う方法と、カスタムコードで実装する方法があります。それぞれの特徴と実装方法を解説します。

症状・原因

  • 日本語と英語の両方のコンテンツを同じWordPressで管理したい
  • 言語切り替えボタンを設置して自動的に言語を変更したい
  • Google検索で各言語のページが正しくインデックスされない
  • hreflangタグが設定されておらずSEOで損している

解決手順

ステップ1:言語管理システムを設計する

// functions.php: シンプルな多言語管理クラス
class Simple_Multilingual {

    const SUPPORTED_LANGUAGES = [
        'ja' => ['label' => '日本語', 'dir' => 'ltr', 'locale' => 'ja'],
        'en' => ['label' => 'English', 'dir' => 'ltr', 'locale' => 'en_US'],
        'zh' => ['label' => '中文',   'dir' => 'ltr', 'locale' => 'zh_CN'],
    ];

    // 現在の言語を取得
    public static function get_current_lang(): string {
        // URLのパスから言語を判定(例: /en/about-us/)
        $uri = $_SERVER['REQUEST_URI'] ?? '/';

        foreach (array_keys(self::SUPPORTED_LANGUAGES) as $lang) {
            if (str_starts_with($uri, "/{$lang}/") || $uri === "/{$lang}") {
                return $lang;
            }
        }

        // デフォルトは日本語
        return 'ja';
    }

    // 投稿の翻訳IDを取得
    public static function get_translation_id(int $post_id, string $lang): int {
        $translation_id = (int) get_post_meta($post_id, "_translation_{$lang}", true);
        return $translation_id ?: $post_id;
    }

    // 言語切り替えURL一覧を生成
    public static function get_language_switcher_urls(int $post_id = 0): array {
        if (!$post_id) $post_id = get_the_ID();

        $urls = [];

        foreach (self::SUPPORTED_LANGUAGES as $lang => $config) {
            $translated_id = self::get_translation_id($post_id, $lang);
            $url = $lang === 'ja'
                ? get_permalink($translated_id)
                : home_url("/{$lang}/") . get_post_field('post_name', $translated_id) . '/';

            $urls[$lang] = [
                'url'   => $url,
                'label' => $config['label'],
            ];
        }

        return $urls;
    }
}

ステップ2:翻訳投稿管理メタボックスを追加する

// 翻訳管理メタボックス
add_action('add_meta_boxes', function(): void {
    add_meta_box(
        'multilingual_translations',
        '翻訳設定',
        function(WP_Post $post): void {
            $translations = [
                'en' => (int) get_post_meta($post->ID, '_translation_en', true),
                'zh' => (int) get_post_meta($post->ID, '_translation_zh', true),
            ];

            echo '<table>';
            foreach (['en' => '英語', 'zh' => '中国語(簡体)'] as $lang => $label) {
                $trans_id  = $translations[$lang] ?? 0;
                $trans_url = $trans_id ? get_edit_post_link($trans_id) : '';
                printf(
                    '<tr><th>%s</th><td><input type="number" name="translation_%s" value="%d" placeholder="翻訳投稿ID"></td>',
                    esc_html($label),
                    esc_attr($lang),
                    $trans_id
                );
                if ($trans_url) {
                    printf('<td><a href="%s" target="_blank">編集</a></td>', esc_url($trans_url));
                }
                echo '</tr>';
            }
            echo '</table>';
            wp_nonce_field('save_translations', 'translation_nonce');
        },
        'post',
        'side'
    );
});

// 翻訳IDを保存
add_action('save_post', function(int $post_id): void {
    if (!wp_verify_nonce($_POST['translation_nonce'] ?? '', 'save_translations')) return;
    if (!current_user_can('edit_post', $post_id)) return;

    foreach (['en', 'zh'] as $lang) {
        $value = (int) ($_POST["translation_{$lang}"] ?? 0);
        if ($value) {
            update_post_meta($post_id, "_translation_{$lang}", $value);
        } else {
            delete_post_meta($post_id, "_translation_{$lang}");
        }
    }
});

ステップ3:hreflangタグを出力する

// SEO: hreflangタグをheadに出力
add_action('wp_head', function(): void {
    if (!is_singular()) return;

    $post_id = get_the_ID();

    // 現在の言語のURL
    $current_lang = Simple_Multilingual::get_current_lang();
    $switcher_urls = Simple_Multilingual::get_language_switcher_urls($post_id);

    echo "\n<!-- hreflang tags for multilingual SEO -->\n";

    foreach ($switcher_urls as $lang => $data) {
        printf(
            '<link rel="alternate" hreflang="%s" href="%s">' . "\n",
            esc_attr($lang),
            esc_url($data['url'])
        );
    }

    // x-default(デフォルト言語)
    printf(
        '<link rel="alternate" hreflang="x-default" href="%s">' . "\n",
        esc_url(get_permalink($post_id))
    );
});

// 言語切り替えウィジェット
add_shortcode('language_switcher', function(): string {
    $post_id = get_the_ID();

    if (!$post_id) return '';

    $urls = Simple_Multilingual::get_language_switcher_urls($post_id);
    $current_lang = Simple_Multilingual::get_current_lang();

    $output = '<ul class="language-switcher">';
    foreach ($urls as $lang => $data) {
        $class = $lang === $current_lang ? ' class="current"' : '';
        $output .= sprintf(
            '<li%s><a href="%s" hreflang="%s">%s</a></li>',
            $class,
            esc_url($data['url']),
            esc_attr($lang),
            esc_html($data['label'])
        );
    }
    $output .= '</ul>';

    return $output;
});

ステップ4:Polylangの基本設定(プラグイン使用時)

// Polylangプラグイン使用時の統合コード
// functions.php に追加

// Polylangで翻訳投稿を取得
function get_polylang_translation(int $post_id, string $lang): int {
    if (!function_exists('pll_get_post')) return $post_id;

    $translated_id = pll_get_post($post_id, $lang);
    return $translated_id ?: $post_id;
}

// 現在の言語を取得
function get_current_language(): string {
    if (function_exists('pll_current_language')) {
        return pll_current_language('slug') ?: 'ja';
    }
    return Simple_Multilingual::get_current_lang();
}

// 言語別のWP_Queryクエリ最適化
add_action('pre_get_posts', function(WP_Query $query): void {
    if (!function_exists('pll_current_language')) return;
    if (!$query->is_main_query() || is_admin()) return;

    // Polylangが自動的に言語フィルターを適用するため
    // 追加の処理は不要(参考として記載)
});

// WPMLと互換性のある翻訳取得
function get_wpml_translation(int $post_id, string $lang): int {
    if (!function_exists('wpml_object_id_filter')) return $post_id;

    return (int) apply_filters('wpml_object_id', $post_id, 'post', true, $lang);
}

ステップ5:言語別サイトマップを設定する

# WP-CLIで言語別の設定確認
wp option get WPLANG  # 現在の言語設定
wp rewrite flush       # リライトルールをリフレッシュ

# Polylangプラグインをインストール
wp plugin install polylang --activate

# Polylangの設定確認
wp pll languages      # 登録済み言語リスト

# hreflangタグの確認
curl -s https://example.com/about/ | grep hreflang

注意事項

  • URL構造: 多言語サイトのURL構造(/en/page/ vs example.en/page/ vs en.example.com/page/)はSEOに影響します。サブディレクトリ方式(/en/)が最も管理しやすいです
  • コンテンツ管理: 翻訳コンテンツは別々の投稿として管理します。自動翻訳APIとの連携も可能ですが、品質管理に注意してください
  • キャッシュ: 言語別にキャッシュを分けることが重要です。CloudflareやWP Rocketでは言語ごとのキャッシュ設定が可能です

まとめ

多言語サイトの実装は「URL設計→翻訳投稿管理→hreflangタグ出力→言語切り替えUI→言語別サイトマップ」の5ステップです。小規模サイトには手動実装、中〜大規模サイトにはPolylang(無料)またはWPML(有料)の使用を推奨します。関連記事:WordPressのスキーママークアップ実装

お気軽にご相談ください

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