@code-plate/iran-cities 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/LICENSE +21 -0
- package/MIGRATION.fa.md +277 -0
- package/MIGRATION.md +272 -0
- package/NOTICE.md +17 -0
- package/README.fa.md +528 -201
- package/README.md +524 -201
- package/dist/boundaries.cjs +9218 -0
- package/dist/boundaries.cjs.map +1 -0
- package/dist/boundaries.d.cts +30 -0
- package/dist/boundaries.d.mts +30 -0
- package/dist/boundaries.mjs +9215 -0
- package/dist/boundaries.mjs.map +1 -0
- package/dist/changes-B-wz9-Gy.cjs +841 -0
- package/dist/changes-B-wz9-Gy.cjs.map +1 -0
- package/dist/changes-CMVZbfZO.d.mts +8 -0
- package/dist/changes-DLZkHBwZ.mjs +800 -0
- package/dist/changes-DLZkHBwZ.mjs.map +1 -0
- package/dist/changes-DnnwRAKl.d.cts +8 -0
- package/dist/components-B7060em0.mjs +240 -0
- package/dist/components-B7060em0.mjs.map +1 -0
- package/dist/components-BuMUB7XT.d.cts +47 -0
- package/dist/components-DZz72pd1.d.mts +47 -0
- package/dist/components-grWmOsaV.cjs +247 -0
- package/dist/components-grWmOsaV.cjs.map +1 -0
- package/dist/components.css +251 -0
- package/dist/data/cities.csv +1253 -0
- package/dist/data/cities.json +23305 -0
- package/dist/data/iran-cities.sql +1487 -0
- package/dist/data/iran-provinces.geojson +1 -0
- package/dist/data/provinces.csv +32 -0
- package/dist/data/provinces.json +460 -0
- package/dist/hooks-C6kjWLIE.mjs +134 -0
- package/dist/hooks-C6kjWLIE.mjs.map +1 -0
- package/dist/hooks-K7pTMF2R.cjs +139 -0
- package/dist/hooks-K7pTMF2R.cjs.map +1 -0
- package/dist/index.cjs +30 -9307
- package/dist/index.d.cts +25 -0
- package/dist/index.d.mts +25 -25
- package/dist/index.mjs +3 -9264
- package/dist/lite-CtNPRhU-.mjs +1926 -0
- package/dist/lite-CtNPRhU-.mjs.map +1 -0
- package/dist/lite-CutarzAc.cjs +2051 -0
- package/dist/lite-CutarzAc.cjs.map +1 -0
- package/dist/lite-react-1HVWJOWt.cjs +55 -0
- package/dist/lite-react-1HVWJOWt.cjs.map +1 -0
- package/dist/lite-react-Ds-DNmEi.mjs +20 -0
- package/dist/lite-react-Ds-DNmEi.mjs.map +1 -0
- package/dist/lite-react-components.cjs +12 -0
- package/dist/lite-react-components.cjs.map +1 -0
- package/dist/lite-react-components.d.cts +5 -0
- package/dist/lite-react-components.d.mts +5 -0
- package/dist/lite-react-components.mjs +11 -0
- package/dist/lite-react-components.mjs.map +1 -0
- package/dist/lite-react.cjs +8 -0
- package/dist/lite-react.d.cts +7 -0
- package/dist/lite-react.d.mts +7 -0
- package/dist/lite-react.mjs +2 -0
- package/dist/lite.cjs +28 -0
- package/dist/lite.d.cts +23 -0
- package/dist/lite.d.mts +23 -0
- package/dist/lite.mjs +3 -0
- package/dist/react-BKmJ476x.mjs +20 -0
- package/dist/react-BKmJ476x.mjs.map +1 -0
- package/dist/react-BZV3TkXB.cjs +55 -0
- package/dist/react-BZV3TkXB.cjs.map +1 -0
- package/dist/react-components.cjs +12 -0
- package/dist/react-components.cjs.map +1 -0
- package/dist/react-components.d.cts +5 -0
- package/dist/react-components.d.mts +5 -0
- package/dist/react-components.mjs +11 -0
- package/dist/react-components.mjs.map +1 -0
- package/dist/react.cjs +8 -9310
- package/dist/react.d.cts +7 -0
- package/dist/react.d.mts +7 -30
- package/dist/react.mjs +2 -9281
- package/dist/rolldown-runtime-78lK1XfQ.cjs +42 -0
- package/dist/rolldown-runtime-D3KUYPsf.mjs +12 -0
- package/dist/src-DRaUVbc4.mjs +4302 -0
- package/dist/src-DRaUVbc4.mjs.map +1 -0
- package/dist/src-mqZTwJfQ.cjs +4439 -0
- package/dist/src-mqZTwJfQ.cjs.map +1 -0
- package/dist/types/boundaries.d.ts +30 -0
- package/dist/types/changes.d.ts +4 -0
- package/dist/types/components.d.ts +47 -0
- package/dist/types/core.d.ts +25 -0
- package/dist/types/geo.d.ts +2 -0
- package/dist/types/hooks.d.ts +2 -0
- package/dist/types/index.d.ts +28 -0
- package/dist/types/keyboard.d.ts +3 -0
- package/dist/types/lite-react-components.d.ts +5 -0
- package/dist/types/lite-react.d.ts +7 -0
- package/dist/types/lite.d.ts +26 -0
- package/dist/types/normalize.d.ts +1 -0
- package/dist/types/packed.d.ts +16 -0
- package/dist/types/react-components.d.ts +5 -0
- package/dist/types/react.d.ts +7 -0
- package/dist/types/types.d.ts +149 -0
- package/dist/types/valibot.d.ts +23 -0
- package/dist/types/validation-shared.d.ts +11 -0
- package/dist/types/zod.d.ts +23 -0
- package/dist/types-Ds_b0yHP.d.cts +150 -0
- package/dist/types-Ds_b0yHP.d.mts +150 -0
- package/dist/valibot.cjs +47 -0
- package/dist/valibot.cjs.map +1 -0
- package/dist/valibot.d.cts +21 -0
- package/dist/valibot.d.mts +21 -0
- package/dist/valibot.mjs +38 -0
- package/dist/valibot.mjs.map +1 -0
- package/dist/validation-shared-BFilTuY1.cjs +58 -0
- package/dist/validation-shared-BFilTuY1.cjs.map +1 -0
- package/dist/validation-shared-C7l2cv4e.d.cts +8 -0
- package/dist/validation-shared-CJjGRx8p.d.mts +7 -0
- package/dist/validation-shared-mh_saZne.mjs +29 -0
- package/dist/validation-shared-mh_saZne.mjs.map +1 -0
- package/dist/zod.cjs +49 -0
- package/dist/zod.cjs.map +1 -0
- package/dist/zod.d.cts +21 -0
- package/dist/zod.d.mts +21 -0
- package/dist/zod.mjs +42 -0
- package/dist/zod.mjs.map +1 -0
- package/package.json +167 -22
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.ts +0 -25
- package/dist/index.mjs.map +0 -1
- package/dist/react.cjs.map +0 -1
- package/dist/react.d.ts +0 -30
- package/dist/react.mjs.map +0 -1
- package/dist/types-DwK3g-Z0.d.mts +0 -53
- package/dist/types-DwK3g-Z0.d.ts +0 -53
package/README.fa.md
CHANGED
|
@@ -1,295 +1,622 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center" dir="rtl">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[English](./README.md) · فارسی
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
# 🇮🇷 شهرهای ایران
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- ✅ Vue 2 / Vue 3
|
|
9
|
-
- ✅ Angular
|
|
10
|
-
- ✅ Svelte
|
|
11
|
-
- ✅ Node.js / Express (بکاند خالص)
|
|
12
|
-
- ✅ Vanilla JS (مرورگر یا هر باندلر دیگر)
|
|
7
|
+
**همهی استانها و شهرهای ایران، به فارسی و انگلیسی، با جستجوی سریع و هوشمند، مختصات، مرز استانها و کامپوننتهای آماده.**
|
|
13
8
|
|
|
14
|
-
|
|
9
|
+
بدون وابستگی · tree-shakeable · تایپ کامل · ساختهشده برای Next.js
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/@code-plate/iran-cities)
|
|
12
|
+
[](https://www.npmjs.com/package/@code-plate/iran-cities)
|
|
13
|
+
[](https://github.com/MJavadSF/Iran-Cities/actions/workflows/ci.yml)
|
|
14
|
+
[](https://scorecard.dev/viewer/?uri=github.com/MJavadSF/Iran-Cities)
|
|
15
|
+
[](#typescript)
|
|
16
|
+
|
|
17
|
+
**[مستندات](https://mjavadsf.github.io/Iran-Cities/fa/)** · **[زمین بازی زنده](https://mjavadsf.github.io/Iran-Cities/fa/playground/)** · **[نقشه](https://mjavadsf.github.io/Iran-Cities/fa/guides/geography/)**
|
|
18
|
+
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div dir="rtl">
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { getCity, searchCities, getNeighborProvinces } from '@code-plate/iran-cities';
|
|
25
|
+
|
|
26
|
+
getCity('شيراز'); // { id: 292, fa: 'شیراز', nameEn: 'Shiraz', provinceFa: 'فارس', phoneCode: '071', ... }
|
|
27
|
+
searchCities('jivhk'); // [تهران] با کیبورد انگلیسی تایپ شده
|
|
28
|
+
getNeighborProvinces('قم'); // [اصفهان، مرکزی، سمنان، تهران]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
<table dir="rtl">
|
|
32
|
+
<tr>
|
|
33
|
+
<td width="33%" valign="top">
|
|
34
|
+
|
|
35
|
+
### 📍 کامل
|
|
36
|
+
**۳۱** استان و **۱۲۵۲** شهر با id ثابت، نام فارسی و انگلیسی، کد تلفن، مرکز استان، استانهای همسایه و مختصات **۱۱۵۲** شهر که همگی با مرز استانها بررسی شدهاند.
|
|
37
|
+
|
|
38
|
+
</td>
|
|
39
|
+
<td width="33%" valign="top">
|
|
40
|
+
|
|
41
|
+
### 🔎 فارسیفهم
|
|
42
|
+
«ي/ی»، «ك/ک»، «آ/ا»، نیمفاصله، اعراب و ارقام فارسی را میشناسد، غلط تایپی را تحمل میکند و متنی را که با کیبورد اشتباه تایپ شده خودش درست میکند.
|
|
43
|
+
|
|
44
|
+
</td>
|
|
45
|
+
<td width="33%" valign="top">
|
|
46
|
+
|
|
47
|
+
### ⚡ آمادهی production
|
|
48
|
+
کامپوننتهای دسترسپذیر با CSS خام، اسکیمای Zod و Valibot، هوکهای `"use client"`، نسخهی `lite`، دادهی freeze شده و تایپهایی از TypeScript 3.9 تا 7.
|
|
49
|
+
|
|
50
|
+
</td>
|
|
51
|
+
</tr>
|
|
52
|
+
</table>
|
|
53
|
+
|
|
54
|
+
> [!TIP]
|
|
55
|
+
> از نسخهی 1.x ارتقا میدهید؟ بیشتر پروژهها به هیچ تغییری نیاز ندارند. [راهنمای مهاجرت](./MIGRATION.fa.md) را ببینید.
|
|
56
|
+
|
|
57
|
+
## فهرست
|
|
58
|
+
|
|
59
|
+
- [نصب](#نصب)
|
|
60
|
+
- [ورودیها](#ورودیها)
|
|
61
|
+
- [شروع سریع](#شروع-سریع)
|
|
62
|
+
- [Next.js](#nextjs)
|
|
63
|
+
- [کامپوننتها](#کامپوننتها)
|
|
64
|
+
- [اعتبارسنجی فرم](#اعتبارسنجی-فرم)
|
|
65
|
+
- [هوکهای React](#هوکهای-react)
|
|
66
|
+
- [نقشه و جغرافیا](#نقشه-و-جغرافیا)
|
|
67
|
+
- [Vue، Svelte، Angular و Express](#vue-svelte-angular-و-express)
|
|
68
|
+
- [زبانهای دیگر: CSV، SQL، JSON](#زبانهای-دیگر-csv-sql-json)
|
|
69
|
+
- [مرجع API](#مرجع-api)
|
|
70
|
+
- [تطبیق متن چطور کار میکند](#تطبیق-متن-چطور-کار-میکند)
|
|
71
|
+
- [دادهها و منابع](#دادهها-و-منابع)
|
|
72
|
+
- [سازگاری](#سازگاری)
|
|
73
|
+
- [مشارکت](#مشارکت)
|
|
74
|
+
- [مجوز و منابع](#مجوز-و-منابع)
|
|
15
75
|
|
|
16
76
|
## نصب
|
|
17
77
|
|
|
18
78
|
```bash
|
|
19
79
|
npm install @code-plate/iran-cities
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
<details>
|
|
83
|
+
<summary>pnpm، yarn، bun، Deno</summary>
|
|
84
|
+
|
|
85
|
+
```bash
|
|
23
86
|
pnpm add @code-plate/iran-cities
|
|
87
|
+
yarn add @code-plate/iran-cities
|
|
88
|
+
bun add @code-plate/iran-cities
|
|
89
|
+
deno add npm:@code-plate/iran-cities
|
|
24
90
|
```
|
|
25
91
|
|
|
26
|
-
|
|
92
|
+
</details>
|
|
27
93
|
|
|
28
|
-
|
|
29
|
-
- `۱۲۵۲` شهر
|
|
30
|
-
- هر رکورد شامل `id` (شناسهی عددی یکتا و پایدار)، `en` (شناسه لاتین kebab-case، مثل `tehran`) و `fa` (نام فارسی، مثل `تهران`) است.
|
|
31
|
-
- هر استان `phoneCode` (کد تلفن بینشهری) و `capitalId` (id مرکز استان) دارد؛ هر شهر `isCapital` دارد و مراکز استانها `coordinates: { lat, lng }` هم دارند.
|
|
32
|
-
- `id` شهرها در **کل کشور یکتاست** (شهرهای همنام مثل «مهاباد» در دو استان، دو id متفاوت دارند) و بین نسخهها تغییر نمیکند؛ شهر جدید همیشه id بعدی را میگیرد.
|
|
94
|
+
## ورودیها
|
|
33
95
|
|
|
34
|
-
|
|
96
|
+
کوچکترین ورودیای را انتخاب کنید که نیازتان را پوشش میدهد. حجمها بعد از minify و فشردهسازی brotli و همراه با دادهها هستند.
|
|
35
97
|
|
|
36
|
-
|
|
98
|
+
| import | محتوا | حجم |
|
|
99
|
+
|---|---|---:|
|
|
100
|
+
| `@code-plate/iran-cities` | همهی توابع، از جمله مختصات و فاصله | ۲۴ کیلوبایت |
|
|
101
|
+
| `@code-plate/iran-cities/lite` | همان جستجو و lookup، بدون مختصات | ۱۶ کیلوبایت |
|
|
102
|
+
| `@code-plate/iran-cities/react` | هوکهای React | ۲۵ کیلوبایت |
|
|
103
|
+
| `@code-plate/iran-cities/lite/react` | هوکهای React بدون مختصات | ۱۷ کیلوبایت |
|
|
104
|
+
| `@code-plate/iran-cities/lite/react/components` | `CityCombobox` و `ProvinceCitySelect` | ۱۸ کیلوبایت |
|
|
105
|
+
| `@code-plate/iran-cities/react/components` | همان کامپوننتها همراه با مختصات | ۲۶ کیلوبایت |
|
|
106
|
+
| `@code-plate/iran-cities/components.css` | استایل کامپوننتها | ۱٫۵ کیلوبایت |
|
|
107
|
+
| `@code-plate/iran-cities/zod` · `/valibot` | اسکیمای اعتبارسنجی | ۱۶ کیلوبایت |
|
|
108
|
+
| `@code-plate/iran-cities/boundaries` | مرز استانها (GeoJSON) و `getProvinceAt` | ۵۳ کیلوبایت |
|
|
109
|
+
| `@code-plate/iran-cities/data/*` | فایلهای CSV، SQL، JSON و GeoJSON | |
|
|
110
|
+
|
|
111
|
+
ورودیها کد مشترک دارند، پس import کردن چند ورودی هیچوقت داده را دو بار ship نمیکند. React، Zod و Valibot وابستگی peer اختیاری هستند.
|
|
112
|
+
|
|
113
|
+
## شروع سریع
|
|
114
|
+
|
|
115
|
+
### پیدا کردن
|
|
37
116
|
|
|
38
117
|
```ts
|
|
39
|
-
import {
|
|
40
|
-
getProvinces,
|
|
41
|
-
getProvincesList,
|
|
42
|
-
getProvince,
|
|
43
|
-
getCities,
|
|
44
|
-
getCity,
|
|
45
|
-
getCityById,
|
|
46
|
-
getProvinceById,
|
|
47
|
-
getCitiesByName,
|
|
48
|
-
getAllCities,
|
|
49
|
-
getProvinceOfCity,
|
|
50
|
-
searchProvinces,
|
|
51
|
-
searchCities,
|
|
52
|
-
getStats,
|
|
53
|
-
} from '@code-plate/iran-cities';
|
|
54
|
-
|
|
55
|
-
// همهی استانها به همراه شهرهایشان
|
|
56
|
-
getProvinces();
|
|
57
|
-
// [{ id: 1, en: 'alborz', fa: 'البرز', cities: [{ id: 1, en: 'karaj', fa: 'کرج', slug: 'alborz/karaj' }, ...] }, ...]
|
|
58
|
-
|
|
59
|
-
// فقط لیست استانها (بدون شهر) - برای Select/Dropdown
|
|
60
|
-
getProvincesList();
|
|
61
|
-
// [{ id: 1, en: 'alborz', fa: 'البرز' }, { id: 2, en: 'ardabil', fa: 'اردبیل' }, ...]
|
|
62
|
-
|
|
63
|
-
// گرفتن یک استان با نام انگلیسی یا فارسی
|
|
64
|
-
getProvince('tehran');
|
|
65
|
-
getProvince('تهران'); // همین نتیجه را میدهد
|
|
66
|
-
getProvince(28); // با id عددی هم کار میکند (رشتهی عددی '28' هم قبول است)
|
|
67
|
-
|
|
68
|
-
// شهرهای یک استان خاص
|
|
69
|
-
getCities('fars'); // بر اساس شناسه انگلیسی
|
|
70
|
-
getCities('فارس'); // یا بر اساس نام فارسی، هر دو یکسان کار میکنند
|
|
71
|
-
|
|
72
|
-
// پیدا کردن یک شهر مشخص (در کل کشور یا محدود به یک استان)
|
|
73
|
-
getCity('shiraz');
|
|
74
|
-
getCity('شیراز', 'fars'); // محدود به استان فارس
|
|
75
|
-
|
|
76
|
-
// لیست مسطح (flat) همهی شهرهای کشور همراه با استان مربوطه
|
|
77
|
-
getAllCities();
|
|
78
|
-
// [{ id: 1, en: 'karaj', fa: 'کرج', slug: 'alborz/karaj', provinceId: 1, provinceEn: 'alborz', provinceFa: 'البرز' }, ...]
|
|
79
|
-
|
|
80
|
-
// گرفتن شهر/استان با id
|
|
81
|
-
getCityById(292); // => شیراز (provinceId: 7)
|
|
82
|
-
getProvinceById(7); // => فارس
|
|
83
|
-
|
|
84
|
-
// نامهای تکراری: همهی شهرهای با این نام
|
|
85
|
-
getCitiesByName('مهاباد'); // => [اصفهان/مهاباد (id 270), آذربایجان غربی/مهاباد (id 1171)]
|
|
86
|
-
|
|
87
|
-
// پیدا کردن استانِ یک شهر
|
|
88
|
-
getProvinceOfCity('اصفهان'); // => { id: 6, en: 'isfahan', fa: 'اصفهان', cities: [...] }
|
|
89
|
-
getProvinceOfCity(292); // با id شهر هم کار میکند
|
|
90
|
-
|
|
91
|
-
// جستجوی زنده (autocomplete) در استانها یا شهرها
|
|
92
|
-
searchProvinces('خراسان'); // هر سه استان خراسان را برمیگرداند
|
|
93
|
-
searchCities('شهر', { limit: 10 }); // ۱۰ نتیجهی اول شامل «شهر»
|
|
94
|
-
searchCities('mashhad', { locale: 'en' }); // جستجو فقط در نام انگلیسی
|
|
95
|
-
|
|
96
|
-
// مرکز استان، کد تلفن و مختصات
|
|
97
|
-
getCapital('khuzestan'); // => { id: 674, en: 'ahvaz', isCapital: true, coordinates: { lat: 31.31901, lng: 48.6842 }, phoneCode: '061', ... }
|
|
98
|
-
getCapitals(); // ۳۱ مرکز استان
|
|
99
|
-
getProvinceByPhoneCode('021'); // => تهران ('21' و 21 هم قبول است)
|
|
100
|
-
getCity('نجفآباد').phoneCode; // => '031' (کد استانِ شهر)
|
|
101
|
-
getCity('اهواز').coordinates; // => { lat, lng, confidence } | برای شهرهای غیرمرکز استان: undefined
|
|
102
|
-
|
|
103
|
-
// آمار کلی
|
|
104
|
-
getStats(); // { provinceCount: 31, cityCount: 1252 }
|
|
105
|
-
```
|
|
118
|
+
import { getProvince, getCities, getCity, getCityById, getProvinceOfCity } from '@code-plate/iran-cities';
|
|
106
119
|
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
getProvince('tehran'); // با شناسهی انگلیسی
|
|
121
|
+
getProvince('تهران'); // با نام فارسی
|
|
122
|
+
getProvince(28); // با id (رشتهی '28' و '۲۸' هم کار میکند)
|
|
109
123
|
|
|
110
|
-
|
|
124
|
+
getCities('fars'); // همهی شهرهای فارس
|
|
125
|
+
getCity('شیراز'); // یک شهر در کل ایران
|
|
126
|
+
getCity('شیراز', 'fars'); // یک شهر داخل یک استان
|
|
127
|
+
getCityById(292); // با id سراسری
|
|
128
|
+
getProvinceOfCity('اصفهان'); // استانی که شهر در آن است
|
|
129
|
+
```
|
|
111
130
|
|
|
112
|
-
|
|
131
|
+
### جستجو و autocomplete
|
|
113
132
|
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
const { getProvincesList, getCities, searchCities } = require('@code-plate/iran-cities');
|
|
133
|
+
```ts
|
|
134
|
+
import { searchCities, searchProvinces } from '@code-plate/iran-cities';
|
|
117
135
|
|
|
118
|
-
|
|
136
|
+
searchProvinces('خراسان'); // [خراسان جنوبی، خراسان رضوی، خراسان شمالی]
|
|
137
|
+
searchCities('شهر', { limit: 10 }); // ۱۰ نتیجهی اول
|
|
138
|
+
searchCities('تهرن', { fuzzy: true }); // تحمل غلط تایپی: [تهران]
|
|
139
|
+
searchCities('jivhk'); // کیبورد اشتباه: [تهران]
|
|
140
|
+
searchCities('', { emptyQuery: 'none' }); // [] بهجای هر ۱۲۵۲ شهر
|
|
141
|
+
```
|
|
119
142
|
|
|
120
|
-
|
|
121
|
-
res.json(getProvincesList());
|
|
122
|
-
});
|
|
143
|
+
### همسایهها، مرکز استان و کد تلفن
|
|
123
144
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
145
|
+
```ts
|
|
146
|
+
import { getNeighborProvinces, areNeighborProvinces, getCapital, getProvinceByPhoneCode } from '@code-plate/iran-cities';
|
|
127
147
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
148
|
+
getNeighborProvinces('قم'); // [اصفهان، مرکزی، سمنان، تهران]
|
|
149
|
+
areNeighborProvinces('yazd', 'south-khorasan'); // true
|
|
150
|
+
getCapital('khuzestan'); // اهواز
|
|
151
|
+
getProvinceByPhoneCode('021'); // تهران ('21'، 21، '۰۲۱' و '+98 21' هم قبول است)
|
|
131
152
|
```
|
|
132
153
|
|
|
133
|
-
|
|
154
|
+
### نام نمایشی انگلیسی
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
getProvince(16)?.nameEn; // 'Kohgiluyeh and Boyer-Ahmad' (شناسهی en برابر 'kohgiluyeh-and-boyer-ahmad' است)
|
|
158
|
+
getCity('bandar-e-gaz')?.nameEn; // 'Bandar-e Gaz'
|
|
159
|
+
```
|
|
134
160
|
|
|
135
|
-
##
|
|
161
|
+
## Next.js
|
|
136
162
|
|
|
137
|
-
|
|
163
|
+
در **Server Componentها** از ورودی اصلی و در **Client Componentها** از هوکها یا کامپوننتها استفاده کنید. همهی ورودیهای React خودشان `"use client"` دارند.
|
|
138
164
|
|
|
139
165
|
```tsx
|
|
140
|
-
|
|
166
|
+
// app/address/page.tsx (Server Component)
|
|
167
|
+
import { getCity } from '@code-plate/iran-cities';
|
|
168
|
+
import AddressForm from './address-form';
|
|
169
|
+
import { save } from './actions';
|
|
170
|
+
|
|
171
|
+
export default function Page() {
|
|
172
|
+
const city = getCity('شیراز', 'fars');
|
|
173
|
+
return <AddressForm action={save} defaultCityId={city?.id} />;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
141
176
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
177
|
+
```tsx
|
|
178
|
+
// app/address/address-form.tsx (Client Component)
|
|
179
|
+
'use client';
|
|
145
180
|
|
|
146
|
-
|
|
147
|
-
|
|
181
|
+
import '@code-plate/iran-cities/components.css';
|
|
182
|
+
import { ProvinceCitySelect } from '@code-plate/iran-cities/lite/react/components';
|
|
148
183
|
|
|
184
|
+
export default function AddressForm({ action, defaultCityId }: { action: (data: FormData) => void; defaultCityId?: number }) {
|
|
149
185
|
return (
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
<option key={p.id} value={p.en}>{p.fa}</option>
|
|
155
|
-
))}
|
|
156
|
-
</select>
|
|
157
|
-
|
|
158
|
-
<select value={city} onChange={(e) => setCity(e.target.value)} disabled={!province}>
|
|
159
|
-
<option value="">انتخاب شهر</option>
|
|
160
|
-
{cities.map((c) => (
|
|
161
|
-
<option key={c.id} value={c.en}>{c.fa}</option>
|
|
162
|
-
))}
|
|
163
|
-
</select>
|
|
164
|
-
</>
|
|
186
|
+
<form action={action}>
|
|
187
|
+
<ProvinceCitySelect provinceName="provinceId" cityName="cityId" defaultValue={{ cityId: defaultCityId }} required />
|
|
188
|
+
<button type="submit">ثبت</button>
|
|
189
|
+
</form>
|
|
165
190
|
);
|
|
166
191
|
}
|
|
167
192
|
```
|
|
168
193
|
|
|
169
|
-
|
|
194
|
+
```ts
|
|
195
|
+
// app/address/actions.ts
|
|
196
|
+
'use server';
|
|
197
|
+
|
|
198
|
+
import { provinceCitySchema } from '@code-plate/iran-cities/zod';
|
|
199
|
+
|
|
200
|
+
export async function save(formData: FormData) {
|
|
201
|
+
const result = provinceCitySchema.safeParse({ provinceId: formData.get('provinceId'), cityId: formData.get('cityId') });
|
|
202
|
+
if (!result.success) return;
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
> [!TIP]
|
|
207
|
+
> در Client Componentها از ورودیهای `lite` استفاده کنید، مگر اینکه مرورگر واقعاً مختصات لازم داشته باشد. این کار حدود ۸ کیلوبایت از bundle کلاینت کم میکند.
|
|
208
|
+
|
|
209
|
+
با Next.js 14، 15 و 16 و با Turbopack و webpack تست شده است.
|
|
210
|
+
|
|
211
|
+
## کامپوننتها
|
|
212
|
+
|
|
213
|
+
دو کامپوننت دسترسپذیر که با یک فایل کوچک CSS خام استایل شدهاند (بدون Tailwind و بدون CSS-in-JS). [نسخهی زنده را ببینید](https://mjavadsf.github.io/Iran-Cities/fa/guides/components/).
|
|
170
214
|
|
|
171
215
|
```tsx
|
|
172
|
-
|
|
216
|
+
'use client';
|
|
173
217
|
|
|
174
|
-
|
|
175
|
-
|
|
218
|
+
import '@code-plate/iran-cities/components.css';
|
|
219
|
+
import { CityCombobox, ProvinceCitySelect } from '@code-plate/iran-cities/lite/react/components';
|
|
176
220
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
221
|
+
<ProvinceCitySelect provinceName="provinceId" cityName="cityId" onChange={({ provinceId, cityId }) => {}} />
|
|
222
|
+
<CityCombobox label="شهر مقصد" name="destinationId" province="fars" onChange={(city) => {}} />
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- **`CityCombobox`** از [الگوی combobox در WAI-ARIA](https://www.w3.org/WAI/ARIA/apg/patterns/combobox/) پیروی میکند: `↑` و `↓`، `Home` و `End`، `Enter` برای انتخاب، `Escape` برای بستن و `Escape` دوم برای پاک کردن. غلط تایپی را تحمل میکند و کیبورد اشتباه را درست میکند.
|
|
226
|
+
- **`ProvinceCitySelect`** دو `<select>` بومی با label است و با تغییر استان، شهر خالی میشود.
|
|
227
|
+
- هر دو **id** را از طریق فیلدهای معمولی فرم (`name`) ارسال میکنند، پس با فرم معمولی و Server Action کار میکنند. هر دو هم controlled (`value` و `onChange`) و هم uncontrolled (`defaultValue`) کار میکنند و بدون تغییر داخل `Controller` در react-hook-form قرار میگیرند.
|
|
228
|
+
- پیشفرض راستبهچپ (`dir="rtl"`)، نام انگلیسی با `locale="en"`، و حالت تاریک خودکار.
|
|
229
|
+
|
|
230
|
+
تم را با متغیرهای CSS روی هر والدی تغییر دهید. همهی selectorها با `:where()` نوشته شدهاند، پس کلاسهای خودتان بدون `!important` اولویت دارند:
|
|
231
|
+
|
|
232
|
+
```css
|
|
233
|
+
.checkout {
|
|
234
|
+
--ic-accent: #2563eb;
|
|
235
|
+
--ic-radius: 0.375rem;
|
|
236
|
+
--ic-height: 2.5rem;
|
|
237
|
+
--ic-font: 'Vazirmatn', sans-serif;
|
|
187
238
|
}
|
|
188
239
|
```
|
|
189
240
|
|
|
190
|
-
|
|
241
|
+
<details>
|
|
242
|
+
<summary>همهی متغیرهای CSS</summary>
|
|
191
243
|
|
|
192
|
-
|
|
|
244
|
+
| متغیر | |
|
|
193
245
|
|---|---|
|
|
194
|
-
| `
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
|
246
|
+
| `--ic-font`، `--ic-font-size`، `--ic-line-height` | تایپوگرافی |
|
|
247
|
+
| `--ic-height`، `--ic-radius`، `--ic-gap`، `--ic-padding-inline` | اندازه و فاصله |
|
|
248
|
+
| `--ic-bg`، `--ic-fg`، `--ic-muted`، `--ic-border`، `--ic-border-hover` | رنگها |
|
|
249
|
+
| `--ic-accent`، `--ic-accent-soft`، `--ic-ring` | رنگ اصلی و حلقهی focus |
|
|
250
|
+
| `--ic-danger`، `--ic-danger-ring` | حالت نامعتبر |
|
|
251
|
+
| `--ic-popover-bg`، `--ic-shadow`، `--ic-listbox-max-height` | فهرست پیشنهادها |
|
|
252
|
+
| `--ic-disabled-bg`، `--ic-transition` | سایر |
|
|
253
|
+
|
|
254
|
+
حالت تاریک از `prefers-color-scheme` پیروی میکند؛ برای اجبار، `data-theme="dark"` یا `"light"` را روی یک والد بگذارید.
|
|
255
|
+
|
|
256
|
+
</details>
|
|
257
|
+
|
|
258
|
+
## اعتبارسنجی فرم
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
import { provinceCitySchema, cityIdSchema, citySchema } from '@code-plate/iran-cities/zod';
|
|
262
|
+
|
|
263
|
+
provinceCitySchema.parse({ provinceId: '7', cityId: '292' }); // { provinceId: 7, cityId: 292 }
|
|
264
|
+
provinceCitySchema.safeParse({ provinceId: 28, cityId: 292 }); // خطا روی cityId: «این شهر در استان انتخابشده نیست.»
|
|
265
|
+
cityIdSchema.parse('۲۹۲'); // 292
|
|
266
|
+
citySchema.parse('شیراز'); // شیء کامل شهر
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
همین اسکیماها برای Valibot در `@code-plate/iran-cities/valibot` هم هست. پیامها بهطور پیشفرض فارسیاند؛ با `createZodSchemas({ ... })` یا `createValibotSchemas({ ... })` پیام خودتان را بدهید.
|
|
270
|
+
|
|
271
|
+
## هوکهای React
|
|
272
|
+
|
|
273
|
+
از `@code-plate/iran-cities/react` یا بدون مختصات از `@code-plate/iran-cities/lite/react` import کنید.
|
|
274
|
+
|
|
275
|
+
| هوک | خروجی |
|
|
276
|
+
|---|---|
|
|
277
|
+
| `useProvinces()` | فهرست استانها |
|
|
278
|
+
| `useCities(province)` | شهرهای یک استان |
|
|
279
|
+
| `useProvinceCitySelect(options?)` | همهی چیزهایی که یک فیلد استان ← شهر لازم دارد؛ گزینهها: `initialProvince`، `initialCity`، `value`، `onChange` |
|
|
280
|
+
| `useCitySearch(initialQuery?, options?)` | `{ query, setQuery, results }` |
|
|
281
|
+
| `useProvinceSearch(initialQuery?, options?)` | `{ query, setQuery, results }` |
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
const { provinces, cities, province, city, setProvince, setCity, provinceId, cityId } = useProvinceCitySelect({ initialCity: 292 });
|
|
285
|
+
const [value, setValue] = useState<ProvinceCityValue>({ provinceId: 7, cityId: 292 });
|
|
286
|
+
useProvinceCitySelect({ value, onChange: setValue }); // controlled
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## نقشه و جغرافیا
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { getDistance, getNearestCities } from '@code-plate/iran-cities';
|
|
293
|
+
import { getProvinceAt, provinceBoundaries } from '@code-plate/iran-cities/boundaries';
|
|
294
|
+
|
|
295
|
+
getDistance('tehran', 'karaj'); // 36.5 (کیلومتر)
|
|
296
|
+
getNearestCities({ lat: 29.6, lng: 52.5 }, { limit: 1 }); // [{ city: شیراز, distanceKm: 5.6 }]
|
|
297
|
+
getProvinceAt({ lat: 34.64, lng: 50.88 }); // قم
|
|
298
|
+
getProvinceAt({ lat: 48.85, lng: 2.35 }); // undefined (خارج از ایران)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
`provinceBoundaries` یک `FeatureCollection` استاندارد GeoJSON است که مستقیم با Leaflet، MapLibre و deck.gl کار میکند. هر شهری که مختصات دارد به استان خودش برمیگردد. [نقشه را امتحان کنید](https://mjavadsf.github.io/Iran-Cities/fa/guides/geography/).
|
|
199
302
|
|
|
200
|
-
|
|
303
|
+
## Vue، Svelte، Angular و Express
|
|
201
304
|
|
|
202
|
-
|
|
305
|
+
توابع اصلی توابع ساده و همگام هستند و در هر فریمورکی کار میکنند.
|
|
203
306
|
|
|
204
|
-
|
|
307
|
+
<details>
|
|
308
|
+
<summary><b>Vue 3</b></summary>
|
|
205
309
|
|
|
206
310
|
```vue
|
|
207
311
|
<script setup>
|
|
208
312
|
import { ref, computed } from 'vue';
|
|
209
|
-
import { getProvincesList, getCities } from '@code-plate/iran-cities';
|
|
313
|
+
import { getProvincesList, getCities } from '@code-plate/iran-cities/lite';
|
|
210
314
|
|
|
211
315
|
const provinces = getProvincesList();
|
|
212
|
-
const
|
|
213
|
-
const cities = computed(() => getCities(
|
|
316
|
+
const province = ref('');
|
|
317
|
+
const cities = computed(() => getCities(province.value));
|
|
214
318
|
</script>
|
|
215
319
|
|
|
216
320
|
<template>
|
|
217
|
-
<select v-model="
|
|
218
|
-
<option v-for="p in provinces" :key="p.
|
|
321
|
+
<select v-model="province">
|
|
322
|
+
<option v-for="p in provinces" :key="p.id" :value="p.en">{{ p.fa }}</option>
|
|
219
323
|
</select>
|
|
220
|
-
<select :disabled="!
|
|
221
|
-
<option v-for="c in cities" :key="c.
|
|
324
|
+
<select :disabled="!province">
|
|
325
|
+
<option v-for="c in cities" :key="c.id" :value="c.en">{{ c.fa }}</option>
|
|
222
326
|
</select>
|
|
223
327
|
</template>
|
|
224
328
|
```
|
|
225
329
|
|
|
226
|
-
|
|
330
|
+
</details>
|
|
331
|
+
|
|
332
|
+
<details>
|
|
333
|
+
<summary><b>Svelte</b></summary>
|
|
334
|
+
|
|
335
|
+
```svelte
|
|
336
|
+
<script>
|
|
337
|
+
import { getProvincesList, getCities } from '@code-plate/iran-cities/lite';
|
|
338
|
+
|
|
339
|
+
const provinces = getProvincesList();
|
|
340
|
+
let province = '';
|
|
341
|
+
$: cities = getCities(province);
|
|
342
|
+
</script>
|
|
343
|
+
|
|
344
|
+
<select bind:value={province}>
|
|
345
|
+
{#each provinces as p (p.id)}<option value={p.en}>{p.fa}</option>{/each}
|
|
346
|
+
</select>
|
|
347
|
+
<select disabled={!province}>
|
|
348
|
+
{#each cities as c (c.id)}<option value={c.en}>{c.fa}</option>{/each}
|
|
349
|
+
</select>
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
</details>
|
|
353
|
+
|
|
354
|
+
<details>
|
|
355
|
+
<summary><b>Angular</b></summary>
|
|
227
356
|
|
|
228
357
|
```ts
|
|
229
358
|
import { Injectable } from '@angular/core';
|
|
230
|
-
import { getProvincesList, getCities, type City } from '@code-plate/iran-cities';
|
|
359
|
+
import { getProvincesList, getCities, type City, type ProvinceListItem } from '@code-plate/iran-cities/lite';
|
|
231
360
|
|
|
232
361
|
@Injectable({ providedIn: 'root' })
|
|
233
362
|
export class IranLocationsService {
|
|
234
|
-
|
|
363
|
+
provinces(): ProvinceListItem[] {
|
|
235
364
|
return getProvincesList();
|
|
236
365
|
}
|
|
237
|
-
|
|
238
|
-
|
|
366
|
+
|
|
367
|
+
citiesOf(province: string): readonly City[] {
|
|
368
|
+
return getCities(province);
|
|
239
369
|
}
|
|
240
370
|
}
|
|
241
371
|
```
|
|
242
372
|
|
|
243
|
-
|
|
373
|
+
</details>
|
|
374
|
+
|
|
375
|
+
<details>
|
|
376
|
+
<summary><b>Node.js / Express</b></summary>
|
|
377
|
+
|
|
378
|
+
```js
|
|
379
|
+
const express = require('express');
|
|
380
|
+
const { getProvincesList, getCities, searchCities } = require('@code-plate/iran-cities');
|
|
381
|
+
|
|
382
|
+
const app = express();
|
|
383
|
+
|
|
384
|
+
app.get('/api/provinces', (req, res) => res.json(getProvincesList()));
|
|
385
|
+
app.get('/api/cities/search', (req, res) => res.json(searchCities(String(req.query.q ?? ''), { limit: 20 })));
|
|
386
|
+
app.get('/api/cities/:province', (req, res) => res.json(getCities(req.params.province)));
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
مسیر جستجو قبل از `/:province` تعریف شده؛ وگرنه Express کلمهی `search` را نام استان حساب میکند.
|
|
390
|
+
|
|
391
|
+
</details>
|
|
392
|
+
|
|
393
|
+
## زبانهای دیگر: CSV، SQL، JSON
|
|
394
|
+
|
|
395
|
+
پکیج همین دادهها را بهصورت فایل معمولی هم دارد، برای Laravel، Django، Go یا هر دیتابیسی:
|
|
396
|
+
|
|
397
|
+
| فایل | |
|
|
398
|
+
|---|---|
|
|
399
|
+
| `data/provinces.csv`، `data/cities.csv` | CSV با کدگذاری UTF-8 و سطر عنوان |
|
|
400
|
+
| `data/provinces.json`، `data/cities.json` | آرایهی مسطح JSON |
|
|
401
|
+
| `data/iran-cities.sql` | جدولها، کلیدهای خارجی و insertها برای PostgreSQL، MySQL، MariaDB و SQLite |
|
|
402
|
+
| `data/iran-provinces.geojson` | مرز استانها |
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
curl -O https://unpkg.com/@code-plate/iran-cities/dist/data/iran-cities.sql
|
|
406
|
+
psql mydb < iran-cities.sql
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## مرجع API
|
|
410
|
+
|
|
411
|
+
هر جا query خواسته شده، میتوانید نام فارسی، شناسهی انگلیسی، نام نمایشی انگلیسی یا id عددی (عدد یا رشته، حتی با ارقام فارسی) بدهید.
|
|
412
|
+
|
|
413
|
+
<details open>
|
|
414
|
+
<summary><b>استانها</b></summary>
|
|
415
|
+
|
|
416
|
+
| تابع | خروجی |
|
|
417
|
+
|---|---|
|
|
418
|
+
| `getProvinces()` | `readonly Province[]` همراه با شهرها |
|
|
419
|
+
| `getProvincesList()` | `ProvinceListItem[]` بدون شهرها (هر بار آرایهی جدید) |
|
|
420
|
+
| `getProvince(query)` | `Province \| undefined` |
|
|
421
|
+
| `getProvinceById(id)` | `Province \| undefined` |
|
|
422
|
+
| `getProvinceByPhoneCode(code)` | `Province \| undefined` |
|
|
423
|
+
| `getProvinceOfCity(cityQuery)` | `Province \| undefined` |
|
|
424
|
+
| `getNeighborProvinces(query)` | `Province[]` هممرز |
|
|
425
|
+
| `areNeighborProvinces(a, b)` | `boolean` |
|
|
426
|
+
|
|
427
|
+
</details>
|
|
428
|
+
|
|
429
|
+
<details open>
|
|
430
|
+
<summary><b>شهرها</b></summary>
|
|
431
|
+
|
|
432
|
+
| تابع | خروجی |
|
|
433
|
+
|---|---|
|
|
434
|
+
| `getCities(provinceQuery)` | `readonly City[]` |
|
|
435
|
+
| `getAllCities()` | `readonly FlatCity[]` |
|
|
436
|
+
| `getCity(cityQuery, provinceQuery?)` | `FlatCity \| undefined` |
|
|
437
|
+
| `getCityById(id)` | `FlatCity \| undefined` |
|
|
438
|
+
| `getCityBySlug(slug)` | `FlatCity \| undefined`، مثلاً `'fars/shiraz'`؛ slugهای قدیمی هم کار میکنند |
|
|
439
|
+
| `getCitiesByName(name)` | `readonly FlatCity[]`، همهی شهرهای همنام یا با همان نام جایگزین |
|
|
440
|
+
| `isAmbiguousCityName(name)` | `boolean` |
|
|
441
|
+
| `getCapital(provinceQuery)` | `FlatCity \| undefined` |
|
|
442
|
+
| `getCapitals()` | `FlatCity[]`، مراکز ۳۱ استان |
|
|
443
|
+
|
|
444
|
+
</details>
|
|
445
|
+
|
|
446
|
+
<details open>
|
|
447
|
+
<summary><b>جستجو</b></summary>
|
|
448
|
+
|
|
449
|
+
| تابع | خروجی |
|
|
450
|
+
|---|---|
|
|
451
|
+
| `searchCities(query, options?)` | `FlatCity[]` |
|
|
452
|
+
| `searchProvinces(query, options?)` | `Province[]` |
|
|
453
|
+
| `normalize(text)` | `string`، همان نرمالسازیای که همهی lookupها استفاده میکنند |
|
|
454
|
+
| `convertKeyboardLayout(text, 'fa' \| 'en')` | `string` |
|
|
455
|
+
|
|
456
|
+
| `SearchOptions` | پیشفرض | |
|
|
457
|
+
|---|---|---|
|
|
458
|
+
| `limit` | بدون محدودیت | حداکثر تعداد نتایج |
|
|
459
|
+
| `locale` | `'both'` | `'fa'`، `'en'` یا `'both'` |
|
|
460
|
+
| `fuzzy` | `false` | تحمل غلط تایپی |
|
|
461
|
+
| `maxDistance` | `2` | حداکثر فاصلهی ویرایشی در حالت `fuzzy`؛ حداکثر یکسوم طول query |
|
|
462
|
+
| `emptyQuery` | `'all'` | خروجی query خالی: `'all'` یا `'none'` |
|
|
463
|
+
| `keyboardFix` | `true` | اگر نتیجهای نبود، با چیدمان کیبورد دیگر دوباره جستجو میکند |
|
|
464
|
+
|
|
465
|
+
</details>
|
|
466
|
+
|
|
467
|
+
<details open>
|
|
468
|
+
<summary><b>جغرافیا</b></summary>
|
|
469
|
+
|
|
470
|
+
| تابع | خروجی |
|
|
471
|
+
|---|---|
|
|
472
|
+
| `getDistance(from, to)` | کیلومتر، یا `undefined` اگر یکی مختصات نداشته باشد |
|
|
473
|
+
| `getNearestCities(point, options?)` | `{ city, distanceKm }[]` به ترتیب فاصله؛ گزینهها `limit` (۵، و `0` یعنی همه)، `minConfidence` (`'low'`)، `maxDistanceKm` |
|
|
474
|
+
| `getProvinceAt(point, options?)` | `Province \| undefined`؛ نقطههایی که تا `maxDistanceKm` (۵) بیرون از مرز باشند به نزدیکترین استان نسبت داده میشوند *(ورودی boundaries)* |
|
|
475
|
+
| `getProvinceBoundary(id)` | یک feature از GeoJSON *(ورودی boundaries)* |
|
|
476
|
+
| `provinceBoundaries` | `FeatureCollection` از GeoJSON *(ورودی boundaries)* |
|
|
477
|
+
|
|
478
|
+
`from`، `to` و `point` میتوانند query شهر یا `{ lat, lng }` باشند.
|
|
244
479
|
|
|
245
|
-
|
|
480
|
+
</details>
|
|
246
481
|
|
|
247
|
-
|
|
482
|
+
<details open>
|
|
483
|
+
<summary><b>نسخهی داده</b></summary>
|
|
248
484
|
|
|
249
|
-
|
|
|
250
|
-
|
|
251
|
-
| `
|
|
252
|
-
| `
|
|
253
|
-
| `
|
|
254
|
-
| `getCapitals()` | - | `FlatCity[]` | مراکز ۳۱ استان |
|
|
255
|
-
| `getProvinceByPhoneCode(code)` | `string \| number` | `Province \| undefined` | استان با کد تلفن بینشهری |
|
|
256
|
-
| `getProvinceById(id)` | `number` | `Province \| undefined` | استان با id |
|
|
257
|
-
| `getCityById(id)` | `number` | `FlatCity \| undefined` | شهر با id سراسری |
|
|
258
|
-
| `getCitiesByName(name)` | `string` | `FlatCity[]` | همهی شهرهای با این نام (برای نامهای تکراری) |
|
|
259
|
-
| `getProvince(query)` | `string \| number` | `Province \| undefined` | یک استان با en یا fa |
|
|
260
|
-
| `getCities(provinceQuery)` | `string \| number` | `City[]` | شهرهای یک استان |
|
|
261
|
-
| `getCity(cityQuery, provinceQuery?)` | `string \| number, (string \| number)?` | `FlatCity \| undefined` | یک شهر مشخص |
|
|
262
|
-
| `getAllCities()` | - | `FlatCity[]` | همهی شهرها بهصورت مسطح |
|
|
263
|
-
| `getProvinceOfCity(cityQuery)` | `string \| number` | `Province \| undefined` | استانِ یک شهر |
|
|
264
|
-
| `searchProvinces(query, options?)` | `string, SearchOptions?` | `Province[]` | جستجوی fuzzy استان |
|
|
265
|
-
| `searchCities(query, options?)` | `string, SearchOptions?` | `FlatCity[]` | جستجوی fuzzy شهر |
|
|
266
|
-
| `getStats()` | - | `{provinceCount, cityCount}` | آمار کلی |
|
|
485
|
+
| export | |
|
|
486
|
+
|---|---|
|
|
487
|
+
| `DATA_VERSION` | نسخهی داده، مثلاً `'2.0.0'` |
|
|
488
|
+
| `DATA_CHANGES` | همهی تغییر نامها، اصلاحهای املایی و حذفها از نسخهی 1.0 |
|
|
489
|
+
| `getDataChanges(sinceVersion?)` | تغییرات بعد از یک نسخه، برای بهروز کردن مقادیری که ذخیره کردهاید |
|
|
267
490
|
|
|
268
|
-
|
|
491
|
+
</details>
|
|
269
492
|
|
|
270
|
-
###
|
|
493
|
+
### TypeScript
|
|
271
494
|
|
|
272
495
|
```ts
|
|
273
|
-
interface
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
496
|
+
interface Province {
|
|
497
|
+
id: number; // 1 – 31
|
|
498
|
+
en: string; // 'fars'
|
|
499
|
+
fa: string; // 'فارس'
|
|
500
|
+
nameEn: string; // 'Fars'
|
|
501
|
+
phoneCode: string; // '071'
|
|
502
|
+
capitalId: number;
|
|
503
|
+
neighborIds: readonly number[];
|
|
504
|
+
cities: readonly City[];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
interface City {
|
|
508
|
+
id: number; // در کل ایران یکتا و هرگز دوباره استفاده نمیشود
|
|
509
|
+
en: string; // 'shiraz'
|
|
510
|
+
fa: string; // 'شیراز'
|
|
511
|
+
nameEn: string; // 'Shiraz'
|
|
512
|
+
slug: string; // 'fars/shiraz'
|
|
513
|
+
isCapital: boolean;
|
|
514
|
+
aliases: readonly string[];
|
|
515
|
+
coordinates?: { lat: number; lng: number; confidence: 'high' | 'medium' | 'low' };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
interface FlatCity extends City {
|
|
519
|
+
provinceId: number;
|
|
520
|
+
provinceEn: string;
|
|
521
|
+
provinceFa: string;
|
|
522
|
+
provinceNameEn: string;
|
|
523
|
+
phoneCode: string;
|
|
524
|
+
}
|
|
277
525
|
```
|
|
278
526
|
|
|
279
|
-
|
|
527
|
+
همهی تایپها export شدهاند، از جمله `ProvinceListItem`، `SearchOptions`، `NearestCity`، `LatLng`، `ProvinceCityValue`، `CityComboboxProps`، `ProvinceCitySelectProps`، `DataChange` و اینترفیسهای `IranCities`، `IranCitiesGeo` و `IranCitiesHooks`.
|
|
528
|
+
|
|
529
|
+
> [!NOTE]
|
|
530
|
+
> استانها، شهرها و آرایههای مشترک با `Object.freeze` قفل شدهاند تا یک بخش برنامه نتواند تصادفاً دادهها را برای بخش دیگر خراب کند. برای تغییر، اول کپی بگیرید: `[...getCities('fars')].sort(...)`. `getProvincesList()`، `getCapitals()`، `getNeighborProvinces()` و توابع جستجو خودشان آرایهی جدید برمیگردانند.
|
|
531
|
+
|
|
532
|
+
## تطبیق متن چطور کار میکند
|
|
533
|
+
|
|
534
|
+
**نرمالسازی.** همهی lookupها و جستجوها متن نرمالشده را مقایسه میکنند، پس اینها یکی حساب میشوند:
|
|
535
|
+
|
|
536
|
+
| یکسان | |
|
|
537
|
+
|---|---|
|
|
538
|
+
| `ی` `ي` `ى` `ئ` | `ک` `ك` |
|
|
539
|
+
| `ا` `آ` `أ` `إ` `ٱ` | `ه` `ة` `ۀ` |
|
|
540
|
+
| `و` `ؤ` | `۰۱۲` `٠١٢` `012` |
|
|
541
|
+
|
|
542
|
+
اعراب، کشیده (`ـ`)، نیمفاصله، فاصله، `-`، `_` و آپاستروف نادیده گرفته میشوند و بزرگی و کوچکی حروف مهم نیست.
|
|
543
|
+
|
|
544
|
+
**چیدمان کیبورد.** اگر جستجو نتیجهای نداشته باشد، دوباره طوری جستجو میشود که انگار با چیدمان دیگر تایپ شده: `jivhk` ← `تهران` و `ساهقشظ` ← `shiraz`. با `{ keyboardFix: false }` خاموش میشود.
|
|
545
|
+
|
|
546
|
+
**ترتیب نتایج.** اول تطابق کامل، بعد شروع نام، بعد شاملبودن، و بعد (با `fuzzy`) فاصلهی ویرایشی. در امتیاز برابر، مراکز استان اول میآیند و بعد ترتیب الفبای فارسی.
|
|
547
|
+
|
|
548
|
+
**نامهای تکراری.** ۲۵ نام شهر در بیش از یک استان وجود دارد. بدون استان، `getCity(name)` مرکز استان را برمیگرداند، وگرنه شهر شناختهشدهتر را، وگرنه کوچکترین id را. اگر انتخاب مهم است، با `isAmbiguousCityName(name)` چک کنید و استان را هم بدهید یا انتخاب را از `getCitiesByName(name)` به کاربر بسپارید.
|
|
549
|
+
|
|
550
|
+
**نامهای جایگزین.** املاهای دیگر و نامهای قدیمی به شهر درست میرسند: `طهران`، `teheran`، `esfahan`، `mashad`، `orumiyeh`، `صومعه سارا`، `حلاشی`.
|
|
280
551
|
|
|
281
|
-
##
|
|
552
|
+
## دادهها و منابع
|
|
553
|
+
|
|
554
|
+
| | |
|
|
555
|
+
|---|---:|
|
|
556
|
+
| استان | ۳۱ |
|
|
557
|
+
| شهر | ۱۲۵۲ |
|
|
558
|
+
| شهر دارای مختصات | ۱۱۵۲ |
|
|
559
|
+
| ↳ اطمینان `high` | ۹۷۷ |
|
|
560
|
+
| ↳ اطمینان `medium` | ۹۸ |
|
|
561
|
+
| ↳ اطمینان `low` | ۷۷ |
|
|
562
|
+
| مرز مشترک بین استانها | ۷۴ |
|
|
563
|
+
|
|
564
|
+
- **`high`**: یک منبع مستقل همان نقطه را تأیید میکند.
|
|
565
|
+
- **`medium`**: نام در GeoNames یکتاست و مکان شبیه شهر است، ولی تأیید مستقل ندارد.
|
|
566
|
+
- **`low`**: فقط تطابق نام در GeoNames. برای نمایش تقریبی مناسب است و خطای چند کیلومتری ممکن است.
|
|
567
|
+
|
|
568
|
+
همهی مختصات در CI با مرز استانها بررسی میشوند و هر ۱۱۵۲ نقطه داخل استان خودشان هستند. برای ۱۰۰ شهر باقیمانده `coordinates` برابر `undefined` است و هیچ مقدار حدسی گذاشته نشده ([فهرست و دلیلها](./reports/coordinates-not-imported.csv)).
|
|
569
|
+
|
|
570
|
+
همسایهها از روی مرز استانها محاسبه شدهاند (حداقل ۳ کیلومتر مرز مشترک) و مرزهای فعلی را نشان میدهند، از جمله انتقال شهرستان طبس از یزد به خراسان جنوبی در سال ۱۳۹۲.
|
|
571
|
+
|
|
572
|
+
idها هرگز تغییر نمیکنند و دوباره استفاده نمیشوند. slugهای تغییرکرده همچنان کار میکنند و همهی تغییرات در `DATA_CHANGES` ثبت شدهاند.
|
|
573
|
+
|
|
574
|
+
## سازگاری
|
|
575
|
+
|
|
576
|
+
هر نسخه در CI روی این ترکیبها تست میشود:
|
|
577
|
+
|
|
578
|
+
| | پشتیبانی |
|
|
579
|
+
|---|---|
|
|
580
|
+
| Node.js | 14، 16، 18، 20، 22، 24 (CommonJS و ES module) |
|
|
581
|
+
| Deno، Bun | نسخههای فعلی |
|
|
582
|
+
| TypeScript | 3.9 تا 7، با `moduleResolution` برابر `node`، `node16` یا `bundler` |
|
|
583
|
+
| React | 16.8 به بالا (تستشده با 17، 18 و 19) |
|
|
584
|
+
| Next.js | 14، 15، 16 (App Router؛ Turbopack و webpack) |
|
|
585
|
+
| Zod، Valibot | Zod 3.25 به بالا و 4، Valibot 1 |
|
|
586
|
+
|
|
587
|
+
هر bundler که فیلد `exports` را بشناسد (Vite، webpack، Rollup، Rolldown، esbuild، Turbopack و ...) میتواند از پکیج استفاده کند.
|
|
588
|
+
|
|
589
|
+
## مشارکت
|
|
590
|
+
|
|
591
|
+
ارزشمندترین مشارکت، اصلاح داده است؛ لطفاً منبع را هم بنویسید. [CONTRIBUTING.fa.md](./CONTRIBUTING.fa.md) را ببینید.
|
|
282
592
|
|
|
283
593
|
```bash
|
|
284
594
|
npm install
|
|
285
|
-
npm
|
|
286
|
-
npm run
|
|
287
|
-
npm run
|
|
288
|
-
npm test # validate + typecheck
|
|
595
|
+
npm test # اعتبارسنجی داده، TypeScript 7، Oxlint، Vitest
|
|
596
|
+
npm run build # tsdown (Rolldown + Oxc)
|
|
597
|
+
npm run test:dist # smoke test فایلهای build شده
|
|
289
598
|
```
|
|
290
599
|
|
|
291
|
-
|
|
600
|
+
برای انتشار نسخهی جدید، `version` را در `package.json` و `CHANGELOG.md` بهروز کنید و بعد:
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
npm publish --access public
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
`npm publish` قبل از انتشار همهی بررسیها را اجرا میکند (تستها، build، smoke test، lint پکیج و بودجهی حجم) و اگر یکی خطا بدهد، انتشار متوقف میشود.
|
|
607
|
+
|
|
608
|
+
## مجوز و منابع
|
|
609
|
+
|
|
610
|
+
| | منبع | مجوز |
|
|
611
|
+
|---|---|---|
|
|
612
|
+
| کد | همین پروژه | [MIT](./LICENSE) |
|
|
613
|
+
| مختصات | [GeoNames](https://www.geonames.org) | CC BY 4.0 |
|
|
614
|
+
| مرزها و همسایهها | [geoBoundaries](https://www.geoboundaries.org) / © OpenStreetMap contributors | ODbL 1.0 |
|
|
615
|
+
|
|
616
|
+
فیلد license پکیج `MIT AND CC-BY-4.0 AND ODbL-1.0` است. ODbL فقط وقتی اعمال میشود که خود دادهی مرزها را بازنشر کنید؛ [`NOTICE.md`](./NOTICE.md) را ببینید.
|
|
292
617
|
|
|
293
|
-
|
|
618
|
+
<div align="center">
|
|
619
|
+
<sub>ساختهشده با ❤️ برای توسعهدهندههای فارسیزبان · <a href="./CHANGELOG.md">Changelog</a> · <a href="./MIGRATION.fa.md">راهنمای مهاجرت</a> · <a href="https://mjavadsf.github.io/Iran-Cities/fa/">مستندات</a> · <a href="https://github.com/MJavadSF/Iran-Cities/issues">Issues</a></sub>
|
|
620
|
+
</div>
|
|
294
621
|
|
|
295
|
-
|
|
622
|
+
</div>
|