2013-08-12 3 views
19

Я работаю на многоязычном веб-сайте в PHP, а в файлах моих языков у меня часто есть строки, которые содержат несколько переменных, которые будут позже заполнены для завершения предложений.Замена переменных в строке

В настоящее время я помещаю {VAR_NAME} в строку и вручную заменяя каждое вхождение соответствующим его значением при его использовании.

Так в основном:

{X} created a thread on {Y}

становится:

Dany created a thread on Stack Overflow

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

И я уже проверил How replace variable in string with value in php?, и на данный момент я в основном использую этот метод.

Но я заинтересован в том, чтобы знать, есть ли встроенный (или, возможно, нет) удобный способ в PHP сделать это, учитывая, что у меня уже есть переменные, названные в точности как X и Y в предыдущем примере, больше как $$ для переменной переменной.

Так вместо того, чтобы делать str_replace на шпагат я бы, возможно, вызвать функцию следующим образом:

$X = 'Dany'; 
$Y = 'Stack Overflow'; 
$lang['example'] = '{X} created a thread on {Y}'; 

echo parse($lang['example']); 

распечатает бы из:

Dany created a thread on Stack Overflow

Спасибо!

Редактировать

Строки служат в качестве шаблонов и может быть использована несколько раз с разными входами.

Так что в основном делать "{$X} ... {$Y}" не будет делать трюк, потому что я потеряю шаблон, и строка будет инициализирована начальными значениями $X и $Y, которые еще не определены.

ответ

41

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

