first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,87 @@
# `cloudflare-ip-geo.ts` — Cloudflare IP-geo detector
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`cloudflareIpGeoDetector(options?)` builds a `LocaleDetector` that
maps the Cloudflare `cf-ipcountry` header (ISO 3166-1 alpha-2) to
a BCP-47 locale. It is the lowest-precedence detector in the
canonical chain — only consulted when no querystring, cookie,
storage, or browser preference signal exists — because country
geolocation is at best a coarse approximation of language
preference (the UK and India both speak English; Switzerland has
four official languages).
Resolution order, per ISO code:
1. `options.countryToLocale[code]` if provided.
2. `${language}-${code}` where `language` is the first entry from
the country record's `languages` array in
`@suaveplan/locale-data`.
3. `null` when neither override nor language data exists.
## Features
- Default header `"cf-ipcountry"`, configurable for non-Cloudflare
edge providers.
- Per-country override map wins over the data-derived default.
- `environment: "server"` — automatic browser skip via the chain
runner.
- Lowercased header values are upper-cased before lookup so
`Headers.get` casing quirks don't break resolution.
- Missing header, empty header, and unknown country codes all
return `null`.
## Usage: Basic Example
```ts
import { cloudflareIpGeoDetector } from "@suaveplan/i18n-detector/detectors/cloudflare-ip-geo";
const detector = cloudflareIpGeoDetector();
const locale = detector.detect?.({
request: {
headers: new Headers({ "cf-ipcountry": "AE" }),
url: new URL("https://example.com/"),
},
supportedLocales: ["en-GB", "ar-AE"],
fallbackLocale: "en-GB",
});
// locale === "ar-AE"
```
## Usage: Advanced Example
```ts
import { cloudflareIpGeoDetector } from "@suaveplan/i18n-detector/detectors/cloudflare-ip-geo";
const detector = cloudflareIpGeoDetector({
headerName: "x-vercel-ip-country",
countryToLocale: {
CH: "de-CH", // Swiss German default — overrides Latin order
BE: "nl-BE",
IN: "en-IN",
},
});
```
## API Reference
- `cloudflareIpGeoDetector(options?): LocaleDetector` —
`environment: "server"`, `name: "cloudflareIpGeo"`. Has only
`detect`; no `detectAsync`, no `cache`.
- `CloudflareIpGeoDetectorOptions.headerName?: string` — default
`"cf-ipcountry"`.
- `CloudflareIpGeoDetectorOptions.countryToLocale?: Readonly<Record<string, string>>`.
## Implementation Notes
- `getAllCountries()` is called once at detector-build time and
the result is captured in closure scope; subsequent `detect`
calls reuse the same record. Each detector instance therefore
has O(1) lookup cost.
- The `${primaryLanguage}-${code}` template is correct for the
vast majority of countries. The override map exists precisely
for the multilingual countries where a single primary language
would mis-serve users.