# How @extension/i18n Delivers Type-Safe Internationalization for Chrome Extensions

> Discover how @extension/i18n provides type-safe internationalization for Chrome extensions. Compile-time checks and automatic API switching ensure robust multilingual support.

- Repository: [JongHak Seo/chrome-extension-boilerplate-react-vite](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite)
- Tags: deep-dive
- Published: 2026-03-05

---

**The @extension/i18n package generates TypeScript types directly from your default locale JSON, exposing a single `t()` function that validates message keys at compile time while automatically switching between local JSON files in development and Chrome's native i18n API in production.**

The `chrome-extension-boilerplate-react-vite` repository includes a dedicated i18n package that eliminates runtime translation errors through compile-time type checking. By deriving message keys directly from the default locale file and swapping implementations based on the build environment, it provides seamless type-safe internationalization for both development and production contexts.

## Compile-Time Type Safety from the Message Catalog

Type safety begins in [`packages/i18n/lib/types.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/i18n/lib/types.ts), where the package imports the default English locale file as a TypeScript constant and derives all valid message keys from its structure.

```typescript
// packages/i18n/lib/types.ts
import enMessage from '../locales/en/messages.json';

export type MessageKeyType = keyof typeof enMessage;   // "greeting" | "error_network" | ...
export type LocalesJSONType = typeof enMessage;       // Full shape of locale JSON

```

**`MessageKeyType`** becomes the source of truth for every translation helper. Because TypeScript validates keys against this type at compile time, any typo in a message key triggers an immediate type error before the code runs. This approach guarantees that only strings actually present in [`locales/en/messages.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/locales/en/messages.json) can be passed to the translation function.

## Dual-Mode Implementation Strategy

The package maintains two separate implementations that share the same type-safe API surface. Depending on the build environment, the system selects either the development or production version.

### Development Mode with Local JSON

In development, [`packages/i18n/lib/i18n-dev.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/i18n/lib/i18n-dev.ts) imports the selected locale JSON directly and performs placeholder substitution using a lightweight runtime routine. The core `translate` function accepts a key and optional substitutions, then replaces placeholders like `$1` or `$2` with the provided values.

```typescript
// packages/i18n/lib/i18n-dev.ts
const translate = (
  key: keyof LocalesJSONType,
  substitutions?: string | string[]
): string => {
  // Placeholder replacement logic
  let text = localeJSON[key]?.message || '';
  // ... substitution handling
  return text;
};

export const t = (...args: Parameters<typeof translate>) =>
  removePlaceholder(translate(...args));

```

The exported **`t`** function wraps `translate` and sanitizes any remaining `$n` placeholders that were not substituted, ensuring clean output during local testing.

### Production Mode with Chrome API

For production builds, [`packages/i18n/lib/i18n-prod.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/i18n/lib/i18n-prod.ts) proxies calls directly to Chrome's native internationalization API while preserving the strict `MessageKeyType` constraint.

```typescript
// packages/i18n/lib/i18n-prod.ts
export const t = (key: MessageKeyType, substitutions?: string | string[]) =>
  chrome.i18n.getMessage(key, substitutions);

```

This implementation delegates placeholder handling to the browser, leveraging Chrome's optimized native routines while maintaining the same compile-time guarantees.

## Build-Time Selection and Locale Resolution

The package uses build scripts to seamlessly swap these implementations and resolve the correct locale files without manual configuration.

### Environment-Based File Swapping

[`packages/i18n/lib/prepare-build.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/i18n/lib/prepare-build.ts) determines which implementation file becomes the active entry point based on the **`IS_DEV`** environment flag.

```typescript
// packages/i18n/lib/prepare-build.ts
const isDev = process.env.IS_DEV === 'true';
const sourceFile = isDev ? 'i18n-dev.ts' : 'i18n-prod.ts';
// Copies selected file to lib/i18n.ts

```

The rest of the codebase imports from `@extension/i18n/lib/i18n.ts`, remaining environment-agnostic while the build process handles the underlying swap.

### Dynamic Locale Import Rewriting

For development, [`packages/i18n/lib/set-related-locale-import.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/i18n/lib/set-related-locale-import.ts) dynamically rewrites the import statement inside the i18n file to load the appropriate locale JSON based on the developer's environment variable or host browser settings.

```typescript
// packages/i18n/lib/set-related-locale-import.ts
// Rewrites import to: import localeJSON from '../locales/ko/messages.json';

```

This ensures that even when running locally, the system loads the correct language bundle (e.g., Korean or Spanish) rather than defaulting to English. The supported language codes are enumerated in [`packages/i18n/lib/consts.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/i18n/lib/consts.ts), providing a single source of truth for validation.

## Practical Usage Examples

Developers consume the package through a single, unified import that works identically in both environments.

```tsx
// src/components/Welcome.tsx
import { t } from '@extension/i18n';

export function Welcome({ userName }: { userName: string }) {
  // TypeScript error if "welcome_message" is not in locales/en/messages.json
  const welcome = t('welcome_message', userName);
  return <h1>{welcome}</h1>;
}

// Multiple placeholders using array substitutions
const errorMsg = t('error_multi', ['network', 'timeout']);
// Chrome replaces $1 with "network" and $2 with "timeout" in production

```

Because the **`t`** function accepts `MessageKeyType`, your IDE provides autocomplete for all valid message keys, and the TypeScript compiler blocks any undefined strings from reaching runtime.

## Summary

- **Type derivation from JSON**: `MessageKeyType` and `LocalesJSONType` in [`types.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/types.ts) generate compile-time constraints from the default locale file.
- **Dual implementations**: [`i18n-dev.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/i18n-dev.ts) handles local JSON loading and placeholder substitution, while [`i18n-prod.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/i18n-prod.ts) delegates to `chrome.i18n.getMessage`.
- **Build-time selection**: [`prepare-build.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/prepare-build.ts) swaps the active implementation based on the `IS_DEV` flag, ensuring the correct code path loads for each environment.
- **Dynamic locale resolution**: [`set-related-locale-import.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/set-related-locale-import.ts) rewrites imports to load the proper language file during development, while [`consts.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/consts.ts) maintains the canonical list of supported locales.

## Frequently Asked Questions

### How does the package prevent typos in translation keys?

The package imports the default locale JSON as a TypeScript type and creates `MessageKeyType` using `keyof typeof enMessage`. Any string passed to the `t()` function must satisfy this type, causing TypeScript to flag invalid keys immediately during development.

### What handles placeholder substitution in development versus production?

In development, the `translate` function inside [`i18n-dev.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/i18n-dev.ts) performs manual placeholder replacement for `$n` syntax. In production, the `t()` function in [`i18n-prod.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/i18n-prod.ts) passes substitutions directly to `chrome.i18n.getMessage`, which handles replacement using Chrome's native engine.

### How does the build process select the correct i18n implementation?

[`prepare-build.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/prepare-build.ts) checks the `IS_DEV` environment variable and copies either [`i18n-dev.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/i18n-dev.ts) or [`i18n-prod.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/i18n-prod.ts) to [`lib/i18n.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/lib/i18n.ts). This allows the rest of the application to import from a stable path while the underlying logic switches automatically.

### Can I add support for new languages without modifying the package source?

Yes. Add new locale directories under `packages/i18n/locales/` following the [`en/messages.json`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/en/messages.json) structure. For automatic import resolution during development, ensure the language code is recognized by the logic in [`set-related-locale-import.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/set-related-locale-import.ts) or listed in [`consts.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/consts.ts) if you require strict validation.