# react-native: Mini Shop を国際化する
URL: https://generaltranslation.com/ja/docs/react-native/tutorials/mini-shop.mdx
---
title: Mini Shop を国際化する
description: GT React Native のコンポーネント、フック、共通文字列を使ってシンプルなショップを国際化する、実践的な React Native チュートリアル
---
`gt-react-native` はまだ実験的な機能であり、すべてのプロジェクトで動作するとは限りません。
問題に遭遇した場合は、[GitHub で issue を作成](https://github.com/generaltranslation/gt/issues)してお知らせください。
小規模で完全にローカルで完結する「mini shop」を React Native で動かし、翻訳対応させてみましょう。外部サービスは不要、ルーティングも不要、Expo 以外の UI フレームワークも使いません。GT React Native の中核機能を一通り使い、シンプルで実践的なモバイル UI の中でそれらがどう組み合わさるかを確認できます。
前提条件: React Native (Expo)、JavaScript/TypeScript の基礎知識
作成するもの
* 商品一覧とシンプルなインメモリのカートを備えた単一画面の「ショップ」
* 言語切り替え機能と共通のナビゲーションラベル
* 適切に国際化された数値、通貨、複数形
このチュートリアルで使用するリンク
* プロバイダー: [``](/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)
***
## インストールしてアプリをラップする
パッケージをインストールし、プロバイダーでアプリをラップします。
```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`) を追加します。
最小限のプロバイダー設定を作成します。
```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 キー
キーがなくてもこのチュートリアルは進められます (デフォルト言語で表示されます) 。開発環境でライブ翻訳を確認し、言語の切り替えをテストするには、開発用キーを追加してください。
詳しくは [本番環境と開発環境](/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 を使う商品カード
JSX の翻訳には [``](/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) で処理します。
```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) を、合計金額には [``](/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 アプリで開くか、iOS シミュレータの場合は `i`、Android エミュレータの場合は `a` を押します。
***
## 学んだこと
* [``](/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)