OpenTrip Docs
Frontend

Internationalization (i18n)

Internationalization (i18n)

The frontend is fully internationalized with react-i18next. All user-facing copy is centralized in locale resources — this also satisfies the repository's "no hardcoded strings" rule.

Setup

  • apps/web/src/shared/i18n configures i18next with the React bindings and a language detector (localStorage + browser language), defaulting to English.
  • The I18nProvider is wired in app/providers.

Languages

  • en — English (default / fallback).
  • zh — Chinese (Simplified).

Locale resources live in shared/i18n/locales/<lang>/<namespace>.json.

Namespaces

  • common — shared words, actions, status labels.
  • trips — the trips home.
  • planner — the planner (sidebar, map, schedule, budget, detail).
  • auth — sign-in / sign-up.
  • invite — invite acceptance surface.
  • agent — the trip agent chat.
  • landing — the marketing landing page.
  • error — error surfaces (404 / 500 / 403 / 503 / offline) and photo alt text.

Usage

import { useTranslation } from "react-i18next";

function AddStopButton() {
  const { t } = useTranslation("planner");
  return <Button>{t("schedule.add")}</Button>;
}

Rules:

  • No literal user-facing strings in components — always a translation key.
  • Keys are namespaced and hierarchical (schedule.add, budget.settleUp).
  • Interpolation for dynamic values: t("itinerary.count", { count }).
  • Numbers/currency use Intl via shared formatters so locale affects grouping; the trip currency (JPY) is fixed by data, not by UI language.

Language switch

A LanguageSwitch coss Select in the app shell lets the user pick a language; the choice is persisted to localStorage by the detector.

Adding a language

  1. Add shared/i18n/locales/<lang>/*.json mirroring the en keys.
  2. Register it in shared/i18n/index.ts.
  3. Add its label to the language switch.

On this page