Comments 27
Было бы неплохо примерчик работающий увидеть. На стороне сервера нужно все равно подстраивать вмето текста название этих ключей, а потом в js вручную создавать хеш значений. Я правильно понимаю как использовать эту библиотеку? Если это так, то я, как разработчик RoR, никакого смысла в этом не вижу. Это просто самописная js библиотека наподобие гема
i18n
(только вот i18n
работает и будет работать как надо без багов). Где нужно было применить вот эту библиотеку, на каком проекте? Очень хотелось бы знать +2
> На стороне сервера нужно все равно подстраивать вмето текста название этих ключей, а потом в js вручную создавать хеш значений
Я не понимаю до конца, что вы здесь имеете в виду.
А применить можно в любом веб-приложении, где необходима локализация.
Я не понимаю до конца, что вы здесь имеете в виду.
А применить можно в любом веб-приложении, где необходима локализация.
0
Я имел ввиду, что генерить код на стороне сервера нужно будет наподобие этого:
<a href='...'>this_is_cancel_link</a>
где this_is_cancel_link
— это ключ, который потом можно будет перевести с помощью вашей библиотеки и готового словаря. 0
Совершенно необязательно. Реализация связывания элементов с хэшем ложится на стороннего программиста, который использует эту библиотеку.
Да, можно использовать и приведённый вами пример; можно указывать ключ в атрибутах; можно вообще не указывать ключ, а совершать «накладывание» JavaScript-объекта с ключами на HTML-структуру.
Суть библиотеки не в замене ключей на их значения, а в изменении текущего значения элемента на новое при изменении словаря. Причём словарь может быть загружен или выгружен в произвольный момент времени.
Да, можно использовать и приведённый вами пример; можно указывать ключ в атрибутах; можно вообще не указывать ключ, а совершать «накладывание» JavaScript-объекта с ключами на HTML-структуру.
Суть библиотеки не в замене ключей на их значения, а в изменении текущего значения элемента на новое при изменении словаря. Причём словарь может быть загружен или выгружен в произвольный момент времени.
0
Хочется пример большой страницы, где реально много всего, мне кажется что если у вас нагруженная структура то можно легкой сойти с ума делаю подобное, тем более ведь не каждый элемент вы создаете динамически.
+2
Специально для вас:
clip2net.unet.by/page/m341/61867
Просто откройте при включенном интернете.
Сваял по-быстрому, поэтому возможны ошибки. Да и на английский не обижайтесь )
clip2net.unet.by/page/m341/61867
Просто откройте при включенном интернете.
Сваял по-быстрому, поэтому возможны ошибки. Да и на английский не обижайтесь )
+1
Не пробовали выдернуть innerHTML из document.body, а потом вставить назад со всеми заменами?
0
Я делал динамическую смену языка следующим образом:
— на клиенте шаблонизатор
— данные для рендеринга шаблонов кешируются на клиенте
— языковые файлы в отдельном хеше
— языковые фразы вставляются через шаблонизатор
— следовательно сменить хеш на другой язык и прорендерить страницу заново не составляет труда
— без обращения к серверу — так как данные для страницы закешированы
Понятно что все это делалось не ради динамической смены языка — она просто как бонус получилась сама собой.
— на клиенте шаблонизатор
— данные для рендеринга шаблонов кешируются на клиенте
— языковые файлы в отдельном хеше
— языковые фразы вставляются через шаблонизатор
— следовательно сменить хеш на другой язык и прорендерить страницу заново не составляет труда
— без обращения к серверу — так как данные для страницы закешированы
Понятно что все это делалось не ради динамической смены языка — она просто как бонус получилась сама собой.
0
Еще раз для вас:
Суть библиотеки не в замене ключей на их значения, а в изменении текущего значения элемента на новое при изменении словаря. Причём словарь может быть загружен или выгружен в произвольный момент времени.
Суть библиотеки не в замене ключей на их значения, а в изменении текущего значения элемента на новое при изменении словаря. Причём словарь может быть загружен или выгружен в произвольный момент времени.
0
> Хотя на самом деле я считаю, что линейку IE до восьмой версии включительно нужно давно похоронить.
Я это уже пол года как практикую — и искренне сочувствую тем кто по каким либо причинам вынужден поддерживать IE версии ниже 9
Я это уже пол года как практикую — и искренне сочувствую тем кто по каким либо причинам вынужден поддерживать IE версии ниже 9
+4
Любой проект, который делается для широких масс вынужден поддерживать IE7-8
0
Хочется Вам тоже посочувствовать… и вот почему:
Пожалуйста, попробуйте следующий сценарий:
IE — меню Сервис (нажать кнопку Alt, если не видно меню) — Свойства обозревателя — Дополнительно — поставьте галку «Не сохранять незашифрованные страницы на диск»
Перезапускаем браузер и пытаемся скачать любой файл с HTTPS-сайта
На IE9 и IE10 (Preview) у Вас это вряд ли выйдет.
На IE8 работает в большинстве сценариев (хотя если после перезапуска браузера сразу вбить прямую ссылку на файл на HTTPS-ресурсе — то тоже не скачает)
P.S. если сразу не сработает — то для большего эффекта попробуйте добавить сайт в надежные узлы.
Подобных багов несложно с десяток найти в разных версиях IE.
Если уж отказываться от IE — то от всех версий.
Если поддерживать IE — то хотя бы с 8 версии и выше (хотя бы потому, что 60-70% юзеров до сих пор сидят на Windows XP, для которого нет 9 версии IE — и только попробуйте им сказать «пользуйтесь другим браузером» — сразу в ответ получите «Я же Вас не прошу вместо Вашего (сисадминского) свитера носить нормальный костюм»).
Удачи!
Пожалуйста, попробуйте следующий сценарий:
IE — меню Сервис (нажать кнопку Alt, если не видно меню) — Свойства обозревателя — Дополнительно — поставьте галку «Не сохранять незашифрованные страницы на диск»
Перезапускаем браузер и пытаемся скачать любой файл с HTTPS-сайта
На IE9 и IE10 (Preview) у Вас это вряд ли выйдет.
На IE8 работает в большинстве сценариев (хотя если после перезапуска браузера сразу вбить прямую ссылку на файл на HTTPS-ресурсе — то тоже не скачает)
P.S. если сразу не сработает — то для большего эффекта попробуйте добавить сайт в надежные узлы.
Подобных багов несложно с десяток найти в разных версиях IE.
Если уж отказываться от IE — то от всех версий.
Если поддерживать IE — то хотя бы с 8 версии и выше (хотя бы потому, что 60-70% юзеров до сих пор сидят на Windows XP, для которого нет 9 версии IE — и только попробуйте им сказать «пользуйтесь другим браузером» — сразу в ответ получите «Я же Вас не прошу вместо Вашего (сисадминского) свитера носить нормальный костюм»).
Удачи!
0
Как клиентский программист, я с вами согласен, что IE можно поддерживать с восьмой версии, но не ниже. Потому что в плане рендеринга страниц восьмая версия, конечно, отстаёт от девятой, но имеет результат гораздно более близкий к ожидаемому. Эта проблема относится к верстальщикам. Огорчает IE и программистов, потому что в своё время IE8 был очень большим продвижением, но всё равно его функционала не хватает для нормальной разработки: постоянно приходится что-то дописывать.
И мне всё равно, что этот комментарий заминусуют. Но поймите же вы, что, идя на уступки в виде поддержки старых браузеров, вы замедляете развитие интернетов!
И мне всё равно, что этот комментарий заминусуют. Но поймите же вы, что, идя на уступки в виде поддержки старых браузеров, вы замедляете развитие интернетов!
0
С одной стороны — да, замедляю.
С другой стороны:
— допустим, у пользователя куплена лицензионная Windows XP. Ради Internet Explorer 9.0 покупать Windows 7 — зачем? Абонента все устраивает… он привык работать на ней… мало того — КПД сотрудика может упасть, пока он будет обучаться Windows 7
— допустим, пользователю ставят Mozilla Firefox, Google Chrome или Opera вторым браузером (единственным браузером не всегда получится — есть куча бизнес-сайтов, использующих ActiveX). В конце концов это закончится тем, что пользователь перестанет пользоваться вторым браузером, потому что он либо запутается в закладках, либо его достанут постоянные обновления браузера, которые «всегда не вовремя», либо еще какая беда случится — я не раз наблюдал такое собственными глазами
К сожалению (по крайней мере, в компании, где я работаю) таких пользователей больше 50 000…
Поэтому я немного переформулирую исходную фразу:
«версии поддерживаемых сайт браузеров зависят не только от желания разработчиков сайта, от задания заказчика и от тенденций развития технологий, но и от результатов анализа, кто же будет посещать этот сайт»
Например, сайт Microsoft на многих страницах до сих пор хорошо рендерится в IE6 (хотя уже и плохо в IE 5.5 на Windows 2000). Причина такой совместимости — вынужденная необходимость из-за большой посещаемости сайта Microsoft с браузера IE6 на Windows XP.
И выражаю большую благодарность всем тем, кто дочитал мое мнение до конца ;-)
С другой стороны:
— допустим, у пользователя куплена лицензионная Windows XP. Ради Internet Explorer 9.0 покупать Windows 7 — зачем? Абонента все устраивает… он привык работать на ней… мало того — КПД сотрудика может упасть, пока он будет обучаться Windows 7
— допустим, пользователю ставят Mozilla Firefox, Google Chrome или Opera вторым браузером (единственным браузером не всегда получится — есть куча бизнес-сайтов, использующих ActiveX). В конце концов это закончится тем, что пользователь перестанет пользоваться вторым браузером, потому что он либо запутается в закладках, либо его достанут постоянные обновления браузера, которые «всегда не вовремя», либо еще какая беда случится — я не раз наблюдал такое собственными глазами
К сожалению (по крайней мере, в компании, где я работаю) таких пользователей больше 50 000…
Поэтому я немного переформулирую исходную фразу:
«версии поддерживаемых сайт браузеров зависят не только от желания разработчиков сайта, от задания заказчика и от тенденций развития технологий, но и от результатов анализа, кто же будет посещать этот сайт»
Например, сайт Microsoft на многих страницах до сих пор хорошо рендерится в IE6 (хотя уже и плохо в IE 5.5 на Windows 2000). Причина такой совместимости — вынужденная необходимость из-за большой посещаемости сайта Microsoft с браузера IE6 на Windows XP.
И выражаю большую благодарность всем тем, кто дочитал мое мнение до конца ;-)
+1
У вас ужасный стиль кода. Написанные в uppercase идентификаторы обычно считаются константами, а у вас — нет. Это запутывает. Также, у вас дурацкие имена методов. Например params() — это setParams() или что?
Также, меня бесят конструкции вида:
if(!(strName instanceof Array))
strName=[strName];
Если по ошибке мы передадим неправильный параметр, функция, вместо того чтобы выругаться и упасть, продолжит работать как ни в чем не бывало. Это затрудняет обнаружение ошибок.
По поводу статьи, идея понятна, вместо того, чтобы вставить переведенный текст и забыть, мы храним ссылки на все DOM-элементы. В общем, мне идея не нравится, так как это серьезное изменение архитектуры и усложнение ради вещи, которую юзер делает в лучшем случае 1 раз в жизни. лучше наверно, как кто-то предложил в комментах, просто заново перегенерировать все виджеты на странице с учетом нового языка, если архитектура это позволяет.
Подход, типа «ИЕ не нужен» на мой взгляд, характеризует разработчика с отрицатеьной стороны. В ИЕ (включая 7 и иногда 6) можно делать почти все, что нужно в современном веб-приложении (а что нельзя. делается через флеш) — было бы желание.
Также, меня бесят конструкции вида:
if(!(strName instanceof Array))
strName=[strName];
Если по ошибке мы передадим неправильный параметр, функция, вместо того чтобы выругаться и упасть, продолжит работать как ни в чем не бывало. Это затрудняет обнаружение ошибок.
По поводу статьи, идея понятна, вместо того, чтобы вставить переведенный текст и забыть, мы храним ссылки на все DOM-элементы. В общем, мне идея не нравится, так как это серьезное изменение архитектуры и усложнение ради вещи, которую юзер делает в лучшем случае 1 раз в жизни. лучше наверно, как кто-то предложил в комментах, просто заново перегенерировать все виджеты на странице с учетом нового языка, если архитектура это позволяет.
Подход, типа «ИЕ не нужен» на мой взгляд, характеризует разработчика с отрицатеьной стороны. В ИЕ (включая 7 и иногда 6) можно делать почти все, что нужно в современном веб-приложении (а что нельзя. делается через флеш) — было бы желание.
+2
Толсто, друг мой.
Переменные верхнего регистра у меня обозначают замыкания, недоступные извне.
Если вас бесят конструкции приведенного вида, вы можете переписать их сами — было бы желание.
По поводу хранения ссылок: их можно и не хранить, но тогда при большом количестве элементов при траверсе HTML при обновлении словаря будут страшные тормоза на странице.
А еще у меня совсем не виджеты, а полноценные приложения. И да, архитектура намного сложнее, чем вы можете представить.
Пожалуйста, это ваше собственное желание — писать по IE. Кстати, да, вот вам баянчик.
И давайте не будем мыслить узко. Эта штука может не только языки менять, а текст как вам угодно в принципе. Например, кто-то независимо дописывает тексты в БД, и как только происходит сохранение, то изменения отражаются на клиенте почти моментально (например, с использованием лонг-поллинга или веб-сокетов).
Переменные верхнего регистра у меня обозначают замыкания, недоступные извне.
Если вас бесят конструкции приведенного вида, вы можете переписать их сами — было бы желание.
По поводу хранения ссылок: их можно и не хранить, но тогда при большом количестве элементов при траверсе HTML при обновлении словаря будут страшные тормоза на странице.
А еще у меня совсем не виджеты, а полноценные приложения. И да, архитектура намного сложнее, чем вы можете представить.
Пожалуйста, это ваше собственное желание — писать по IE. Кстати, да, вот вам баянчик.
И давайте не будем мыслить узко. Эта штука может не только языки менять, а текст как вам угодно в принципе. Например, кто-то независимо дописывает тексты в БД, и как только происходит сохранение, то изменения отражаются на клиенте почти моментально (например, с использованием лонг-поллинга или веб-сокетов).
-2
1. Соглашусь с предыдущим комментарием, код оформлен ужастно: проблемы с отступами, отсуствие пробелов, мешанина с именованием. Именуйте по общепринятыми правилам (КОНСТАНТЫ, Классы, остальныеНазвания) и прогоняйте ваш код через какой нибуть jsbeautifier.org перед публикацией.
2.
Так массивы не проверяют. В ES5 есть функция Array.isArray, для старых браузеров делается простая замена:
3.
Объясните, для чего вы делаете так:
Во всех этих случаях задавать имя функции не нужно. Хотя для первых функции не ясно зачем нужно делать из них переменные?
4. Вы первый на моей памяти человек который default запихнул в начало switch… Даже не знал что так будет работать :)
5. Свойство textContent относительно новое свойство в DOM, и предназначено для других целей нежели используете вы. Самый близкий аналог это innerText, который поддерживают все кроме Firefox (то есть работает в IE/Opera/Chrome/Safari). Для текстовых узлов, атрибутов (AttributeNode) и комментариев поменять из значение можно через свойство nodeValue. Либо же свойство data, но оно работает не для всех типов.
Поэтому если использовать nodeValue — то минус один «хак», который решает вами же созданную проблему, и только для IE8 (defineProperty работает только там) и только для текстовых узлов.
То есть замените «textContent» на «nodeValue» и проблема будет решена.
6. Самый грязный хак из вашего примера это:
Я намеренно не указал на IE, так как проверка не адресная: условие «v» == "\v" не определяет наличие или отсутствие фич у браузера. Здесь нужно проверять каждую фичу по отдельности.
7. indexOf входит в состав ES5 и относительно молодой метод, а это значит что реализация метода отсутсвует не только в IE8, но и в других браузерах (в версиях отличных от последних). Поэтому хорошо будет позаботиться о всех, и делается это так:
8. Да, compareDocumentPosition не поддерживается в IE до 9 версии. Но там есть неплохая альтернатива с sourceIndex.
Вы использовали «магическую» константу, что затрудняет понимание кода, а это всего лишь DOCUMENT_POSITION_CONTAINS. Если используете compareDocumentPosition, то используйте и соответствующие константы (либо указывайте это в комментариях). Итого, вы проверяете находится ли узел внутри document.body или нет. Для этого есть более простое решение:
Этот метод поддерживает даже IE6! А вот Firefox до недавнего времени его не имел, поэтому нужно сделать фикс для старых firefox
Можете так же почитать статью John Resig по этой теме, там так же есть реализация кроссбраузерного comparePosition.
9. Что касается самого метода.
Во-первых, добавление у вас N^2/2. Когда узлов будет много вставка будет тормозить.
Во-вторых, при каждом обновлении словаря перетрясается DOM, дергается compareDocumentPosition и все обновляется. Причем не важно используется ли новое значение узлом или нет (не увидел проверок).
А потом вы проверяете сколько времени уже затратили:
Хотели проверять время каждый десятый раз? Так вот проверяется оно 9 раз из 10… а вызов new Date операция дорогая.
Если переписать lang_set можно значительно ускорить:
Самая медленная операция — это запись в DOM. Потом чтение из DOM. А вот javascript вычисления значительно дешевле. У вас тут в лучшем случае (коих предполагаю будет большинство) будет только чтение из DOM (node[propName]) и вычисление нового значения — без обязательной записи в DOM.
Вообще код еще пилить и пилить ;)
2.
arrParams instanceof Array
Так массивы не проверяют. В ES5 есть функция Array.isArray, для старых браузеров делается простая замена:
if (typeof Array.isArray != 'function')
Array.isArray = function(value){
return Object.prototype.toString.call(value) === '[object Array]';
}
3.
Объясните, для чего вы делаете так:
hash_rebuild=function hash_rebuild(){
...
LANG_HASH[prop].replace(/%(\d+)/g,function rep(a,b){return params[b]||"";})
...
get:function get(strProp){
Во всех этих случаях задавать имя функции не нужно. Хотя для первых функции не ясно зачем нужно делать из них переменные?
4. Вы первый на моей памяти человек который default запихнул в начало switch… Даже не знал что так будет работать :)
switch(typeof data){
default:
return;
case "string":
LANG_PROPS_TO_UPDATE[data]=1;
break;
case "object":
lang_mixer(LANG_PROPS_TO_UPDATE,data);
}
5. Свойство textContent относительно новое свойство в DOM, и предназначено для других целей нежели используете вы. Самый близкий аналог это innerText, который поддерживают все кроме Firefox (то есть работает в IE/Opera/Chrome/Safari). Для текстовых узлов, атрибутов (AttributeNode) и комментариев поменять из значение можно через свойство nodeValue. Либо же свойство data, но оно работает не для всех типов.
Поэтому если использовать nodeValue — то минус один «хак», который решает вами же созданную проблему, и только для IE8 (defineProperty работает только там) и только для текстовых узлов.
То есть замените «textContent» на «nodeValue» и проблема будет решена.
6. Самый грязный хак из вашего примера это:
if("v"=="\v"){
.. какие то хаки для каких то браузеров
}
Я намеренно не указал на IE, так как проверка не адресная: условие «v» == "\v" не определяет наличие или отсутствие фич у браузера. Здесь нужно проверять каждую фичу по отдельности.
7. indexOf входит в состав ES5 и относительно молодой метод, а это значит что реализация метода отсутсвует не только в IE8, но и в других браузерах (в версиях отличных от последних). Поэтому хорошо будет позаботиться о всех, и делается это так:
// для старых браузер не имеющих реализации indexOf для массивов
if (typeof Array.prototype.indexOf != 'function')
Array.prototype.indexOf = function(value, offset){
offset = parseInt(offset);
for (var i = offset > 0 ? offset : 0, l = this.length; i < l; i++)
if (this[i] === value)
return i;
return -1;
};
8. Да, compareDocumentPosition не поддерживается в IE до 9 версии. Но там есть неплохая альтернатива с sourceIndex.
Вы использовали «магическую» константу, что затрудняет понимание кода, а это всего лишь DOCUMENT_POSITION_CONTAINS. Если используете compareDocumentPosition, то используйте и соответствующие константы (либо указывайте это в комментариях). Итого, вы проверяете находится ли узел внутри document.body или нет. Для этого есть более простое решение:
if (document.body.contains(node))
alert('node находится внутри document.body');
Этот метод поддерживает даже IE6! А вот Firefox до недавнего времени его не имел, поэтому нужно сделать фикс для старых firefox
if (typeof Node.prototype.contains != 'function')
Node.prototype.contains = function(node){
return !!(this.compareDocumentPosition(node) & this.POSITION_CONTAINED_BY)
}
Можете так же почитать статью John Resig по этой теме, там так же есть реализация кроссбраузерного comparePosition.
9. Что касается самого метода.
Во-первых, добавление у вас N^2/2. Когда узлов будет много вставка будет тормозить.
Во-вторых, при каждом обновлении словаря перетрясается DOM, дергается compareDocumentPosition и все обновляется. Причем не важно используется ли новое значение узлом или нет (не увидел проверок).
А потом вы проверяете сколько времени уже затратили:
if((LANG_UPDATE_LAST%10)&&(new Date()-date>50))
return;
Хотели проверять время каждый десятый раз? Так вот проверяется оно 9 раз из 10… а вызов new Date операция дорогая.
Если переписать lang_set можно значительно ускорить:
function lang_set(node, langKey, params){
var propName = params[0];
var newValue = langKey in LANG_HASH
? LANG_HASH[langKey].replace(/%(\d+)/g, function(a, b){return params[b]||""})
: "#" + prop + (params.length > 1 ? "(" + params.slice(1) + ")" : "");
if (node[propName] != newValue)
node[propName] = newValue;
};
Самая медленная операция — это запись в DOM. Потом чтение из DOM. А вот javascript вычисления значительно дешевле. У вас тут в лучшем случае (коих предполагаю будет большинство) будет только чтение из DOM (node[propName]) и вычисление нового значения — без обязательной записи в DOM.
Вообще код еще пилить и пилить ;)
+4
1-2. За совет спасибо.
3. Почитайте, что такое strict mode. Если мне вдруг придётся сделать рекурсивную функцию, то с помощью arguments.callee я не смогу обратиться к текущей анонимной функции. Во-вторых, при отладке я сразу же буду видеть по именам в стеке вызовов, где произошла ошибка. Это, кстати, для меня и есть главная причина писать имена. В-третьих, гляньте пример:
6-7. В заголовке написано — именно для IE8.
9. Признаю фейл с оператором %. И, да, я потерял проверку, когда переписывал эту функцию.
За пункты 5, 8 и 9 ставлю плюс.
Отдельная благодарность будет в статье.
3. Почитайте, что такое strict mode. Если мне вдруг придётся сделать рекурсивную функцию, то с помощью arguments.callee я не смогу обратиться к текущей анонимной функции. Во-вторых, при отладке я сразу же буду видеть по именам в стеке вызовов, где произошла ошибка. Это, кстати, для меня и есть главная причина писать имена. В-третьих, гляньте пример:
(function init() {
var f = function f() {
alert(f);
},
z = f;
f = 1;
alert(f);
z();
})();
В таком случае внутри функции f переменная f не будет замыканием.6-7. В заголовке написано — именно для IE8.
9. Признаю фейл с оператором %. И, да, я потерял проверку, когда переписывал эту функцию.
За пункты 5, 8 и 9 ставлю плюс.
Отдельная благодарность будет в статье.
0
3. Почитайте, что такое strict mode. Если мне вдруг придётся сделать рекурсивную функцию, то с помощью arguments.callee я не смогу обратиться к текущей анонимной функции.
Что-то я не увидел у в коде strict режима. Как в прочем не увидел у вас рекурсивных функций. И что мешает объявить функции, как функции, а не как выражение?
(function(){
function factorial(n){
if (n <= 1) return 1;
return n * factorial(n - 1);
}
function factorial5(){
return factorial(5);
}
...
})()
Во-вторых, при отладке я сразу же буду видеть по именам в стеке вызовов, где произошла ошибка. Это, кстати, для меня и есть главная причина писать имена.
В большинстве случаев, вы и так увидите имена функций, современные браузеры неплохо разрешают имена.
0
Я бы сказал, что слишком сложно. Может, быть, я не прав.
Я считаю, что чем проще структура, API, тем больше вероятность для фреймворка стать популярным.
В своей реализации я задействовал всего 6 методов для полноценной работы с переводами, в т.ч. с подставляемыми параметрами.
В JSGettext я даже рабочего примера не нашёл, да и как сваять его — тоже. Да и цели, по-моему, несколько различаются.
Вот это всё, что могу сказать.
Я считаю, что чем проще структура, API, тем больше вероятность для фреймворка стать популярным.
В своей реализации я задействовал всего 6 методов для полноценной работы с переводами, в т.ч. с подставляемыми параметрами.
В JSGettext я даже рабочего примера не нашёл, да и как сваять его — тоже. Да и цели, по-моему, несколько различаются.
Вот это всё, что могу сказать.
0
Sign up to leave a comment.
Динамический перевод страницы на другой язык