# 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)