RORK LABEN
BUILD — Rork Max はクラウド上の実機 Mac に Xcode と iOS SDK を載せ、SwiftUI を書き、ビルドし、エラーを読んで直す反復を回します。コードを吐いて終わりではない点が生成物の質に効いていますNATIVE — 生成されるのは React Native ではなく純粋な Swift / SwiftUI です。AR や Metal、ウィジェットなど React Native からは届かない機能に手が届くことが、他ビルダーとの実質的な差になりますPLATFORMS — 対応は iPhone・iPad・Apple Watch・Apple TV・Vision Pro に加えて iMessage まで含みます。ウォッチや拡張から入る設計を試すのに向いていますCOMPANION — Rork Companion により、有料の Apple Developer アカウントなしで生成したアプリを実機の iPhone で確認できます。最初の一本を試す段階の障壁が一段下がりましたPRICING — 無料で開始でき有料プランは月額25ドルから、Rork Max は月額200ドルの Max プランです。回収できる本数と規模を先に見積もっておく価値がありますDEADLINE — Google Play は2026年8月31日以降、新規アプリと更新の双方に対象 API レベル36 以上を要求します。残り10日で、生成物の targetSdkVersion は自分で確認が必要ですBUILD — Rork Max はクラウド上の実機 Mac に Xcode と iOS SDK を載せ、SwiftUI を書き、ビルドし、エラーを読んで直す反復を回します。コードを吐いて終わりではない点が生成物の質に効いていますNATIVE — 生成されるのは React Native ではなく純粋な Swift / SwiftUI です。AR や Metal、ウィジェットなど React Native からは届かない機能に手が届くことが、他ビルダーとの実質的な差になりますPLATFORMS — 対応は iPhone・iPad・Apple Watch・Apple TV・Vision Pro に加えて iMessage まで含みます。ウォッチや拡張から入る設計を試すのに向いていますCOMPANION — Rork Companion により、有料の Apple Developer アカウントなしで生成したアプリを実機の iPhone で確認できます。最初の一本を試す段階の障壁が一段下がりましたPRICING — 無料で開始でき有料プランは月額25ドルから、Rork Max は月額200ドルの Max プランです。回収できる本数と規模を先に見積もっておく価値がありますDEADLINE — Google Play は2026年8月31日以降、新規アプリと更新の双方に対象 API レベル36 以上を要求します。残り10日で、生成物の targetSdkVersion は自分で確認が必要です
記事一覧/開発ツール
開発ツール/2026-06-23中級

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

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

Expo175React Native227Intlローカライズ7Hermes7

プレミアム記事

ある朝、自分の運営アプリのレビューに「価格表示が崩れている」という一文が届きました。スクリーンショットを見ると、本来「¥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-08-09
Hermes バイトコードの差分更新を実測する — モジュール1本の追加が更新サイズを29倍にした
2.4MB の Hermes バイトコードで OTA 差分を実測しました。1行の変更は 2,104 バイト、モジュールを1本足すと 61,197 バイト。よく言われるモジュールID安定化はほぼ効かず、効いたのは別の2つでした。
開発ツール2026-03-15
Expo SDK 55 で始める最新パフォーマンス最適化テクニック
2026年2月リリースのExpo SDK 55が備えるパフォーマンス改善を解説します。デフォルトになったHermes V1エンジン、EASビルドキャッシング、react-native-screens 4.23.0によるモーダル改善、本番監視のExpo Observeまで、実装例付きで紹介します。
開発ツール2026-08-17
Expo UI の drop-in 置き換えで、依存が本当に1本減る条件
Expo UI が SDK 56 で安定版になり、コミュニティ製パッケージ8件に drop-in の置き換えが用意されました。import を1行入れ替えても依存が減らない場合があります。減る条件を依存グラフから機械的に判定する手順をまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →