RORK LABEN
PRODUCT — Rork Maxがネイティブ Swift アプリを生成。iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageに対応しますNATIVE — Rork MaxはAR/LiDAR・Metalの3Dゲーム・Dynamic Island・Live Activities・HealthKit・Core MLなどを解放しますCLASSIC — 通常のRorkはReact Native(Expo)で、英語の説明だけからiOS/Androidアプリを生成しストア配信できますFUNDING — a16zから$2.8Mを調達(別途$15Mも)。月743,000訪問・成長率85%と伸びていますPRICING — 無料で始められ、有料プランは月25ドル〜。Rork Maxは月200ドルですCHOICE — クロスプラットフォームのRorkか、Apple専用機能まで踏み込むRork Maxか、用途で選び分けられますPRODUCT — Rork Maxがネイティブ Swift アプリを生成。iPhone・iPad・Apple Watch・Apple TV・Vision Pro・iMessageに対応しますNATIVE — Rork MaxはAR/LiDAR・Metalの3Dゲーム・Dynamic Island・Live Activities・HealthKit・Core MLなどを解放しますCLASSIC — 通常のRorkはReact Native(Expo)で、英語の説明だけからiOS/Androidアプリを生成しストア配信できますFUNDING — a16zから$2.8Mを調達(別途$15Mも)。月743,000訪問・成長率85%と伸びていますPRICING — 無料で始められ、有料プランは月25ドル〜。Rork Maxは月200ドルですCHOICE — クロスプラットフォームのRorkか、Apple専用機能まで踏み込むRork Maxか、用途で選び分けられます
記事一覧/開発ツール
開発ツール/2026-06-23中級

価格が一部の端末だけ「¥1234」になる——Expo の Intl を信用しきらないフォーマット層の作り方

同じコードなのに、ある端末では「¥1,234」、別の端末では「¥1234」。Expo / Hermes の Intl は端末とOSのロケールデータに依存して出力が変わります。通貨・数値・日付・タイムゾーンを1ファイルに集約し、Intl が欠ける端末へ安全に後退するフォーマット層の設計を、動くコードで整理しました。

Expo95React Native178Intlローカライズ5Hermes5

プレミアム記事

ある朝、自分の運営アプリのレビューに「価格表示が崩れている」という一文が届きました。スクリーンショットを見ると、本来「¥1,480」と出るはずの値段が「¥1480」と、桁区切りのカンマだけが抜け落ちています。手元の検証端末では再現しません。コードは一行も変えていないのに、です。

原因はコードではなく、端末が持っているロケールデータの差でした。React Native(Expo) のアプリは JavaScript エンジンに Hermes を使うことが多く、その Intl 実装は端末側の国際化データ(ICU)に寄りかかっています。つまり同じ Intl.NumberFormat を呼んでも、OS のバージョンや言語設定によって出力文字列が微妙に変わり得ます。

個人開発で複数のアプリを各国向けに出していると、この「自分の端末では起きない不具合」が一番やっかいです。今日は、価格・数値・日付の表示を Intl 任せにせず、崩れを出荷前に捕まえるためのフォーマット層を、実際に私が使っている形で整理します。

なぜ「同じコード」で表示が変わるのか

Intl は ECMAScript の国際化 API ですが、フォーマットに必要なロケールデータ(通貨記号の位置、桁区切り、月名、タイムゾーン規則)はランタイムが用意します。Hermes はこの部分を端末の ICU に橋渡しする設計のため、次の3つが変数になります。

ひとつ目は OS バージョンです。Android は OS に同梱された ICU を使うため、古い端末ほどロケールデータが古く、新しい通貨や地域の表記が欠けることがあります。ふたつ目はユーザーの言語・地域設定で、navigator 由来のデフォルトロケールはユーザーが自由に変えられます。みっつ目はエンジンのビルド構成で、Intl を含むかどうか、どこまで含むかがプロジェクトの設定に依存します。

ここで重要なのは、Intl が「ある/ない」の二択ではなく、「あるが、機能ごとに届く範囲が違う」状態だという点です。NumberFormat は広く動くのに、DateTimeFormattimeZone に任意の IANA 名(Asia/Tokyo など)を渡すと一部端末で無視される、といった部分的な欠落が現実に起きます。

フォーマットの呼び出しを一点に集める

最初の打ち手は、アプリ中に散らばった toLocaleString() や手書きの桁区切りを禁止し、フォーマットの入口を1ファイルに集約することです。表示の崩れが起きても、直す場所が1か所で済みます。

// lib/format.ts — アプリ内のフォーマットはすべてここを通す
import { getLocales } from "expo-localization";
 
// 端末の第一言語。取れなければ日本語にフォールバック
export const APP_LOCALE = getLocales()[0]?.languageTag ?? "ja-JP";
 
// Intl が実際に使えるかを起動時に一度だけ判定する
const hasIntl =
  typeof Intl !== "undefined" &&
  typeof Intl.NumberFormat === "function" &&
  // 通貨スタイルが例外を投げないかまで確かめる
  (() => {
    try {
      new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }).format(1);
      return true;
    } catch {
      return false;
    }
  })();
 
export function formatNumber(value: number, locale: string = APP_LOCALE): string {
  if (hasIntl) {
    return new Intl.NumberFormat(locale).format(value);
  }
  // 後退: 3桁ごとにカンマを入れる素朴な実装
  return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

hasIntltry/catch 付きで一度だけ評価しているのが要点です。Intl オブジェクトが存在しても、style: "currency" のような特定オプションで例外を投げる端末が稀にあるため、「呼べること」まで確認してから採用します。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
Intl.NumberFormat / DateTimeFormat が端末・OS・エンジンで出力を変える理由と、出荷前に崩れを検知する方法
通貨・数値・日付・相対時間を1ファイルに集約する format 層の実装(フォールバック付きの動くコード)
任意の IANA タイムゾーンが効かない端末への後退と、UTC保存・ロケール表示を分ける設計
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

関連記事

開発ツール2026-03-15
Expo SDK 55 で始める最新パフォーマンス最適化テクニック
Expo SDK 55がもたらすHermes V1、EASビルドキャッシング、モーダル改善など最新のパフォーマンス最適化手法を完全解説。実装例とベストプラクティス付き。
開発ツール2026-06-23
アプリスイッチャーに個人的な画面が残る——背景へ回した瞬間のスナップショットを隠す設計
Rork が生成した React Native アプリをホームに戻すと、iOS はその瞬間の画面をアプリスイッチャー用に撮影してディスクに保存します。手記や個人的な入力画面はそこに残り、第三者の目に触れます。iOS の目隠しオーバーレイ(inactive で出す理由)、Android の FLAG_SECURE、機密画面だけに絞る方法、スクショ検知までを動くコードで示します。
開発ツール2026-06-22
壊れたキャッシュで毎回起動時に落ちるアプリ——ユーザーが自力で抜け出せる「セーフモード起動」を設計する
永続化したキャッシュが壊れて起動のたびに同じ場所で落ちると、ユーザーには再インストールしか残りません。アプリ自身が早期クラッシュの連続を数え、対話可能になって初めて起動を確定し、危険な状態だけを段階的にリセットするセーフモードを、Expo(React Native)の実装で設計します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →