# @better-i18n/expo

## 0.8.7

### Patch Changes

- Updated dependencies [a2562b2]
- Updated dependencies [61636a4]
  - @better-i18n/core@0.12.0

## 0.8.6

### Patch Changes

- Updated dependencies [5522999]
  - @better-i18n/core@0.11.0

## 0.8.5

### Patch Changes

- Updated dependencies [6698bd0]
  - @better-i18n/core@0.10.0

## 0.8.4

### Patch Changes

- Updated dependencies [f0847a2]
  - @better-i18n/core@0.9.0

## 0.8.3

### Patch Changes

- Updated dependencies [b8958fc]
  - @better-i18n/core@0.8.0

## 0.8.2

### Patch Changes

- Updated dependencies [0407289]
  - @better-i18n/core@0.7.0

## 0.8.1

### Patch Changes

- 89dd681: Remove "bun" export conditions that referenced unpublished src/ files

  All packages had `"bun": "./src/*.ts"` conditions in their exports map, but `src/` is not included in the npm package (`files: ["dist"]`). Bun runtime resolves the "bun" condition before "default", causing module resolution failures for customers using Bun to build their apps.

- Updated dependencies [89dd681]
  - @better-i18n/core@0.6.2

## 0.8.0

### Minor Changes

- 50d6bf7: Fix critical bug where unsupported device locales caused raw translation keys to appear. When a device language (e.g. Italian) isn't in the project's supported languages, the SDK now falls back to defaultLocale instead of loading empty translations. Also adds defense-in-depth warnings for missing storage, empty staticData, namespace drift, and useDeviceLocale without localeKey.

### Patch Changes

- Updated dependencies [817c761]
  - @better-i18n/core@0.6.1

## 0.7.8

### Patch Changes

- 9015776: Fix unsupported device locale causing raw translation keys. When a device locale (e.g. Spanish) doesn't exist in the project's supported languages, the SDK now automatically pre-loads the fallback locale (defaultLocale) so i18next's fallbackLng works correctly. Previously, only the unsupported locale's empty translations were loaded, causing all t() calls to return raw keys.

## 0.7.7

### Patch Changes

- 28ed2b2: Add defense-in-depth warnings for mobile offline safety. The SDK now warns at init time when: no persistent storage is provided, no staticData bundled, staticData contains empty objects, or staticData is missing namespaces that exist on CDN. A critical warning is logged when neither storage nor staticData is configured — a combination that will cause App Store rejection if CDN is unreachable.

## 0.7.6

### Patch Changes

- Updated dependencies [3a5b2b6]
- Updated dependencies [6bf6952]
- Updated dependencies [11c3426]
  - @better-i18n/core@0.6.0

## 0.7.5

### Patch Changes

- Updated dependencies [1c8bc9b]
  - @better-i18n/core@0.5.0

## 0.7.4

### Patch Changes

- Updated dependencies [faccfdd]
  - @better-i18n/core@0.4.0

## 0.7.3

### Patch Changes

- Updated dependencies [1da02c8]
  - @better-i18n/core@0.3.0

## 0.7.2

### Patch Changes

- Updated dependencies
  - @better-i18n/core@0.2.4

## 0.7.1

### Patch Changes

- Updated dependencies
  - @better-i18n/core@0.2.3

## 0.7.0

### Minor Changes

- fc5fbc1: Add reactive `useLanguages()` hook and `refreshLanguages()` to prevent empty language list on fast cold starts

  `getLanguages()` is synchronous and returns `[]` before `initBetterI18n` completes. In production builds (no Metro overhead), users can navigate to language settings before init finishes, seeing an empty list.

  New exports:
  - `useLanguages()` — React hook using `useSyncExternalStore` that returns `[]` initially and automatically re-renders when languages are loaded
  - `refreshLanguages()` — Re-fetches language list from CDN, updates cache, and notifies all subscribers (useful for pull-to-refresh)
  - `subscribeLanguages(listener)` — Low-level subscription for non-React consumers
  - `getLanguagesSnapshot()` — Snapshot function for `useSyncExternalStore` compatibility

## 0.6.2

### Patch Changes

- 01f77f9: Fix widget gallery showing stale language after language change

  When language was changed via `changeLanguage()`, the persistent cache (AppGroup storage) was only written during CDN fetch. If translations were already loaded in memory, the cache was never updated — causing iOS widget gallery to display the previous language.

  Now `changeLanguage()` reconstructs messages from the i18next store and writes them to persistent cache even when no CDN fetch is needed.

