星期一 , 9 6 月 2025

How to make a multi-language WordPress theme

Developing a multi-language WordPress theme without plugins requires implementing internationalization (i18n) and localization (l10n) directly in your theme code. Here’s a comprehensive approach:

1. Theme Structure Setup

First, organize your theme with language support in mind:

your-theme/

├── languages/

│   ├── theme-name.pot

│   ├── theme-name-es_ES.po

│   ├── theme-name-es_ES.mo

│   └── theme-name-fr_FR.po

├── functions.php

├── index.php

└── other theme files…

2. Enable Text Domain in functions.php

php

function theme_setup() {

    // Make theme available for translation

    load_theme_textdomain(‘theme-name’, get_template_directory() . ‘/languages’);

    // Add language switcher to admin bar

    add_theme_support(‘customize-selective-refresh-widgets’);

}

add_action(‘after_setup_theme’, ‘theme_setup’);

3. Internationalize Theme Strings

Wrap all translatable strings in your theme files:

php

// Instead of: echo “Hello World”;

echo __(‘Hello World’, ‘theme-name’);

// For attributes

echo ‘<input placeholder=”‘ . esc_attr__(‘Enter your name’, ‘theme-name’) . ‘”>’;

// For pluralization

printf(_n(‘One comment’, ‘%s comments’, $comment_count, ‘theme-name’), $comment_count);

4. Handle Multi-Language Content

Create a custom solution for content translation:

php

// Add to functions.php

class MultiLanguageContent {

    public function __construct() {

        add_action(‘init’, array($this, ‘init’));

        add_action(‘wp_enqueue_scripts’, array($this, ‘enqueue_scripts’));

        add_filter(‘the_content’, array($this, ‘filter_content’));

        add_filter(‘the_title’, array($this, ‘filter_title’));

    }

    public function init() {

        // Register custom fields for translations

        add_meta_box(‘translation_fields’, ‘Translations’,

                    array($this, ‘translation_meta_box’), ‘post’);

        add_meta_box(‘translation_fields’, ‘Translations’,

                    array($this, ‘translation_meta_box’), ‘page’);

        add_action(‘save_post’, array($this, ‘save_translations’));

    }

    public function get_current_language() {

        // Check URL parameter first

        if (isset($_GET[‘lang’]) && in_array($_GET[‘lang’], $this->get_available_languages())) {

            return sanitize_text_field($_GET[‘lang’]);

        }

        // Check session/cookie

        if (isset($_COOKIE[‘site_language’])) {

            return sanitize_text_field($_COOKIE[‘site_language’]);

        }

        // Default language

        return ‘en’;

    }

    public function get_available_languages() {

        return array(‘en’, ‘es’, ‘fr’, ‘de’); // Add your languages

    }

    public function translation_meta_box($post) {

        $languages = $this->get_available_languages();

        wp_nonce_field(‘translation_nonce’, ‘translation_nonce’);

        foreach ($languages as $lang) {

            if ($lang === ‘en’) continue; // Skip default language

            $title = get_post_meta($post->ID, “_title_{$lang}”, true);

            $content = get_post_meta($post->ID, “_content_{$lang}”, true);

            echo “<h4>” . strtoupper($lang) . ” Translation</h4>”;

            echo “<p><label>Title:</label><br>”;

            echo “<input type=’text’ name=’title_{$lang}’ value='” . esc_attr($title) . “‘ style=’width:100%’></p>”;

            echo “<p><label>Content:</label><br>”;

            echo “<textarea name=’content_{$lang}’ rows=’10’ style=’width:100%’>” . esc_textarea($content) . “</textarea></p>”;

        }

    }

