Skip to content
Comvi

Fetch loader

View .md

With an i18n instance from the Quick start and a Comvi project with Content Deploy enabled as on Connect to Comvi, fetch each locale’s JSON over HTTP with @comvi/plugin-fetch-loader. The root entry reads the public CDN and is the smaller one; the /api entry also talks to the platform API.

Terminal window
npm install @comvi/plugin-fetch-loader
src/main.tsx
import { createRoot } from 'react-dom/client';
import { createI18n, I18nProvider } from '@comvi/react';
import { cdnLoader } from '@comvi/plugin-fetch-loader';
import App from './App';
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
}).use(cdnLoader({ cdnUrl: 'https://cdn.comvi.io/<project-token>' }));
createRoot(document.getElementById('root')!).render(
<I18nProvider i18n={i18n} fallback={<p>Loading…</p>}>
<App />
</I18nProvider>,
);

.use(cdnLoader()) adds HTTP loading to the i18n instance. fallback paints until the first catalog is in. Omitting cdnUrl throws at composition time with [@comvi/plugin-fetch-loader] cdnUrl is required. On Vue the same .use() goes on the wrapper createI18n returns; on Nuxt it goes in the comvi.i18n.ts factory.

The CDN endpoint URL is under Settings → Content Deploy in your Comvi project, once Content Deploy is enabled and saved; pass it as cdnUrl. The URL format is on CDN Deployment.

cdnLoader(options) returns an installer: it takes the instance, composes the loader and plugin capabilities onto it, queues cdnLoaderPlugin(options) and returns the instance widened with registerLoader, reloadMessages and the rest of the loader surface. The plugin itself runs inside init(): it registers a load function for (locale, namespace) pairs, and core then loads the active namespaces through it, for the active locale and for each locale in fallbackLocale. setLocale() loads the target locale only. Compose the loader before init(); a plugin queued afterwards does not run until the next init().

A catalog with ICU messages ({count, plural, …}) needs compiler: icuCompiler on the instance; see Pluralize with ICU.

src/i18n.ts
import { createI18n } from '@comvi/core';
import { cdnLoader } from '@comvi/plugin-fetch-loader';
export const i18n = createI18n({ locale: 'en', fallbackLocale: 'en' }).use(
cdnLoader({
cdnUrl: 'https://cdn.comvi.io/<project-token>',
fallback: {
en: () => import('./locales/en.json'),
de: () => import('./locales/de.json'),
'en:admin': () => import('./locales/en/admin.json'),
},
}),
);
await i18n.init();

When a network load fails, the loader serves the matching import: the exact 'locale:namespace' key first, then the 'locale' shorthand for the default namespace. A fallback that resolves reports through onLoadSuccess, not onLoadError, because from the instance’s point of view the catalog arrived.

src/i18n.server.ts
import { createI18n } from '@comvi/core';
import { fetchLoader } from '@comvi/plugin-fetch-loader/api';
export const i18n = createI18n({
locale: 'en',
apiKey: process.env.COMVI_API_KEY,
}).use(
fetchLoader({
cdnUrl: 'https://cdn.comvi.io/<project-token>',
baseUrl: process.env.COMVI_API_URL,
}),
);
await i18n.init();

fetchLoader() from the /api entry reads the CDN, and switches to the platform API when the instance carries an apiKey: requests go out with Authorization: Bearer <apiKey>, one request carries every active namespace, and draft translations and private projects are reachable. The key needs project read and translation read access. cdnLoader() never builds an API URL, so an apiKey changes nothing there. The /api entry ships the API request path either way, which is why the root entry is smaller (Bundle size).

Option Default Entry Behaviour
cdnUrl required both The CDN base URL from Settings → Content Deploy.
baseUrl https://api.comvi.io /api The API origin. Precedence: baseUrl, then NEXT_PUBLIC_COMVI_API_URL, then the preset. The variable is read from process.env on the server; the name follows Next’s convention, and only Next can inline it into a browser bundle. Read only when the instance has an apiKey. Unrelated to Nuxt’s useLocaleHead({ baseUrl }).
fallback none both Import map served when a network load fails.
onLoadError none both (locale, namespace, error) when a pair fails and no fallback produced data. The instance’s loadError event fires too.
onLoadSuccess none both (locale, namespace) when a pair loads, including from fallback.
timeout 10000 both Per-request budget in milliseconds, covering the body.
retry true /api Retries the project-info request after a 429, 5xx or transport failure, twice at most. Timeouts and translation loads, CDN or API, are never retried; the root entry has no retry at all.
loadOnInit true both Whether the plugin loads at install time. Inside init() the instance’s own load is the only one, so the flag matters only when the factory runs by hand. Pass ns: [] to createI18n to start with no request.
cdnLayout { rootNamespace: defaultNs } both Which namespace lives at {cdnUrl}/{locale}.json.
cache none both { revalidate, tags } forwarded to fetch() as Next.js’s next option, which Next’s patched fetch honours; Nitro ignores it. revalidate: 0 always refetches, false caches indefinitely.

Both callbacks are read once, at install time, and neither fires after the plugin’s cleanup. Naming an /api-only option on the root entry is a compile error. Concurrent requests for one locale:namespace are deduplicated, clearMessages() aborts the loads it covers, and destroy() aborts everything in flight.

A failed pair emits loadError and calls your onLoadError. When other namespaces for the same locale loaded, the miss is reported through onError and t() keeps rendering from the cache, walking fallbackLocale for the keys that never arrived. When every requested namespace fails, the load rejects, so await i18n.init() or setLocale() throws. A failed load of a fallbackLocale catalog is reported the same way and never rejects.

src/i18n.ts
import { createI18n } from '@comvi/core';
import { cdnLoader } from '@comvi/plugin-fetch-loader';
export const i18n = createI18n({
locale: 'en',
defaultNs: 'storefront',
ns: ['storefront', 'default'],
}).use(
cdnLoader({
cdnUrl: 'https://cdn.comvi.io/<project-token>',
cdnLayout: { rootNamespace: 'default' },
}),
);
await i18n.init();
Requested namespace URL
default {cdnUrl}/en.json
storefront {cdnUrl}/storefront/en.json

By default the instance’s defaultNs loads from {cdnUrl}/{locale}.json and every other namespace from {cdnUrl}/{namespace}/{locale}.json. Set rootNamespace when the namespace stored at the CDN root differs from your defaultNs, or rootNamespace: false to put every namespace in its own folder. API mode always names the namespaces it requests.