Running a Velocity X site in 3+ languages used to mean either duplicating your entire brand.json per locale (a nightmare when you add a new service), or using a CMS with built-in translation workflows (slow, vendor lock-in). Velocity X flips this: a single base brand.json, locale-specific overrides in brand.en.json + brand.fr.json + brand.es.json, and a merge function at build time that stitches them together. One source of truth. N language voices.
This post walks the pattern: why merge beats duplication, the file structure, the merge loader, how AI voice rules inherit per locale, and 6 questions to decide if you need it now.
Why Merge Beats Full Duplication
Duplication is a trap. If you maintain three separate brand.json files and add a new service category, you update base English, then remember to update Spanish, then remember French. You miss one. Now three versions are out of sync. Merge-at-build-time keeps your schema in one place — changes propagate automatically.
A merged approach scales: add a 4th locale by dropping a single brand.fr-ca.json override file. Your build merges it. Your routes render it. No code changes, no duplication ceremony.
File Structure — Locale Overrides
Start with a base brand.json at the root:
// data/brand.json (base, English defaults)
{
"name": "Your Brand",
"tagline": "English tagline here",
"services": [
{ "id": "web-design", "title": "Web Design", "description": "Build beautiful sites" },
{ "id": "branding", "title": "Branding", "description": "Strong visual identity" }
],
"ctas": {
"primary": "Get Started",
"secondary": "Learn More"
}
}
Now, create locale overrides in subdirectories. Only include fields you want to change:
// data/brands/es/brand.json (Spanish overrides)
{
"tagline": "Tagline en español",
"services": [
{ "id": "web-design", "title": "Diseño Web", "description": "Crea sitios hermosos" },
{ "id": "branding", "title": "Marca", "description": "Identidad visual fuerte" }
],
"ctas": {
"primary": "Comenzar"
}
}
Notice: you don't repeat name (reuses English). You only override what differs. A shallow merge handles nested objects — services array replaces the base entirely (you control that depth), CTAs merge at the property level.
The Merge Loader — Build-Time Magic
// src/lib/locale-brand.ts
import baseBrand from '../data/brand.json';
export async function getBrand(locale: string) {
try {
const localeOverride = await import(
`../data/brands/${locale}/brand.json`
);
// Deep merge: locale overrides inherit from base
return deepMerge(baseBrand, localeOverride.default);
} catch (e) {
// No override for this locale — return base
return baseBrand;
}
}
function deepMerge(base: any, override: any) {
const result = { ...base };
for (const key in override) {
if (typeof override[key] === 'object' && !Array.isArray(override[key])) {
result[key] = deepMerge(base[key] || {}, override[key]);
} else {
result[key] = override[key];
}
}
return result;
}
Drop this in your Astro component and call it per route. A Spanish page calls getBrand('es'), French calls getBrand('fr'), fallback calls getBrand('en'). One function, zero duplication.
AI Voice Rules Per Locale
Velocity X's system prompt — the one Claude uses to generate category descriptions and copy variants — lives in brand.json under aiVoiceRules. Locale overrides change that voice:
// data/brands/es/brand.json
{
"aiVoiceRules": "Tono amable, sin jerga. Español de España. Énfasis en confianza. Frases cortas."
}
// data/brands/fr/brand.json
{
"aiVoiceRules": "Ton professionnel, précis. Français moderne. Accent sur la valeur mesurable."
}
When you ask Claude to generate a new service description, it reads brand.aiVoiceRules for that locale and adapts. Spanish gets friendly, familiar phrasing. French gets measured precision. Same AI, different voice rules.
6 Questions: Do You Need Locale Overrides Now?
1. Are you shipping 2+ languages? If yes, merge beats duplication from day one.
2. Does each locale have a different voice? Spanish → casual, French → formal? Locale overrides handle it in aiVoiceRules. Same AI, different instructions.
3. Will you add new services after launch? If yes, merge saves you 3 update cycles (base + es + fr) instead of 1. ROI is clear.
4. Are your locales close enough to share structure? If your Spanish site and French site have the exact same category order, merge works perfectly. If each locale has unique categories, you're back to duplication.
5. Is your team small enough to handle translation reviews? If you have 1 translator or a native speaker, merges keep them focused. If you're outsourcing to 3 separate agencies, merge doesn't help with coordination.
6. Do you version-control your brand? Merges work best in Git. If you're manually editing JSON files, the complexity isn't worth it yet.
If you hit 4+ yeses, implement it. Otherwise, stick with a single-language base for now and refactor when revenue justifies it.
The Catch: Deep Merge Complexity
Arrays are tricky. If you want to override just one service in a 10-service list, shallow merge replaces the entire array. You'd need to duplicate the other 9. Deep merge on arrays is rare — usually you replace the whole thing and accept that cost. If you need surgical array updates per locale, you're back to manual duplication or a CMS.
The Verdict
For service agencies expanding to 2–4 markets with a shared structure: locale-aware brand.json is unbeatable. Routes are automatic (Astro i18n), voice rules adapt per market, and your team doesn't juggle 3 separate schemas. See how we build multi-language service sites — this is the pattern. One brand definition. Many voices. See our full i18n post for routing strategy.
Ship once. Scale N languages.