    public function save_translations($post_id) {

        if (!isset($_POST[‘translation_nonce’]) ||

            !wp_verify_nonce($_POST[‘translation_nonce’], ‘translation_nonce’)) {

            return;

        }

        $languages = $this->get_available_languages();

        foreach ($languages as $lang) {

            if ($lang === ‘en’) continue;

            if (isset($_POST[“title_{$lang}”])) {

                update_post_meta($post_id, “_title_{$lang}”,

                               sanitize_text_field($_POST[“title_{$lang}”]));

            }

            if (isset($_POST[“content_{$lang}”])) {

                update_post_meta($post_id, “_content_{$lang}”,

                               wp_kses_post($_POST[“content_{$lang}”]));

            }

        }

    }

    public function filter_title($title) {

        if (is_admin()) return $title;

        $current_lang = $this->get_current_language();

        if ($current_lang === ‘en’) return $title;

        global $post;

        if ($post) {

            $translated_title = get_post_meta($post->ID, “_title_{$current_lang}”, true);

            return $translated_title ? $translated_title : $title;

        }

        return $title;

    }

    public function filter_content($content) {

        if (is_admin()) return $content;

        $current_lang = $this->get_current_language();

        if ($current_lang === ‘en’) return $content;

        global $post;

        if ($post) {

            $translated_content = get_post_meta($post->ID, “_content_{$current_lang}”, true);

            return $translated_content ? $translated_content : $content;

        }

        return $content;

    }

    public function enqueue_scripts() {

        wp_enqueue_script(‘multilang-script’, get_template_directory_uri() . ‘/js/multilang.js’,

                         array(‘jquery’), ‘1.0’, true);

    }

}

new MultiLanguageContent();

5. Create Language Switcher

Add this function to display language switcher:

php

function display_language_switcher() {

    $current_lang = (new MultiLanguageContent())->get_current_language();

    $languages = array(

        ‘en’ => ‘English’,

        ‘es’ => ‘Español’,

        ‘fr’ => ‘Français’,

        ‘de’ => ‘Deutsch’

    );

    echo ‘<div class=”language-switcher”>’;

    foreach ($languages as $code => $name) {

        $url = add_query_arg(‘lang’, $code, get_permalink());

        $active = ($current_lang === $code) ? ‘active’ : ”;

        echo “<a href='{$url}’ class=’lang-link {$active}’ data-lang='{$code}’>{$name}</a> “;

    }

    echo ‘</div>’;

}

6. JavaScript for Language Switching

Create /js/multilang.js:

javascript

jQuery(document).ready(function($) {

    $(‘.lang-link’).on(‘click’, function(e) {

        e.preventDefault();

        var lang = $(this).data(‘lang’);

        // Set cookie for language preference

        document.cookie = “site_language=” + lang + “; path=/; max-age=31536000”;

        // Reload page with language parameter

        window.location.href = $(this).attr(‘href’);

    });

});

7. CSS for Language Switcher

css

.language-switcher {

    padding: 10px;

    text-align: right;

}

.lang-link {

    margin: 0 5px;

    padding: 5px 10px;

    text-decoration: none;

    border: 1px solid #ddd;

    border-radius: 3px;

}

.lang-link.active {

    background-color: #0073aa;

    color: white;

}

8. Generate Translation Files

Create a .pot file for translators:

  1. Use WP-CLI: wp i18n make-pot . languages/theme-name.pot
  2. Or use Poedit to scan your theme files
  3. Create .po files for each language
  4. Generate .mo files from .po files

9. Handle Menus and Widgets

For navigation menus in different languages:

php

function get_multilang_menu($menu_location) {

    $current_lang = (new MultiLanguageContent())->get_current_language();

    $menu_location_lang = $menu_location . ‘_’ . $current_lang;

    if (has_nav_menu($menu_location_lang)) {

        return $menu_location_lang;

    }

    return $menu_location; // Fallback to default

}

// Register language-specific menus

function register_multilang_menus() {

    $languages = array(‘en’, ‘es’, ‘fr’, ‘de’);

    $menus = array();

    foreach ($languages as $lang) {

        $menus[“primary_{$lang}”] = “Primary Menu ({$lang})”;

        $menus[“footer_{$lang}”] = “Footer Menu ({$lang})”;

    }

    register_nav_menus($menus);

}

add_action(‘init’, ‘register_multilang_menus’);

10. SEO Considerations

Add hreflang tags:

php

function add_hreflang_tags() {

    if (is_singular()) {

        $languages = array(‘en’, ‘es’, ‘fr’, ‘de’);

        $current_url = get_permalink();

        foreach ($languages as $lang) {

            $lang_url = add_query_arg(‘lang’, $lang, $current_url);

            echo “<link rel=’alternate’ hreflang='{$lang}’ href='{$lang_url}’ />\n”;

        }

    }

}

add_action(‘wp_head’, ‘add_hreflang_tags’);

This approach gives you complete control over multi-language functionality without relying on plugins. You can extend it further by adding features like automatic language detection based on browser settings, RTL language support, or custom post type translations.

Yes, the setup I provided will make post content multi-language, but let me clarify and expand on how it works:

How the Content Translation Works

The code I provided creates separate translation fields for each post/page where you can store translated versions of:

  • Post titles
  • Post content
  • Any other custom fields you want to translate

Here’s the Complete Flow:

1. Admin Interface

When editing a post, you’ll see additional meta boxes with fields like:

ES Translation
Title: [input field for Spanish title]
Content: [textarea for Spanish content]
 
FR Translation  
Title: [input field for French title]
Content: [textarea for French content]

2. Content Storage

  • Original content stays in wp_posts table
  • Translations are stored as post meta:
    • _title_es for Spanish title
    • _content_es for Spanish content
    • _title_fr for French title
    • _content_fr for French content

3. Frontend Display

The filters automatically swap content based on selected language:

php
public function filter_content($content) {
    $current_lang = $this->get_current_language();
    if ($current_lang === 'en') return $content; // Show original
    
    global $post;
    $translated_content = get_post_meta($post->ID, "_content_{$current_lang}", true);
    return $translated_content ? $translated_content : $content; // Show translation or fallback
}

Enhanced Content Translation Setup

Here’s an improved version that handles more content types:

Enhanced Multi-Language Content System Code  <?php // Enhanced Multi-Language Content System // Add this to your theme’s functions.php class EnhancedMultiLanguageContent { private $default_language = ‘en’; private $available_languages = array( ‘en’ => ‘English’, ‘e How to Use This Enhanced System

1. Edit Posts/Pages

When you edit any post or page, you’ll see a new “Content Translations” meta box with tabs for each language where you can enter:

  • Translated titles
  • Translated content (with full WordPress editor)
  • Translated excerpts
  • Translated custom fields

2. Display Language Switcher

Add this to your theme templates:

php
// Simple text switcher
display_language_switcher();
 
// With flag icons
display_language_switcher(true);

3. Get Translated Content Programmatically

php
// Get translated title
$spanish_title = get_multilang_content($post_id, 'title', 'es');
 
// Get translated content
$french_content = get_multilang_content($post_id, 'content', 'fr');
 
// Get translated custom field
$german_button = get_multilang_content($post_id, 'button_text', 'de');

4. Automatic Content Switching

The system automatically shows translated content based on the selected language. If no translation exists, it falls back to the original content.

What Gets Translated

Post/Page Content: Full content with WordPress editor ✅ Titles: Post and page titles
Excerpts: Post excerpts ✅ Custom Fields: Any custom fields you define ✅ Theme Strings: All text wrapped in translation functions

Frontend Usage Example

php
// In your theme files
<h1><?php the_title(); ?></h1> <!-- Automatically translated -->
<div><?php the_content(); ?></div> <!-- Automatically translated -->
 
// For custom fields
<button><?php echo get_post_meta(get_the_ID(), 'button_text', true); ?></button>

