@better-i18n/core 0.3.0 → 0.5.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
@@ -8,7 +8,7 @@ Framework-agnostic core utilities for fetching translations from Better i18n CDN
8
8
  - **Edge-Ready** - Optimized for edge environments (Cloudflare, Vercel, etc.)
9
9
  - **Type-Safe** - Full TypeScript support
10
10
  - **Cached** - Built-in manifest caching with configurable TTL
11
- - **Offline Fallback** - 3-tier fallback chain: CDN → persistent storage → static data
11
+ - **Offline Fallback** - 5-layer fallback chain: TtlCache → CDN (with ETag 304) → persistent storage → static data
12
12
  - **Resilient** - Configurable fetch timeout and retry with exponential backoff
13
13
 
14
14
  ## Installation
@@ -69,12 +69,18 @@ interface I18nCoreConfig {
69
69
 
70
70
  ## Offline Fallback
71
71
 
72
- When CDN is unavailable, translations are resolved through a 3-tier fallback chain:
72
+ Translations are resolved through a 5-layer fallback chain:
73
73
 
74
74
  ```
75
- CDN (primary) → Storage (persistent cache) → Static Data (bundled) → throw
75
+ TtlCache (in-memory) → CDN fetch → CDN 304 (ETag) → Storage (persistent cache) → Static Data (bundled) → throw
76
76
  ```
77
77
 
78
+ 1. **TtlCache** — In-memory cache with configurable TTL (default: 5 minutes). Instant, no I/O.
79
+ 2. **CDN fetch** — Fresh translation data from Better i18n CDN.
80
+ 3. **CDN 304 (ETag)** — If CDN returns "not modified", reuse cached version.
81
+ 4. **Storage** — Persistent cache (localStorage, AsyncStorage, etc.) survives app restarts.
82
+ 5. **Static Data** — Bundled translations as absolute last resort.
83
+
78
84
  ### Storage Adapter
79
85
 
80
86
  Persist translations locally so they survive CDN outages and app restarts:
@@ -1,6 +1,49 @@
1
1
  import type { LocaleDetectionOptions, LocaleDetectionResult } from "./types.js";
2
+ import type { LanguageOption } from "../types.js";
2
3
  /**
3
- * Framework-agnostic locale detection logic
4
+ * Build a country→locale map from CDN manifest languages.
5
+ *
6
+ * Uses each language's `countryCode` field as the primary source
7
+ * (set in the Better i18n dashboard). Falls back to `LANGUAGE_TO_COUNTRY`
8
+ * reverse lookup for languages without explicit country codes.
9
+ *
10
+ * Also includes multi-country overrides (e.g. AT→de, MX→es) so that
11
+ * visitors from countries sharing a language are correctly mapped.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const languages = await i18n.getLanguages();
16
+ * const countryMap = buildCountryLocaleMap(languages);
17
+ * // { tr: "tr", de: "de", jp: "ja", cn: "zh-hans", at: "de", mx: "es", ... }
18
+ * ```
19
+ */
20
+ export declare function buildCountryLocaleMap(languages: LanguageOption[]): Record<string, string>;
21
+ /**
22
+ * Framework-agnostic locale detection with geo-IP support.
23
+ *
24
+ * Detection priority: path > cookie > geo (country) > Accept-Language header > default
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * // In your middleware/loader:
29
+ * const languages = await i18n.getLanguages();
30
+ * const countryMap = buildCountryLocaleMap(languages);
31
+ *
32
+ * const result = detectLocale({
33
+ * project: "acme/web",
34
+ * defaultLocale: "en",
35
+ * availableLocales: languages.map(l => l.code),
36
+ * pathLocale: getLocaleFromURL(pathname),
37
+ * cookieLocale: getCookie("locale"),
38
+ * countryCode: request.cf?.country, // Cloudflare
39
+ * // countryCode: request.geo?.country, // Vercel
40
+ * countryLocaleMap: countryMap,
41
+ * headerLocale: parseAcceptLanguage(request),
42
+ * });
43
+ *
44
+ * // result.locale = "tr"
45
+ * // result.detectedFrom = "geo"
46
+ * ```
4
47
  */
5
48
  export declare function detectLocale(options: LocaleDetectionOptions): LocaleDetectionResult;
6
49
  //# sourceMappingURL=detection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"detection.d.ts","sourceRoot":"","sources":["../../src/i18n/detection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAOhF;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAmCvB"}
1
+ {"version":3,"file":"detection.d.ts","sourceRoot":"","sources":["../../src/i18n/detection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAChF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAQlD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,cAAc,EAAE,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAwDxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,sBAAsB,GAC9B,qBAAqB,CAyCvB"}
@@ -1,16 +1,111 @@
1
1
  import { normalizeLocale } from "../utils/locale.js";
2
+ import { LANGUAGE_TO_COUNTRY } from "../utils/locale-ui.js";
2
3
  /** Case-insensitive locale lookup — returns the canonical (CDN) form if matched */
3
4
  const findLocale = (code, available) => code ? available.find((a) => normalizeLocale(a) === normalizeLocale(code)) : undefined;
4
5
  /**
5
- * Framework-agnostic locale detection logic
6
+ * Build a country→locale map from CDN manifest languages.
7
+ *
8
+ * Uses each language's `countryCode` field as the primary source
9
+ * (set in the Better i18n dashboard). Falls back to `LANGUAGE_TO_COUNTRY`
10
+ * reverse lookup for languages without explicit country codes.
11
+ *
12
+ * Also includes multi-country overrides (e.g. AT→de, MX→es) so that
13
+ * visitors from countries sharing a language are correctly mapped.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const languages = await i18n.getLanguages();
18
+ * const countryMap = buildCountryLocaleMap(languages);
19
+ * // { tr: "tr", de: "de", jp: "ja", cn: "zh-hans", at: "de", mx: "es", ... }
20
+ * ```
21
+ */
22
+ export function buildCountryLocaleMap(languages) {
23
+ const map = {};
24
+ const availableCodes = new Set(languages.map((l) => l.code));
25
+ // Primary: manifest countryCode → locale code
26
+ for (const lang of languages) {
27
+ if (lang.countryCode) {
28
+ map[lang.countryCode.toLowerCase()] = lang.code;
29
+ }
30
+ }
31
+ // Secondary: reverse LANGUAGE_TO_COUNTRY for languages with no countryCode
32
+ for (const lang of languages) {
33
+ if (!lang.countryCode) {
34
+ const country = LANGUAGE_TO_COUNTRY[lang.code]
35
+ || LANGUAGE_TO_COUNTRY[lang.code.split("-")[0]];
36
+ if (country && !map[country]) {
37
+ map[country] = lang.code;
38
+ }
39
+ }
40
+ }
41
+ // Multi-country overrides: countries whose primary language is available
42
+ // but they aren't the "home country" for that language
43
+ const MULTI_COUNTRY = {
44
+ // German-speaking
45
+ at: "de", ch: "de", li: "de",
46
+ // French-speaking
47
+ be: "fr", lu: "fr", mc: "fr",
48
+ // Spanish-speaking
49
+ mx: "es", ar: "es", co: "es", cl: "es", pe: "es",
50
+ ve: "es", ec: "es", gt: "es", cu: "es", do: "es",
51
+ hn: "es", sv: "es", ni: "es", cr: "es", pa: "es",
52
+ uy: "es", py: "es", bo: "es",
53
+ // Portuguese-speaking
54
+ br: "pt-br", ao: "pt", mz: "pt",
55
+ // Arabic-speaking
56
+ ae: "ar", eg: "ar", ma: "ar", dz: "ar", iq: "ar",
57
+ kw: "ar", qa: "ar", bh: "ar", om: "ar", jo: "ar", lb: "ar",
58
+ // Chinese-speaking
59
+ sg: "zh-hans", tw: "zh-hans", hk: "zh-hans", mo: "zh-hans",
60
+ // English-speaking (fallback)
61
+ us: "en", gb: "en", au: "en", nz: "en", ca: "en", ie: "en",
62
+ // Russian-speaking
63
+ by: "ru", kz: "ru", kg: "ru",
64
+ // Malay-speaking
65
+ bn: "ms",
66
+ };
67
+ for (const [country, locale] of Object.entries(MULTI_COUNTRY)) {
68
+ if (!map[country] && availableCodes.has(locale)) {
69
+ map[country] = locale;
70
+ }
71
+ }
72
+ return map;
73
+ }
74
+ /**
75
+ * Framework-agnostic locale detection with geo-IP support.
76
+ *
77
+ * Detection priority: path > cookie > geo (country) > Accept-Language header > default
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * // In your middleware/loader:
82
+ * const languages = await i18n.getLanguages();
83
+ * const countryMap = buildCountryLocaleMap(languages);
84
+ *
85
+ * const result = detectLocale({
86
+ * project: "acme/web",
87
+ * defaultLocale: "en",
88
+ * availableLocales: languages.map(l => l.code),
89
+ * pathLocale: getLocaleFromURL(pathname),
90
+ * cookieLocale: getCookie("locale"),
91
+ * countryCode: request.cf?.country, // Cloudflare
92
+ * // countryCode: request.geo?.country, // Vercel
93
+ * countryLocaleMap: countryMap,
94
+ * headerLocale: parseAcceptLanguage(request),
95
+ * });
96
+ *
97
+ * // result.locale = "tr"
98
+ * // result.detectedFrom = "geo"
99
+ * ```
6
100
  */
7
101
  export function detectLocale(options) {
8
- const { pathLocale, cookieLocale, headerLocale, defaultLocale, availableLocales, } = options;
102
+ const { pathLocale, cookieLocale, headerLocale, countryCode, countryLocaleMap, defaultLocale, availableLocales, } = options;
9
103
  let locale;
10
104
  let detectedFrom;
11
- // Priority: path > cookie > header > default
105
+ // Priority: path > cookie > geo > header > default
12
106
  const pathMatch = findLocale(pathLocale, availableLocales);
13
107
  const cookieMatch = findLocale(cookieLocale, availableLocales);
108
+ const geoMatch = resolveGeoLocale(countryCode, countryLocaleMap, availableLocales);
14
109
  const headerMatch = findLocale(headerLocale, availableLocales);
15
110
  if (pathMatch) {
16
111
  locale = pathMatch;
@@ -20,6 +115,10 @@ export function detectLocale(options) {
20
115
  locale = cookieMatch;
21
116
  detectedFrom = "cookie";
22
117
  }
118
+ else if (geoMatch) {
119
+ locale = geoMatch;
120
+ detectedFrom = "geo";
121
+ }
23
122
  else if (headerMatch) {
24
123
  locale = headerMatch;
25
124
  detectedFrom = "header";
@@ -32,4 +131,89 @@ export function detectLocale(options) {
32
131
  const shouldSetCookie = cookieLocale !== locale;
33
132
  return { locale, detectedFrom, shouldSetCookie };
34
133
  }
134
+ /**
135
+ * Built-in multi-country overrides for countries that share a language
136
+ * with another country already in LANGUAGE_TO_COUNTRY.
137
+ * This allows geo detection to work without a manifest-driven map.
138
+ */
139
+ const BUILT_IN_COUNTRY_OVERRIDES = {
140
+ // German-speaking
141
+ at: "de", ch: "de", li: "de",
142
+ // French-speaking
143
+ be: "fr", lu: "fr", mc: "fr",
144
+ // Spanish-speaking
145
+ mx: "es", ar: "es", co: "es", cl: "es", pe: "es",
146
+ ve: "es", ec: "es", gt: "es", cu: "es", hn: "es",
147
+ sv: "es", ni: "es", cr: "es", pa: "es", uy: "es",
148
+ py: "es", bo: "es", do: "es",
149
+ // Portuguese-speaking
150
+ br: "pt", ao: "pt", mz: "pt",
151
+ // Arabic-speaking
152
+ ae: "ar", eg: "ar", ma: "ar", dz: "ar", iq: "ar",
153
+ kw: "ar", qa: "ar", bh: "ar", om: "ar", jo: "ar", lb: "ar",
154
+ // Chinese-speaking
155
+ sg: "zh", tw: "zh", hk: "zh", mo: "zh",
156
+ // Russian-speaking
157
+ by: "ru", kz: "ru", kg: "ru",
158
+ // Malay-speaking
159
+ bn: "ms",
160
+ // English-speaking
161
+ us: "en", au: "en", nz: "en", ca: "en", ie: "en",
162
+ };
163
+ /**
164
+ * Fuzzy locale match — finds a locale in available list even when the
165
+ * base language matches but the full code differs.
166
+ * e.g., lang="zh", available=["zh-hans"] → returns "zh-hans"
167
+ * e.g., lang="pt", available=["pt-br", "pt"] → returns "pt"
168
+ */
169
+ function fuzzyFindLocale(lang, available) {
170
+ // Exact match first
171
+ const exact = findLocale(lang, available);
172
+ if (exact)
173
+ return exact;
174
+ // Base language match (zh → zh-hans, pt → pt-br)
175
+ const base = lang.split("-")[0].toLowerCase();
176
+ return available.find((a) => a.toLowerCase().startsWith(base + "-"))
177
+ ?? available.find((a) => a.toLowerCase() === base);
178
+ }
179
+ /**
180
+ * Resolve a country code to a locale.
181
+ *
182
+ * Resolution chain:
183
+ * 1. Explicit countryLocaleMap (manifest-driven, most accurate)
184
+ * 2. LANGUAGE_TO_COUNTRY reverse lookup (built-in)
185
+ * 3. BUILT_IN_COUNTRY_OVERRIDES (multi-country fallback)
186
+ *
187
+ * All lookups use fuzzy matching so zh→zh-hans, pt→pt-br work.
188
+ */
189
+ function resolveGeoLocale(countryCode, countryLocaleMap, availableLocales) {
190
+ if (!countryCode)
191
+ return undefined;
192
+ const normalized = countryCode.toLowerCase();
193
+ // 1. Explicit map (manifest-driven)
194
+ if (countryLocaleMap) {
195
+ const mapped = countryLocaleMap[normalized];
196
+ if (mapped) {
197
+ const match = fuzzyFindLocale(mapped, availableLocales);
198
+ if (match)
199
+ return match;
200
+ }
201
+ }
202
+ // 2. Reverse LANGUAGE_TO_COUNTRY (e.g., tr→"tr", ja→"jp")
203
+ for (const [lang, country] of Object.entries(LANGUAGE_TO_COUNTRY)) {
204
+ if (country === normalized) {
205
+ const match = fuzzyFindLocale(lang, availableLocales);
206
+ if (match)
207
+ return match;
208
+ }
209
+ }
210
+ // 3. Multi-country overrides (e.g., AT→de, MX→es, BR→pt)
211
+ const override = BUILT_IN_COUNTRY_OVERRIDES[normalized];
212
+ if (override) {
213
+ const match = fuzzyFindLocale(override, availableLocales);
214
+ if (match)
215
+ return match;
216
+ }
217
+ return undefined;
218
+ }
35
219
  //# sourceMappingURL=detection.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"detection.js","sourceRoot":"","sources":["../../src/i18n/detection.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,mFAAmF;AACnF,MAAM,UAAU,GAAG,CAAC,IAA+B,EAAE,SAAmB,EAAE,EAAE,CAC1E,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAEzF;;GAEG;AACH,MAAM,UAAU,YAAY,CAC1B,OAA+B;IAE/B,MAAM,EACJ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,gBAAgB,GACjB,GAAG,OAAO,CAAC;IAEZ,IAAI,MAAc,CAAC;IACnB,IAAI,YAAmD,CAAC;IAExD,6CAA6C;IAC7C,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,UAAU,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IAC/D,MAAM,WAAW,GAAG,UAAU,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IAE/D,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,GAAG,SAAS,CAAC;QACnB,YAAY,GAAG,MAAM,CAAC;IACxB,CAAC;SAAM,IAAI,WAAW,EAAE,CAAC;QACvB,MAAM,GAAG,WAAW,CAAC;QACrB,YAAY,GAAG,QAAQ,CAAC;IAC1B,CAAC;SAAM,IAAI,WAAW,EAAE,CAAC;QACvB,MAAM,GAAG,WAAW,CAAC;QACrB,YAAY,GAAG,QAAQ,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,aAAa,CAAC;QACvB,YAAY,GAAG,SAAS,CAAC;IAC3B,CAAC;IAED,0DAA0D;IAC1D,MAAM,eAAe,GAAG,YAAY,KAAK,MAAM,CAAC;IAEhD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,CAAC;AACnD,CAAC"}
1
+ {"version":3,"file":"detection.js","sourceRoot":"","sources":["../../src/i18n/detection.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,mFAAmF;AACnF,MAAM,UAAU,GAAG,CAAC,IAA+B,EAAE,SAAmB,EAAE,EAAE,CAC1E,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,KAAK,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAEzF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,qBAAqB,CACnC,SAA2B;IAE3B,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAE7D,8CAA8C;IAC9C,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACrB,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;QAClD,CAAC;IACH,CAAC;IAED,2EAA2E;IAC3E,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC;mBACzC,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAClD,IAAI,OAAO,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC7B,GAAG,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,uDAAuD;IACvD,MAAM,aAAa,GAA2B;QAC5C,kBAAkB;QAClB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAC5B,kBAAkB;QAClB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAC5B,mBAAmB;QACnB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAChD,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAChD,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAChD,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAC5B,sBAAsB;QACtB,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAC/B,kBAAkB;QAClB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAChD,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAC1D,mBAAmB;QACnB,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS;QAC1D,8BAA8B;QAC9B,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAC1D,mBAAmB;QACnB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;QAC5B,iBAAiB;QACjB,EAAE,EAAE,IAAI;KACT,CAAC;IAEF,KAAK,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;QAC9D,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAChD,GAAG,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC;QACxB,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,YAAY,CAC1B,OAA+B;IAE/B,MAAM,EACJ,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,GAAG,OAAO,CAAC;IAEZ,IAAI,MAAc,CAAC;IACnB,IAAI,YAAmD,CAAC;IAExD,mDAAmD;IACnD,MAAM,SAAS,GAAG,UAAU,CAAC,UAAU,EAAE,gBAAgB,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,UAAU,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IAC/D,MAAM,QAAQ,GAAG,gBAAgB,CAAC,WAAW,EAAE,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;IACnF,MAAM,WAAW,GAAG,UAAU,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;IAE/D,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,GAAG,SAAS,CAAC;QACnB,YAAY,GAAG,MAAM,CAAC;IACxB,CAAC;SAAM,IAAI,WAAW,EAAE,CAAC;QACvB,MAAM,GAAG,WAAW,CAAC;QACrB,YAAY,GAAG,QAAQ,CAAC;IAC1B,CAAC;SAAM,IAAI,QAAQ,EAAE,CAAC;QACpB,MAAM,GAAG,QAAQ,CAAC;QAClB,YAAY,GAAG,KAAK,CAAC;IACvB,CAAC;SAAM,IAAI,WAAW,EAAE,CAAC;QACvB,MAAM,GAAG,WAAW,CAAC;QACrB,YAAY,GAAG,QAAQ,CAAC;IAC1B,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,aAAa,CAAC;QACvB,YAAY,GAAG,SAAS,CAAC;IAC3B,CAAC;IAED,0DAA0D;IAC1D,MAAM,eAAe,GAAG,YAAY,KAAK,MAAM,CAAC;IAEhD,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACH,MAAM,0BAA0B,GAA2B;IACzD,kBAAkB;IAClB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAC5B,kBAAkB;IAClB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAC5B,mBAAmB;IACnB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAChD,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAChD,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAChD,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAC5B,sBAAsB;IACtB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAC5B,kBAAkB;IAClB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAChD,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAC1D,mBAAmB;IACnB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IACtC,mBAAmB;IACnB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;IAC5B,iBAAiB;IACjB,EAAE,EAAE,IAAI;IACR,mBAAmB;IACnB,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI;CACjD,CAAC;AAEF;;;;;GAKG;AACH,SAAS,eAAe,CAAC,IAAY,EAAE,SAAmB;IACxD,oBAAoB;IACpB,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAC1C,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IAExB,iDAAiD;IACjD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;WAC/D,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CACvB,WAAsC,EACtC,gBAAoD,EACpD,gBAA0B;IAE1B,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IAEnC,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;IAE7C,oCAAoC;IACpC,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,MAAM,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;QAC5C,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,KAAK,GAAG,eAAe,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;YACxD,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAClE,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;YACtD,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,yDAAyD;IACzD,MAAM,QAAQ,GAAG,0BAA0B,CAAC,UAAU,CAAC,CAAC;IACxD,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,eAAe,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;QAC1D,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -20,11 +20,30 @@ export interface LocaleDetectionOptions {
20
20
  pathLocale?: string | null;
21
21
  cookieLocale?: string | null;
22
22
  headerLocale?: string | null;
23
+ /**
24
+ * ISO 3166-1 alpha-2 country code from geo-IP detection.
25
+ * Pass this from your edge platform:
26
+ * - Cloudflare Workers: `request.cf?.country`
27
+ * - Vercel Edge: `request.geo?.country`
28
+ * - Node.js behind CF: `request.headers.get("CF-IPCountry")`
29
+ *
30
+ * Used with `countryLocaleMap` to resolve country → locale.
31
+ * Priority: path > cookie > geo (country) > header > default
32
+ */
33
+ countryCode?: string | null;
34
+ /**
35
+ * Country code → locale mapping. Built from CDN manifest's
36
+ * `languages[].countryCode` field via `buildCountryLocaleMap()`.
37
+ *
38
+ * If not provided but `countryCode` is set, falls back to
39
+ * `LANGUAGE_TO_COUNTRY` reverse lookup from core.
40
+ */
41
+ countryLocaleMap?: Record<string, string>;
23
42
  availableLocales: string[];
24
43
  }
25
44
  export interface LocaleDetectionResult {
26
45
  locale: string;
27
- detectedFrom: "path" | "cookie" | "header" | "default";
46
+ detectedFrom: "path" | "cookie" | "geo" | "header" | "default";
28
47
  shouldSetCookie: boolean;
29
48
  }
30
49
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/i18n/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACvD,eAAe,EAAE,OAAO,CAAC;CAC1B"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/i18n/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,SAAS,CAAC,EAAE;QACV,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;;;;;;OASG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC/D,eAAe,EAAE,OAAO,CAAC;CAC1B"}
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export { createAutoStorage, createLocalStorage, createMemoryStorage } from "./st
3
3
  export { normalizeConfig, parseProject, getProjectBaseUrl } from "./config.js";
4
4
  export { extractLanguages } from "./manifest.js";
5
5
  export { createLogger } from "./logger.js";
6
- export { detectLocale } from "./i18n/detection.js";
6
+ export { detectLocale, buildCountryLocaleMap } from "./i18n/detection.js";
7
7
  export { TtlCache, buildCacheKey } from "./cache.js";
8
8
  export { LANGUAGE_TO_COUNTRY, getCountryCodeFromLocale, getFlagEmoji, getLanguageLabel, resolveFlag, type ResolvedFlag, } from "./utils/locale-ui.js";
9
9
  export { normalizeLocale, extractLocale, getLocaleFromPath, hasLocalePrefix, removeLocalePrefix, addLocalePrefix, replaceLocaleInPath, createLocalePath, type LocaleConfig, } from "./utils/locale.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGlF,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGhG,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,KAAK,YAAY,GAClB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAEV,cAAc,EACd,gBAAgB,EAChB,aAAa,EAGb,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EAGd,QAAQ,EACR,MAAM,EAGN,QAAQ,EAGR,kBAAkB,EAGlB,MAAM,EACN,QAAQ,EACR,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,GACb,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAGlF,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGhG,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAG/E,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAGjD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAG1E,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,gBAAgB,EAChB,WAAW,EACX,KAAK,YAAY,GAClB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EACL,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,mBAAmB,CAAC;AAG3B,YAAY,EAEV,cAAc,EACd,gBAAgB,EAChB,aAAa,EAGb,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,cAAc,EAGd,QAAQ,EACR,MAAM,EAGN,QAAQ,EAGR,kBAAkB,EAGlB,MAAM,EACN,QAAQ,EACR,UAAU,GACX,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,oBAAoB,EACpB,sBAAsB,EACtB,qBAAqB,EACrB,YAAY,GACb,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ export { extractLanguages } from "./manifest.js";
9
9
  // Logger
10
10
  export { createLogger } from "./logger.js";
11
11
  // Middleware logic
12
- export { detectLocale } from "./i18n/detection.js";
12
+ export { detectLocale, buildCountryLocaleMap } from "./i18n/detection.js";
13
13
  // Cache utilities
14
14
  export { TtlCache, buildCacheKey } from "./cache.js";
15
15
  // Locale UI utilities (flags, labels, country codes)
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAElF,mBAAmB;AACnB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEhG,0BAA0B;AAC1B,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE/E,qBAAqB;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,kBAAkB;AAClB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAErD,qDAAqD;AACrD,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,gBAAgB,EAChB,WAAW,GAEZ,MAAM,sBAAsB,CAAC;AAE9B,mBAAmB;AACnB,OAAO,EACL,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,GAEjB,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAElF,mBAAmB;AACnB,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEhG,0BAA0B;AAC1B,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE/E,qBAAqB;AACrB,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjD,SAAS;AACT,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,mBAAmB;AACnB,OAAO,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE1E,kBAAkB;AAClB,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAErD,qDAAqD;AACrD,OAAO,EACL,mBAAmB,EACnB,wBAAwB,EACxB,YAAY,EACZ,gBAAgB,EAChB,WAAW,GAEZ,MAAM,sBAAsB,CAAC;AAE9B,mBAAmB;AACnB,OAAO,EACL,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,GAEjB,MAAM,mBAAmB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAcpB;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,UAAU,gBAAgB,KACzB,cAAc,EAahB,CAAC"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAepB;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAC3B,UAAU,gBAAgB,KACzB,cAAc,EAahB,CAAC"}
package/dist/manifest.js CHANGED
@@ -6,6 +6,7 @@ const normalizeLanguage = (language) => ({
6
6
  name: language.name,
7
7
  nativeName: language.nativeName || language.name || language.code.toUpperCase(),
8
8
  flagUrl: language.flagUrl ?? null,
9
+ countryCode: language.countryCode ?? null,
9
10
  isDefault: language.isSource ?? false,
10
11
  });
11
12
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,iBAAiB,GAAG,CAAC,QAA0B,EAAkB,EAAE,CAAC,CAAC;IACzE,IAAI,EAAE,QAAQ,CAAC,IAAI;IACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;IACnB,UAAU,EACR,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE;IACrE,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,IAAI;IACjC,SAAS,EAAE,QAAQ,CAAC,QAAQ,IAAI,KAAK;CACtC,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,QAA0B,EACR,EAAE;IACpB,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;QACjD,CAAC,CAAC,QAAQ,CAAC,SAAS;QACpB,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO,SAAS;SACb,MAAM,CACL,CAAC,QAAQ,EAAgC,EAAE,CACzC,CAAC,CAAC,QAAQ;QACV,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ;QACjC,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAC3B;SACA,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAC5B,CAAC,CAAC"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,iBAAiB,GAAG,CAAC,QAA0B,EAAkB,EAAE,CAAC,CAAC;IACzE,IAAI,EAAE,QAAQ,CAAC,IAAI;IACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;IACnB,UAAU,EACR,QAAQ,CAAC,UAAU,IAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE;IACrE,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAI,IAAI;IACjC,WAAW,EAAE,QAAQ,CAAC,WAAW,IAAI,IAAI;IACzC,SAAS,EAAE,QAAQ,CAAC,QAAQ,IAAI,KAAK;CACtC,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,QAA0B,EACR,EAAE;IACpB,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC;QACjD,CAAC,CAAC,QAAQ,CAAC,SAAS;QACpB,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO,SAAS;SACb,MAAM,CACL,CAAC,QAAQ,EAAgC,EAAE,CACzC,CAAC,CAAC,QAAQ;QACV,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ;QACjC,QAAQ,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAC3B;SACA,GAAG,CAAC,iBAAiB,CAAC,CAAC;AAC5B,CAAC,CAAC"}
package/dist/types.d.ts CHANGED
@@ -108,6 +108,8 @@ export interface ManifestLanguage {
108
108
  name?: string;
109
109
  nativeName?: string;
110
110
  flagUrl?: string | null;
111
+ /** ISO 3166-1 alpha-2 country code (e.g. "tr", "jp", "cn") */
112
+ countryCode?: string | null;
111
113
  isSource?: boolean;
112
114
  lastUpdated?: string | null;
113
115
  keyCount?: number;
@@ -138,6 +140,8 @@ export interface LanguageOption {
138
140
  name?: string;
139
141
  nativeName?: string;
140
142
  flagUrl?: string | null;
143
+ /** ISO 3166-1 alpha-2 country code (e.g. "tr", "jp", "cn") */
144
+ countryCode?: string | null;
141
145
  /**
142
146
  * Whether this is the default/source language
143
147
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -20,10 +20,23 @@ export interface LocaleConfig {
20
20
  */
21
21
  locales: string[];
22
22
  /**
23
- * Default locale code (will NOT have a prefix in URL)
23
+ * Default locale code
24
24
  * @example 'en'
25
25
  */
26
26
  defaultLocale: string;
27
+ /**
28
+ * URL prefix strategy for locale codes.
29
+ *
30
+ * - `"as-needed"` (default): Default locale has NO prefix (`/about`), others do (`/tr/about`).
31
+ * Follows next-intl / Paraglide JS convention.
32
+ * - `"always"`: ALL locales get a prefix, including the default (`/en/about`, `/tr/about`).
33
+ * Use with TanStack Router `$locale/` route segments.
34
+ * - `"never"`: NO locale prefix in URL for any locale. Locale is stored only in cookie.
35
+ * Ideal for dashboards and apps where URL structure shouldn't change per locale.
36
+ *
37
+ * @default "as-needed"
38
+ */
39
+ localePrefix?: "always" | "as-needed" | "never";
27
40
  }
28
41
  /**
29
42
  * Extract locale from URL pathname
@@ -67,27 +80,43 @@ export declare function hasLocalePrefix(pathname: string, config: LocaleConfig):
67
80
  */
68
81
  export declare function removeLocalePrefix(pathname: string, config: LocaleConfig): string;
69
82
  /**
70
- * Add locale prefix to pathname (respects default locale convention)
83
+ * Add locale prefix to pathname
71
84
  *
72
- * Default locale does NOT get a prefix (follows next-intl/Paraglide JS pattern)
85
+ * Respects `localePrefix` config:
86
+ * - `"as-needed"` (default): default locale has no prefix
87
+ * - `"always"`: all locales get a prefix
88
+ * - `"never"`: no locale prefix for any locale (cookie-only)
73
89
  *
74
90
  * @example
91
+ * // as-needed (default)
75
92
  * addLocalePrefix('/about', 'tr', config) // '/tr/about'
76
- * addLocalePrefix('/about', 'en', config) // '/about' (en is default, no prefix)
77
- * addLocalePrefix('/', 'tr', config) // '/tr'
93
+ * addLocalePrefix('/about', 'en', config) // '/about' (en is default)
94
+ *
95
+ * // always
96
+ * addLocalePrefix('/about', 'en', { ...config, localePrefix: 'always' }) // '/en/about'
97
+ *
98
+ * // never
99
+ * addLocalePrefix('/about', 'tr', { ...config, localePrefix: 'never' }) // '/about'
78
100
  */
79
101
  export declare function addLocalePrefix(pathname: string, locale: string, config: LocaleConfig): string;
80
102
  /**
81
103
  * Replace/add locale in pathname
82
104
  *
83
- * Handles the default locale specially (removes prefix instead of adding)
84
- * This is the main utility for locale switching in URLs
105
+ * Respects `localePrefix` config:
106
+ * - `"as-needed"` (default): default locale has no prefix
107
+ * - `"always"`: all locales always get a prefix
108
+ * - `"never"`: no locale prefix for any locale (cookie-only)
85
109
  *
86
110
  * @example
87
- * replaceLocaleInPath('/about', 'tr', config) // '/tr/about'
111
+ * // as-needed (default)
88
112
  * replaceLocaleInPath('/tr/about', 'en', config) // '/about' (en is default)
89
113
  * replaceLocaleInPath('/tr/about', 'de', config) // '/de/about'
90
- * replaceLocaleInPath('/en/about', 'tr', config) // '/tr/about'
114
+ *
115
+ * // always
116
+ * replaceLocaleInPath('/tr/about', 'en', { ...config, localePrefix: 'always' }) // '/en/about'
117
+ *
118
+ * // never
119
+ * replaceLocaleInPath('/tr/about', 'de', { ...config, localePrefix: 'never' }) // '/about'
91
120
  */
92
121
  export declare function replaceLocaleInPath(pathname: string, newLocale: string, config: LocaleConfig): string;
93
122
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/utils/locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,MACR,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAElB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,MAAM,GAAG,IAAI,CASf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,OAAO,CAET;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,MAAM,CAMR;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,GACnB,MAAM,CAUR;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GACnB,MAAM,CAWR;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,IAC3C,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,MAAM,CAI/C"}
1
+ {"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/utils/locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,MACR,CAAC;AAE1C;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAElB;;;OAGG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;CACjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,MAAM,GAAG,IAAI,CASf;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,MAAM,CAER;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,OAAO,CAET;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,YAAY,GACnB,MAAM,CAMR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,YAAY,GACnB,MAAM,CAeR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,GACnB,MAAM,CAgBR;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,IAC3C,MAAM,MAAM,EAAE,SAAS,MAAM,KAAG,MAAM,CAI/C"}
@@ -69,18 +69,31 @@ export function removeLocalePrefix(pathname, config) {
69
69
  return result || "/";
70
70
  }
71
71
  /**
72
- * Add locale prefix to pathname (respects default locale convention)
72
+ * Add locale prefix to pathname
73
73
  *
74
- * Default locale does NOT get a prefix (follows next-intl/Paraglide JS pattern)
74
+ * Respects `localePrefix` config:
75
+ * - `"as-needed"` (default): default locale has no prefix
76
+ * - `"always"`: all locales get a prefix
77
+ * - `"never"`: no locale prefix for any locale (cookie-only)
75
78
  *
76
79
  * @example
80
+ * // as-needed (default)
77
81
  * addLocalePrefix('/about', 'tr', config) // '/tr/about'
78
- * addLocalePrefix('/about', 'en', config) // '/about' (en is default, no prefix)
79
- * addLocalePrefix('/', 'tr', config) // '/tr'
82
+ * addLocalePrefix('/about', 'en', config) // '/about' (en is default)
83
+ *
84
+ * // always
85
+ * addLocalePrefix('/about', 'en', { ...config, localePrefix: 'always' }) // '/en/about'
86
+ *
87
+ * // never
88
+ * addLocalePrefix('/about', 'tr', { ...config, localePrefix: 'never' }) // '/about'
80
89
  */
81
90
  export function addLocalePrefix(pathname, locale, config) {
82
- // Don't add prefix for default locale
83
- if (locale === config.defaultLocale) {
91
+ // Never add prefix locale is cookie-only
92
+ if (config.localePrefix === "never") {
93
+ return pathname;
94
+ }
95
+ // Don't add prefix for default locale in "as-needed" mode
96
+ if (config.localePrefix !== "always" && locale === config.defaultLocale) {
84
97
  return pathname;
85
98
  }
86
99
  if (pathname === "/" || pathname === "") {
@@ -91,20 +104,31 @@ export function addLocalePrefix(pathname, locale, config) {
91
104
  /**
92
105
  * Replace/add locale in pathname
93
106
  *
94
- * Handles the default locale specially (removes prefix instead of adding)
95
- * This is the main utility for locale switching in URLs
107
+ * Respects `localePrefix` config:
108
+ * - `"as-needed"` (default): default locale has no prefix
109
+ * - `"always"`: all locales always get a prefix
110
+ * - `"never"`: no locale prefix for any locale (cookie-only)
96
111
  *
97
112
  * @example
98
- * replaceLocaleInPath('/about', 'tr', config) // '/tr/about'
113
+ * // as-needed (default)
99
114
  * replaceLocaleInPath('/tr/about', 'en', config) // '/about' (en is default)
100
115
  * replaceLocaleInPath('/tr/about', 'de', config) // '/de/about'
101
- * replaceLocaleInPath('/en/about', 'tr', config) // '/tr/about'
116
+ *
117
+ * // always
118
+ * replaceLocaleInPath('/tr/about', 'en', { ...config, localePrefix: 'always' }) // '/en/about'
119
+ *
120
+ * // never
121
+ * replaceLocaleInPath('/tr/about', 'de', { ...config, localePrefix: 'never' }) // '/about'
102
122
  */
103
123
  export function replaceLocaleInPath(pathname, newLocale, config) {
104
124
  // Remove existing locale prefix if present
105
125
  const cleanPath = removeLocalePrefix(pathname, config);
106
- // Don't add prefix for default locale
107
- if (newLocale === config.defaultLocale) {
126
+ // Never add prefix locale is cookie-only
127
+ if (config.localePrefix === "never") {
128
+ return cleanPath;
129
+ }
130
+ // In "as-needed" mode, don't add prefix for default locale
131
+ if (config.localePrefix !== "always" && newLocale === config.defaultLocale) {
108
132
  return cleanPath;
109
133
  }
110
134
  // Add new locale prefix
@@ -1 +1 @@
1
- {"version":3,"file":"locale.js","sourceRoot":"","sources":["../../src/utils/locale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAU,EAAE,CACxD,MAAM,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAmB1C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAC3B,QAAgB,EAChB,MAAoB;IAEpB,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEjC,IAAI,YAAY,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAC1D,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,OAAO,IAAI,CAAC,CAAC,oCAAoC;AACnD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAC/B,QAAgB,EAChB,MAAoB;IAEpB,OAAO,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,aAAa,CAAC;AACjE,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,MAAoB;IAEpB,OAAO,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,IAAI,CAAC;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,MAAoB;IAEpB,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAE7B,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,KAAK,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/D,OAAO,MAAM,IAAI,GAAG,CAAC;AACvB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAgB,EAChB,MAAc,EACd,MAAoB;IAEpB,sCAAsC;IACtC,IAAI,MAAM,KAAK,MAAM,CAAC,aAAa,EAAE,CAAC;QACpC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,QAAQ,KAAK,GAAG,IAAI,QAAQ,KAAK,EAAE,EAAE,CAAC;QACxC,OAAO,IAAI,MAAM,EAAE,CAAC;IACtB,CAAC;IACD,OAAO,IAAI,MAAM,GAAG,QAAQ,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAgB,EAChB,SAAiB,EACjB,MAAoB;IAEpB,2CAA2C;IAC3C,MAAM,SAAS,GAAG,kBAAkB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAEvD,sCAAsC;IACtC,IAAI,SAAS,KAAK,MAAM,CAAC,aAAa,EAAE,CAAC;QACvC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,wBAAwB;IACxB,OAAO,IAAI,SAAS,GAAG,SAAS,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAoB;IACnD,OAAO,CAAC,IAAY,EAAE,MAAe,EAAU,EAAE;QAC/C,MAAM,YAAY,GAAG,MAAM,IAAI,MAAM,CAAC,aAAa,CAAC;QACpD,OAAO,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IACrD,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"locale.js","sourceRoot":"","sources":["../../src/utils/locale.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-i18n/core",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Framework-agnostic core utilities for Better i18n",
5
5
  "license": "MIT",
6
6
  "repository": {