Интернационализация мини‑магазина
Практическое руководство по React, в котором интернационализируется простой магазин с использованием компонентов GT React, хуков и общих строк
Запустите небольшой, полностью локальный «мини‑магазин» с переводом — без внешних сервисов, без маршрутизации, без UI‑фреймворков. Вы задействуете ключевые возможности GT React от начала до конца и увидите, как они работают вместе в простом, реалистичном интерфейсе.
Предварительные требования: React, базовые знания JavaScript/TypeScript
Что вы будете создавать
- Одностраничный «магазин» с сеткой товаров и простой корзиной в памяти
- Переключатель языка и общие элементы навигации
- Корректно интернационализированные числа, валюту и множественное число
- Необязательно: локальное хранилище переводов для production‑сборок
Ссылки, используемые в этом руководстве
- Провайдер:
<GTProvider> - Компоненты:
<T>,<Var>,<Num>,<Currency>,<DateTime>,<Branch>,<Plural>,<LocaleSelector> - Строки и общие строки:
useGT,msg,useMessages - Руководства: Переменные, Ветвления, Строки, Локальное хранилище переводов, Смена языка
Установите пакеты и подключите провайдер
Установите пакеты и оберните приложение провайдером.
npm i gt-react
npm i --save-dev gtx-cliyarn add gt-react
yarn add --dev gtx-clibun add gt-react
bun add --dev gtx-clipnpm add gt-react
pnpm add --save-dev gtx-cliНеобязательно: стартовый проект (Vite)
Если вы начинаете с нуля, создайте скелет приложения Vite React + TypeScript, затем установите пакеты GT:
npm create vite@latest mini-shop -- --template react-ts
cd mini-shop
npm i gt-react
npm i --save-dev gtx-cliЗатем добавьте файлы из разделов ниже (например, src/main.tsx, src/App.tsx, src/components/*, src/data.ts, src/nav.ts).
Создайте минимальную конфигурацию провайдера.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { GTProvider } from 'gt-react'; // См.: /docs/react/api/components/gtprovider
import App from './App';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<GTProvider locales={["es", "fr"]}> {/* Включает испанский и французский */}
<App />
</GTProvider>
</StrictMode>
);При необходимости добавьте сейчас файл gt.config.json (позже пригодится для CI и локального хранения данных):
{
"defaultLocale": "en",
"locales": ["es", "fr"]
}Ключи API для разработки
Вы можете пройти это руководство без ключей (будет отображаться язык по умолчанию). Чтобы увидеть актуальные переводы и протестировать переключение языка в режиме разработки, добавьте ключи разработки.
Подробнее см. в разделе Продакшн и разработка.
VITE_GT_API_KEY="your-dev-api-key"
VITE_GT_PROJECT_ID="your-project-id"REACT_APP_GT_API_KEY="your-dev-api-key"
REACT_APP_GT_PROJECT_ID="your-project-id"- Панель: https://dash.generaltranslation.com/signup
- Или через CLI:
npx gtx-cli auth
Исходные данные и структура приложения
Мы захардкодим небольшой массив товаров и оставим всё на стороне клиента. Никаких серверов, никакой маршрутизации.
export type Product = {
id: string;
name: string;
description: string;
price: number;
currency: 'USD' | 'EUR';
inStock: boolean;
addedAt: string; // ISO date string
};
export const products: Product[] = [
{
id: 'p-1',
name: 'Беспроводные наушники',
description: 'Полноразмерные наушники с активным шумоподавлением, время работы 30 ч',
price: 199.99,
currency: 'USD',
inStock: true,
addedAt: new Date().toISOString()
},
{
id: 'p-2',
name: 'Термокружка',
description: 'Нержавеющая сталь с двойными стенками (350 мл)',
price: 24.5,
currency: 'USD',
inStock: false,
addedAt: new Date().toISOString()
}
];Общие элементы навигации с msg и useMessages
Используйте msg для обозначения общих строк в конфигурации, а затем извлекайте их с помощью useMessages во время рендеринга.
import { msg } from 'gt-react'; // See: /docs/react/api/strings/msg
export const nav = [
{ label: msg('Главная'), href: '#' },
{ label: msg('Товары'), href: '#products' },
{ label: msg('Корзина'), href: '#cart' }
];import { LocaleSelector, T } from 'gt-react';
import { useMessages } from 'gt-react'; // See: /docs/react/api/strings/useMessages
import { nav } from '../nav';
export default function Header() {
const m = useMessages();
return (
<header style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
<T><h1>Мини‑шоп</h1></T> {/* See: /docs/react/api/components/t */}
<nav style={{ display: 'flex', gap: 12 }}>
{nav.map(item => (
<a key={item.href} href={item.href} title={m(item.label)}>
{m(item.label)}
</a>
))}
</nav>
<div style={{ marginLeft: 'auto' }}>
<LocaleSelector /> {/* See: /docs/react/api/components/localeSelector */}
</div>
</header>
);
}Карточки товара с <T>, переменными, Branch и валютой
Используйте <T> для перевода в JSX. Оборачивайте динамический контент компонентами переменных, такими как <Var>, <Num>, <Currency> и <DateTime>. Управляйте состоянием наличия через <Branch>.
import { T, Var, Num, Currency, DateTime, Branch } from 'gt-react';
import type { Product } from '../data';
export default function ProductCard({ product, onAdd }: { product: Product; onAdd: () => void; }) {
return (
<div style={{ border: '1px solid #ddd', padding: 12, borderRadius: 8 }}>
<T>
<h3><Var>{product.name}</Var></h3>
<p><Var>{product.description}</Var></p>
<p>
Цена: <Currency currency={product.currency}>{product.price}</Currency>
</p>
<p>
Добавлено: <DateTime options={{ dateStyle: 'medium', timeStyle: 'short' }}>{product.addedAt}</DateTime>
</p>
<Branch
branch={product.inStock}
true={<p>В наличии</p>}
false={<p style={{ color: 'tomato' }}>Нет в наличии</p>}
/>
<button onClick={onAdd} disabled={!product.inStock}>
Добавить в корзину
</button>
</T>
</div>
);
}Корзина: множественное число и суммы
Используйте <Plural> для фразы «X товаров в корзине» и <Currency> для итоговых сумм. Комбинируйте с <T>, <Var> и <Num>.
import { T, Plural, Var, Num, Currency } from 'gt-react';
import type { Product } from '../data';
export default function Cart({ items, onClear }: { items: Product[]; onClear: () => void; }) {
const total = items.reduce((sum, p) => sum + p.price, 0);
const itemCount = items.length;
return (
<div style={{ borderTop: '1px solid #eee', paddingTop: 12 }}>
<T>
<h2>Корзина</h2>
<Plural
n={itemCount}
zero={<p>Ваша корзина пуста</p>}
one={<p>У вас <Num>{itemCount}</Num> товар</p>}
other={<p>У вас <Num>{itemCount}</Num> товаров</p>}
/>
{items.map((p) => (
<p key={p.id}>
<Var>{p.name}</Var> — <Currency currency={p.currency}>{p.price}</Currency>
</p>
))}
<p>
Итого: <Currency currency={items[0]?.currency || 'USD'}>{total}</Currency>
</p>
<button onClick={onClear} disabled={itemCount === 0}>Очистить корзину</button>
</T>
</div>
);
}Атрибуты и плейсхолдеры с useGT
Используйте useGT для перевода простых строк, например плейсхолдеров полей ввода и ARIA‑меток.
import { useGT } from 'gt-react';
export default function Search({ onQuery }: { onQuery: (q: string) => void; }) {
const t = useGT();
return (
<input
type="search"
placeholder={t('Поиск товаров...')}
aria-label={t('Поле для поиска')}
onChange={(e) => onQuery(e.target.value)}
style={{ padding: 8, width: '100%', maxWidth: 320 }}
/>
);
}Собираем всё воедино
Одностраничное приложение с корзиной в памяти и простым поисковым фильтром.
import { useMemo, useState } from 'react';
import Header from './components/Header';
import Search from './components/Search';
import ProductCard from './components/ProductCard';
import Cart from './components/Cart';
import { products } from './data';
export default function App() {
const [query, setQuery] = useState('');
const [cart, setCart] = useState<string[]>([]);
const filtered = useMemo(() => {
const q = query.toLowerCase();
return products.filter(p =>
p.name.toLowerCase().includes(q) || p.description.toLowerCase().includes(q)
);
}, [query]);
const items = products.filter(p => cart.includes(p.id));
return (
<div style={{ margin: '24px auto', maxWidth: 960, padding: 16 }}>
<Header />
<div style={{ margin: '16px 0' }}>
<Search onQuery={setQuery} />
</div>
<section id="products" style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: 16 }}>
{filtered.map(p => (
<ProductCard
key={p.id}
product={p}
onAdd={() => setCart(c => (p.inStock ? [...new Set([...c, p.id])] : c))}
/>
))}
</section>
<section id="cart" style={{ marginTop: 24 }}>
<Cart
items={items}
onClear={() => setCart([])}
/>
</section>
</div>
);
}Запуск локально
Добавьте простой dev‑скрипт в package.json, затем запустите приложение.
{
"scripts": {
"dev": "vite"
}
}Выполните:
npm run dev{
"scripts": {
"start": "react-scripts start"
}
}Выполните:
npm startЧто вы узнали
- Перевод JSX с помощью
<T>и работа с динамическим содержимым через<Var>,<Num>,<Currency>,<DateTime> - Описание условного содержимого с
<Branch>и количественных форм с<Plural> - Перевод атрибутов с помощью
useGT - Обмен навигационными и конфигурационными строками через
msgи их декодирование с помощьюuseMessages - Переключение языков с помощью
<LocaleSelector>
Дальше
Насколько полезно это руководство?