Internationalization (i18n)¶
Vectis supports multi-language content at both the backend (product data) and frontend (UI strings). English is the base language; translations are additive overlays.
Backend: Translatable Content¶
Products, categories, brands, and CMS pages store translations in a translations JSONB column:
product.name = "Disposable Vaporizer"
product.translations = {
"es": {"name": "Vaporizador Desechable", "description": "..."}
}
Translatable fields per entity (vectis/core/i18n.py): Product has name, description, seo_title, seo_description; Category/Brand have name, description; Page has title, blocks, seo_title, seo_description.
GraphQL resolvers call resolve_field() to merge base values with locale overrides:
from vectis.core.i18n import resolve_field
def resolve_product_name(product, info):
locale = info.context.get("locale", "en")
return resolve_field(product, "name", locale, product.translations)
The backend resolves locale from: X-Locale header → Accept-Language → "en".
Storefront: UI Strings¶
JSON message files live at $lib/i18n/{en,es}.json. Import t() with dot-notation keys:
<script lang="ts">
import { t } from '$lib/i18n';
</script>
<button>{t('product.add_to_cart')}</button>
<!-- English: "Add to Cart" | Spanish: "Agregar al Carrito" -->
<p>{t('cart.item_count', { count: 3 })}</p>
<!-- Parameter substitution via {placeholder} syntax -->
Missing keys fall back to English, then to the key string itself.
Setting the Locale¶
The root +layout.svelte calls setLocale() with server data:
<script lang="ts">
import { setLocale } from '$lib/i18n';
let { data, children } = $props();
setLocale(data.locale);
</script>
LanguageSwitcher¶
The LanguageSwitcher component sets the vectis_locale cookie and reloads:
<script lang="ts">
import { SUPPORTED_LOCALES, LOCALE_LABELS } from '$lib/i18n';
import { Globe } from '@lucide/svelte';
function switchLocale(locale: string) {
document.cookie = `vectis_locale=${locale};path=/;max-age=${365 * 86400}`;
location.reload();
}
</script>
{#each SUPPORTED_LOCALES as loc}
<button onclick={() => switchLocale(loc)}>{LOCALE_LABELS[loc]}</button>
{/each}
Admin: Help Content¶
The admin uses a parallel JSON structure in $lib/help/{en,es}.json for in-app contextual help. Both files must be kept in sync.
Adding a New Language¶
- Backend — add locale to
channel.supported_languages: - Product translations — add via admin or API mutation
- Storefront — create
$lib/i18n/fr.json, import inindex.ts, add tomessagesmap - Admin help — create
$lib/help/fr.jsonwith the same structure - Constants — add
'fr'toSUPPORTED_LOCALESin$lib/i18n/index.tsandhooks.server.ts
Note
hooks.server.ts only recognizes locales in its SUPPORTED_LOCALES array. Missing locales fall back to English.
Tip
Run grep -r "t('" src/ to find all i18n keys in use, then verify they exist in every locale JSON file.