Step 3: Configure localisation with Ovok CMS
What we are building
An i18next setup that reads the app's published strings from Ovok CMS. Translation content lives in the Console, not in JSON files bundled with the app. When a translator updates a published value, the app can receive it without a new native release.
What you should already have
- The Expo app and
ovokConfigfrom steps 1–2. - Ovok CMS enabled for the sandbox project.
i18nextandreact-i18nextinstalled in step 1.
The implementation
Add the first translation group in Ovok Console
In the Console, open the sandbox project's CMS and create a document in the built-in translations collection:
| Field | Value |
|---|---|
slug | app |
status | published |
strings | Add title, welcome, and description keys with English values. |
For example, the welcome key can have the English value Welcome. The CMS document stores localized values; it is not a file in the app. The translations collection is open by default, so the app needs its tenant code but no CMS API key. Publish this document to the sandbox staging environment, which is the environment used below. See Use Ovok CMS with i18n for the Console fields and fallback rules.
Initialize i18next without local translation resources
Create src/localization/i18n.ts:
import "@ovok/native/auth";
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
export const localizationReady = i18n
.use(initReactI18next)
.init({
lng: "en",
fallbackLng: "en",
supportedLngs: ["en", "de"],
interpolation: { escapeValue: false },
react: { useSuspense: false },
});
export default i18n;
There is intentionally no resources option and no locales/en.json: the CMS supplies app strings. Importing @ovok/native/auth registers the SDK's built-in English auth copy. When the CMS hook loads a matching group such as sign-in, its published values can replace those defaults.
Load CMS strings and gate the first screen
Create src/localization/localization-gate.tsx:
import * as React from "react";
import { ActivityIndicator, View } from "react-native";
import { Button, Text } from "react-native-paper";
import { useCmsTranslations } from "@ovok/native/cms";
import { ovokConfig } from "../config/ovok";
import { localizationReady } from "./i18n";
function CmsTranslations({ children }: React.PropsWithChildren) {
const { data, loading, error, reload } = useCmsTranslations({
tenantCode: ovokConfig.tenantCode,
environment: "staging",
});
if (error) {
return (
<View>
<Text>App language content could not be loaded.</Text>
<Button onPress={() => void reload()}>Retry</Button>
</View>
);
}
if (loading || !data) return <ActivityIndicator />;
return children;
}
export function LocalizationGate({ children }: React.PropsWithChildren) {
const [ready, setReady] = React.useState(false);
React.useEffect(() => {
void localizationReady.then(() => setReady(true));
}, []);
if (!ready) return <ActivityIndicator />;
return <CmsTranslations>{children}</CmsTranslations>;
}
useCmsTranslations reads the current i18next language, fetches the published CMS translation groups for it, and merges them into the translation namespace. Passing tenantCode allows this request before sign-in. It refetches when the language changes. The inline error and retry text are a bootstrap fallback for a CMS outage; they are not the app's translation catalog.
In step 5, place LocalizationGate inside OvokProvider and the app ThemeProvider, around the Expo Router stack. The gate waits for i18next and the first CMS response before showing screens that use translated text.
Use the starter group in a screen:
import { useTranslation } from "react-i18next";
import { Text } from "react-native";
export function WelcomeScreen() {
const { t } = useTranslation();
return <Text>{t("app.welcome")}</Text>;
}
Important Ovok decisions
- Keep application copy in the project's CMS
translationscollection. Do not add app translation JSON files. - Use bare CMS language codes such as
enandde; the CMS supportsde,en,fr, andes. The app's allowed language list should match languages actually published for its tenant. - A draft group is not delivered. Publish the group in the same environment the app requests.
- CMS text is public content. Never put patient data, secrets, or private project configuration in translation values.
- The system Bluetooth permission prompt is native platform copy, not CMS content. Step 10 explains where that text must be localized.
Expected result
The app initializes i18next without bundled translation dictionaries, loads the published app group from the sandbox CMS, and renders app.welcome from the CMS response.
Common errors and troubleshooting
- The app stays on the loading screen: confirm CMS is enabled, the
appgroup is published, and its tenant/environment match the app configuration. - The screen shows
app.welcome: check that the group slug isappand the key iswelcome; confirm the document is published. - The CMS request returns an unknown tenant: use the tenant code from the same sandbox project and allow time for a newly created tenant code to become available.
- The language changes but text does not: make sure a published group exists for the selected language. Missing CMS values fall back to English field by field.
Next step
Continue to step 4: choose connected devices.