Language Specialist
Expert assistant for all i18n/l10n work in the Raamattu Nyt project.
Architecture Overview
packages/shared-i18n/ # Shared i18n package
├── src/
│ ├── config.ts # i18next initialization
│ ├── types.ts # SupportedLanguage, LANGUAGE_INFO
│ ├── provider.tsx # I18nProvider component
│ ├── hooks/useLanguage.ts # Language preference hook
│ └── utils/
│ ├── setLanguage.ts # Simple language switcher
│ └── dateLocale.ts # date-fns locale helper
apps/raamattu-nyt/public/locales/ # Translation files (lazy-loaded)
├── fi/
│ ├── common.json # Shared UI strings
│ └── profile.json # Profile page strings
└── en/
├── common.json
└── profile.json
Quick Reference
Add translation to existing namespace
- Add key to both locale files:
// public/locales/fi/common.json
{ "myKey": "Suomeksi" }
// public/locales/en/common.json
{ "myKey": "In English" }
- Use in component:
import { useTranslation } from '@shared-i18n/index';
const { t } = useTranslation('common');
return <span>{t('myKey')}</span>;
Create new namespace
- Add namespace to
packages/shared-i18n/src/config.ts:
ns: ['common', 'profile', 'newNamespace'],
-
Create files:
public/locales/{fi,en}/newNamespace.json -
Use:
const { t } = useTranslation('newNamespace');
Add new language
- Update
packages/shared-i18n/src/types.ts:
export type SupportedLanguage = 'fi' | 'en' | 'sv';
export const SUPPORTED_LANGUAGES = ['fi', 'en', 'sv'] as const;
export const LANGUAGE_INFO = {
fi: { code: 'fi', name: 'Finnish', nativeName: 'Suomi' },
en: { code: 'en', name: 'English', nativeName: 'English' },
sv: { code: 'sv', name: 'Swedish', nativeName: 'Svenska' },
};
- Update DB constraint in new migration:
ALTER TABLE public.profiles
DROP CONSTRAINT profiles_language_preference_check,
ADD CONSTRAINT profiles_language_preference_check
CHECK (language_preference IN ('fi', 'en', 'sv'));
-
Create locale files:
public/locales/sv/*.json -
Update
dateLocale.tswith new locale import
Translation Patterns
Interpolation
{ "greeting": "Hello, {{name}}!" }
t('greeting', { name: 'John' })
Pluralization
{
"item_one": "{{count}} item",
"item_other": "{{count}} items"
}
t('item', { count: 5 })
Nested keys
{ "nav": { "home": "Home", "settings": "Settings" } }
t('nav.home')
Trans component (JSX interpolation)
import { Trans } from '@shared-i18n/index';
<Trans i18nKey="terms" t={t}>
By continuing you agree to our <Link to="/terms">terms</Link>.
</Trans>
Migration Workflow
When migrating a component to i18n:
- Identify all user-visible strings
- Create translation keys (use descriptive, nested structure)
- Add to appropriate namespace (common for shared, feature-specific otherwise)
- Replace inline strings with
t()calls - Test both languages
Example migration:
// Before
<Button>Tallenna</Button>
// After
const { t } = useTranslation('common');
<Button>{t('common.save')}</Button>
Cross-cutting learnings: See .claude/LEARNINGS.md → "i18n/Translations" section for namespace patterns and useTranslation gotchas.
Files Reference
For detailed API and patterns, see references/i18n-api.md.