Internationalization makes one system capable of serving different languages, scripts, regions, time zones, currencies, address conventions, and legal regimes without forking the core application. Localization supplies the resources and configuration for a particular market. The design boundary matters: a locale can choose how to display 1234.50 CAD, but it must not decide which currency was charged or which tax rule applies.

Separate the Axes

ConcernStore or transmitPresentation or policy decision
Language and scriptBCP 47 language tag such as sr-LatnResource lookup, line breaking, font, and fallback
Messages and pluralsStable message key plus typed argumentsLocale-specific complete message and plural category
Text directionScript and resolved localeRTL layout, mirroring, cursor order, and icon review
TimeInstant; time-zone identifier when local intent mattersCalendar, offset, zone name, and daylight-saving transition
NumbersNumeric valueDigits, grouping, decimal separator, and percent pattern
MoneyDecimal amount plus ISO currency codeCurrency symbol and display pattern; settlement remains a business operation
AddressCountry code plus country-appropriate fieldsField order, labels, validation, and postal formatting
Business rulesExplicit jurisdiction and effective dateTax, accounting, entity, compliance, and product eligibility policy

Do not build messages by concatenating translated fragments. Translators need the complete sentence because word order, agreement, and plural forms vary. English has one and other; other languages select more categories, and the category is not a direct synonym for the number.

The diagram usefully separates localized frontends from core and market-specific services, but “UTC time” is not a sufficient data model. Store an instant for when something happened. Also store an IANA time-zone identifier when the user means a recurring local time such as “09:00 Europe/Kyiv,” because future offsets can change. Keep translation, formatting, foreign exchange, settlement, accounting, and legal-entity rules as different responsibilities.

.NET Boundary

static string FormatMoney(
    Money money,
    CultureInfo displayCulture,
    int minorUnitDigits)
{
    string numericFormat = $"N{minorUnitDigits}";
    return $"{money.Amount.ToString(numericFormat, displayCulture)} {money.Currency}";
}
 
CultureInfo displayCulture = CultureInfo.GetCultureInfo("fr-CA");
Money charged = new(1234.50m, "CAD");
DateTimeOffset paidAt = DateTimeOffset.FromUnixTimeSeconds(1_735_732_800);
TimeZoneInfo customerZone = TimeZoneInfo.FindSystemTimeZoneById("America/Toronto");
 
string displayedAmount = FormatMoney(charged, displayCulture, minorUnitDigits: 2);
DateTimeOffset localPaidAt = TimeZoneInfo.ConvertTime(paidAt, customerZone);
 
public sealed record Money(decimal Amount, string Currency);

displayedAmount is UI text such as 1 234,50 CAD: the culture selects separators and grouping, while Money.Currency supplies the currency that was actually charged. The example passes two minor-unit digits because CAD uses two; production code should obtain that value from maintained ISO 4217 currency metadata keyed by Money.Currency, not from the display culture. The standard "C" format alone uses the culture’s default currency symbol and decimal digits, so it cannot faithfully render an arbitrary ISO currency code. Parsing the formatted string back into settlement data would couple correctness to a locale. DateTimeOffset preserves an instant and offset, while the separate zone supplies daylight-saving rules for conversion. For localized messages, .NET resource lookup follows culture fallback (fr-CAfr → neutral resource), but the application still needs a message-formatting strategy that implements the relevant CLDR plural rules.

Test with structurally different cases, not only translated English: Arabic RTL layout, Serbian script variants, a daylight-saving gap and overlap, currencies with different minor units, long German labels, and an address that lacks the fields your home-country form assumes.

References