From storefront-next
Implements i18n in Storefront Next using i18next with useTranslation for components and getTranslation for server code. Covers namespaces, pluralization, interpolation, and locale configuration.
How this skill is triggered — by the user, by Claude, or both
Slash command
/storefront-next:sfnext-i18nThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill covers internationalization in Storefront Next using i18next with a dual-instance architecture (server + client).
This skill covers internationalization in Storefront Next using i18next with a dual-instance architecture (server + client).
useTranslation() for components, getTranslation() for non-component codeTranslations are organized as namespace files per locale, compiled into a TypeScript index:
src/locales/en-US/
├── index.ts # Merges all namespace files + extensions
├── translations.json # Default namespace (top-level keys become namespaces)
└── product.json # "product" namespace (separate file)
src/locales/en-GB/
├── index.ts
├── translations.json
└── product.json
The index.ts imports all namespace files and extension translations:
import translations from '@/locales/en-US/translations.json';
import product from '@/locales/en-US/product.json';
import extensionTranslations from '@/extensions/locales/en-US/';
const allTranslations = { ...translations, product, ...extensionTranslations };
export default allTranslations satisfies ResourceLanguage;
translations.jsonEach top-level key is a namespace:
{
"header": {
"search": "Search",
"account": "Account"
},
"footer": {
"copyright": "© {{year}} Company"
}
}
product.json){
"title": "Product Details",
"addToCart": "Add to Cart",
"greeting": "Hello, {{name}}!",
"itemCount_one": "{{count}} item",
"itemCount_other": "{{count}} items"
}
import { useTranslation } from 'react-i18next';
export function ProductCard() {
const { t } = useTranslation('product');
return (
<div>
<h1>{t('title')}</h1>
<button>{t('addToCart')}</button>
<p>{t('greeting', { name: 'John' })}</p>
<p>{t('itemCount', { count: 5 })}</p>
</div>
);
}
Critical: Always pass a namespace to useTranslation():
// WRONG — missing namespace
const { t } = useTranslation();
t('title'); // Will not find the key
// CORRECT — with namespace
const { t } = useTranslation('product');
t('title'); // Works
import { getTranslation } from '@/lib/i18next';
// In loaders/actions (pass context)
export function loader(args: LoaderFunctionArgs) {
const { t } = getTranslation(args.context);
return { title: t('product:title') };
}
// Client-side utilities (no context)
const { t } = getTranslation();
const message = t('product:addToCart');
Critical: Use a factory function for Zod schemas with translated messages to avoid race conditions:
// WRONG — Module-level schema (race condition: t() may not be initialized)
export const schema = z.object({
email: z.string().email(t('validation:emailInvalid'))
});
// CORRECT — Factory function
import type { TFunction } from 'i18next';
export const createSchema = (t: TFunction) => {
return z.object({
email: z.string().email(t('validation:emailInvalid'))
});
};
// Usage in component
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
function MyForm() {
const { t } = useTranslation();
const schema = useMemo(() => createSchema(t), [t]);
const form = useForm({ resolver: zodResolver(schema) });
}
import LocaleSwitcher from '@/components/locale-switcher';
export function Footer() {
return <footer><LocaleSwitcher /></footer>;
}
Extensions use extPascalCase namespace auto-derived from the extension directory name:
src/extensions/my-extension/locales/
├── en-US/translations.json
└── it-IT/translations.json
const { t } = useTranslation('extMyExtension');
t('welcome');
| Pitfall | Problem | Solution |
|---|---|---|
| Missing namespace | Keys not found | Always pass namespace: useTranslation('product') |
Module-level t() in schemas | Race condition on initialization | Use factory pattern: createSchema(t) |
| Forgetting context on server | Translations not found | Use getTranslation(args.context) in loaders |
| Duplicate keys across namespaces | Wrong translation shown | Prefix with namespace: t('product:title') |
storefront-next:sfnext-components - Using translations in UI componentsstorefront-next:sfnext-extensions - Extension translation namespacingstorefront-next:sfnext-configuration - Locale and site configurationnpx claudepluginhub salesforcecommercecloud/b2c-developer-tooling --plugin storefront-nextAdd internationalization (i18n) to an existing T3/Next.js project using next-intl with sub-path routing. Uses `localePrefix: "as-needed"` so the default locale keeps its URLs unprefixed (preserving existing SEO and backlinks), and only non-default locales get a prefix.
Implements Next.js 16 internationalization with next-intl or a DIY dictionary approach. Covers locale routing, translations, formatting, SEO, and RTL support.
Build Storefront Next extensions using target-config.json, target points, routes, and translation namespaces. Includes the base-audit gate for deciding when to extend vs override.