localized-regions 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,6 +24,7 @@ provinceOptions('ES'); // [{ value: 'ES-MA', label: 'Málaga' }, { value: 'ES-B
24
24
  - **Correct Spanish provinces** - all **50 provinces** with ISO 3166-2 codes and their parent autonomous community. (Popular datasets like `country-state-city` return only ~16 of them for Spain.)
25
25
  - **Stable, invoicing-safe values** - country = ISO 3166-1 code (`ES`), province = ISO 3166-2 code (`ES-MA`). Labels are display-only.
26
26
  - **Full city database** - cities for every country, sourced from `country-state-city` at runtime.
27
+ - **Correct Dutch cities** - the Netherlands is served from the official Dutch place register (BAG woonplaatsen), not `country-state-city`, whose NL list is 19% `Gemeente X` municipality records and missing roughly 1000 real places.
27
28
  - **Framework-agnostic** - no React / MUI / Chakra dependency. Plain data plus ready-to-use `{ value, label }` options.
28
29
  - **TypeScript types included.**
29
30
 
@@ -92,9 +93,12 @@ import { countryOptions } from 'localized-regions';
92
93
  | `getProvinces(countryCode)` | `Province[]` (Spain = curated 50) |
93
94
  | `getProvince(provinceCode)` | `Province` |
94
95
  | `getCities(countryCode, provinceCode?)` | `City[]` |
96
+ | `cityMatches(city, input)` | `boolean` - does this city match typed input, by name or alias |
95
97
  | `countryOptions(locale?)` | `Option[]` (`{ value, label }`) |
96
98
  | `provinceOptions(countryCode)` | `Option[]` |
97
99
  | `cityOptions(countryCode, provinceCode?)` | `Option[]` |
100
+ | `formatAddressLine1(countryCode, street, houseNumber)` | address line 1 in the country's own order |
101
+ | `isHouseNumberFirst(countryCode)` | `boolean` - does this country write the number before the street |
98
102
 
99
103
  ## How the data is built
100
104
 
@@ -102,7 +106,8 @@ Data is generated at build time (`npm run generate`) and committed under `data/`
102
106
 
103
107
  - **Countries** - `Intl.DisplayNames` (English) plus a code list, 250 countries.
104
108
  - **Provinces** - `iso-3166-2` subdivisions. Spain is curated to 50 provinces, with 4 regional-language names aliased to their common form (Nafarroa -> Navarra, Balears -> Balearic Islands, Alacant -> Alicante, Castelló -> Castellón).
105
- - **Cities** - `country-state-city` at runtime.
109
+ - **Cities** - `country-state-city` at runtime, except the Netherlands.
110
+ - **Netherlands cities** - the official BAG woonplaats register, fetched at build time from the PDOK Locatieserver and baked into `data/cities-nl`. No runtime API call and no API key. Generation fails if a `Gemeente X` entry ever reappears.
106
111
 
107
112
  Regenerate after a data-source bump:
108
113
 
@@ -110,6 +115,50 @@ Regenerate after a data-source bump:
110
115
  npm run generate && npm run build
