Adding a new locale, language, and market in Nimara
Nimara storefront currently supports two locales: en-US (United States) and en-GB (Great Britain). This guide walks you through adding a new locale, language, and market (for example, Spanish/Spain) to your Nimara storefront.
Architecture Overview
Nimara’s i18n system is organized across two main layers:
@nimara/i18npackage: Shared i18n configuration, routing, and message loading (locales, locale prefixes, message paths)apps/storefront/src/foundation/regions: Storefront-specific market/region configuration (channels, languages, currencies, markets)
This separation allows flexibility: you can have multiple locales mapped to the same market, or create region-specific configurations independent of i18n.
1. Create a new sales channel in Saleor
Go to your Saleor dashboard → Configuration → Channels → click Create Channel.
Enter the channel details:
- Name: e.g., “Spain Channel”
- Slug: e.g., “channel-es” (used in storefront configuration)
- Default country: Spain
- Shipping zones: Assign appropriate zones (e.g., “Europe” for Spain)
After creating the channel, assign the products you want to offer in this channel.
2. Add the locale to the shared i18n config
The i18n package (packages/i18n/src/config.ts) centralizes locale configuration that applies across the storefront.
Edit packages/i18n/src/config.ts:
/**
* List of supported locales.
* Format: BCP 47 / IETF language tag (e.g. "en-US", "es-ES")
* Combines ISO 639-1 language code with ISO 3166-1 region code.
*/
export const LOCALES = ["en-US", "en-GB", "es-ES"] as const;
/**
* Map of locales to their display names.
*/
export const LOCALE_LABELS = {
"en-US": "English (United States)",
"en-GB": "English (United Kingdom)",
"es-ES": "Español (España)",
} as const satisfies Record<SupportedLocale, string>;
/**
* Map of locales to their message files.
* Each locale must have a corresponding JSON file in src/messages/
*/
export const MESSAGES_PATH_MAP = {
"en-US": "@nimara/i18n/messages/en-US.json",
"en-GB": "@nimara/i18n/messages/en-GB.json",
"es-ES": "@nimara/i18n/messages/es-ES.json",
} as const satisfies Record<SupportedLocale, string>;
/**
* Map of non-default locales to their URL prefixes.
* Default locale ("en-US") does not need a prefix.
*/
export const LOCALE_PREFIXES = {
"en-GB": "/gb",
"es-ES": "/es",
} as const satisfies Record<
Exclude<SupportedLocale, typeof DEFAULT_LOCALE>,
`/${string}`
>;3. Add translations for the new locale
Create a new translation file with all required messages.
# Copy an existing locale as a template
cp packages/i18n/src/messages/en-GB.json packages/i18n/src/messages/es-ES.jsonThen translate the messages in packages/i18n/src/messages/es-ES.json to Spanish.
The structure should mirror existing files:
{
"common": {
"locale_name": "Español (España)",
"cart": "Carrito",
"checkout": "Pagar"
},
"errors": {
"not_found": "No encontrado"
}
}4. Configure the market/region in the storefront
Storefront-specific market configuration lives in apps/storefront/src/foundation/regions/config.ts.
This maps the locale to a Saleor channel, defines language metadata, and market-specific details.
Edit apps/storefront/src/foundation/regions/config.ts:
import { LOCALES } from "@nimara/i18n/config";
import { clientEnvs } from "@/envs/client";
export const CHANNEL = clientEnvs.NEXT_PUBLIC_DEFAULT_CHANNEL;
export const LOCALE_CHANNEL_MAP = {
"en-GB": "gb",
"en-US": "us",
"es-ES": "es", // Map the locale to the market ID
} as const;
export const SUPPORTED_CURRENCIES = ["USD", "GBP", "EUR"] as const;
export const SUPPORTED_CHANNELS = [
"default-channel",
"channel-us",
"channel-uk",
"channel-es",
] as const;
export const LANGUAGES = {
GB: {
id: "gb",
name: "English (United Kingdom)",
code: "EN_GB",
locale: "en-GB",
},
US: {
id: "us",
name: "English (United States)",
code: "EN_US",
locale: "en-US",
},
ES: {
id: "es",
name: "Español (España)",
code: "ES_ES",
locale: "es-ES",
},
} as const;
export const MARKETS = {
GB: {
id: "gb",
name: "United Kingdom",
channel: "channel-uk", // Saleor channel slug
currency: "GBP",
continent: "Europe",
countryCode: "GB",
defaultLanguage: LANGUAGES.GB,
supportedLanguages: [LANGUAGES.GB],
},
US: {
id: "us",
name: "United States of America",
channel: "channel-us",
currency: "USD",
continent: "North America",
countryCode: "US",
defaultLanguage: LANGUAGES.US,
supportedLanguages: [LANGUAGES.US],
},
ES: {
id: "es",
name: "España",
channel: "channel-es", // Must match Saleor channel slug
currency: "EUR",
continent: "Europe",
countryCode: "ES",
defaultLanguage: LANGUAGES.ES,
supportedLanguages: [LANGUAGES.ES],
},
} as const;
export const REGIONS_CONFIG = {
channel: CHANNEL,
supportedLocales: LOCALES,
languages: LANGUAGES,
markets: MARKETS,
localeToMarket: LOCALE_CHANNEL_MAP,
supportedCurrencies: SUPPORTED_CURRENCIES,
} as const;5. Environment variables
Ensure your .env.local includes the Saleor channel configuration:
# Your default channel (should match a channel slug in Saleor)
NEXT_PUBLIC_DEFAULT_CHANNEL=channel-esWhen the storefront loads, it uses the current locale’s channel map to fetch products and configurations for that market.
6. Test the new locale
Start the dev server and test the new locale:
pnpm run dev:storefrontVisit:
http://localhost:3000- Default locale (en-US)http://localhost:3000/gb- United Kingdom (en-GB)http://localhost:3000/es- Spain (es-ES)
Verify:
- Correct locale is displayed in headers/footers
- Products are from the correct Saleor channel
- Prices are in the correct currency
- Checkout works with the correct market configuration
Key Concepts
Locale: A BCP 47 language tag combining language and region (e.g., “es-ES” = Spanish in Spain).
Market: Storefront-specific configuration including channel, currency, language metadata, and region details.
Channel: A Saleor sales channel that groups products, pricing, and regional configuration.
Locale Prefix: URL path segment for non-default locales (e.g., /es for “es-ES”).
LOCALE_CHANNEL_MAP: Maps each locale to a storefront market ID for routing and configuration.
Summary
Adding a new locale involves:
- Create channel in Saleor
- Add locale to
@nimara/i18n/config.ts(LOCALES, LOCALE_LABELS, MESSAGES_PATH_MAP, LOCALE_PREFIXES) - Create translation file:
packages/i18n/src/messages/es-ES.json - Map locale to market in storefront:
apps/storefront/src/foundation/regions/config.ts - Test across all locales
This modular approach keeps Nimara scalable and international-ready.