Issue #225: Replace self-written address formatter with existing library¶
Status: ✅ Completed Labels: F: Invoicing, Refactor Milestone: MVP Launch
Overview¶
Replaced the custom address formatting implementation with @fragaria/address-formatter library, which provides international address formatting based on OpenCage Data's comprehensive address formatting specifications.
Problem Statement¶
The project had a custom address formatting implementation (AddressFormatters.ts) that only supported two formats:
- USA format (house number first)
- European format (street name first)
This limited approach didn't account for the diverse address formatting conventions across different countries worldwide.
Solution¶
Integrated @fragaria/address-formatter (v6.7.1), a JavaScript implementation of OpenCage Data's address formatting specifications that supports almost all international address formats.
Changes Made¶
1. Package Installation¶
pnpm add @fragaria/address-formatter
Added to schemas/package.json as a dependency.
2. Code Refactoring¶
File: schemas/src/AddressFormatters.ts
Before:
- Custom formatAddressUSA() function for USA addresses
- Custom formatAddressEuropean() function for European addresses
- Manual country detection and format selection
- ~196 lines of code
After:
- Single toAddressFormatterInput() helper to convert PersistedAddress to library format
- Uses library's format() function with international templates
- Automatic country detection based on ISO country codes
- ~141 lines of code
- Country code normalization (USA → US) for proper formatting
Key Implementation Details:
// Convert PersistedAddress format to library's expected format
function toAddressFormatterInput(
address: PersistedAddress,
countryNameResolver?: (code: string) => string
): Record<string, string | number> {
// Map our field names to library's expected names
// street → road, postal_code → postcode, house_number → houseNumber
const input = {
road: address.street,
city: address.city,
postcode: address.postal_code,
country: countryName,
countryCode: normalizedCountryCode,
// ... conditionally add houseNumber and state
};
return input;
}
export function formatPersistedAddress(
address: PersistedAddress | null | undefined,
countryNameResolver?: (code: string) => string
): string | undefined {
const input = toAddressFormatterInput(address, countryNameResolver);
let formatted = addressFormatter.format(input, {
appendCountry: true,
});
// Normalize output
formatted = formatted.trimEnd(); // Remove trailing newline
formatted = formatted.replace("United States of America", "United States");
// Add note if present
if (address.note) {
formatted += `\n${address.note}`;
}
return formatted;
}
3. Test Updates¶
File: schemas/src/AddressFormatters.test.ts
Updated test expectations to match the library's international formatting standards:
- Austrian addresses: Library doesn't include state on a separate line (per international standards)
- USA without state: Library includes comma between city and postal code
- Trailing newlines: Removed from expected values (now trimmed)
All 26 tests passing ✅
Benefits¶
- International Coverage: Supports address formats for almost all countries worldwide
- Standards-Based: Uses OpenCage Data's well-researched formatting rules
- Maintainability: Formatting rules maintained by the library, not custom code
- Reduced Code: Simplified from ~196 to ~141 lines
- Better Accuracy: Handles edge cases and regional variations automatically
Library Features Used¶
- Automatic format detection based on country code
- Multi-line formatting for invoices and documents
- Single-line formatting for compact displays
- Country name resolution for internationalization
- Optional field handling (house number, state, notes)
Backwards Compatibility¶
The refactoring maintains the same public API:
formatPersistedAddress(address, countryNameResolver?) → string | undefined
formatPersistedAddressSingleLine(address, countryNameResolver?) → string | undefined
Output format changes are minimal and align with international standards: - Austrian addresses no longer show state on separate line (matches international format) - USA addresses without state include comma before postal code (standard USA format)
Testing¶
Unit Tests¶
All existing tests updated and passing:
pnpm --filter @meisterbill/schemas test AddressFormatters
Results: 26 tests, all passing ✅
Test Coverage¶
- ✅ German addresses
- ✅ Swiss addresses
- ✅ Austrian addresses (with state)
- ✅ USA addresses (various state configurations)
- ✅ Addresses without house numbers
- ✅ Addresses with notes
- ✅ Single-line formatting
- ✅ Edge cases (null, undefined, empty country)
Example Outputs¶
German Address:
Waldweg 22
81627 München
Germany
USA Address:
100 Main Street
New York, NY 10001
United States
Swiss Address (Single Line):
Hauptstrasse 123, 8000 Zürich, Switzerland
Technical Notes¶
Country Code Handling¶
The library requires standard ISO country codes. We normalize non-standard codes:
if (countryCode === "USA" || countryCode === "UNITED STATES") {
countryCode = "US";
}
Output Normalization¶
Two post-processing steps ensure consistency:
- Trim trailing newline: Library adds
\nat end, we trim it - Normalize country name: "United States of America" → "United States"
Field Mapping¶
Our PersistedAddress schema maps to library format:
| Our Field | Library Field |
|---|---|
| street | road |
| house_number | houseNumber |
| postal_code | postcode |
| city | city |
| state | state |
| country | country |
| - | countryCode |
Files Modified¶
- ✅
schemas/package.json- Added dependency - ✅
schemas/src/AddressFormatters.ts- Replaced implementation - ✅
schemas/src/AddressFormatters.test.ts- Updated test expectations
Dependencies Added¶
@fragaria/address-formatterv6.7.1- Based on OpenCage Data's address formatting templates
- Supports international address formats
- ~62 transitive dependencies (includes formatting templates)
Related Issues¶
- Original issue: #225
- Related to invoicing feature (F: Invoicing label)
References¶
Regression Testing Checklist¶
When testing this change:
- [ ] Verify invoice PDF generation shows correct addresses
- [ ] Test addresses from different countries (US, DE, CH, AT, etc.)
- [ ] Check customer/service provider address display
- [ ] Validate email templates with addresses
- [ ] Test addresses with and without house numbers
- [ ] Verify addresses with additional notes
- [ ] Check single-line address display in compact views
Future Enhancements¶
The library supports additional features we could leverage:
-
Abbreviation mode: Shorten "Avenue" to "Ave", etc.
typescript addressFormatter.format(input, { abbreviate: true }) -
Custom country override: Force specific formatting
typescript addressFormatter.format(input, { countryCode: 'UK' }) -
Fallback country: Handle invalid country codes
typescript addressFormatter.format(input, { fallbackCountryCode: 'US' })
Migration Impact¶
Risk Level: Low
- Changes are in shared
@meisterbill/schemaspackage - All consuming apps (web, api, app) import through the same interface
- Test coverage ensures backwards compatibility
- Format differences are minor and standards-compliant
Deployment Notes:
- Rebuild @meisterbill/schemas package first
- No database migrations required
- No API changes required
- Works with existing address data