i18n Guide
The i18n system bridges host-provided translations (Moodle core/str, WordPress wp.i18n) with a unified React API. Strings can be pre-loaded for performance or fetched on demand.
Architecture
Host provides:
├── Pre-loaded strings via Inertia shared props (theme.strings)
└── Async resolver for missing keys (host-specific)
I18nProvider merges both:
├── Server strings (sync, fast)
└── Async strings (on-demand, cached in state)
useTranslation() → { t, tAsync }The t(key) function returns the translation synchronously -- if the key is not pre-loaded, it returns the key itself as a fallback. The tAsync(key, component?) function uses the injected resolver to fetch missing keys on demand and caches them for subsequent calls.
Provider Setup
Wrap your app with I18nProvider and optionally pass an async resolver for on-demand string fetching:
// App.tsx
import { I18nProvider } from '@middag-io/react';
<I18nProvider asyncResolver={myResolver}>
<App />
</I18nProvider>No resolver needed for pre-loaded strings
If all your strings are pre-loaded via Inertia shared props, the async resolver is optional. The provider works with sync-only strings out of the box.
Host Resolvers
Each host platform provides its own async resolver:
// Moodle resolver
const moodleResolver = async (key, component) => {
const { get_string } = await import('core/str');
return get_string(key, component || 'local_myplugin');
};// WordPress resolver
const wpResolver = async (key) => {
return wp.i18n.__(key, 'my-plugin');
};Resolver signature
The resolver receives (key: string, component?: string) and returns Promise<string>. The component parameter is Moodle-specific (the Frankenstyle plugin name).
Pre-loading Strings
For best performance, pre-load frequently used strings from the backend via Inertia shared props. This avoids async lookups at render time:
// PHP (Moodle)
// In Moodle shared props:
$strings = [
'dashboard_title' => get_string('dashboard_title', 'local_myplugin'),
'save' => get_string('save', 'core'),
'cancel' => get_string('cancel', 'core'),
];
return Inertia::share('theme', ['strings' => $strings]);// PHP (WordPress)
$strings = [
'dashboard_title' => __('Dashboard', 'my-plugin'),
'save' => __('Save', 'my-plugin'),
'cancel' => __('Cancel', 'my-plugin'),
];
return Inertia::share('theme', ['strings' => $strings]);Mock i18n (Development)
The mock build uses a standalone i18n system that does not depend on Inertia or a host application:
- MockI18nProvider -- standalone provider with flat key lookups, no Inertia dependency.
- Flat dot-prefix keys --
t('dashboard.title')looks updashboard.titlein the locale map. - Factory pattern -- page data factories accept
tas a parameter:createDashboardPageContract(t). - Locale files -- organized in
mock/i18n/locales/{en,pt-BR}/with per-domain modules.
Adding Translations
Step-by-step guide to adding new translations in the mock build:
- Create or edit a locale file -- Add keys to
mock/i18n/locales/en/your-domain.ts:
// mock/i18n/locales/en/your-domain.ts
export const yourDomain: Record<string, string> = {
'your_domain.title': 'My Title',
'your_domain.description': 'A helpful description.',
};- Update the barrel export -- Add your domain to
mock/i18n/locales/en/index.ts:
// mock/i18n/locales/en/index.ts
import { yourDomain } from './your-domain';
export const en: Record<string, string> = {
...yourDomain,
// ... existing domains
};Repeat for other locales -- Add the same keys with translated values in
mock/i18n/locales/pt-BR/your-domain.ts.Use in factories -- Reference keys via
t():
const title = t('your_domain.title');