Skip to content

Internationalization

Both apps/web and apps/admin support internationalization. The implementation is self-rolled (no react-i18next or similar library) using Zustand stores and flat TypeScript locale files.

Locale files

Web portal (apps/web)

app/locales/
├── index.ts       # exports useLocaleStore and t() helper
├── en-US.ts       # English translations
└── zh-CN.ts       # Chinese translations

Each locale file exports a flat record of key-value pairs:

ts
export default {
  "dashboard.title": "Energy Dashboard",
  "dashboard.welcome": "Welcome, {name}",
  "nav.overview": "Overview",
  "nav.energy": "Energy",
  // ...
};

Using translations

In a React component:

tsx
import { useLocaleStore } from "~/locales";

function Header() {
  const { t } = useLocaleStore();
  return <h1>{t("dashboard.title")}</h1>;
}

Interpolation is supported:

tsx
<p>{t("dashboard.welcome", { name: user.username })}</p>

Switching language

The locale store persists the user's choice in localStorage. On first visit, it detects the browser language and falls back to zh-CN if the browser prefers Chinese, otherwise en-US.

tsx
const { setLocale } = useLocaleStore();
setLocale("zh-CN"); // or "en-US"

Adding a new translation key

  1. Add the key to both en-US.ts and zh-CN.ts with the appropriate translation.

  2. Use the key in your component via t("your.new.key").

  3. Run the dead-code checker to ensure no orphaned keys exist:

    bash
    uv run apps/web/scripts/check-locale-dead-code.py

Removing a translation key

  1. Remove all usages of the key from components.
  2. Remove the key from both locale files.
  3. Run the dead-code checker to confirm cleanliness.

Admin dashboard i18n notes

The admin dashboard does not currently use the locale system — most admin-facing text is kept in English inline. Only apps/web implements full i18n.

Locale file conventions

  • Use dot notation for namespacing: "nav.overview", "form.email.placeholder".
  • Keep keys alphabetical within each namespace for readability.
  • Do not nest objects — flat keys are easier to search and grep.
  • Use sentence case for English, avoid ALL CAPS.

Released under the MIT License.