Изменение языка интерфейса по клику на кнопку

5 дек. 2011 г., 20:53:52
Просмотры: 14.2K
Голосов: 22

Цель — предоставить кнопку/выпадающий список или подобный элемент для переключения языка публичного интерфейса1) на лету.

Что я ищу:

  • Плагин или код для темы...
  • ... или идеи
  • Желательно использование обходного пути с load_textdomain(), чтобы оставить переводы в .po/.mo файлах
  • Идеи по парсингу строк в интерфейс (например, через ajax/чистый php/ini, json, xml файлы), если не полагаться на функции textdomain

Примечания:

1) Речь не о публикации контента на разных языках.

2) Мне не нужен код для создания самого выпадающего списка/кнопки и т.п. Только код/система, которая будет доставлять строки для интерфейса.

Спасибо!

5
Комментарии

Если вас устраивает обновление страницы, переопределение константы WPLANG может быть вариантом. Я использую это на двух сайтах с многоязычным контентом, где плагин мультиязычности не срабатывает для перевода интерфейса. Если же требуется решение через AJAX/JSON, я не смогу помочь.

Johannes Pille Johannes Pille
5 дек. 2011 г. 21:01:45

@JohannesPille В какой момент/на каком хуке вы переключаете? Кстати: не могли бы вы перенести это в ответ. Уже заслуживает голоса. Спасибо.

kaiser kaiser
5 дек. 2011 г. 21:04:22

Я делаю это в плагине, без использования хуков. Это довольно грязный костыль, и я не особо им горжусь, но на тот момент он решил задачу. Вы можете увидеть код плагина в этом pastebin. Что он делает - понятно из кода. Сайт, с которого я скопировал это, использует последнюю бесплатную версию WPML. Ладно, перенес в ответ.

Johannes Pille Johannes Pille
5 дек. 2011 г. 21:33:59

Ссылка на pastebin, похоже, не работает.

PapaFreud PapaFreud
20 мар. 2012 г. 16:04:48

@JohannesPille Не могли бы вы опубликовать свой код в обновлении к вашему вопросу или добавить pastebin, который не истекает?

kaiser kaiser
20 мар. 2012 г. 16:59:56
Все ответы на вопрос 3
5
15

Самый простой и лучший способ - использовать фильтр локали (внутри функции 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();
5 июл. 2012 г. 16:21:51
Комментарии

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

kaiser kaiser
6 июл. 2012 г. 14:36:16

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

Stephen Harris Stephen Harris
6 июл. 2012 г. 14:57:16

Отлично. В закладки. +1

Johannes Pille Johannes Pille
6 июл. 2012 г. 19:50:13

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

Stephen Harris Stephen Harris
6 июл. 2012 г. 19:54:46

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

kaiser kaiser
7 июл. 2012 г. 14:15:46
5

Если вас устраивает обновление страницы, переопределение константы WPLANG может быть вариантом. Я использую этот подход на двух сайтах с многоязычным контентом, где плагин мультиязычности не срабатывает для перевода интерфейса.

5 дек. 2011 г. 21:23:47
Комментарии

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

Joshua Abenazer Joshua Abenazer
5 дек. 2011 г. 21:44:46

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

kaiser kaiser
5 дек. 2011 г. 23:18:07

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

Johannes Pille Johannes Pille
6 дек. 2011 г. 00:40:40

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

kaiser kaiser
6 дек. 2011 г. 03:19:00

Я снова задумался об этом (решение до сих пор не встроено в проект): Не мог бы ты обернуть это в ajax? Думаю, это был бы лучший способ обойти проблему. Я хочу закрыть вопрос и отметить его как решение :) Спасибо.

kaiser kaiser
4 февр. 2012 г. 05:21:44
5

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. :-) несмотря на то, что это не ООП - это гениальный плагин, из которого можно многому научиться в разных областях. учитывая "когда" вышел этот плагин, это просто удивительно. стоит неописуемых усилий прочитать НЕ ООП код - даже если он не ООП. И даже если он неправильно отформатирован (боже мой!).

5 дек. 2011 г. 22:30:30
Комментарии

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

kaiser kaiser
5 дек. 2011 г. 23:24:40

@kaiser - см. правку I для части "про браузер".

krembo99 krembo99
8 июл. 2012 г. 12:19:43

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

krembo99 krembo99
8 июл. 2012 г. 12:49:32

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

kaiser kaiser
8 июл. 2012 г. 14:45:17

Пожалуйста, не оставляйте комментарии для меня. Вносите правки в свой ответ, чтобы все могли ознакомиться с вашими идеями. Примечание: imho внешний код всегда следует добавлять как Plugin/MU-Plugin/Theme/DropIn, чтобы его можно было перемещать.

kaiser kaiser
8 июл. 2012 г. 18:40:23