共有文字列
複数のコンポーネントやファイルで使われる文字列を国際化する方法
共有文字列は、アプリケーション内の複数箇所で使用されるテキスト値です。たとえば、ナビゲーションのラベル、フォームメッセージ、設定データなどが該当します。各所で翻訳ロジックを重複させるのではなく、msg で文字列を翻訳対象としてマークし、useMessages でデコードします。
共有コンテンツの問題点
アプリ全体で共有されている次のナビゲーション設定を考えてみましょう:
// navData.ts
export const navData = [
{
label: 'ホーム',
description: 'トップページ',
href: '/'
},
{
label: '概要',
description: '会社情報',
href: '/about'
}
];これを国際化するには、通常は次の対応が必要です。
- 翻訳関数を受け取る関数へ書き換える
- すべての使用箇所を、
tを渡してその関数を呼ぶ形に更新する - コードベース全体でその複雑さを管理する
これは保守コストを増やし、コードの可読性を下げます。msg 関数は、翻訳対象の文字列をその場でマーキングし、必要なときにデコードできるようにすることで、この問題を解決します。
クイックスタート
文字列のマーキングには msg を、デコードには useMessages を使います。
// navData.ts - 翻訳対象の文字列をマークする
import { msg } from 'gt-react';
export const navData = [
{
label: msg('ホーム'),
description: msg('トップページ'),
href: '/'
},
{
label: msg('会社概要'),
description: msg('会社情報'),
href: '/about'
}
];// コンポーネントの使用例 - マークされた文字列をデコードする
import { useMessages } from 'gt-react';
import { navData } from './navData';
function Navigation() {
const m = useMessages();
return (
<nav>
{navData.map((item) => (
<a key={item.href} href={item.href} title={m(item.description)}>
{m(item.label)}
</a>
))}
</nav>
);
}共有文字列の仕組み
共有文字列システムは2つのフェーズで動作します:
- マークフェーズ:
msgが文字列に翻訳メタデータをエンコードします - デコードフェーズ:
useMessagesが文字列をデコードし、翻訳します
// msg() は文字列をメタデータとともにエンコードします
const encoded = msg('Hello, world!');
console.log(encoded); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="
// useMessages() はデコードして翻訳します
const m = useMessages();
const translated = m(encoded); // ユーザーの言語での「Hello, world!」msg からの encoded string は直接使用できません。必ず useMessages でデコードしてください。
コンポーネント
useMessages フックを使います:
import { useMessages } from 'gt-react';
const encodedString = msg('こんにちは、世界!');
function MyComponent() {
const m = useMessages();
return <div>{m(encodedString)}</div>;
}decodeMsg で元の文字列を取得する
ログ出力、デバッグ、比較など、翻訳せずに元の文字列へアクセスしたい場合があります。decodeMsg を使うと、元のテキストを取り出せます。
import { decodeMsg } from 'gt-react';
const encoded = msg('Hello, world!');
const original = decodeMsg(encoded); // 「Hello, world!」(元の文字列)
const translated = m(encoded); // 「Hello, world!」(ユーザーの言語)
// ログやデバッグに便利
console.log('元の文字列:', decodeMsg(encoded));
console.log('翻訳済み文字列:', m(encoded));decodeMsg のユースケース
- 開発・デバッグ: トラブルシューティングのために元の文字列をログ出力
- フォールバック処理: 翻訳に失敗した場合は原文を使用
- 文字列比較: 既知の原文の値と比較
- 分析: 原文文字列の使用状況をトラッキング
// 例: フォールバック処理
function getDisplayText(encodedStr) {
const m = useMessages();
try {
return m(encodedStr);
} catch (error) {
console.warn('翻訳に失敗しました。元の文字列を使用します:', decodeMsg(encodedStr));
return decodeMsg(encodedStr);
}
}Variable の使用
動的な内容を含む文字列にはプレースホルダーを使い、variables を渡します:
// 変数付きの文字列をマークする
const items = 100;
export const pricing = [
{
name: 'Basic',
price: 100,
description: msg('Basic プランには {items} 件が含まれます', { items })
}
];// コンポーネントで使用する
function PricingCard() {
const m = useMessages();
return (
<div>
<h3>{pricing[0].name}</h3>
<p>{m(pricing[0].description)}</p>
</div>
);
}ICUメッセージ形式
高度なフォーマットには、ICU の構文を使用します。
const count = 10;
const message = msg('カートに{count, plural, =0 {商品はありません} =1 {商品が1点あります} other {{count}点の商品があります}}', { count });ICU Message Format については、Unicode のドキュメントをご覧ください。
例
ナビゲーション設定
// config/navigation.ts
import { msg } from 'gt-react';
export const mainNav = [
{
label: msg('ホーム'),
href: '/',
icon: 'home'
},
{
label: msg('製品'),
href: '/products',
icon: 'package'
},
{
label: msg('会社概要'),
href: '/about',
icon: 'info'
}
];
export const footerLinks = [
{
title: msg('企業情報'),
links: [
{ label: msg('会社概要'), href: '/about' },
{ label: msg('採用情報'), href: '/careers' },
{ label: msg('お問い合わせ'), href: '/contact' }
]
},
{
title: msg('サポート'),
links: [
{ label: msg('ヘルプセンター'), href: '/help' },
{ label: msg('ドキュメント'), href: '/docs' },
{ label: msg('APIリファレンス'), href: '/api' }
]
}
];// components/Navigation.tsx
import { useMessages } from 'gt-react';
import { mainNav } from '../config/navigation';
function Navigation() {
const m = useMessages();
return (
<nav>
{mainNav.map((item) => (
<a key={item.href} href={item.href}>
<Icon name={item.icon} />
{m(item.label)}
</a>
))}
</nav>
);
}フォーム構成
// config/forms.ts
import { msg } from 'gt-react';
export const formMessages = {
placeholders: {
email: msg('メールアドレスを入力してください'),
password: msg('パスワードを入力してください'),
message: msg('メッセージを入力してください...'),
},
actions: {
send: msg('メッセージを送信'),
save: msg('変更を保存'),
cancel: msg('キャンセル')
},
validation: {
required: msg('この項目は必須です'),
email: msg('有効なメールアドレスを入力してください'),
minLength: msg('{min}文字以上で入力してください', { min: 8 }),
maxLength: msg('{max}文字以内で入力してください', { max: 100 })
},
success: {
saved: msg('変更を保存しました'),
sent: msg('メッセージを送信しました'),
updated: msg('プロフィールを更新しました')
},
errors: {
network: msg('ネットワークエラーが発生しました。もう一度お試しください'),
server: msg('サーバーエラーが発生しました。サポートにお問い合わせください'),
timeout: msg('リクエストがタイムアウトしました。もう一度お試しください')
}
};// components/ContactForm.tsx
import { useMessages } from 'gt-react';
import { formMessages } from '../config/forms';
function ContactForm() {
const m = useMessages();
const [errors, setErrors] = useState({});
return (
<form>
<input
type="email"
placeholder={m(formMessages.placeholders.email)}
required
/>
{errors.email && <span>{m(formMessages.validation.email)}</span>}
<button type="submit">
{m(formMessages.actions.send)}
</button>
</form>
);
}動的コンテンツ生成
// utils/productData.ts
import { msg } from 'gt-react';
function mockProducts() {
return [
{ name: 'iPhone 15', company: 'Apple', category: 'エレクトロニクス' },
{ name: 'Galaxy S24', company: 'Samsung', category: 'エレクトロニクス' }
];
}
export function getProductData() {
const products = mockProducts();
return products.map(product => ({
...product,
description: msg('{name} は {company} の{category}製品です', {
name: product.name,
category: product.category,
company: product.company
})
}));
}// components/ProductList.tsx
import { useMessages } from 'gt-react';
import { getProductData } from '../utils/productData';
function ProductList() {
const m = useMessages();
const products = getProductData();
return (
<div>
{products.map(product => (
<div key={product.name}>
<h3>{product.name}</h3>
<p>{m(product.description)}</p>
</div>
))}
</div>
);
}よくある課題
エンコード済み文字列を直接使う
msg の出力をそのまま使わないでください:
// ❌ 誤り - encoded string をそのまま使っている
const encoded = msg('Hello, world!');
return <div>{encoded}</div>; // 翻訳ではなく encoded string が表示される
// ✅ 正しい - 先に文字列をデコードする
const encoded = msg('Hello, world!');
const m = useMessages();
return <div>{m(encoded)}</div>; // 正しく翻訳が表示されるmsg() の動的コンテンツ
文字列はビルド時に確定している必要があります:
// ❌ 誤り - 動的テンプレートリテラル
const name = 'John';
const message = msg(`Hello, ${name}`); // ビルド時エラー
// ✅ 正解 - variables を使用
const name = 'John';
const message = msg('Hello, {name}', { name });デコードのし忘れ
すべてのmsg文字列はデコードが必要です。
// ❌ デコード漏れ
const config = {
title: msg('Dashboard'),
subtitle: msg('Welcome back')
};
// コンポーネント内の後段 — デコードし忘れ
return <h1>{config.title}</h1>; // エンコードされた文字列が表示される
// ✅ 正しい例 — 使用時にデコードする
const m = useMessages();
return <h1>{m(config.title)}</h1>; // 翻訳済みのタイトルが表示される次のステップ
- Dictionaries ガイド - 構造化データで翻訳を整理
- Languages ガイド - 対応言語を設定
- APIリファレンス:
このガイドはいかがですか?