Quick Start

Expo

This page demonstrates the minimal setup of Intor in an Expo project.


Installation

This guide assumes that an Expo project has already been created.
For project setup, see: Expo

To enable locale persistence, install:

  • expo-localization
  • @react-native-async-storage/async-storage
npx expo install expo-localization @react-native-async-storage/async-storage

Install Intor:

npm install intor
yarn add intor
pnpm add intor
bun add intor

Project Structure

index.json
U

index.json
U

_layout.tsx
M
index.tsx
M

intor-config.ts
U
intor-client-provider.tsx
U

Integration Steps

♯1 Messages

Create a messages directory in your project, and add a subdirectory for each locale.
Each locale provides an index.json file.

index.json
U

index.json
U
{
  "hello": "Hello, {name}",
  "rich": "A <tag>text</tag>."
}
{
  "hello": "Bonjour, {name}",
  "rich": "Un <tag>texte</tag>."
}

♯2 Config

Create an i18n/ directory under src/, and add an intor-config.ts file inside it.
This example uses the most basic translation loading approach by defining messages directly in the config.

For more details, see: Message Loading

intor-config.ts
U
import { defineIntorConfig } from "intor";
import EN from "../../messages/en/index.json";
import FR from "../../messages/fr/index.json";

export const intorConfig = defineIntorConfig({
  defaultLocale: "en",
  supportedLocales: ["en", "fr"],
  messages: {
    en: EN,
    fr: FR,
  },
});

♯3 Initialization

Create intor-client-provider.tsx inside the i18n/ directory.

intor-client-provider.tsx
U
import AsyncStorage from "@react-native-async-storage/async-storage";
import { getLocales } from "expo-localization";
import { matchLocale } from "intor";
import { IntorProvider } from "intor/react";
import { useEffect, useState, type ReactNode } from "react";
import { intorConfig } from "./intor-config";

const STORAGE_KEY = "locale";

function resolveLocale(candidate?: string): string {
  const matched = matchLocale(candidate, intorConfig.supportedLocales);
  return matched ?? intorConfig.defaultLocale;
}

function getInitialLocale(): string {
  return resolveLocale(getLocales()[0]?.languageTag);
}

export function IntorClientProvider({ children }: { children: ReactNode }) {
  const [locale, setLocale] = useState(getInitialLocale);

  useEffect(() => {
    (async () => {
      const stored = await AsyncStorage.getItem(STORAGE_KEY);
      if (stored) setLocale(resolveLocale(stored));
    })();
  }, []);

  return (
    <IntorProvider
      value={{
        config: intorConfig,
        locale,
        onLocaleChange: async (locale) => {
          await AsyncStorage.setItem(STORAGE_KEY, locale);
        },
      }}
    >
      {children}
    </IntorProvider>
  );
}

Then, wrap your application with this provider in _layout.tsx.

_layout.tsx
M
// ...
import { IntorClientProvider } from "../i18n/intor-client-provider";

export default function TabLayout() {
  const colorScheme = useColorScheme();
  return (
    <IntorClientProvider>
      <ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
        <AnimatedSplashOverlay />
        <AppTabs />
      </ThemeProvider>
    </IntorClientProvider>
  );
}

Usage Example

Use useTranslator to access translation capabilities:

  • t: returns the resolved text
  • tRich: renders structured messages with semantic tags
index.tsx
M
import { useTranslator } from "intor/react";
import { Button, Text, View } from "react-native";

export default function HomeScreen() {
  const { t, tRich, setLocale } = useTranslator();

  return (
    <View style={{ margin: 80, gap: 20, backgroundColor: "white" }}>
      <Text>{t("hello", { name: "Intor" })}</Text>

      <Text>
        {tRich("rich", {
          tag: (children) => (
            <Text style={{ fontWeight: "bold" }}>{children}</Text>
          ),
        })}
      </Text>

      <Button title="English" onPress={() => setLocale("en")} />
      <Button title="French" onPress={() => setLocale("fr")} />
    </View>
  );
}