urdu-text-utils 0.3.0 → 0.4.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 +28 -0
- package/README.md +41 -1
- package/dist/index.cjs +367 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +174 -6
- package/dist/index.d.ts +174 -6
- package/dist/index.iife.js +1 -1
- package/dist/index.iife.js.map +1 -1
- package/dist/index.js +359 -14
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add `formatUrduNumber`, which formats numbers with South Asian lakh/crore digit grouping (`1234567` -> `۱۲٬۳۴٬۵۶۷`) and Urdu digits by default, plus western and no-grouping modes and a `decimals` cap. Rounding is applied to the binary double identically in both ports, so JS and PHP stay byte-identical rather than diverging at ties.
|
|
8
|
+
- Add `formatUrduCurrency`, which picks `روپیہ` for exactly one and `روپے` otherwise, or spells the amount out with `style: "words"`.
|
|
9
|
+
- Add `toUrduOrdinal`, which spells ordinals and inflects for gender (`پہلا`/`پہلی`). 1-6 come from a table; larger ordinals suffix the last word of the cardinal, a word-final `و` contracts (`نواں`, `سواں`), and an exact scale value drops its `ایک` (`1000` -> `ہزارواں`).
|
|
10
|
+
- Add `parseUrduDate`, the inverse of `formatUrduDate`. It reads Urdu and English month names, Urdu/Arabic-Indic/ASCII digits, day-first numeric dates, ISO dates and day-period times, and returns the same wall-clock moment `formatUrduDate` formatted — verified over all 24 hours.
|
|
11
|
+
- Export the number word tables (`URDU_ONES`, `URDU_TEENS_AND_TENS`, `URDU_TENS_UNITS`, `URDU_SCALES`, `URDU_ORDINAL_IRREGULARS`) from the package root.
|
|
12
|
+
- Extend `scripts/verify-php-parity.mjs` to mirror `Numbers.php` and `Date.php`, so the no-PHP check covers 300 fixture cases instead of 89.
|
|
13
|
+
- Add `npm run diff:php` (`scripts/diff-php.mjs` with `php/tools/diff-runner.php`), which runs the same seeded randomized number and date inputs through the real TypeScript build and the real PHP port and fails on any disagreement. Unlike the fixtures it can cover inputs nobody wrote down, and unlike the parity mirror it executes PHP rather than re-describing it, so it observes PHP's own behaviour. Errors are compared too, so both languages must reject the same inputs.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `numberToUrduWords` now spells 21-99 with the real irregular Urdu words instead of a composed `بیس ایک`-style fallback. `21` is `اکیس` and `1234567` is `بارہ لاکھ چونتیس ہزار پانچ سو سڑسٹھ`; every cardinal from 0 to 99 is now a single correct word, covered by a completeness test.
|
|
18
|
+
- Complete the South Asian scale with `ارب` (10^9) and `کھرب` (10^11), which the docstring had promised but the scale table did not implement. Values below 10^8 are unchanged.
|
|
19
|
+
- Flow the number word tables through `php/data/tables.json`, generated from `src/numbers.ts`, so `Numbers.php` no longer keeps a hand-copied second set of cardinals.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `formatUrduNumber` no longer emits exponential notation for large magnitudes. `String(1e21)` is `"1e+21"` in JavaScript, which reached the output verbatim — and digit grouping then inserted its separator inside the exponent (`۱e,+۲۱`, with western grouping `1e,+21`). Anything at or above 10^21 now prints as digits, matching the PHP port.
|
|
24
|
+
- Fix the PHP port so `parseUrduDate` works at all. `DateTime::createFromFormat` requires exactly two digits for the `i` and `s` tokens, so the single-digit minute and second the port built (`0:0:0`) made it return `false`, and every non-numeric date parsed to `null`.
|
|
25
|
+
|
|
26
|
+
## 0.3.1
|
|
27
|
+
|
|
28
|
+
- Publish the PHP port to Packagist. A root composer.json (Packagist reads repo-root manifests only) exposes php/src/UrduTextUtils via PSR-4 autoload, with export-ignore rules in .gitattributes for a clean dist archive. The new Packagist workflow runs on the same version tags as the npm Release: a PHP 8.1-8.3 matrix re-verifies table sync, strict manifest validation and the 536-test parity suite, then notifies packagist.org's update API so every tag publishes to both registries. v0.3.1 is the first version indexed on Packagist (composer require urdu-text-utils/urdu-text-utils).
|
|
29
|
+
|
|
30
|
+
|
|
3
31
|
## 0.3.0
|
|
4
32
|
|
|
5
33
|
- Complete the PHP port (stages 0.2-0.4): add Detect.php, StopWords.php, Words.php sentence/statistics APIs, Stemmer.php, Date.php and Names.php with full fixture parity against the TypeScript source
|
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# urdu-text-utils — Urdu Text Processing Toolkit for JavaScript & TypeScript
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/urdu-text-utils)
|
|
4
|
+
[](https://packagist.org/packages/urdu-text-utils/urdu-text-utils)
|
|
4
5
|
[](https://www.jsdelivr.com/package/npm/urdu-text-utils)
|
|
5
6
|
[](https://unpkg.com/urdu-text-utils/)
|
|
6
7
|
[](https://github.com/Zaid-maker/urdu-text-utils/actions/workflows/ci.yml)
|
|
@@ -16,6 +17,25 @@ Zero runtime dependencies. ESM + CJS. Fully typed.
|
|
|
16
17
|
|
|
17
18
|
**[Documentation and live playground →](https://zaid-maker.github.io/urdu-text-utils/)**
|
|
18
19
|
|
|
20
|
+
## Also available for PHP
|
|
21
|
+
|
|
22
|
+
The complete toolkit ships as a native PHP package: [`urdu-text-utils/urdu-text-utils`](https://packagist.org/packages/urdu-text-utils/urdu-text-utils) on Packagist (PHP 8.1+, zero dependencies, no required extensions).
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
composer require urdu-text-utils/urdu-text-utils
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```php
|
|
29
|
+
use UrduTextUtils\Normalizer;
|
|
30
|
+
use UrduTextUtils\Search;
|
|
31
|
+
|
|
32
|
+
Normalizer::normalizeUrdu("كيا حال ہے"); // "کیا حال ہے"
|
|
33
|
+
Search::searchUrdu("محمد", ["مُحَمَّد علی", "احمد", "محمد خان"]);
|
|
34
|
+
// ["مُحَمَّد علی", "محمد خان"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The port is not a fork: the [TypeScript source stays the single source of truth](https://zaid-maker.github.io/urdu-text-utils/guide/php). Shared tables and parity fixtures are generated from it, and CI re-verifies both on every push on PHP 8.1–8.3, with and without `ext-intl`. Same functions, same output, byte for byte. See [`php/README.md`](./php/README.md) for the full API.
|
|
38
|
+
|
|
19
39
|
```bash
|
|
20
40
|
npm install urdu-text-utils
|
|
21
41
|
```
|
|
@@ -53,7 +73,11 @@ import {
|
|
|
53
73
|
isStopWord,
|
|
54
74
|
removeStopWords,
|
|
55
75
|
formatUrduDate,
|
|
76
|
+
parseUrduDate,
|
|
56
77
|
timeAgoUrdu,
|
|
78
|
+
formatUrduNumber,
|
|
79
|
+
formatUrduCurrency,
|
|
80
|
+
toUrduOrdinal,
|
|
57
81
|
stemUrdu,
|
|
58
82
|
stemUrduText,
|
|
59
83
|
isUrdu,
|
|
@@ -151,9 +175,17 @@ toArabicIndicDigits("123"); // "١٢٣"
|
|
|
151
175
|
|
|
152
176
|
parseUrduNumber("۱٬۲۳۴"); // 1234 — handles ٬ and ٫
|
|
153
177
|
parseUrduNumber("۳٫۱۴"); // 3.14
|
|
154
|
-
numberToUrduWords(
|
|
178
|
+
numberToUrduWords(1234567); // "بارہ لاکھ چونتیس ہزار پانچ سو سڑسٹھ" — South Asian scale
|
|
179
|
+
|
|
180
|
+
formatUrduNumber(1234567); // "۱۲٬۳۴٬۵۶۷" — lakh/crore grouping, not 1,234,567
|
|
181
|
+
formatUrduCurrency(1500); // "۱٬۵۰۰ روپے"
|
|
182
|
+
formatUrduCurrency(1); // "۱ روپیہ" — singular for exactly one
|
|
183
|
+
toUrduOrdinal(21); // "اکیسواں"
|
|
184
|
+
toUrduOrdinal(1, { gender: "feminine" }); // "پہلی"
|
|
155
185
|
```
|
|
156
186
|
|
|
187
|
+
The word tables cover every cardinal from 0 to 99 (`21` is `اکیس`, never a composed `بیس ایک`) and the scale runs ہزار → لاکھ → کروڑ → ارب → کھرب. `numberToUrduWords`, `formatUrduCurrency`'s `style: "words"` and `toUrduOrdinal` are `@experimental`: several of the 21–99 words have recognised variant spellings.
|
|
188
|
+
|
|
157
189
|
## Urdu dates & relative time
|
|
158
190
|
|
|
159
191
|
```ts
|
|
@@ -173,8 +205,16 @@ timeAgoUrdu(Date.now() - 3 * 3600 * 1000); // "۳ گھنٹے پہلے"
|
|
|
173
205
|
timeAgoUrdu(Date.now() - 86400 * 1000); // "کل"
|
|
174
206
|
timeAgoUrdu(Date.now() - 2 * 86400 * 1000);// "پرسوں"
|
|
175
207
|
timeAgoUrdu(Date.now() + 10 * 60 * 1000); // "۱۰ منٹ بعد"
|
|
208
|
+
|
|
209
|
+
// Parse it back — the inverse of formatUrduDate
|
|
210
|
+
parseUrduDate("۲۲ اگست ۲۰۲۶"); // Date(2026, 7, 22)
|
|
211
|
+
parseUrduDate("22/08/2026"); // day-first, Pakistani convention
|
|
212
|
+
parseUrduDate("۵ جنوری", { defaultYear: 1999 });
|
|
213
|
+
parseUrduDate("۳۱ فروری ۲۰۲۶"); // null — that date does not exist
|
|
176
214
|
```
|
|
177
215
|
|
|
216
|
+
Anything `formatUrduDate` can emit — including `صبح دوپہر شام رات` periods — parses back to the same wall-clock moment, hours included. Absolute instants (a trailing `Z` or offset) and Hijri month names return `null` rather than guessing.
|
|
217
|
+
|
|
178
218
|
## Urdu stemming
|
|
179
219
|
|
|
180
220
|
Rule-based stemmer and affix stripper with morphological vowel/letter restorations:
|
package/dist/index.cjs
CHANGED
|
@@ -193,8 +193,23 @@ function parseUrduNumber(input) {
|
|
|
193
193
|
if (!/^[+-]?(\d+(\.\d*)?|\.\d+)$/u.test(ascii)) return NaN;
|
|
194
194
|
return Number(ascii);
|
|
195
195
|
}
|
|
196
|
-
var
|
|
197
|
-
|
|
196
|
+
var SEPARATORS = {
|
|
197
|
+
urdu: { group: "\u066C", decimal: "\u066B" },
|
|
198
|
+
english: { group: ",", decimal: "." }
|
|
199
|
+
};
|
|
200
|
+
var URDU_ONES = [
|
|
201
|
+
"",
|
|
202
|
+
"\u0627\u06CC\u06A9",
|
|
203
|
+
"\u062F\u0648",
|
|
204
|
+
"\u062A\u06CC\u0646",
|
|
205
|
+
"\u0686\u0627\u0631",
|
|
206
|
+
"\u067E\u0627\u0646\u0686",
|
|
207
|
+
"\u0686\u06BE",
|
|
208
|
+
"\u0633\u0627\u062A",
|
|
209
|
+
"\u0622\u0679\u06BE",
|
|
210
|
+
"\u0646\u0648"
|
|
211
|
+
];
|
|
212
|
+
var URDU_TEENS_AND_TENS = {
|
|
198
213
|
10: "\u062F\u0633",
|
|
199
214
|
11: "\u06AF\u06CC\u0627\u0631\u06C1",
|
|
200
215
|
12: "\u0628\u0627\u0631\u06C1",
|
|
@@ -215,20 +230,104 @@ var TEENS_AND_TENS = {
|
|
|
215
230
|
90: "\u0646\u0648\u06D2",
|
|
216
231
|
100: "\u0633\u0648"
|
|
217
232
|
};
|
|
233
|
+
var URDU_TENS_UNITS = {
|
|
234
|
+
21: "\u0627\u06A9\u06CC\u0633",
|
|
235
|
+
22: "\u0628\u0627\u0626\u06CC\u0633",
|
|
236
|
+
23: "\u062A\u0626\u06CC\u0633",
|
|
237
|
+
24: "\u0686\u0648\u0628\u06CC\u0633",
|
|
238
|
+
25: "\u067E\u0686\u06CC\u0633",
|
|
239
|
+
26: "\u0686\u06BE\u0628\u06CC\u0633",
|
|
240
|
+
27: "\u0633\u062A\u0627\u0626\u06CC\u0633",
|
|
241
|
+
28: "\u0627\u0679\u06BE\u0627\u0626\u06CC\u0633",
|
|
242
|
+
29: "\u0627\u0646\u062A\u06CC\u0633",
|
|
243
|
+
31: "\u0627\u06A9\u062A\u06CC\u0633",
|
|
244
|
+
32: "\u0628\u062A\u06CC\u0633",
|
|
245
|
+
33: "\u062A\u06CC\u0646\u062A\u06CC\u0633",
|
|
246
|
+
34: "\u0686\u0648\u0646\u062A\u06CC\u0633",
|
|
247
|
+
35: "\u067E\u06CC\u0646\u062A\u06CC\u0633",
|
|
248
|
+
36: "\u0686\u06BE\u062A\u06CC\u0633",
|
|
249
|
+
37: "\u0633\u06CC\u0646\u062A\u06CC\u0633",
|
|
250
|
+
38: "\u0627\u0691\u062A\u06CC\u0633",
|
|
251
|
+
39: "\u0627\u0646\u062A\u0627\u0644\u06CC\u0633",
|
|
252
|
+
41: "\u0627\u06A9\u062A\u0627\u0644\u06CC\u0633",
|
|
253
|
+
42: "\u0628\u06CC\u0627\u0644\u06CC\u0633",
|
|
254
|
+
43: "\u062A\u06CC\u0646\u062A\u0627\u0644\u06CC\u0633",
|
|
255
|
+
44: "\u0686\u0648\u0627\u0644\u06CC\u0633",
|
|
256
|
+
45: "\u067E\u06CC\u0646\u062A\u0627\u0644\u06CC\u0633",
|
|
257
|
+
46: "\u0686\u06BE\u06CC\u0627\u0644\u06CC\u0633",
|
|
258
|
+
47: "\u0633\u06CC\u0646\u062A\u0627\u0644\u06CC\u0633",
|
|
259
|
+
48: "\u0627\u0691\u062A\u0627\u0644\u06CC\u0633",
|
|
260
|
+
49: "\u0627\u0646\u0686\u0627\u0633",
|
|
261
|
+
51: "\u0627\u06A9\u06CC\u0627\u0648\u0646",
|
|
262
|
+
52: "\u0628\u0627\u0648\u0646",
|
|
263
|
+
53: "\u062A\u0631\u067E\u0646",
|
|
264
|
+
54: "\u0686\u0648\u0646",
|
|
265
|
+
55: "\u067E\u0686\u067E\u0646",
|
|
266
|
+
56: "\u0686\u06BE\u067E\u0646",
|
|
267
|
+
57: "\u0633\u062A\u0627\u0648\u0646",
|
|
268
|
+
58: "\u0627\u0679\u06BE\u0627\u0648\u0646",
|
|
269
|
+
59: "\u0627\u0646\u0633\u0679\u06BE",
|
|
270
|
+
61: "\u0627\u06A9\u0633\u0679\u06BE",
|
|
271
|
+
62: "\u0628\u0627\u0633\u0679\u06BE",
|
|
272
|
+
63: "\u062A\u0631\u0633\u0679\u06BE",
|
|
273
|
+
64: "\u0686\u0648\u0646\u0633\u0679\u06BE",
|
|
274
|
+
65: "\u067E\u06CC\u0646\u0633\u0679\u06BE",
|
|
275
|
+
66: "\u0686\u06BE\u06CC\u0627\u0633\u0679\u06BE",
|
|
276
|
+
67: "\u0633\u0691\u0633\u0679\u06BE",
|
|
277
|
+
68: "\u0627\u0691\u0633\u0679\u06BE",
|
|
278
|
+
69: "\u0627\u0646\u06C1\u062A\u0631",
|
|
279
|
+
71: "\u0627\u06A9\u06C1\u062A\u0631",
|
|
280
|
+
72: "\u0628\u06C1\u062A\u0631",
|
|
281
|
+
73: "\u062A\u06C1\u062A\u0631",
|
|
282
|
+
74: "\u0686\u0648\u06C1\u062A\u0631",
|
|
283
|
+
75: "\u067E\u0686\u06C1\u062A\u0631",
|
|
284
|
+
76: "\u0686\u06BE\u06C1\u062A\u0631",
|
|
285
|
+
77: "\u0633\u062A\u062A\u0631",
|
|
286
|
+
78: "\u0627\u0679\u06BE\u06C1\u062A\u0631",
|
|
287
|
+
79: "\u0627\u0646\u0627\u0633\u06CC",
|
|
288
|
+
81: "\u0627\u06A9\u06CC\u0627\u0633\u06CC",
|
|
289
|
+
82: "\u0628\u06CC\u0627\u0633\u06CC",
|
|
290
|
+
83: "\u062A\u0631\u0627\u0633\u06CC",
|
|
291
|
+
84: "\u0686\u0648\u0631\u0627\u0633\u06CC",
|
|
292
|
+
85: "\u067E\u0686\u0627\u0633\u06CC",
|
|
293
|
+
86: "\u0686\u06BE\u06CC\u0627\u0633\u06CC",
|
|
294
|
+
87: "\u0633\u062A\u0627\u0633\u06CC",
|
|
295
|
+
88: "\u0627\u0679\u06BE\u0627\u0633\u06CC",
|
|
296
|
+
89: "\u0646\u0648\u0627\u0633\u06CC",
|
|
297
|
+
91: "\u0627\u06A9\u0627\u0646\u0648\u06D2",
|
|
298
|
+
92: "\u0628\u0627\u0646\u0648\u06D2",
|
|
299
|
+
93: "\u062A\u0631\u0627\u0646\u0648\u06D2",
|
|
300
|
+
94: "\u0686\u0648\u0631\u0627\u0646\u0648\u06D2",
|
|
301
|
+
95: "\u067E\u0686\u0627\u0646\u0648\u06D2",
|
|
302
|
+
96: "\u0686\u06BE\u06CC\u0627\u0646\u0648\u06D2",
|
|
303
|
+
97: "\u0633\u062A\u0627\u0646\u0648\u06D2",
|
|
304
|
+
98: "\u0627\u0679\u06BE\u0627\u0646\u0648\u06D2",
|
|
305
|
+
99: "\u0646\u0627\u0646\u0648\u06D2"
|
|
306
|
+
};
|
|
307
|
+
var URDU_SCALES = [
|
|
308
|
+
[1e11, "\u06A9\u06BE\u0631\u0628"],
|
|
309
|
+
[1e9, "\u0627\u0631\u0628"],
|
|
310
|
+
[1e7, "\u06A9\u0631\u0648\u0691"],
|
|
311
|
+
[1e5, "\u0644\u0627\u06A9\u06BE"],
|
|
312
|
+
[1e3, "\u06C1\u0632\u0627\u0631"],
|
|
313
|
+
[100, "\u0633\u0648"]
|
|
314
|
+
];
|
|
315
|
+
var URDU_ORDINAL_IRREGULARS = {
|
|
316
|
+
1: { masculine: "\u067E\u06C1\u0644\u0627", feminine: "\u067E\u06C1\u0644\u06CC" },
|
|
317
|
+
2: { masculine: "\u062F\u0648\u0633\u0631\u0627", feminine: "\u062F\u0648\u0633\u0631\u06CC" },
|
|
318
|
+
3: { masculine: "\u062A\u06CC\u0633\u0631\u0627", feminine: "\u062A\u06CC\u0633\u0631\u06CC" },
|
|
319
|
+
4: { masculine: "\u0686\u0648\u062A\u06BE\u0627", feminine: "\u0686\u0648\u062A\u06BE\u06CC" },
|
|
320
|
+
5: { masculine: "\u067E\u0627\u0646\u0686\u0648\u0627\u06BA", feminine: "\u067E\u0627\u0646\u0686\u0648\u06CC\u06BA" },
|
|
321
|
+
6: { masculine: "\u0686\u06BE\u0679\u0627", feminine: "\u0686\u06BE\u0679\u06CC" }
|
|
322
|
+
};
|
|
218
323
|
function numberToUrduWords(value) {
|
|
219
324
|
if (!Number.isFinite(value)) return "";
|
|
220
325
|
if (!Number.isInteger(value)) throw new TypeError("numberToUrduWords expects an integer");
|
|
221
326
|
if (value < 0) return `\u0645\u0646\u0641\u06CC ${numberToUrduWords(-value)}`;
|
|
222
327
|
if (value === 0) return "\u0635\u0641\u0631";
|
|
223
|
-
const scales = [
|
|
224
|
-
[1e7, "\u06A9\u0631\u0648\u0691"],
|
|
225
|
-
[1e5, "\u0644\u0627\u06A9\u06BE"],
|
|
226
|
-
[1e3, "\u06C1\u0632\u0627\u0631"],
|
|
227
|
-
[100, "\u0633\u0648"]
|
|
228
|
-
];
|
|
229
328
|
const parts = [];
|
|
230
329
|
let rest = value;
|
|
231
|
-
for (const [size, name] of
|
|
330
|
+
for (const [size, name] of URDU_SCALES) {
|
|
232
331
|
if (rest >= size) {
|
|
233
332
|
const count = Math.floor(rest / size);
|
|
234
333
|
rest %= size;
|
|
@@ -236,17 +335,83 @@ function numberToUrduWords(value) {
|
|
|
236
335
|
}
|
|
237
336
|
}
|
|
238
337
|
if (rest > 0) {
|
|
239
|
-
|
|
240
|
-
|
|
338
|
+
const irregular = URDU_TENS_UNITS[rest];
|
|
339
|
+
if (irregular !== void 0) {
|
|
340
|
+
parts.push(irregular);
|
|
341
|
+
} else if (URDU_TEENS_AND_TENS[rest]) {
|
|
342
|
+
parts.push(URDU_TEENS_AND_TENS[rest]);
|
|
241
343
|
} else if (rest < 10) {
|
|
242
|
-
parts.push(
|
|
344
|
+
parts.push(URDU_ONES[rest]);
|
|
243
345
|
} else {
|
|
244
346
|
const tens = Math.floor(rest / 10) * 10;
|
|
245
|
-
parts.push(`${
|
|
347
|
+
parts.push(`${URDU_TEENS_AND_TENS[tens] ?? ""} ${URDU_ONES[rest % 10] ?? ""}`.trim());
|
|
246
348
|
}
|
|
247
349
|
}
|
|
248
350
|
return parts.join(" ").replace(/\s+/gu, " ").trim();
|
|
249
351
|
}
|
|
352
|
+
function toDigitString(n) {
|
|
353
|
+
const plain = String(n);
|
|
354
|
+
const exponential = /^(\d)(?:\.(\d+))?e\+(\d+)$/u.exec(plain);
|
|
355
|
+
if (!exponential) return plain;
|
|
356
|
+
const lead = exponential[1];
|
|
357
|
+
const fraction = exponential[2] ?? "";
|
|
358
|
+
const zeros = Number(exponential[3]) - fraction.length;
|
|
359
|
+
return `${lead}${fraction}${"0".repeat(zeros)}`;
|
|
360
|
+
}
|
|
361
|
+
function groupDigits(intStr, grouping, separator) {
|
|
362
|
+
if (grouping === "none" || separator === "") return intStr;
|
|
363
|
+
if (grouping === "western") {
|
|
364
|
+
return intStr.replace(/\B(?=(\d{3})+(?!\d))/gu, separator);
|
|
365
|
+
}
|
|
366
|
+
const lastThree = intStr.slice(-3);
|
|
367
|
+
const rest = intStr.slice(0, -3);
|
|
368
|
+
return rest === "" ? lastThree : `${rest.replace(/\B(?=(\d{2})+(?!\d))/gu, separator)}${separator}${lastThree}`;
|
|
369
|
+
}
|
|
370
|
+
function formatUrduNumber(value, options = {}) {
|
|
371
|
+
if (!Number.isFinite(value)) return "";
|
|
372
|
+
const digits = options.digits ?? "urdu";
|
|
373
|
+
const grouping = options.grouping ?? "southAsian";
|
|
374
|
+
const decimals = Math.min(15, Math.max(0, Math.floor(options.decimals ?? 2)));
|
|
375
|
+
const separators = SEPARATORS[digits];
|
|
376
|
+
const negative = value < 0;
|
|
377
|
+
const abs = Math.abs(value);
|
|
378
|
+
const scale = 10 ** decimals;
|
|
379
|
+
let intPart = Math.floor(abs);
|
|
380
|
+
let scaledFraction = Math.floor((abs - intPart) * scale + 0.5);
|
|
381
|
+
if (scaledFraction >= scale) {
|
|
382
|
+
intPart += 1;
|
|
383
|
+
scaledFraction -= scale;
|
|
384
|
+
}
|
|
385
|
+
const intStr = groupDigits(toDigitString(intPart), grouping, separators.group);
|
|
386
|
+
const fraction = String(scaledFraction).padStart(decimals, "0").replace(/0+$/u, "");
|
|
387
|
+
const body = fraction === "" ? intStr : `${intStr}${separators.decimal}${fraction}`;
|
|
388
|
+
const sign = negative ? "-" : "";
|
|
389
|
+
return digits === "urdu" ? `${sign}${toUrduDigits(body)}` : `${sign}${body}`;
|
|
390
|
+
}
|
|
391
|
+
function formatUrduCurrency(value, options = {}) {
|
|
392
|
+
if (!Number.isFinite(value)) return "";
|
|
393
|
+
const style = options.style ?? "digits";
|
|
394
|
+
const unit = options.unit ?? "\u0631\u0648\u067E\u06D2";
|
|
395
|
+
const singularUnit = options.singularUnit ?? "\u0631\u0648\u067E\u06CC\u06C1";
|
|
396
|
+
const amount = style === "words" ? numberToUrduWords(value) : formatUrduNumber(value, options);
|
|
397
|
+
if (amount === "") return "";
|
|
398
|
+
return `${amount} ${Math.abs(value) === 1 ? singularUnit : unit}`;
|
|
399
|
+
}
|
|
400
|
+
function toUrduOrdinal(value, options = {}) {
|
|
401
|
+
if (!Number.isFinite(value)) return "";
|
|
402
|
+
if (!Number.isInteger(value)) throw new TypeError("toUrduOrdinal expects an integer");
|
|
403
|
+
const gender = options.gender ?? "masculine";
|
|
404
|
+
if (value < 0) return `\u0645\u0646\u0641\u06CC ${toUrduOrdinal(-value, options)}`;
|
|
405
|
+
if (value === 0) return "\u0635\u0641\u0631";
|
|
406
|
+
const irregular = URDU_ORDINAL_IRREGULARS[value];
|
|
407
|
+
if (irregular) return irregular[gender];
|
|
408
|
+
const words = numberToUrduWords(value).split(" ");
|
|
409
|
+
const last = words.pop();
|
|
410
|
+
const stem = last.endsWith("\u0648") ? last.slice(0, -1) : last;
|
|
411
|
+
const ordinated = `${stem}${gender === "feminine" ? "\u0648\u06CC\u06BA" : "\u0648\u0627\u06BA"}`;
|
|
412
|
+
const prefix = words.length === 1 && words[0] === "\u0627\u06CC\u06A9" ? [] : words;
|
|
413
|
+
return [...prefix, ordinated].join(" ");
|
|
414
|
+
}
|
|
250
415
|
|
|
251
416
|
// src/detect.ts
|
|
252
417
|
function urduRatio(input) {
|
|
@@ -4078,6 +4243,186 @@ function timeAgoUrdu(date, relativeTo = /* @__PURE__ */ new Date(), options = {}
|
|
|
4078
4243
|
if (!addSuffix) return `${count} \u0633\u0627\u0644`;
|
|
4079
4244
|
return isPast ? `${count} \u0633\u0627\u0644 \u067E\u06C1\u0644\u06D2` : `${count} \u0633\u0627\u0644 \u0628\u0639\u062F`;
|
|
4080
4245
|
}
|
|
4246
|
+
var ENGLISH_MONTH_NAMES = [
|
|
4247
|
+
"january",
|
|
4248
|
+
"february",
|
|
4249
|
+
"march",
|
|
4250
|
+
"april",
|
|
4251
|
+
"may",
|
|
4252
|
+
"june",
|
|
4253
|
+
"july",
|
|
4254
|
+
"august",
|
|
4255
|
+
"september",
|
|
4256
|
+
"october",
|
|
4257
|
+
"november",
|
|
4258
|
+
"december"
|
|
4259
|
+
];
|
|
4260
|
+
var ENGLISH_WEEKDAY_NAMES = [
|
|
4261
|
+
"sunday",
|
|
4262
|
+
"monday",
|
|
4263
|
+
"tuesday",
|
|
4264
|
+
"wednesday",
|
|
4265
|
+
"thursday",
|
|
4266
|
+
"friday",
|
|
4267
|
+
"saturday"
|
|
4268
|
+
];
|
|
4269
|
+
var MONTH_LOOKUP = (() => {
|
|
4270
|
+
const lookup = /* @__PURE__ */ Object.create(null);
|
|
4271
|
+
URDU_MONTHS_GREGORIAN.forEach((name, index) => {
|
|
4272
|
+
lookup[name] = index;
|
|
4273
|
+
});
|
|
4274
|
+
ENGLISH_MONTH_NAMES.forEach((name, index) => {
|
|
4275
|
+
lookup[name] = index;
|
|
4276
|
+
lookup[name.slice(0, 3)] = index;
|
|
4277
|
+
});
|
|
4278
|
+
return lookup;
|
|
4279
|
+
})();
|
|
4280
|
+
var WEEKDAY_TOKENS = /* @__PURE__ */ new Set();
|
|
4281
|
+
for (const name of URDU_WEEKDAYS) WEEKDAY_TOKENS.add(name);
|
|
4282
|
+
for (const name of ENGLISH_WEEKDAY_NAMES) {
|
|
4283
|
+
WEEKDAY_TOKENS.add(name);
|
|
4284
|
+
WEEKDAY_TOKENS.add(name.slice(0, 3));
|
|
4285
|
+
}
|
|
4286
|
+
var URDU_PERIODS = Object.assign(
|
|
4287
|
+
/* @__PURE__ */ Object.create(null),
|
|
4288
|
+
{
|
|
4289
|
+
\u0635\u0628\u062D: "morning",
|
|
4290
|
+
\u062F\u0648\u067E\u06C1\u0631: "afternoon",
|
|
4291
|
+
\u0634\u0627\u0645: "evening",
|
|
4292
|
+
\u0631\u0627\u062A: "night"
|
|
4293
|
+
}
|
|
4294
|
+
);
|
|
4295
|
+
function resolveHour(hour, period) {
|
|
4296
|
+
switch (period) {
|
|
4297
|
+
case "am":
|
|
4298
|
+
case "morning":
|
|
4299
|
+
return hour === 12 ? 0 : hour;
|
|
4300
|
+
case "pm":
|
|
4301
|
+
case "afternoon":
|
|
4302
|
+
case "evening":
|
|
4303
|
+
return hour < 12 ? hour + 12 : hour;
|
|
4304
|
+
case "night":
|
|
4305
|
+
if (hour === 12) return 0;
|
|
4306
|
+
return hour >= 8 && hour <= 11 ? hour + 12 : hour;
|
|
4307
|
+
default:
|
|
4308
|
+
return hour;
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
function buildLocalDate(year, month, day, hour, minute, second) {
|
|
4312
|
+
if (!Number.isInteger(year) || year < 100) return null;
|
|
4313
|
+
if (!Number.isInteger(month) || month < 0 || month > 11) return null;
|
|
4314
|
+
if (!Number.isInteger(day) || day < 1 || day > 31) return null;
|
|
4315
|
+
if (!Number.isInteger(hour) || hour < 0 || hour > 23) return null;
|
|
4316
|
+
if (!Number.isInteger(minute) || minute < 0 || minute > 59) return null;
|
|
4317
|
+
if (!Number.isInteger(second) || second < 0 || second > 59) return null;
|
|
4318
|
+
const date = new Date(year, month, day, hour, minute, second);
|
|
4319
|
+
if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day || date.getHours() !== hour) {
|
|
4320
|
+
return null;
|
|
4321
|
+
}
|
|
4322
|
+
return date;
|
|
4323
|
+
}
|
|
4324
|
+
function expandYear(year) {
|
|
4325
|
+
if (year >= 100) return year;
|
|
4326
|
+
return year < 70 ? 2e3 + year : 1900 + year;
|
|
4327
|
+
}
|
|
4328
|
+
var INVISIBLE_CHARS_RE = /[\u200B-\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/gu;
|
|
4329
|
+
function parseNumericDate(input) {
|
|
4330
|
+
const iso = /^(\d{4})-(\d{1,2})-(\d{1,2})(?:[T ](\d{1,2}):(\d{2})(?::(\d{2}))?)?$/u.exec(input);
|
|
4331
|
+
if (iso) {
|
|
4332
|
+
return buildLocalDate(
|
|
4333
|
+
Number(iso[1]),
|
|
4334
|
+
Number(iso[2]) - 1,
|
|
4335
|
+
Number(iso[3]),
|
|
4336
|
+
iso[4] === void 0 ? 0 : Number(iso[4]),
|
|
4337
|
+
iso[5] === void 0 ? 0 : Number(iso[5]),
|
|
4338
|
+
iso[6] === void 0 ? 0 : Number(iso[6])
|
|
4339
|
+
);
|
|
4340
|
+
}
|
|
4341
|
+
const dayFirst = /^(\d{1,2})[/\-.](\d{1,2})[/\-.](\d{2,4})(?:[ ,T]+(\d{1,2}):(\d{2})(?::(\d{2}))?)?$/u.exec(input);
|
|
4342
|
+
if (dayFirst) {
|
|
4343
|
+
return buildLocalDate(
|
|
4344
|
+
expandYear(Number(dayFirst[3])),
|
|
4345
|
+
Number(dayFirst[2]) - 1,
|
|
4346
|
+
Number(dayFirst[1]),
|
|
4347
|
+
dayFirst[4] === void 0 ? 0 : Number(dayFirst[4]),
|
|
4348
|
+
dayFirst[5] === void 0 ? 0 : Number(dayFirst[5]),
|
|
4349
|
+
dayFirst[6] === void 0 ? 0 : Number(dayFirst[6])
|
|
4350
|
+
);
|
|
4351
|
+
}
|
|
4352
|
+
return null;
|
|
4353
|
+
}
|
|
4354
|
+
function parseUrduDate(input, options = {}) {
|
|
4355
|
+
if (!input) return null;
|
|
4356
|
+
const normalized = toEnglishDigits(input).replace(INVISIBLE_CHARS_RE, "").replace(/[،۔]/gu, " ").replace(/\s+/gu, " ").trim();
|
|
4357
|
+
if (normalized === "") return null;
|
|
4358
|
+
const numeric = parseNumericDate(normalized);
|
|
4359
|
+
if (numeric) return numeric;
|
|
4360
|
+
let month = null;
|
|
4361
|
+
let hour = null;
|
|
4362
|
+
let minute = 0;
|
|
4363
|
+
let second = 0;
|
|
4364
|
+
let period;
|
|
4365
|
+
const numbers = [];
|
|
4366
|
+
for (const rawToken of normalized.split(" ")) {
|
|
4367
|
+
const token = rawToken.replace(/^[^\p{L}\p{N}]+/u, "").replace(/[^\p{L}\p{N}]+$/u, "");
|
|
4368
|
+
if (token === "") continue;
|
|
4369
|
+
const urduPeriod = URDU_PERIODS[token];
|
|
4370
|
+
if (urduPeriod) {
|
|
4371
|
+
period = urduPeriod;
|
|
4372
|
+
continue;
|
|
4373
|
+
}
|
|
4374
|
+
const lower = token.toLowerCase();
|
|
4375
|
+
if (lower === "am" || lower === "pm") {
|
|
4376
|
+
period = lower;
|
|
4377
|
+
continue;
|
|
4378
|
+
}
|
|
4379
|
+
if (WEEKDAY_TOKENS.has(token) || WEEKDAY_TOKENS.has(lower)) continue;
|
|
4380
|
+
const time = /^(\d{1,2}):(\d{2})(?::(\d{2}))?$/u.exec(token);
|
|
4381
|
+
if (time) {
|
|
4382
|
+
hour = Number(time[1]);
|
|
4383
|
+
minute = Number(time[2]);
|
|
4384
|
+
second = time[3] === void 0 ? 0 : Number(time[3]);
|
|
4385
|
+
continue;
|
|
4386
|
+
}
|
|
4387
|
+
const monthIndex = MONTH_LOOKUP[token] ?? MONTH_LOOKUP[lower];
|
|
4388
|
+
if (monthIndex !== void 0) {
|
|
4389
|
+
month = monthIndex;
|
|
4390
|
+
continue;
|
|
4391
|
+
}
|
|
4392
|
+
if (/^\d+$/u.test(token)) numbers.push(Number(token));
|
|
4393
|
+
}
|
|
4394
|
+
if (month === null || numbers.length === 0) return null;
|
|
4395
|
+
let day;
|
|
4396
|
+
let year;
|
|
4397
|
+
if (numbers.length === 1) {
|
|
4398
|
+
const only = numbers[0];
|
|
4399
|
+
if (only > 31) {
|
|
4400
|
+
day = 1;
|
|
4401
|
+
year = only;
|
|
4402
|
+
} else {
|
|
4403
|
+
day = only;
|
|
4404
|
+
year = options.defaultYear ?? (/* @__PURE__ */ new Date()).getFullYear();
|
|
4405
|
+
}
|
|
4406
|
+
} else {
|
|
4407
|
+
const first = numbers[0];
|
|
4408
|
+
const last = numbers[numbers.length - 1];
|
|
4409
|
+
if (first > 31 && last <= 31) {
|
|
4410
|
+
day = last;
|
|
4411
|
+
year = first;
|
|
4412
|
+
} else {
|
|
4413
|
+
day = first;
|
|
4414
|
+
year = last;
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
return buildLocalDate(
|
|
4418
|
+
expandYear(year),
|
|
4419
|
+
month,
|
|
4420
|
+
day,
|
|
4421
|
+
hour === null ? 0 : resolveHour(hour, period),
|
|
4422
|
+
minute,
|
|
4423
|
+
second
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4081
4426
|
|
|
4082
4427
|
// src/stemmer.ts
|
|
4083
4428
|
var URDU_PREFIXES = [
|
|
@@ -4721,9 +5066,14 @@ exports.URDU_FAMILY_NAMES = URDU_FAMILY_NAMES;
|
|
|
4721
5066
|
exports.URDU_FIRST_NAMES = URDU_FIRST_NAMES;
|
|
4722
5067
|
exports.URDU_MONTHS_GREGORIAN = URDU_MONTHS_GREGORIAN;
|
|
4723
5068
|
exports.URDU_MONTHS_HIJRI = URDU_MONTHS_HIJRI;
|
|
5069
|
+
exports.URDU_ONES = URDU_ONES;
|
|
5070
|
+
exports.URDU_ORDINAL_IRREGULARS = URDU_ORDINAL_IRREGULARS;
|
|
4724
5071
|
exports.URDU_PREFIXES = URDU_PREFIXES;
|
|
5072
|
+
exports.URDU_SCALES = URDU_SCALES;
|
|
4725
5073
|
exports.URDU_STOP_WORDS = URDU_STOP_WORDS;
|
|
4726
5074
|
exports.URDU_SUFFIXES = URDU_SUFFIXES;
|
|
5075
|
+
exports.URDU_TEENS_AND_TENS = URDU_TEENS_AND_TENS;
|
|
5076
|
+
exports.URDU_TENS_UNITS = URDU_TENS_UNITS;
|
|
4727
5077
|
exports.URDU_WEEKDAYS = URDU_WEEKDAYS;
|
|
4728
5078
|
exports.analyzeUrdu = analyzeUrdu;
|
|
4729
5079
|
exports.compareUrdu = compareUrdu;
|
|
@@ -4734,7 +5084,9 @@ exports.editDistance = editDistance;
|
|
|
4734
5084
|
exports.extractNameParts = extractNameParts;
|
|
4735
5085
|
exports.filterStopWords = filterStopWords;
|
|
4736
5086
|
exports.foldUrdu = foldUrdu;
|
|
5087
|
+
exports.formatUrduCurrency = formatUrduCurrency;
|
|
4737
5088
|
exports.formatUrduDate = formatUrduDate;
|
|
5089
|
+
exports.formatUrduNumber = formatUrduNumber;
|
|
4738
5090
|
exports.getAffixes = getAffixes;
|
|
4739
5091
|
exports.getUrduMonthName = getUrduMonthName;
|
|
4740
5092
|
exports.getUrduWeekdayName = getUrduWeekdayName;
|
|
@@ -4744,6 +5096,7 @@ exports.isStopWord = isStopWord;
|
|
|
4744
5096
|
exports.isUrdu = isUrdu;
|
|
4745
5097
|
exports.normalizeUrdu = normalizeUrdu;
|
|
4746
5098
|
exports.numberToUrduWords = numberToUrduWords;
|
|
5099
|
+
exports.parseUrduDate = parseUrduDate;
|
|
4747
5100
|
exports.parseUrduNumber = parseUrduNumber;
|
|
4748
5101
|
exports.removeDiacritics = removeDiacritics;
|
|
4749
5102
|
exports.removeStopWords = removeStopWords;
|
|
@@ -4760,6 +5113,7 @@ exports.timeAgoUrdu = timeAgoUrdu;
|
|
|
4760
5113
|
exports.toArabicIndicDigits = toArabicIndicDigits;
|
|
4761
5114
|
exports.toEnglishDigits = toEnglishDigits;
|
|
4762
5115
|
exports.toUrduDigits = toUrduDigits;
|
|
5116
|
+
exports.toUrduOrdinal = toUrduOrdinal;
|
|
4763
5117
|
exports.transliterateNameToEnglish = transliterateNameToEnglish;
|
|
4764
5118
|
exports.transliterateNameToUrdu = transliterateNameToUrdu;
|
|
4765
5119
|
exports.urduRatio = urduRatio;
|