Skip to content

Mobile Translation Guide

This guide explains how to add and maintain translations in the Okta-Mobile React Native application.

File Structure & Locations

Translation files are stored in:

Okta-Mobile/locales/
  en.json    # English (reference locale)
  ru.json    # Russian
  • Supported locales: en (English), ru (Russian)
  • Reference locale: English (en) is the source of truth
  • Library: Custom I18nManager class using Expo Localization

Adding New Translations

Step-by-Step Process

  1. Add the key to the English file first (Okta-Mobile/locales/en.json)
  2. Add the same key to the Russian file (Okta-Mobile/locales/ru.json)
  3. Maintain identical key order - keys should be in the same position in both files
  4. Verify in the app - test that translations appear correctly

Example

To add a new "session expired" message:

English (en.json):

{
  "common": {
    "start": "Start",
    "close": "Close",
    "loading": "Loading...",
    "sessionExpired": "Your session has expired. Please log in again."
  }
}

Russian (ru.json):

{
  "common": {
    "start": "Начать",
    "close": "Закрыть",
    "loading": "Загрузка...",
    "sessionExpired": "Ваша сессия истекла. Пожалуйста, войдите снова."
  }
}

Key Naming Conventions

Hierarchical Structure

Keys are organized hierarchically by feature or screen:

{
  "welcome": { ... },
  "login": { ... },
  "patientDashboard": { ... },
  "workout": { ... },
  "messages": { ... },
  "profile": { ... }
}

Naming Rules

  • Use camelCase for all keys: forgotPassword, dontHaveAccount, startExercise
  • Group related translations under a parent key
  • Use descriptive names that indicate purpose

Common Patterns

Pattern Example Use Case
feature.title workout.title Screen titles
feature.subtitle planPreview.subtitle Secondary headings
feature.buttonName login.submit Button labels
feature.loading signup.loading Loading states
feature.placeholder login.emailPlaceholder Input placeholders
feature.statusMessage aiStreaming.analyzing Rotating status messages during async operations
tabTitles.tabName tabTitles.sessions Tab bar labels

Variable Interpolation

Syntax

Use double curly braces for variables: {{variableName}}

The mobile app uses a regex pattern to match and replace variables:

/\{\{(\w+)\}\}/g

Examples from the Codebase

Greeting with name:

{
  "patientDashboard": {
    "greeting": "Hello {{name}}!",
    "greeting_fallback": "Hello Patient!"
  }
}

Count display:

{
  "profile": {
    "preferences": {
      "methodsSelected": "methods selected"
    }
  }
}

Follow-up round number:

{
  "followUp": {
    "followUpRound": "Follow-up Round "
  }
}

Using Variables in Components

Import the translation hook from the I18nContext and use the t() function:

import { useTranslation } from "../context/I18nContext";

function PatientDashboard({ user }) {
  const { t } = useTranslation();

  return (
    <Text>{t("patientDashboard.greeting", { name: user.firstName })}</Text>
  );
}

For translations without variables:

<Button title={t("common.start")} />

With fallback handling:

const displayName = user?.firstName || t("patientDashboard.greeting_fallback");

Technical Details

The t() function in mobile: 1. Splits the key by . to navigate nested objects 2. Looks up the value in the current locale 3. Falls back to English if key not found in current locale 4. Returns the key itself if not found anywhere (with console warning) 5. Replaces {{variable}} patterns with provided params

// Internal implementation
t(key: string, params?: Record<string, any>): string {
  // ... key lookup logic ...

  // Variable interpolation
  if (params) {
    return value.replace(
      /\{\{(\w+)\}\}/g,
      (match, paramKey) => params[paramKey]?.toString() || match
    );
  }
  return value;
}

Language Detection & Switching

Automatic Detection

The mobile app automatically detects the device language on startup:

  1. Checks for a saved user preference in AsyncStorage
  2. Falls back to device locale detection via Expo Localization
  3. Defaults to English if the detected language is not supported

Manual Language Switching

Users can change language via the LanguageSwitcher component:

import { useTranslation } from "../context/I18nContext";

function LanguageSwitcher() {
  const { currentLocale, setLanguage, availableLocales } = useTranslation();

  const handleLanguageChange = async (locale: string) => {
    await setLanguage(locale);
  };

  // ... render UI
}

Differences from Web

Aspect Web (OktaPT-FE) Mobile (Okta-Mobile)
Library next-i18next Custom I18nManager
File location public/locales/{lang}/common.json locales/{lang}.json
Namespace Uses common namespace No namespace (flat structure)
Import import { useTranslation } from "next-i18next" import { useTranslation } from "../context/I18nContext"
Hook usage const { t } = useTranslation("common") const { t } = useTranslation()
Validation npm run i18n:check Manual verification

Best Practices

  1. Always add translations to both files simultaneously - never commit with only one locale updated

  2. Keep English as the source of truth - add new keys to English first, then translate

  3. Preserve key order - maintain consistent ordering between locale files for easier comparison

  4. Use descriptive variable names - choose names that clearly indicate what value will be injected:

    "greeting": "Hello {{userName}}!"  // Good
    "greeting": "Hello {{u}}!"         // Bad
    

  5. Handle missing values - always provide fallback values:

    t("greeting", { name: user?.name || "Guest" })
    

  6. Provide fallback translations - use _fallback suffix for dynamic content:

    {
      "greeting": "Hello {{name}}!",
      "greeting_fallback": "Hello Patient!"
    }
    

  7. Don't hardcode text in components - always use the t() function for user-facing strings

  8. Test both languages - verify translations look correct in both English and Russian

  9. Keep translations consistent across platforms - use similar keys and structures as the web app where possible to maintain parity