Internationalise a Mini Shop
A hands-on React tutorial that internationalises a simple shop using GT React components, hooks, and shared strings
Get a small, fully local “mini shop” running and translated — no external services, no routing, no UI frameworks. You’ll use the core GT React features end to end and see how they fit together in a simple, realistic UI.
Prerequisites: React, basic JavaScript/TypeScript
What you’ll build
- A single-page “shop” with a product grid and a simple in-memory cart
- Language switcher and shared navigation labels
- Properly internationalised numbers, currency, and pluralisation
- Optional: local translation storage for production builds
Links used in this tutorial
- Provider:
<GTProvider> - Components:
<T>,<Var>,<Num>,<Currency>,<DateTime>,<Branch>,<Plural>,<LocaleSelector> - Strings and Shared Strings:
useGT,msg,useMessages - Guides: Variables, Branching, Strings, Local Translation Storage, Changing Languages
Install and wrap your app
Install the packages and wrap your app with the provider.
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-cliOptional: starter project (Vite)
If you’re starting from scratch, scaffold a Vite React + TypeScript app, then install the GT packages:
npm create vite@latest mini-shop -- --template react-ts
cd mini-shop
npm i gt-react
npm i --save-dev gtx-cliThen add the files in the sections below (e.g. src/main.tsx, src/App.tsx, src/components/*, src/data.ts, src/nav.ts).
Create a minimal provider setup.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { GTProvider } from 'gt-react'; // See: /docs/react/api/components/gtprovider
import App from './App';
createRoot(document.getElementById('root')!).render(
<StrictMode>
<GTProvider locales={["es", "fr"]}> {/* Enable Spanish and French */}
<App />
</GTProvider>
</StrictMode>
);Optionally add a gt.config.json now (useful later for CI and local storage):
{
"defaultLocale": "en",
"locales": ["es", "fr"]
}Development API keys
You can follow this tutorial without keys (it will render the default language). To see live translations and test language switching during development, add development keys.
Learn more in Production vs Development.
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"- Dashboard: https://dash.generaltranslation.com/signup
- Or via CLI:
npx gtx-cli auth
Seed Data and App Structure
We’ll hard‑code a tiny product array and keep everything client‑side. No servers, no routing.
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: 'Wireless Headphones',
description: 'Noise-cancelling over-ear design with 30h battery life',
price: 199.99,
currency: 'USD',
inStock: true,
addedAt: new Date().toISOString()
},
{
id: 'p-2',
name: 'Travel Mug',
description: 'Double-walled insulated stainless steel (12oz)',
price: 24.5,
currency: 'USD',
inStock: false,
addedAt: new Date().toISOString()
}
];Shared Navigation Labels with msg and useMessages
Use msg to mark shared strings in config, then decode them via useMessages at render time.
import { msg } from 'gt-react'; // See: /docs/react/api/strings/msg
export const nav = [
{ label: msg('Home'), href: '#' },
{ label: msg('Products'), href: '#products' },
{ label: msg('Cart'), 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>Mini Shop</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>
);
}Product Cards with <T>, Variables, Branch, and Currency
Use <T> for JSX translation. Wrap dynamic content with variable components such as <Var>, <Num>, <Currency>, and <DateTime>. Manage stock status via <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>
Price: <Currency currency={product.currency}>{product.price}</Currency>
</p>
<p>
Added: <DateTime options={{ dateStyle: 'medium', timeStyle: 'short' }}>{product.addedAt}</DateTime>
</p>
<Branch
branch={product.inStock}
true={<p>In stock</p>}
false={<p style={{ color: 'tomato' }}>Out of stock</p>}
/>
<button onClick={onAdd} disabled={!product.inStock}>
Add to basket
</button>
</T>
</div>
);
}Cart with Pluralisation and Totals
Use <Plural> to express “X items in the cart” and <Currency> for totals. Combine with <T>, <Var>, and <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>Basket</h2>
<Plural
n={itemCount}
zero={<p>Your basket is empty</p>}
one={<p>You have <Num>{itemCount}</Num> item</p>}
other={<p>You have <Num>{itemCount}</Num> items</p>}
/>
{items.map((p) => (
<p key={p.id}>
<Var>{p.name}</Var> — <Currency currency={p.currency}>{p.price}</Currency>
</p>
))}
<p>
Total: <Currency currency={items[0]?.currency || 'USD'}>{total}</Currency>
</p>
<button onClick={onClear} disabled={itemCount === 0}>Empty basket</button>
</T>
</div>
);
}Attributes and placeholders with useGT
Use useGT for plain-string translations such as input placeholders and ARIA labels.
import { useGT } from 'gt-react';
export default function Search({ onQuery }: { onQuery: (q: string) => void; }) {
const t = useGT();
return (
<input
type="search"
placeholder={t('Search products…')}
aria-label={t('Search input')}
onChange={(e) => onQuery(e.target.value)}
style={{ padding: 8, width: '100%', maxWidth: 320 }}
/>
);
}Put It Together
A single‑page app with an in‑memory basket and a simple search filter.
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>
);
}Run locally
Add a simple dev script to your package.json, then start the app.
{
"scripts": {
"dev": "vite"
}
}Run:
npm run dev{
"scripts": {
"start": "react-scripts start"
}
}Run:
npm startWhat You Learned
- Translating JSX with
<T>and handling dynamic content via<Var>,<Num>,<Currency>,<DateTime> - Expressing conditional content with
<Branch>and quantities with<Plural> - Translating attributes with
useGT - Sharing navigation/config strings using
msgand decoding them withuseMessages - Switching languages with
<LocaleSelector>
Next steps
How is this guide?