开发一个WP的多语言主题要求部署国际化i18n和本地化l10n,下面是完整的过程:
1:主题结构
首先组织你的主题结构来启用多语言支持:
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: 在functions.php里开启text domain
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: 国际化主题的文本
用国际化函数包裹所有文本
// 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: 多语言内容的翻译
创建内容的自定义翻译的方法
// 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: 创建语言的切换器
添加下面的函数来展示语言的切换
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代码 来支持切换
创建js/multilang.js 文件
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
.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: 生成可翻译的文件
创建 .pot 文件 用来翻译:
1:用WP-CLI : wp i18n make-pot . languages/theme-name.pot
2: 或者使用poedit来扫描你的主题文件
3: 给每种语言都创建.po 文件
4: 从 .po 文件来创建 .mo 文件
9: 处理菜单和小工具
对于不同语言的菜单导航的处理
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 优化
添加hreflang标签
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');
这个方法给了你不用依赖插件来完整的实现多语言主题的功能,你可以进行扩展比方说通过访客的浏览器来检测访客的语言并跳转到对应的语言的页面,RTL 语言的支持或者自定义文章类型的翻译
上面的方法也支持文章的内容的翻译
文章内容是怎么被翻译的
上面的代码创建了分别的翻译的字段为每篇文章和页面, 你可以存储以下翻译的版本:
- Post titles 文章标题
- Post Content 文章内容
- Any custom field you want to translate 你希望翻译的任意字段的内容
下面是完整的工作流
1. 管理界面
当编辑一个文章的时候,你可以看到字段翻译的地方比方说:
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. 内容存储
- 原始的内容存在wp_posts表里
- 翻译的内容存储在post meta : _title_es 对于西班牙语的标题,_content_fr对于法语的内容等
3:前端的展示
下面的过滤自动交换内容基于选择的语言
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
}
加强内容翻译的设置
下面是一个提升版本来处理更多的内容类型:
<?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');
?>
怎样来使用这个增强的系统
1: 编辑文章和页面的时候
当你编辑任何文章的时候,你会看到一个新的 content translations 的 带切换语言的字段框 来可以输入:
- 翻译过的标题
- 翻译过的内容(和全部wordpress编辑器)
- 翻译过的excerpts
- 翻译过的自定义字段
2:在前端显示语言切换器
在页面模板添加下面函数即可
// 简单文字语言导航
display_language_switcher();
// 带旗帜icon的切换器
display_language_switcher(true);
3: 程序化获取翻译过的内容
// 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: 内容的自动切换
这个系统自动展示翻译过的内容基于选择的语言,如果没有翻译过的内容,就会显示原始的内容。
什么会被翻译
- post/page 内容: wp编辑器里所有的内容
- titles: 任何标题
- excerpt: 任何摘要
- custom fields: 任意自定义字段
- 主题的文本: 任何主题设置的文本,通过翻译函数包裹的文本
前端使用范例
// 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>