Building Robust Offline-First Apps with i18next
In the modern web and mobile landscape, we often build applications under the assumption of perfect connectivity. We assume the API is always reachable, the assets will always load, and the user is always online. But the reality is different: trains go into tunnels, mobile signals drop in elevators, and corporate firewalls block "non-essential" traffic.
For a truly resilient user experience, adoption of an Offline-First architecture is essential. This means your application should work as well (or as well as possible) without a network connection as it does with one.
But how does this apply to internationalization (i18n)? If your translations are hosted on a CDN or own backend to allow for instant updates, what happens when the user loses connection?
The Challenge: Dynamic vs. Static
Using a CDN for translations is powerful. It allows you to fix typos, update content, or add new languages without redeploying your entire application. However, relying solely on a network request for text content introduces a single point of failure. If the CDN is unreachable, your users might see translation keys (e.g., button.submit) instead of text.
To solve this, we need a strategy that offers the best of both worlds:
- Up-to-date translations from the CDN when online.
- Guaranteed availability from a local cache or bundle when offline.
The Solution: i18next Chained Backend
If you are using i18next, the architecture for this is already solved via the i18next-chained-backend plugin. This plugin allows you to define a "waterfall" of backends. i18next will attempt to load translations from the first backend; if that fails (or if the cache is missing), it falls back to the next one in the chain.
Here is how to implement this pattern across different environments.
1. Browser: Caching with LocalStorage
For web applications, localStorage is an excellent place to cache translation JSON files. This reduces network requests on subsequent visits and allows the app to load text immediately, even if the user is offline.
You can combine i18next-locize-backend (primary) with i18next-localstorage-backend (cache).
import i18next from "i18next";
import ChainedBackend from "i18next-chained-backend";
import LocizeBackend from "i18next-locize-backend";
import LocalStorageBackend from "i18next-localstorage-backend";
i18next
.use(ChainedBackend)
.init({
fallbackLng: "en",
backend: {
backends: [
LocalStorageBackend, // 1. Try local storage first
LocizeBackend // 2. Fallback to network/CDN
],
backendOptions: [{
expirationTime: 7 * 24 * 60 * 60 * 1000 // Cache for 7 days
}, {
projectId: "YOUR_PROJECT_ID"
}]
}
});In this setup, i18next checks LocalStorage first. If the translations are found and valid (not expired), it uses them instantly. If not, it fetches them from Locize and updates the cache.
2. Mobile: React Native and AsyncStorage
Mobile apps are the most frequent victims of fluctuating network conditions. For React Native, the logic is identical, but we swap the storage engine for AsyncStorage.
import AsyncStorageBackend from "i18next-async-storage-backend";
// ... inside i18next init
backend: {
backends: [
AsyncStorageBackend, // Cache layer
LocizeBackend // Network layer
],
// ... options
}3. The Ultimate Safety Net: Bundled Fallback
What if the user opens the app for the very first time and they are already offline? The cache is empty, and the CDN is unreachable.
To cover this "Day 0" offline scenario, you can bundle a snapshot of your translations directly into the application build using i18next-resources-to-backend.
This creates a robust three-layer strategy:
- Locize CDN: Checks for the absolute latest version.
- Local Cache: Used if CDN is unreachable or for performance.
- Bundled Resources: Used if everything else fails.
import i18next from "i18next";
import ChainedBackend from "i18next-chained-backend";
import LocizeBackend from "i18next-locize-backend";
import resourcesToBackend from "i18next-resources-to-backend";
// Import your local JSON files (or load them dynamically)
const bundledResources = {
en: {
translation: {
welcome: "Hello World (Bundled)"
}
}
};
i18next
.use(ChainedBackend)
.init({
backend: {
backends: [
// LocalStorageBackend, // alternatively also use a local cache...
LocizeBackend, // 1. Try Network
resourcesToBackend(bundledResources) // 2. Fallback to Bundled
// resourcesToBackend((language, namespace) => import(`./locales/${language}/${namespace}.json`))
],
backendOptions: [{
projectId: "YOUR_PROJECT_ID",
}]
}
});A Note on "Stale" Data
When using a chained backend strategy, be cautious with features like saveMissing (which uploads missing keys to Locize) or updateMissing. Since your app might be reading from an old cache or an outdated bundle, triggering updates based on this stale data can lead to inconsistencies. It is generally recommended to disable these features when using multiple backends.
Summary
You do not have to choose between the agility of a CDN and the reliability of local files. By using Locize as your source of truth and i18next's chained backend to handle caching and fallback, you ensure your application remains useful in highly regulated networks, tunnels, and flights.
Ready to make your app offline-capable? Check out the backend fallback documentation for deep dives into specific configurations for Node.js, Next.js, and more.