This system creates a complete multi-language solution where all post content is translatable without any plugins. The translations are stored separately and automatically displayed based on the selected language.

improved version that handles more content types:

<?php
// Enhanced Multi-Language Content System
// Add this to your theme's functions.php
class EnhancedMultiLanguageContent {
private $default_language = 'en';
private $available_languages = array(
'en' => 'English',
'es' => 'Español', 
'fr' => 'Français',
'de' => 'Deutsch',
'it' => 'Italiano'
);
public function __construct() {
add_action('init', array($this, 'init'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
// Content filters
add_filter('the_content', array($this, 'filter_content'));
add_filter('the_title', array($this, 'filter_title'));
add_filter('the_excerpt', array($this, 'filter_excerpt'));
add_filter('get_the_excerpt', array($this, 'filter_excerpt'));
// Admin hooks
add_action('add_meta_boxes', array($this, 'add_translation_meta_boxes'));
add_action('save_post', array($this, 'save_translations'));
// Custom fields support
add_filter('get_post_metadata', array($this, 'filter_custom_fields'), 10, 4);
}
public function init() {
// Register text domain
load_theme_textdomain('multilang-theme', get_template_directory() . '/languages');
// Set current language
$this->set_current_language();
}
public function get_current_language() {
if (isset($_SESSION['current_language'])) {
return $_SESSION['current_language'];
}
// Check URL parameter
if (isset($_GET['lang']) && array_key_exists($_GET['lang'], $this->available_languages)) {
return sanitize_text_field($_GET['lang']);
}
// Check cookie
if (isset($_COOKIE['site_language']) && array_key_exists($_COOKIE['site_language'], $this->available_languages)) {
return sanitize_text_field($_COOKIE['site_language']);
}
return $this->default_language;
}
private function set_current_language() {
if (!session_id()) {
session_start();
}
$_SESSION['current_language'] = $this->get_current_language();
}
public function add_translation_meta_boxes() {
$post_types = get_post_types(array('public' => true), 'names');
foreach ($post_types as $post_type) {
add_meta_box(
'translation_fields',
__('Content Translations', 'multilang-theme'),
array($this, 'translation_meta_box'),
$post_type,
'normal',
'high'
);
}
}
public function translation_meta_box($post) {
wp_nonce_field('translation_nonce', 'translation_nonce');
echo '<div class="translation-tabs">';
echo '<div class="tab-nav">';
foreach ($this->available_languages as $code => $name) {
if ($code === $this->default_language) continue;
$active = ($code === 'es') ? 'active' : '';
echo "<button type='button' class='tab-btn {$active}' data-tab='{$code}'>{$name}</button>";
}
echo '</div>';
foreach ($this->available_languages as $code => $name) {
if ($code === $this->default_language) continue;
$display = ($code === 'es') ? 'block' : 'none';
echo "<div class='tab-content' id='tab-{$code}' style='display: {$display}'>";
$this->render_translation_fields($post->ID, $code, $name);
echo '</div>';
}
echo '</div>';
}
private function render_translation_fields($post_id, $lang_code, $lang_name) {
$title = get_post_meta($post_id, "_title_{$lang_code}", true);
$content = get_post_meta($post_id, "_content_{$lang_code}", true);
$excerpt = get_post_meta($post_id, "_excerpt_{$lang_code}", true);
echo "<h3>{$lang_name} Translation</h3>";
// Title field
echo "<p><label><strong>" . __('Title:', 'multilang-theme') . "</strong></label><br>";
echo "<input type='text' name='title_{$lang_code}' value='" . esc_attr($title) . "' style='width:100%; padding: 5px;'></p>";
// Content field
echo "<p><label><strong>" . __('Content:', 'multilang-theme') . "</strong></label><br>";
wp_editor($content, "content_{$lang_code}", array(
'textarea_name' => "content_{$lang_code}",
'textarea_rows' => 10,
'media_buttons' => true,
'teeny' => false
));
echo "</p>";
// Excerpt field
echo "<p><label><strong>" . __('Excerpt:', 'multilang-theme') . "</strong></label><br>";
echo "<textarea name='excerpt_{$lang_code}' rows='3' style='width:100%; padding: 5px;'>" . esc_textarea($excerpt) . "</textarea></p>";
// Custom fields support
$this->render_custom_field_translations($post_id, $lang_code);
}
private function render_custom_field_translations($post_id, $lang_code) {
// Define translatable custom fields
$translatable_fields = apply_filters('multilang_custom_fields', array(
'custom_title' => __('Custom Title', 'multilang-theme'),
'custom_description' => __('Custom Description', 'multilang-theme'),
'button_text' => __('Button Text', 'multilang-theme')
));
if (!empty($translatable_fields)) {
echo "<h4>" . __('Custom Fields', 'multilang-theme') . "</h4>";
foreach ($translatable_fields as $field_key => $field_label) {
$value = get_post_meta($post_id, "_{$field_key}_{$lang_code}", true);
echo "<p><label><strong>{$field_label}:</strong></label><br>";
echo "<input type='text' name='{$field_key}_{$lang_code}' value='" . esc_attr($value) . "' style='width:100%; padding: 3px;'></p>";
}
}
}
public function save_translations($post_id) {
if (!isset($_POST['translation_nonce']) || 
!wp_verify_nonce($_POST['translation_nonce'], 'translation_nonce') ||
defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return;
}
if (!current_user_can('edit_post', $post_id)) {
return;
}
foreach ($this->available_languages as $code => $name) {
if ($code === $this->default_language) continue;
// Save basic fields
if (isset($_POST["title_{$code}"])) {
update_post_meta($post_id, "_title_{$code}", sanitize_text_field($_POST["title_{$code}"]));
}
if (isset($_POST["content_{$code}"])) {
update_post_meta($post_id, "_content_{$code}", wp_kses_post($_POST["content_{$code}"]));
}
if (isset($_POST["excerpt_{$code}"])) {
update_post_meta($post_id, "_excerpt_{$code}", sanitize_textarea_field($_POST["excerpt_{$code}"]));
}
// Save custom fields
$translatable_fields = apply_filters('multilang_custom_fields', array(
'custom_title' => '',
'custom_description' => '',
'button_text' => ''
));
foreach ($translatable_fields as $field_key => $field_label) {
if (isset($_POST["{$field_key}_{$code}"])) {
update_post_meta($post_id, "_{$field_key}_{$code}", sanitize_text_field($_POST["{$field_key}_{$code}"]));
}
}
}
}
public function filter_title($title) {
if (is_admin() || !in_the_loop()) return $title;
$current_lang = $this->get_current_language();
if ($current_lang === $this->default_language) return $title;
global $post;
if ($post && isset($post->ID)) {
$translated_title = get_post_meta($post->ID, "_title_{$current_lang}", true);
return !empty($translated_title) ? $translated_title : $title;
}
return $title;
}
public function filter_content($content) {
if (is_admin() || !in_the_loop()) return $content;
$current_lang = $this->get_current_language();
if ($current_lang === $this->default_language) return $content;
global $post;
if ($post && isset($post->ID)) {
$translated_content = get_post_meta($post->ID, "_content_{$current_lang}", true);
return !empty($translated_content) ? $translated_content : $content;
}
return $content;
}
public function filter_excerpt($excerpt) {
if (is_admin()) return $excerpt;
$current_lang = $this->get_current_language();
if ($current_lang === $this->default_language) return $excerpt;
global $post;
if ($post && isset($post->ID)) {
$translated_excerpt = get_post_meta($post->ID, "_excerpt_{$current_lang}", true);
return !empty($translated_excerpt) ? $translated_excerpt : $excerpt;
}
return $excerpt;
}
public function filter_custom_fields($value, $object_id, $meta_key, $single) {
if (is_admin()) return $value;
$current_lang = $this->get_current_language();
if ($current_lang === $this->default_language) return $value;
// Check if this is a translatable custom field
$translatable_fields = apply_filters('multilang_custom_fields', array(
'custom_title' => '',
'custom_description' => '',
'button_text' => ''
));
if (array_key_exists($meta_key, $translatable_fields)) {
$translated_value = get_post_meta($object_id, "_{$meta_key}_{$current_lang}", true);
if (!empty($translated_value)) {
return $single ? $translated_value : array($translated_value);
}
}
return $value;
}
public function enqueue_scripts() {
wp_enqueue_script('multilang-frontend', get_template_directory_uri() . '/js/multilang-frontend.js', array('jquery'), '1.0', true);
wp_localize_script('multilang-frontend', 'multilang_ajax', array(
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('multilang_nonce')
));
}
public function admin_enqueue_scripts() {
wp_enqueue_script('multilang-admin', get_template_directory_uri() . '/js/multilang-admin.js', array('jquery'), '1.0', true);
wp_enqueue_style('multilang-admin', get_template_directory_uri() . '/css/multilang-admin.css', array(), '1.0');
}
public function get_language_switcher($show_flags = false) {
$current_lang = $this->get_current_language();
$current_url = $this->get_current_url();
$output = '<div class="language-switcher">';
foreach ($this->available_languages as $code => $name) {
$lang_url = add_query_arg('lang', $code, $current_url);
$active_class = ($current_lang === $code) ? 'active' : '';
if ($show_flags) {
$flag = "<img src='" . get_template_directory_uri() . "/images/flags/{$code}.png' alt='{$name}' class='flag-icon'>";
$output .= "<a href='{$lang_url}' class='lang-link {$active_class}' data-lang='{$code}'>{$flag} {$name}</a>";
} else {
$output .= "<a href='{$lang_url}' class='lang-link {$active_class}' data-lang='{$code}'>{$name}</a>";
}
}
$output .= '</div>';
return $output;
}
private function get_current_url() {
return remove_query_arg('lang', $_SERVER['REQUEST_URI']);
}
// Helper function to get translated content
public function get_translated_content($post_id, $field = 'content', $lang = null) {
if (!$lang) {
$lang = $this->get_current_language();
}
if ($lang === $this->default_language) {
switch ($field) {
case 'title':
return get_the_title($post_id);
case 'content':
return get_post_field('post_content', $post_id);
case 'excerpt':
return get_post_field('post_excerpt', $post_id);
default:
return get_post_meta($post_id, $field, true);
}
}
return get_post_meta($post_id, "_{$field}_{$lang}", true);
}
}
// Initialize the multi-language system
new EnhancedMultiLanguageContent();
// Helper functions for theme developers
function get_multilang_content($post_id, $field = 'content', $lang = null) {
global $enhanced_multilang;
if (!$enhanced_multilang) {
$enhanced_multilang = new EnhancedMultiLanguageContent();
}
return $enhanced_multilang->get_translated_content($post_id, $field, $lang);
}
function display_language_switcher($show_flags = false) {
global $enhanced_multilang;
if (!$enhanced_multilang) {
$enhanced_multilang = new EnhancedMultiLanguageContent();
}
echo $enhanced_multilang->get_language_switcher($show_flags);
}
// Custom field translation support
function add_translatable_custom_fields($fields) {
// Add your custom fields here
$fields['hero_title'] = __('Hero Title', 'multilang-theme');
$fields['hero_subtitle'] = __('Hero Subtitle', 'multilang-theme');
$fields['cta_button'] = __('CTA Button Text', 'multilang-theme');
return $fields;
}
add_filter('multilang_custom_fields', 'add_translatable_custom_fields');
?>

Check Also

WP怎样实现ajax或者rest api的url 动态化

Here are the di …

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注