npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@better-i18n/expo

v0.10.0

Published

Better i18n backend plugin for i18next in Expo/React Native

Readme

@better-i18n/expo

Better i18n integration for Expo and React Native. Fetches translations from the CDN with offline caching and auto-detected device locale.

Features

  • CDN-Powered — Translations served from the edge via Better i18n CDN
  • Offline-First — Network-first strategy with automatic persistent cache fallback
  • Persistent Storage — Pass storageAdapter() with MMKV or AsyncStorage for offline caching
  • Device Locale — Detect device language via expo-localization
  • i18next Compatible — Works as a backend plugin or one-call init helper
  • Namespace Auto-Discovery — Automatically registers all namespaces from CDN data
  • Lightweight — Built on @better-i18n/core (~2KB)

Installation

npm install @better-i18n/expo i18next react-i18next
# or
bun add @better-i18n/expo i18next react-i18next

Optional Peer Dependencies

| Package | Purpose | | --- | --- | | expo-localization | Auto-detect device locale | | react-native-mmkv | Fastest persistent storage | | @react-native-async-storage/async-storage | Common persistent storage |

Pass storageAdapter() with either package to enable persistent offline caching. Without it, the SDK uses in-memory storage (no persistence across restarts).

Quick Start

Recommended: initBetterI18n

One-call setup that fetches translations and initializes i18next with all namespaces pre-loaded:

import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { initBetterI18n } from "@better-i18n/expo";

i18n.use(initReactI18next);

const { core, languages } = await initBetterI18n({
  project: "acme/my-app",
  i18n,
  useDeviceLocale: true, // auto-detect device language
});

// `languages` contains available languages with metadata
// [{ code: "en", name: "English", ... }, { code: "tr", name: "Turkish", ... }]

Then use translations in any component:

import { useTranslation } from "react-i18next";

function HomeScreen() {
  // Colon-notation (namespace explicit):
  const { t } = useTranslation("common");
  return <Text>{t("welcome")}</Text>;

  // Dot-notation (default namespace):
  // const { t } = useTranslation();
  // return <Text>{t("common.welcome")}</Text>;
}

Translation Patterns

initBetterI18n supports both i18next usage styles — choose one and use it consistently:

Colon-notation — t('namespace:key')

// Scoped to a namespace via hook argument
const { t } = useTranslation("auth");
t("login")        // ✓
t("auth:login")   // ✓ (namespace explicit)

Dot-notation — t('section.key')

// Default namespace, dot-separated path
const { t } = useTranslation();
t("auth.login")      // ✓
t("common.welcome")  // ✓

Both patterns work simultaneously with no extra configuration — all CDN namespaces are merged into the "translation" default namespace in addition to being registered individually.


### Alternative: Backend Plugin

For more control, use `BetterI18nBackend` as an i18next backend plugin:

```ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import { BetterI18nBackend } from "@better-i18n/expo";

i18n
  .use(BetterI18nBackend)
  .use(initReactI18next)
  .init({
    backend: {
      project: "acme/my-app",
      defaultLocale: "en",
    },
    lng: "en",
    fallbackLng: "en",
    ns: ["common", "auth"],
    defaultNS: "common",
    interpolation: { escapeValue: false },
  });

API

initBetterI18n(options)

One-call setup that fetches translations from CDN and initializes i18next.

| Option | Type | Default | Description | | --- | --- | --- | --- | | project | string | Required | Project identifier in org/project format | | i18n | i18n | Required | i18next instance to configure | | defaultLocale | string | "en" | Fallback locale | | storage | TranslationStorage | In-memory | Persistent storage via storageAdapter() — see Offline & Caching | | staticData | Record \| () => Promise | undefined | Bundled translations for offline-first (e.g., first launch in airplane mode) | | fetchTimeout | number | 10000 | CDN fetch timeout in ms | | retryCount | number | 1 | Retry attempts on CDN failure | | useDeviceLocale | boolean | false | Auto-detect device locale via expo-localization | | debug | boolean | false | Enable debug logging | | i18nextOptions | Partial<InitOptions> | {} | Additional i18next init options (SDK always sets defaultNS and fallbackNS to "translation") |

Returns Promise<BetterI18nResult>:

| Property | Type | Description | | --- | --- | --- | | core | I18nCore | Core instance for accessing manifest, languages, etc. | | languages | LanguageOption[] | Available languages from the CDN manifest |

BetterI18nBackend

i18next backend plugin class. Pass options via backend in i18n.init():

| Option | Type | Default | Description | | --- | --- | --- | --- | | project | string | Required | Project identifier | | defaultLocale | string | "en" | Fallback locale | | cdnBaseUrl | string | "https://cdn.better-i18n.com" | Custom CDN URL | | cacheExpiration | number | 86400000 (24h) | In-memory cache TTL in ms | | storage | TranslationStorage | In-memory | Persistent storage via storageAdapter() — see Offline & Caching | | staticData | Record \| () => Promise | undefined | Bundled translations for offline-first | | fetchTimeout | number | 10000 | CDN fetch timeout in ms | | retryCount | number | 1 | Retry attempts on CDN failure | | debug | boolean | false | Enable debug logging |

getDeviceLocale(options?)

Get the device's primary locale using expo-localization.

import { getDeviceLocale } from "@better-i18n/expo";

const locale = getDeviceLocale({ fallback: "en" });
// "tr" on a Turkish device, "en" if detection fails

getDeviceLocales()

Get all device locales as language codes.

import { getDeviceLocales } from "@better-i18n/expo";

const locales = getDeviceLocales();
// ["tr", "en", "de"]

createMemoryStorage()

Create an in-memory storage adapter (no persistence). Useful for testing.

import { createMemoryStorage } from "@better-i18n/expo";

const storage = createMemoryStorage();

Caching Strategy

The SDK uses a network-first strategy with automatic fallback:

1. Check in-memory cache (TtlCache) → avoids redundant fetches within a session
2. Fetch from CDN (with timeout + retry) → always get fresh translations
3. On CDN failure → try staticData (bundled translations)
4. On staticData miss → fall back to persistent storage (MMKV/AsyncStorage)
5. No cache, no CDN, no static data → throw error

Offline-First with Static Data

For apps that must work on first launch without network (e.g., airplane mode):

import en from "./locales/en.json"; // { auth: {...}, common: {...} }
import tr from "./locales/tr.json";

const { core, languages } = await initBetterI18n({
  project: "acme/my-app",
  i18n,
  // Pass locale messages directly — same format as CDN response
  staticData: { en, tr },
  // Or lazy-load to keep bundle small:
  // staticData: () => import('./locales/fallback.json'),
  fetchTimeout: 5000, // fail fast on slow networks
  retryCount: 2,      // retry twice before falling back
});

Persistent Storage

Pass storageAdapter() with your preferred storage library:

import { MMKV } from 'react-native-mmkv';
import { storageAdapter } from '@better-i18n/expo';

storage: storageAdapter(new MMKV({ id: 'app' }))

Without storage, in-memory fallback is used (no offline support). See Offline & Caching for details.

Language Switching

When using initBetterI18n, language switching is handled automatically. New translations are fetched from CDN on demand:

import { useTranslation } from "react-i18next";

function LanguagePicker({ languages }) {
  const { i18n } = useTranslation();

  return (
    <FlatList
      data={languages}
      renderItem={({ item }) => (
        <Pressable onPress={() => i18n.changeLanguage(item.code)}>
          <Text>{item.nativeName}</Text>
        </Pressable>
      )}
    />
  );
}

Custom Storage Adapter

Implement the TranslationStorage interface to use any key-value store:

import type { TranslationStorage } from "@better-i18n/expo";

const myStorage: TranslationStorage = {
  getItem: async (key) => { /* ... */ },
  setItem: async (key, value) => { /* ... */ },
  removeItem: async (key) => { /* ... */ },
};

await initBetterI18n({
  project: "acme/my-app",
  i18n,
  storage: myStorage,
});

Part of Better i18n

This package is part of the Better i18n ecosystem:

| Package | Description | | --- | --- | | @better-i18n/core | Framework-agnostic core (CDN client, caching, locale utilities) | | @better-i18n/use-intl | React/TanStack Start integration via use-intl | | @better-i18n/next | Next.js integration via next-intl | | @better-i18n/expo | Expo/React Native integration via i18next | | @better-i18n/cli | CLI for detecting hardcoded strings | | @better-i18n/mcp | MCP server for AI-powered translation management |

Documentation

Full documentation at docs.better-i18n.com/frameworks/expo

License

MIT © Better i18n