Изменение языка интерфейса по клику на кнопку
Цель — предоставить кнопку/выпадающий список или подобный элемент для переключения языка публичного интерфейса1) на лету.
Что я ищу:
- Плагин или код для темы...
- ... или идеи
- Желательно использование обходного пути с
load_textdomain()
, чтобы оставить переводы в .po/.mo файлах - Идеи по парсингу строк в интерфейс (например, через ajax/чистый php/ini, json, xml файлы), если не полагаться на функции textdomain
Примечания:
1) Речь не о публикации контента на разных языках.
2) Мне не нужен код для создания самого выпадающего списка/кнопки и т.п. Только код/система, которая будет доставлять строки для интерфейса.
Спасибо!

Самый простой и лучший способ - использовать фильтр локали (внутри функции get_locale()
).
Сначала создадим быструю функцию для получения другого языка, который будет использоваться в фильтре locale
.
/**
* Функция возвращает выбранную пользователем локаль, если она сохранена.
*/
function wpse35622_get_new_locale($locale=false){
$new_locale = get_user_meta(get_current_user_id(), 'wpse_locale', true);
if($new_locale)
return $new_locale;
return $locale;
}
Я оставил эту функцию глобальной, может пригодиться... Остальную часть плагина я обернул в класс.
Он создает выпадающий список доступных языков, из которого можно выбрать нужный.
Недостатки
- Ищет файлы вида
xx_xx.mo
в папкеwp-content/languages
. Если файл имеет другой формат (не все .mo файлы соответствуют), плагин его не обнаружит! - Выпадающий список показывает найденные локали в формате 'xx_xx', что не очень удобно для восприятия - есть идеи как улучшить?
- Влияет ли
$locale
на что-то кроме языка? Если да, то все равно можно оставить локаль той же, но предоставить альтернативные переводы. Хотя это будет более грязное решение...
Код
class SH_Pick_Lang{
/**
* Уникальное имя для этого плагина
*/
static $name ='sh_pick_lang';
/**
* Подключаем функции
*/
public function __construct(){
if( isset($_POST[self::$name]) ){
add_action('locale',array($this,'update_user'));
}
add_filter( 'locale', 'wpse35622_get_new_locale',20 );
add_action( 'wp_before_admin_bar_render', array( $this, 'admin_bar') );
}
/**
* Обновляем настройку пользователя в последний момент! Только один раз...
*/
function update_user($locale){
$save_locale = $_POST[self::$name];
update_user_meta(get_current_user_id(), 'wpse_locale', $save_locale);
remove_filter(current_filter(),__FUNCTION__);
return $locale;
}
/**
* Добавляем не очень красивый выпадающий список, который, я уверен, Kaiser сделает потрясающим.
*/
function admin_bar(){
global $wp_admin_bar;
$wp_admin_bar->add_menu( array(
'id' => 'shlangpick',
'title' => $this->wpse_language_dropown(),
) );
}
/**
* Генерирует список доступных локалей.
* Ищет в папке wp-content/languages файлы вида xx_xx.mo
*
* TODO Не все локали имеют вид xx_xx.mo - можем пропустить некоторые.
* TODO Лучший способ получить путь к wp-content/languages без использования константы?
*/
function wpse_language_dropown(){
$name = self::$name;
$locale = get_locale();
$langs = get_available_languages();
$langs[] = 'en_US';
//Для меток (format_code_lang)
require_once( ABSPATH . 'wp-admin/includes/ms.php');
$html = "<form method='post'> ";
$html .= "<select name='{$name}'>";
foreach($langs as $lang){
$label = format_code_lang( $lang );
$html .= "<option ".selected( $lang, $locale, false)." value='{$lang}'>{$label}</option>";
}
$html .= "</select>";
$html .= get_submit_button('Изменить язык','secondary', 'submit', true);
$html .= "</form >";
return $html;
}
} // Конец класса
//Инициализация...
$sh_pick_lang = new SH_Pick_Lang();

Голос засчитан (смеюсь над всеми встроенными комментариями). Награда установлена и будет вручена вам, как только это станет возможным (осталось 23 часа - напомните мне, если я забуду).

Обновлено в соответствии с ответом на этот вопрос.

