@lynxflow/seo-engine 1.5.7 → 1.5.9
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 +74 -20
- package/dist/built-in-locations.d.ts +12 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +152 -7
- package/dist/index.mjs +152 -7
- package/dist/matrix-engine.d.ts +3 -0
- package/package.json +1 -1
- package/src/built-in-locations.ts +170 -0
- package/src/engine.test.ts +17 -0
- package/src/index.ts +1 -0
- package/src/llm-prompt.ts +3 -5
- package/src/matrix-engine.ts +9 -2
package/README.md
CHANGED
|
@@ -57,47 +57,101 @@ The SDK structures programmatic content across **10 canonical root pillars** exp
|
|
|
57
57
|
|
|
58
58
|
## Quickstart
|
|
59
59
|
|
|
60
|
-
### 1. Initialize Engine
|
|
60
|
+
### 1. Initialize Engine & Configure Minimal Inputs
|
|
61
|
+
|
|
62
|
+
The SDK runs **in-memory inside the client application** (< 0.05ms) and automatically provisions verified cities, populations, GPS coordinates, and currencies from its built-in global demographic database.
|
|
61
63
|
|
|
62
64
|
```typescript
|
|
63
65
|
import { PseoMatrixEngine } from "@lynxflow/seo-engine";
|
|
64
66
|
|
|
65
|
-
export const matrixEngine = new PseoMatrixEngine(
|
|
66
|
-
companyName: "Acme Cloud",
|
|
67
|
-
language: "en", // "en" default, supports "fr", "de", "es", "it", "pt", etc.
|
|
68
|
-
correctionsEmail: "compliance@acme.com",
|
|
69
|
-
});
|
|
70
|
-
```
|
|
67
|
+
export const matrixEngine = new PseoMatrixEngine();
|
|
71
68
|
|
|
72
|
-
|
|
69
|
+
export const SEO_CONFIG = {
|
|
70
|
+
brandName: "Acme Cloud",
|
|
71
|
+
languages: ["fr", "en", "es"], // Supported site languages
|
|
72
|
+
countries: ["france", "espagne", "belgique"], // Or "europe" / "international" — SDK auto-provisions verified cities!
|
|
73
|
+
cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.) for direct /{service}/{city}
|
|
74
|
+
};
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
const pages = matrixEngine.generateAllMatrices("https://example.com", {
|
|
76
|
+
export const PSEO_DATASET = {
|
|
76
77
|
services: [
|
|
77
|
-
{
|
|
78
|
+
{
|
|
79
|
+
slug: "autopost-facebook",
|
|
80
|
+
name: "Autopost Facebook & Instagram",
|
|
81
|
+
category: "Social Media",
|
|
82
|
+
keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"],
|
|
83
|
+
description: "Automated social media posting engine"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
slug: "crm-pipeline",
|
|
87
|
+
name: "Visual CRM Pipeline",
|
|
88
|
+
category: "Sales",
|
|
89
|
+
keyFeatures: ["Kanban", "Lead scoring"],
|
|
90
|
+
description: "Visual deal tracking software"
|
|
91
|
+
},
|
|
78
92
|
],
|
|
79
93
|
targets: [
|
|
80
|
-
{ slug: "
|
|
94
|
+
{ slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] }
|
|
81
95
|
],
|
|
82
96
|
integrations: [
|
|
83
|
-
{ slug: "
|
|
97
|
+
{ slug: "shopify", name: "Shopify", category: "E-Commerce", syncFeatures: ["Catalog sync"] }
|
|
84
98
|
],
|
|
85
99
|
templates: [
|
|
86
|
-
{ slug: "
|
|
100
|
+
{ slug: "social-calendar-excel", title: "Social Media Calendar", topic: "Social Media", format: "excel", benefits: ["Free download"] }
|
|
87
101
|
],
|
|
88
102
|
glossaryTerms: [
|
|
89
|
-
{ slug: "
|
|
103
|
+
{ slug: "engagement-rate", term: "Engagement Rate", shortDefinition: "Percentage of audience interacting with content" }
|
|
90
104
|
]
|
|
91
|
-
}
|
|
105
|
+
};
|
|
106
|
+
```
|
|
92
107
|
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
### 2. Next.js Universal Dynamic Catch-All (`app/[...slug]/page.tsx`)
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
import { notFound } from "next/navigation";
|
|
112
|
+
import { matrixEngine, SEO_CONFIG, PSEO_DATASET } from "@/lib/seo";
|
|
113
|
+
|
|
114
|
+
export async function generateMetadata({ params }: { params: { slug: string[] } }) {
|
|
115
|
+
const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
|
|
116
|
+
const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
|
|
117
|
+
if (!page) return {};
|
|
118
|
+
return { title: page.title, description: page.description, alternates: { canonical: page.canonicalUrl }, robots: page.robots };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default async function ProgrammaticPage({ params }: { params: { slug: string[] } }) {
|
|
122
|
+
const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
|
|
123
|
+
// ⚡ Resolved mathematically in local RAM in < 0.05ms
|
|
124
|
+
const page = matrixEngine.resolvePage(params.slug, domain, PSEO_DATASET, SEO_CONFIG);
|
|
125
|
+
if (!page) notFound();
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<article className="min-h-screen bg-background text-foreground py-12 px-6 max-w-5xl mx-auto space-y-8">
|
|
129
|
+
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(page.schemaGraph) }} />
|
|
130
|
+
<h1 className="text-4xl font-extrabold">{page.h1}</h1>
|
|
131
|
+
<p className="text-lg text-muted-foreground">{page.description}</p>
|
|
132
|
+
{page.neighboringLinks && (
|
|
133
|
+
<nav className="flex flex-wrap gap-2 pt-6">
|
|
134
|
+
{page.neighboringLinks.map((l) => (
|
|
135
|
+
<a key={l.url} href={l.url} className="px-3 py-1 bg-secondary rounded text-xs">{l.name}</a>
|
|
136
|
+
))}
|
|
137
|
+
</nav>
|
|
138
|
+
)}
|
|
139
|
+
</article>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
95
142
|
```
|
|
96
143
|
|
|
97
|
-
### 3.
|
|
144
|
+
### 3. XML Sitemap & `/llms.txt` Feeds (1 Line of Code)
|
|
98
145
|
|
|
99
146
|
```typescript
|
|
100
|
-
|
|
147
|
+
// app/sitemap.ts
|
|
148
|
+
export default function sitemap() {
|
|
149
|
+
const domain = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";
|
|
150
|
+
return matrixEngine.generateAllMatrices(domain, PSEO_DATASET, SEO_CONFIG)
|
|
151
|
+
.filter(p => p.robots.includes("index"))
|
|
152
|
+
.map(p => ({ url: p.canonicalUrl, lastModified: new Date() }));
|
|
153
|
+
}
|
|
154
|
+
```
|
|
101
155
|
|
|
102
156
|
const slug = cleanSeoSlug("The best software for managing client invoices in 2026", { language: "en" });
|
|
103
157
|
console.log(slug); // "best-software-managing-client-invoices"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🌍 Built-in Global Geographic & Demographic Database
|
|
3
|
+
* Automatically provisions verified cities, populations, currencies, and coordinates
|
|
4
|
+
* across Europe, North America, and international markets without requiring the user to list cities manually.
|
|
5
|
+
*/
|
|
6
|
+
import { PseoLocation } from "./matrix-engine";
|
|
7
|
+
export declare const BUILT_IN_LOCATIONS_DATABASE: Record<string, PseoLocation[]>;
|
|
8
|
+
/**
|
|
9
|
+
* Resolves built-in locations by country code or territory keyword.
|
|
10
|
+
* Supports: "fr", "france", "es", "spain", "de", "germany", "uk", "us", "be", "ch", "it", "ca", "europe", "international", "all"
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveBuiltInLocations(territories?: string[] | string): PseoLocation[];
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./engine";
|
|
|
13
13
|
export * from "./slug-engine";
|
|
14
14
|
export * from "./legal-disclaimers";
|
|
15
15
|
export * from "./matrix-engine";
|
|
16
|
+
export * from "./built-in-locations";
|
|
16
17
|
export * from "./brand-icons";
|
|
17
18
|
export * from "./urlytics-engine";
|
|
18
19
|
export * from "./keyword-permutator";
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,7 @@ var __export = (target, all) => {
|
|
|
40
40
|
var exports_src = {};
|
|
41
41
|
__export(exports_src, {
|
|
42
42
|
validateSeoSlug: () => validateSeoSlug,
|
|
43
|
+
resolveBuiltInLocations: () => resolveBuiltInLocations,
|
|
43
44
|
resolveBrandIcon: () => resolveBrandIcon,
|
|
44
45
|
renderBrandIconSvg: () => renderBrandIconSvg,
|
|
45
46
|
getSeoAgentPrompt: () => getSeoAgentPrompt,
|
|
@@ -93,6 +94,7 @@ __export(exports_src, {
|
|
|
93
94
|
CopywritingFrameworksMaster: () => CopywritingFrameworksMaster,
|
|
94
95
|
BrandDnaCalendarEngine: () => BrandDnaCalendarEngine,
|
|
95
96
|
BacklinksClient: () => BacklinksClient,
|
|
97
|
+
BUILT_IN_LOCATIONS_DATABASE: () => BUILT_IN_LOCATIONS_DATABASE,
|
|
96
98
|
BRAND_ICONS: () => BRAND_ICONS,
|
|
97
99
|
ApiKeyGuardian: () => ApiKeyGuardian,
|
|
98
100
|
AiCopilotClient: () => AiCopilotClient,
|
|
@@ -3190,6 +3192,150 @@ class ExtendedSchemaGraphBuilder {
|
|
|
3190
3192
|
}
|
|
3191
3193
|
}
|
|
3192
3194
|
|
|
3195
|
+
// src/built-in-locations.ts
|
|
3196
|
+
var BUILT_IN_LOCATIONS_DATABASE = {
|
|
3197
|
+
fr: [
|
|
3198
|
+
{ slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2161000, currency: "EUR", currencySymbol: "€", latitude: 48.8566, longitude: 2.3522, neighborSlugs: ["boulogne-billancourt", "saint-denis", "montreuil"] },
|
|
3199
|
+
{ slug: "marseille", name: "Marseille", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 873000, currency: "EUR", currencySymbol: "€", latitude: 43.2965, longitude: 5.3698, neighborSlugs: ["aix-en-provence", "aubagne", "toulon"] },
|
|
3200
|
+
{ slug: "lyon", name: "Lyon", region: "Auvergne-Rhône-Alpes", country: "France", population: 522000, currency: "EUR", currencySymbol: "€", latitude: 45.764, longitude: 4.8357, neighborSlugs: ["villeurbanne", "venissieux", "saint-etienne"] },
|
|
3201
|
+
{ slug: "toulouse", name: "Toulouse", region: "Occitanie", country: "France", population: 498000, currency: "EUR", currencySymbol: "€", latitude: 43.6047, longitude: 1.4442, neighborSlugs: ["montauban", "albi", "colomiers"] },
|
|
3202
|
+
{ slug: "nice", name: "Nice", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 343000, currency: "EUR", currencySymbol: "€", latitude: 43.7102, longitude: 7.262, neighborSlugs: ["cannes", "antibes", "monaco"] },
|
|
3203
|
+
{ slug: "nantes", name: "Nantes", region: "Pays de la Loire", country: "France", population: 320000, currency: "EUR", currencySymbol: "€", latitude: 47.2184, longitude: -1.5536, neighborSlugs: ["saint-nazaire", "angers", "rennes"] },
|
|
3204
|
+
{ slug: "montpellier", name: "Montpellier", region: "Occitanie", country: "France", population: 299000, currency: "EUR", currencySymbol: "€", latitude: 43.6108, longitude: 3.8767, neighborSlugs: ["nimes", "beziers", "sete"] },
|
|
3205
|
+
{ slug: "strasbourg", name: "Strasbourg", region: "Grand Est", country: "France", population: 290000, currency: "EUR", currencySymbol: "€", latitude: 48.5734, longitude: 7.7521, neighborSlugs: ["mulhouse", "colmar", "metz"] },
|
|
3206
|
+
{ slug: "bordeaux", name: "Bordeaux", region: "Nouvelle-Aquitaine", country: "France", population: 260000, currency: "EUR", currencySymbol: "€", latitude: 44.8378, longitude: -0.5792, neighborSlugs: ["merignac", "pessac", "arcachon"] },
|
|
3207
|
+
{ slug: "lille", name: "Lille", region: "Hauts-de-France", country: "France", population: 236000, currency: "EUR", currencySymbol: "€", latitude: 50.6292, longitude: 3.0573, neighborSlugs: ["roubaix", "tourcoing", "villeneuve-d-ascq"] },
|
|
3208
|
+
{ slug: "rennes", name: "Rennes", region: "Bretagne", country: "France", population: 222000, currency: "EUR", currencySymbol: "€", latitude: 48.1173, longitude: -1.6778, neighborSlugs: ["saint-malo", "brest", "nantes"] },
|
|
3209
|
+
{ slug: "toulon", name: "Toulon", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 179000, currency: "EUR", currencySymbol: "€", latitude: 43.1242, longitude: 5.928, neighborSlugs: ["hyeres", "la-seyne-sur-mer", "marseille"] },
|
|
3210
|
+
{ slug: "reims", name: "Reims", region: "Grand Est", country: "France", population: 179000, currency: "EUR", currencySymbol: "€", latitude: 49.2583, longitude: 4.0317, neighborSlugs: ["chalons-en-champagne", "troyes", "metz"] },
|
|
3211
|
+
{ slug: "saint-etienne", name: "Saint-Étienne", region: "Auvergne-Rhône-Alpes", country: "France", population: 174000, currency: "EUR", currencySymbol: "€", latitude: 45.4397, longitude: 4.3872, neighborSlugs: ["lyon", "roanne", "saint-chamond"] },
|
|
3212
|
+
{ slug: "le-havre", name: "Le Havre", region: "Normandie", country: "France", population: 166000, currency: "EUR", currencySymbol: "€", latitude: 49.4944, longitude: 0.1079, neighborSlugs: ["rouen", "caen", "honfleur"] },
|
|
3213
|
+
{ slug: "grenoble", name: "Grenoble", region: "Auvergne-Rhône-Alpes", country: "France", population: 158000, currency: "EUR", currencySymbol: "€", latitude: 45.1885, longitude: 5.7245, neighborSlugs: ["echirolles", "chambery", "annecy"] },
|
|
3214
|
+
{ slug: "dijon", name: "Dijon", region: "Bourgogne-Franche-Comté", country: "France", population: 159000, currency: "EUR", currencySymbol: "€", latitude: 47.322, longitude: 5.0415, neighborSlugs: ["besancon", "chalon-sur-saone", "beaune"] },
|
|
3215
|
+
{ slug: "angers", name: "Angers", region: "Pays de la Loire", country: "France", population: 155000, currency: "EUR", currencySymbol: "€", latitude: 47.4784, longitude: -0.5632, neighborSlugs: ["saumur", "cholet", "nantes"] },
|
|
3216
|
+
{ slug: "villeurbanne", name: "Villeurbanne", region: "Auvergne-Rhône-Alpes", country: "France", population: 154000, currency: "EUR", currencySymbol: "€", latitude: 45.7667, longitude: 4.8833, neighborSlugs: ["lyon", "bron", "caluire-et-cuire"] },
|
|
3217
|
+
{ slug: "nimes", name: "Nîmes", region: "Occitanie", country: "France", population: 148000, currency: "EUR", currencySymbol: "€", latitude: 43.8367, longitude: 4.3601, neighborSlugs: ["montpellier", "avignon", "arles"] },
|
|
3218
|
+
{ slug: "aix-en-provence", name: "Aix-en-Provence", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 147000, currency: "EUR", currencySymbol: "€", latitude: 43.5297, longitude: 5.4474, neighborSlugs: ["marseille", "gardanne", "salon-de-provence"] },
|
|
3219
|
+
{ slug: "clermont-ferrand", name: "Clermont-Ferrand", region: "Auvergne-Rhône-Alpes", country: "France", population: 147000, currency: "EUR", currencySymbol: "€", latitude: 45.7772, longitude: 3.087, neighborSlugs: ["riom", "cournon-d-auvergne", "vichy"] },
|
|
3220
|
+
{ slug: "brest", name: "Brest", region: "Bretagne", country: "France", population: 139000, currency: "EUR", currencySymbol: "€", latitude: 48.3904, longitude: -4.4861, neighborSlugs: ["quimper", "morlaix", "rennes"] },
|
|
3221
|
+
{ slug: "tours", name: "Tours", region: "Centre-Val de Loire", country: "France", population: 137000, currency: "EUR", currencySymbol: "€", latitude: 47.3941, longitude: 0.6848, neighborSlugs: ["orleans", "blois", "angers"] },
|
|
3222
|
+
{ slug: "amiens", name: "Amiens", region: "Hauts-de-France", country: "France", population: 134000, currency: "EUR", currencySymbol: "€", latitude: 49.8941, longitude: 2.2958, neighborSlugs: ["beauvais", "compiegne", "lille"] },
|
|
3223
|
+
{ slug: "annecy", name: "Annecy", region: "Auvergne-Rhône-Alpes", country: "France", population: 131000, currency: "EUR", currencySymbol: "€", latitude: 45.8992, longitude: 6.1294, neighborSlugs: ["chambery", "geneve", "grenoble"] }
|
|
3224
|
+
],
|
|
3225
|
+
es: [
|
|
3226
|
+
{ slug: "madrid", name: "Madrid", region: "Comunidad de Madrid", country: "Spain", population: 3300000, currency: "EUR", currencySymbol: "€", latitude: 40.4168, longitude: -3.7038, neighborSlugs: ["alcala-de-henares", "mostoles", "getafe"] },
|
|
3227
|
+
{ slug: "barcelona", name: "Barcelona", region: "Catalunya", country: "Spain", population: 1636000, currency: "EUR", currencySymbol: "€", latitude: 41.3851, longitude: 2.1734, neighborSlugs: ["hospitalet-de-llobregat", "badalona", "terrassa"] },
|
|
3228
|
+
{ slug: "valencia", name: "Valencia", region: "Comunitat Valenciana", country: "Spain", population: 800000, currency: "EUR", currencySymbol: "€", latitude: 39.4699, longitude: -0.3763, neighborSlugs: ["alicante", "castellon-de-la-plana", "gandia"] },
|
|
3229
|
+
{ slug: "sevilla", name: "Sevilla", region: "Andalucía", country: "Spain", population: 688000, currency: "EUR", currencySymbol: "€", latitude: 37.3891, longitude: -5.9845, neighborSlugs: ["cordoba", "huelva", "cadiz"] },
|
|
3230
|
+
{ slug: "zaragoza", name: "Zaragoza", region: "Aragón", country: "Spain", population: 675000, currency: "EUR", currencySymbol: "€", latitude: 41.6488, longitude: -0.8891, neighborSlugs: ["huesca", "teruel", "madrid"] },
|
|
3231
|
+
{ slug: "malaga", name: "Málaga", region: "Andalucía", country: "Spain", population: 579000, currency: "EUR", currencySymbol: "€", latitude: 36.7213, longitude: -4.4214, neighborSlugs: ["marbella", "torremolinos", "granada"] },
|
|
3232
|
+
{ slug: "bilbao", name: "Bilbao", region: "País Vasco", country: "Spain", population: 346000, currency: "EUR", currencySymbol: "€", latitude: 43.263, longitude: -2.935, neighborSlugs: ["san-sebastian", "vitoria-gasteiz", "santander"] }
|
|
3233
|
+
],
|
|
3234
|
+
de: [
|
|
3235
|
+
{ slug: "berlin", name: "Berlin", region: "Berlin", country: "Germany", population: 3645000, currency: "EUR", currencySymbol: "€", latitude: 52.52, longitude: 13.405, neighborSlugs: ["potsdam", "cottbus", "brandenburg"] },
|
|
3236
|
+
{ slug: "hamburg", name: "Hamburg", region: "Hamburg", country: "Germany", population: 1841000, currency: "EUR", currencySymbol: "€", latitude: 53.5511, longitude: 9.9937, neighborSlugs: ["luebeck", "bremen", "kiel"] },
|
|
3237
|
+
{ slug: "muenchen", name: "München", region: "Bayern", country: "Germany", population: 1472000, currency: "EUR", currencySymbol: "€", latitude: 48.1351, longitude: 11.582, neighborSlugs: ["augsburg", "ingolstadt", "rosenheim"] },
|
|
3238
|
+
{ slug: "koeln", name: "Köln", region: "Nordrhein-Westfalen", country: "Germany", population: 1086000, currency: "EUR", currencySymbol: "€", latitude: 50.9375, longitude: 6.9603, neighborSlugs: ["duesseldorf", "bonn", "leverkusen"] },
|
|
3239
|
+
{ slug: "frankfurt", name: "Frankfurt am Main", region: "Hessen", country: "Germany", population: 753000, currency: "EUR", currencySymbol: "€", latitude: 50.1109, longitude: 8.6821, neighborSlugs: ["wiesbaden", "mainz", "darmstadt"] },
|
|
3240
|
+
{ slug: "stuttgart", name: "Stuttgart", region: "Baden-Württemberg", country: "Germany", population: 635000, currency: "EUR", currencySymbol: "€", latitude: 48.7758, longitude: 9.1829, neighborSlugs: ["esslingen", "ludwigsburg", "karlsruhe"] }
|
|
3241
|
+
],
|
|
3242
|
+
uk: [
|
|
3243
|
+
{ slug: "london", name: "London", region: "Greater London", country: "United Kingdom", population: 8982000, currency: "GBP", currencySymbol: "£", latitude: 51.5074, longitude: -0.1278, neighborSlugs: ["croydon", "watford", "reading"] },
|
|
3244
|
+
{ slug: "birmingham", name: "Birmingham", region: "West Midlands", country: "United Kingdom", population: 1149000, currency: "GBP", currencySymbol: "£", latitude: 52.4862, longitude: -1.8904, neighborSlugs: ["coventry", "wolverhampton", "solihull"] },
|
|
3245
|
+
{ slug: "manchester", name: "Manchester", region: "Greater Manchester", country: "United Kingdom", population: 553000, currency: "GBP", currencySymbol: "£", latitude: 53.4808, longitude: -2.2426, neighborSlugs: ["salford", "bolton", "stockport"] },
|
|
3246
|
+
{ slug: "edinburgh", name: "Edinburgh", region: "Scotland", country: "United Kingdom", population: 527000, currency: "GBP", currencySymbol: "£", latitude: 55.9533, longitude: -3.1883, neighborSlugs: ["glasgow", "livingston", "dundee"] }
|
|
3247
|
+
],
|
|
3248
|
+
us: [
|
|
3249
|
+
{ slug: "new-york", name: "New York", region: "NY", country: "United States", population: 8336000, currency: "USD", currencySymbol: "$", latitude: 40.7128, longitude: -74.006, neighborSlugs: ["brooklyn", "queens", "jersey-city"] },
|
|
3250
|
+
{ slug: "los-angeles", name: "Los Angeles", region: "CA", country: "United States", population: 3979000, currency: "USD", currencySymbol: "$", latitude: 34.0522, longitude: -118.2437, neighborSlugs: ["long-beach", "pasadena", "glendale"] },
|
|
3251
|
+
{ slug: "chicago", name: "Chicago", region: "IL", country: "United States", population: 2693000, currency: "USD", currencySymbol: "$", latitude: 41.8781, longitude: -87.6298, neighborSlugs: ["evanston", "naperville", "aurora"] },
|
|
3252
|
+
{ slug: "houston", name: "Houston", region: "TX", country: "United States", population: 2320000, currency: "USD", currencySymbol: "$", latitude: 29.7604, longitude: -95.3698, neighborSlugs: ["the-woodlands", "sugar-land", "katy"] },
|
|
3253
|
+
{ slug: "miami", name: "Miami", region: "FL", country: "United States", population: 442000, currency: "USD", currencySymbol: "$", latitude: 25.7617, longitude: -80.1918, neighborSlugs: ["fort-lauderdale", "miami-beach", "hialeah"] },
|
|
3254
|
+
{ slug: "san-francisco", name: "San Francisco", region: "CA", country: "United States", population: 873000, currency: "USD", currencySymbol: "$", latitude: 37.7749, longitude: -122.4194, neighborSlugs: ["oakland", "san-jose", "berkeley"] }
|
|
3255
|
+
],
|
|
3256
|
+
be: [
|
|
3257
|
+
{ slug: "bruxelles", name: "Bruxelles", region: "Bruxelles-Capitale", country: "Belgium", population: 1220000, currency: "EUR", currencySymbol: "€", latitude: 50.8503, longitude: 4.3517, neighborSlugs: ["anvers", "gand", "liege"] },
|
|
3258
|
+
{ slug: "anvers", name: "Anvers", region: "Flandre", country: "Belgium", population: 530000, currency: "EUR", currencySymbol: "€", latitude: 51.2194, longitude: 4.4025, neighborSlugs: ["bruxelles", "malines", "gand"] },
|
|
3259
|
+
{ slug: "liege", name: "Liège", region: "Wallonie", country: "Belgium", population: 197000, currency: "EUR", currencySymbol: "€", latitude: 50.6326, longitude: 5.5797, neighborSlugs: ["namur", "charleroi", "bruxelles"] }
|
|
3260
|
+
],
|
|
3261
|
+
ch: [
|
|
3262
|
+
{ slug: "zurich", name: "Zürich", region: "Zürich", country: "Switzerland", population: 434000, currency: "CHF", currencySymbol: "CHF", latitude: 47.3769, longitude: 8.5417, neighborSlugs: ["bale", "berne", "lucerne"] },
|
|
3263
|
+
{ slug: "geneve", name: "Genève", region: "Genève", country: "Switzerland", population: 203000, currency: "EUR", currencySymbol: "CHF", latitude: 46.2044, longitude: 6.1432, neighborSlugs: ["lausanne", "annecy", "nyon"] },
|
|
3264
|
+
{ slug: "lausanne", name: "Lausanne", region: "Vaud", country: "Switzerland", population: 140000, currency: "CHF", currencySymbol: "CHF", latitude: 46.5197, longitude: 6.6323, neighborSlugs: ["geneve", "montreux", "vevey"] }
|
|
3265
|
+
],
|
|
3266
|
+
it: [
|
|
3267
|
+
{ slug: "roma", name: "Roma", region: "Lazio", country: "Italy", population: 2873000, currency: "EUR", currencySymbol: "€", latitude: 41.9028, longitude: 12.4964, neighborSlugs: ["latina", "fiumicino", "tivoli"] },
|
|
3268
|
+
{ slug: "milano", name: "Milano", region: "Lombardia", country: "Italy", population: 1366000, currency: "EUR", currencySymbol: "€", latitude: 45.4642, longitude: 9.19, neighborSlugs: ["monza", "bergamo", "brescia"] },
|
|
3269
|
+
{ slug: "torino", name: "Torino", region: "Piemonte", country: "Italy", population: 870000, currency: "EUR", currencySymbol: "€", latitude: 45.0703, longitude: 7.6869, neighborSlugs: ["novara", "alessandria", "asti"] }
|
|
3270
|
+
],
|
|
3271
|
+
ca: [
|
|
3272
|
+
{ slug: "toronto", name: "Toronto", region: "ON", country: "Canada", population: 2794000, currency: "CAD", currencySymbol: "$", latitude: 43.6532, longitude: -79.3832, neighborSlugs: ["mississauga", "brampton", "markham"] },
|
|
3273
|
+
{ slug: "montreal", name: "Montréal", region: "QC", country: "Canada", population: 1762000, currency: "CAD", currencySymbol: "$", latitude: 45.5017, longitude: -73.5673, neighborSlugs: ["laval", "longueuil", "quebec"] },
|
|
3274
|
+
{ slug: "vancouver", name: "Vancouver", region: "BC", country: "Canada", population: 662000, currency: "CAD", currencySymbol: "$", latitude: 49.2827, longitude: -123.1207, neighborSlugs: ["burnaby", "richmond", "surrey"] }
|
|
3275
|
+
]
|
|
3276
|
+
};
|
|
3277
|
+
function resolveBuiltInLocations(territories) {
|
|
3278
|
+
if (!territories) {
|
|
3279
|
+
return [...BUILT_IN_LOCATIONS_DATABASE.fr];
|
|
3280
|
+
}
|
|
3281
|
+
const requested = (Array.isArray(territories) ? territories : [territories]).map((t) => t.toLowerCase().trim());
|
|
3282
|
+
const results = [];
|
|
3283
|
+
const addedSlugs = new Set;
|
|
3284
|
+
const isAllOrEurope = requested.some((t) => ["europe", "international", "all", "global", "world"].includes(t));
|
|
3285
|
+
if (isAllOrEurope) {
|
|
3286
|
+
for (const list of Object.values(BUILT_IN_LOCATIONS_DATABASE)) {
|
|
3287
|
+
for (const loc of list) {
|
|
3288
|
+
if (!addedSlugs.has(loc.slug)) {
|
|
3289
|
+
addedSlugs.add(loc.slug);
|
|
3290
|
+
results.push(loc);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
return results;
|
|
3295
|
+
}
|
|
3296
|
+
const countryAliasMap = {
|
|
3297
|
+
france: "fr",
|
|
3298
|
+
fr: "fr",
|
|
3299
|
+
spain: "es",
|
|
3300
|
+
espagne: "es",
|
|
3301
|
+
es: "es",
|
|
3302
|
+
germany: "de",
|
|
3303
|
+
allemagne: "de",
|
|
3304
|
+
de: "de",
|
|
3305
|
+
uk: "uk",
|
|
3306
|
+
gb: "uk",
|
|
3307
|
+
"united kingdom": "uk",
|
|
3308
|
+
angleterre: "uk",
|
|
3309
|
+
us: "us",
|
|
3310
|
+
usa: "us",
|
|
3311
|
+
"united states": "us",
|
|
3312
|
+
etatsunis: "us",
|
|
3313
|
+
belgium: "be",
|
|
3314
|
+
belgique: "be",
|
|
3315
|
+
be: "be",
|
|
3316
|
+
switzerland: "ch",
|
|
3317
|
+
suisse: "ch",
|
|
3318
|
+
ch: "ch",
|
|
3319
|
+
italy: "it",
|
|
3320
|
+
italie: "it",
|
|
3321
|
+
it: "it",
|
|
3322
|
+
canada: "ca",
|
|
3323
|
+
ca: "ca"
|
|
3324
|
+
};
|
|
3325
|
+
for (const req of requested) {
|
|
3326
|
+
const code = countryAliasMap[req.replace(/[^a-z0-9]/g, "")];
|
|
3327
|
+
if (code && BUILT_IN_LOCATIONS_DATABASE[code]) {
|
|
3328
|
+
for (const loc of BUILT_IN_LOCATIONS_DATABASE[code]) {
|
|
3329
|
+
if (!addedSlugs.has(loc.slug)) {
|
|
3330
|
+
addedSlugs.add(loc.slug);
|
|
3331
|
+
results.push(loc);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3193
3339
|
// src/matrix-engine.ts
|
|
3194
3340
|
class PseoMatrixEngine {
|
|
3195
3341
|
legalEngine;
|
|
@@ -3667,8 +3813,9 @@ class PseoMatrixEngine {
|
|
|
3667
3813
|
}
|
|
3668
3814
|
generateAllMatrices(domain, data, options) {
|
|
3669
3815
|
const allPages = [];
|
|
3670
|
-
if (data.services
|
|
3671
|
-
|
|
3816
|
+
if (data.services) {
|
|
3817
|
+
const locs = data.locations && data.locations.length > 0 ? data.locations : resolveBuiltInLocations(options.countries || options.territories || options.language || "fr");
|
|
3818
|
+
allPages.push(...this.generateLocalGeoMatrix(domain, data.services, locs, options));
|
|
3672
3819
|
}
|
|
3673
3820
|
if (data.competitors) {
|
|
3674
3821
|
allPages.push(...this.generateVsMatrix(domain, data.competitors, options));
|
|
@@ -4067,21 +4214,19 @@ export const matrixEngine = new PseoMatrixEngine();
|
|
|
4067
4214
|
|
|
4068
4215
|
export const SEO_CONFIG = {
|
|
4069
4216
|
brandName: "Acme",
|
|
4070
|
-
language: "
|
|
4217
|
+
language: "fr",
|
|
4218
|
+
countries: ["france", "belgique", "suisse"], // Or "europe" / "international". The SDK automatically provides verified cities!
|
|
4071
4219
|
cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
|
|
4072
4220
|
minPopulationToIndex: 15_000,
|
|
4073
4221
|
};
|
|
4074
4222
|
|
|
4075
4223
|
// Discovered EXCLUSIVELY from Frontend UI components
|
|
4224
|
+
// NOTE: DO NOT list cities manually! The SDK auto-provisions them based on your target countries/scope.
|
|
4076
4225
|
export const PSEO_DATASET = {
|
|
4077
4226
|
services: [
|
|
4078
4227
|
// 1 per major frontend feature branch
|
|
4079
4228
|
{ slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
|
|
4080
4229
|
],
|
|
4081
|
-
locations: [
|
|
4082
|
-
{ slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2_161_000, currency: "EUR", currencySymbol: "€" },
|
|
4083
|
-
{ slug: "lyon", name: "Lyon", region: "Rhône-Alpes", country: "France", population: 522_000, currency: "EUR", currencySymbol: "€" },
|
|
4084
|
-
],
|
|
4085
4230
|
targets: [
|
|
4086
4231
|
{ slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
|
|
4087
4232
|
],
|
package/dist/index.mjs
CHANGED
|
@@ -3087,6 +3087,150 @@ class ExtendedSchemaGraphBuilder {
|
|
|
3087
3087
|
}
|
|
3088
3088
|
}
|
|
3089
3089
|
|
|
3090
|
+
// src/built-in-locations.ts
|
|
3091
|
+
var BUILT_IN_LOCATIONS_DATABASE = {
|
|
3092
|
+
fr: [
|
|
3093
|
+
{ slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2161000, currency: "EUR", currencySymbol: "€", latitude: 48.8566, longitude: 2.3522, neighborSlugs: ["boulogne-billancourt", "saint-denis", "montreuil"] },
|
|
3094
|
+
{ slug: "marseille", name: "Marseille", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 873000, currency: "EUR", currencySymbol: "€", latitude: 43.2965, longitude: 5.3698, neighborSlugs: ["aix-en-provence", "aubagne", "toulon"] },
|
|
3095
|
+
{ slug: "lyon", name: "Lyon", region: "Auvergne-Rhône-Alpes", country: "France", population: 522000, currency: "EUR", currencySymbol: "€", latitude: 45.764, longitude: 4.8357, neighborSlugs: ["villeurbanne", "venissieux", "saint-etienne"] },
|
|
3096
|
+
{ slug: "toulouse", name: "Toulouse", region: "Occitanie", country: "France", population: 498000, currency: "EUR", currencySymbol: "€", latitude: 43.6047, longitude: 1.4442, neighborSlugs: ["montauban", "albi", "colomiers"] },
|
|
3097
|
+
{ slug: "nice", name: "Nice", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 343000, currency: "EUR", currencySymbol: "€", latitude: 43.7102, longitude: 7.262, neighborSlugs: ["cannes", "antibes", "monaco"] },
|
|
3098
|
+
{ slug: "nantes", name: "Nantes", region: "Pays de la Loire", country: "France", population: 320000, currency: "EUR", currencySymbol: "€", latitude: 47.2184, longitude: -1.5536, neighborSlugs: ["saint-nazaire", "angers", "rennes"] },
|
|
3099
|
+
{ slug: "montpellier", name: "Montpellier", region: "Occitanie", country: "France", population: 299000, currency: "EUR", currencySymbol: "€", latitude: 43.6108, longitude: 3.8767, neighborSlugs: ["nimes", "beziers", "sete"] },
|
|
3100
|
+
{ slug: "strasbourg", name: "Strasbourg", region: "Grand Est", country: "France", population: 290000, currency: "EUR", currencySymbol: "€", latitude: 48.5734, longitude: 7.7521, neighborSlugs: ["mulhouse", "colmar", "metz"] },
|
|
3101
|
+
{ slug: "bordeaux", name: "Bordeaux", region: "Nouvelle-Aquitaine", country: "France", population: 260000, currency: "EUR", currencySymbol: "€", latitude: 44.8378, longitude: -0.5792, neighborSlugs: ["merignac", "pessac", "arcachon"] },
|
|
3102
|
+
{ slug: "lille", name: "Lille", region: "Hauts-de-France", country: "France", population: 236000, currency: "EUR", currencySymbol: "€", latitude: 50.6292, longitude: 3.0573, neighborSlugs: ["roubaix", "tourcoing", "villeneuve-d-ascq"] },
|
|
3103
|
+
{ slug: "rennes", name: "Rennes", region: "Bretagne", country: "France", population: 222000, currency: "EUR", currencySymbol: "€", latitude: 48.1173, longitude: -1.6778, neighborSlugs: ["saint-malo", "brest", "nantes"] },
|
|
3104
|
+
{ slug: "toulon", name: "Toulon", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 179000, currency: "EUR", currencySymbol: "€", latitude: 43.1242, longitude: 5.928, neighborSlugs: ["hyeres", "la-seyne-sur-mer", "marseille"] },
|
|
3105
|
+
{ slug: "reims", name: "Reims", region: "Grand Est", country: "France", population: 179000, currency: "EUR", currencySymbol: "€", latitude: 49.2583, longitude: 4.0317, neighborSlugs: ["chalons-en-champagne", "troyes", "metz"] },
|
|
3106
|
+
{ slug: "saint-etienne", name: "Saint-Étienne", region: "Auvergne-Rhône-Alpes", country: "France", population: 174000, currency: "EUR", currencySymbol: "€", latitude: 45.4397, longitude: 4.3872, neighborSlugs: ["lyon", "roanne", "saint-chamond"] },
|
|
3107
|
+
{ slug: "le-havre", name: "Le Havre", region: "Normandie", country: "France", population: 166000, currency: "EUR", currencySymbol: "€", latitude: 49.4944, longitude: 0.1079, neighborSlugs: ["rouen", "caen", "honfleur"] },
|
|
3108
|
+
{ slug: "grenoble", name: "Grenoble", region: "Auvergne-Rhône-Alpes", country: "France", population: 158000, currency: "EUR", currencySymbol: "€", latitude: 45.1885, longitude: 5.7245, neighborSlugs: ["echirolles", "chambery", "annecy"] },
|
|
3109
|
+
{ slug: "dijon", name: "Dijon", region: "Bourgogne-Franche-Comté", country: "France", population: 159000, currency: "EUR", currencySymbol: "€", latitude: 47.322, longitude: 5.0415, neighborSlugs: ["besancon", "chalon-sur-saone", "beaune"] },
|
|
3110
|
+
{ slug: "angers", name: "Angers", region: "Pays de la Loire", country: "France", population: 155000, currency: "EUR", currencySymbol: "€", latitude: 47.4784, longitude: -0.5632, neighborSlugs: ["saumur", "cholet", "nantes"] },
|
|
3111
|
+
{ slug: "villeurbanne", name: "Villeurbanne", region: "Auvergne-Rhône-Alpes", country: "France", population: 154000, currency: "EUR", currencySymbol: "€", latitude: 45.7667, longitude: 4.8833, neighborSlugs: ["lyon", "bron", "caluire-et-cuire"] },
|
|
3112
|
+
{ slug: "nimes", name: "Nîmes", region: "Occitanie", country: "France", population: 148000, currency: "EUR", currencySymbol: "€", latitude: 43.8367, longitude: 4.3601, neighborSlugs: ["montpellier", "avignon", "arles"] },
|
|
3113
|
+
{ slug: "aix-en-provence", name: "Aix-en-Provence", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 147000, currency: "EUR", currencySymbol: "€", latitude: 43.5297, longitude: 5.4474, neighborSlugs: ["marseille", "gardanne", "salon-de-provence"] },
|
|
3114
|
+
{ slug: "clermont-ferrand", name: "Clermont-Ferrand", region: "Auvergne-Rhône-Alpes", country: "France", population: 147000, currency: "EUR", currencySymbol: "€", latitude: 45.7772, longitude: 3.087, neighborSlugs: ["riom", "cournon-d-auvergne", "vichy"] },
|
|
3115
|
+
{ slug: "brest", name: "Brest", region: "Bretagne", country: "France", population: 139000, currency: "EUR", currencySymbol: "€", latitude: 48.3904, longitude: -4.4861, neighborSlugs: ["quimper", "morlaix", "rennes"] },
|
|
3116
|
+
{ slug: "tours", name: "Tours", region: "Centre-Val de Loire", country: "France", population: 137000, currency: "EUR", currencySymbol: "€", latitude: 47.3941, longitude: 0.6848, neighborSlugs: ["orleans", "blois", "angers"] },
|
|
3117
|
+
{ slug: "amiens", name: "Amiens", region: "Hauts-de-France", country: "France", population: 134000, currency: "EUR", currencySymbol: "€", latitude: 49.8941, longitude: 2.2958, neighborSlugs: ["beauvais", "compiegne", "lille"] },
|
|
3118
|
+
{ slug: "annecy", name: "Annecy", region: "Auvergne-Rhône-Alpes", country: "France", population: 131000, currency: "EUR", currencySymbol: "€", latitude: 45.8992, longitude: 6.1294, neighborSlugs: ["chambery", "geneve", "grenoble"] }
|
|
3119
|
+
],
|
|
3120
|
+
es: [
|
|
3121
|
+
{ slug: "madrid", name: "Madrid", region: "Comunidad de Madrid", country: "Spain", population: 3300000, currency: "EUR", currencySymbol: "€", latitude: 40.4168, longitude: -3.7038, neighborSlugs: ["alcala-de-henares", "mostoles", "getafe"] },
|
|
3122
|
+
{ slug: "barcelona", name: "Barcelona", region: "Catalunya", country: "Spain", population: 1636000, currency: "EUR", currencySymbol: "€", latitude: 41.3851, longitude: 2.1734, neighborSlugs: ["hospitalet-de-llobregat", "badalona", "terrassa"] },
|
|
3123
|
+
{ slug: "valencia", name: "Valencia", region: "Comunitat Valenciana", country: "Spain", population: 800000, currency: "EUR", currencySymbol: "€", latitude: 39.4699, longitude: -0.3763, neighborSlugs: ["alicante", "castellon-de-la-plana", "gandia"] },
|
|
3124
|
+
{ slug: "sevilla", name: "Sevilla", region: "Andalucía", country: "Spain", population: 688000, currency: "EUR", currencySymbol: "€", latitude: 37.3891, longitude: -5.9845, neighborSlugs: ["cordoba", "huelva", "cadiz"] },
|
|
3125
|
+
{ slug: "zaragoza", name: "Zaragoza", region: "Aragón", country: "Spain", population: 675000, currency: "EUR", currencySymbol: "€", latitude: 41.6488, longitude: -0.8891, neighborSlugs: ["huesca", "teruel", "madrid"] },
|
|
3126
|
+
{ slug: "malaga", name: "Málaga", region: "Andalucía", country: "Spain", population: 579000, currency: "EUR", currencySymbol: "€", latitude: 36.7213, longitude: -4.4214, neighborSlugs: ["marbella", "torremolinos", "granada"] },
|
|
3127
|
+
{ slug: "bilbao", name: "Bilbao", region: "País Vasco", country: "Spain", population: 346000, currency: "EUR", currencySymbol: "€", latitude: 43.263, longitude: -2.935, neighborSlugs: ["san-sebastian", "vitoria-gasteiz", "santander"] }
|
|
3128
|
+
],
|
|
3129
|
+
de: [
|
|
3130
|
+
{ slug: "berlin", name: "Berlin", region: "Berlin", country: "Germany", population: 3645000, currency: "EUR", currencySymbol: "€", latitude: 52.52, longitude: 13.405, neighborSlugs: ["potsdam", "cottbus", "brandenburg"] },
|
|
3131
|
+
{ slug: "hamburg", name: "Hamburg", region: "Hamburg", country: "Germany", population: 1841000, currency: "EUR", currencySymbol: "€", latitude: 53.5511, longitude: 9.9937, neighborSlugs: ["luebeck", "bremen", "kiel"] },
|
|
3132
|
+
{ slug: "muenchen", name: "München", region: "Bayern", country: "Germany", population: 1472000, currency: "EUR", currencySymbol: "€", latitude: 48.1351, longitude: 11.582, neighborSlugs: ["augsburg", "ingolstadt", "rosenheim"] },
|
|
3133
|
+
{ slug: "koeln", name: "Köln", region: "Nordrhein-Westfalen", country: "Germany", population: 1086000, currency: "EUR", currencySymbol: "€", latitude: 50.9375, longitude: 6.9603, neighborSlugs: ["duesseldorf", "bonn", "leverkusen"] },
|
|
3134
|
+
{ slug: "frankfurt", name: "Frankfurt am Main", region: "Hessen", country: "Germany", population: 753000, currency: "EUR", currencySymbol: "€", latitude: 50.1109, longitude: 8.6821, neighborSlugs: ["wiesbaden", "mainz", "darmstadt"] },
|
|
3135
|
+
{ slug: "stuttgart", name: "Stuttgart", region: "Baden-Württemberg", country: "Germany", population: 635000, currency: "EUR", currencySymbol: "€", latitude: 48.7758, longitude: 9.1829, neighborSlugs: ["esslingen", "ludwigsburg", "karlsruhe"] }
|
|
3136
|
+
],
|
|
3137
|
+
uk: [
|
|
3138
|
+
{ slug: "london", name: "London", region: "Greater London", country: "United Kingdom", population: 8982000, currency: "GBP", currencySymbol: "£", latitude: 51.5074, longitude: -0.1278, neighborSlugs: ["croydon", "watford", "reading"] },
|
|
3139
|
+
{ slug: "birmingham", name: "Birmingham", region: "West Midlands", country: "United Kingdom", population: 1149000, currency: "GBP", currencySymbol: "£", latitude: 52.4862, longitude: -1.8904, neighborSlugs: ["coventry", "wolverhampton", "solihull"] },
|
|
3140
|
+
{ slug: "manchester", name: "Manchester", region: "Greater Manchester", country: "United Kingdom", population: 553000, currency: "GBP", currencySymbol: "£", latitude: 53.4808, longitude: -2.2426, neighborSlugs: ["salford", "bolton", "stockport"] },
|
|
3141
|
+
{ slug: "edinburgh", name: "Edinburgh", region: "Scotland", country: "United Kingdom", population: 527000, currency: "GBP", currencySymbol: "£", latitude: 55.9533, longitude: -3.1883, neighborSlugs: ["glasgow", "livingston", "dundee"] }
|
|
3142
|
+
],
|
|
3143
|
+
us: [
|
|
3144
|
+
{ slug: "new-york", name: "New York", region: "NY", country: "United States", population: 8336000, currency: "USD", currencySymbol: "$", latitude: 40.7128, longitude: -74.006, neighborSlugs: ["brooklyn", "queens", "jersey-city"] },
|
|
3145
|
+
{ slug: "los-angeles", name: "Los Angeles", region: "CA", country: "United States", population: 3979000, currency: "USD", currencySymbol: "$", latitude: 34.0522, longitude: -118.2437, neighborSlugs: ["long-beach", "pasadena", "glendale"] },
|
|
3146
|
+
{ slug: "chicago", name: "Chicago", region: "IL", country: "United States", population: 2693000, currency: "USD", currencySymbol: "$", latitude: 41.8781, longitude: -87.6298, neighborSlugs: ["evanston", "naperville", "aurora"] },
|
|
3147
|
+
{ slug: "houston", name: "Houston", region: "TX", country: "United States", population: 2320000, currency: "USD", currencySymbol: "$", latitude: 29.7604, longitude: -95.3698, neighborSlugs: ["the-woodlands", "sugar-land", "katy"] },
|
|
3148
|
+
{ slug: "miami", name: "Miami", region: "FL", country: "United States", population: 442000, currency: "USD", currencySymbol: "$", latitude: 25.7617, longitude: -80.1918, neighborSlugs: ["fort-lauderdale", "miami-beach", "hialeah"] },
|
|
3149
|
+
{ slug: "san-francisco", name: "San Francisco", region: "CA", country: "United States", population: 873000, currency: "USD", currencySymbol: "$", latitude: 37.7749, longitude: -122.4194, neighborSlugs: ["oakland", "san-jose", "berkeley"] }
|
|
3150
|
+
],
|
|
3151
|
+
be: [
|
|
3152
|
+
{ slug: "bruxelles", name: "Bruxelles", region: "Bruxelles-Capitale", country: "Belgium", population: 1220000, currency: "EUR", currencySymbol: "€", latitude: 50.8503, longitude: 4.3517, neighborSlugs: ["anvers", "gand", "liege"] },
|
|
3153
|
+
{ slug: "anvers", name: "Anvers", region: "Flandre", country: "Belgium", population: 530000, currency: "EUR", currencySymbol: "€", latitude: 51.2194, longitude: 4.4025, neighborSlugs: ["bruxelles", "malines", "gand"] },
|
|
3154
|
+
{ slug: "liege", name: "Liège", region: "Wallonie", country: "Belgium", population: 197000, currency: "EUR", currencySymbol: "€", latitude: 50.6326, longitude: 5.5797, neighborSlugs: ["namur", "charleroi", "bruxelles"] }
|
|
3155
|
+
],
|
|
3156
|
+
ch: [
|
|
3157
|
+
{ slug: "zurich", name: "Zürich", region: "Zürich", country: "Switzerland", population: 434000, currency: "CHF", currencySymbol: "CHF", latitude: 47.3769, longitude: 8.5417, neighborSlugs: ["bale", "berne", "lucerne"] },
|
|
3158
|
+
{ slug: "geneve", name: "Genève", region: "Genève", country: "Switzerland", population: 203000, currency: "EUR", currencySymbol: "CHF", latitude: 46.2044, longitude: 6.1432, neighborSlugs: ["lausanne", "annecy", "nyon"] },
|
|
3159
|
+
{ slug: "lausanne", name: "Lausanne", region: "Vaud", country: "Switzerland", population: 140000, currency: "CHF", currencySymbol: "CHF", latitude: 46.5197, longitude: 6.6323, neighborSlugs: ["geneve", "montreux", "vevey"] }
|
|
3160
|
+
],
|
|
3161
|
+
it: [
|
|
3162
|
+
{ slug: "roma", name: "Roma", region: "Lazio", country: "Italy", population: 2873000, currency: "EUR", currencySymbol: "€", latitude: 41.9028, longitude: 12.4964, neighborSlugs: ["latina", "fiumicino", "tivoli"] },
|
|
3163
|
+
{ slug: "milano", name: "Milano", region: "Lombardia", country: "Italy", population: 1366000, currency: "EUR", currencySymbol: "€", latitude: 45.4642, longitude: 9.19, neighborSlugs: ["monza", "bergamo", "brescia"] },
|
|
3164
|
+
{ slug: "torino", name: "Torino", region: "Piemonte", country: "Italy", population: 870000, currency: "EUR", currencySymbol: "€", latitude: 45.0703, longitude: 7.6869, neighborSlugs: ["novara", "alessandria", "asti"] }
|
|
3165
|
+
],
|
|
3166
|
+
ca: [
|
|
3167
|
+
{ slug: "toronto", name: "Toronto", region: "ON", country: "Canada", population: 2794000, currency: "CAD", currencySymbol: "$", latitude: 43.6532, longitude: -79.3832, neighborSlugs: ["mississauga", "brampton", "markham"] },
|
|
3168
|
+
{ slug: "montreal", name: "Montréal", region: "QC", country: "Canada", population: 1762000, currency: "CAD", currencySymbol: "$", latitude: 45.5017, longitude: -73.5673, neighborSlugs: ["laval", "longueuil", "quebec"] },
|
|
3169
|
+
{ slug: "vancouver", name: "Vancouver", region: "BC", country: "Canada", population: 662000, currency: "CAD", currencySymbol: "$", latitude: 49.2827, longitude: -123.1207, neighborSlugs: ["burnaby", "richmond", "surrey"] }
|
|
3170
|
+
]
|
|
3171
|
+
};
|
|
3172
|
+
function resolveBuiltInLocations(territories) {
|
|
3173
|
+
if (!territories) {
|
|
3174
|
+
return [...BUILT_IN_LOCATIONS_DATABASE.fr];
|
|
3175
|
+
}
|
|
3176
|
+
const requested = (Array.isArray(territories) ? territories : [territories]).map((t) => t.toLowerCase().trim());
|
|
3177
|
+
const results = [];
|
|
3178
|
+
const addedSlugs = new Set;
|
|
3179
|
+
const isAllOrEurope = requested.some((t) => ["europe", "international", "all", "global", "world"].includes(t));
|
|
3180
|
+
if (isAllOrEurope) {
|
|
3181
|
+
for (const list of Object.values(BUILT_IN_LOCATIONS_DATABASE)) {
|
|
3182
|
+
for (const loc of list) {
|
|
3183
|
+
if (!addedSlugs.has(loc.slug)) {
|
|
3184
|
+
addedSlugs.add(loc.slug);
|
|
3185
|
+
results.push(loc);
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
return results;
|
|
3190
|
+
}
|
|
3191
|
+
const countryAliasMap = {
|
|
3192
|
+
france: "fr",
|
|
3193
|
+
fr: "fr",
|
|
3194
|
+
spain: "es",
|
|
3195
|
+
espagne: "es",
|
|
3196
|
+
es: "es",
|
|
3197
|
+
germany: "de",
|
|
3198
|
+
allemagne: "de",
|
|
3199
|
+
de: "de",
|
|
3200
|
+
uk: "uk",
|
|
3201
|
+
gb: "uk",
|
|
3202
|
+
"united kingdom": "uk",
|
|
3203
|
+
angleterre: "uk",
|
|
3204
|
+
us: "us",
|
|
3205
|
+
usa: "us",
|
|
3206
|
+
"united states": "us",
|
|
3207
|
+
etatsunis: "us",
|
|
3208
|
+
belgium: "be",
|
|
3209
|
+
belgique: "be",
|
|
3210
|
+
be: "be",
|
|
3211
|
+
switzerland: "ch",
|
|
3212
|
+
suisse: "ch",
|
|
3213
|
+
ch: "ch",
|
|
3214
|
+
italy: "it",
|
|
3215
|
+
italie: "it",
|
|
3216
|
+
it: "it",
|
|
3217
|
+
canada: "ca",
|
|
3218
|
+
ca: "ca"
|
|
3219
|
+
};
|
|
3220
|
+
for (const req of requested) {
|
|
3221
|
+
const code = countryAliasMap[req.replace(/[^a-z0-9]/g, "")];
|
|
3222
|
+
if (code && BUILT_IN_LOCATIONS_DATABASE[code]) {
|
|
3223
|
+
for (const loc of BUILT_IN_LOCATIONS_DATABASE[code]) {
|
|
3224
|
+
if (!addedSlugs.has(loc.slug)) {
|
|
3225
|
+
addedSlugs.add(loc.slug);
|
|
3226
|
+
results.push(loc);
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3090
3234
|
// src/matrix-engine.ts
|
|
3091
3235
|
class PseoMatrixEngine {
|
|
3092
3236
|
legalEngine;
|
|
@@ -3564,8 +3708,9 @@ class PseoMatrixEngine {
|
|
|
3564
3708
|
}
|
|
3565
3709
|
generateAllMatrices(domain, data, options) {
|
|
3566
3710
|
const allPages = [];
|
|
3567
|
-
if (data.services
|
|
3568
|
-
|
|
3711
|
+
if (data.services) {
|
|
3712
|
+
const locs = data.locations && data.locations.length > 0 ? data.locations : resolveBuiltInLocations(options.countries || options.territories || options.language || "fr");
|
|
3713
|
+
allPages.push(...this.generateLocalGeoMatrix(domain, data.services, locs, options));
|
|
3569
3714
|
}
|
|
3570
3715
|
if (data.competitors) {
|
|
3571
3716
|
allPages.push(...this.generateVsMatrix(domain, data.competitors, options));
|
|
@@ -3964,21 +4109,19 @@ export const matrixEngine = new PseoMatrixEngine();
|
|
|
3964
4109
|
|
|
3965
4110
|
export const SEO_CONFIG = {
|
|
3966
4111
|
brandName: "Acme",
|
|
3967
|
-
language: "
|
|
4112
|
+
language: "fr",
|
|
4113
|
+
countries: ["france", "belgique", "suisse"], // Or "europe" / "international". The SDK automatically provides verified cities!
|
|
3968
4114
|
cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
|
|
3969
4115
|
minPopulationToIndex: 15_000,
|
|
3970
4116
|
};
|
|
3971
4117
|
|
|
3972
4118
|
// Discovered EXCLUSIVELY from Frontend UI components
|
|
4119
|
+
// NOTE: DO NOT list cities manually! The SDK auto-provisions them based on your target countries/scope.
|
|
3973
4120
|
export const PSEO_DATASET = {
|
|
3974
4121
|
services: [
|
|
3975
4122
|
// 1 per major frontend feature branch
|
|
3976
4123
|
{ slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
|
|
3977
4124
|
],
|
|
3978
|
-
locations: [
|
|
3979
|
-
{ slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2_161_000, currency: "EUR", currencySymbol: "€" },
|
|
3980
|
-
{ slug: "lyon", name: "Lyon", region: "Rhône-Alpes", country: "France", population: 522_000, currency: "EUR", currencySymbol: "€" },
|
|
3981
|
-
],
|
|
3982
4125
|
targets: [
|
|
3983
4126
|
{ slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
|
|
3984
4127
|
],
|
|
@@ -6300,6 +6443,7 @@ var LynxSeo = {
|
|
|
6300
6443
|
var src_default = LynxSeo;
|
|
6301
6444
|
export {
|
|
6302
6445
|
validateSeoSlug,
|
|
6446
|
+
resolveBuiltInLocations,
|
|
6303
6447
|
resolveBrandIcon,
|
|
6304
6448
|
renderBrandIconSvg,
|
|
6305
6449
|
getSeoAgentPrompt,
|
|
@@ -6353,6 +6497,7 @@ export {
|
|
|
6353
6497
|
CopywritingFrameworksMaster,
|
|
6354
6498
|
BrandDnaCalendarEngine,
|
|
6355
6499
|
BacklinksClient,
|
|
6500
|
+
BUILT_IN_LOCATIONS_DATABASE,
|
|
6356
6501
|
BRAND_ICONS,
|
|
6357
6502
|
ApiKeyGuardian,
|
|
6358
6503
|
AiCopilotClient,
|
package/dist/matrix-engine.d.ts
CHANGED
|
@@ -127,6 +127,9 @@ export interface GeneratedPageMeta {
|
|
|
127
127
|
export interface MatrixOptions {
|
|
128
128
|
brandName: string;
|
|
129
129
|
language?: string;
|
|
130
|
+
languages?: string[];
|
|
131
|
+
countries?: string[] | string;
|
|
132
|
+
territories?: string[] | string;
|
|
130
133
|
cleanDirectRoutes?: boolean;
|
|
131
134
|
minPopulationToIndex?: number;
|
|
132
135
|
defaultCurrency?: string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🌍 Built-in Global Geographic & Demographic Database
|
|
3
|
+
* Automatically provisions verified cities, populations, currencies, and coordinates
|
|
4
|
+
* across Europe, North America, and international markets without requiring the user to list cities manually.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { PseoLocation } from "./matrix-engine";
|
|
8
|
+
|
|
9
|
+
export const BUILT_IN_LOCATIONS_DATABASE: Record<string, PseoLocation[]> = {
|
|
10
|
+
fr: [
|
|
11
|
+
{ slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2_161_000, currency: "EUR", currencySymbol: "€", latitude: 48.8566, longitude: 2.3522, neighborSlugs: ["boulogne-billancourt", "saint-denis", "montreuil"] },
|
|
12
|
+
{ slug: "marseille", name: "Marseille", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 873_000, currency: "EUR", currencySymbol: "€", latitude: 43.2965, longitude: 5.3698, neighborSlugs: ["aix-en-provence", "aubagne", "toulon"] },
|
|
13
|
+
{ slug: "lyon", name: "Lyon", region: "Auvergne-Rhône-Alpes", country: "France", population: 522_000, currency: "EUR", currencySymbol: "€", latitude: 45.764, longitude: 4.8357, neighborSlugs: ["villeurbanne", "venissieux", "saint-etienne"] },
|
|
14
|
+
{ slug: "toulouse", name: "Toulouse", region: "Occitanie", country: "France", population: 498_000, currency: "EUR", currencySymbol: "€", latitude: 43.6047, longitude: 1.4442, neighborSlugs: ["montauban", "albi", "colomiers"] },
|
|
15
|
+
{ slug: "nice", name: "Nice", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 343_000, currency: "EUR", currencySymbol: "€", latitude: 43.7102, longitude: 7.262, neighborSlugs: ["cannes", "antibes", "monaco"] },
|
|
16
|
+
{ slug: "nantes", name: "Nantes", region: "Pays de la Loire", country: "France", population: 320_000, currency: "EUR", currencySymbol: "€", latitude: 47.2184, longitude: -1.5536, neighborSlugs: ["saint-nazaire", "angers", "rennes"] },
|
|
17
|
+
{ slug: "montpellier", name: "Montpellier", region: "Occitanie", country: "France", population: 299_000, currency: "EUR", currencySymbol: "€", latitude: 43.6108, longitude: 3.8767, neighborSlugs: ["nimes", "beziers", "sete"] },
|
|
18
|
+
{ slug: "strasbourg", name: "Strasbourg", region: "Grand Est", country: "France", population: 290_000, currency: "EUR", currencySymbol: "€", latitude: 48.5734, longitude: 7.7521, neighborSlugs: ["mulhouse", "colmar", "metz"] },
|
|
19
|
+
{ slug: "bordeaux", name: "Bordeaux", region: "Nouvelle-Aquitaine", country: "France", population: 260_000, currency: "EUR", currencySymbol: "€", latitude: 44.8378, longitude: -0.5792, neighborSlugs: ["merignac", "pessac", "arcachon"] },
|
|
20
|
+
{ slug: "lille", name: "Lille", region: "Hauts-de-France", country: "France", population: 236_000, currency: "EUR", currencySymbol: "€", latitude: 50.6292, longitude: 3.0573, neighborSlugs: ["roubaix", "tourcoing", "villeneuve-d-ascq"] },
|
|
21
|
+
{ slug: "rennes", name: "Rennes", region: "Bretagne", country: "France", population: 222_000, currency: "EUR", currencySymbol: "€", latitude: 48.1173, longitude: -1.6778, neighborSlugs: ["saint-malo", "brest", "nantes"] },
|
|
22
|
+
{ slug: "toulon", name: "Toulon", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 179_000, currency: "EUR", currencySymbol: "€", latitude: 43.1242, longitude: 5.928, neighborSlugs: ["hyeres", "la-seyne-sur-mer", "marseille"] },
|
|
23
|
+
{ slug: "reims", name: "Reims", region: "Grand Est", country: "France", population: 179_000, currency: "EUR", currencySymbol: "€", latitude: 49.2583, longitude: 4.0317, neighborSlugs: ["chalons-en-champagne", "troyes", "metz"] },
|
|
24
|
+
{ slug: "saint-etienne", name: "Saint-Étienne", region: "Auvergne-Rhône-Alpes", country: "France", population: 174_000, currency: "EUR", currencySymbol: "€", latitude: 45.4397, longitude: 4.3872, neighborSlugs: ["lyon", "roanne", "saint-chamond"] },
|
|
25
|
+
{ slug: "le-havre", name: "Le Havre", region: "Normandie", country: "France", population: 166_000, currency: "EUR", currencySymbol: "€", latitude: 49.4944, longitude: 0.1079, neighborSlugs: ["rouen", "caen", "honfleur"] },
|
|
26
|
+
{ slug: "grenoble", name: "Grenoble", region: "Auvergne-Rhône-Alpes", country: "France", population: 158_000, currency: "EUR", currencySymbol: "€", latitude: 45.1885, longitude: 5.7245, neighborSlugs: ["echirolles", "chambery", "annecy"] },
|
|
27
|
+
{ slug: "dijon", name: "Dijon", region: "Bourgogne-Franche-Comté", country: "France", population: 159_000, currency: "EUR", currencySymbol: "€", latitude: 47.322, longitude: 5.0415, neighborSlugs: ["besancon", "chalon-sur-saone", "beaune"] },
|
|
28
|
+
{ slug: "angers", name: "Angers", region: "Pays de la Loire", country: "France", population: 155_000, currency: "EUR", currencySymbol: "€", latitude: 47.4784, longitude: -0.5632, neighborSlugs: ["saumur", "cholet", "nantes"] },
|
|
29
|
+
{ slug: "villeurbanne", name: "Villeurbanne", region: "Auvergne-Rhône-Alpes", country: "France", population: 154_000, currency: "EUR", currencySymbol: "€", latitude: 45.7667, longitude: 4.8833, neighborSlugs: ["lyon", "bron", "caluire-et-cuire"] },
|
|
30
|
+
{ slug: "nimes", name: "Nîmes", region: "Occitanie", country: "France", population: 148_000, currency: "EUR", currencySymbol: "€", latitude: 43.8367, longitude: 4.3601, neighborSlugs: ["montpellier", "avignon", "arles"] },
|
|
31
|
+
{ slug: "aix-en-provence", name: "Aix-en-Provence", region: "Provence-Alpes-Côte d'Azur", country: "France", population: 147_000, currency: "EUR", currencySymbol: "€", latitude: 43.5297, longitude: 5.4474, neighborSlugs: ["marseille", "gardanne", "salon-de-provence"] },
|
|
32
|
+
{ slug: "clermont-ferrand", name: "Clermont-Ferrand", region: "Auvergne-Rhône-Alpes", country: "France", population: 147_000, currency: "EUR", currencySymbol: "€", latitude: 45.7772, longitude: 3.087, neighborSlugs: ["riom", "cournon-d-auvergne", "vichy"] },
|
|
33
|
+
{ slug: "brest", name: "Brest", region: "Bretagne", country: "France", population: 139_000, currency: "EUR", currencySymbol: "€", latitude: 48.3904, longitude: -4.4861, neighborSlugs: ["quimper", "morlaix", "rennes"] },
|
|
34
|
+
{ slug: "tours", name: "Tours", region: "Centre-Val de Loire", country: "France", population: 137_000, currency: "EUR", currencySymbol: "€", latitude: 47.3941, longitude: 0.6848, neighborSlugs: ["orleans", "blois", "angers"] },
|
|
35
|
+
{ slug: "amiens", name: "Amiens", region: "Hauts-de-France", country: "France", population: 134_000, currency: "EUR", currencySymbol: "€", latitude: 49.8941, longitude: 2.2958, neighborSlugs: ["beauvais", "compiegne", "lille"] },
|
|
36
|
+
{ slug: "annecy", name: "Annecy", region: "Auvergne-Rhône-Alpes", country: "France", population: 131_000, currency: "EUR", currencySymbol: "€", latitude: 45.8992, longitude: 6.1294, neighborSlugs: ["chambery", "geneve", "grenoble"] },
|
|
37
|
+
],
|
|
38
|
+
|
|
39
|
+
es: [
|
|
40
|
+
{ slug: "madrid", name: "Madrid", region: "Comunidad de Madrid", country: "Spain", population: 3_300_000, currency: "EUR", currencySymbol: "€", latitude: 40.4168, longitude: -3.7038, neighborSlugs: ["alcala-de-henares", "mostoles", "getafe"] },
|
|
41
|
+
{ slug: "barcelona", name: "Barcelona", region: "Catalunya", country: "Spain", population: 1_636_000, currency: "EUR", currencySymbol: "€", latitude: 41.3851, longitude: 2.1734, neighborSlugs: ["hospitalet-de-llobregat", "badalona", "terrassa"] },
|
|
42
|
+
{ slug: "valencia", name: "Valencia", region: "Comunitat Valenciana", country: "Spain", population: 800_000, currency: "EUR", currencySymbol: "€", latitude: 39.4699, longitude: -0.3763, neighborSlugs: ["alicante", "castellon-de-la-plana", "gandia"] },
|
|
43
|
+
{ slug: "sevilla", name: "Sevilla", region: "Andalucía", country: "Spain", population: 688_000, currency: "EUR", currencySymbol: "€", latitude: 37.3891, longitude: -5.9845, neighborSlugs: ["cordoba", "huelva", "cadiz"] },
|
|
44
|
+
{ slug: "zaragoza", name: "Zaragoza", region: "Aragón", country: "Spain", population: 675_000, currency: "EUR", currencySymbol: "€", latitude: 41.6488, longitude: -0.8891, neighborSlugs: ["huesca", "teruel", "madrid"] },
|
|
45
|
+
{ slug: "malaga", name: "Málaga", region: "Andalucía", country: "Spain", population: 579_000, currency: "EUR", currencySymbol: "€", latitude: 36.7213, longitude: -4.4214, neighborSlugs: ["marbella", "torremolinos", "granada"] },
|
|
46
|
+
{ slug: "bilbao", name: "Bilbao", region: "País Vasco", country: "Spain", population: 346_000, currency: "EUR", currencySymbol: "€", latitude: 43.263, longitude: -2.935, neighborSlugs: ["san-sebastian", "vitoria-gasteiz", "santander"] },
|
|
47
|
+
],
|
|
48
|
+
|
|
49
|
+
de: [
|
|
50
|
+
{ slug: "berlin", name: "Berlin", region: "Berlin", country: "Germany", population: 3_645_000, currency: "EUR", currencySymbol: "€", latitude: 52.52, longitude: 13.405, neighborSlugs: ["potsdam", "cottbus", "brandenburg"] },
|
|
51
|
+
{ slug: "hamburg", name: "Hamburg", region: "Hamburg", country: "Germany", population: 1_841_000, currency: "EUR", currencySymbol: "€", latitude: 53.5511, longitude: 9.9937, neighborSlugs: ["luebeck", "bremen", "kiel"] },
|
|
52
|
+
{ slug: "muenchen", name: "München", region: "Bayern", country: "Germany", population: 1_472_000, currency: "EUR", currencySymbol: "€", latitude: 48.1351, longitude: 11.582, neighborSlugs: ["augsburg", "ingolstadt", "rosenheim"] },
|
|
53
|
+
{ slug: "koeln", name: "Köln", region: "Nordrhein-Westfalen", country: "Germany", population: 1_086_000, currency: "EUR", currencySymbol: "€", latitude: 50.9375, longitude: 6.9603, neighborSlugs: ["duesseldorf", "bonn", "leverkusen"] },
|
|
54
|
+
{ slug: "frankfurt", name: "Frankfurt am Main", region: "Hessen", country: "Germany", population: 753_000, currency: "EUR", currencySymbol: "€", latitude: 50.1109, longitude: 8.6821, neighborSlugs: ["wiesbaden", "mainz", "darmstadt"] },
|
|
55
|
+
{ slug: "stuttgart", name: "Stuttgart", region: "Baden-Württemberg", country: "Germany", population: 635_000, currency: "EUR", currencySymbol: "€", latitude: 48.7758, longitude: 9.1829, neighborSlugs: ["esslingen", "ludwigsburg", "karlsruhe"] },
|
|
56
|
+
],
|
|
57
|
+
|
|
58
|
+
uk: [
|
|
59
|
+
{ slug: "london", name: "London", region: "Greater London", country: "United Kingdom", population: 8_982_000, currency: "GBP", currencySymbol: "£", latitude: 51.5074, longitude: -0.1278, neighborSlugs: ["croydon", "watford", "reading"] },
|
|
60
|
+
{ slug: "birmingham", name: "Birmingham", region: "West Midlands", country: "United Kingdom", population: 1_149_000, currency: "GBP", currencySymbol: "£", latitude: 52.4862, longitude: -1.8904, neighborSlugs: ["coventry", "wolverhampton", "solihull"] },
|
|
61
|
+
{ slug: "manchester", name: "Manchester", region: "Greater Manchester", country: "United Kingdom", population: 553_000, currency: "GBP", currencySymbol: "£", latitude: 53.4808, longitude: -2.2426, neighborSlugs: ["salford", "bolton", "stockport"] },
|
|
62
|
+
{ slug: "edinburgh", name: "Edinburgh", region: "Scotland", country: "United Kingdom", population: 527_000, currency: "GBP", currencySymbol: "£", latitude: 55.9533, longitude: -3.1883, neighborSlugs: ["glasgow", "livingston", "dundee"] },
|
|
63
|
+
],
|
|
64
|
+
|
|
65
|
+
us: [
|
|
66
|
+
{ slug: "new-york", name: "New York", region: "NY", country: "United States", population: 8_336_000, currency: "USD", currencySymbol: "$", latitude: 40.7128, longitude: -74.006, neighborSlugs: ["brooklyn", "queens", "jersey-city"] },
|
|
67
|
+
{ slug: "los-angeles", name: "Los Angeles", region: "CA", country: "United States", population: 3_979_000, currency: "USD", currencySymbol: "$", latitude: 34.0522, longitude: -118.2437, neighborSlugs: ["long-beach", "pasadena", "glendale"] },
|
|
68
|
+
{ slug: "chicago", name: "Chicago", region: "IL", country: "United States", population: 2_693_000, currency: "USD", currencySymbol: "$", latitude: 41.8781, longitude: -87.6298, neighborSlugs: ["evanston", "naperville", "aurora"] },
|
|
69
|
+
{ slug: "houston", name: "Houston", region: "TX", country: "United States", population: 2_320_000, currency: "USD", currencySymbol: "$", latitude: 29.7604, longitude: -95.3698, neighborSlugs: ["the-woodlands", "sugar-land", "katy"] },
|
|
70
|
+
{ slug: "miami", name: "Miami", region: "FL", country: "United States", population: 442_000, currency: "USD", currencySymbol: "$", latitude: 25.7617, longitude: -80.1918, neighborSlugs: ["fort-lauderdale", "miami-beach", "hialeah"] },
|
|
71
|
+
{ slug: "san-francisco", name: "San Francisco", region: "CA", country: "United States", population: 873_000, currency: "USD", currencySymbol: "$", latitude: 37.7749, longitude: -122.4194, neighborSlugs: ["oakland", "san-jose", "berkeley"] },
|
|
72
|
+
],
|
|
73
|
+
|
|
74
|
+
be: [
|
|
75
|
+
{ slug: "bruxelles", name: "Bruxelles", region: "Bruxelles-Capitale", country: "Belgium", population: 1_220_000, currency: "EUR", currencySymbol: "€", latitude: 50.8503, longitude: 4.3517, neighborSlugs: ["anvers", "gand", "liege"] },
|
|
76
|
+
{ slug: "anvers", name: "Anvers", region: "Flandre", country: "Belgium", population: 530_000, currency: "EUR", currencySymbol: "€", latitude: 51.2194, longitude: 4.4025, neighborSlugs: ["bruxelles", "malines", "gand"] },
|
|
77
|
+
{ slug: "liege", name: "Liège", region: "Wallonie", country: "Belgium", population: 197_000, currency: "EUR", currencySymbol: "€", latitude: 50.6326, longitude: 5.5797, neighborSlugs: ["namur", "charleroi", "bruxelles"] },
|
|
78
|
+
],
|
|
79
|
+
|
|
80
|
+
ch: [
|
|
81
|
+
{ slug: "zurich", name: "Zürich", region: "Zürich", country: "Switzerland", population: 434_000, currency: "CHF", currencySymbol: "CHF", latitude: 47.3769, longitude: 8.5417, neighborSlugs: ["bale", "berne", "lucerne"] },
|
|
82
|
+
{ slug: "geneve", name: "Genève", region: "Genève", country: "Switzerland", population: 203_000, currency: "EUR", currencySymbol: "CHF", latitude: 46.2044, longitude: 6.1432, neighborSlugs: ["lausanne", "annecy", "nyon"] },
|
|
83
|
+
{ slug: "lausanne", name: "Lausanne", region: "Vaud", country: "Switzerland", population: 140_000, currency: "CHF", currencySymbol: "CHF", latitude: 46.5197, longitude: 6.6323, neighborSlugs: ["geneve", "montreux", "vevey"] },
|
|
84
|
+
],
|
|
85
|
+
|
|
86
|
+
it: [
|
|
87
|
+
{ slug: "roma", name: "Roma", region: "Lazio", country: "Italy", population: 2_873_000, currency: "EUR", currencySymbol: "€", latitude: 41.9028, longitude: 12.4964, neighborSlugs: ["latina", "fiumicino", "tivoli"] },
|
|
88
|
+
{ slug: "milano", name: "Milano", region: "Lombardia", country: "Italy", population: 1_366_000, currency: "EUR", currencySymbol: "€", latitude: 45.4642, longitude: 9.19, neighborSlugs: ["monza", "bergamo", "brescia"] },
|
|
89
|
+
{ slug: "torino", name: "Torino", region: "Piemonte", country: "Italy", population: 870_000, currency: "EUR", currencySymbol: "€", latitude: 45.0703, longitude: 7.6869, neighborSlugs: ["novara", "alessandria", "asti"] },
|
|
90
|
+
],
|
|
91
|
+
|
|
92
|
+
ca: [
|
|
93
|
+
{ slug: "toronto", name: "Toronto", region: "ON", country: "Canada", population: 2_794_000, currency: "CAD", currencySymbol: "$", latitude: 43.6532, longitude: -79.3832, neighborSlugs: ["mississauga", "brampton", "markham"] },
|
|
94
|
+
{ slug: "montreal", name: "Montréal", region: "QC", country: "Canada", population: 1_762_000, currency: "CAD", currencySymbol: "$", latitude: 45.5017, longitude: -73.5673, neighborSlugs: ["laval", "longueuil", "quebec"] },
|
|
95
|
+
{ slug: "vancouver", name: "Vancouver", region: "BC", country: "Canada", population: 662_000, currency: "CAD", currencySymbol: "$", latitude: 49.2827, longitude: -123.1207, neighborSlugs: ["burnaby", "richmond", "surrey"] },
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolves built-in locations by country code or territory keyword.
|
|
101
|
+
* Supports: "fr", "france", "es", "spain", "de", "germany", "uk", "us", "be", "ch", "it", "ca", "europe", "international", "all"
|
|
102
|
+
*/
|
|
103
|
+
export function resolveBuiltInLocations(territories?: string[] | string): PseoLocation[] {
|
|
104
|
+
if (!territories) {
|
|
105
|
+
// Default: France + Belgium + Switzerland if unspecified
|
|
106
|
+
return [...BUILT_IN_LOCATIONS_DATABASE.fr];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const requested = (Array.isArray(territories) ? territories : [territories]).map((t) => t.toLowerCase().trim());
|
|
110
|
+
const results: PseoLocation[] = [];
|
|
111
|
+
const addedSlugs = new Set<string>();
|
|
112
|
+
|
|
113
|
+
const isAllOrEurope = requested.some((t) => ["europe", "international", "all", "global", "world"].includes(t));
|
|
114
|
+
|
|
115
|
+
if (isAllOrEurope) {
|
|
116
|
+
for (const list of Object.values(BUILT_IN_LOCATIONS_DATABASE)) {
|
|
117
|
+
for (const loc of list) {
|
|
118
|
+
if (!addedSlugs.has(loc.slug)) {
|
|
119
|
+
addedSlugs.add(loc.slug);
|
|
120
|
+
results.push(loc);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const countryAliasMap: Record<string, string> = {
|
|
128
|
+
france: "fr",
|
|
129
|
+
fr: "fr",
|
|
130
|
+
spain: "es",
|
|
131
|
+
espagne: "es",
|
|
132
|
+
es: "es",
|
|
133
|
+
germany: "de",
|
|
134
|
+
allemagne: "de",
|
|
135
|
+
de: "de",
|
|
136
|
+
uk: "uk",
|
|
137
|
+
gb: "uk",
|
|
138
|
+
"united kingdom": "uk",
|
|
139
|
+
angleterre: "uk",
|
|
140
|
+
us: "us",
|
|
141
|
+
usa: "us",
|
|
142
|
+
"united states": "us",
|
|
143
|
+
etatsunis: "us",
|
|
144
|
+
belgium: "be",
|
|
145
|
+
belgique: "be",
|
|
146
|
+
be: "be",
|
|
147
|
+
switzerland: "ch",
|
|
148
|
+
suisse: "ch",
|
|
149
|
+
ch: "ch",
|
|
150
|
+
italy: "it",
|
|
151
|
+
italie: "it",
|
|
152
|
+
it: "it",
|
|
153
|
+
canada: "ca",
|
|
154
|
+
ca: "ca",
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
for (const req of requested) {
|
|
158
|
+
const code = countryAliasMap[req.replace(/[^a-z0-9]/g, "")];
|
|
159
|
+
if (code && BUILT_IN_LOCATIONS_DATABASE[code]) {
|
|
160
|
+
for (const loc of BUILT_IN_LOCATIONS_DATABASE[code]) {
|
|
161
|
+
if (!addedSlugs.has(loc.slug)) {
|
|
162
|
+
addedSlugs.add(loc.slug);
|
|
163
|
+
results.push(loc);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return results.length > 0 ? results : [...BUILT_IN_LOCATIONS_DATABASE.fr];
|
|
170
|
+
}
|
package/src/engine.test.ts
CHANGED
|
@@ -217,4 +217,21 @@ describe("Audited Reference Engines (Advertools, Santifer, Seonaut)", () => {
|
|
|
217
217
|
expect(llmsTxt).toContain("# Acme — AI Knowledge Graph");
|
|
218
218
|
expect(llmsTxt).toContain("https://acme.com/autopost/paris");
|
|
219
219
|
});
|
|
220
|
+
|
|
221
|
+
it("Built-in Locations: Auto-provisions cities without manual entry", () => {
|
|
222
|
+
const engine = new PseoMatrixEngine();
|
|
223
|
+
const pages = engine.generateAllMatrices(
|
|
224
|
+
"https://acme.com",
|
|
225
|
+
{
|
|
226
|
+
services: [{ slug: "autopost-facebook", name: "Autopost Facebook", category: "Social" }],
|
|
227
|
+
},
|
|
228
|
+
{ brandName: "Acme", countries: ["france", "spain"] },
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const paths = pages.map((p) => p.urlPath);
|
|
232
|
+
expect(paths).toContain("/autopost-facebook/paris");
|
|
233
|
+
expect(paths).toContain("/autopost-facebook/lyon");
|
|
234
|
+
expect(paths).toContain("/autopost-facebook/madrid");
|
|
235
|
+
expect(paths).toContain("/autopost-facebook/barcelona");
|
|
236
|
+
});
|
|
220
237
|
});
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ export * from "./engine";
|
|
|
14
14
|
export * from "./slug-engine";
|
|
15
15
|
export * from "./legal-disclaimers";
|
|
16
16
|
export * from "./matrix-engine";
|
|
17
|
+
export * from "./built-in-locations";
|
|
17
18
|
export * from "./brand-icons";
|
|
18
19
|
export * from "./urlytics-engine";
|
|
19
20
|
export * from "./keyword-permutator";
|
package/src/llm-prompt.ts
CHANGED
|
@@ -86,21 +86,19 @@ export const matrixEngine = new PseoMatrixEngine();
|
|
|
86
86
|
|
|
87
87
|
export const SEO_CONFIG = {
|
|
88
88
|
brandName: "Acme",
|
|
89
|
-
language: "
|
|
89
|
+
language: "fr",
|
|
90
|
+
countries: ["france", "belgique", "suisse"], // Or "europe" / "international". The SDK automatically provides verified cities!
|
|
90
91
|
cleanDirectRoutes: true, // Eliminates parasite words (/solutions/, etc.)
|
|
91
92
|
minPopulationToIndex: 15_000,
|
|
92
93
|
};
|
|
93
94
|
|
|
94
95
|
// Discovered EXCLUSIVELY from Frontend UI components
|
|
96
|
+
// NOTE: DO NOT list cities manually! The SDK auto-provisions them based on your target countries/scope.
|
|
95
97
|
export const PSEO_DATASET = {
|
|
96
98
|
services: [
|
|
97
99
|
// 1 per major frontend feature branch
|
|
98
100
|
{ slug: "autopost-facebook", name: "Autopost Facebook & Instagram", category: "Social Media", keyFeatures: ["AI Scheduling", "Visual Calendar", "Auto-Hashtags"], description: "Automated social media posting engine" },
|
|
99
101
|
],
|
|
100
|
-
locations: [
|
|
101
|
-
{ slug: "paris", name: "Paris", region: "Île-de-France", country: "France", population: 2_161_000, currency: "EUR", currencySymbol: "€" },
|
|
102
|
-
{ slug: "lyon", name: "Lyon", region: "Rhône-Alpes", country: "France", population: 522_000, currency: "EUR", currencySymbol: "€" },
|
|
103
|
-
],
|
|
104
102
|
targets: [
|
|
105
103
|
{ slug: "agences", name: "Marketing Agencies", type: "industry", painPoints: ["Manual Posting"], benefits: ["10x Output"] },
|
|
106
104
|
],
|
package/src/matrix-engine.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { cleanSeoSlug } from "./slug-engine";
|
|
14
14
|
import { LegalDisclaimerEngine } from "./legal-disclaimers";
|
|
15
15
|
import { ExtendedSchemaGraphBuilder } from "./extended-schemas";
|
|
16
|
+
import { resolveBuiltInLocations } from "./built-in-locations";
|
|
16
17
|
|
|
17
18
|
export type MatrixFamily =
|
|
18
19
|
| "local-geo"
|
|
@@ -155,6 +156,9 @@ export interface GeneratedPageMeta {
|
|
|
155
156
|
export interface MatrixOptions {
|
|
156
157
|
brandName: string;
|
|
157
158
|
language?: string; // Default: "en", supports "fr", "de", "es", "pt", "it", etc.
|
|
159
|
+
languages?: string[]; // Array of supported site languages (e.g. ["fr", "en", "es"])
|
|
160
|
+
countries?: string[] | string; // e.g. ["france", "spain"] or "europe" or "international"
|
|
161
|
+
territories?: string[] | string; // Alias for countries
|
|
158
162
|
cleanDirectRoutes?: boolean; // When true (default), eliminates parasite words (/solutions/, etc.) for direct /{service}/{city}
|
|
159
163
|
minPopulationToIndex?: number;
|
|
160
164
|
defaultCurrency?: string;
|
|
@@ -790,8 +794,11 @@ export class PseoMatrixEngine {
|
|
|
790
794
|
): GeneratedPageMeta[] {
|
|
791
795
|
const allPages: GeneratedPageMeta[] = [];
|
|
792
796
|
|
|
793
|
-
if (data.services
|
|
794
|
-
|
|
797
|
+
if (data.services) {
|
|
798
|
+
const locs = data.locations && data.locations.length > 0
|
|
799
|
+
? data.locations
|
|
800
|
+
: resolveBuiltInLocations(options.countries || options.territories || options.language || "fr");
|
|
801
|
+
allPages.push(...this.generateLocalGeoMatrix(domain, data.services, locs, options));
|
|
795
802
|
}
|
|
796
803
|
if (data.competitors) {
|
|
797
804
|
allPages.push(...this.generateVsMatrix(domain, data.competitors, options));
|