## 0.6.1

### Patch Changes

- f3403e1: fix: add .js extensions to relative imports for Node.js ESM compatibility
- Updated dependencies [f3403e1]
  - @better-i18n/core@0.2.2

## 0.6.0

### Minor Changes

- abf4532: Add `getLanguages()` — a synchronous function that returns the languages resolved during `initBetterI18n()`. This eliminates the need for consumers to cache language data in React state.

## 0.5.1

### Patch Changes

- Fix offline caching: translations now survive app restarts.
  - Bridge persistent storage to `@better-i18n/core` so both cache layers (core + expo) write and read correctly
  - Gracefully handle manifest CDN failure — `getLanguages()` no longer kills the entire init when offline
  - Warn in debug mode when no persistent storage is provided
  - Handle empty `supportedLngs` when manifest is unavailable offline

## 0.5.0

### Minor Changes

- feat(expo): storageAdapter localeKey + optional i18n instance

  ### New Features

  **`storageAdapter(storage, { localeKey })`** — locale persistence built-in:
  - Adds `readLocale()` / `writeLocale()` to the adapter
  - `initBetterI18n` reads the saved locale before init (eliminates English flash)
  - Every `i18n.changeLanguage()` call auto-persists the new locale
  - Works with both AsyncStorage and MMKV

  **`i18n` parameter is now optional** — defaults to the global i18next singleton,
  which is always the same module instance in React Native.

  ### Migration

  ```ts
  // Before (v0.4.x) — 40 lines of boilerplate
  const LOCALE_KEY = '@app:locale';
  export async function initializeI18n() {
    const stored = await AsyncStorage.getItem(LOCALE_KEY);
    return initBetterI18n({ i18n, defaultLocale: stored ?? 'en', ... });
  }
  export function setLanguage(lang: string) {
    AsyncStorage.setItem(LOCALE_KEY, lang);
    i18n.changeLanguage(lang);
  }

  // After (v0.5.0) — 12 lines
  export const i18nReady = initBetterI18n({
    storage: storageAdapter(AsyncStorage, { localeKey: '@app:locale' }),
    defaultLocale: 'en',
  });
  ```

## 0.4.2

### Patch Changes

- 173335f: fix(expo): revert dynamic import() → require() to prevent Metro bundle crash

  Metro bundler crash when using `import()` for optional peer dependencies:

  ```
  The "to" argument must be of type string. Received undefined: node:path
    at Object.relative (node:path:1358:5)
    at @expo/metro-config/src/serializer/fork/js.ts:109
  ```

  **Root cause:** `import()` triggers Metro's lazy chunk mechanism. For missing
  modules, Metro sets the path to `undefined` → `path.relative(bundleDir, undefined)` crashes
  at serialization time (before the app even boots).

  **Fix:** Use `require()` instead. Metro creates stubs with real file paths for
  `require()` calls → `path.relative()` works → stub executes at runtime →
  `throw` is caught by the surrounding `try/catch`.

## 0.4.1

### Patch Changes

- 12210eb: fix(expo): offline cache fallback + Metro storage resolution

  ### Offline fallback (helpers.ts)

  `Promise.all` içinde `core.getLanguages()` (manifest fetch) offline'da throw edince tüm init iptal oluyordu — MMKV cache'deki çeviriler hiç yüklenmiyordu.

  ```ts
  // Before: getLanguages() throw → Promise.all → init cancelled
  core.getLanguages();

  // After: offline'da boş array döner, çeviriler MMKV'den yüklenir
  core.getLanguages().catch(() => [] as LanguageOption[]);
  ```

  ### Metro storage resolution (storage.ts)

  `require("react-native-mmkv")` Metro stub bazı versiyonlarda try/catch'i bypass edip "Requiring unknown module" hatasına yol açıyordu. `import()` ile Promise-tabanlı hata yakalamaya geçildi.

  ```ts
  // Before: senkron require → Metro stub try/catch bypass
  const { MMKV } = require("react-native-mmkv");

  // After: async import → her zaman catch edilebilir
  const { MMKV } = await import("react-native-mmkv");
  ```

  `@react-native-async-storage/async-storage` ve `react-native-mmkv`'nin **ikisi de yüklü olmadığında** artık `createMemoryStorage()` fallback'ine düşer.

  ### react-native export condition (package.json)

  Metro 0.82+ varsayılan condition listesi `['require', 'react-native']` — eksik `"react-native"` export koşulu bazı Metro versiyonlarında modül resolution belirsizliğine neden oluyordu.

