Назад

Хватит оборачивать строки в вызовы функций

Ernest McCarter avatarErnest McCarter
gt-reacti18ntagged-templatemacrodeveloper-experience

Проблема с t("Hello, {name}", { name })

Если вам когда-нибудь приходилось интернационализировать React-приложение, вы наверняка писали что-то вроде этого:

const gt = useGT();

return <p>{gt("Hello, {name}! You have {count} items.", { name, count })}</p>;

Это работает. Но выглядит громоздко. Вы вручную пишете синтаксис ICU MessageFormat, дублируете каждое имя переменной в объекте параметров и вытаскиваете t из Hook, которому нужен контекст React. Для вещи, которая должна быть всего лишь тонкой прослойкой над вашими существующими строками, это слишком много лишней возни.

И дальше только хуже. Как только вам нужен перевод вне React-компонента — во вспомогательной функции, обработчике событий, серверном действии, — приходится искать обходные пути, потому что Hook там не работают.

Шаблонные литералы должны работать как есть

Вот как выглядит тот же код с макросом t:

import { t } from "gt-react/browser";

return <p>{t`Hello, ${name}! You have ${count} items.`}</p>;

Вот и всё. Стандартный синтаксис шаблонных литералов JavaScript. Никаких заполнителей ICU, никакого объекта параметров, никакого Hook, никакого провайдера контекста. Вы пишете строку так же, как любую шаблонную строку, а всё остальное берёт на себя компилятор.

На этапе сборки компилятор GT преобразует:

t`Hello, ${name}!`

в:

t("Hello, {0}!", { "0": name })

Тегированный шаблонный литерал — это просто синтаксический сахар: поведение во время выполнения полностью идентично вызову t() со строкой. Но для разработчика это гораздо удобнее.

Больше никакой зависимости от контекста React

Главное изменение здесь — не синтаксис, а архитектура. Функция t, экспортируемая из gt-react/browser, не использует контекст React. Ей не нужен Hook. Её не нужно вызывать внутри компонента.

Это значит, что вы можете использовать t в:

  • Обработчиках событий: onClick={() => alert(tSaved!)}
  • Вспомогательных функциях: function formatError(code) { return tError: $ }
  • Константах и конфигурации: const LABELS = { save: tSave, cancel: tCancel }
  • Везде, где JavaScript выполняется на клиенте

Старый подход — useGT(), возвращающий функцию, привязанную к контексту React, — был узким местом. Он вынуждал завязывать перевод на React, хотя на самом деле речь идёт просто о строках.

Глобальная регистрация

Если вы не хотите импортировать t в каждый файл, можно зарегистрировать его глобально:

// В точке входа вашего приложения
import "gt-react/macros";

Это задаёт globalThis.t, благодаря чему тегированный шаблонный литерал становится доступен везде без импорта. Компилятор достаточно умён, чтобы это распознать: если t уже импортирован из источника GT, он не добавит дублирующий импорт. Если же нет и вы использовали t как тегированный шаблонный литерал, компилятор сам добавит импорт.

Конкатенация тоже поддерживается

Раскрытие макроса не ограничивается тегированными шаблонными литералами. Оно также поддерживает шаблонные литералы, передаваемые в качестве аргументов, и конкатенацию строк:

// Шаблонный литерал как аргумент — также преобразуется
t(`Welcome back, ${user}`)

// Конкатенация строк — также преобразуется
t("Hello, " + name + "! Welcome.")

Оба варианта приводятся к одному и тому же вызову t("...", { ... }) на этапе сборки.

Начало работы

1. Установите последнюю версию gt-react

npm install gt-react@latest

2. Используйте макрос t

Либо импортируйте его напрямую:

import { t } from "gt-react/browser";

export function Greeting({ name }) {
  return <p>{t`Hello, ${name}!`}</p>;
}

Или зарегистрируйте глобально и обойдитесь без импортов:

// app/layout.tsx или точка входа
import "gt-react/macros";
// В любом месте вашего приложения
export function Greeting({ name }) {
  return <p>{t`Hello, ${name}!`}</p>;
}

3. Вот и всё

Компилятор GT автоматически выполняет это преобразование во время сборки. Дополнительная настройка не требуется — раскрытие макросов включено по умолчанию.


Макрос t — небольшое изменение в API, но за ним стоит более важный сдвиг: переводы должны быть естественной частью JavaScript, а не выглядеть как обходное решение для конкретного фреймворка. Пишите строки как обычно. Остальное сделает инструментарий.