function parse(
    /* string */ $subject, 
    array  $variables, 
    /* string */ $escapeChar = '@', 
    /* string */ $errPlaceholder = null 
) { 
    $esc = preg_quote($escapeChar); 
    $expr = "/ 
     $esc$esc(?=$esc*+{) 
     | $esc{ 
     | {(\w+)} 
    /x"; 

    $callback = function($match) use($variables, $escapeChar, $errPlaceholder) { 
     switch ($match[0]) { 
      case $escapeChar . $escapeChar: 
       return $escapeChar; 

      case $escapeChar . '{': 
       return '{'; 

      default: 
       if (isset($variables[$match[1]])) { 
        return $variables[$match[1]]; 
       } 

       return isset($errPlaceholder) ? $errPlaceholder : $match[0]; 
     } 
    }; 

    return preg_replace_callback($expr, $callback, $subject); 
} 

Что это делать?

В двух словах:

  • Создать регулярное выражение с использованием указанного экранирующего символа, который будет соответствовать одна из трех последовательностей (подробнее об этом ниже)
  • поток, который в preg_replace_callback(), где обратного вызова точно обрабатывает две из этих последовательностей и рассматривает все остальное как операцию замены.
  • Возврат результирующая строка

Регулярное выражение

Регулярное выражение совпадает с любым из этих трех последовательностей:

  • Два вхождения экранирующего символа, за которым следует ноль или более вхождений из escape-символа, за которым следует открывающая фигурная скобка. Расходуются только первые два появления escape-символа. Это заменяется одним вхождением escape-символа.
  • Единственное появление escape-символа, за которым следует открывающая фигурная скобка. Это заменяется буквальной открытой фигурной скобкой.
  • Открытая фигурная скобка, за которой следуют один или несколько символов слова perl (альфа-число и символ подчеркивания), а затем закрывающая фигурная скобка. Это рассматривается как заполнитель, и поиск выполняется для имени между фигурными скобками в массиве $variables, если он найден, затем возвращает значение замены, если нет, то возвращает значение $errPlaceholder - по умолчанию это null, который обрабатывается как частный случай, и возвращается исходный заполнитель (т. е. строка не изменяется).

Почему это лучше?

Чтобы понять, почему это лучше, давайте посмотрим на подходы замены, принятые другими ответами. С one exception (единственный недостаток которого является совместимость с PHP 5.4 < и слегка неочевидное поведение), эти делятся на две категории:

  • strtr() - Это не обеспечивает механизм для обработки экранирующего символа. Что делать, если ваша строка ввода нуждается в литерале {X}? strtr() не учитывает это, и он будет заменен значением $X.
  • str_replace() - это страдает той же проблемой, что и strtr(), и еще одной проблемой. Когда вы вызываете str_replace() с аргументом массива для аргументов поиска/замены, он ведет себя так, как если бы вы вызывали его несколько раз - по одному для каждого из пар пар замены. Это означает, что если одна из ваших строк замены содержит значение, которое появляется позже в массиве поиска, вы также замените это.

Чтобы продемонстрировать эту проблему с str_replace(), рассмотрим следующий код:

$pairs = array('A' => 'B', 'B' => 'C'); 
echo str_replace(array_keys($pairs), array_values($pairs), 'AB'); 

Теперь, вы, вероятно, ожидать, что выход здесь, чтобы быть BC, но на самом деле будет CC (demo) - это потому что первая итерация заменила A на B, а на второй итерации строка темы была BB - поэтому оба этих события B были заменены на C.

Эта проблема также выдает оценку эффективности, которая может быть не сразу очевидной - поскольку каждая пара обрабатывается отдельно, операция равна O(n), для каждой пары замены выполняется поиск всей строки и выполняется одна операция замены. Если у вас была очень большая сюжетная строка и много пар замен, это значительная операция, проходящая под капотом.

Возможно это соображение производительности не является проблемой - вам потребуется очень большая строка и много пара замены, прежде чем вы получили значимое замедление, но это все-таки стоит помнить. Также стоит помнить, что регулярное выражение имеет собственные штрафы за производительность, поэтому в целом это соображение не должно включаться в процесс принятия решений.

Вместо этого мы используем preg_replace_callback(). Это посещает любую часть строки, которая ищет совпадения ровно один раз, в пределах предоставленного регулярного выражения. Я добавляю этот определитель, потому что, если вы напишете выражение, которое вызывает catastrophic backtracking, оно будет значительно больше одного раза, но в этом случае это не должно быть проблемой (чтобы избежать этого, я сделал единственное повторение в выражении possessive).

Мы используем preg_replace_callback() вместо preg_replace(), чтобы мы могли применять пользовательскую логику, ища строку замены.

Что это позволяет сделать

оригинальный пример из вопроса

$X = 'Dany'; 
$Y = 'Stack Overflow'; 
$lang['example'] = '{X} created a thread on {Y}'; 

echo parse($lang['example']); 

Это становится:

$pairs = array(
    'X' = 'Dany', 
    'Y' = 'Stack Overflow', 
); 

$lang['example'] = '{X} created a thread on {Y}'; 

echo parse($lang['example'], $pairs); 
// Dany created a thread on Stack Overflow 

Что-то более продвинутые

Теперь предположим, что мы имеем:

$lang['example'] = '{X} created a thread on {Y} and it contained {X}'; 
// Dany created a thread on Stack Overflow and it contained Dany 

... и мы хотим, чтобы второй {X} появляться буквально в результирующей строке. Использование экранирующего символа по умолчанию @, мы бы изменить его на:

$lang['example'] = '{X} created a thread on {Y} and it contained @{X}'; 
// Dany created a thread on Stack Overflow and it contained {X} 

ОК, выглядит хорошо до сих пор. Но что, если этот @ должен был быть буквальным?

$lang['example'] = '{X} created a thread on {Y} and it contained @@{X}'; 
// Dany created a thread on Stack Overflow and it contained @Dany 

Обратите внимание, что регулярное выражение было разработано, чтобы только обратить внимание, чтобы избежать последовательности, немедленно предшествуют открывающей фигурной скобки. Это означает, что вам не нужно избегать escape-символа, если он не появится сразу перед заполнителем.

Примечание об использовании массива в качестве аргумента

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

  1. ясность и безопасность - это гораздо проще, чтобы посмотреть, что будет в конечном итоге замещено, и вы не рискуете случайно заменяющие переменные, которые вы не хотите подвергаться. Было бы неплохо, если бы кто-то мог просто прокормить {dbPass} и посмотреть ваш пароль базы данных, не так ли?
  2. Область применения - невозможно импортировать переменные из области вызова, если вызывающий объект не является глобальной областью. Это делает функцию бесполезной, если вызвана из другой функции, а импорт данных из другой области - очень плохая практика.

Если вы действительно хотите использовать имена переменных из текущей области (и я не рекомендовать это из-за вышеупомянутых проблем безопасности) вы можете передать результат вызова get_defined_vars() ко второму аргументу ,

Примечание о выборе экранирующего символа

Вы заметите, что я выбрал @ в качестве эвакуационных частей по умолчанию. Вы можете использовать любой символ (или последовательность символов, его может быть несколько), передав его третьему аргументу - и у вас может возникнуть соблазн использовать \, так как это то, что используют многие языки, , но держитесь, прежде чем делать это ,

Причина вы не хотите использовать \ является , потому что многие языки используют его в качестве своего собственного побега характер, что означает, что если вы хотите, чтобы указать свой экранирующий символ, скажем, в PHP строковый литерал, вы запустите в эту проблему:

$lang['example'] = '\\{X}'; // results in {X} 
$lang['example'] = '\\\{X}'; // results in \Dany 
$lang['example'] = '\\\\{X}'; // results in \Dany 

Это может привести к читаемости кошмара, а некоторые неочевидные поведение со сложными узорами. Выберите escape-символ, который не используется каким-либо другим языком (например, если вы используете этот метод для генерации фрагментов HTML, не используйте & в качестве escape-символа).

Резюмируя

Что вы делаете имеет край-кейсы. Чтобы решить проблему должным образом, вам нужно использовать инструмент, способный обрабатывать эти кромки - и когда дело доходит до строковых манипуляций, инструмент для работы чаще всего является регулярным выражением.

+4

Отличный ответ, содержит все подробное объяснение, которое я искал, спасибо за то, что поделился своим опытом, и особенно с тем, почему это лучше, я очень ценю то время, которое вы вкладываете в пишу это :) –

+0

@DanyKhalife Нет проблем, рад помочь :-) – DaveRandom

+1

Это хороший ответ. @DaveRandom You noob, я потерял +15 за это! : P – Jimbo

0

Простой:

$X = 'Dany'; 
$Y = 'Stack Overflow'; 
$lang['example'] = "{$X} created a thread on {$Y}"; 

Следовательно:

echo $lang['example']; 

Выведет:

Dany created a thread on Stack Overflow 

Как вы просили.

UPDATE:

Согласно комментариям Op о том, чтобы решение более портативными:

Иметь класс сделать синтаксический анализ для вас каждый раз, когда:

class MyParser { 
    function parse($vstr) { 
    return "{$x} created a thread on {$y}"; 
    } 
} 

Таким образом, если происходит следующее:

$X = 3; 
$Y = 4; 

$a = new MyParser(); 
$lang['example'] = $a->parse($X, $Y); 

echo $lang['example']; 

, который возвратит:

3 created a thread on 4; 

И перепроверки:

$X = 'Steve'; 
$Y = 10.9; 

$lang['example'] = $a->parse($X, $Y); 

Напечатает:

Steve created a thread on 10.9; 

По желанию.

UPDATE 2:

Согласно комментариям Op по поводу улучшения переносимости:

class MyParser { 
    function parse($vstr) { 
    return "{$vstr}"; 
    } 
} 

$a = new MyParser(); 

$X = 3; 
$Y = 4; 
$vstr = "{$X} created a thread on {$Y}"; 

$a = new MyParser(); 
$lang['example'] = $a->parse($vstr); 

echo $lang['example']; 

будет выводить результаты, приведенные выше.

+0

пожалуйста, проверьте мой комментарий RiggsFolly в ответ –

+0

@DanyKhalife: обновленный. – jrd1

+0

интересно! один недостаток, хотя, из того, что я вижу, это требует от меня создания метода для каждого шаблона, который содержит переменные –

0

Попробуйте

$lang['example'] = "$X created a thread on $Y"; 

EDIT: на основе последней информации

Может быть, вы должны смотреть на функции Sprintf()

Тогда вы могли бы ваша строка шаблона определяется как этого

$template_string = '%s created a thread on %s'; 


$X = 'Fred'; 
$Y = 'Sunday'; 

echo sprintf($template_string, $X, $Y); 

$template_string не изменяется, но позже в вашем коде когда вы назначены различные значения $X и $Y вы все еще можете использовать echo sprintf($template_string, $X, $Y);

See PHP Manual

