Skip to content
Hooney
한국어

Engines & modules · In use

i18n

A multilingual package that gives browser screens, servers, notifications, and email the same translation and date-format rules. A phrase missing from the translations shows up while the code is being written.

Period
2026.04 ~ Present
Organization
Firstage
Role
Design and implementation (solo)
Three phone screens of the Firstage homepage in Korean, English, and Japanese, with the headline, buttons, and date formats changing for each language.

Core value

Servers keep the language of the person on screen apart from the language of the person receiving a message, so a push notification about someone else reaches each recipient in their own language.

Outcomes

  • Powers ROUND, the Firstage homepage, the Journey app and server, and email templates
  • Screens (React), web servers (Next.js), and app servers (Node) share one translation core
  • Flags any phrase missing from the translations as an error right away
  • Push copy chosen by the recipient's language is tested in Korean, Japanese, and English

Tech stack

  • TypeScript
  • React
  • Next.js
  • Node.js

Context

Firstage serves Korean, English, and Japanese, and the same copy appears in browser screens, Next.js server rendering, and the notifications and emails sent by NestJS servers. When each runtime translates its own way, missing keys and mixed-up languages slip in, so I gathered translation and formatting into one core and split only the runtime adapters.

Decisions and implementation

  • The core runs anywhere, and the React (client), Next.js App Router (server), and Node (NestJS and Express) adapters are separate entry points, so each bundle takes only what it uses.
  • Translation keys are inferred as types from the imported JSON catalogs, so an unknown key fails type checking. Intl formatters are cached per set of options.
  • The language of the person on screen and a message that must be translated into a named language go through different doors. A message sent by a server is read by its recipient, not its sender, so the server adapters carry a separate forLocale that names the language.
  • React deliberately has no door that names a language. A screen renders for one reader, so a second language means a second Provider. The decision came from a workaround in an app that drifted away from its Provider.
  • Language negotiation and Accept-Language parsing come from a single internationalization package in the workspace.

Current state

ROUND, the Firstage homepage and blog, the Journey app and server, the internal Dashboard, and email templates handle languages with this package. The Journey server picks push copy in the language each recipient saved and falls back to English when none is stored.