111
116
  ```
112
117
 
118
+ ## Netherlands cities
119
+
120
+ NL city data comes from the official Dutch place register (BAG woonplaatsen, via
121
+ the PDOK Locatieserver) rather than `country-state-city`, whose NL list contains
122
+ 325 `Gemeente X` municipality records and is missing roughly 1000 real places.
123
+
124
+ The Hague is returned as `Den Haag` - the name the municipality, PostNL and
125
+ Taaladvies all use for addresses. Its formal name `'s-Gravenhage` is kept as a
126
+ search alias on `City.aliases`, never as a separate selectable option. Use
127
+ `cityMatches` as your select's `filterOption` so aliases are searchable:
128
+
129
+ ```js
130
+ import { getCities, cityMatches } from 'localized-regions';
131
+
132
+ <Select
133
+ options={getCities('NL', 'NL-ZH')}
134
+ getOptionLabel={(c) => c.name}
135
+ getOptionValue={(c) => c.name}
136
+ filterOption={(option, input) => cityMatches(option.data, input)}
137
+ />
138
+ ```
139
+
140
+ `getProvinces('NL')` returns the 12 real provinces. Aruba, Curacao and Sint
141
+ Maarten are separate constituent countries, and Bonaire, Saba and Sint Eustatius
142
+ are Caribbean special municipalities; ISO 3166-2 lists all six under NL, but they
143
+ are not provinces and have no cities, so they are excluded.
144
+
145
+ ## Address line 1 order
146
+
147
+ Countries disagree on whether the house number comes before or after the
148
+ street. `formatAddressLine1` builds line 1 from the separate street and number
149
+ (for example Google Places `route` + `street_number`) in the country's order:
150
+
151
+ ```js
152
+ import { formatAddressLine1 } from 'localized-regions';
153
+
154
+ formatAddressLine1('NL', 'Boerendanserdijk', '43a'); // "Boerendanserdijk 43a"
155
+ formatAddressLine1('ES', 'Calle Mayor', '12'); // "Calle Mayor 12"
156
+ formatAddressLine1('GB', 'Downing Street', '10'); // "10 Downing Street"
157
+ ```
158
+
159
+ GB, IE, LU, FR and US put the number first; every other country puts the street
160
+ first. A missing street or number is simply left out.
161
+
113
162
  ## Notes and limitations
114
163
 
115
164
  - **Spain city cascade is community-level.** The source city data groups Spanish cities under autonomous communities, not the 50 provinces, so `getCities('ES', 'ES-MA')` returns the parent community's cities (best-effort). City is best treated as a free-text field.
@@ -0,0 +1,8 @@
1
+ /** Does this country write the house number before the street? */
2
+ export declare function isHouseNumberFirst(countryCode: string): boolean;
3
+ /**
4
+ * Address line 1 in the order the country writes it. `houseNumber` keeps any
5
+ * addition as given ("43a"). A missing street or number is left out rather
6
+ * than leaving a stray space, so `formatAddressLine1('NL', 'Dam', '')` is "Dam".
7
+ */
8
+ export declare function formatAddressLine1(countryCode: string, street: string, houseNumber: string): string;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isHouseNumberFirst = isHouseNumberFirst;
4
+ exports.formatAddressLine1 = formatAddressLine1;
5
+ /**
6
+ * Countries whose postal address puts the house number before the street, e.g.
7
+ * "10 Downing Street". Every other country writes the street first, e.g.
8
+ * "Boerendanserdijk 43a" (NL) or "Calle Mayor 12" (ES).
9
+ *
10
+ * ISO 3166-1 alpha-2, as Google Places returns it - the UK is "GB" (RAT-2178).
11
+ */
12
+ const HOUSE_NUMBER_FIRST_COUNTRIES = new Set(['GB', 'IE', 'LU', 'FR', 'US']);
13
+ /** Does this country write the house number before the street? */
14
+ function isHouseNumberFirst(countryCode) {
15
+ return HOUSE_NUMBER_FIRST_COUNTRIES.has((countryCode ?? '').trim().toUpperCase());
16
+ }
17
+ /**
18
+ * Address line 1 in the order the country writes it. `houseNumber` keeps any
19
+ * addition as given ("43a"). A missing street or number is left out rather
20
+ * than leaving a stray space, so `formatAddressLine1('NL', 'Dam', '')` is "Dam".
21
+ */
22
+ function formatAddressLine1(countryCode, street, houseNumber) {
23
+ const parts = isHouseNumberFirst(countryCode) ? [houseNumber, street] : [street, houseNumber];
24
+ return parts
25
+ .map((part) => (part ?? '').trim())
26
+ .filter(Boolean)
27
+ .join(' ');
28
+ }
@@ -0,0 +1,3 @@
1
+ import type { City } from '../types';
2
+ declare const data: Record<string, City[]>;
3
+ export default data;