+0

жаль, что забыл упомянуть, что $ X и $ Y неизвестны, когда эта строка инициализирована –

+0

, чтобы сделать ее более понятной: это в основном инициализирует строку текущими значениями X и Y но в случае, если я хотел бы повторно использовать этот шаблон строки, я не могу ... –

+0

Вы могли бы установить условие для переменной '$ _SESSION'? – verbumSapienti

1

Почему бы не использовать str_replace тогда? Если вы хотите его как шаблон.

echo str_replace(array('{X}', '{Y}'), array($X, $Y), $lang['example']); 

для каждого вхождения этого, что вам нужно

str_replace был построен для этого в первую очередь.

+0

Я просто ищу более портативное решение –

+0

Что вы подразумеваете под более портативный, str_replace работает на всех ОС, на которых работает php? – bumperbox

+0

Не уверен, что я понимаю, что вы подразумеваете под более портативными? –

0

Как насчет определения «переменных» частей как массива с ключами, соответствующими заполнителям в вашей строке?

$string = "{X} created a thread on {Y}"; 
$values = array(
    'X' => "Danny", 
    'Y' => "Stack Overflow", 
); 

echo str_replace(
    array_map(function($v) { return '{'.$v.'}'; }, array_keys($values)), 
    array_values($values), 
    $string 
); 
+0

, вы определенно получили мое внимание со своим array_map :), но я бы хотел немного подождать и посмотреть, есть ли у кого-то способ сделать то, что не требует передачи массива переменных при каждом вызове –

11

Вот портативное решение, использующее переменные переменные. Ура!

$string = "I need to replace {X} and {Y}"; 
$X = 'something'; 
$Y = 'something else'; 

preg_match_all('/\{(.*?)\}/', $string, $matches);   

foreach ($matches[1] as $value) 
{ 
    $string = str_replace('{'.$value.'}', ${$value}, $string); 
} 

Сначала вы настроили свою строку и свои замены. Затем вы выполняете регулярное выражение, чтобы получить массив совпадений (строки в {и}, включая эти скобки). Наконец, вы обходите вокруг них и заменяете их теми переменными, которые вы создали выше, используя переменные переменные. Прекрасный!


Просто подумал, что я обновил бы это с помощью другого варианта, даже если вы отметили его как правильное. У вас не есть, чтобы использовать переменные переменные, и массив можно использовать в его месте.

$map = array(
    'X' => 'something', 
    'Y' => 'something else' 
); 

preg_match_all('/\{(.*?)\}/', $string, $matches);   

foreach ($matches[1] as $value) 
{ 
    $string = str_replace('{'.$value.'}', $map[$value], $string); 
} 

Это позволит вам создать функцию со следующей подписью:

public function parse($string, $map); // Probably what I'd do tbh 
+0

PERFECT спасибо! плохо просто оберните это в класс/функцию :) Должны ли я иметь какие-либо проблемы с точки зрения производительности, учитывая, что вы используете RE? –

+0

Это действительно простое регулярное выражение, здесь я не вижу никакой проблемы. Если вы действительно волнуетесь, выполните некоторые тесты производительности, но нет никакой микро-оптимизации - все будет хорошо. – Jimbo

+0

Огромное спасибо, также хорошая точка зрения на объем переменных: D –

2

strtr, вероятно, является лучшим выбором для такого рода вещей, потому что он заменяет длинные ключи первым:

$repls = array(
    'X' => 'Dany', 
    'Y' => 'Stack Overflow', 
); 

foreach($data as $key => $value) 
    $repls['{' . $key . '}'] = $value; 

$result = strtr($text, $repls); 

(подумайте о ситуациях, когда у вас есть такие ключи, как XX и X)


И если вы не хотите использовать массив и вместо того, чтобы раскрыть все переменные из текущей области:

$repls = get_defined_vars(); 
+0

+1 хорошая точка для замены более длинных ключей сначала :) –

0

Почему вы не можете просто использовать строку шаблона в функции?

function threadTemplate($x, $y) { 
    return "{$x} created a thread on {$y}"; 
} 
echo threadTemplate($foo, $bar); 
+0

, потому что мне нужно будет создать функцию для каждого шаблона, это выйдут из-под контроля в кратчайшие сроки :) –

-1

Возможно, другие функции кодирования нуждаются в функции синтаксического анализа;

$X = 'Dany'; 
$Y = 'Stack Overflow'; 
$lang['example'] = "{X} created a thread on {Y}"; 
function parse($var) { 
    $return_value = $var; 
    $matches_value = preg_match_all('/\{(.*?)\}/',$var,$matches); 
    if($matches_value > 0){ 
     foreach($matches[1] as $match){ 
      if(isset($GLOBALS[$match])){ // !empty($GLOBALS[$match]) -- you choose 
       $return_value = str_replace('{'.$match.'}',$GLOBALS[$match],$return_value); 
      } 
     } 
    } 
    return $return_value; 
} 
echo parse($lang['example']); 

если переменные $ X и $ Y определены всегда. с Eval:

$X = 'Dany'; 
$Y = 'Stack Overflow'; 
$lang['example'] = "{X} created a thread on {Y}"; 

$lang['example'] = preg_replace('/\\{(.*?)\\}/', '\\$\\1',$lang['example']); 
eval("\$return_value = \"".$lang['example']."\";"); 
echo $return_value; 
+0

lol, '$ GLOBALS' использование !? – Jimbo

+0

, хотя это работает, я бы, вероятно, хотел бы держаться подальше от $ GLOBALS –

+0

@ Jimbo вы можете делать с функцией без $ GLOBALS и параметров;) – mccakici

4

Если вы работаете 5.4 и вы заботитесь о возможности использования встроенной переменной интерполяции PHP в строке, вы можете использовать bindTo() метод Closure так:

// Strings use interpolation, but have to return themselves from an anon func 
$strings = [ 
    'en' => [ 
     'message_sent' => function() { return "You just sent a message to $this->recipient that said: $this->message."; } 
    ], 
    'es' => [ 
     'message_sent' => function() { return "Acabas de enviar un mensaje a $this->recipient que dijo: $this->message."; } 
    ] 
]; 

class LocalizationScope { 
    private $data; 

    public function __construct($data) { 
     $this->data = $data; 
    } 

    public function __get($param) { 
     if(isset($this->data[$param])) { 
      return $this->data[$param]; 
     } 

     return ''; 
    } 
} 

// Bind the string anon func to an object of the array data passed in and invoke (returns string) 
function localize($stringCb, $data) { 
    return $stringCb->bindTo(new LocalizationScope($data))->__invoke(); 
} 

// Demo 
foreach($strings as $str) { 
    var_dump(localize($str['message_sent'], array(
     'recipient' => 'Jeff Atwood', 
     'message' => 'The project should be done in 6 to 8 weeks.' 
    ))); 
} 

//string(93) "You just sent a message to Jeff Atwood that said: The project should be done in 6 to 8 weeks." 
//string(95) "Acabas de enviar un mensaje a Jeff Atwood que dijo: The project should be done in 6 to 8 weeks." 

(Codepad Demo)

Возможно, он чувствует себя немного взломанным, и мне не очень нравится использовать $this в этом случае. Но вы получаете дополнительное преимущество от использования интерполяции переменных PHP (что позволяет вам делать такие вещи, как экранирование, которых трудно достичь с помощью регулярных выражений).


EDIT: Добавлен LocalizationScope, который добавляет еще одно преимущество: нет предупреждений при локализации анонимные функции пытаются получить доступ к данным, которые не был предоставлен.

+1

отличный ответ! это было именно то, что я искал, но, к сожалению, я бегу 5.3 :(так что причина, по которой я не буду отмечать ваш ответ как выбранный :) –

+1

Это на самом деле действительно умный (+1), но мне потребовался минутку или около того, чтобы прочитать его, прежде чем я мог действительно увидеть, что он делает (в основном из-за проблемы '$ this', которую вы упомянули сами). Я бы избегал этого из-за неочевидного поведения, но в то же время это механически большое и очень гибкое решение проблемы. – DaveRandom

+0

@DaveRandom Спасибо! Я согласен с проблемой '$ this'. Когда я впервые написал код, я опустил его (предполагая, что с помощью javascript-esque scope bind), и вскоре обнаружил, что это не сработало. Если бы существовал способ позднего связывания переменных 'use', это могло бы быть более интуитивным, но на данный момент я полностью согласен с неочевидностью' $ this'. –

