Multi-country configuration
Archibald can serve several countries from a single build. Country-specific
overrides live under a top-level countries map, keyed by country code, and are
deep-merged on top of the base configuration when a country is active. Arrays are
replaced, not concatenated, so a country can fully redefine a list.
// environment/common.ts
{
app: { features: ['wishlist', 'store-finder'] },
countries: {
AT: { app: { features: ['wishlist'] } }, // replaces the array
DE: { app: { baseSite: 'shop-de' } }
}
}
Multi-country resolution only runs when the multiCountry flag is enabled in the
Archibald config. With the flag off, the base configuration is returned as-is.
On the native (React Native) client
On native, configuration is served by NativeConfig. Two hooks drive country
resolution:
calculateCountry()— override it to return the active country code (the shop template reads it from local storage). It is consulted on every configuration read, so keep it cheap (an in-memory lookup).- Switching country — replace the whole config instance rather than mutating the
active one. The shop template does this via
setCountryNativeConfig, which persists the new country and callssetConfig(new ProjectNativeConfig()).
export class ProjectNativeConfig extends NativeConfig<ProjectConfig> {
override calculateCountry() {
return nativeStorage.get('country');
}
}
export async function setCountryNativeConfig(country: Countries | null) {
await nativeStorage.set('country', country);
setConfig(new ProjectNativeConfig()); // swap the instance, don't mutate
}
Performance: the merged config is memoized
Every configuration read (get, getAll, isSet, …) resolves the active
country configuration. Because the merged result is invariant for the lifetime of a
NativeConfig instance — the raw config is set once and switching country replaces
the instance — NativeConfig memoizes the country-merged config instead of
deep-merging on every read.
The cache is keyed by the resolved country, so:
- Reading configuration many times per render performs the merge once, not once per read. (This previously showed up as hundreds of deep merges per render and noticeable jank on Android.)
- A change in the value returned by
calculateCountry()recomputes the merge once. This also covers the case where the country is loaded asynchronously: early reads resolve against the not-yet-loaded value and the config is recomputed once the country becomes available. set()andreplace()mutate the raw config and therefore clear the cache.
While the cache is warm, the object returned by config / getAll() is a stable
reference rather than a fresh copy on each read. Treat configuration as read-only and
route changes through set() / replace(); do not mutate the returned object in
place.