fix(ui): полный справочник стран и городов как в VPS Tracker
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 4s
quality / docker-check (push) Skipped
quality / web (push) Successful in 45s
quality / api (push) Successful in 36s
CD / quality (push) Successful in 1m30s
CD / publish (push) Successful in 1m51s
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 4s
quality / docker-check (push) Skipped
quality / web (push) Successful in 45s
quality / api (push) Successful in 36s
CD / quality (push) Successful in 1m30s
CD / publish (push) Successful in 1m51s
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
+467
@@ -0,0 +1,467 @@
|
||||
// src/geo/countries.ts
|
||||
var COUNTRIES = [
|
||||
{ code: "AU", name: "\u0410\u0432\u0441\u0442\u0440\u0430\u043B\u0438\u044F", nameEn: "Australia" },
|
||||
{ code: "AT", name: "\u0410\u0432\u0441\u0442\u0440\u0438\u044F", nameEn: "Austria" },
|
||||
{ code: "AZ", name: "\u0410\u0437\u0435\u0440\u0431\u0430\u0439\u0434\u0436\u0430\u043D", nameEn: "Azerbaijan" },
|
||||
{ code: "AL", name: "\u0410\u043B\u0431\u0430\u043D\u0438\u044F", nameEn: "Albania" },
|
||||
{ code: "DZ", name: "\u0410\u043B\u0436\u0438\u0440", nameEn: "Algeria" },
|
||||
{ code: "AO", name: "\u0410\u043D\u0433\u043E\u043B\u0430", nameEn: "Angola" },
|
||||
{ code: "AR", name: "\u0410\u0440\u0433\u0435\u043D\u0442\u0438\u043D\u0430", nameEn: "Argentina" },
|
||||
{ code: "AM", name: "\u0410\u0440\u043C\u0435\u043D\u0438\u044F", nameEn: "Armenia" },
|
||||
{ code: "AF", name: "\u0410\u0444\u0433\u0430\u043D\u0438\u0441\u0442\u0430\u043D", nameEn: "Afghanistan" },
|
||||
{ code: "BE", name: "\u0411\u0435\u043B\u044C\u0433\u0438\u044F", nameEn: "Belgium" },
|
||||
{ code: "BG", name: "\u0411\u043E\u043B\u0433\u0430\u0440\u0438\u044F", nameEn: "Bulgaria" },
|
||||
{ code: "BO", name: "\u0411\u043E\u043B\u0438\u0432\u0438\u044F", nameEn: "Bolivia" },
|
||||
{ code: "BA", name: "\u0411\u043E\u0441\u043D\u0438\u044F \u0438 \u0413\u0435\u0440\u0446\u0435\u0433\u043E\u0432\u0438\u043D\u0430", nameEn: "Bosnia and Herzegovina" },
|
||||
{ code: "BR", name: "\u0411\u0440\u0430\u0437\u0438\u043B\u0438\u044F", nameEn: "Brazil" },
|
||||
{ code: "GB", name: "\u0412\u0435\u043B\u0438\u043A\u043E\u0431\u0440\u0438\u0442\u0430\u043D\u0438\u044F", nameEn: "United Kingdom" },
|
||||
{ code: "HU", name: "\u0412\u0435\u043D\u0433\u0440\u0438\u044F", nameEn: "Hungary" },
|
||||
{ code: "VE", name: "\u0412\u0435\u043D\u0435\u0441\u0443\u044D\u043B\u0430", nameEn: "Venezuela" },
|
||||
{ code: "VN", name: "\u0412\u044C\u0435\u0442\u043D\u0430\u043C", nameEn: "Vietnam" },
|
||||
{ code: "GA", name: "\u0413\u0430\u0431\u043E\u043D", nameEn: "Gabon" },
|
||||
{ code: "HT", name: "\u0413\u0430\u0438\u0442\u0438", nameEn: "Haiti" },
|
||||
{ code: "GY", name: "\u0413\u0430\u0439\u0430\u043D\u0430", nameEn: "Guyana" },
|
||||
{ code: "GM", name: "\u0413\u0430\u043C\u0431\u0438\u044F", nameEn: "Gambia" },
|
||||
{ code: "GH", name: "\u0413\u0430\u043D\u0430", nameEn: "Ghana" },
|
||||
{ code: "GT", name: "\u0413\u0432\u0430\u0442\u0435\u043C\u0430\u043B\u0430", nameEn: "Guatemala" },
|
||||
{ code: "GN", name: "\u0413\u0432\u0438\u043D\u0435\u044F", nameEn: "Guinea" },
|
||||
{ code: "DE", name: "\u0413\u0435\u0440\u043C\u0430\u043D\u0438\u044F", nameEn: "Germany" },
|
||||
{ code: "HN", name: "\u0413\u043E\u043D\u0434\u0443\u0440\u0430\u0441", nameEn: "Honduras" },
|
||||
{ code: "GR", name: "\u0413\u0440\u0435\u0446\u0438\u044F", nameEn: "Greece" },
|
||||
{ code: "GE", name: "\u0413\u0440\u0443\u0437\u0438\u044F", nameEn: "Georgia" },
|
||||
{ code: "DK", name: "\u0414\u0430\u043D\u0438\u044F", nameEn: "Denmark" },
|
||||
{ code: "CD", name: "\u0414\u0420 \u041A\u043E\u043D\u0433\u043E", nameEn: "DR Congo" },
|
||||
{ code: "EG", name: "\u0415\u0433\u0438\u043F\u0435\u0442", nameEn: "Egypt" },
|
||||
{ code: "ZM", name: "\u0417\u0430\u043C\u0431\u0438\u044F", nameEn: "Zambia" },
|
||||
{ code: "ZW", name: "\u0417\u0438\u043C\u0431\u0430\u0431\u0432\u0435", nameEn: "Zimbabwe" },
|
||||
{ code: "IL", name: "\u0418\u0437\u0440\u0430\u0438\u043B\u044C", nameEn: "Israel" },
|
||||
{ code: "IN", name: "\u0418\u043D\u0434\u0438\u044F", nameEn: "India" },
|
||||
{ code: "ID", name: "\u0418\u043D\u0434\u043E\u043D\u0435\u0437\u0438\u044F", nameEn: "Indonesia" },
|
||||
{ code: "JO", name: "\u0418\u043E\u0440\u0434\u0430\u043D\u0438\u044F", nameEn: "Jordan" },
|
||||
{ code: "IQ", name: "\u0418\u0440\u0430\u043A", nameEn: "Iraq" },
|
||||
{ code: "IR", name: "\u0418\u0440\u0430\u043D", nameEn: "Iran" },
|
||||
{ code: "IE", name: "\u0418\u0440\u043B\u0430\u043D\u0434\u0438\u044F", nameEn: "Ireland" },
|
||||
{ code: "IS", name: "\u0418\u0441\u043B\u0430\u043D\u0434\u0438\u044F", nameEn: "Iceland" },
|
||||
{ code: "ES", name: "\u0418\u0441\u043F\u0430\u043D\u0438\u044F", nameEn: "Spain" },
|
||||
{ code: "IT", name: "\u0418\u0442\u0430\u043B\u0438\u044F", nameEn: "Italy" },
|
||||
{ code: "YE", name: "\u0419\u0435\u043C\u0435\u043D", nameEn: "Yemen" },
|
||||
{ code: "KZ", name: "\u041A\u0430\u0437\u0430\u0445\u0441\u0442\u0430\u043D", nameEn: "Kazakhstan" },
|
||||
{ code: "KH", name: "\u041A\u0430\u043C\u0431\u043E\u0434\u0436\u0430", nameEn: "Cambodia" },
|
||||
{ code: "CM", name: "\u041A\u0430\u043C\u0435\u0440\u0443\u043D", nameEn: "Cameroon" },
|
||||
{ code: "CA", name: "\u041A\u0430\u043D\u0430\u0434\u0430", nameEn: "Canada" },
|
||||
{ code: "QA", name: "\u041A\u0430\u0442\u0430\u0440", nameEn: "Qatar" },
|
||||
{ code: "KE", name: "\u041A\u0435\u043D\u0438\u044F", nameEn: "Kenya" },
|
||||
{ code: "CY", name: "\u041A\u0438\u043F\u0440", nameEn: "Cyprus" },
|
||||
{ code: "KG", name: "\u041A\u0438\u0440\u0433\u0438\u0437\u0438\u044F", nameEn: "Kyrgyzstan" },
|
||||
{ code: "CN", name: "\u041A\u0438\u0442\u0430\u0439", nameEn: "China" },
|
||||
{ code: "CO", name: "\u041A\u043E\u043B\u0443\u043C\u0431\u0438\u044F", nameEn: "Colombia" },
|
||||
{ code: "CG", name: "\u041A\u043E\u043D\u0433\u043E", nameEn: "Congo" },
|
||||
{ code: "KR", name: "\u042E\u0436\u043D\u0430\u044F \u041A\u043E\u0440\u0435\u044F", nameEn: "South Korea" },
|
||||
{ code: "KP", name: "\u0421\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u041A\u043E\u0440\u0435\u044F", nameEn: "North Korea" },
|
||||
{ code: "XK", name: "\u041A\u043E\u0441\u043E\u0432\u043E", nameEn: "Kosovo" },
|
||||
{ code: "CR", name: "\u041A\u043E\u0441\u0442\u0430-\u0420\u0438\u043A\u0430", nameEn: "Costa Rica" },
|
||||
{ code: "CI", name: "\u041A\u043E\u0442-\u0434'\u0418\u0432\u0443\u0430\u0440", nameEn: "C\xF4te d'Ivoire" },
|
||||
{ code: "CU", name: "\u041A\u0443\u0431\u0430", nameEn: "Cuba" },
|
||||
{ code: "KW", name: "\u041A\u0443\u0432\u0435\u0439\u0442", nameEn: "Kuwait" },
|
||||
{ code: "LA", name: "\u041B\u0430\u043E\u0441", nameEn: "Laos" },
|
||||
{ code: "LV", name: "\u041B\u0430\u0442\u0432\u0438\u044F", nameEn: "Latvia" },
|
||||
{ code: "LR", name: "\u041B\u0438\u0431\u0435\u0440\u0438\u044F", nameEn: "Liberia" },
|
||||
{ code: "LB", name: "\u041B\u0438\u0432\u0430\u043D", nameEn: "Lebanon" },
|
||||
{ code: "LY", name: "\u041B\u0438\u0432\u0438\u044F", nameEn: "Libya" },
|
||||
{ code: "LT", name: "\u041B\u0438\u0442\u0432\u0430", nameEn: "Lithuania" },
|
||||
{ code: "LI", name: "\u041B\u0438\u0445\u0442\u0435\u043D\u0448\u0442\u0435\u0439\u043D", nameEn: "Liechtenstein" },
|
||||
{ code: "LU", name: "\u041B\u044E\u043A\u0441\u0435\u043C\u0431\u0443\u0440\u0433", nameEn: "Luxembourg" },
|
||||
{ code: "MU", name: "\u041C\u0430\u0432\u0440\u0438\u043A\u0438\u0439", nameEn: "Mauritius" },
|
||||
{ code: "MR", name: "\u041C\u0430\u0432\u0440\u0438\u0442\u0430\u043D\u0438\u044F", nameEn: "Mauritania" },
|
||||
{ code: "MG", name: "\u041C\u0430\u0434\u0430\u0433\u0430\u0441\u043A\u0430\u0440", nameEn: "Madagascar" },
|
||||
{ code: "MO", name: "\u041C\u0430\u043A\u0430\u043E", nameEn: "Macao" },
|
||||
{ code: "MW", name: "\u041C\u0430\u043B\u0430\u0432\u0438", nameEn: "Malawi" },
|
||||
{ code: "MY", name: "\u041C\u0430\u043B\u0430\u0439\u0437\u0438\u044F", nameEn: "Malaysia" },
|
||||
{ code: "ML", name: "\u041C\u0430\u043B\u0438", nameEn: "Mali" },
|
||||
{ code: "MT", name: "\u041C\u0430\u043B\u044C\u0442\u0430", nameEn: "Malta" },
|
||||
{ code: "MA", name: "\u041C\u0430\u0440\u043E\u043A\u043A\u043E", nameEn: "Morocco" },
|
||||
{ code: "MX", name: "\u041C\u0435\u043A\u0441\u0438\u043A\u0430", nameEn: "Mexico" },
|
||||
{ code: "MZ", name: "\u041C\u043E\u0437\u0430\u043C\u0431\u0438\u043A", nameEn: "Mozambique" },
|
||||
{ code: "MD", name: "\u041C\u043E\u043B\u0434\u0430\u0432\u0438\u044F", nameEn: "Moldova" },
|
||||
{ code: "MC", name: "\u041C\u043E\u043D\u0430\u043A\u043E", nameEn: "Monaco" },
|
||||
{ code: "MN", name: "\u041C\u043E\u043D\u0433\u043E\u043B\u0438\u044F", nameEn: "Mongolia" },
|
||||
{ code: "MM", name: "\u041C\u044C\u044F\u043D\u043C\u0430", nameEn: "Myanmar" },
|
||||
{ code: "NA", name: "\u041D\u0430\u043C\u0438\u0431\u0438\u044F", nameEn: "Namibia" },
|
||||
{ code: "NP", name: "\u041D\u0435\u043F\u0430\u043B", nameEn: "Nepal" },
|
||||
{ code: "NE", name: "\u041D\u0438\u0433\u0435\u0440", nameEn: "Niger" },
|
||||
{ code: "NG", name: "\u041D\u0438\u0433\u0435\u0440\u0438\u044F", nameEn: "Nigeria" },
|
||||
{ code: "NL", name: "\u041D\u0438\u0434\u0435\u0440\u043B\u0430\u043D\u0434\u044B", nameEn: "Netherlands" },
|
||||
{ code: "NI", name: "\u041D\u0438\u043A\u0430\u0440\u0430\u0433\u0443\u0430", nameEn: "Nicaragua" },
|
||||
{ code: "NZ", name: "\u041D\u043E\u0432\u0430\u044F \u0417\u0435\u043B\u0430\u043D\u0434\u0438\u044F", nameEn: "New Zealand" },
|
||||
{ code: "NO", name: "\u041D\u043E\u0440\u0432\u0435\u0433\u0438\u044F", nameEn: "Norway" },
|
||||
{ code: "AE", name: "\u041E\u0410\u042D", nameEn: "United Arab Emirates" },
|
||||
{ code: "OM", name: "\u041E\u043C\u0430\u043D", nameEn: "Oman" },
|
||||
{ code: "PK", name: "\u041F\u0430\u043A\u0438\u0441\u0442\u0430\u043D", nameEn: "Pakistan" },
|
||||
{ code: "PA", name: "\u041F\u0430\u043D\u0430\u043C\u0430", nameEn: "Panama" },
|
||||
{ code: "PG", name: "\u041F\u0430\u043F\u0443\u0430 \u2014 \u041D\u043E\u0432\u0430\u044F \u0413\u0432\u0438\u043D\u0435\u044F", nameEn: "Papua New Guinea" },
|
||||
{ code: "PY", name: "\u041F\u0430\u0440\u0430\u0433\u0432\u0430\u0439", nameEn: "Paraguay" },
|
||||
{ code: "PE", name: "\u041F\u0435\u0440\u0443", nameEn: "Peru" },
|
||||
{ code: "PL", name: "\u041F\u043E\u043B\u044C\u0448\u0430", nameEn: "Poland" },
|
||||
{ code: "PT", name: "\u041F\u043E\u0440\u0442\u0443\u0433\u0430\u043B\u0438\u044F", nameEn: "Portugal" },
|
||||
{ code: "RU", name: "\u0420\u043E\u0441\u0441\u0438\u044F", nameEn: "Russia" },
|
||||
{ code: "RW", name: "\u0420\u0443\u0430\u043D\u0434\u0430", nameEn: "Rwanda" },
|
||||
{ code: "RO", name: "\u0420\u0443\u043C\u044B\u043D\u0438\u044F", nameEn: "Romania" },
|
||||
{ code: "SV", name: "\u0421\u0430\u043B\u044C\u0432\u0430\u0434\u043E\u0440", nameEn: "El Salvador" },
|
||||
{ code: "SA", name: "\u0421\u0430\u0443\u0434\u043E\u0432\u0441\u043A\u0430\u044F \u0410\u0440\u0430\u0432\u0438\u044F", nameEn: "Saudi Arabia" },
|
||||
{ code: "MK", name: "\u0421\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u041C\u0430\u043A\u0435\u0434\u043E\u043D\u0438\u044F", nameEn: "North Macedonia" },
|
||||
{ code: "SC", name: "\u0421\u0435\u0439\u0448\u0435\u043B\u044B", nameEn: "Seychelles" },
|
||||
{ code: "SN", name: "\u0421\u0435\u043D\u0435\u0433\u0430\u043B", nameEn: "Senegal" },
|
||||
{ code: "RS", name: "\u0421\u0435\u0440\u0431\u0438\u044F", nameEn: "Serbia" },
|
||||
{ code: "SG", name: "\u0421\u0438\u043D\u0433\u0430\u043F\u0443\u0440", nameEn: "Singapore" },
|
||||
{ code: "SK", name: "\u0421\u043B\u043E\u0432\u0430\u043A\u0438\u044F", nameEn: "Slovakia" },
|
||||
{ code: "SI", name: "\u0421\u043B\u043E\u0432\u0435\u043D\u0438\u044F", nameEn: "Slovenia" },
|
||||
{ code: "SO", name: "\u0421\u043E\u043C\u0430\u043B\u0438", nameEn: "Somalia" },
|
||||
{ code: "SD", name: "\u0421\u0443\u0434\u0430\u043D", nameEn: "Sudan" },
|
||||
{ code: "US", name: "\u0421\u0428\u0410", nameEn: "United States" },
|
||||
{ code: "TJ", name: "\u0422\u0430\u0434\u0436\u0438\u043A\u0438\u0441\u0442\u0430\u043D", nameEn: "Tajikistan" },
|
||||
{ code: "TH", name: "\u0422\u0430\u0438\u043B\u0430\u043D\u0434", nameEn: "Thailand" },
|
||||
{ code: "TZ", name: "\u0422\u0430\u043D\u0437\u0430\u043D\u0438\u044F", nameEn: "Tanzania" },
|
||||
{ code: "TG", name: "\u0422\u043E\u0433\u043E", nameEn: "Togo" },
|
||||
{ code: "TT", name: "\u0422\u0440\u0438\u043D\u0438\u0434\u0430\u0434 \u0438 \u0422\u043E\u0431\u0430\u0433\u043E", nameEn: "Trinidad and Tobago" },
|
||||
{ code: "TV", name: "\u0422\u0443\u0432\u0430\u043B\u0443", nameEn: "Tuvalu" },
|
||||
{ code: "TN", name: "\u0422\u0443\u043D\u0438\u0441", nameEn: "Tunisia" },
|
||||
{ code: "TM", name: "\u0422\u0443\u0440\u043A\u043C\u0435\u043D\u0438\u044F", nameEn: "Turkmenistan" },
|
||||
{ code: "TR", name: "\u0422\u0443\u0440\u0446\u0438\u044F", nameEn: "Turkey" },
|
||||
{ code: "UG", name: "\u0423\u0433\u0430\u043D\u0434\u0430", nameEn: "Uganda" },
|
||||
{ code: "UZ", name: "\u0423\u0437\u0431\u0435\u043A\u0438\u0441\u0442\u0430\u043D", nameEn: "Uzbekistan" },
|
||||
{ code: "UA", name: "\u0423\u043A\u0440\u0430\u0438\u043D\u0430", nameEn: "Ukraine" },
|
||||
{ code: "UY", name: "\u0423\u0440\u0443\u0433\u0432\u0430\u0439", nameEn: "Uruguay" },
|
||||
{ code: "FJ", name: "\u0424\u0438\u0434\u0436\u0438", nameEn: "Fiji" },
|
||||
{ code: "PH", name: "\u0424\u0438\u043B\u0438\u043F\u043F\u0438\u043D\u044B", nameEn: "Philippines" },
|
||||
{ code: "FI", name: "\u0424\u0438\u043D\u043B\u044F\u043D\u0434\u0438\u044F", nameEn: "Finland" },
|
||||
{ code: "FR", name: "\u0424\u0440\u0430\u043D\u0446\u0438\u044F", nameEn: "France" },
|
||||
{ code: "HR", name: "\u0425\u043E\u0440\u0432\u0430\u0442\u0438\u044F", nameEn: "Croatia" },
|
||||
{ code: "TD", name: "\u0427\u0430\u0434", nameEn: "Chad" },
|
||||
{ code: "ME", name: "\u0427\u0435\u0440\u043D\u043E\u0433\u043E\u0440\u0438\u044F", nameEn: "Montenegro" },
|
||||
{ code: "CZ", name: "\u0427\u0435\u0445\u0438\u044F", nameEn: "Czechia" },
|
||||
{ code: "CL", name: "\u0427\u0438\u043B\u0438", nameEn: "Chile" },
|
||||
{ code: "CH", name: "\u0428\u0432\u0435\u0439\u0446\u0430\u0440\u0438\u044F", nameEn: "Switzerland" },
|
||||
{ code: "SE", name: "\u0428\u0432\u0435\u0446\u0438\u044F", nameEn: "Sweden" },
|
||||
{ code: "LK", name: "\u0428\u0440\u0438-\u041B\u0430\u043D\u043A\u0430", nameEn: "Sri Lanka" },
|
||||
{ code: "EC", name: "\u042D\u043A\u0432\u0430\u0434\u043E\u0440", nameEn: "Ecuador" },
|
||||
{ code: "GQ", name: "\u042D\u043A\u0432\u0430\u0442\u043E\u0440\u0438\u0430\u043B\u044C\u043D\u0430\u044F \u0413\u0432\u0438\u043D\u0435\u044F", nameEn: "Equatorial Guinea" },
|
||||
{ code: "ER", name: "\u042D\u0440\u0438\u0442\u0440\u0435\u044F", nameEn: "Eritrea" },
|
||||
{ code: "EE", name: "\u042D\u0441\u0442\u043E\u043D\u0438\u044F", nameEn: "Estonia" },
|
||||
{ code: "ET", name: "\u042D\u0444\u0438\u043E\u043F\u0438\u044F", nameEn: "Ethiopia" },
|
||||
{ code: "ZA", name: "\u042E\u0410\u0420", nameEn: "South Africa" },
|
||||
{ code: "JM", name: "\u042F\u043C\u0430\u0439\u043A\u0430", nameEn: "Jamaica" },
|
||||
{ code: "JP", name: "\u042F\u043F\u043E\u043D\u0438\u044F", nameEn: "Japan" }
|
||||
];
|
||||
var COUNTRY_BY_CODE = Object.fromEntries(
|
||||
COUNTRIES.map((c) => [c.code, c])
|
||||
);
|
||||
var COUNTRY_BY_NAME_RU = Object.fromEntries(
|
||||
COUNTRIES.map((c) => [c.name.toLowerCase(), c])
|
||||
);
|
||||
|
||||
// src/geo/cities.ts
|
||||
var CITIES = [
|
||||
// Россия
|
||||
{ countryCode: "RU", name: "\u041C\u043E\u0441\u043A\u0432\u0430", nameEn: "Moscow" },
|
||||
{ countryCode: "RU", name: "\u0421\u0430\u043D\u043A\u0442-\u041F\u0435\u0442\u0435\u0440\u0431\u0443\u0440\u0433", nameEn: "Saint Petersburg" },
|
||||
{ countryCode: "RU", name: "\u041D\u043E\u0432\u043E\u0441\u0438\u0431\u0438\u0440\u0441\u043A", nameEn: "Novosibirsk" },
|
||||
{ countryCode: "RU", name: "\u0415\u043A\u0430\u0442\u0435\u0440\u0438\u043D\u0431\u0443\u0440\u0433", nameEn: "Yekaterinburg" },
|
||||
{ countryCode: "RU", name: "\u041A\u0430\u0437\u0430\u043D\u044C", nameEn: "Kazan" },
|
||||
{ countryCode: "RU", name: "\u041D\u0438\u0436\u043D\u0438\u0439 \u041D\u043E\u0432\u0433\u043E\u0440\u043E\u0434", nameEn: "Nizhny Novgorod" },
|
||||
{ countryCode: "RU", name: "\u0421\u0430\u043C\u0430\u0440\u0430", nameEn: "Samara" },
|
||||
{ countryCode: "RU", name: "\u0420\u043E\u0441\u0442\u043E\u0432-\u043D\u0430-\u0414\u043E\u043D\u0443", nameEn: "Rostov-on-Don" },
|
||||
{ countryCode: "RU", name: "\u0423\u0444\u0430", nameEn: "Ufa" },
|
||||
{ countryCode: "RU", name: "\u041A\u0440\u0430\u0441\u043D\u043E\u0434\u0430\u0440", nameEn: "Krasnodar" },
|
||||
{ countryCode: "RU", name: "\u0421\u043E\u0447\u0438", nameEn: "Sochi" },
|
||||
{ countryCode: "RU", name: "\u0412\u043B\u0430\u0434\u0438\u0432\u043E\u0441\u0442\u043E\u043A", nameEn: "Vladivostok" },
|
||||
{ countryCode: "RU", name: "\u0418\u0440\u043A\u0443\u0442\u0441\u043A", nameEn: "Irkutsk" },
|
||||
{ countryCode: "RU", name: "\u0425\u0430\u0431\u0430\u0440\u043E\u0432\u0441\u043A", nameEn: "Khabarovsk" },
|
||||
{ countryCode: "RU", name: "\u0422\u044E\u043C\u0435\u043D\u044C", nameEn: "Tyumen" },
|
||||
// Нидерланды
|
||||
{ countryCode: "NL", name: "\u0410\u043C\u0441\u0442\u0435\u0440\u0434\u0430\u043C", nameEn: "Amsterdam" },
|
||||
{ countryCode: "NL", name: "\u0420\u043E\u0442\u0442\u0435\u0440\u0434\u0430\u043C", nameEn: "Rotterdam" },
|
||||
{ countryCode: "NL", name: "\u0413\u0430\u0430\u0433\u0430", nameEn: "The Hague" },
|
||||
// Германия
|
||||
{ countryCode: "DE", name: "\u0424\u0440\u0430\u043D\u043A\u0444\u0443\u0440\u0442", nameEn: "Frankfurt" },
|
||||
{ countryCode: "DE", name: "\u0411\u0435\u0440\u043B\u0438\u043D", nameEn: "Berlin" },
|
||||
{ countryCode: "DE", name: "\u041C\u044E\u043D\u0445\u0435\u043D", nameEn: "Munich" },
|
||||
{ countryCode: "DE", name: "\u0413\u0430\u043C\u0431\u0443\u0440\u0433", nameEn: "Hamburg" },
|
||||
{ countryCode: "DE", name: "\u041A\u0451\u043B\u044C\u043D", nameEn: "Cologne" },
|
||||
{ countryCode: "DE", name: "\u0414\u044E\u0441\u0441\u0435\u043B\u044C\u0434\u043E\u0440\u0444", nameEn: "D\xFCsseldorf" },
|
||||
{ countryCode: "DE", name: "\u0428\u0442\u0443\u0442\u0433\u0430\u0440\u0442", nameEn: "Stuttgart" },
|
||||
// Финляндия
|
||||
{ countryCode: "FI", name: "\u0425\u0435\u043B\u044C\u0441\u0438\u043D\u043A\u0438", nameEn: "Helsinki" },
|
||||
{ countryCode: "FI", name: "\u042D\u0441\u043F\u043E\u043E", nameEn: "Espoo" },
|
||||
{ countryCode: "FI", name: "\u0422\u0430\u043C\u043F\u0435\u0440\u0435", nameEn: "Tampere" },
|
||||
// Швеция
|
||||
{ countryCode: "SE", name: "\u0421\u0442\u043E\u043A\u0433\u043E\u043B\u044C\u043C", nameEn: "Stockholm" },
|
||||
{ countryCode: "SE", name: "\u0413\u0451\u0442\u0435\u0431\u043E\u0440\u0433", nameEn: "Gothenburg" },
|
||||
{ countryCode: "SE", name: "\u041C\u0430\u043B\u044C\u043C\u0451", nameEn: "Malm\xF6" },
|
||||
// Норвегия
|
||||
{ countryCode: "NO", name: "\u041E\u0441\u043B\u043E", nameEn: "Oslo" },
|
||||
{ countryCode: "NO", name: "\u0411\u0435\u0440\u0433\u0435\u043D", nameEn: "Bergen" },
|
||||
// Дания
|
||||
{ countryCode: "DK", name: "\u041A\u043E\u043F\u0435\u043D\u0433\u0430\u0433\u0435\u043D", nameEn: "Copenhagen" },
|
||||
{ countryCode: "DK", name: "\u041E\u0440\u0445\u0443\u0441", nameEn: "Aarhus" },
|
||||
// Великобритания
|
||||
{ countryCode: "GB", name: "\u041B\u043E\u043D\u0434\u043E\u043D", nameEn: "London" },
|
||||
{ countryCode: "GB", name: "\u041C\u0430\u043D\u0447\u0435\u0441\u0442\u0435\u0440", nameEn: "Manchester" },
|
||||
{ countryCode: "GB", name: "\u0413\u043B\u0430\u0437\u0433\u043E", nameEn: "Glasgow" },
|
||||
{ countryCode: "GB", name: "\u041A\u0430\u0440\u0434\u0438\u0444\u0444", nameEn: "Cardiff" },
|
||||
// Франция
|
||||
{ countryCode: "FR", name: "\u041F\u0430\u0440\u0438\u0436", nameEn: "Paris" },
|
||||
{ countryCode: "FR", name: "\u041C\u0430\u0440\u0441\u0435\u043B\u044C", nameEn: "Marseille" },
|
||||
{ countryCode: "FR", name: "\u041B\u0438\u043E\u043D", nameEn: "Lyon" },
|
||||
{ countryCode: "FR", name: "\u0421\u0442\u0440\u0430\u0441\u0431\u0443\u0440\u0433", nameEn: "Strasbourg" },
|
||||
// Испания
|
||||
{ countryCode: "ES", name: "\u041C\u0430\u0434\u0440\u0438\u0434", nameEn: "Madrid" },
|
||||
{ countryCode: "ES", name: "\u0411\u0430\u0440\u0441\u0435\u043B\u043E\u043D\u0430", nameEn: "Barcelona" },
|
||||
{ countryCode: "ES", name: "\u0412\u0430\u043B\u0435\u043D\u0441\u0438\u044F", nameEn: "Valencia" },
|
||||
// Италия
|
||||
{ countryCode: "IT", name: "\u0420\u0438\u043C", nameEn: "Rome" },
|
||||
{ countryCode: "IT", name: "\u041C\u0438\u043B\u0430\u043D", nameEn: "Milan" },
|
||||
{ countryCode: "IT", name: "\u0422\u0443\u0440\u0438\u043D", nameEn: "Turin" },
|
||||
// Чехия
|
||||
{ countryCode: "CZ", name: "\u041F\u0440\u0430\u0433\u0430", nameEn: "Prague" },
|
||||
{ countryCode: "CZ", name: "\u0411\u0440\u043D\u043E", nameEn: "Brno" },
|
||||
// Польша
|
||||
{ countryCode: "PL", name: "\u0412\u0430\u0440\u0448\u0430\u0432\u0430", nameEn: "Warsaw" },
|
||||
{ countryCode: "PL", name: "\u041A\u0440\u0430\u043A\u043E\u0432", nameEn: "Krak\xF3w" },
|
||||
{ countryCode: "PL", name: "\u0412\u0440\u043E\u0446\u043B\u0430\u0432", nameEn: "Wroc\u0142aw" },
|
||||
{ countryCode: "PL", name: "\u0413\u0434\u0430\u043D\u044C\u0441\u043A", nameEn: "Gda\u0144sk" },
|
||||
// Швейцария
|
||||
{ countryCode: "CH", name: "\u0426\u044E\u0440\u0438\u0445", nameEn: "Zurich" },
|
||||
{ countryCode: "CH", name: "\u0416\u0435\u043D\u0435\u0432\u0430", nameEn: "Geneva" },
|
||||
{ countryCode: "CH", name: "\u0411\u0430\u0437\u0435\u043B\u044C", nameEn: "Basel" },
|
||||
// Бельгия
|
||||
{ countryCode: "BE", name: "\u0411\u0440\u044E\u0441\u0441\u0435\u043B\u044C", nameEn: "Brussels" },
|
||||
{ countryCode: "BE", name: "\u0410\u043D\u0442\u0432\u0435\u0440\u043F\u0435\u043D", nameEn: "Antwerp" },
|
||||
// Австрия
|
||||
{ countryCode: "AT", name: "\u0412\u0435\u043D\u0430", nameEn: "Vienna" },
|
||||
{ countryCode: "AT", name: "\u0413\u0440\u0430\u0446", nameEn: "Graz" },
|
||||
// Латвия
|
||||
{ countryCode: "LV", name: "\u0420\u0438\u0433\u0430", nameEn: "Riga" },
|
||||
// Литва
|
||||
{ countryCode: "LT", name: "\u0412\u0438\u043B\u044C\u043D\u044E\u0441", nameEn: "Vilnius" },
|
||||
{ countryCode: "LT", name: "\u041A\u0430\u0443\u043D\u0430\u0441", nameEn: "Kaunas" },
|
||||
// Эстония
|
||||
{ countryCode: "EE", name: "\u0422\u0430\u043B\u043B\u0438\u043D", nameEn: "Tallinn" },
|
||||
// Украина
|
||||
{ countryCode: "UA", name: "\u041A\u0438\u0435\u0432", nameEn: "Kyiv" },
|
||||
{ countryCode: "UA", name: "\u0425\u0430\u0440\u044C\u043A\u043E\u0432", nameEn: "Kharkiv" },
|
||||
{ countryCode: "UA", name: "\u041E\u0434\u0435\u0441\u0441\u0430", nameEn: "Odesa" },
|
||||
{ countryCode: "UA", name: "\u041B\u044C\u0432\u043E\u0432", nameEn: "Lviv" },
|
||||
// Казахстан
|
||||
{ countryCode: "KZ", name: "\u0410\u043B\u043C\u0430\u0442\u044B", nameEn: "Almaty" },
|
||||
{ countryCode: "KZ", name: "\u0410\u0441\u0442\u0430\u043D\u0430", nameEn: "Astana" },
|
||||
// Беларусь
|
||||
{ countryCode: "BY", name: "\u041C\u0438\u043D\u0441\u043A", nameEn: "Minsk" },
|
||||
// США
|
||||
{ countryCode: "US", name: "\u041D\u044C\u044E-\u0419\u043E\u0440\u043A", nameEn: "New York" },
|
||||
{ countryCode: "US", name: "\u041B\u043E\u0441-\u0410\u043D\u0434\u0436\u0435\u043B\u0435\u0441", nameEn: "Los Angeles" },
|
||||
{ countryCode: "US", name: "\u0427\u0438\u043A\u0430\u0433\u043E", nameEn: "Chicago" },
|
||||
{ countryCode: "US", name: "\u0425\u044C\u044E\u0441\u0442\u043E\u043D", nameEn: "Houston" },
|
||||
{ countryCode: "US", name: "\u0414\u0430\u043B\u043B\u0430\u0441", nameEn: "Dallas" },
|
||||
{ countryCode: "US", name: "\u041C\u0430\u0439\u0430\u043C\u0438", nameEn: "Miami" },
|
||||
{ countryCode: "US", name: "\u0421\u0438\u044D\u0442\u043B", nameEn: "Seattle" },
|
||||
{ countryCode: "US", name: "\u0421\u0430\u043D-\u0424\u0440\u0430\u043D\u0446\u0438\u0441\u043A\u043E", nameEn: "San Francisco" },
|
||||
{ countryCode: "US", name: "\u0412\u0430\u0448\u0438\u043D\u0433\u0442\u043E\u043D", nameEn: "Washington" },
|
||||
{ countryCode: "US", name: "\u0410\u0442\u043B\u0430\u043D\u0442\u0430", nameEn: "Atlanta" },
|
||||
{ countryCode: "US", name: "\u0414\u0435\u043D\u0432\u0435\u0440", nameEn: "Denver" },
|
||||
// Канада
|
||||
{ countryCode: "CA", name: "\u0422\u043E\u0440\u043E\u043D\u0442\u043E", nameEn: "Toronto" },
|
||||
{ countryCode: "CA", name: "\u041C\u043E\u043D\u0440\u0435\u0430\u043B\u044C", nameEn: "Montreal" },
|
||||
{ countryCode: "CA", name: "\u0412\u0430\u043D\u043A\u0443\u0432\u0435\u0440", nameEn: "Vancouver" },
|
||||
// Сингапур
|
||||
{ countryCode: "SG", name: "\u0421\u0438\u043D\u0433\u0430\u043F\u0443\u0440", nameEn: "Singapore" },
|
||||
// Япония
|
||||
{ countryCode: "JP", name: "\u0422\u043E\u043A\u0438\u043E", nameEn: "Tokyo" },
|
||||
{ countryCode: "JP", name: "\u041E\u0441\u0430\u043A\u0430", nameEn: "Osaka" },
|
||||
// Южная Корея
|
||||
{ countryCode: "KR", name: "\u0421\u0435\u0443\u043B", nameEn: "Seoul" },
|
||||
// Китай
|
||||
{ countryCode: "CN", name: "\u041F\u0435\u043A\u0438\u043D", nameEn: "Beijing" },
|
||||
{ countryCode: "CN", name: "\u0428\u0430\u043D\u0445\u0430\u0439", nameEn: "Shanghai" },
|
||||
{ countryCode: "CN", name: "\u0413\u0443\u0430\u043D\u0447\u0436\u043E\u0443", nameEn: "Guangzhou" },
|
||||
{ countryCode: "CN", name: "\u0428\u044D\u043D\u044C\u0447\u0436\u044D\u043D\u044C", nameEn: "Shenzhen" },
|
||||
{ countryCode: "CN", name: "\u0413\u043E\u043D\u043A\u043E\u043D\u0433", nameEn: "Hong Kong" },
|
||||
// Индия
|
||||
{ countryCode: "IN", name: "\u041C\u0443\u043C\u0431\u0430\u0438", nameEn: "Mumbai" },
|
||||
{ countryCode: "IN", name: "\u0414\u0435\u043B\u0438", nameEn: "Delhi" },
|
||||
{ countryCode: "IN", name: "\u0411\u0430\u043D\u0433\u0430\u043B\u043E\u0440", nameEn: "Bangalore" },
|
||||
{ countryCode: "IN", name: "\u0427\u0435\u043D\u043D\u0430\u0438", nameEn: "Chennai" },
|
||||
// ОАЭ
|
||||
{ countryCode: "AE", name: "\u0414\u0443\u0431\u0430\u0439", nameEn: "Dubai" },
|
||||
{ countryCode: "AE", name: "\u0410\u0431\u0443-\u0414\u0430\u0431\u0438", nameEn: "Abu Dhabi" },
|
||||
// Турция
|
||||
{ countryCode: "TR", name: "\u0421\u0442\u0430\u043C\u0431\u0443\u043B", nameEn: "Istanbul" },
|
||||
{ countryCode: "TR", name: "\u0410\u043D\u043A\u0430\u0440\u0430", nameEn: "Ankara" },
|
||||
{ countryCode: "TR", name: "\u0418\u0437\u043C\u0438\u0440", nameEn: "Izmir" },
|
||||
// Бразилия
|
||||
{ countryCode: "BR", name: "\u0421\u0430\u043D-\u041F\u0430\u0443\u043B\u0443", nameEn: "S\xE3o Paulo" },
|
||||
{ countryCode: "BR", name: "\u0420\u0438\u043E-\u0434\u0435-\u0416\u0430\u043D\u0435\u0439\u0440\u043E", nameEn: "Rio de Janeiro" },
|
||||
// Австралия
|
||||
{ countryCode: "AU", name: "\u0421\u0438\u0434\u043D\u0435\u0439", nameEn: "Sydney" },
|
||||
{ countryCode: "AU", name: "\u041C\u0435\u043B\u044C\u0431\u0443\u0440\u043D", nameEn: "Melbourne" },
|
||||
// Болгария
|
||||
{ countryCode: "BG", name: "\u0421\u043E\u0444\u0438\u044F", nameEn: "Sofia" },
|
||||
// Румыния
|
||||
{ countryCode: "RO", name: "\u0411\u0443\u0445\u0430\u0440\u0435\u0441\u0442", nameEn: "Bucharest" },
|
||||
// Венгрия
|
||||
{ countryCode: "HU", name: "\u0411\u0443\u0434\u0430\u043F\u0435\u0448\u0442", nameEn: "Budapest" },
|
||||
// Греция
|
||||
{ countryCode: "GR", name: "\u0410\u0444\u0438\u043D\u044B", nameEn: "Athens" },
|
||||
// Португалия
|
||||
{ countryCode: "PT", name: "\u041B\u0438\u0441\u0441\u0430\u0431\u043E\u043D", nameEn: "Lisbon" },
|
||||
// Ирландия
|
||||
{ countryCode: "IE", name: "\u0414\u0443\u0431\u043B\u0438\u043D", nameEn: "Dublin" },
|
||||
// Израиль
|
||||
{ countryCode: "IL", name: "\u0422\u0435\u043B\u044C-\u0410\u0432\u0438\u0432", nameEn: "Tel Aviv" }
|
||||
];
|
||||
var CITY_BY_NAME_RU = Object.fromEntries(
|
||||
CITIES.map((c) => [c.name.toLowerCase(), c])
|
||||
);
|
||||
function resolveCityRef(cityName) {
|
||||
return CITY_BY_NAME_RU[cityName.trim().toLowerCase()];
|
||||
}
|
||||
function resolveCountryForCity(cityName) {
|
||||
const city = resolveCityRef(cityName);
|
||||
return city ? COUNTRY_BY_CODE[city.countryCode] : void 0;
|
||||
}
|
||||
function cityMatchesCountry(cityName, countryName, rows) {
|
||||
const countryCode = COUNTRY_BY_NAME_RU[countryName.trim().toLowerCase()]?.code;
|
||||
if (!countryCode) return true;
|
||||
const catalog = resolveCityRef(cityName);
|
||||
if (catalog) return catalog.countryCode === countryCode;
|
||||
return (rows ?? []).some(
|
||||
(row) => row.city?.trim().toLowerCase() === cityName.trim().toLowerCase() && COUNTRY_BY_NAME_RU[(row.country || "").trim().toLowerCase()]?.code === countryCode
|
||||
);
|
||||
}
|
||||
function buildCityOptions(rows, countryName, options) {
|
||||
const includeCatalog = options?.includeCatalog ?? true;
|
||||
const names = /* @__PURE__ */ new Set();
|
||||
const countryCode = countryName?.trim() ? COUNTRY_BY_NAME_RU[countryName.trim().toLowerCase()]?.code : void 0;
|
||||
const belongsToCountry = (city, rowCountry) => {
|
||||
if (!countryCode) return true;
|
||||
const catalog = resolveCityRef(city);
|
||||
if (catalog) return catalog.countryCode === countryCode;
|
||||
if (rowCountry?.trim()) {
|
||||
return COUNTRY_BY_NAME_RU[rowCountry.trim().toLowerCase()]?.code === countryCode;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
for (const row of rows ?? []) {
|
||||
const city = (row.city || "").trim();
|
||||
if (!city) continue;
|
||||
if (belongsToCountry(city, row.country ?? void 0)) {
|
||||
names.add(city);
|
||||
}
|
||||
}
|
||||
if (includeCatalog) {
|
||||
for (const { name } of listCities(countryCode)) {
|
||||
names.add(name);
|
||||
}
|
||||
}
|
||||
return [...names].sort((a, b) => a.localeCompare(b, "ru")).map((name) => ({ value: name, label: name }));
|
||||
}
|
||||
function resolveCountryForCityFromRows(cityName, rows) {
|
||||
const trimmed = cityName.trim();
|
||||
if (!trimmed) return void 0;
|
||||
const fromCatalog = resolveCountryForCity(trimmed);
|
||||
if (fromCatalog) return fromCatalog.name;
|
||||
const row = (rows ?? []).find(
|
||||
(r) => r.city?.trim().toLowerCase() === trimmed.toLowerCase() && r.country?.trim()
|
||||
);
|
||||
return row?.country?.trim();
|
||||
}
|
||||
function listCities(countryCode) {
|
||||
return CITIES.filter((c) => !countryCode || c.countryCode === countryCode).map((c) => ({
|
||||
...c,
|
||||
country: COUNTRY_BY_CODE[c.countryCode]
|
||||
}));
|
||||
}
|
||||
function countriesWithCities() {
|
||||
const codes = new Set(CITIES.map((c) => c.countryCode));
|
||||
return COUNTRIES.filter((c) => codes.has(c.code));
|
||||
}
|
||||
|
||||
// src/geo/loc-codes.ts
|
||||
var CANONICAL_LOC_CODES = {
|
||||
\u043C\u043E\u0441\u043A\u0432\u0430: "msk",
|
||||
\u0444\u0440\u0430\u043D\u043A\u0444\u0443\u0440\u0442: "fra",
|
||||
\u0430\u043C\u0441\u0442\u0435\u0440\u0434\u0430\u043C: "ams",
|
||||
\u0445\u0435\u043B\u044C\u0441\u0438\u043D\u043A\u0438: "hel",
|
||||
\u043F\u0430\u0440\u0438\u0436: "par",
|
||||
"\u0441\u0430\u043D\u043A\u0442-\u043F\u0435\u0442\u0435\u0440\u0431\u0443\u0440\u0433": "led",
|
||||
\u0431\u0435\u0440\u043B\u0438\u043D: "ber",
|
||||
\u043C\u044E\u043D\u0445\u0435\u043D: "muc",
|
||||
\u043B\u043E\u043D\u0434\u043E\u043D: "lon",
|
||||
"\u043D\u044C\u044E-\u0439\u043E\u0440\u043A": "nyc",
|
||||
\u0442\u043E\u043A\u0438\u043E: "tyo",
|
||||
\u0441\u0438\u043D\u0433\u0430\u043F\u0443\u0440: "sin",
|
||||
\u0434\u0443\u0431\u0430\u0439: "dxb",
|
||||
\u0432\u0430\u0440\u0448\u0430\u0432\u0430: "waw",
|
||||
\u043F\u0440\u0430\u0433\u0430: "prg",
|
||||
\u0432\u0435\u043D\u0430: "vie",
|
||||
\u0440\u0438\u0433\u0430: "rix",
|
||||
\u0442\u0430\u043B\u043B\u0438\u043D: "tll",
|
||||
\u043C\u0438\u043D\u0441\u043A: "msq",
|
||||
\u043A\u0438\u0435\u0432: "iev",
|
||||
\u0441\u0442\u043E\u043A\u0433\u043E\u043B\u044C\u043C: "arn",
|
||||
\u043E\u0441\u043B\u043E: "osl",
|
||||
\u043A\u043E\u043F\u0435\u043D\u0433\u0430\u0433\u0435\u043D: "cph",
|
||||
\u0446\u044E\u0440\u0438\u0445: "zrh",
|
||||
\u0431\u0440\u044E\u0441\u0441\u0435\u043B\u044C: "bru",
|
||||
\u043C\u0430\u0434\u0440\u0438\u0434: "mad",
|
||||
\u0431\u0430\u0440\u0441\u0435\u043B\u043E\u043D\u0430: "bcn",
|
||||
\u043C\u0438\u043B\u0430\u043D: "mxp",
|
||||
\u0440\u0438\u043C: "rom"
|
||||
};
|
||||
function locCodeForCity(city, usedCodes) {
|
||||
const known = CANONICAL_LOC_CODES[city.name.trim().toLowerCase()];
|
||||
if (known && !usedCodes.has(known)) return known;
|
||||
if (known) {
|
||||
}
|
||||
const base = city.nameEn.toLowerCase().replace(/[^a-z]/g, "");
|
||||
if (!base) {
|
||||
return `${city.countryCode.toLowerCase()}${usedCodes.size}`;
|
||||
}
|
||||
for (let len = 3; len <= Math.max(3, base.length); len++) {
|
||||
const candidate = base.slice(0, len);
|
||||
if (!usedCodes.has(candidate)) return candidate;
|
||||
}
|
||||
let i = 2;
|
||||
while (usedCodes.has(`${base.slice(0, 2)}${i}`)) i += 1;
|
||||
return `${base.slice(0, 2)}${i}`;
|
||||
}
|
||||
function catalogCitiesWithLocCodes() {
|
||||
const used = /* @__PURE__ */ new Set();
|
||||
const ordered = [...CITIES].sort((a, b) => {
|
||||
const ak = CANONICAL_LOC_CODES[a.name.toLowerCase()] ? 0 : 1;
|
||||
const bk = CANONICAL_LOC_CODES[b.name.toLowerCase()] ? 0 : 1;
|
||||
return ak - bk;
|
||||
});
|
||||
return ordered.map((city) => {
|
||||
const locCode = locCodeForCity(city, used);
|
||||
used.add(locCode);
|
||||
return { ...city, locCode };
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
COUNTRIES,
|
||||
COUNTRY_BY_CODE,
|
||||
COUNTRY_BY_NAME_RU,
|
||||
CITIES,
|
||||
CITY_BY_NAME_RU,
|
||||
resolveCityRef,
|
||||
resolveCountryForCity,
|
||||
cityMatchesCountry,
|
||||
buildCityOptions,
|
||||
resolveCountryForCityFromRows,
|
||||
listCities,
|
||||
countriesWithCities,
|
||||
CANONICAL_LOC_CODES,
|
||||
locCodeForCity,
|
||||
catalogCitiesWithLocCodes
|
||||
};
|
||||
Vendored
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Стандартизированный справочник стран (ISO 3166-1 alpha-2) с русскими названиями.
|
||||
* Источник: общедоступный справочник. Не зависит от данных в БД vps-tracker.
|
||||
*/
|
||||
interface CountryRef {
|
||||
/** ISO 3166-1 alpha-2 код */
|
||||
code: string;
|
||||
/** Русское название */
|
||||
name: string;
|
||||
/** Английское название */
|
||||
nameEn: string;
|
||||
}
|
||||
declare const COUNTRIES: readonly CountryRef[];
|
||||
declare const COUNTRY_BY_CODE: Record<string, CountryRef>;
|
||||
declare const COUNTRY_BY_NAME_RU: Record<string, CountryRef>;
|
||||
|
||||
/**
|
||||
* Стандартизированный справочник городов по странам.
|
||||
* Используется в фильтрах VPS и форме редактирования.
|
||||
* Не зависит от данных в БД vps-tracker — общедоступный статичный справочник.
|
||||
*/
|
||||
|
||||
interface CityRef {
|
||||
/** ISO 3166-1 alpha-2 код страны */
|
||||
countryCode: string;
|
||||
/** Русское название города */
|
||||
name: string;
|
||||
/** Английское название города */
|
||||
nameEn: string;
|
||||
}
|
||||
/**
|
||||
* Топ-города по странам (включая города с крупными дата-центрами).
|
||||
* Список намеренно ограничен — расширяется по мере необходимости.
|
||||
*/
|
||||
declare const CITIES: readonly CityRef[];
|
||||
declare const CITY_BY_NAME_RU: Record<string, CityRef>;
|
||||
/** Найти город в справочнике по русскому названию. */
|
||||
declare function resolveCityRef(cityName: string): CityRef | undefined;
|
||||
/** Страна из справочника для города (если есть привязка). */
|
||||
declare function resolveCountryForCity(cityName: string): CountryRef | undefined;
|
||||
interface CityLocationRow {
|
||||
city?: string | null;
|
||||
country?: string | null;
|
||||
}
|
||||
/** Проверить, относится ли город к выбранной стране (справочник или данные VPS). */
|
||||
declare function cityMatchesCountry(cityName: string, countryName: string, rows?: readonly CityLocationRow[]): boolean;
|
||||
/** Опции городов: из VPS и опционально справочника, опционально по стране. */
|
||||
declare function buildCityOptions(rows: readonly CityLocationRow[] | undefined, countryName?: string, options?: {
|
||||
includeCatalog?: boolean;
|
||||
}): {
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
/** Страна для города: справочник, иначе первая запись VPS с таким городом. */
|
||||
declare function resolveCountryForCityFromRows(cityName: string, rows: readonly CityLocationRow[] | undefined): string | undefined;
|
||||
interface CityOption extends CityRef {
|
||||
country: CountryRef;
|
||||
}
|
||||
/** Список городов с привязкой к стране. */
|
||||
declare function listCities(countryCode?: string): CityOption[];
|
||||
/** Список стран, у которых есть города в справочнике. */
|
||||
declare function countriesWithCities(): CountryRef[];
|
||||
|
||||
/**
|
||||
* Канонические коды {loc} для hostname (из naming seed / docs).
|
||||
* Ключ — русское имя города (lower case).
|
||||
*/
|
||||
declare const CANONICAL_LOC_CODES: Record<string, string>;
|
||||
/** Детерминированный уникальный loc-код для города (hostname {loc}). */
|
||||
declare function locCodeForCity(city: Pick<CityRef, 'name' | 'nameEn' | 'countryCode'>, usedCodes: ReadonlySet<string>): string;
|
||||
/** Все города каталога с выделенными loc-кодами (без коллизий). */
|
||||
declare function catalogCitiesWithLocCodes(): Array<CityRef & {
|
||||
locCode: string;
|
||||
}>;
|
||||
|
||||
export { CANONICAL_LOC_CODES, CITIES, CITY_BY_NAME_RU, COUNTRIES, COUNTRY_BY_CODE, COUNTRY_BY_NAME_RU, type CityLocationRow, type CityOption, type CityRef, type CountryRef, buildCityOptions, catalogCitiesWithLocCodes, cityMatchesCountry, countriesWithCities, listCities, locCodeForCity, resolveCityRef, resolveCountryForCity, resolveCountryForCityFromRows };
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
CANONICAL_LOC_CODES,
|
||||
CITIES,
|
||||
CITY_BY_NAME_RU,
|
||||
COUNTRIES,
|
||||
COUNTRY_BY_CODE,
|
||||
COUNTRY_BY_NAME_RU,
|
||||
buildCityOptions,
|
||||
catalogCitiesWithLocCodes,
|
||||
cityMatchesCountry,
|
||||
countriesWithCities,
|
||||
listCities,
|
||||
locCodeForCity,
|
||||
resolveCityRef,
|
||||
resolveCountryForCity,
|
||||
resolveCountryForCityFromRows
|
||||
} from "../chunk-FK3U7DSV.js";
|
||||
export {
|
||||
CANONICAL_LOC_CODES,
|
||||
CITIES,
|
||||
CITY_BY_NAME_RU,
|
||||
COUNTRIES,
|
||||
COUNTRY_BY_CODE,
|
||||
COUNTRY_BY_NAME_RU,
|
||||
buildCityOptions,
|
||||
catalogCitiesWithLocCodes,
|
||||
cityMatchesCountry,
|
||||
countriesWithCities,
|
||||
listCities,
|
||||
locCodeForCity,
|
||||
resolveCityRef,
|
||||
resolveCountryForCity,
|
||||
resolveCountryForCityFromRows
|
||||
};
|
||||
Vendored
+1
@@ -1,4 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
export { CANONICAL_LOC_CODES, CITIES, CITY_BY_NAME_RU, COUNTRIES, COUNTRY_BY_CODE, COUNTRY_BY_NAME_RU, CityLocationRow, CityOption, CityRef, CountryRef, buildCityOptions, catalogCitiesWithLocCodes, cityMatchesCountry, countriesWithCities, listCities, locCodeForCity, resolveCityRef, resolveCountryForCity, resolveCountryForCityFromRows } from './geo/index.js';
|
||||
|
||||
declare const appSwitcherIconSchema: z.ZodEnum<{
|
||||
server: "server";
|
||||
|
||||
Vendored
+33
@@ -1,3 +1,21 @@
|
||||
import {
|
||||
CANONICAL_LOC_CODES,
|
||||
CITIES,
|
||||
CITY_BY_NAME_RU,
|
||||
COUNTRIES,
|
||||
COUNTRY_BY_CODE,
|
||||
COUNTRY_BY_NAME_RU,
|
||||
buildCityOptions,
|
||||
catalogCitiesWithLocCodes,
|
||||
cityMatchesCountry,
|
||||
countriesWithCities,
|
||||
listCities,
|
||||
locCodeForCity,
|
||||
resolveCityRef,
|
||||
resolveCountryForCity,
|
||||
resolveCountryForCityFromRows
|
||||
} from "./chunk-FK3U7DSV.js";
|
||||
|
||||
// src/app-switcher.ts
|
||||
import { z } from "zod";
|
||||
var appSwitcherIconSchema = z.enum([
|
||||
@@ -265,6 +283,12 @@ var ValidationError = class extends Error {
|
||||
}
|
||||
};
|
||||
export {
|
||||
CANONICAL_LOC_CODES,
|
||||
CITIES,
|
||||
CITY_BY_NAME_RU,
|
||||
COUNTRIES,
|
||||
COUNTRY_BY_CODE,
|
||||
COUNTRY_BY_NAME_RU,
|
||||
ValidationError,
|
||||
addressFamilySchema,
|
||||
aliasCreateSchema,
|
||||
@@ -279,10 +303,16 @@ export {
|
||||
appSwitcherEntrySchema,
|
||||
appSwitcherIconSchema,
|
||||
bindExportSchema,
|
||||
buildCityOptions,
|
||||
catalogCitiesWithLocCodes,
|
||||
cfDnsRecordSchema,
|
||||
cfZoneSchema,
|
||||
cityMatchesCountry,
|
||||
countriesWithCities,
|
||||
createDnsRecordPayloadSchema,
|
||||
dashboardStatsSchema,
|
||||
listCities,
|
||||
locCodeForCity,
|
||||
locationSchema,
|
||||
loginSchema,
|
||||
nodeAddressSchema,
|
||||
@@ -292,6 +322,9 @@ export {
|
||||
nodeSchema,
|
||||
orphanIgnoreSchema,
|
||||
patchDnsRecordPayloadSchema,
|
||||
resolveCityRef,
|
||||
resolveCountryForCity,
|
||||
resolveCountryForCityFromRows,
|
||||
syncApplySchema,
|
||||
syncDiffOpSchema,
|
||||
syncJobSchema,
|
||||
|
||||
@@ -12,10 +12,15 @@
|
||||
"types": "./dist/index.d.ts",
|
||||
"development": "./src/index.ts",
|
||||
"import": "./dist/index.js"
|
||||
},
|
||||
"./geo": {
|
||||
"types": "./dist/geo/index.d.ts",
|
||||
"development": "./src/geo/index.ts",
|
||||
"import": "./dist/geo/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts --format esm --dts",
|
||||
"build": "tsup src/index.ts src/geo/index.ts --format esm --dts",
|
||||
"dev": "tsup src/index.ts --format esm --dts --watch",
|
||||
"test": "vitest run --passWithNoTests"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* Стандартизированный справочник городов по странам.
|
||||
* Используется в фильтрах VPS и форме редактирования.
|
||||
* Не зависит от данных в БД vps-tracker — общедоступный статичный справочник.
|
||||
*/
|
||||
|
||||
import { COUNTRY_BY_CODE, COUNTRY_BY_NAME_RU, COUNTRIES, type CountryRef } from './countries.js'
|
||||
|
||||
export interface CityRef {
|
||||
/** ISO 3166-1 alpha-2 код страны */
|
||||
countryCode: string
|
||||
/** Русское название города */
|
||||
name: string
|
||||
/** Английское название города */
|
||||
nameEn: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Топ-города по странам (включая города с крупными дата-центрами).
|
||||
* Список намеренно ограничен — расширяется по мере необходимости.
|
||||
*/
|
||||
export const CITIES: readonly CityRef[] = [
|
||||
// Россия
|
||||
{ countryCode: 'RU', name: 'Москва', nameEn: 'Moscow' },
|
||||
{ countryCode: 'RU', name: 'Санкт-Петербург', nameEn: 'Saint Petersburg' },
|
||||
{ countryCode: 'RU', name: 'Новосибирск', nameEn: 'Novosibirsk' },
|
||||
{ countryCode: 'RU', name: 'Екатеринбург', nameEn: 'Yekaterinburg' },
|
||||
{ countryCode: 'RU', name: 'Казань', nameEn: 'Kazan' },
|
||||
{ countryCode: 'RU', name: 'Нижний Новгород', nameEn: 'Nizhny Novgorod' },
|
||||
{ countryCode: 'RU', name: 'Самара', nameEn: 'Samara' },
|
||||
{ countryCode: 'RU', name: 'Ростов-на-Дону', nameEn: 'Rostov-on-Don' },
|
||||
{ countryCode: 'RU', name: 'Уфа', nameEn: 'Ufa' },
|
||||
{ countryCode: 'RU', name: 'Краснодар', nameEn: 'Krasnodar' },
|
||||
{ countryCode: 'RU', name: 'Сочи', nameEn: 'Sochi' },
|
||||
{ countryCode: 'RU', name: 'Владивосток', nameEn: 'Vladivostok' },
|
||||
{ countryCode: 'RU', name: 'Иркутск', nameEn: 'Irkutsk' },
|
||||
{ countryCode: 'RU', name: 'Хабаровск', nameEn: 'Khabarovsk' },
|
||||
{ countryCode: 'RU', name: 'Тюмень', nameEn: 'Tyumen' },
|
||||
// Нидерланды
|
||||
{ countryCode: 'NL', name: 'Амстердам', nameEn: 'Amsterdam' },
|
||||
{ countryCode: 'NL', name: 'Роттердам', nameEn: 'Rotterdam' },
|
||||
{ countryCode: 'NL', name: 'Гаага', nameEn: 'The Hague' },
|
||||
// Германия
|
||||
{ countryCode: 'DE', name: 'Франкфурт', nameEn: 'Frankfurt' },
|
||||
{ countryCode: 'DE', name: 'Берлин', nameEn: 'Berlin' },
|
||||
{ countryCode: 'DE', name: 'Мюнхен', nameEn: 'Munich' },
|
||||
{ countryCode: 'DE', name: 'Гамбург', nameEn: 'Hamburg' },
|
||||
{ countryCode: 'DE', name: 'Кёльн', nameEn: 'Cologne' },
|
||||
{ countryCode: 'DE', name: 'Дюссельдорф', nameEn: 'Düsseldorf' },
|
||||
{ countryCode: 'DE', name: 'Штутгарт', nameEn: 'Stuttgart' },
|
||||
// Финляндия
|
||||
{ countryCode: 'FI', name: 'Хельсинки', nameEn: 'Helsinki' },
|
||||
{ countryCode: 'FI', name: 'Эспоо', nameEn: 'Espoo' },
|
||||
{ countryCode: 'FI', name: 'Тампере', nameEn: 'Tampere' },
|
||||
// Швеция
|
||||
{ countryCode: 'SE', name: 'Стокгольм', nameEn: 'Stockholm' },
|
||||
{ countryCode: 'SE', name: 'Гётеборг', nameEn: 'Gothenburg' },
|
||||
{ countryCode: 'SE', name: 'Мальмё', nameEn: 'Malmö' },
|
||||
// Норвегия
|
||||
{ countryCode: 'NO', name: 'Осло', nameEn: 'Oslo' },
|
||||
{ countryCode: 'NO', name: 'Берген', nameEn: 'Bergen' },
|
||||
// Дания
|
||||
{ countryCode: 'DK', name: 'Копенгаген', nameEn: 'Copenhagen' },
|
||||
{ countryCode: 'DK', name: 'Орхус', nameEn: 'Aarhus' },
|
||||
// Великобритания
|
||||
{ countryCode: 'GB', name: 'Лондон', nameEn: 'London' },
|
||||
{ countryCode: 'GB', name: 'Манчестер', nameEn: 'Manchester' },
|
||||
{ countryCode: 'GB', name: 'Глазго', nameEn: 'Glasgow' },
|
||||
{ countryCode: 'GB', name: 'Кардифф', nameEn: 'Cardiff' },
|
||||
// Франция
|
||||
{ countryCode: 'FR', name: 'Париж', nameEn: 'Paris' },
|
||||
{ countryCode: 'FR', name: 'Марсель', nameEn: 'Marseille' },
|
||||
{ countryCode: 'FR', name: 'Лион', nameEn: 'Lyon' },
|
||||
{ countryCode: 'FR', name: 'Страсбург', nameEn: 'Strasbourg' },
|
||||
// Испания
|
||||
{ countryCode: 'ES', name: 'Мадрид', nameEn: 'Madrid' },
|
||||
{ countryCode: 'ES', name: 'Барселона', nameEn: 'Barcelona' },
|
||||
{ countryCode: 'ES', name: 'Валенсия', nameEn: 'Valencia' },
|
||||
// Италия
|
||||
{ countryCode: 'IT', name: 'Рим', nameEn: 'Rome' },
|
||||
{ countryCode: 'IT', name: 'Милан', nameEn: 'Milan' },
|
||||
{ countryCode: 'IT', name: 'Турин', nameEn: 'Turin' },
|
||||
// Чехия
|
||||
{ countryCode: 'CZ', name: 'Прага', nameEn: 'Prague' },
|
||||
{ countryCode: 'CZ', name: 'Брно', nameEn: 'Brno' },
|
||||
// Польша
|
||||
{ countryCode: 'PL', name: 'Варшава', nameEn: 'Warsaw' },
|
||||
{ countryCode: 'PL', name: 'Краков', nameEn: 'Kraków' },
|
||||
{ countryCode: 'PL', name: 'Вроцлав', nameEn: 'Wrocław' },
|
||||
{ countryCode: 'PL', name: 'Гданьск', nameEn: 'Gdańsk' },
|
||||
// Швейцария
|
||||
{ countryCode: 'CH', name: 'Цюрих', nameEn: 'Zurich' },
|
||||
{ countryCode: 'CH', name: 'Женева', nameEn: 'Geneva' },
|
||||
{ countryCode: 'CH', name: 'Базель', nameEn: 'Basel' },
|
||||
// Бельгия
|
||||
{ countryCode: 'BE', name: 'Брюссель', nameEn: 'Brussels' },
|
||||
{ countryCode: 'BE', name: 'Антверпен', nameEn: 'Antwerp' },
|
||||
// Австрия
|
||||
{ countryCode: 'AT', name: 'Вена', nameEn: 'Vienna' },
|
||||
{ countryCode: 'AT', name: 'Грац', nameEn: 'Graz' },
|
||||
// Латвия
|
||||
{ countryCode: 'LV', name: 'Рига', nameEn: 'Riga' },
|
||||
// Литва
|
||||
{ countryCode: 'LT', name: 'Вильнюс', nameEn: 'Vilnius' },
|
||||
{ countryCode: 'LT', name: 'Каунас', nameEn: 'Kaunas' },
|
||||
// Эстония
|
||||
{ countryCode: 'EE', name: 'Таллин', nameEn: 'Tallinn' },
|
||||
// Украина
|
||||
{ countryCode: 'UA', name: 'Киев', nameEn: 'Kyiv' },
|
||||
{ countryCode: 'UA', name: 'Харьков', nameEn: 'Kharkiv' },
|
||||
{ countryCode: 'UA', name: 'Одесса', nameEn: 'Odesa' },
|
||||
{ countryCode: 'UA', name: 'Львов', nameEn: 'Lviv' },
|
||||
// Казахстан
|
||||
{ countryCode: 'KZ', name: 'Алматы', nameEn: 'Almaty' },
|
||||
{ countryCode: 'KZ', name: 'Астана', nameEn: 'Astana' },
|
||||
// Беларусь
|
||||
{ countryCode: 'BY', name: 'Минск', nameEn: 'Minsk' },
|
||||
// США
|
||||
{ countryCode: 'US', name: 'Нью-Йорк', nameEn: 'New York' },
|
||||
{ countryCode: 'US', name: 'Лос-Анджелес', nameEn: 'Los Angeles' },
|
||||
{ countryCode: 'US', name: 'Чикаго', nameEn: 'Chicago' },
|
||||
{ countryCode: 'US', name: 'Хьюстон', nameEn: 'Houston' },
|
||||
{ countryCode: 'US', name: 'Даллас', nameEn: 'Dallas' },
|
||||
{ countryCode: 'US', name: 'Майами', nameEn: 'Miami' },
|
||||
{ countryCode: 'US', name: 'Сиэтл', nameEn: 'Seattle' },
|
||||
{ countryCode: 'US', name: 'Сан-Франциско', nameEn: 'San Francisco' },
|
||||
{ countryCode: 'US', name: 'Вашингтон', nameEn: 'Washington' },
|
||||
{ countryCode: 'US', name: 'Атланта', nameEn: 'Atlanta' },
|
||||
{ countryCode: 'US', name: 'Денвер', nameEn: 'Denver' },
|
||||
// Канада
|
||||
{ countryCode: 'CA', name: 'Торонто', nameEn: 'Toronto' },
|
||||
{ countryCode: 'CA', name: 'Монреаль', nameEn: 'Montreal' },
|
||||
{ countryCode: 'CA', name: 'Ванкувер', nameEn: 'Vancouver' },
|
||||
// Сингапур
|
||||
{ countryCode: 'SG', name: 'Сингапур', nameEn: 'Singapore' },
|
||||
// Япония
|
||||
{ countryCode: 'JP', name: 'Токио', nameEn: 'Tokyo' },
|
||||
{ countryCode: 'JP', name: 'Осака', nameEn: 'Osaka' },
|
||||
// Южная Корея
|
||||
{ countryCode: 'KR', name: 'Сеул', nameEn: 'Seoul' },
|
||||
// Китай
|
||||
{ countryCode: 'CN', name: 'Пекин', nameEn: 'Beijing' },
|
||||
{ countryCode: 'CN', name: 'Шанхай', nameEn: 'Shanghai' },
|
||||
{ countryCode: 'CN', name: 'Гуанчжоу', nameEn: 'Guangzhou' },
|
||||
{ countryCode: 'CN', name: 'Шэньчжэнь', nameEn: 'Shenzhen' },
|
||||
{ countryCode: 'CN', name: 'Гонконг', nameEn: 'Hong Kong' },
|
||||
// Индия
|
||||
{ countryCode: 'IN', name: 'Мумбаи', nameEn: 'Mumbai' },
|
||||
{ countryCode: 'IN', name: 'Дели', nameEn: 'Delhi' },
|
||||
{ countryCode: 'IN', name: 'Бангалор', nameEn: 'Bangalore' },
|
||||
{ countryCode: 'IN', name: 'Ченнаи', nameEn: 'Chennai' },
|
||||
// ОАЭ
|
||||
{ countryCode: 'AE', name: 'Дубай', nameEn: 'Dubai' },
|
||||
{ countryCode: 'AE', name: 'Абу-Даби', nameEn: 'Abu Dhabi' },
|
||||
// Турция
|
||||
{ countryCode: 'TR', name: 'Стамбул', nameEn: 'Istanbul' },
|
||||
{ countryCode: 'TR', name: 'Анкара', nameEn: 'Ankara' },
|
||||
{ countryCode: 'TR', name: 'Измир', nameEn: 'Izmir' },
|
||||
// Бразилия
|
||||
{ countryCode: 'BR', name: 'Сан-Паулу', nameEn: 'São Paulo' },
|
||||
{ countryCode: 'BR', name: 'Рио-де-Жанейро', nameEn: 'Rio de Janeiro' },
|
||||
// Австралия
|
||||
{ countryCode: 'AU', name: 'Сидней', nameEn: 'Sydney' },
|
||||
{ countryCode: 'AU', name: 'Мельбурн', nameEn: 'Melbourne' },
|
||||
// Болгария
|
||||
{ countryCode: 'BG', name: 'София', nameEn: 'Sofia' },
|
||||
// Румыния
|
||||
{ countryCode: 'RO', name: 'Бухарест', nameEn: 'Bucharest' },
|
||||
// Венгрия
|
||||
{ countryCode: 'HU', name: 'Будапешт', nameEn: 'Budapest' },
|
||||
// Греция
|
||||
{ countryCode: 'GR', name: 'Афины', nameEn: 'Athens' },
|
||||
// Португалия
|
||||
{ countryCode: 'PT', name: 'Лиссабон', nameEn: 'Lisbon' },
|
||||
// Ирландия
|
||||
{ countryCode: 'IE', name: 'Дублин', nameEn: 'Dublin' },
|
||||
// Израиль
|
||||
{ countryCode: 'IL', name: 'Тель-Авив', nameEn: 'Tel Aviv' },
|
||||
]
|
||||
|
||||
export const CITY_BY_NAME_RU: Record<string, CityRef> = Object.fromEntries(
|
||||
CITIES.map((c) => [c.name.toLowerCase(), c]),
|
||||
)
|
||||
|
||||
/** Найти город в справочнике по русскому названию. */
|
||||
export function resolveCityRef(cityName: string): CityRef | undefined {
|
||||
return CITY_BY_NAME_RU[cityName.trim().toLowerCase()]
|
||||
}
|
||||
|
||||
/** Страна из справочника для города (если есть привязка). */
|
||||
export function resolveCountryForCity(cityName: string): CountryRef | undefined {
|
||||
const city = resolveCityRef(cityName)
|
||||
return city ? COUNTRY_BY_CODE[city.countryCode] : undefined
|
||||
}
|
||||
|
||||
export interface CityLocationRow {
|
||||
city?: string | null
|
||||
country?: string | null
|
||||
}
|
||||
|
||||
/** Проверить, относится ли город к выбранной стране (справочник или данные VPS). */
|
||||
export function cityMatchesCountry(
|
||||
cityName: string,
|
||||
countryName: string,
|
||||
rows?: readonly CityLocationRow[],
|
||||
): boolean {
|
||||
const countryCode = COUNTRY_BY_NAME_RU[countryName.trim().toLowerCase()]?.code
|
||||
if (!countryCode) return true
|
||||
|
||||
const catalog = resolveCityRef(cityName)
|
||||
if (catalog) return catalog.countryCode === countryCode
|
||||
|
||||
return (rows ?? []).some(
|
||||
(row) =>
|
||||
row.city?.trim().toLowerCase() === cityName.trim().toLowerCase() &&
|
||||
COUNTRY_BY_NAME_RU[(row.country || '').trim().toLowerCase()]?.code === countryCode,
|
||||
)
|
||||
}
|
||||
|
||||
/** Опции городов: из VPS и опционально справочника, опционально по стране. */
|
||||
export function buildCityOptions(
|
||||
rows: readonly CityLocationRow[] | undefined,
|
||||
countryName?: string,
|
||||
options?: { includeCatalog?: boolean },
|
||||
): { value: string; label: string }[] {
|
||||
const includeCatalog = options?.includeCatalog ?? true
|
||||
const names = new Set<string>()
|
||||
const countryCode = countryName?.trim()
|
||||
? COUNTRY_BY_NAME_RU[countryName.trim().toLowerCase()]?.code
|
||||
: undefined
|
||||
|
||||
const belongsToCountry = (city: string, rowCountry?: string): boolean => {
|
||||
if (!countryCode) return true
|
||||
const catalog = resolveCityRef(city)
|
||||
if (catalog) return catalog.countryCode === countryCode
|
||||
if (rowCountry?.trim()) {
|
||||
return COUNTRY_BY_NAME_RU[rowCountry.trim().toLowerCase()]?.code === countryCode
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for (const row of rows ?? []) {
|
||||
const city = (row.city || '').trim()
|
||||
if (!city) continue
|
||||
if (belongsToCountry(city, row.country ?? undefined)) {
|
||||
names.add(city)
|
||||
}
|
||||
}
|
||||
|
||||
if (includeCatalog) {
|
||||
for (const { name } of listCities(countryCode)) {
|
||||
names.add(name)
|
||||
}
|
||||
}
|
||||
|
||||
return [...names]
|
||||
.sort((a, b) => a.localeCompare(b, 'ru'))
|
||||
.map((name) => ({ value: name, label: name }))
|
||||
}
|
||||
|
||||
/** Страна для города: справочник, иначе первая запись VPS с таким городом. */
|
||||
export function resolveCountryForCityFromRows(
|
||||
cityName: string,
|
||||
rows: readonly CityLocationRow[] | undefined,
|
||||
): string | undefined {
|
||||
const trimmed = cityName.trim()
|
||||
if (!trimmed) return undefined
|
||||
|
||||
const fromCatalog = resolveCountryForCity(trimmed)
|
||||
if (fromCatalog) return fromCatalog.name
|
||||
|
||||
const row = (rows ?? []).find(
|
||||
(r) => r.city?.trim().toLowerCase() === trimmed.toLowerCase() && r.country?.trim(),
|
||||
)
|
||||
return row?.country?.trim()
|
||||
}
|
||||
|
||||
export interface CityOption extends CityRef {
|
||||
country: CountryRef
|
||||
}
|
||||
|
||||
/** Список городов с привязкой к стране. */
|
||||
export function listCities(countryCode?: string): CityOption[] {
|
||||
return CITIES.filter((c) => !countryCode || c.countryCode === countryCode).map((c) => ({
|
||||
...c,
|
||||
country: COUNTRY_BY_CODE[c.countryCode],
|
||||
}))
|
||||
}
|
||||
|
||||
/** Список стран, у которых есть города в справочнике. */
|
||||
export function countriesWithCities(): CountryRef[] {
|
||||
const codes = new Set(CITIES.map((c) => c.countryCode))
|
||||
return COUNTRIES.filter((c) => codes.has(c.code))
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Стандартизированный справочник стран (ISO 3166-1 alpha-2) с русскими названиями.
|
||||
* Источник: общедоступный справочник. Не зависит от данных в БД vps-tracker.
|
||||
*/
|
||||
|
||||
export interface CountryRef {
|
||||
/** ISO 3166-1 alpha-2 код */
|
||||
code: string
|
||||
/** Русское название */
|
||||
name: string
|
||||
/** Английское название */
|
||||
nameEn: string
|
||||
}
|
||||
|
||||
export const COUNTRIES: readonly CountryRef[] = [
|
||||
{ code: 'AU', name: 'Австралия', nameEn: 'Australia' },
|
||||
{ code: 'AT', name: 'Австрия', nameEn: 'Austria' },
|
||||
{ code: 'AZ', name: 'Азербайджан', nameEn: 'Azerbaijan' },
|
||||
{ code: 'AL', name: 'Албания', nameEn: 'Albania' },
|
||||
{ code: 'DZ', name: 'Алжир', nameEn: 'Algeria' },
|
||||
{ code: 'AO', name: 'Ангола', nameEn: 'Angola' },
|
||||
{ code: 'AR', name: 'Аргентина', nameEn: 'Argentina' },
|
||||
{ code: 'AM', name: 'Армения', nameEn: 'Armenia' },
|
||||
{ code: 'AF', name: 'Афганистан', nameEn: 'Afghanistan' },
|
||||
{ code: 'BE', name: 'Бельгия', nameEn: 'Belgium' },
|
||||
{ code: 'BG', name: 'Болгария', nameEn: 'Bulgaria' },
|
||||
{ code: 'BO', name: 'Боливия', nameEn: 'Bolivia' },
|
||||
{ code: 'BA', name: 'Босния и Герцеговина', nameEn: 'Bosnia and Herzegovina' },
|
||||
{ code: 'BR', name: 'Бразилия', nameEn: 'Brazil' },
|
||||
{ code: 'GB', name: 'Великобритания', nameEn: 'United Kingdom' },
|
||||
{ code: 'HU', name: 'Венгрия', nameEn: 'Hungary' },
|
||||
{ code: 'VE', name: 'Венесуэла', nameEn: 'Venezuela' },
|
||||
{ code: 'VN', name: 'Вьетнам', nameEn: 'Vietnam' },
|
||||
{ code: 'GA', name: 'Габон', nameEn: 'Gabon' },
|
||||
{ code: 'HT', name: 'Гаити', nameEn: 'Haiti' },
|
||||
{ code: 'GY', name: 'Гайана', nameEn: 'Guyana' },
|
||||
{ code: 'GM', name: 'Гамбия', nameEn: 'Gambia' },
|
||||
{ code: 'GH', name: 'Гана', nameEn: 'Ghana' },
|
||||
{ code: 'GT', name: 'Гватемала', nameEn: 'Guatemala' },
|
||||
{ code: 'GN', name: 'Гвинея', nameEn: 'Guinea' },
|
||||
{ code: 'DE', name: 'Германия', nameEn: 'Germany' },
|
||||
{ code: 'HN', name: 'Гондурас', nameEn: 'Honduras' },
|
||||
{ code: 'GR', name: 'Греция', nameEn: 'Greece' },
|
||||
{ code: 'GE', name: 'Грузия', nameEn: 'Georgia' },
|
||||
{ code: 'DK', name: 'Дания', nameEn: 'Denmark' },
|
||||
{ code: 'CD', name: 'ДР Конго', nameEn: 'DR Congo' },
|
||||
{ code: 'EG', name: 'Египет', nameEn: 'Egypt' },
|
||||
{ code: 'ZM', name: 'Замбия', nameEn: 'Zambia' },
|
||||
{ code: 'ZW', name: 'Зимбабве', nameEn: 'Zimbabwe' },
|
||||
{ code: 'IL', name: 'Израиль', nameEn: 'Israel' },
|
||||
{ code: 'IN', name: 'Индия', nameEn: 'India' },
|
||||
{ code: 'ID', name: 'Индонезия', nameEn: 'Indonesia' },
|
||||
{ code: 'JO', name: 'Иордания', nameEn: 'Jordan' },
|
||||
{ code: 'IQ', name: 'Ирак', nameEn: 'Iraq' },
|
||||
{ code: 'IR', name: 'Иран', nameEn: 'Iran' },
|
||||
{ code: 'IE', name: 'Ирландия', nameEn: 'Ireland' },
|
||||
{ code: 'IS', name: 'Исландия', nameEn: 'Iceland' },
|
||||
{ code: 'ES', name: 'Испания', nameEn: 'Spain' },
|
||||
{ code: 'IT', name: 'Италия', nameEn: 'Italy' },
|
||||
{ code: 'YE', name: 'Йемен', nameEn: 'Yemen' },
|
||||
{ code: 'KZ', name: 'Казахстан', nameEn: 'Kazakhstan' },
|
||||
{ code: 'KH', name: 'Камбоджа', nameEn: 'Cambodia' },
|
||||
{ code: 'CM', name: 'Камерун', nameEn: 'Cameroon' },
|
||||
{ code: 'CA', name: 'Канада', nameEn: 'Canada' },
|
||||
{ code: 'QA', name: 'Катар', nameEn: 'Qatar' },
|
||||
{ code: 'KE', name: 'Кения', nameEn: 'Kenya' },
|
||||
{ code: 'CY', name: 'Кипр', nameEn: 'Cyprus' },
|
||||
{ code: 'KG', name: 'Киргизия', nameEn: 'Kyrgyzstan' },
|
||||
{ code: 'CN', name: 'Китай', nameEn: 'China' },
|
||||
{ code: 'CO', name: 'Колумбия', nameEn: 'Colombia' },
|
||||
{ code: 'CG', name: 'Конго', nameEn: 'Congo' },
|
||||
{ code: 'KR', name: 'Южная Корея', nameEn: 'South Korea' },
|
||||
{ code: 'KP', name: 'Северная Корея', nameEn: 'North Korea' },
|
||||
{ code: 'XK', name: 'Косово', nameEn: 'Kosovo' },
|
||||
{ code: 'CR', name: 'Коста-Рика', nameEn: 'Costa Rica' },
|
||||
{ code: 'CI', name: "Кот-д'Ивуар", nameEn: "Côte d'Ivoire" },
|
||||
{ code: 'CU', name: 'Куба', nameEn: 'Cuba' },
|
||||
{ code: 'KW', name: 'Кувейт', nameEn: 'Kuwait' },
|
||||
{ code: 'LA', name: 'Лаос', nameEn: 'Laos' },
|
||||
{ code: 'LV', name: 'Латвия', nameEn: 'Latvia' },
|
||||
{ code: 'LR', name: 'Либерия', nameEn: 'Liberia' },
|
||||
{ code: 'LB', name: 'Ливан', nameEn: 'Lebanon' },
|
||||
{ code: 'LY', name: 'Ливия', nameEn: 'Libya' },
|
||||
{ code: 'LT', name: 'Литва', nameEn: 'Lithuania' },
|
||||
{ code: 'LI', name: 'Лихтенштейн', nameEn: 'Liechtenstein' },
|
||||
{ code: 'LU', name: 'Люксембург', nameEn: 'Luxembourg' },
|
||||
{ code: 'MU', name: 'Маврикий', nameEn: 'Mauritius' },
|
||||
{ code: 'MR', name: 'Мавритания', nameEn: 'Mauritania' },
|
||||
{ code: 'MG', name: 'Мадагаскар', nameEn: 'Madagascar' },
|
||||
{ code: 'MO', name: 'Макао', nameEn: 'Macao' },
|
||||
{ code: 'MW', name: 'Малави', nameEn: 'Malawi' },
|
||||
{ code: 'MY', name: 'Малайзия', nameEn: 'Malaysia' },
|
||||
{ code: 'ML', name: 'Мали', nameEn: 'Mali' },
|
||||
{ code: 'MT', name: 'Мальта', nameEn: 'Malta' },
|
||||
{ code: 'MA', name: 'Марокко', nameEn: 'Morocco' },
|
||||
{ code: 'MX', name: 'Мексика', nameEn: 'Mexico' },
|
||||
{ code: 'MZ', name: 'Мозамбик', nameEn: 'Mozambique' },
|
||||
{ code: 'MD', name: 'Молдавия', nameEn: 'Moldova' },
|
||||
{ code: 'MC', name: 'Монако', nameEn: 'Monaco' },
|
||||
{ code: 'MN', name: 'Монголия', nameEn: 'Mongolia' },
|
||||
{ code: 'MM', name: 'Мьянма', nameEn: 'Myanmar' },
|
||||
{ code: 'NA', name: 'Намибия', nameEn: 'Namibia' },
|
||||
{ code: 'NP', name: 'Непал', nameEn: 'Nepal' },
|
||||
{ code: 'NE', name: 'Нигер', nameEn: 'Niger' },
|
||||
{ code: 'NG', name: 'Нигерия', nameEn: 'Nigeria' },
|
||||
{ code: 'NL', name: 'Нидерланды', nameEn: 'Netherlands' },
|
||||
{ code: 'NI', name: 'Никарагуа', nameEn: 'Nicaragua' },
|
||||
{ code: 'NZ', name: 'Новая Зеландия', nameEn: 'New Zealand' },
|
||||
{ code: 'NO', name: 'Норвегия', nameEn: 'Norway' },
|
||||
{ code: 'AE', name: 'ОАЭ', nameEn: 'United Arab Emirates' },
|
||||
{ code: 'OM', name: 'Оман', nameEn: 'Oman' },
|
||||
{ code: 'PK', name: 'Пакистан', nameEn: 'Pakistan' },
|
||||
{ code: 'PA', name: 'Панама', nameEn: 'Panama' },
|
||||
{ code: 'PG', name: 'Папуа — Новая Гвинея', nameEn: 'Papua New Guinea' },
|
||||
{ code: 'PY', name: 'Парагвай', nameEn: 'Paraguay' },
|
||||
{ code: 'PE', name: 'Перу', nameEn: 'Peru' },
|
||||
{ code: 'PL', name: 'Польша', nameEn: 'Poland' },
|
||||
{ code: 'PT', name: 'Португалия', nameEn: 'Portugal' },
|
||||
{ code: 'RU', name: 'Россия', nameEn: 'Russia' },
|
||||
{ code: 'RW', name: 'Руанда', nameEn: 'Rwanda' },
|
||||
{ code: 'RO', name: 'Румыния', nameEn: 'Romania' },
|
||||
{ code: 'SV', name: 'Сальвадор', nameEn: 'El Salvador' },
|
||||
{ code: 'SA', name: 'Саудовская Аравия', nameEn: 'Saudi Arabia' },
|
||||
{ code: 'MK', name: 'Северная Македония', nameEn: 'North Macedonia' },
|
||||
{ code: 'SC', name: 'Сейшелы', nameEn: 'Seychelles' },
|
||||
{ code: 'SN', name: 'Сенегал', nameEn: 'Senegal' },
|
||||
{ code: 'RS', name: 'Сербия', nameEn: 'Serbia' },
|
||||
{ code: 'SG', name: 'Сингапур', nameEn: 'Singapore' },
|
||||
{ code: 'SK', name: 'Словакия', nameEn: 'Slovakia' },
|
||||
{ code: 'SI', name: 'Словения', nameEn: 'Slovenia' },
|
||||
{ code: 'SO', name: 'Сомали', nameEn: 'Somalia' },
|
||||
{ code: 'SD', name: 'Судан', nameEn: 'Sudan' },
|
||||
{ code: 'US', name: 'США', nameEn: 'United States' },
|
||||
{ code: 'TJ', name: 'Таджикистан', nameEn: 'Tajikistan' },
|
||||
{ code: 'TH', name: 'Таиланд', nameEn: 'Thailand' },
|
||||
{ code: 'TZ', name: 'Танзания', nameEn: 'Tanzania' },
|
||||
{ code: 'TG', name: 'Того', nameEn: 'Togo' },
|
||||
{ code: 'TT', name: 'Тринидад и Тобаго', nameEn: 'Trinidad and Tobago' },
|
||||
{ code: 'TV', name: 'Тувалу', nameEn: 'Tuvalu' },
|
||||
{ code: 'TN', name: 'Тунис', nameEn: 'Tunisia' },
|
||||
{ code: 'TM', name: 'Туркмения', nameEn: 'Turkmenistan' },
|
||||
{ code: 'TR', name: 'Турция', nameEn: 'Turkey' },
|
||||
{ code: 'UG', name: 'Уганда', nameEn: 'Uganda' },
|
||||
{ code: 'UZ', name: 'Узбекистан', nameEn: 'Uzbekistan' },
|
||||
{ code: 'UA', name: 'Украина', nameEn: 'Ukraine' },
|
||||
{ code: 'UY', name: 'Уругвай', nameEn: 'Uruguay' },
|
||||
{ code: 'FJ', name: 'Фиджи', nameEn: 'Fiji' },
|
||||
{ code: 'PH', name: 'Филиппины', nameEn: 'Philippines' },
|
||||
{ code: 'FI', name: 'Финляндия', nameEn: 'Finland' },
|
||||
{ code: 'FR', name: 'Франция', nameEn: 'France' },
|
||||
{ code: 'HR', name: 'Хорватия', nameEn: 'Croatia' },
|
||||
{ code: 'TD', name: 'Чад', nameEn: 'Chad' },
|
||||
{ code: 'ME', name: 'Черногория', nameEn: 'Montenegro' },
|
||||
{ code: 'CZ', name: 'Чехия', nameEn: 'Czechia' },
|
||||
{ code: 'CL', name: 'Чили', nameEn: 'Chile' },
|
||||
{ code: 'CH', name: 'Швейцария', nameEn: 'Switzerland' },
|
||||
{ code: 'SE', name: 'Швеция', nameEn: 'Sweden' },
|
||||
{ code: 'LK', name: 'Шри-Ланка', nameEn: 'Sri Lanka' },
|
||||
{ code: 'EC', name: 'Эквадор', nameEn: 'Ecuador' },
|
||||
{ code: 'GQ', name: 'Экваториальная Гвинея', nameEn: 'Equatorial Guinea' },
|
||||
{ code: 'ER', name: 'Эритрея', nameEn: 'Eritrea' },
|
||||
{ code: 'EE', name: 'Эстония', nameEn: 'Estonia' },
|
||||
{ code: 'ET', name: 'Эфиопия', nameEn: 'Ethiopia' },
|
||||
{ code: 'ZA', name: 'ЮАР', nameEn: 'South Africa' },
|
||||
{ code: 'JM', name: 'Ямайка', nameEn: 'Jamaica' },
|
||||
{ code: 'JP', name: 'Япония', nameEn: 'Japan' },
|
||||
]
|
||||
|
||||
export const COUNTRY_BY_CODE: Record<string, CountryRef> = Object.fromEntries(
|
||||
COUNTRIES.map((c) => [c.code, c]),
|
||||
)
|
||||
|
||||
export const COUNTRY_BY_NAME_RU: Record<string, CountryRef> = Object.fromEntries(
|
||||
COUNTRIES.map((c) => [c.name.toLowerCase(), c]),
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './countries.js'
|
||||
export * from './cities.js'
|
||||
export * from './loc-codes.js'
|
||||
@@ -0,0 +1,80 @@
|
||||
import { CITIES, type CityRef } from './cities.js'
|
||||
|
||||
/**
|
||||
* Канонические коды {loc} для hostname (из naming seed / docs).
|
||||
* Ключ — русское имя города (lower case).
|
||||
*/
|
||||
export const CANONICAL_LOC_CODES: Record<string, string> = {
|
||||
москва: 'msk',
|
||||
франкфурт: 'fra',
|
||||
амстердам: 'ams',
|
||||
хельсинки: 'hel',
|
||||
париж: 'par',
|
||||
'санкт-петербург': 'led',
|
||||
берлин: 'ber',
|
||||
мюнхен: 'muc',
|
||||
лондон: 'lon',
|
||||
'нью-йорк': 'nyc',
|
||||
токио: 'tyo',
|
||||
сингапур: 'sin',
|
||||
дубай: 'dxb',
|
||||
варшава: 'waw',
|
||||
прага: 'prg',
|
||||
вена: 'vie',
|
||||
рига: 'rix',
|
||||
таллин: 'tll',
|
||||
минск: 'msq',
|
||||
киев: 'iev',
|
||||
стокгольм: 'arn',
|
||||
осло: 'osl',
|
||||
копенгаген: 'cph',
|
||||
цюрих: 'zrh',
|
||||
брюссель: 'bru',
|
||||
мадрид: 'mad',
|
||||
барселона: 'bcn',
|
||||
милан: 'mxp',
|
||||
рим: 'rom',
|
||||
}
|
||||
|
||||
/** Детерминированный уникальный loc-код для города (hostname {loc}). */
|
||||
export function locCodeForCity(
|
||||
city: Pick<CityRef, 'name' | 'nameEn' | 'countryCode'>,
|
||||
usedCodes: ReadonlySet<string>,
|
||||
): string {
|
||||
const known = CANONICAL_LOC_CODES[city.name.trim().toLowerCase()]
|
||||
if (known && !usedCodes.has(known)) return known
|
||||
if (known) {
|
||||
// known занят другим — fallback ниже
|
||||
}
|
||||
|
||||
const base = city.nameEn.toLowerCase().replace(/[^a-z]/g, '')
|
||||
if (!base) {
|
||||
return `${city.countryCode.toLowerCase()}${usedCodes.size}`
|
||||
}
|
||||
|
||||
for (let len = 3; len <= Math.max(3, base.length); len++) {
|
||||
const candidate = base.slice(0, len)
|
||||
if (!usedCodes.has(candidate)) return candidate
|
||||
}
|
||||
|
||||
let i = 2
|
||||
while (usedCodes.has(`${base.slice(0, 2)}${i}`)) i += 1
|
||||
return `${base.slice(0, 2)}${i}`
|
||||
}
|
||||
|
||||
/** Все города каталога с выделенными loc-кодами (без коллизий). */
|
||||
export function catalogCitiesWithLocCodes(): Array<CityRef & { locCode: string }> {
|
||||
const used = new Set<string>()
|
||||
// Сначала закрепить канонические, чтобы generate не занял msk раньше Москвы
|
||||
const ordered = [...CITIES].sort((a, b) => {
|
||||
const ak = CANONICAL_LOC_CODES[a.name.toLowerCase()] ? 0 : 1
|
||||
const bk = CANONICAL_LOC_CODES[b.name.toLowerCase()] ? 0 : 1
|
||||
return ak - bk
|
||||
})
|
||||
|
||||
return ordered.map((city) => {
|
||||
const locCode = locCodeForCity(city, used)
|
||||
used.add(locCode)
|
||||
return { ...city, locCode }
|
||||
})
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from "./app-switcher.js";
|
||||
export * from "./settings.js";
|
||||
export * from "./fleet.js";
|
||||
export * from "./geo/index.js";
|
||||
|
||||
export class ValidationError extends Error {
|
||||
constructor(message: string) {
|
||||
|
||||
Reference in New Issue
Block a user