apps/readest-app/docs/i18n.md
Readest uses a key-as-content approach — English strings are the translation keys. The English locale (en/translation.json) is empty because keys serve as content. Other locales contain actual translations.
import { useTranslation } from '@/hooks/useTranslation';
const _ = useTranslation();
_('Progress synced');
Two-step process:
1. Declaration — Use stubTranslation to mark strings for scanner extraction (returns key as-is, does NOT translate):
import { stubTranslation as _ } from '@/utils/misc';
// These calls only register keys for extraction
_('Reveal in Finder');
_('Reveal in Explorer');
2. Usage — In the React component that consumes the value, apply the real _() from useTranslation:
const _ = useTranslation();
const label = _(getRevealLabel()); // translates at runtime
pnpm i18n:extract # Scans codebase, adds new keys with __STRING_NOT_TRANSLATED__
public/locales/<locale>/translation.json_('KEY') and _('KEY', options) patterns are recognized by i18next-scannerstubTranslation is for extraction only — always apply _() from useTranslation in the component for runtime translation.stubTranslation in utility modules (e.g. src/services/errors.ts), return the English key from helpers, wrap with _() in the component.