Adding a Country
Countries are static catalog entries in src/registry/countries/. They drive currency, distance defaults, tax-installment hints, and the tax profile consumed by onboarding, the tax module, notifications, and Intl formatting (formatCurrency when a 2-letter country code is passed).
For registry background, see the Registry Architecture. After a country exists, you can attach provinces whose countryId matches — see Adding a Province.
Checklist
Section titled “Checklist”- Copy
src/registry/countries/_TEMPLATE.country.jstosrc/registry/countries/{ISO}.country.js(e.g.AU.country.js). PreferCA.country.jsorUS.country.jsas a real-world shape: the template may lag minor optional keys. - Set
idto a two-letter uppercase ISO market code (CA,US,AU). This must matchuser.countryId/user.locale.countrywherever you persist that market. - Set
labelKeyand add the same path understrings.enandstrings.frinsrc/utils/strings.js(e.g.onboarding.steps.countryUS— follow existing country keys). - Fill required top-level fields validated by
validateCountryDefinition:currency,symbol,distanceUnit('km'|'mi'), plusid. - Provide
taxInstallmentDates(array of{ month, day, label }, optionalfollowYear) forgetNextTaxDeadline/ tax notifications — use[]only if truly N/A. - Provide a full
taxobject (see below). Code paths such asgetCountryTaxProfileassumedef.taxexists. - Optional top-level flags merged into locale-style config via
countryDefToLocaleConfig: e.g.hasCPP,hasHST,hasSETax(seeCA.country.js). Omit legacy keys you do not use (the template still mentionsmileageRateSource; current CA omits it — align with how existing countries are written). - Register in
src/registry/countries/index.js:import+ add toCOUNTRIES. - If this country has subdivisions in the app, add matching province defs with the same
countryId. - Optional
defaultAvailablePlatforms: array ofPlatformRegistryids used when no province row applies yet (see Market Resolution). Stripped from locale config viacountryDefToLocaleConfigso it never leaks intoIntl/ currency helpers. - Run
node build.js --prodand confirm boot passesassertCountryRegistryValid()(main.js).
tax profile (required in practice)
Section titled “tax profile (required in practice)”getCountryTaxProfile(countryCode) returns CountryRegistry.getById(code).tax. Mirror a peer country and adjust:
| Field | Purpose |
|---|---|
intlLocaleTag | BCP-47 tag for Intl.NumberFormat when formatting with a country code (formatters.js). |
hstOnboarding | Whether GST/HST-style onboarding / expense HST UI applies (true for Canada). |
regionPresetType | Drives preset behaviour in onboarding / tax copy ('CA', 'US', or null). |
| Withholding preset tables | CA/US per-region set-aside % hints live in src/registry/tax/withholding-presets.js — do not duplicate maps in country files or modules. |
regionLabel | 'province' vs 'state' (wording in onboarding). |
defaultWithholdingPct | Suggested set-aside percentage. |
fallbackCurrency | Same idea as top-level currency for tax-specific fallbacks. |
hstRateWhenRegistered | Used where HST-on-goods logic applies (0 when N/A). |
calcCpp / calcSeTax | Feature flags for estimator paths. |
defaultRegionCode | Default subdivision code when you need a seed (e.g. 'ON' for CA). |
footnote / secondaryEstimator | Strings consumed by tax / copy layers — copy naming from CA/US/UK until those modules are generalized. |
v3 note: Self-employed Canada is actual vehicle costs, not CRA standard mileage, in product copy and tax summaries. New CA-like defs should stay consistent with CA.country.js (no revived stdMileageChoice for Ontario-first flows unless you intentionally support another product line).
Fallback behaviour
Section titled “Fallback behaviour”CountryRegistry.getById(unknown) returns FALLBACK_ID (CA) when the code is missing. Changing FALLBACK_ID in index.js affects every unresolved country code system-wide.
Wiring outside the registry
Section titled “Wiring outside the registry”| Area | Notes |
|---|---|
| User / DB defaults | DEFAULT_USER / migrations — set countryId and locale.country / currency consistently when this country should be a default. |
countryDef in store | store resolves countryDef from user.countryId (and related locale fields). No extra hook is needed if only the catalog row was added. |
| Onboarding | Uses resolveAvailablePlatformIds and shared withholding presets — see Market Resolution. |
| Provinces | ProvinceRegistry.getByCountry('XX') is empty until you add at least one province with countryId: 'XX'. |
Minimal skeleton (abbreviated)
Section titled “Minimal skeleton (abbreviated)”export default { id: 'AU', labelKey: 'onboarding.steps.countryAU', currency: 'AUD', symbol: '$', distanceUnit: 'km', taxInstallmentDates: [ /* PAYG-ish due dates — research real schedule */ ], tax: { taxInstallmentReminderDays: 10, hstOnboarding: false, intlLocaleTag: 'en-AU', defaultWithholdingPct: 25, regionPresetType: null, fallbackCurrency: 'AUD', hstRateWhenRegistered: 0, calcCpp: false, calcSeTax: false, regionLabel: 'state', secondaryEstimator: 'none', footnote: 'generic', defaultRegionCode: '', },};Register:
import AU from './AU.country.js';const COUNTRIES = [CA, US, UK, AU];- Temporarily set
user.locale.country/countryIdto the new code and reload: currency symbol and distance unit should match the def. - Open Tax and onboarding flows: no thrown errors from
getCountryTaxProfile. - Add at least one province (or document “no subdivisions yet”) before shipping a country that expects region-scoped tax UX.
Related files
Section titled “Related files”| File | Role |
|---|---|
src/registry/countries/_TEMPLATE.country.js | Starter file (verify against CA/US). |
src/registry/countries/CA.country.js | Reference for Canada / HST / CPP flags. |
src/registry/countries/US.country.js | Reference for regionPresetType: 'US', quarterly labels. |
src/registry/countries/index.js | Registry, getCountryTaxProfile, countryDefToLocaleConfig, fallback. |
src/utils/locale.js | getCountryDef, tax deadline helpers from installment arrays. |
See also
Section titled “See also”- Adding a Province — subdivisions;
countryIdmust match this country’sid. - Adding a Platform — platforms listed on province defs, not usually on the country file itself.