@open-mercato/shared 0.6.6-develop.6526.1.6df9b3dd84 → 0.6.6-develop.6530.1.6d8442bafd

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.
@@ -11,9 +11,14 @@ const RAW_COUNTRIES = registry.filter(isIsoAlpha2).map((entry) => ({
11
11
  code: entry.Subtag,
12
12
  name: entry.Description.join(", ")
13
13
  }));
14
- const ISO_COUNTRIES = [...RAW_COUNTRIES].sort(
15
- (a, b) => a.name.localeCompare(b.name, "en", { sensitivity: "base" })
16
- );
14
+ const SUPPLEMENTAL_COUNTRIES = [
15
+ { code: "XK", name: "Kosovo" }
16
+ ];
17
+ const RAW_COUNTRY_CODES = new Set(RAW_COUNTRIES.map((entry) => entry.code));
18
+ const ISO_COUNTRIES = [
19
+ ...RAW_COUNTRIES,
20
+ ...SUPPLEMENTAL_COUNTRIES.filter((entry) => !RAW_COUNTRY_CODES.has(entry.code))
21
+ ].sort((a, b) => a.name.localeCompare(b.name, "en", { sensitivity: "base" }));
17
22
  const COUNTRY_PRIORITY = ["PL", "DE", "ES", "FR", "IT", "US", "GB", "CA"];
18
23
  const displayNameCache = /* @__PURE__ */ new Map();