Я заметил, что простое подключение 'wp-admin/includes/ms.php'
вызывает административное уведомление об обновлении сетевых сайтов (когда у вас всего один сайт!). Возможно, что format_code_lang()
придется продублировать внутри пользовательской функции.

Я добавил Gist с моей очисткой - не было времени протестировать, так как сейчас я разбираюсь с папками плагинов, и это означает, что другие плагины не работают. Надеюсь, вы сможете работать с предоставленным gist и, возможно, форкнуть/обновить. Я могу вручить награду через полчаса, но сейчас мне нужно уйти, поэтому отправлю завтра. Спасибо за всю вашу работу - это должно быть в стандартной установке :)

Хм.. вы можете использовать ajax для замены тела страницы или всего html-тега, после выполнения вышеуказанного кода (в том же ajax-запросе), по клику на вашу кнопку или что-то подобное.

+1. Только что посмотрел ваш pastebin. Это означает, что у вас настройка http://example.com/language/whatever/
, верно?

@kaiser Да, именно так настроен сайт, откуда взят код. Если вас интересует только язык интерфейса, передача URL query string или установка переменной $_SESSION
для переопределения WPLANG
при следующей загрузке страницы должна сработать. Если же вам нужно реализовать это на многоязычном сайте, обратите внимание, что, как говорится в комментарии к pastebin, код в нем работает с WPML, последней бесплатной версией. Он не может быть воспроизведён один в один с qTranslate, потому что эти URI не физические, а переписываются через модуль apache mod_rewrite
. Уверен, что его можно адаптировать, но "как есть" он работать не будет.

Я не буду использовать qTranslate или WPML. Речь идет только о языке интерфейса. Добавьте $_SESSION
) Моя проблема в том, что, насколько я знаю, с ними довольно сложно работать, и данные между глобальными переменными и массивом могут легко перемешаться. Так что, если я попытаюсь найти способ изменить WPLANG
, то мне нужно найти надежный способ передавать его от одного запроса к другому. Возможно, add_query_arg/get_query_var
поможет решить эту задачу...

