共有文字列の翻訳

共有文字列を国際化する方法

概要

共有文字列とは、アプリ内の複数の箇所で使用される文字列や定数のことです。

たとえば、複数のコンポーネントで使われる文字列や、複数のファイルで使われる定数がある場合があります。

従来の i18n ライブラリでは、共有文字列ごとに辞書を用意する必要がありました。

このガイドでは、msg() 関数と useMessages() フックを使って、Next.js アプリの共有文字列を簡単に国際化する方法を解説します。

本ガイドでは次の内容を扱います:

msg() 関数を使うべきタイミング

msg() 関数と useMessages() フックの使い方

変数の使い方

よくある落とし穴


msg() 関数を使うべきとき

msg() 関数は、翻訳対象の文字列にマークを付けるためのシンプルな関数です。

文字列を含む共有オブジェクトの例を見てみましょう。

src/navData.ts
export const navData = {
  home: {
    label: 'Home',
    description: 'The home page',
    icon: 'home',
    href: '/',
  },
  about: {
    label: 'About',
    description: 'Information about the company',
    icon: 'info',
    href: '/about',
  },
}

通常、このオブジェクトを国際化するには、各文字列ごとに辞書エントリを作成するか、オブジェクトを翻訳済みデータを返す関数にリファクタリングする必要があります。

たとえば、次のようにします。

src/navData.ts
export const getNavData = (t: (content: string) => string) => ({
  home: {
    label: t('Home'),
    description: t('The home page'),
    icon: 'home',
    href: '/',
  },
  about: {
    label: t('About'),
    description: t('Information about the company'),
    icon: 'info',
    href: '/about',
  },
})

アプリ内で navData をインポートしていた箇所では、代わりに getNavData(t) を呼び出す必要があります。

src/app/page.tsx
import { getNavData } from '@/navData';

export default function Home() {
  const t = useGT();
  const navData = getNavData(t);
  return <div>{navData.home.label}</div>;
}

これは手間が増える原因になります。解決策は msg() 関数を使うことです。


msg() 関数の使い方

msg() 関数を使うには、文字列をそのまま関数に渡します。 この関数は翻訳に利用できる特別なエンコード済み文字列を返します。

src/navData.ts
import { msg } from 'gt-next';

export const navData = [
  {
    label: msg('Home'), // 出力の型は `string`
    description: msg('The home page'),
    icon: 'home',
    href: '/',
  },
  {
    label: msg('About'),
    description: msg('Information about the company'),
    icon: 'info',
    href: '/about',
  },
]

続いて、そのエンコード済み文字列を useMessages() フックに渡します。

src/app/page.tsx
import { useMessages } from 'gt-next';
import { navData } from '@/navData';

export default function Home() {
  const m = useMessages();
  return (
    <div>
      {navData.map((item) => (
        <div key={item.label}>{m(item.label)}</div>
      ))}
    </div>
  );
}

非同期コンポーネント内では、useMessages() フックの代わりに getMessages() 関数を使用してください。

src/app/page.tsx
import { getMessages } from 'gt-next/server';

export default async function Home() {
  const m = await getMessages();
  return (
    <div>
      {navData.map((item) => (
        <div key={item.label}>{m(item.label)}</div>
      ))}
    </div>
  );
}

注意: msg() 関数が返す文字列はエンコードされており、元の入力文字列とは異なります。 元の文字列を取得したい場合は、decodeMsg() でデコードしてください。


変数の使用

文字列がテンプレート文字列で、変数を含む場合は、msg() 関数にその変数を渡せます。

テンプレート文字列内の ${} 記法を {variable} に置き換え、msg() の第2引数に変数名をキーとするオブジェクトを渡します。

src/navData.ts
const items = 100;
export const pricing = [
  {
    name: 'Basic',
    price: 100,
    description: `The basic plan includes ${items} items`
  },
]

次のようになります:

src/navData.ts
import { msg } from 'gt-next';

const items = 100;
export const pricing = [
  {
    name: 'Basic',
    price: 100,
    description: msg('The basic plan includes {items} items', { items })
  },
]

{items} プレースホルダーは、items 変数の値に置き換えられます。

これにより、翻訳内で動的な値を表示できます。

API の詳細は、API reference を参照してください。

gt-next は ICU メッセージ形式をサポートしており、変数のフォーマットも可能です。

const price = 100;
msg('There are {count, plural, =0 {no items} =1 {one item} other {{count} items}} in the cart', { count: 10 });

ICU メッセージ形式は、変数を柔軟かつ強力にフォーマットする方法です。 詳細は、ICU message format documentation を参照してください。


  1. 共有グローバルオブジェクトを翻訳する
src/llms.ts
import { msg } from 'gt-next';

export const llmData = [
  {
    name: 'GPT-4.1',
    id: 'gpt-4.1',
    description: msg('GPT-4.1 is a large language model developed by OpenAI'),
  },
  {
    name: 'Claude 3.7 Sonnet',
    id: 'claude-3-7-sonnet',
    description: msg('Claude 3.7 Sonnet is a large language model developed by Anthropic'),
  },
];
src/app/page.tsx
import { llmData } from '@/llms';
import { useMessages } from 'gt-next';

export default function MyComponent() {
  const m = useMessages();
  return (
    <div>
      {llmData.map((llm) => (
        <div key={llm.id}>
          <h1>{llm.name}</h1>
          <p>{m(llm.description)}</p>
        </div>
      ))}
    </div>
  )
}
src/llms.ts
export const llms = [
  {
    name: 'GPT-4.1',
    id: 'gpt-4.1',
    description: 'GPT-4.1 is a large language model developed by OpenAI',
  },
  {
    name: 'Claude 3.7 Sonnet',
    id: 'claude-3-7-sonnet',
    description: 'Claude 3.7 Sonnet is a large language model developed by Anthropic',
  },
]
src/app/page.tsx
import { llms } from '@/llms';

export default function MyComponent() {
  return (
    <div>
      {llms.map((llm) => (
        <div key={llm.id}>
          <h1>{llm.name}</h1>
          <p>{llm.description}</p>
        </div>
      ))}
    </div>
  )
}
  1. 関数の戻り値を翻訳する
src/llms.ts
import { msg } from 'gt-next';

function mockData() {
  return [
    {
      name: 'GPT-4.1',
      id: 'gpt-4.1',
      company: 'OpenAI',
    },
    {
      name: 'Claude 3.7 Sonnet',
      id: 'claude-3-7-sonnet',
      company: 'Anthropic',
    },
  ];
}

export function getData() {
  const data = mockData();
  const modifiedData = data.map((item) => ({
    ...item,
    description: msg('{name} is a large language model developed by {company}', {
      name: item.name,
      company: item.company,
    }),
  }));
  return modifiedData;
}
src/app/page.tsx
import { getData } from '@/llms';
import { useMessages } from 'gt-next';

export default function MyComponent() {
  const m = useMessages();
  const data = getData();
  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>
          <h1>{item.name}</h1>
          <p>{m(item.description)}</p>
        </div>
      ))}
    </div>
  )
}
src/llms.ts
import { msg } from 'gt-next';

function mockData() {
  return [
    {
      name: 'GPT-4.1',
      id: 'gpt-4.1',
      company: 'OpenAI',
    },
    {
      name: 'Claude 3.7 Sonnet',
      id: 'claude-3-7-sonnet',
      company: 'Anthropic',
    },
  ];
}

export function getData() {
  const data = mockData();
  const modifiedData = data.map((item) => ({
    ...item,
    description: `${item.name} is a large language model developed by ${item.company}`,
  }));
  return modifiedData;
}
src/app/page.tsx
import { getData } from '@/llms';

export default function MyComponent() {
  const data = getData();
  return (
    <div>
      {data.map((item) => (
        <div key={item.id}>
          <h1>{item.name}</h1>
          <p>{item.description}</p>
        </div>
      ))}
    </div>
  )
}

よくある落とし穴

useMessages() の呼び出し忘れ

msg() は入力文字列をエンコードするため、JSX などでそのまま使用することはできません。

エンコード済みの文字列を直接使おうとすると、次のような見慣れない文字列になります:

const encodedString = msg('Hello, world!');
console.log(encodedString); // "Hello, world!:eyIkX2hhc2giOiJkMjA3MDliZGExNjNlZmM2In0="

これを解決するには、useMessages() フックを使って翻訳済みの文字列を取得します。

import { useMessages } from 'gt-next';

const encodedString = msg('Hello, world!');

export default function MyComponent() {
  const m = useMessages();
  return <div>{m(encodedString)}</div>; // 現在の言語での "Hello, world!"
}

また、元の文字列を復元したい場合は、decodeMsg() 関数を使用できます。

import { decodeMsg } from 'gt-next';

const encodedString = msg('Hello, world!');
const decodedString = decodeMsg(encodedString);
console.log(decodedString); // "Hello, world!"

動的コンテンツを msg() で囲む

すべての文字列はビルド時に確定している必要があります。つまり、動的コンテンツを msg() で囲むことはできません。

たとえば、次は無効です:

const dynamicContent = msg(`Hello, ${name}`);

これを解決するには、動的部分は変数として用意し、それを msg() に渡します。

const dynamicContent = msg('Hello, {name}', { name });

動的コンテンツを msg() で囲もうとすると、CLI ツールが警告します。


次のステップ

このガイドはいかがですか?