19
24
  function getDisplayNames(locale) {
@@ -37,7 +42,8 @@ function resolveCountryName(code, options = {}) {
37
42
  if (!displayNames) return fallback;
38
43
  try {
39
44
  const label = displayNames.of(normalized);
40
- return typeof label === "string" ? label : fallback;
45
+ if (typeof label !== "string" || label === normalized) return fallback;
46
+ return label;
41
47
  } catch {
42
48
  return fallback;
43
49
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/lib/location/countries.ts"],
4
- "sourcesContent": ["import registry from 'language-subtag-registry/data/json/registry.json'\n\ntype RegistryEntry = {\n Type: string\n Subtag?: string\n Description?: string[]\n Deprecated?: string\n}\n\nexport type IsoCountry = {\n code: string\n name: string\n}\n\nfunction isIsoAlpha2(entry: RegistryEntry): entry is RegistryEntry & { Subtag: string; Description: string[] } {\n if (entry.Type !== 'region') return false\n if (!entry.Subtag || !/^[A-Z]{2}$/.test(entry.Subtag)) return false\n if (entry.Deprecated) return false\n if (!entry.Description || !entry.Description.length) return false\n if (entry.Description[0] === 'Private use') return false\n return true\n}\n\nconst RAW_COUNTRIES: IsoCountry[] = (registry as RegistryEntry[])\n .filter(isIsoAlpha2)\n .map((entry) => ({\n code: entry.Subtag,\n name: entry.Description.join(', '),\n }))\n\nexport const ISO_COUNTRIES: IsoCountry[] = [...RAW_COUNTRIES].sort((a, b) =>\n a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })\n)\n\nexport const COUNTRY_PRIORITY: string[] = ['PL', 'DE', 'ES', 'FR', 'IT', 'US', 'GB', 'CA']\n\nconst displayNameCache = new Map<string, Intl.DisplayNames>()\n\nfunction getDisplayNames(locale: string): Intl.DisplayNames | null {\n if (typeof Intl === 'undefined' || typeof Intl.DisplayNames === 'undefined') return null\n const key = locale || 'en'\n let instance = displayNameCache.get(key)\n if (!instance) {\n try {\n instance = new Intl.DisplayNames([key], { type: 'region' })\n displayNameCache.set(key, instance)\n } catch {\n return null\n }\n }\n return instance\n}\n\nexport function resolveCountryName(code: string, options: { locale?: string } = {}): string {\n const normalized = code.toUpperCase()\n const fallback = ISO_COUNTRIES.find((entry) => entry.code === normalized)?.name ?? normalized\n const displayNames = getDisplayNames(options.locale ?? 'en')\n if (!displayNames) return fallback\n try {\n const label = displayNames.of(normalized as any)\n return typeof label === 'string' ? label : fallback\n } catch {\n return fallback\n }\n}\n\nexport function buildCountryOptions(options: {\n locale?: string\n priority?: string[]\n transformLabel?: (code: string, defaultLabel: string) => string\n} = {}): Array<{ code: string; label: string }> {\n const { locale, transformLabel } = options\n const priority = (options.priority ?? COUNTRY_PRIORITY).map((code) => code.toUpperCase())\n const prioritySet = new Set(priority)\n const labelFor = (code: string) => {\n const base = resolveCountryName(code, { locale })\n return transformLabel ? transformLabel(code, base) : base\n }\n\n const byCode = new Map(ISO_COUNTRIES.map((entry) => [entry.code, entry]))\n\n const prioritized = priority\n .map((code) => {\n const base = byCode.get(code)\n if (!base) return null\n return { code, label: labelFor(code) }\n })\n .filter((entry): entry is { code: string; label: string } => !!entry)\n .sort((a, b) => a.label.localeCompare(b.label, locale ?? 'en', { sensitivity: 'base' }))\n\n const remaining = ISO_COUNTRIES\n .filter((entry) => !prioritySet.has(entry.code))\n .map((entry) => ({ code: entry.code, label: labelFor(entry.code) }))\n .sort((a, b) => a.label.localeCompare(b.label, locale ?? 'en', { sensitivity: 'base' }))\n\n return [...prioritized, ...remaining]\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import registry from 'language-subtag-registry/data/json/registry.json'\n\ntype RegistryEntry = {\n Type: string\n Subtag?: string\n Description?: string[]\n Deprecated?: string\n}\n\nexport type IsoCountry = {\n code: string\n name: string\n}\n\nfunction isIsoAlpha2(entry: RegistryEntry): entry is RegistryEntry & { Subtag: string; Description: string[] } {\n if (entry.Type !== 'region') return false\n if (!entry.Subtag || !/^[A-Z]{2}$/.test(entry.Subtag)) return false\n if (entry.Deprecated) return false\n if (!entry.Description || !entry.Description.length) return false\n if (entry.Description[0] === 'Private use') return false\n return true\n}\n\nconst RAW_COUNTRIES: IsoCountry[] = (registry as RegistryEntry[])\n .filter(isIsoAlpha2)\n .map((entry) => ({\n code: entry.Subtag,\n name: entry.Description.join(', '),\n }))\n\n// Codes in common use that the language-subtag registry does not list as region subtags.\n// XK is user-assigned rather than ISO 3166-1 assigned, so it never appears in the registry.\nconst SUPPLEMENTAL_COUNTRIES: IsoCountry[] = [\n { code: 'XK', name: 'Kosovo' },\n]\n\nconst RAW_COUNTRY_CODES = new Set(RAW_COUNTRIES.map((entry) => entry.code))\n\nexport const ISO_COUNTRIES: IsoCountry[] = [\n ...RAW_COUNTRIES,\n ...SUPPLEMENTAL_COUNTRIES.filter((entry) => !RAW_COUNTRY_CODES.has(entry.code)),\n].sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }))\n\nexport const COUNTRY_PRIORITY: string[] = ['PL', 'DE', 'ES', 'FR', 'IT', 'US', 'GB', 'CA']\n\nconst displayNameCache = new Map<string, Intl.DisplayNames>()\n\nfunction getDisplayNames(locale: string): Intl.DisplayNames | null {\n if (typeof Intl === 'undefined' || typeof Intl.DisplayNames === 'undefined') return null\n const key = locale || 'en'\n let instance = displayNameCache.get(key)\n if (!instance) {\n try {\n instance = new Intl.DisplayNames([key], { type: 'region' })\n displayNameCache.set(key, instance)\n } catch {\n return null\n }\n }\n return instance\n}\n\nexport function resolveCountryName(code: string, options: { locale?: string } = {}): string {\n const normalized = code.toUpperCase()\n const fallback = ISO_COUNTRIES.find((entry) => entry.code === normalized)?.name ?? normalized\n const displayNames = getDisplayNames(options.locale ?? 'en')\n if (!displayNames) return fallback\n try {\n const label = displayNames.of(normalized as any)\n // Intl echoes the raw code back when its ICU data cannot resolve the region.\n if (typeof label !== 'string' || label === normalized) return fallback\n return label\n } catch {\n return fallback\n }\n}\n\nexport function buildCountryOptions(options: {\n locale?: string\n priority?: string[]\n transformLabel?: (code: string, defaultLabel: string) => string\n} = {}): Array<{ code: string; label: string }> {\n const { locale, transformLabel } = options\n const priority = (options.priority ?? COUNTRY_PRIORITY).map((code) => code.toUpperCase())\n const prioritySet = new Set(priority)\n const labelFor = (code: string) => {\n const base = resolveCountryName(code, { locale })\n return transformLabel ? transformLabel(code, base) : base\n }\n\n const byCode = new Map(ISO_COUNTRIES.map((entry) => [entry.code, entry]))\n\n const prioritized = priority\n .map((code) => {\n const base = byCode.get(code)\n if (!base) return null\n return { code, label: labelFor(code) }\n })\n .filter((entry): entry is { code: string; label: string } => !!entry)\n .sort((a, b) => a.label.localeCompare(b.label, locale ?? 'en', { sensitivity: 'base' }))\n\n const remaining = ISO_COUNTRIES\n .filter((entry) => !prioritySet.has(entry.code))\n .map((entry) => ({ code: entry.code, label: labelFor(entry.code) }))\n .sort((a, b) => a.label.localeCompare(b.label, locale ?? 'en', { sensitivity: 'base' }))\n\n return [...prioritized, ...remaining]\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,4 @@
1
- const APP_VERSION = "0.6.6-develop.6526.1.6df9b3dd84";
1
+ const APP_VERSION = "0.6.6-develop.6530.1.6d8442bafd";
2
2
  const appVersion = APP_VERSION;
3
3
  export {
4
4
  APP_VERSION,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/lib/version.ts"],
4
- "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.6-develop.6526.1.6df9b3dd84'\nexport const appVersion = APP_VERSION\n"],
4
+ "sourcesContent": ["// Build-time generated version\nexport const APP_VERSION = '0.6.6-develop.6530.1.6d8442bafd'\nexport const appVersion = APP_VERSION\n"],
5
5
  "mappings": "AACO,MAAM,cAAc;AACpB,MAAM,aAAa;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/shared",
3
- "version": "0.6.6-develop.6526.1.6df9b3dd84",
3
+ "version": "0.6.6-develop.6530.1.6d8442bafd",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -97,7 +97,7 @@
97
97
  "@mikro-orm/core": "^7.1.5",
98
98
  "@mikro-orm/decorators": "^7.1.5",
99
99
  "@mikro-orm/postgresql": "^7.1.5",
100
- "@open-mercato/cache": "0.6.6-develop.6526.1.6df9b3dd84",
100
+ "@open-mercato/cache": "0.6.6-develop.6530.1.6d8442bafd",
101
101
  "@types/sanitize-html": "^2.16.1",
102
102
  "dotenv": "^17.4.2",
103
103
  "pino": "^10.3.1",
@@ -0,0 +1,82 @@
1
+ import {
2
+ COUNTRY_PRIORITY,
3
+ ISO_COUNTRIES,
4
+ buildCountryOptions,
5
+ resolveCountryName,
6
+ } from '../countries'
7
+
8
+ describe('ISO_COUNTRIES', () => {
9
+ it('includes Kosovo, which the language-subtag registry does not list', () => {
10
+ const kosovo = ISO_COUNTRIES.filter((entry) => entry.code === 'XK')
11
+ expect(kosovo).toEqual([{ code: 'XK', name: 'Kosovo' }])
12
+ })
13
+
14
+ it('does not duplicate any country code', () => {
15
+ const codes = ISO_COUNTRIES.map((entry) => entry.code)
16
+ expect(codes).toHaveLength(new Set(codes).size)
17
+ })
18
+
19
+ it('keeps registry-derived countries intact', () => {
20
+ expect(ISO_COUNTRIES).toEqual(
21
+ expect.arrayContaining([
22
+ { code: 'PL', name: 'Poland' },
23
+ { code: 'DE', name: 'Germany' },
24
+ ])
25
+ )
26
+ })
27
+
28
+ it('stays sorted by name', () => {
29
+ const names = ISO_COUNTRIES.map((entry) => entry.name)
30
+ const sorted = [...names].sort((a, b) => a.localeCompare(b, 'en', { sensitivity: 'base' }))
31
+ expect(names).toEqual(sorted)
32
+ })
33
+ })
34
+
35
+ describe('resolveCountryName', () => {
36
+ it('resolves Kosovo for XK', () => {
37
+ expect(resolveCountryName('XK')).toBe('Kosovo')
38
+ })
39
+
40
+ it('resolves Kosovo for a lowercase xk', () => {
41
+ expect(resolveCountryName('xk')).toBe('Kosovo')
42
+ })
43
+
44
+ it('still resolves registry-backed countries', () => {
45
+ expect(resolveCountryName('PL')).toBe('Poland')
46
+ })
47
+
48
+ it('falls back to the raw code for an unknown region', () => {
49
+ expect(resolveCountryName('ZZZ')).toBe('ZZZ')
50
+ })
51
+ })
52
+
53
+ describe('buildCountryOptions', () => {
54
+ it('offers Kosovo as a selectable option', () => {
55
+ const options = buildCountryOptions()
56
+ expect(options).toEqual(expect.arrayContaining([{ code: 'XK', label: 'Kosovo' }]))
57
+ })
58
+
59
+ it('offers exactly one Kosovo option', () => {
60
+ const options = buildCountryOptions().filter((option) => option.code === 'XK')
61
+ expect(options).toHaveLength(1)
62
+ })
63
+
64
+ it('lists Kosovo after the prioritized countries', () => {
65
+ const options = buildCountryOptions()
66
+ const kosovoIndex = options.findIndex((option) => option.code === 'XK')
67
+ expect(kosovoIndex).toBeGreaterThanOrEqual(COUNTRY_PRIORITY.length)
68
+ })
69
+
70
+ it('keeps the prioritized countries at the top', () => {
71
+ const options = buildCountryOptions()
72
+ const leading = options.slice(0, COUNTRY_PRIORITY.length).map((option) => option.code)
73
+ expect(leading.slice().sort()).toEqual(COUNTRY_PRIORITY.slice().sort())
74
+ })
75
+
76
+ it('applies transformLabel to the supplemental country too', () => {
77
+ const options = buildCountryOptions({
78
+ transformLabel: (code, defaultLabel) => `${defaultLabel} (${code})`,
79
+ })
80
+ expect(options).toEqual(expect.arrayContaining([{ code: 'XK', label: 'Kosovo (XK)' }]))
81
+ })
82
+ })
@@ -28,9 +28,18 @@ const RAW_COUNTRIES: IsoCountry[] = (registry as RegistryEntry[])
28
28
  name: entry.Description.join(', '),
29
29
  }))
30
30
 
31
- export const ISO_COUNTRIES: IsoCountry[] = [...RAW_COUNTRIES].sort((a, b) =>
32
- a.name.localeCompare(b.name, 'en', { sensitivity: 'base' })
33
- )
31
+ // Codes in common use that the language-subtag registry does not list as region subtags.
32
+ // XK is user-assigned rather than ISO 3166-1 assigned, so it never appears in the registry.
33
+ const SUPPLEMENTAL_COUNTRIES: IsoCountry[] = [
34
+ { code: 'XK', name: 'Kosovo' },
35
+ ]
36
+
37
+ const RAW_COUNTRY_CODES = new Set(RAW_COUNTRIES.map((entry) => entry.code))
38
+
39
+ export const ISO_COUNTRIES: IsoCountry[] = [
40
+ ...RAW_COUNTRIES,
41
+ ...SUPPLEMENTAL_COUNTRIES.filter((entry) => !RAW_COUNTRY_CODES.has(entry.code)),
42
+ ].sort((a, b) => a.name.localeCompare(b.name, 'en', { sensitivity: 'base' }))
34
43
 
35
44
  export const COUNTRY_PRIORITY: string[] = ['PL', 'DE', 'ES', 'FR', 'IT', 'US', 'GB', 'CA']
36
45
 
@@ -58,7 +67,9 @@ export function resolveCountryName(code: string, options: { locale?: string } =
58
67
  if (!displayNames) return fallback
59
68
  try {
60
69
  const label = displayNames.of(normalized as any)
61
- return typeof label === 'string' ? label : fallback
70
+ // Intl echoes the raw code back when its ICU data cannot resolve the region.
71
+ if (typeof label !== 'string' || label === normalized) return fallback
72
+ return label
62
73
  } catch {
63
74
  return fallback
64
75
  }