# react-native: 为 Mini Shop 添加国际化
URL: https://generaltranslation.com/zh/docs/react-native/tutorials/mini-shop.mdx
---
title: 为 Mini Shop 添加国际化
description: 一个动手实践的 React Native 教程,使用 GT React Native 组件、Hook 和共享字符串为一个简单商店实现国际化
---
`gt-react-native` 仍处于实验阶段,可能并不适用于所有项目。
如果你遇到任何问题,请通过[在 GitHub 上提交 issue](https://github.com/generaltranslation/gt/issues)告知我们。
在 React Native 中搭建并翻译一个小巧、完全本地运行的“mini shop”——无需外部服务、无需路由,除 Expo 外也不依赖任何 UI 框架。你将端到端使用 GT React Native 的核心功能,并了解它们如何在一个简单但贴近实际的移动端 UI 中配合使用。
前提条件:React Native (Expo) 、基础 JavaScript/TypeScript
你将构建的内容
* 一个单屏“商店”,包含商品列表和一个简单的内存购物车
* 语言切换器和共享导航标签
* 正确国际化处理的数字、货币和复数形式
本教程中使用的链接
* Provider:[``](/docs/react-native/api/components/gtprovider)
* 组件:[``](/docs/react-native/api/components/t), [``](/docs/react-native/api/components/var), [``](/docs/react-native/api/components/num), [``](/docs/react-native/api/components/currency), [``](/docs/react-native/api/components/datetime), [``](/docs/react-native/api/components/branch), [``](/docs/react-native/api/components/plural), [``](/docs/react-native/api/components/locale-selector)
* 字符串和共享字符串:[`useGT`](/docs/react-native/api/strings/use-gt), [`msg`](/docs/react-native/api/strings/msg), [`useMessages`](/docs/react-native/api/strings/use-messages)
* 指南:[变量](/docs/react-native/guides/variables), [分支](/docs/react-native/guides/branches), [字符串](/docs/react-native/guides/strings), [本地翻译存储](/docs/react-native/guides/local-tx), [切换语言](/docs/react-native/guides/languages)
***
## 安装并包装应用
安装依赖包,并使用 provider 包装你的应用。
```bash
npm i gt-react-native
npm i --save-dev gt
```
```bash
yarn add gt-react-native
yarn add --dev gt
```
```bash
bun add gt-react-native
bun add --dev gt
```
```bash
pnpm add gt-react-native
pnpm add --save-dev gt
```
可选:起始项目 (Expo)
如果你要从零开始,可以先用脚手架创建一个 Expo 应用,然后安装 GT 包:
```bash copy
npx create-expo-app mini-shop --template blank-typescript
cd mini-shop
npm i gt-react-native
npm i --save-dev gt
```
然后添加下方各节中的文件 (例如 `App.tsx`、`components/*`、`data.ts`、`nav.ts`) 。
创建一个最小的 provider 配置。
```tsx title="App.tsx" copy
import { GTProvider } from 'gt-react-native';
import Shop from './Shop';
export default function App() {
return (
);
}
```
现在也可以选择添加一个 `gt.config.json` (后续用于 CI 和本地存储会很有帮助) :
```json title="gt.config.json" copy
{
"defaultLocale": "en",
"locales": ["es", "fr"]
}
```
### 开发 API 密钥
你可以不使用密钥直接跟着本教程操作 (此时会渲染默认语言) 。如果要查看实时翻译并在开发环境中测试语言切换,请添加开发密钥。
更多信息请参见 [Production vs Development](/docs/react-native/concepts/environments)。
```bash title=".env" copy
EXPO_PUBLIC_GT_API_KEY="your-dev-api-key"
EXPO_PUBLIC_GT_PROJECT_ID="your-project-id"
```
* 仪表板:https://dash.generaltranslation.com/signup
* 或通过 CLI:
```bash copy
npx gt auth
```
***
## 种子数据与应用结构
我们会硬编码一个很小的产品数组,并让所有内容都在客户端运行。不需要服务器,也不涉及导航。
```ts title="data.ts" copy
export type Product = {
id: string;
name: string;
description: string;
price: number;
currency: 'USD' | 'EUR';
inStock: boolean;
addedAt: string; // ISO 日期字符串
};
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()
}
];
```
***
## 使用 msg 和 useMessages 复用导航标签
使用 [`msg`](/docs/react-native/api/strings/msg) 在配置中标记可共享的字符串,然后在渲染时通过 [`useMessages`](/docs/react-native/api/strings/use-messages) 将其解码。
```tsx title="nav.ts" copy
import { msg } from 'gt-react-native';
export const navItems = [
{ label: msg('Home'), id: 'home' },
{ label: msg('Products'), id: 'products' },
{ label: msg('Cart'), id: 'cart' }
];
```
```tsx title="components/Header.tsx" copy
import { View, Text, StyleSheet } from 'react-native';
import { LocaleSelector, T } from 'gt-react-native';
import { useMessages } from 'gt-react-native';
import { navItems } from '../nav';
export default function Header() {
const m = useMessages();
return (
Mini Shop
{navItems.map(item => (
{m(item.label)}
))}
);
}
const styles = StyleSheet.create({
header: {
paddingVertical: 16,
paddingHorizontal: 12,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 8,
},
nav: {
flexDirection: 'row',
gap: 12,
marginBottom: 8,
},
navItem: {
fontSize: 14,
color: '#555',
},
});
```
***
## 使用 T、变量、Branch 和 Currency 的产品卡片
使用 [``](/docs/react-native/api/components/t) 翻译 JSX 内容。用 [``](/docs/react-native/api/components/var)、[``](/docs/react-native/api/components/num)、[``](/docs/react-native/api/components/currency) 和 [``](/docs/react-native/api/components/datetime) 等变量组件包裹动态内容。通过 [``](/docs/react-native/api/components/branch) 处理库存状态。
```tsx title="components/ProductCard.tsx" copy
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { T, Var, Currency, DateTime, Branch } from 'gt-react-native';
import type { Product } from '../data';
export default function ProductCard({ product, onAdd }: { product: Product; onAdd: () => void }) {
return (
{product.name}
{product.description}
Price: {product.price}
Added: {product.addedAt}
In stock}
false={Out of stock}
/>
Add to cart
);
}
const styles = StyleSheet.create({
card: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 12,
marginBottom: 12,
},
name: {
fontSize: 18,
fontWeight: '600',
marginBottom: 4,
},
description: {
fontSize: 14,
color: '#666',
marginBottom: 8,
},
price: {
fontSize: 16,
marginBottom: 4,
},
date: {
fontSize: 12,
color: '#999',
marginBottom: 8,
},
inStock: {
color: 'green',
marginBottom: 8,
},
outOfStock: {
color: 'tomato',
marginBottom: 8,
},
button: {
backgroundColor: '#007AFF',
paddingVertical: 10,
borderRadius: 6,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#ccc',
},
buttonText: {
color: '#fff',
fontWeight: '600',
},
});
```
***
## 带复数和总计的购物车
使用 [``](/docs/react-native/api/components/plural) 表达“购物车中有 X 件商品”,并使用 [``](/docs/react-native/api/components/currency) 显示总金额。可结合 [``](/docs/react-native/api/components/t)、[``](/docs/react-native/api/components/var) 和 [``](/docs/react-native/api/components/num) 使用。
```tsx title="components/Cart.tsx" copy
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { T, Plural, Var, Num, Currency } from 'gt-react-native';
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 (
Cart
Your cart is empty}
one={You have {itemCount} item}
other={You have {itemCount} items}
/>
{items.map((p) => (
{p.name} — {p.price}
))}
Total: {total}
Clear cart
);
}
const styles = StyleSheet.create({
container: {
borderTopWidth: 1,
borderTopColor: '#eee',
paddingTop: 16,
marginTop: 16,
},
heading: {
fontSize: 20,
fontWeight: 'bold',
marginBottom: 8,
},
empty: {
color: '#999',
marginBottom: 8,
},
item: {
fontSize: 14,
marginVertical: 4,
},
total: {
fontSize: 16,
fontWeight: '600',
marginTop: 8,
marginBottom: 12,
},
button: {
backgroundColor: '#FF3B30',
paddingVertical: 10,
borderRadius: 6,
alignItems: 'center',
},
buttonDisabled: {
backgroundColor: '#ccc',
},
buttonText: {
color: '#fff',
fontWeight: '600',
},
});
```
***
## 使用 `useGT` 处理属性和占位符
像输入框占位符和无障碍标签这类纯字符串的翻译,请使用 [`useGT`](/docs/react-native/api/strings/use-gt)。
```tsx title="components/Search.tsx" copy
import { TextInput, StyleSheet } from 'react-native';
import { useGT } from 'gt-react-native';
export default function Search({ onQuery }: { onQuery: (q: string) => void }) {
const gt = useGT();
return (
);
}
const styles = StyleSheet.create({
input: {
borderWidth: 1,
borderColor: '#ddd',
borderRadius: 8,
padding: 10,
fontSize: 16,
},
});
```
***
## 整合一下
一个单页面应用,带有内存购物车和简单的搜索筛选功能。
```tsx title="Shop.tsx" copy
import { useMemo, useState } from 'react';
import { SafeAreaView, ScrollView, View, StyleSheet } from 'react-native';
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 Shop() {
const [query, setQuery] = useState('');
const [cart, setCart] = useState([]);
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 (
{filtered.map(p => (
setCart(c => (p.inStock ? [...new Set([...c, p.id])] : c))}
/>
))}
setCart([])}
/>
);
}
const styles = StyleSheet.create({
safe: {
flex: 1,
backgroundColor: '#fff',
},
scroll: {
padding: 16,
},
searchContainer: {
marginVertical: 12,
},
products: {
gap: 0,
},
});
```
***
## 本地运行
启动 Expo 开发服务器:
```bash
npx expo start
```
然后在手机上的 Expo Go 应用中打开,或者按 `i` 在 iOS 模拟器中打开 / 按 `a` 在 Android 模拟器中打开。
***
## 你学到了什么
* 使用 [``](/docs/react-native/api/components/t) 翻译 JSX,并通过 [``](/docs/react-native/api/components/var)、[``](/docs/react-native/api/components/num)、[``](/docs/react-native/api/components/currency)、[``](/docs/react-native/api/components/datetime) 处理动态内容
* 使用 [``](/docs/react-native/api/components/branch) 表达条件内容,并使用 [``](/docs/react-native/api/components/plural) 处理数量
* 使用 [`useGT`](/docs/react-native/api/strings/use-gt) 翻译属性
* 使用 [`msg`](/docs/react-native/api/strings/msg) 共享导航和配置字符串,并使用 [`useMessages`](/docs/react-native/api/strings/use-messages) 对其解码
* 使用 [``](/docs/react-native/api/components/locale-selector) 切换语言
## 下一步
* 参阅:[变量指南](/docs/react-native/guides/variables)、[分支指南](/docs/react-native/guides/branches)、[字符串指南](/docs/react-native/guides/strings)、[切换语言](/docs/react-native/guides/languages)