Testing
The library is easy to test because it's pure: a provider, a hook, a string out. Below is a small kit we use ourselves.
Render helper​
Wrap RTL's render with a provider so every test starts in the right locale:
import {
render as rtlRender,
type RenderOptions,
} from '@testing-library/react';
import { LocalizationProvider, type Translations } from 'localize-react';
import type { ReactElement, ReactNode } from 'react';
type Options = Omit<RenderOptions, 'wrapper'> & {
locale?: string;
translations?: Translations;
};
export function render(
ui: ReactElement,
{ locale = 'en', translations = {}, ...opts }: Options = {},
) {
const Wrapper = ({ children }: { children: ReactNode }) => (
<LocalizationProvider locale={locale} translations={translations}>
{children}
</LocalizationProvider>
);
return rtlRender(ui, { wrapper: Wrapper, ...opts });
}
Now every test reads cleanly:
import { render } from '../test/render';
import { Cart } from './Cart';
it('renders the cart summary', () => {
const { getByText } = render(<Cart count={3} />, {
locale: 'en',
translations: { en: { cart: { summary: '{{count}} items' } } },
});
expect(getByText('3 items')).toBeInTheDocument();
});
Isolate the module cache​
The translate cache is module-scoped. In Vitest, reset it between tests so a stale entry from a previous test can't leak:
import '@testing-library/jest-dom/vitest';
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
// The library exports clearCache from src/helpers.ts internally; for tests
// against your own components, the cache clears on locale/translations
// change anyway, so usually you just need cleanup() here.
afterEach(() => cleanup());
Descriptor coverage​
Write a single test that proves every descriptor your app uses resolves in every locale. Easy if your descriptors are typed:
import { translations } from '@/i18n';
const ALL_DESCRIPTORS: string[] = JSON.parse(
readFileSync('all-descriptors.json', 'utf8'),
);
describe('translation coverage', () => {
for (const locale of Object.keys(translations)) {
it(`every descriptor resolves in ${locale}`, () => {
const tree = translations[locale];
for (const d of ALL_DESCRIPTORS) {
const resolved = d
.split('.')
.reduce((cur: any, seg) => cur?.[seg], tree);
expect(typeof resolved).toBe('string');
}
});
}
});
The all-descriptors.json list can be generated by a tiny codemod over your codebase (ts-morph, recast, or grep -rE 'translate\\("([^"]+)"').
Snapshot-friendly fallbacks​
Use defaultMessage for components rendered in snapshot tests so they don't bake locale into the snapshot:
<Message descriptor="cta.subscribe" defaultMessage="Subscribe" />
Same component, deterministic snapshot, real translation in prod.