http://www.qianqin.de/qtranslate/
это то, что вам нужно...
Правка I - после комментария.
Во-первых - спасибо всем, кто внес свой вклад в поток минусов.
(вот что бывает, когда не заходишь достаточно часто :- ) )
Теперь -
Функция, которая обрабатывает переключение, находится в qtranslate-core.
(начинается примерно с 80 строки - зависит от версии, которую вы хотите.)
Поскольку вы не можете заглянуть в НЕ ООП код, а у меня сейчас больше нечего делать - я потратил 10 минут, чтобы синтезировать, перефразировать и скомпилировать этот не-OOP код с еще одним не-OOP кодом для вас.
(извините, я старомодный примитив)
Предполагая, что я правильно понял вопрос && Предполагая, что вы хотите, чтобы код был для админки && предполагая, что вы знаете, как перенести его на фронтенд, если хотите && предполагая, что вы понимаете, что код не оптимален:
<?php
/*
Plugin Name: k99 language switcher
Plugin URI: http://www.krembo99.com
Description: Админ-переключатель языков - только концепт, не использовать в продакшене.
Version: 0.0.0.0.0.0.0.1
Author: Krembo99
Author URI: http://www.krembo99.com
*/
?>
<?php function k99_add_language_menu() {
// k99_load_ajax_display_functions();
?>
<div class="mgmb_help_setting">
<?php _e('Язык:','your_text_domain'); ?>
<select name="mgmb_language_setting_help" id="mgmb_language_setting_help" onChange="mgmb_set_language_cookies(this.value);" >
<option value="en_US" <?php if($_COOKIE['k99_userLang']=="en_US"){echo "selected";} ?>><?php _e('Английский','your_text_domain');?></option>
<option value="de_DE" <?php if($_COOKIE['k99_userLang']=="de_DE"){echo "selected";} ?>><?php _e('Немецкий','your_text_domain');?></option>
<option value="zh_CN" <?php if($_COOKIE['k99_userLang']=="zh_CN"){echo "selected";} ?>><?php _e('Китайский','your_text_domain');?></option>
</select>
</div>
<?php
}
// Теперь мы настраиваем эту функцию на выполнение при вызове контекстной помощи
add_filter('contextual_help', 'k99_add_language_menu');
// Полагаю, это ваша загадочная "браузерная штука"
function k99_language_change($lang){
global $locale;
// wp_cache_set( "language", $lang, 'options' );
if ( isset($_COOKIE['k99_userLang'])) {
$lang = $_COOKIE['k99_userLang'];
}
define( 'WPLANG', $lang );
if($locale!= $lang) {
$locale = $lang;
load_plugin_textdomain('your_text_domain', false, dirname( plugin_basename(__FILE__) ) . '/lang'); // пример использования для текстового домена плагина
load_plugin_textdomain('your_text_domain2', false, dirname( plugin_basename(__FILE__) ) . '/lang');
load_plugin_textdomain('your_text_domain3', false, dirname( plugin_basename(__FILE__) ) . '/lang');
}
return $locale;
}
add_filter('locale', 'k99_language_change',99);
////////// +++++++++++++++ НАЧАЛО СОЗДАНИЯ COOKIES +++++++++++++++//////////////////
// JavaScript функция для установки языковых cookies
// внешний доступ
// @param lang - код языка
// @return NULL
// или, может быть, это та самая загадочная "браузерная штука" автора вопроса ??
function mgmb_print_script() {
?>
<script type="text/javascript" >
function mgmb_set_language_cookies(lang){
var Then = new Date();
Then.setTime(Then.getTime() + 10000*60*60*1000 ); //устанавливаем срок действия cookie на 10000 часов (час*минута*секунды*1000)
document.cookie = "k99_userLang="+lang+";expires="+ Then.toGMTString();
window.location.reload();
}
</script>
<?php
}
add_action('admin_print_scripts', 'mgmb_print_script'); // это не самый правильный способ.. но сейчас нет времени.
?>
ЗАМЕЧАНИЯ:
1 - Это было собрано за 10 минут на локальной машине в аэропорту.
Причина, по которой я это говорю, в том, что единственная готовая установка WordPress, которая у меня здесь есть, довольно старая. (2.9, кажется).
Это означает, что, вероятно, языковое меню появится ПОД контекстной справкой из-за того, что недавно была введена новая структура div (с вкладками) - но все равно должно работать.
(Обещаю вернуться к этому позже, если будет время)
Если бы у меня было больше времени (и новый код WordPress здесь), я бы, вероятно, интегрировал это в новую админ-панель с add_action( 'admin_bar_menu', 'k99_add_language_menu_2', 1000 );
(я уже обещал вернуться к этому)
2 - у вас должны быть перечисленные языковые файлы внутри папки "languages" в wp-content (лучше??) или wp-include.
касательно этого момента - код только концепт - и конечно, если есть больше времени, меню должно строиться динамически, а не так, как сделано здесь.
3 - ваш WPLANG в конфиге должен быть пустым. Не уверен, что это будет работать в новых версиях WP, если язык уже установлен.
4 - это простой синтезированный и перефразированный концепт, построенный на том, что было замечено в коде qtranslate (тоже старая версия) - так что большинство вещей сделано "быстрым" способом, не обязательно "правильным" (как добавление JS, отсутствие выделенного текстового домена и т.д.).
Правка II
кому-то сегодня повезло! (Задержка рейса)
Поскольку вы искали "идеи" для подходов, вот еще один известный способ.
Он не подойдет для плагина или вообще любой неэксклюзивной среды, но может дать вам еще одно направление в обработке "браузерной штуки".
Это позволит получать язык через GET, например: <a href="index.php?lang=de">Немецкий</a>
или <a href="whatever.php?lang=ml">мой Язык</a>
Для использования можно создать код (назовем его wp-langswitch.php)
session_start();
if ( isset( $_GET['lang'] ) ) {
$_SESSION['WPLANG'] = $_GET['lang'];
define ('WPLANG', $_SESSION[WPLANG]);
} else {
if(isset($_SESSION['WPLANG'])) {
define ('WPLANG', $_SESSION['WPLANG']);
$_GET['lang'] = $_SESSION['WPLANG'];
} else {
if ( isset( $_SERVER["HTTP_ACCEPT_LANGUAGE"] ) ) {
$languages = strtolower( $_SERVER["HTTP_ACCEPT_LANGUAGE"] );
$languages = explode( ",", $languages );
$_SESSION['WPLANG'] = $languages[0];
$_SESSION['WPLANG'] = str_replace("-", "_", $_SESSION['WPLANG']);
$_GET['lang'] = substr($_SESSION['WPLANG'],0,2);
define ('WPLANG', $_SESSION[WPLANG]);
} else {
define ('WPLANG', '');
}
}
}
Теперь в wp-config, прямо перед константой WPLANG, нам нужно подключить наш код.
require_once(dirname(__FILE__).'/wp-langswitch.php');
define ('WPLANG', '');
Теперь - очевидно, было бы лучше добавить куки для хранения языков.
Правка III
Я обещал вернуться к этому позже - вот как (очень некорректным и примитивным способом) это можно интегрировать в админ-панель (вместо контекстного меню справки)
function k99_add_language_menu_bar() {
global $wp_admin_bar, $wpdb;
if ( !is_super_admin() || !is_admin_bar_showing() )
return;
// Уверен, вы сможете найти способ построить массив, читая папку. Если нет - комментируйте, и я обновлю снова.
if($_COOKIE['k99_userLang']=="en_US"){$sel==$_COOKIE['k99_userLang'];}
if($_COOKIE['k99_userLang']=="de_DE"){$sel==$_COOKIE['k99_userLang'];}
if($_COOKIE['k99_userLang']=="zh_CN"){$sel==$_COOKIE['k99_userLang'];}
$k99_lang = '';
$k99_lang .= '<select name="mgmb_language_setting_help" id="mgmb_language_setting_help" onChange="mgmb_set_language_cookies(this.value);" >';
$k99_lang .= '<option value="en_US" >Английский</option>';
$k99_lang .= '<option value="de_DE" >Немецкий</option>';
$k99_lang .= '<option value="zh_CN" >Китайский</option></select>';
/* Добавляем основной пункт меню админки */
$wp_admin_bar->add_menu( array( 'id' => 'Language', 'title' => __( 'язык', 'your_text_domain3' ), 'href' => FALSE ) );
$wp_admin_bar->add_menu( array( 'parent' => 'Language', 'title' => $k99_lang, 'href' => FALSE ) );
}
add_action( 'admin_bar_menu', 'k99_add_language_menu_bar', 999 );
Просто замените эту функцию (или добавьте) к старому коду (оригинальный НЕ-OOP не-плагин).
Как я уже сказал, у меня нет новой установки WordPress здесь на локальной машине с админ-панелью - но это должно работать.
(а может и не работать...но я уверен, вы сможете это исправить, если действительно захотите - даже несмотря на то, что это не ООП) :-)
Мне пора. Надеюсь, это как-то поможет.
Правка IV - изменена Правка III на рабочую версию для админ-панели (по крайней мере на моем wp 3.4)
так как у меня не было рабочей установки 3.4 - а теперь я вернулся, и она есть - я отредактировал решение из Правки III - и оно работает у меня.
Что касается плюсов, мне все равно. И мне не нужны никакие "награды" (что бы это ни было...). Это было просто ради упражнения. Может, вам стоит отдать их автору qTranslate. :-) несмотря на то, что это не ООП - это гениальный плагин, из которого можно многому научиться в разных областях. учитывая "когда" вышел этот плагин, это просто удивительно. стоит неописуемых усилий прочитать НЕ ООП код - даже если он не ООП. И даже если он неправильно отформатирован (боже мой!).

Возможно, решение зарыто в глубинах этого кода, но разобраться во всех возможных вариантах практически невозможно, так как он не написан в ООП-стиле. Если вы точно знаете, где находится определение языка (часть про браузер) и как оно фильтрует строки gettext, дайте мне знать.

@kaiser ..и см. правку II для дополнительной информации "про браузер" - и правку III о том, как интегрировать "часть про браузер" внутрь "браузера (части)".

Хорошо, я проголосовал за (не против, чтобы отменить здесь), за ваши усилия. Идея использования куки хороша. В любом случае, вот несколько замечаний: Редакция II выдаст ошибку после require()
. Нельзя использовать define()
с одной и той же строкой дважды. Редакция III имеет проблемы с языками. Выбор en_US
ничего не сделает(?), а de_DE
установит его в English
;). Подводя итог: я назначаю награды и выдаю их (например, StephenHarris) за полный и рабочий ответ. Если у вас есть что-то подобное, пожалуйста, обновите свой ответ (подход с куки интересен), и я могу добавить и наградить вас наградой.
