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:
- Supported locales:
en(English),ru(Russian) - Reference locale: English (
en) is the source of truth - Library: Custom
I18nManagerclass using Expo Localization
Adding New Translations¶
Step-by-Step Process¶
- Add the key to the English file first (
Okta-Mobile/locales/en.json) - Add the same key to the Russian file (
Okta-Mobile/locales/ru.json) - Maintain identical key order - keys should be in the same position in both files
- 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:
Examples from the Codebase¶
Greeting with name:
Count display:
Follow-up round number:
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:
With fallback handling:
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:
- Checks for a saved user preference in AsyncStorage
- Falls back to device locale detection via Expo Localization
- 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¶
-
Always add translations to both files simultaneously - never commit with only one locale updated
-
Keep English as the source of truth - add new keys to English first, then translate
-
Preserve key order - maintain consistent ordering between locale files for easier comparison
-
Use descriptive variable names - choose names that clearly indicate what value will be injected:
-
Handle missing values - always provide fallback values:
-
Provide fallback translations - use
_fallbacksuffix for dynamic content: -
Don't hardcode text in components - always use the
t()function for user-facing strings -
Test both languages - verify translations look correct in both English and Russian
-
Keep translations consistent across platforms - use similar keys and structures as the web app where possible to maintain parity