- Updated dependencies [12210eb]
  - @better-i18n/core@0.2.1

## 0.4.0

### Minor Changes

- de2de32: feat: support both colon-notation and dot-notation translation patterns

  All CDN namespaces are now merged into a unified `"translation"` namespace in addition
  to being registered as individual namespaces. This means both usage styles work without
  any configuration or workarounds:
  - `t('auth:login')` — colon-notation with explicit namespace (unchanged)
  - `t('auth.login')` — dot-notation via default `"translation"` namespace (new)
  - `useTranslation('auth')` + `t('login')` — scoped namespace hook (unchanged)

  `defaultNS` is now always `"translation"` instead of being resolved dynamically.
  Language switching (`changeLanguage`, `languageChanged`) pre-loads both the merged
  bundle and individual namespaces for the new locale.

  **staticData format**: Pass locale messages directly (`staticData: { en, tr }`) —
  no extra namespace wrapper is needed.

### Patch Changes

- Updated dependencies [5fd34e0]
  - @better-i18n/core@0.2.0

## 0.3.3

### Patch Changes

- fix: Normalize locale to lowercase for CDN compatibility (BCP 47 sub-locale fix)
- Updated dependencies
  - @better-i18n/core@0.1.10

## 0.3.2

### Patch Changes

- 6024288: Deprecate BetterI18nBackend in favor of initBetterI18n
  - Add `@deprecated` JSDoc to BetterI18nBackend class and its re-export
  - Fix changeLanguage callback type from `any` to `i18next.Callback`
  - Reorder exports: initBetterI18n first, deprecated backend last
  - Rewrite all docs to use initBetterI18n as the primary API

## 0.3.1

### Patch Changes

- 3836f94: Add staticData, fetchTimeout, and retryCount passthrough to core
  - initBetterI18n and BetterI18nBackend now accept staticData, fetchTimeout, retryCount
  - staticData enables offline-first: bundled translations work on first launch without network
  - Backend tests updated for core's global cache behavior

- Updated dependencies [3836f94]
  - @better-i18n/core@0.1.9

## 0.3.0

### Minor Changes

- 24dd2a0: Add namespace auto-registration in backend plugin

  When the CDN returns multi-namespace data (nested or flat format), the backend now automatically registers sibling namespaces in i18next's resource store via `addResourceBundle()`. This eliminates the need for manual namespace discovery on the app side — all namespaces from a single CDN response are immediately available to i18next.

## 0.2.1

### Patch Changes

- Fix package entry points to resolve from dist/ instead of missing src/

  Root-level main/types/exports now point to dist/ directly. Added "bun" conditional export for monorepo development.

- Updated dependencies
  - @better-i18n/core@0.1.8

## 0.2.0

### Minor Changes

- 043c5f4: Add `@better-i18n/expo` - i18next backend plugin for Expo/React Native with offline caching

  **@better-i18n/expo:**
  - i18next `BackendModule` with network-first strategy (always fresh translations)
  - Persistent offline fallback via MMKV or AsyncStorage (auto-detected, zero config)
  - Device locale detection via expo-localization
  - `initBetterI18n()` convenience helper for one-liner setup

  **@better-i18n/core:**
  - Add `Cache-Control: no-cache` header to CDN fetch requests for proper cache revalidation

### Patch Changes

- 1f45586: Fix broken package entry points — `main`/`exports` now point to `dist/` directly

  `bun publish` does not apply `publishConfig` overrides, causing `main: "./src/index.ts"` to be published while `src/` is not included in the tarball. This broke Metro (React Native) and any bundler that doesn't fall back to `publishConfig`.
  - Root-level `main`/`types` now point to `./dist/index.js` and `./dist/index.d.ts`
  - Added `"bun"` conditional export for monorepo dev (`./src/index.ts`)
  - Removed broken `publishConfig` field overrides (kept `access: "public"` only)

- Updated dependencies [043c5f4]
- Updated dependencies [1f45586]
  - @better-i18n/core@0.1.7
