/ src / config / countries.js
countries.js
  1  /**
  2   * Country Configuration Registry
  3   *
  4   * Defines country-specific settings for multi-country support including:
  5   * - Google search domains for SERP scraping
  6   * - Currency and formatting preferences
  7   * - Phone number formats and mobile patterns
  8   * - Common cities for local keyword generation
  9   * - GDPR compliance requirements
 10   *
 11   * Countries ordered by GDP (top 25 economies)
 12   */
 13  
 14  export const COUNTRIES = {
 15    // 1. United States
 16    US: {
 17      code: 'US',
 18      name: 'United States',
 19      googleDomain: 'google.com',
 20      language: 'en',
 21      currency: 'USD',
 22      currencySymbol: '$',
 23      dateFormat: 'MM/DD/YYYY',
 24      phoneFormat: '+1',
 25      mobilePattern: /^(\+1|1)?[2-9]\d{9}$/, // US mobile (hard to distinguish from landline)
 26      commonCities: [
 27        'new york',
 28        'los angeles',
 29        'chicago',
 30        'houston',
 31        'phoenix',
 32        'philadelphia',
 33        'san antonio',
 34        'san diego',
 35        'dallas',
 36        'austin',
 37      ],
 38      requiresGDPRCheck: false,
 39      timezone: 'America/New_York', // Eastern (can vary by city)
 40    },
 41  
 42    // 2. China (limited Google access)
 43    CN: {
 44      code: 'CN',
 45      name: 'China',
 46      googleDomain: 'google.com.hk', // Google blocked in mainland, use HK
 47      language: 'zh',
 48      currency: 'CNY',
 49      currencySymbol: '¥',
 50      dateFormat: 'YYYY/MM/DD',
 51      phoneFormat: '+86',
 52      mobilePattern: /^(\+86|86)?1[3-9]\d{9}$/, // Chinese mobile
 53      commonCities: [
 54        'beijing',
 55        'shanghai',
 56        'guangzhou',
 57        'shenzhen',
 58        'chengdu',
 59        'hangzhou',
 60        'wuhan',
 61        'xian',
 62        'tianjin',
 63        'nanjing',
 64      ],
 65      requiresGDPRCheck: false,
 66      timezone: 'Asia/Shanghai',
 67    },
 68  
 69    // 3. Japan
 70    JP: {
 71      code: 'JP',
 72      name: 'Japan',
 73      googleDomain: 'google.co.jp',
 74      language: 'ja',
 75      currency: 'JPY',
 76      currencySymbol: '¥',
 77      dateFormat: 'YYYY/MM/DD',
 78      phoneFormat: '+81',
 79      mobilePattern: /^(\+81|0)(70|80|90)\d{8}$/, // Japanese mobile
 80      commonCities: [
 81        'tokyo',
 82        'osaka',
 83        'yokohama',
 84        'nagoya',
 85        'sapporo',
 86        'fukuoka',
 87        'kobe',
 88        'kyoto',
 89        'kawasaki',
 90        'saitama',
 91      ],
 92      requiresGDPRCheck: false,
 93      timezone: 'Asia/Tokyo',
 94    },
 95  
 96    // 4. Germany
 97    DE: {
 98      code: 'DE',
 99      name: 'Germany',
100      googleDomain: 'google.de',
101      language: 'de',
102      currency: 'EUR',
103      currencySymbol: '€',
104      dateFormat: 'DD.MM.YYYY',
105      phoneFormat: '+49',
106      mobilePattern: /^(\+49|0)(15|16|17)\d{8,9}$/, // German mobile
107      commonCities: [
108        'berlin',
109        'munich',
110        'hamburg',
111        'cologne',
112        'frankfurt',
113        'stuttgart',
114        'dusseldorf',
115        'dortmund',
116        'essen',
117        'leipzig',
118      ],
119      requiresGDPRCheck: true,
120      companyTypes: ['GmbH', 'AG', 'UG (haftungsbeschränkt)', 'UG', 'KG', 'e.V.', 'OHG'],
121      companyKeywords: [
122        'Handelsregister',
123        'Unternehmensnummer',
124        'USt-IdNr',
125        'Geschäftsführer',
126        'Registergericht',
127        'Amtsgericht',
128      ],
129      keyPageNames: ['impressum', 'kontakt', 'über uns', 'rechtliches', 'datenschutz'],
130      timezone: 'Europe/Berlin',
131    },
132  
133    // 5. United Kingdom
134    UK: {
135      code: 'UK',
136      name: 'United Kingdom',
137      googleDomain: 'google.co.uk',
138      language: 'en',
139      currency: 'GBP',
140      currencySymbol: '£',
141      dateFormat: 'DD/MM/YYYY',
142      phoneFormat: '+44',
143      mobilePattern: /^(\+44|0)(7\d{9}|447\d{8})$/, // UK mobiles start with 07 or +447
144      commonCities: [
145        'london',
146        'manchester',
147        'birmingham',
148        'glasgow',
149        'liverpool',
150        'edinburgh',
151        'leeds',
152        'bristol',
153        'cardiff',
154        'belfast',
155      ],
156      requiresGDPRCheck: true,
157      // Corporate subscribers — CAN receive unsolicited B2B marketing under UK PECR
158      companyTypes: [
159        'Ltd',
160        'Limited',
161        'PLC',
162        'LLP',
163        'Limited Liability Partnership',
164        'Incorporated',
165        'CIC',
166        'CIO',
167      ],
168      // Individual subscribers under PECR — CANNOT be contacted without consent.
169      // Sole traders and non-LLP partnerships are legally individuals, not corporate.
170      individualIndicators: ['Sole trader', 'Sole proprietor', 'Partnership'],
171      companyKeywords: [
172        'Company number',
173        'Company No',
174        'Co. No',
175        'Company registration',
176        'Reg. No',
177        'Registration number',
178        'Registered in England',
179        'Registered in Scotland',
180        'Registered in Wales',
181        'Registered in Northern Ireland',
182        'Registered office',
183        'Registered address',
184        'Companies House',
185        'VAT number',
186        'VAT No',
187        'VAT registration',
188        'Director:',
189        'Directors:',
190        'ICO registration',
191        'ICO number',
192        'Data Protection Registration',
193      ],
194      keyPageNames: ['contact', 'about', 'legal', 'company information', 'privacy policy'],
195      timezone: 'Europe/London',
196    },
197  
198    // 6. India
199    IN: {
200      code: 'IN',
201      name: 'India',
202      googleDomain: 'google.co.in',
203      language: 'en',
204      currency: 'INR',
205      currencySymbol: '₹',
206      dateFormat: 'DD/MM/YYYY',
207      phoneFormat: '+91',
208      mobilePattern: /^(\+91|91|0)?[6-9]\d{9}$/, // Indian mobile
209      commonCities: [
210        'mumbai',
211        'delhi',
212        'bangalore',
213        'hyderabad',
214        'chennai',
215        'kolkata',
216        'pune',
217        'ahmedabad',
218        'jaipur',
219        'surat',
220      ],
221      requiresGDPRCheck: false,
222      timezone: 'Asia/Kolkata',
223    },
224  
225    // 7. France
226    FR: {
227      code: 'FR',
228      name: 'France',
229      googleDomain: 'google.fr',
230      language: 'fr',
231      currency: 'EUR',
232      currencySymbol: '€',
233      dateFormat: 'DD/MM/YYYY',
234      phoneFormat: '+33',
235      mobilePattern: /^(\+33|0)(6|7)\d{8}$/, // French mobile
236      commonCities: [
237        'paris',
238        'marseille',
239        'lyon',
240        'toulouse',
241        'nice',
242        'nantes',
243        'montpellier',
244        'strasbourg',
245        'bordeaux',
246        'lille',
247      ],
248      requiresGDPRCheck: true,
249      companyTypes: ['SARL', 'SA', 'SAS', 'EURL', 'SNC', 'SCS'],
250      companyKeywords: [
251        'Numéro SIREN',
252        'Numéro SIRET',
253        'RCS',
254        'Capital social',
255        'Siège social',
256        'Numéro de TVA',
257      ],
258      keyPageNames: ['contact', 'à propos', 'mentions légales', 'informations légales'],
259      timezone: 'Europe/Paris',
260    },
261  
262    // 8. Italy
263    IT: {
264      code: 'IT',
265      name: 'Italy',
266      googleDomain: 'google.it',
267      language: 'it',
268      currency: 'EUR',
269      currencySymbol: '€',
270      dateFormat: 'DD/MM/YYYY',
271      phoneFormat: '+39',
272      mobilePattern: /^(\+39|39)?3\d{8,9}$/, // Italian mobile
273      commonCities: [
274        'rome',
275        'milan',
276        'naples',
277        'turin',
278        'palermo',
279        'genoa',
280        'bologna',
281        'florence',
282        'bari',
283        'catania',
284      ],
285      requiresGDPRCheck: true,
286      companyTypes: ['S.r.l.', 'S.p.A.', 'S.n.c.', 'S.a.s.'],
287      companyKeywords: [
288        'Partita IVA',
289        'Codice Fiscale',
290        'Registro Imprese',
291        'Capitale sociale',
292        'Sede legale',
293      ],
294      keyPageNames: ['contatti', 'chi siamo', 'note legali', 'informazioni legali'],
295      timezone: 'Europe/Rome',
296    },
297  
298    // 9. Canada
299    CA: {
300      code: 'CA',
301      name: 'Canada',
302      googleDomain: 'google.ca',
303      language: 'en',
304      currency: 'CAD',
305      currencySymbol: '$',
306      dateFormat: 'DD/MM/YYYY',
307      phoneFormat: '+1',
308      mobilePattern: /^(\+1|1)?[2-9]\d{9}$/, // Canadian mobile (similar to US)
309      commonCities: [
310        'toronto',
311        'montreal',
312        'vancouver',
313        'calgary',
314        'ottawa',
315        'edmonton',
316        'winnipeg',
317        'quebec city',
318        'hamilton',
319        'kitchener',
320      ],
321      requiresGDPRCheck: false,
322      timezone: 'America/Toronto', // Eastern (can vary by province)
323    },
324  
325    // 10. South Korea
326    KR: {
327      code: 'KR',
328      name: 'South Korea',
329      googleDomain: 'google.co.kr',
330      language: 'ko',
331      currency: 'KRW',
332      currencySymbol: '₩',
333      dateFormat: 'YYYY.MM.DD',
334      phoneFormat: '+82',
335      mobilePattern: /^(\+82|0)?1[0-9]\d{7,8}$/, // Korean mobile
336      commonCities: [
337        'seoul',
338        'busan',
339        'incheon',
340        'daegu',
341        'daejeon',
342        'gwangju',
343        'ulsan',
344        'suwon',
345        'changwon',
346        'goyang',
347      ],
348      requiresGDPRCheck: false,
349      timezone: 'Asia/Seoul',
350    },
351  
352    // 11. Spain
353    ES: {
354      code: 'ES',
355      name: 'Spain',
356      googleDomain: 'google.es',
357      language: 'es',
358      currency: 'EUR',
359      currencySymbol: '€',
360      dateFormat: 'DD/MM/YYYY',
361      phoneFormat: '+34',
362      mobilePattern: /^(\+34|34)?[67]\d{8}$/, // Spanish mobile
363      commonCities: [
364        'madrid',
365        'barcelona',
366        'valencia',
367        'seville',
368        'zaragoza',
369        'malaga',
370        'murcia',
371        'palma',
372        'bilbao',
373        'alicante',
374      ],
375      requiresGDPRCheck: true,
376      companyTypes: ['SL', 'SA', 'SRL', 'SLU'],
377      companyKeywords: ['CIF', 'NIF', 'Registro Mercantil', 'Capital social', 'Domicilio social'],
378      keyPageNames: ['contacto', 'acerca de', 'aviso legal', 'información legal'],
379      timezone: 'Europe/Madrid',
380    },
381  
382    // 12. Australia
383    AU: {
384      code: 'AU',
385      name: 'Australia',
386      googleDomain: 'google.com.au',
387      language: 'en',
388      currency: 'AUD',
389      currencySymbol: '$',
390      dateFormat: 'DD/MM/YYYY',
391      phoneFormat: '+61',
392      mobilePattern: /^(\+61|0)(4\d{8}|614\d{6})$/, // Australian mobiles start with 04 or +614
393      commonCities: [
394        'sydney',
395        'melbourne',
396        'brisbane',
397        'perth',
398        'adelaide',
399        'gold coast',
400        'canberra',
401        'newcastle',
402        'wollongong',
403        'hobart',
404      ],
405      requiresGDPRCheck: false,
406      timezone: 'Australia/Sydney', // Eastern (can vary by state)
407    },
408  
409    // 13. Mexico
410    MX: {
411      code: 'MX',
412      name: 'Mexico',
413      googleDomain: 'google.com.mx',
414      language: 'es',
415      currency: 'MXN',
416      currencySymbol: '$',
417      dateFormat: 'DD/MM/YYYY',
418      phoneFormat: '+52',
419      mobilePattern: /^(\+52|52)?1?\d{10}$/, // Mexican mobile
420      commonCities: [
421        'mexico city',
422        'guadalajara',
423        'monterrey',
424        'puebla',
425        'tijuana',
426        'leon',
427        'juarez',
428        'zapopan',
429        'merida',
430        'cancun',
431      ],
432      requiresGDPRCheck: false,
433      timezone: 'America/Mexico_City',
434    },
435  
436    // 14. Indonesia
437    ID: {
438      code: 'ID',
439      name: 'Indonesia',
440      googleDomain: 'google.co.id',
441      language: 'id',
442      currency: 'IDR',
443      currencySymbol: 'Rp',
444      dateFormat: 'DD/MM/YYYY',
445      phoneFormat: '+62',
446      mobilePattern: /^(\+62|0)8\d{8,10}$/, // Indonesian mobile
447      commonCities: [
448        'jakarta',
449        'surabaya',
450        'bandung',
451        'medan',
452        'semarang',
453        'makassar',
454        'palembang',
455        'tangerang',
456        'depok',
457        'bekasi',
458      ],
459      requiresGDPRCheck: false,
460      timezone: 'Asia/Jakarta',
461    },
462  
463    // 15. Netherlands
464    NL: {
465      code: 'NL',
466      name: 'Netherlands',
467      googleDomain: 'google.nl',
468      language: 'nl',
469      currency: 'EUR',
470      currencySymbol: '€',
471      dateFormat: 'DD-MM-YYYY',
472      phoneFormat: '+31',
473      mobilePattern: /^(\+31|0)6\d{8}$/, // Dutch mobile
474      commonCities: [
475        'amsterdam',
476        'rotterdam',
477        'the hague',
478        'utrecht',
479        'eindhoven',
480        'groningen',
481        'tilburg',
482        'almere',
483        'breda',
484        'nijmegen',
485      ],
486      requiresGDPRCheck: true,
487      companyTypes: ['BV', 'NV', 'VOF', 'CV'],
488      companyKeywords: ['KvK-nummer', 'BTW-nummer', 'Vestigingsadres', 'Handelsregister'],
489      keyPageNames: ['contact', 'over ons', 'juridisch', 'privacybeleid'],
490      timezone: 'Europe/Amsterdam',
491    },
492  
493    // 16. Switzerland
494    CH: {
495      code: 'CH',
496      name: 'Switzerland',
497      googleDomain: 'google.ch',
498      language: 'de', // Primary, but also fr, it
499      currency: 'CHF',
500      currencySymbol: 'CHF',
501      dateFormat: 'DD.MM.YYYY',
502      phoneFormat: '+41',
503      mobilePattern: /^(\+41|0)7[6-9]\d{7}$/, // Swiss mobile
504      commonCities: [
505        'zurich',
506        'geneva',
507        'basel',
508        'lausanne',
509        'bern',
510        'winterthur',
511        'lucerne',
512        'st gallen',
513        'lugano',
514        'biel',
515      ],
516      requiresGDPRCheck: false, // Not EU but has similar laws
517      timezone: 'Europe/Zurich',
518    },
519  
520    // 17. Poland
521    PL: {
522      code: 'PL',
523      name: 'Poland',
524      googleDomain: 'google.pl',
525      language: 'pl',
526      currency: 'PLN',
527      currencySymbol: 'zł',
528      dateFormat: 'DD.MM.YYYY',
529      phoneFormat: '+48',
530      mobilePattern: /^(\+48|48)?[5-8]\d{8}$/, // Polish mobile
531      commonCities: [
532        'warsaw',
533        'krakow',
534        'lodz',
535        'wroclaw',
536        'poznan',
537        'gdansk',
538        'szczecin',
539        'bydgoszcz',
540        'lublin',
541        'katowice',
542      ],
543      requiresGDPRCheck: true,
544      companyTypes: ['Sp. z o.o.', 'S.A.', 'sp.j.', 'sp.k.'],
545      companyKeywords: ['NIP', 'REGON', 'KRS', 'Siedziba'],
546      keyPageNames: ['kontakt', 'o nas', 'informacje prawne', 'polityka prywatności'],
547      timezone: 'Europe/Warsaw',
548    },
549  
550    // 18. Belgium
551    BE: {
552      code: 'BE',
553      name: 'Belgium',
554      googleDomain: 'google.be',
555      language: 'nl', // Also fr, de
556      currency: 'EUR',
557      currencySymbol: '€',
558      dateFormat: 'DD/MM/YYYY',
559      phoneFormat: '+32',
560      mobilePattern: /^(\+32|0)4\d{8}$/, // Belgian mobile
561      commonCities: [
562        'brussels',
563        'antwerp',
564        'ghent',
565        'charleroi',
566        'liege',
567        'bruges',
568        'namur',
569        'leuven',
570        'mons',
571        'aalst',
572      ],
573      requiresGDPRCheck: true,
574      companyTypes: ['BV', 'NV', 'BVBA', 'SPRL'],
575      companyKeywords: ['Ondernemingsnummer', 'BTW-nummer', 'Maatschappelijke zetel'],
576      keyPageNames: ['contact', 'over ons', 'juridische informatie', 'privacy'],
577      timezone: 'Europe/Brussels',
578    },
579  
580    // 19. Sweden
581    SE: {
582      code: 'SE',
583      name: 'Sweden',
584      googleDomain: 'google.se',
585      language: 'sv',
586      currency: 'SEK',
587      currencySymbol: 'kr',
588      dateFormat: 'YYYY-MM-DD',
589      phoneFormat: '+46',
590      mobilePattern: /^(\+46|0)7[0-9]\d{7}$/, // Swedish mobile
591      commonCities: [
592        'stockholm',
593        'gothenburg',
594        'malmo',
595        'uppsala',
596        'vasteras',
597        'orebro',
598        'linkoping',
599        'helsingborg',
600        'jonkoping',
601        'norrkoping',
602      ],
603      requiresGDPRCheck: true,
604      companyTypes: ['AB', 'Aktiebolag'],
605      companyKeywords: ['Organisationsnummer', 'Momsregistreringsnummer', 'Säte'],
606      keyPageNames: ['kontakt', 'om oss', 'juridisk information', 'integritetspolicy'],
607      timezone: 'Europe/Stockholm',
608    },
609  
610    // 20. Austria
611    AT: {
612      code: 'AT',
613      name: 'Austria',
614      googleDomain: 'google.at',
615      language: 'de',
616      currency: 'EUR',
617      currencySymbol: '€',
618      dateFormat: 'DD.MM.YYYY',
619      phoneFormat: '+43',
620      mobilePattern: /^(\+43|0)6\d{8,9}$/, // Austrian mobile
621      commonCities: [
622        'vienna',
623        'graz',
624        'linz',
625        'salzburg',
626        'innsbruck',
627        'klagenfurt',
628        'villach',
629        'wels',
630        'st polten',
631        'dornbirn',
632      ],
633      requiresGDPRCheck: true,
634      companyTypes: ['GmbH', 'AG', 'OG', 'KG'],
635      companyKeywords: ['Firmenbuchnummer', 'UID-Nummer', 'Geschäftsführer', 'Firmensitz'],
636      keyPageNames: ['impressum', 'kontakt', 'über uns', 'rechtliches'],
637      timezone: 'Europe/Vienna',
638    },
639  
640    // 21. Norway
641    NO: {
642      code: 'NO',
643      name: 'Norway',
644      googleDomain: 'google.no',
645      language: 'en', // Temporarily using 'en' - DataForSEO doesn't support 'no'
646      currency: 'NOK',
647      currencySymbol: 'kr',
648      dateFormat: 'DD.MM.YYYY',
649      phoneFormat: '+47',
650      mobilePattern: /^(\+47|47)?[49]\d{7}$/, // Norwegian mobile
651      commonCities: [
652        'oslo',
653        'bergen',
654        'stavanger',
655        'trondheim',
656        'drammen',
657        'fredrikstad',
658        'kristiansand',
659        'sandnes',
660        'tromso',
661        'sarpsborg',
662      ],
663      requiresGDPRCheck: true,
664      companyTypes: ['AS', 'ASA', 'BA', 'ANS'],
665      companyKeywords: ['Organisasjonsnummer', 'MVA-nummer', 'Forretningsadresse'],
666      keyPageNames: ['kontakt', 'om oss', 'juridisk informasjon', 'personvern'],
667      timezone: 'Europe/Oslo',
668    },
669  
670    // 22. Denmark
671    DK: {
672      code: 'DK',
673      name: 'Denmark',
674      googleDomain: 'google.dk',
675      language: 'da',
676      currency: 'DKK',
677      currencySymbol: 'kr',
678      dateFormat: 'DD-MM-YYYY',
679      phoneFormat: '+45',
680      mobilePattern: /^(\+45|45)?[2-9]\d{7}$/, // Danish mobile
681      commonCities: [
682        'copenhagen',
683        'aarhus',
684        'odense',
685        'aalborg',
686        'esbjerg',
687        'randers',
688        'kolding',
689        'horsens',
690        'vejle',
691        'roskilde',
692      ],
693      requiresGDPRCheck: true,
694      companyTypes: ['ApS', 'A/S', 'I/S', 'K/S'],
695      companyKeywords: ['CVR-nummer', 'Momsregistreringsnummer', 'Hjemsted'],
696      keyPageNames: ['kontakt', 'om os', 'juridisk information', 'privatlivspolitik'],
697      timezone: 'Europe/Copenhagen',
698    },
699  
700    // 23. Singapore
701    SG: {
702      code: 'SG',
703      name: 'Singapore',
704      googleDomain: 'google.com.sg',
705      language: 'en',
706      currency: 'SGD',
707      currencySymbol: 'S$',
708      dateFormat: 'DD/MM/YYYY',
709      phoneFormat: '+65',
710      mobilePattern: /^(\+65|65)?[89]\d{7}$/, // Singapore mobile
711      commonCities: ['singapore', 'jurong west', 'woodlands', 'tampines', 'bedok'],
712      requiresGDPRCheck: false,
713      timezone: 'Asia/Singapore',
714    },
715  
716    // 24. New Zealand
717    NZ: {
718      code: 'NZ',
719      name: 'New Zealand',
720      googleDomain: 'google.co.nz',
721      language: 'en',
722      currency: 'NZD',
723      currencySymbol: '$',
724      dateFormat: 'DD/MM/YYYY',
725      phoneFormat: '+64',
726      mobilePattern: /^(\+64|0)(2[0-9]\d{7,8})$/, // NZ mobiles start with 02 or +642
727      commonCities: [
728        'auckland',
729        'wellington',
730        'christchurch',
731        'hamilton',
732        'tauranga',
733        'dunedin',
734        'palmerston north',
735        'napier',
736        'porirua',
737        'new plymouth',
738      ],
739      requiresGDPRCheck: false,
740      timezone: 'Pacific/Auckland',
741    },
742  
743    // 25. Ireland
744    IE: {
745      code: 'IE',
746      name: 'Ireland',
747      googleDomain: 'google.ie',
748      language: 'en',
749      currency: 'EUR',
750      currencySymbol: '€',
751      dateFormat: 'DD/MM/YYYY',
752      phoneFormat: '+353',
753      mobilePattern: /^(\+353|0)8[356789]\d{7}$/, // Irish mobile
754      commonCities: [
755        'dublin',
756        'cork',
757        'limerick',
758        'galway',
759        'waterford',
760        'drogheda',
761        'dundalk',
762        'swords',
763        'bray',
764        'navan',
765      ],
766      requiresGDPRCheck: true,
767      companyTypes: ['Ltd', 'Limited', 'PLC', 'Teoranta', 'Teo'],
768      individualIndicators: ['Sole trader', 'Sole proprietor', 'Partnership'],
769      companyKeywords: [
770        'Company number',
771        'Company No',
772        'Co. No',
773        'CRO number',
774        'Company registration',
775        'VAT number',
776        'VAT No',
777        'Registered office',
778        'Registered address',
779        'Director:',
780        'Directors:',
781      ],
782      keyPageNames: ['contact', 'about', 'legal', 'privacy policy'],
783      timezone: 'Europe/Dublin',
784    },
785  
786    // 26. South Africa
787    ZA: {
788      code: 'ZA',
789      name: 'South Africa',
790      googleDomain: 'google.co.za',
791      language: 'en',
792      currency: 'ZAR',
793      currencySymbol: 'R',
794      dateFormat: 'DD/MM/YYYY',
795      phoneFormat: '+27',
796      mobilePattern: /^(\+27|0)[67]\d{8}$/, // ZA mobiles: +27 6x/7x xxxxxxxx
797      commonCities: [
798        'johannesburg',
799        'cape town',
800        'durban',
801        'pretoria',
802        'port elizabeth',
803        'bloemfontein',
804        'east london',
805        'polokwane',
806        'nelspruit',
807        'kimberley',
808      ],
809      requiresGDPRCheck: false,
810      companyTypes: ['(Pty) Ltd', 'Pty Ltd', 'Ltd', 'CC', 'Inc'],
811      companyKeywords: ['Registration number', 'Reg no', 'VAT number'],
812      keyPageNames: ['contact', 'about', 'privacy policy'],
813      timezone: 'Africa/Johannesburg',
814    },
815  };
816  
817  /**
818   * Get country configuration by ISO country code
819   * @param {string} code - ISO 3166-1 alpha-2 country code (e.g., 'AU', 'US', 'UK')
820   * @returns {Object} Country configuration object
821   */
822  export function getCountryByCode(code) {
823    if (!code) {
824      throw new Error('Country code is required');
825    }
826  
827    // Handle country code aliases (GB is the official ISO code for UK)
828    const codeUpper = code.toUpperCase();
829    const aliases = {
830      GB: 'UK', // Great Britain → United Kingdom
831    };
832  
833    const lookupCode = aliases[codeUpper] || codeUpper;
834    const country = COUNTRIES[lookupCode];
835  
836    if (!country) {
837      return null; // Unknown code (e.g. 'EU') — callers should check for null
838    }
839  
840    return country;
841  }
842  
843  /**
844   * Get country configuration by Google domain
845   * @param {string} domain - Google domain (e.g., 'google.com.au', 'google.co.uk')
846   * @returns {Object} Country configuration object
847   */
848  export function getCountryByGoogleDomain(domain) {
849    if (!domain) {
850      throw new Error('Google domain is required');
851    }
852  
853    const country = Object.values(COUNTRIES).find(c => c.googleDomain === domain);
854    if (!country) {
855      const supportedDomains = Object.values(COUNTRIES)
856        .map(c => c.googleDomain)
857        .join(', ');
858      throw new Error(`Unknown Google domain: ${domain}. Supported domains: ${supportedDomains}`);
859    }
860  
861    return country;
862  }
863  
864  /**
865   * Check if a phone number matches the country's mobile pattern
866   * @param {string} phone - Phone number to validate
867   * @param {Object} country - Country configuration object
868   * @returns {boolean} True if the phone number is a mobile number
869   */
870  export function isMobileNumber(phone, country) {
871    if (!phone || !country || !country.mobilePattern) {
872      return false;
873    }
874  
875    // Remove all whitespace and common separators
876    const cleanPhone = phone.replace(/[\s\-().]/g, '');
877  
878    return country.mobilePattern.test(cleanPhone);
879  }
880  
881  /**
882   * Get list of all supported country codes
883   * @returns {string[]} Array of ISO country codes
884   */
885  export function getSupportedCountries() {
886    return Object.keys(COUNTRIES);
887  }
888  
889  /**
890   * Get list of countries that require GDPR compliance checks
891   * @returns {Object[]} Array of country configuration objects
892   */
893  export function getGDPRCountries() {
894    return Object.values(COUNTRIES).filter(c => c.requiresGDPRCheck);
895  }
896  
897  /**
898   * Normalise an inbound country code to the canonical code used in this codebase.
899   * Call this at every DB write point to prevent code drift (e.g. DataForSEO returns 'GB',
900   * we store 'UK'; without normalisation the two accumulate in the same column).
901   *
902   * Canonical codes match the keys in the COUNTRIES map above.
903   * Unknown codes are returned as-is (uppercased) — don't silently drop them.
904   *
905   * @param {string} code - Raw country code from any external source
906   * @returns {string} Canonical country code
907   */
908  export function normaliseCountryCode(code) {
909    if (!code) return code;
910    const upper = code.toUpperCase();
911    // Map external ISO codes → our canonical codes
912    const canonicalMap = {
913      GB: 'UK', // ISO 3166-1 alpha-2 for United Kingdom; we use 'UK'
914    };
915    return canonicalMap[upper] ?? upper;
916  }
917  
918  /**
919   * Free email provider domains (used for GDPR filtering)
920   */
921  export const FREE_EMAIL_PROVIDERS = [
922    'gmail.com',
923    'googlemail.com',
924    'outlook.com',
925    'hotmail.com',
926    'live.com',
927    'yahoo.com',
928    'yahoo.co.uk',
929    'yahoo.de',
930    'yahoo.fr',
931    'yahoo.es',
932    'yahoo.it',
933    'gmx.com',
934    'gmx.de',
935    'gmx.net',
936    'gmx.at',
937    'gmx.ch',
938    'web.de',
939    'mail.com',
940    'icloud.com',
941    'me.com',
942    'mac.com',
943    'aol.com',
944    'protonmail.com',
945    'proton.me',
946    'tutanota.com',
947    'zoho.com',
948    'yandex.com',
949    'mail.ru',
950    'qq.com',
951    '163.com',
952    '126.com',
953  ];
954  
955  /**
956   * Check if an email address is from a free email provider
957   * @param {string} email - Email address to check
958   * @returns {boolean} True if the email is from a free provider
959   */
960  export function isFreeEmailProvider(email) {
961    if (!email || typeof email !== 'string') {
962      return false;
963    }
964  
965    const domain = email.toLowerCase().split('@')[1];
966    return FREE_EMAIL_PROVIDERS.includes(domain);
967  }