Internationalize a Mini Shop
A hands-on React tutorial that internationalizes 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 internationalized numbers, currency, and pluralization
- 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 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 and then install 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 in 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 hardcode 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',
price: 199.99,
currency: 'USD',
inStock: true,
addedAt: new Date().toISOString()
},
{
id: 'p-2',
name: 'Travel Mug',
description: 'Double-wall 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 like <Var>, <Num>, <Currency>, and <DateTime>. Handle stock state 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 cart
</button>
</T>
</div>
);
}Cart with Pluralization and Totals
Use <Plural> to express “X items in 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>Cart</h2>
<Plural
n={itemCount}
zero={<p>Your cart 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}>Clear cart</button>
</T>
</div>
);
}Attributes and Placeholders with useGT
Use useGT for plain string translations like 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 in-memory cart and 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?