2

Если ваша единственная проблема с sprintf - это порядок аргументов, вы можете использовать обмен аргументами.

Из док (http://php.net/manual/en/function.sprintf.php):

$format = 'The %2$s contains %1$d monkeys'; 
echo sprintf($format, $num, $location); 
+0

Почему было ли это проголосовано? Кажется очевидным ответом на меня. Хотя порядок может меняться от языка к языку, вы знаете порядок при написании строки шаблона. Для использования примера OP «% 1 $ s создал поток на% 2 $ s» или «% 2 $ s» имеет новую запись% 1 $ s »или все, что поддерживается sprintf ($ template_string,« Dany », Переполнение стека'). Именно то, сколько систем решает переводы (например, Wordpress использует gettext и рекомендует передать вашу строку gettext через printf именно таким образом, если вам нужны параметры в строке) http://codex.wordpress.org/I18n_for_WordPress_Developers#Placeholders – Adam

+0

ОК. Я вижу, что OP хочет, чтобы переменные назывались именно так. Так как это требует документирования для каждой строки, я не уверен, что это того стоит. Поскольку он также сказал, что он коснулся sprintf, но уклонился от него из-за проблемы с упорядочением, я думаю, что это по-прежнему действительный ответ. – Adam

2

Gettext широко используется универсальная система локализации, которая делает именно то, что вы хотите. Есть библиотеки для большинства языков программирования и PHP имеет встроенный в двигатель. Он управляется po-файлами, простым текстовым форматом, для которого существует множество редакторов, и он совместим с синтаксисом sprintf.

У него даже есть некоторые функции для работы с такими вещами, как сложные множественные числа, которые есть на некоторых языках.

Вот несколько примеров того, что он делает. Обратите внимание, что _() является псевдонимом для Gettext():

  • echo _('Hello world'); // Выведет привет мир в текущем выбранном языке
  • echo sprintf(_("%s has created a thread on %s"), $name, $site); // переводит строку, и передает его Sprintf()
  • echo sprintf(_("%2$s has created a thread on %1$s"), $site, $name); // тот же, что и выше, но с измененным порядком параметров.

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

Проверка Википедии и документация PHP для основного обзора о том, как это работает:

Google находит кучу документации и ваш любимый репозиторий программного обеспечения, скорее всего, несколько инструментов для управления po-файлами.

Некоторые, которые я использовал являются:

  • PoEdit: Очень легкий и простой. Хорошо, если у вас нет слишком много материала для перевода, и вы не хотите тратить время на размышления о том, как это работает.
  • Virtaal: Немного сложнее и имеет немного кривой обучения, но также и некоторые приятные функции, которые облегчают вашу жизнь. Хорошо, если вам нужно много перевести.
  • GlotPress - это веб-приложение (от людей Wordpress), которое позволяет осуществлять совместное редактирование файлов базы данных перевода.
+0

Лучше ли gettext по сравнению с xml? Он отправляет запрос на сервер для перевода каждого сообщения? –

+0

Нет, это не имеет ничего общего с XML. И он не отправляет никаких запросов во время выполнения. Это почти набор строк, которые заданы для каждого языка. – MrTweek

0

просто метать другое решение при использовании ассоциативных массивов. Это будет проходить через ассоциативный массив и либо заменить шаблон, либо оставить его пустым.

пример:

$list = array(); 
$list['X'] = 'Dany'; 
$list['Y'] = 'Stack Overflow'; 

$str = '{X} created a thread on {Y}'; 

$newstring = textReplaceContent($str,$list); 


    function textReplaceContent($contents, $list) { 


       while (list($key, $val) = each($list)) { 
        $key = "{" . $key . "}"; 
        if ($val) { 
         $contents = str_replace($key, $val, $contents); 
        } else { 
         $contents = str_replace($key, "", $contents); 
        } 
       } 
       $final = preg_replace('/\[\w+\]/', '', $contents); 

       return ($final); 
      } 
+0

Zzzz Zzzzz Zzzzz zzZZzzzz :) – hakre

Смежные вопросы