В рамках изучения нового стандарта наткнулся на Tagged Template Literals, по-русски часто обзывают строковые шаблоны.
За рубежом советуют переименовать в tagged string literals, а Template Literals тем временем в interpolated string literals или просто interpoliterals . Обожаю js тусовку, где халивар начинают даже из-за название фичи :)
Что, куда, зачем
Прежде чем перейдем непосредственно к тому о чем я хотел рассказать исходя из заголовка, пару слов о Template Literals.
Наверняка каждый из вас их уже попробовал:
var text =
`Now is the time for all good men
to come to the aid of their
country!`;
console.log( text );
// Now is the time for all good men
// to come to the aid of their
// country!
Первая их крутость, заключается в том, что текст такого литерала можно писать на нескольких строчках и он сам где надо вставит переход на новую строку.
Конечно же вторая крутость заключается в возможности вставки выражений в литерал, что значительно упрощает построение строк в которые вы хотите вставить какое-либо значение.
До ES6:
var name = "Kyle";
var greeting = "Hello " + name + "!";
console.log( greeting ); // "Hello Kyle!"
console.log( typeof greeting ); // "string"
Решение проблемы с помощью Template Literals:
var name = "Kyle";
var greeting = `Hello ${name}!`;
console.log( greeting ); // "Hello Kyle!"
console.log( typeof greeting ); // "string"
Тут конечно надо заметить, что переиспользовать данный литерал с другим значением нельзя. Поэтому ходят споры об именовании. Куда было бы разумней назвать не используя слово шаблон, а интерполяция, что бы не вводить в заблуждение. Данный литерал подобен Immediately-Invoked Function Expression (IIFE), он сразу же запекается в строку. Поэтому передать его и переиспользовать к сожалению возможности нет:
function foo(str) {
var name = "foo";
console.log( str ); //name уже давно в str и str typeof 'string'
}
function bar() {
var name = "bar";
foo( `Hello from ${name}!` );
}
var name = "global";
bar();
Как я писал выше, вставить в строку можно и результат выражения:
function upper(s) {
return s.toUpperCase();
}
var who = "reader";
var text =
`A very ${upper( "warm" )} welcome
to all of you ${upper( `${who}s` )}!`;
console.log( text );
// A very WARM welcome
// to all of you READERS!
Tagged Template Literals
А теперь самое интересное, рассмотрим с вами использование Tagged Template Literals:
function foo(strings, ...values) {
console.log( strings );
console.log( values );
}
var desc = "awesome";
foo`Everything is ${desc}!`;
// [ "Everything is ", "!"]
// [ "awesome" ]
В данном случае
tag foo
, своего рода вызов функции без (...)
. Таким образом, мы можем обработать строку дополнительно.В качестве аргументов, функция
foo
получает массив строк, которые были вокруг выражения ${desk}
, и собственно сами значения выражений. С помощью такого сахарного синтаксиса, очень просто представить себе перевод строк:
const greeting = lang`Hello ${ name }!`
Как по мне куда проще нежели — учитывая, что в строке есть динамические данные:
const greeting = `${ lang('Hello') } ${ name }!`
Или совсем сносный пример который можно часто увидеть:
const greeting = lang('Hello') + name + '!';
Так как мы получаем в функции по факту разобранную строку на части, нам придется ее самим собрать, с этим нам поможет reduce:
function lang(strings, ...values) {
return strings.reduce( function(s,v,idx){
return s + (idx > 0 ? values[idx-1] : "") + v;
}, "" );
}
Добавим вызов функции перевода и сделаем пример чуть более читабельным, получив вот такой сниппет:
const lang = l = (strings, ...values) =>
strings.reduce((prevString, nextString, index) =>
prevString + (index > 0 ? values[index - 1] : '') + translate(nextString), ''); //Реализация функции translate в ваших руках :)
Использование:
const name = 'Дмитрий';
let greeting = lang`Hello ${ name }, how are you?`;
//Или сокращенный
greeting = l`Hello ${ name }, how are you?`;
//Привет Дмитрий, как дела?
Еще один очень полезный пример перевода чисел в формат US $:
function dollabillsyall(strings, ...values) {
return strings.reduce( function(s,v,idx){
if (idx > 0) {
if (typeof values[idx-1] == "number") {
// look, also using interpolated
// string literals!
s += `$${values[idx-1].toFixed( 2 )}`;
}
else {
s += values[idx-1];
}
}
return s + v;
}, "" );
}
var amt1 = 11.99,
amt2 = amt1 * 1.08,
name = "Kyle";
var text = dollabillsyall
`Thanks for your purchase, ${name}! Your
product cost was ${amt1}, which with tax
comes out to ${amt2}.`
console.log( text );
// Thanks for your purchase, Kyle! Your
// product cost was $11.99, which with tax
// comes out to $12.95.
Собственно говоря у пост обработки строк Tagged Template Literals есть и другие применения, но это уже за рамками темы, пишите в комментариях если есть, что добавить :)
Источник вдохновения:
github.com/getify/You-Dont-Know-JS/blob/master/es6%20%26%20beyond/ch2.md
UPD: спасибо dotme
UPD1: спасибо alip
UPD2: спасибо frol
Кроме того, часто нужен контекст (чтобы «name» в одних случаях переводилось как «имя», а в других — «наименование»), где можно было бы добавить в том числе и внешнюю информацию (пол {М/Ж}, число {единственное/множественное}, и тд и тп).
Интересное замечание по поводу более продвинутого перевода. В целом в статье я попытался показать именно сахар, а не способы и подходы перевода, но кое-что все же забыл упомянуть:
function bar() {
return function foo(strings, ...values) {
console.log( strings );
console.log( values );
}
}
const desc = 'OK';
bar()`Everything is ${desc}!`;
Тегом может быть выражение которое возвращает функцию. Благодаря этому возможно передать какие-либо дополнительные аргументы:
lang('ru')`Everything is ${desc}!`;
//
lang('name', 'number')`${name}'s team is about ${count}`;
Комментарии (9)
frol
18.03.2016 16:57+1Всё-таки мне кажется, что для переводов важен контекст, поэтому переводить нужно всю шаблонизированную строку, а не отдельные её части.
Например: "${name}'s team" ("Vasya Pupkin's team") -> "Команда ${name}" ("Команда Васи Пупкина"), конечно, тут ещё проблема со склонением имён, но даже без проблемы склонения имён, порядок слов изменился.
Кроме того, часто нужен контекст (чтобы "name" в одних случаях переводилось как "имя", а в других — "наименование"), где можно было бы добавить в том числе и внешнюю информацию (пол {М/Ж}, число {единственное/множественное}, и тд и тп).auine
18.03.2016 17:07Я думаю на основе аргументов которые попадают в функцию, вполне можно провести подобный перевод :) Еще упустил момент, можно использовать например так:
bar()`Everything is ${desc}!`;
Допустим передать язык или формат:
lang('ru')`Everything is ${desc}!`; lang('name', 'number')`${name}'s team is about ${count}`;
frol
18.03.2016 17:28Такой вызов лично для меня выглядит ужасно, тем не менее, спасибо за демонстрацию такой возможности ES6. В своём проекте я бы такой синтаксис не использовал, а взял бы i18next:
{ "en": { "translation": { "key": "item", "key_plural": "items", "keyWithCount": "{{count}} item", "keyWithCount_plural": "{{count}} items" } } }
i18next.t('key', {count: 0}); // output: 'items' i18next.t('key', {count: 1}); // output: 'item' i18next.t('key', {count: 5}); // output: 'items' i18next.t('key', {count: 100}); // output: 'items' i18next.t('keyWithCount', {count: 0}); // output: '0 items' i18next.t('keyWithCount', {count: 1}); // output: '1 item' i18next.t('keyWithCount', {count: 5}); // output: '5 items' i18next.t('keyWithCount', {count: 100}); // output: '100 items'
P.S. В i18next учтено много нюансов, просто для комментария выбрал один из готовых примеров.
denis_g
18.03.2016 19:50Спасибо за статью, интересно было почитать про TTL. А для локализации я для себя кроме gettext-совместимых решений пока что ничего не нашёл.
dangreen
19.03.2016 09:30Спасибо автору! Статья пришлась очень к стати. Реализовал подобный функционал у себя в библиотеке (i18n-for-browser).
Halt
Или i18n или l10n.
auine
Спасибо, поправил :)
frol
Вы "поправили" на I10n (первая буква — заглавная i), а там либо i18n, либо L10n должно быть. (В теги можно оба варианта добавить.)
denis_g
Немного ясности для тех, кто еще не в курсе: