@handijit/tr 0.0.0-stage → 1.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 +46 -0
- package/LICENSE +21 -0
- package/README.md +1434 -2
- package/build/index.cjs +789 -0
- package/build/index.cjs.map +1 -0
- package/build/index.d.mts +136 -0
- package/build/index.d.ts +136 -0
- package/build/index.mjs +758 -0
- package/build/index.mjs.map +1 -0
- package/build/index.umd.js +2 -0
- package/build/index.umd.js.map +1 -0
- package/package.json +98 -4
package/build/index.cjs
ADDED
|
@@ -0,0 +1,789 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/** Names a value for an error message; `typeof null` alone would say "object". */
|
|
4
|
+
const describeValue = (value) => value === null ? "null" : typeof value;
|
|
5
|
+
|
|
6
|
+
/** Throws when a required text argument is not a primitive string. */
|
|
7
|
+
const assertString = (value, source) => {
|
|
8
|
+
if (typeof value !== "string") {
|
|
9
|
+
throw new TypeError(`${source}: expected a string, received ${describeValue(value)}`);
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const DOTTED_SMALL_I = /i/g;
|
|
14
|
+
/** Upper-cases text by the Turkish rule: "i" becomes "İ" and "ı" becomes "I". */
|
|
15
|
+
const toUpperCase = (text) => {
|
|
16
|
+
assertString(text, "toUpperCase");
|
|
17
|
+
// Only the dotted "i" differs from the default mapping ("ı" already maps to "I"), so the
|
|
18
|
+
// result does not depend on the locale data the engine was built with.
|
|
19
|
+
return text.replace(DOTTED_SMALL_I, "İ").toUpperCase();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Upper-cases the first letter by the Turkish rule and leaves the rest as written. */
|
|
23
|
+
const capitalize = (text) => {
|
|
24
|
+
assertString(text, "capitalize");
|
|
25
|
+
const firstCodePoint = text.codePointAt(0);
|
|
26
|
+
if (firstCodePoint === undefined)
|
|
27
|
+
return "";
|
|
28
|
+
// By code point, so a first character outside the BMP is not cut in half.
|
|
29
|
+
const first = String.fromCodePoint(firstCodePoint);
|
|
30
|
+
return toUpperCase(first) + text.slice(first.length);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// "İ" and its decomposed spelling "I" + combining dot above. The default mapping would turn
|
|
34
|
+
// "İ" into "i" followed by a stray combining dot.
|
|
35
|
+
const DOTTED_CAPITAL_I = /İ|İ/g;
|
|
36
|
+
const DOTLESS_CAPITAL_I = /I/g;
|
|
37
|
+
/** Lower-cases text by the Turkish rule: "I" becomes "ı" and "İ" becomes "i". */
|
|
38
|
+
const toLowerCase = (text) => {
|
|
39
|
+
assertString(text, "toLowerCase");
|
|
40
|
+
return text
|
|
41
|
+
.replace(DOTTED_CAPITAL_I, "i")
|
|
42
|
+
.replace(DOTLESS_CAPITAL_I, "ı")
|
|
43
|
+
.toLowerCase();
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// The 29 letters in dictionary order, with q, w and x where Latin loanwords put them.
|
|
47
|
+
const ALPHABET = "abcçdefgğhıijklmnoöpqrsştuüvwxyz";
|
|
48
|
+
const COMBINING_MARK = /\p{M}/u;
|
|
49
|
+
const FIRST_NON_ASCII = 128;
|
|
50
|
+
const DIGIT_ZERO$3 = 48;
|
|
51
|
+
const DIGIT_NINE = 57;
|
|
52
|
+
// Weight bands: ASCII punctuation, then digits, then letters, then every other character.
|
|
53
|
+
const DIGIT_BAND = 1_000;
|
|
54
|
+
const LETTER_BAND = 2_000;
|
|
55
|
+
const OTHER_BAND = 3_000;
|
|
56
|
+
// A combining mark that survived composition weighs nothing at the letter level.
|
|
57
|
+
const IGNORED = -1;
|
|
58
|
+
const weightOf = (character) => {
|
|
59
|
+
const rank = ALPHABET.indexOf(character);
|
|
60
|
+
if (rank >= 0)
|
|
61
|
+
return LETTER_BAND + rank;
|
|
62
|
+
const codePoint = character.codePointAt(0);
|
|
63
|
+
if (codePoint < FIRST_NON_ASCII) {
|
|
64
|
+
return codePoint >= DIGIT_ZERO$3 && codePoint <= DIGIT_NINE
|
|
65
|
+
? DIGIT_BAND + codePoint
|
|
66
|
+
: codePoint;
|
|
67
|
+
}
|
|
68
|
+
if (COMBINING_MARK.test(character))
|
|
69
|
+
return IGNORED;
|
|
70
|
+
// An accented letter sorts with its base letter ("â" with "a"); the accent only breaks ties.
|
|
71
|
+
const baseRank = ALPHABET.indexOf(character.normalize("NFD").charAt(0));
|
|
72
|
+
return baseRank >= 0 ? LETTER_BAND + baseRank : OTHER_BAND + codePoint;
|
|
73
|
+
};
|
|
74
|
+
const weigh = (text) => {
|
|
75
|
+
const weights = [];
|
|
76
|
+
for (const character of text) {
|
|
77
|
+
const weight = weightOf(character);
|
|
78
|
+
if (weight !== IGNORED)
|
|
79
|
+
weights.push(weight);
|
|
80
|
+
}
|
|
81
|
+
return weights;
|
|
82
|
+
};
|
|
83
|
+
// "0" for a character that is already lower case, "1" otherwise: lower case sorts first.
|
|
84
|
+
const caseKeyOf = (text) => {
|
|
85
|
+
let caseKey = "";
|
|
86
|
+
for (const character of text) {
|
|
87
|
+
caseKey += toLowerCase(character) === character ? "0" : "1";
|
|
88
|
+
}
|
|
89
|
+
return caseKey;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Compares two texts in Turkish alphabetical order ("ç" after "c", "ı" before "i") for
|
|
93
|
+
* `Array.prototype.sort`; case and accents only break ties, lower case first.
|
|
94
|
+
*/
|
|
95
|
+
const compareTurkish = (left, right) => {
|
|
96
|
+
assertString(left, "compareTurkish");
|
|
97
|
+
assertString(right, "compareTurkish");
|
|
98
|
+
// Composed form, so "ö" typed as "o" + diaeresis is the same letter.
|
|
99
|
+
const leftText = left.normalize("NFC");
|
|
100
|
+
const rightText = right.normalize("NFC");
|
|
101
|
+
if (leftText === rightText)
|
|
102
|
+
return 0;
|
|
103
|
+
const leftLower = toLowerCase(leftText);
|
|
104
|
+
const rightLower = toLowerCase(rightText);
|
|
105
|
+
const leftWeights = weigh(leftLower);
|
|
106
|
+
const rightWeights = weigh(rightLower);
|
|
107
|
+
const sharedLength = Math.min(leftWeights.length, rightWeights.length);
|
|
108
|
+
for (let i = 0; i < sharedLength; i += 1) {
|
|
109
|
+
const difference = leftWeights[i] - rightWeights[i];
|
|
110
|
+
if (difference !== 0)
|
|
111
|
+
return difference < 0 ? -1 : 1;
|
|
112
|
+
}
|
|
113
|
+
if (leftWeights.length !== rightWeights.length) {
|
|
114
|
+
return leftWeights.length < rightWeights.length ? -1 : 1;
|
|
115
|
+
}
|
|
116
|
+
// Same letters. An accent decides next ("kar" before "kâr"), then case ("ali" before "Ali").
|
|
117
|
+
if (leftLower !== rightLower)
|
|
118
|
+
return leftLower < rightLower ? -1 : 1;
|
|
119
|
+
const leftCase = caseKeyOf(leftText);
|
|
120
|
+
const rightCase = caseKeyOf(rightText);
|
|
121
|
+
if (leftCase !== rightCase)
|
|
122
|
+
return leftCase < rightCase ? -1 : 1;
|
|
123
|
+
// Two capital forms of one letter ("DŽ" and "Dž"): code units keep the order total.
|
|
124
|
+
return leftText < rightText ? -1 : 1;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Turkish day names, Monday first as on a Turkish calendar: `DAY_NAMES[0]` is "Pazartesi".
|
|
129
|
+
* This is not the order of `Date.prototype.getDay`, where 0 is Sunday.
|
|
130
|
+
*/
|
|
131
|
+
const DAY_NAMES = /* @__PURE__ */ Object.freeze([
|
|
132
|
+
"Pazartesi",
|
|
133
|
+
"Salı",
|
|
134
|
+
"Çarşamba",
|
|
135
|
+
"Perşembe",
|
|
136
|
+
"Cuma",
|
|
137
|
+
"Cumartesi",
|
|
138
|
+
"Pazar",
|
|
139
|
+
]);
|
|
140
|
+
|
|
141
|
+
const DOTLESS_SMALL_I = /ı/g;
|
|
142
|
+
const COMBINING_MARKS = /[̀-ͯ]/g;
|
|
143
|
+
/** Folds text to a search key: Turkish lower case, then "ç ğ ı ö ş ü" and accents to plain letters. */
|
|
144
|
+
const foldText = (text) => {
|
|
145
|
+
assertString(text, "foldText");
|
|
146
|
+
// "ı" has no decomposition, so it is mapped by hand; the other letters lose their marks.
|
|
147
|
+
return toLowerCase(text)
|
|
148
|
+
.replace(DOTLESS_SMALL_I, "i")
|
|
149
|
+
.normalize("NFD")
|
|
150
|
+
.replace(COMBINING_MARKS, "");
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
154
|
+
const isLeapYear = (year) => (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
155
|
+
/** Tells whether year, month and day name a day that exists (years 1 to 9999). */
|
|
156
|
+
const isRealDate = (year, month, day) => {
|
|
157
|
+
if (year < 1 || year > 9999 || month < 1 || month > 12 || day < 1) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const lastDay = month === 2 && isLeapYear(year) ? 29 : DAYS_IN_MONTH[month - 1];
|
|
161
|
+
return day <= lastDay;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** Left-pads a non-negative whole number with zeros: (7, 2) → "07". */
|
|
165
|
+
const padNumber = (value, length) => String(value).padStart(length, "0");
|
|
166
|
+
|
|
167
|
+
const DEFAULT_TIME_ZONE = "Europe/Istanbul";
|
|
168
|
+
const ISO_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
169
|
+
// 0001-01-02T00:00Z and 9999-12-30T23:59:59.999Z. The one-day margin keeps the year inside
|
|
170
|
+
// 1 to 9999 in every time zone, so the text never needs an era or a fifth year digit.
|
|
171
|
+
const MIN_TIMESTAMP = -621355104e5;
|
|
172
|
+
const MAX_TIMESTAMP = 253402214399999;
|
|
173
|
+
// Building an Intl.DateTimeFormat costs far more than using one, so they are reused; the cap
|
|
174
|
+
// keeps a caller that walks every zone from growing the cache without bound.
|
|
175
|
+
const MAX_CACHED_FORMATTERS = 8;
|
|
176
|
+
const MAX_ECHOED_LENGTH = 32;
|
|
177
|
+
const formatters = /* @__PURE__ */ new Map();
|
|
178
|
+
const getFormatter = (timeZone, source) => {
|
|
179
|
+
const cached = formatters.get(timeZone);
|
|
180
|
+
if (cached !== undefined)
|
|
181
|
+
return cached;
|
|
182
|
+
let formatter;
|
|
183
|
+
try {
|
|
184
|
+
// Intl only moves the instant into the zone; the fixed locale, calendar, digits and hour
|
|
185
|
+
// cycle make the parts plain numbers that read the same on every engine.
|
|
186
|
+
formatter = new Intl.DateTimeFormat("en-US", {
|
|
187
|
+
timeZone,
|
|
188
|
+
calendar: "gregory",
|
|
189
|
+
numberingSystem: "latn",
|
|
190
|
+
hourCycle: "h23",
|
|
191
|
+
year: "numeric",
|
|
192
|
+
month: "2-digit",
|
|
193
|
+
day: "2-digit",
|
|
194
|
+
hour: "2-digit",
|
|
195
|
+
minute: "2-digit",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
throw new RangeError(`${source}: unknown time zone "${timeZone.slice(0, MAX_ECHOED_LENGTH)}", expected an IANA name like "Europe/Istanbul"`);
|
|
200
|
+
}
|
|
201
|
+
if (formatters.size >= MAX_CACHED_FORMATTERS) {
|
|
202
|
+
formatters.delete(formatters.keys().next().value);
|
|
203
|
+
}
|
|
204
|
+
formatters.set(timeZone, formatter);
|
|
205
|
+
return formatter;
|
|
206
|
+
};
|
|
207
|
+
const toTimestamp = (date, source) => {
|
|
208
|
+
let timestamp;
|
|
209
|
+
if (typeof date === "number") {
|
|
210
|
+
timestamp = date;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
try {
|
|
214
|
+
// Reads the internal time value, so a Date from another realm (an iframe, an Electron
|
|
215
|
+
// context) passes and a look-alike object does not.
|
|
216
|
+
timestamp = Date.prototype.getTime.call(date);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
throw new TypeError(`${source}: expected a Date or a timestamp in milliseconds, received ${describeValue(date)}`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (Number.isNaN(timestamp)) {
|
|
223
|
+
throw new RangeError(`${source}: received an invalid date`);
|
|
224
|
+
}
|
|
225
|
+
if (timestamp < MIN_TIMESTAMP || timestamp > MAX_TIMESTAMP) {
|
|
226
|
+
throw new RangeError(`${source}: the date is outside the supported years 0001 to 9999`);
|
|
227
|
+
}
|
|
228
|
+
return timestamp;
|
|
229
|
+
};
|
|
230
|
+
/** Reads the wall-clock date and time an instant has in a time zone. */
|
|
231
|
+
const readInstant = (date, timeZone, source) => {
|
|
232
|
+
const timestamp = toTimestamp(date, source);
|
|
233
|
+
if (typeof timeZone !== "string") {
|
|
234
|
+
throw new TypeError(`${source}: expected timeZone to be a string, received ${describeValue(timeZone)}`);
|
|
235
|
+
}
|
|
236
|
+
const fields = {};
|
|
237
|
+
for (const part of getFormatter(timeZone, source).formatToParts(timestamp)) {
|
|
238
|
+
fields[part.type] = part.value;
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
year: Number(fields.year),
|
|
242
|
+
month: Number(fields.month),
|
|
243
|
+
day: Number(fields.day),
|
|
244
|
+
hour: Number(fields.hour),
|
|
245
|
+
minute: Number(fields.minute),
|
|
246
|
+
};
|
|
247
|
+
};
|
|
248
|
+
/**
|
|
249
|
+
* Reads a calendar date: a "yyyy-MM-dd" string is taken as written (a business date has no
|
|
250
|
+
* time zone), anything else is an instant read in `timeZone`.
|
|
251
|
+
*/
|
|
252
|
+
const readCalendarDate = (date, timeZone, source) => {
|
|
253
|
+
if (typeof date !== "string")
|
|
254
|
+
return readInstant(date, timeZone, source);
|
|
255
|
+
const match = ISO_DATE_PATTERN.exec(date);
|
|
256
|
+
if (match !== null) {
|
|
257
|
+
const year = Number(match[1]);
|
|
258
|
+
const month = Number(match[2]);
|
|
259
|
+
const day = Number(match[3]);
|
|
260
|
+
if (isRealDate(year, month, day))
|
|
261
|
+
return { year, month, day };
|
|
262
|
+
}
|
|
263
|
+
throw new RangeError(`${source}: expected a real calendar date like "2026-11-10", received "${date.slice(0, MAX_ECHOED_LENGTH)}"`);
|
|
264
|
+
};
|
|
265
|
+
/** Writes a calendar date as "dd.MM.yyyy". */
|
|
266
|
+
const toDottedDate = ({ year, month, day }) => `${padNumber(day, 2)}.${padNumber(month, 2)}.${padNumber(year, 4)}`;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Writes a date as "dd.MM.yyyy". A `Date` or a timestamp is read in `timeZone`; a
|
|
270
|
+
* "yyyy-MM-dd" string is a calendar date already and is written as it stands.
|
|
271
|
+
*/
|
|
272
|
+
const formatDate = (date, { timeZone = DEFAULT_TIME_ZONE } = {}) => toDottedDate(readCalendarDate(date, timeZone, "formatDate"));
|
|
273
|
+
|
|
274
|
+
/** Writes an instant as "dd.MM.yyyy HH:mm" on the 24-hour clock of `timeZone`. */
|
|
275
|
+
const formatDateTime = (date, { timeZone = DEFAULT_TIME_ZONE } = {}) => {
|
|
276
|
+
const instant = readInstant(date, timeZone, "formatDateTime");
|
|
277
|
+
return `${toDottedDate(instant)} ${padNumber(instant.hour, 2)}:${padNumber(instant.minute, 2)}`;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const WHITESPACE = /\s+/g;
|
|
281
|
+
/** Drops every whitespace character, including the no-break spaces of pasted text. */
|
|
282
|
+
const stripWhitespace = (value) => value.replace(WHITESPACE, "");
|
|
283
|
+
|
|
284
|
+
const TR_IBAN_PATTERN = /^TR\d{24}$/;
|
|
285
|
+
// "TR" as IBAN letter values: T = 29, R = 27.
|
|
286
|
+
const COUNTRY_DIGITS = "2927";
|
|
287
|
+
const DIGIT_ZERO$2 = 48;
|
|
288
|
+
/** Returns the compact upper-case form of a valid Turkish IBAN, or `null`. */
|
|
289
|
+
const parseIban = (value) => {
|
|
290
|
+
const iban = stripWhitespace(value).toUpperCase();
|
|
291
|
+
if (!TR_IBAN_PATTERN.test(iban))
|
|
292
|
+
return null;
|
|
293
|
+
// ISO 13616: move the first four characters to the end and read the result as one number mod 97.
|
|
294
|
+
// The number has 30 digits, so the remainder is carried digit by digit instead of parsing it.
|
|
295
|
+
const rearranged = `${iban.slice(4)}${COUNTRY_DIGITS}${iban.slice(2, 4)}`;
|
|
296
|
+
let remainder = 0;
|
|
297
|
+
for (let i = 0; i < rearranged.length; i += 1) {
|
|
298
|
+
remainder = (remainder * 10 + rearranged.charCodeAt(i) - DIGIT_ZERO$2) % 97;
|
|
299
|
+
}
|
|
300
|
+
return remainder === 1 ? iban : null;
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const FOUR_CHARACTERS = /.{4}(?=.)/g;
|
|
304
|
+
/** Returns a valid Turkish IBAN in printed groups of four ("TR33 0006 1005 …"), or `null`. */
|
|
305
|
+
const formatIban = (value) => {
|
|
306
|
+
assertString(value, "formatIban");
|
|
307
|
+
const iban = parseIban(value);
|
|
308
|
+
return iban === null ? null : iban.replace(FOUR_CHARACTERS, "$& ");
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/** Throws unless the amount is a whole number of kuruş that a double holds exactly. */
|
|
312
|
+
const assertKurus = (value, source) => {
|
|
313
|
+
if (typeof value !== "number") {
|
|
314
|
+
throw new TypeError(`${source}: expected an amount in kuruş as a number, received ${describeValue(value)}`);
|
|
315
|
+
}
|
|
316
|
+
// A fraction or an unsafe integer has already lost kuruş; printing it would invent an amount.
|
|
317
|
+
if (!Number.isSafeInteger(value)) {
|
|
318
|
+
throw new RangeError(`${source}: expected a whole number of kuruş within the safe integer range, received ${value}`);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const GROUP_SIZE = 3;
|
|
323
|
+
const groupThousands = (digits) => {
|
|
324
|
+
let grouped = "";
|
|
325
|
+
for (let end = digits.length; end > 0; end -= GROUP_SIZE) {
|
|
326
|
+
const group = digits.slice(Math.max(0, end - GROUP_SIZE), end);
|
|
327
|
+
grouped = grouped === "" ? group : `${group}.${grouped}`;
|
|
328
|
+
}
|
|
329
|
+
return grouped;
|
|
330
|
+
};
|
|
331
|
+
/** Writes an amount held in whole kuruş as Turkish lira: 349000 → "3.490,00 TL". */
|
|
332
|
+
const formatMoney = (kurus, { symbol = false } = {}) => {
|
|
333
|
+
assertKurus(kurus, "formatMoney");
|
|
334
|
+
const absolute = Math.abs(kurus);
|
|
335
|
+
// Remainder first, then an exact subtraction: dividing a large amount by 100 directly
|
|
336
|
+
// rounds, and a rounded lira part would print a different number.
|
|
337
|
+
const kurusPart = absolute % 100;
|
|
338
|
+
const liraPart = (absolute - kurusPart) / 100;
|
|
339
|
+
const amount = `${groupThousands(String(liraPart))},${padNumber(kurusPart, 2)}`;
|
|
340
|
+
// `kurus < 0` is false for -0, so a zero never carries a sign.
|
|
341
|
+
const sign = kurus < 0 ? "-" : "";
|
|
342
|
+
return symbol === true ? `${sign}₺${amount}` : `${sign}${amount} TL`;
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const ALLOWED_CHARACTERS = /^\+?[\d\s().-]+$/;
|
|
346
|
+
const NON_DIGITS = /\D/g;
|
|
347
|
+
// With "+" the country code is mandatory; without it the trunk "0", "90" and "0090" are optional.
|
|
348
|
+
const INTERNATIONAL_DIGITS = /^90([2-58]\d{9})$/;
|
|
349
|
+
const DOMESTIC_DIGITS = /^(?:0090|90|0)?([2-58]\d{9})$/;
|
|
350
|
+
const typeOf = (national) => {
|
|
351
|
+
const leadingDigit = national.charAt(0);
|
|
352
|
+
if (leadingDigit === "5")
|
|
353
|
+
return "mobile";
|
|
354
|
+
if (leadingDigit === "8")
|
|
355
|
+
return "nonGeographic";
|
|
356
|
+
return "landline";
|
|
357
|
+
};
|
|
358
|
+
/** Reads a Turkish phone number in any common spelling, or returns `null`. */
|
|
359
|
+
const parsePhone = (value) => {
|
|
360
|
+
const trimmed = value.trim();
|
|
361
|
+
if (!ALLOWED_CHARACTERS.test(trimmed))
|
|
362
|
+
return null;
|
|
363
|
+
const digits = trimmed.replace(NON_DIGITS, "");
|
|
364
|
+
const pattern = trimmed.startsWith("+")
|
|
365
|
+
? INTERNATIONAL_DIGITS
|
|
366
|
+
: DOMESTIC_DIGITS;
|
|
367
|
+
const national = pattern.exec(digits)?.[1];
|
|
368
|
+
if (national === undefined)
|
|
369
|
+
return null;
|
|
370
|
+
return { national, type: typeOf(national) };
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
/** Returns a Turkish phone number in its printed form, or `null`. */
|
|
374
|
+
const formatPhone = (value, { style = "national" } = {}) => {
|
|
375
|
+
assertString(value, "formatPhone");
|
|
376
|
+
const phone = parsePhone(value);
|
|
377
|
+
if (phone === null)
|
|
378
|
+
return null;
|
|
379
|
+
const areaCode = phone.national.slice(0, 3);
|
|
380
|
+
const subscriber = `${phone.national.slice(3, 6)} ${phone.national.slice(6, 8)} ${phone.national.slice(8)}`;
|
|
381
|
+
// Any style other than "international" is the documented default, never a third layout.
|
|
382
|
+
return style === "international"
|
|
383
|
+
? `+90 ${areaCode} ${subscriber}`
|
|
384
|
+
: `0 (${areaCode}) ${subscriber}`;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/** Returns the Turkish name of the day of the week a date falls on, such as "Salı". */
|
|
388
|
+
const getDayName = (date, { timeZone = DEFAULT_TIME_ZONE } = {}) => {
|
|
389
|
+
const { year, month, day } = readCalendarDate(date, timeZone, "getDayName");
|
|
390
|
+
// setUTCFullYear, because Date.UTC reads the years 0 to 99 as 1900 to 1999.
|
|
391
|
+
const calendarDay = new Date(0);
|
|
392
|
+
calendarDay.setUTCFullYear(year, month - 1, day);
|
|
393
|
+
// getUTCDay counts from Sunday, DAY_NAMES from Monday.
|
|
394
|
+
return DAY_NAMES[(calendarDay.getUTCDay() + 6) % 7];
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
/** Turkish month names, January first: `MONTH_NAMES[0]` is "Ocak". */
|
|
398
|
+
const MONTH_NAMES = /* @__PURE__ */ Object.freeze([
|
|
399
|
+
"Ocak",
|
|
400
|
+
"Şubat",
|
|
401
|
+
"Mart",
|
|
402
|
+
"Nisan",
|
|
403
|
+
"Mayıs",
|
|
404
|
+
"Haziran",
|
|
405
|
+
"Temmuz",
|
|
406
|
+
"Ağustos",
|
|
407
|
+
"Eylül",
|
|
408
|
+
"Ekim",
|
|
409
|
+
"Kasım",
|
|
410
|
+
"Aralık",
|
|
411
|
+
]);
|
|
412
|
+
|
|
413
|
+
/** Returns the Turkish name of the month a date falls in, such as "Kasım". */
|
|
414
|
+
const getMonthName = (date, { timeZone = DEFAULT_TIME_ZONE } = {}) => {
|
|
415
|
+
const { month } = readCalendarDate(date, timeZone, "getMonthName");
|
|
416
|
+
return MONTH_NAMES[month - 1];
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
/** Tells a mobile number (5XX) from a landline (2XX to 4XX) and a service number (8XX), or returns `null`. */
|
|
420
|
+
const getPhoneType = (value) => {
|
|
421
|
+
assertString(value, "getPhoneType");
|
|
422
|
+
return parsePhone(value)?.type ?? null;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// In plate-code order: the name at index 0 belongs to code 01.
|
|
426
|
+
const PROVINCE_NAMES = [
|
|
427
|
+
"Adana",
|
|
428
|
+
"Adıyaman",
|
|
429
|
+
"Afyonkarahisar",
|
|
430
|
+
"Ağrı",
|
|
431
|
+
"Amasya",
|
|
432
|
+
"Ankara",
|
|
433
|
+
"Antalya",
|
|
434
|
+
"Artvin",
|
|
435
|
+
"Aydın",
|
|
436
|
+
"Balıkesir",
|
|
437
|
+
"Bilecik",
|
|
438
|
+
"Bingöl",
|
|
439
|
+
"Bitlis",
|
|
440
|
+
"Bolu",
|
|
441
|
+
"Burdur",
|
|
442
|
+
"Bursa",
|
|
443
|
+
"Çanakkale",
|
|
444
|
+
"Çankırı",
|
|
445
|
+
"Çorum",
|
|
446
|
+
"Denizli",
|
|
447
|
+
"Diyarbakır",
|
|
448
|
+
"Edirne",
|
|
449
|
+
"Elazığ",
|
|
450
|
+
"Erzincan",
|
|
451
|
+
"Erzurum",
|
|
452
|
+
"Eskişehir",
|
|
453
|
+
"Gaziantep",
|
|
454
|
+
"Giresun",
|
|
455
|
+
"Gümüşhane",
|
|
456
|
+
"Hakkari",
|
|
457
|
+
"Hatay",
|
|
458
|
+
"Isparta",
|
|
459
|
+
"Mersin",
|
|
460
|
+
"İstanbul",
|
|
461
|
+
"İzmir",
|
|
462
|
+
"Kars",
|
|
463
|
+
"Kastamonu",
|
|
464
|
+
"Kayseri",
|
|
465
|
+
"Kırklareli",
|
|
466
|
+
"Kırşehir",
|
|
467
|
+
"Kocaeli",
|
|
468
|
+
"Konya",
|
|
469
|
+
"Kütahya",
|
|
470
|
+
"Malatya",
|
|
471
|
+
"Manisa",
|
|
472
|
+
"Kahramanmaraş",
|
|
473
|
+
"Mardin",
|
|
474
|
+
"Muğla",
|
|
475
|
+
"Muş",
|
|
476
|
+
"Nevşehir",
|
|
477
|
+
"Niğde",
|
|
478
|
+
"Ordu",
|
|
479
|
+
"Rize",
|
|
480
|
+
"Sakarya",
|
|
481
|
+
"Samsun",
|
|
482
|
+
"Siirt",
|
|
483
|
+
"Sinop",
|
|
484
|
+
"Sivas",
|
|
485
|
+
"Tekirdağ",
|
|
486
|
+
"Tokat",
|
|
487
|
+
"Trabzon",
|
|
488
|
+
"Tunceli",
|
|
489
|
+
"Şanlıurfa",
|
|
490
|
+
"Uşak",
|
|
491
|
+
"Van",
|
|
492
|
+
"Yozgat",
|
|
493
|
+
"Zonguldak",
|
|
494
|
+
"Aksaray",
|
|
495
|
+
"Bayburt",
|
|
496
|
+
"Karaman",
|
|
497
|
+
"Kırıkkale",
|
|
498
|
+
"Batman",
|
|
499
|
+
"Şırnak",
|
|
500
|
+
"Bartın",
|
|
501
|
+
"Ardahan",
|
|
502
|
+
"Iğdır",
|
|
503
|
+
"Yalova",
|
|
504
|
+
"Karabük",
|
|
505
|
+
"Kilis",
|
|
506
|
+
"Osmaniye",
|
|
507
|
+
"Düzce",
|
|
508
|
+
];
|
|
509
|
+
// Frozen all the way down: every consumer shares this one list, so nobody may edit it.
|
|
510
|
+
const buildProvinces = () => Object.freeze(PROVINCE_NAMES.map((name, index) => Object.freeze({ code: index + 1, name })));
|
|
511
|
+
/** The 81 provinces of Turkey in plate-code order: `PROVINCES[0]` is Adana, code 1. */
|
|
512
|
+
const PROVINCES = /* @__PURE__ */ buildProvinces();
|
|
513
|
+
|
|
514
|
+
const CODE_PATTERN = /^\d{1,2}$/;
|
|
515
|
+
/** Finds a province by its plate code, given as a number (6) or as text ("06"), or returns `null`. */
|
|
516
|
+
const getProvinceByCode = (code) => {
|
|
517
|
+
if (typeof code === "string") {
|
|
518
|
+
const text = code.trim();
|
|
519
|
+
return CODE_PATTERN.test(text)
|
|
520
|
+
? (PROVINCES[Number(text) - 1] ?? null)
|
|
521
|
+
: null;
|
|
522
|
+
}
|
|
523
|
+
if (typeof code !== "number") {
|
|
524
|
+
throw new TypeError(`getProvinceByCode: expected a plate code as a number or a string, received ${describeValue(code)}`);
|
|
525
|
+
}
|
|
526
|
+
// A fraction, NaN or a code outside 1 to 81 names no index of the list.
|
|
527
|
+
return PROVINCES[code - 1] ?? null;
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// Built on the first lookup and never grown: 81 entries. Building it at import time would
|
|
531
|
+
// run code in every bundle that merely imports the package.
|
|
532
|
+
let provincesByFoldedName;
|
|
533
|
+
/**
|
|
534
|
+
* Finds a province by name without regard to case or Turkish letters ("istanbul", "ISTANBUL"
|
|
535
|
+
* and "İstanbul" all match), or returns `null`.
|
|
536
|
+
*/
|
|
537
|
+
const getProvinceByName = (name) => {
|
|
538
|
+
assertString(name, "getProvinceByName");
|
|
539
|
+
provincesByFoldedName ??= new Map(PROVINCES.map((province) => [foldText(province.name), province]));
|
|
540
|
+
return provincesByFoldedName.get(foldText(name.trim())) ?? null;
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
/** Checks a Turkish IBAN: "TR", 24 digits and the ISO 13616 mod-97 checksum. */
|
|
544
|
+
const isValidIban = (value) => typeof value === "string" && parseIban(value) !== null;
|
|
545
|
+
|
|
546
|
+
// Province code 01 to 81, then one of the six civil layouts: one letter with four or five
|
|
547
|
+
// digits, two letters with three or four, three letters with two or three. Plates use the
|
|
548
|
+
// 23 letters left after Ç, Ğ, İ, Ö, Ş, Ü and Q, W, X are taken out.
|
|
549
|
+
const LICENSE_PLATE_PATTERN = /^(?:0[1-9]|[1-7]\d|8[01])\s*(?:[A-PR-VYZa-pr-vyz]\s*\d{4,5}|[A-PR-VYZa-pr-vyz]{2}\s*\d{3,4}|[A-PR-VYZa-pr-vyz]{3}\s*\d{2,3})$/;
|
|
550
|
+
/**
|
|
551
|
+
* Checks that a value has the shape of a civil Turkish licence plate ("34 ABC 123"). Only
|
|
552
|
+
* the shape is checked: whether the plate was ever issued is not something a format can tell.
|
|
553
|
+
*/
|
|
554
|
+
const isValidLicensePlate = (value) => typeof value === "string" && LICENSE_PLATE_PATTERN.test(value.trim());
|
|
555
|
+
|
|
556
|
+
/** Checks that a value is a Turkish mobile, landline or 8XX service number. */
|
|
557
|
+
const isValidPhone = (value) => typeof value === "string" && parsePhone(value) !== null;
|
|
558
|
+
|
|
559
|
+
const TCKN_PATTERN = /^[1-9]\d{10}$/;
|
|
560
|
+
const DIGIT_ZERO$1 = 48;
|
|
561
|
+
/** Checks the two check digits of a T.C. identity number (11 digits, the first not 0). */
|
|
562
|
+
const isValidTckn = (value) => {
|
|
563
|
+
if (typeof value !== "string")
|
|
564
|
+
return false;
|
|
565
|
+
const tckn = stripWhitespace(value);
|
|
566
|
+
if (!TCKN_PATTERN.test(tckn))
|
|
567
|
+
return false;
|
|
568
|
+
const digitAt = (index) => tckn.charCodeAt(index) - DIGIT_ZERO$1;
|
|
569
|
+
const oddSum = digitAt(0) + digitAt(2) + digitAt(4) + digitAt(6) + digitAt(8);
|
|
570
|
+
const evenSum = digitAt(1) + digitAt(3) + digitAt(5) + digitAt(7);
|
|
571
|
+
// The subtraction can go below zero; a plain % would then hand back a negative digit.
|
|
572
|
+
const tenth = (((oddSum * 7 - evenSum) % 10) + 10) % 10;
|
|
573
|
+
const eleventh = (oddSum + evenSum + digitAt(9)) % 10;
|
|
574
|
+
return digitAt(9) === tenth && digitAt(10) === eleventh;
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const VKN_PATTERN = /^\d{10}$/;
|
|
578
|
+
const DIGIT_ZERO = 48;
|
|
579
|
+
/** Checks the check digit of a tax identification number (VKN, 10 digits). */
|
|
580
|
+
const isValidVkn = (value) => {
|
|
581
|
+
if (typeof value !== "string")
|
|
582
|
+
return false;
|
|
583
|
+
const vkn = stripWhitespace(value);
|
|
584
|
+
if (!VKN_PATTERN.test(vkn))
|
|
585
|
+
return false;
|
|
586
|
+
let sum = 0;
|
|
587
|
+
for (let i = 0; i < 9; i += 1) {
|
|
588
|
+
const shifted = (vkn.charCodeAt(i) - DIGIT_ZERO + 9 - i) % 10;
|
|
589
|
+
const weighted = (shifted * 2 ** (9 - i)) % 9;
|
|
590
|
+
// A shifted 9 is a multiple of nine; the algorithm counts it as 9, not as 0.
|
|
591
|
+
sum += shifted !== 0 && weighted === 0 ? 9 : weighted;
|
|
592
|
+
}
|
|
593
|
+
return (10 - (sum % 10)) % 10 === vkn.charCodeAt(9) - DIGIT_ZERO;
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const ONES = [
|
|
597
|
+
"",
|
|
598
|
+
"bir",
|
|
599
|
+
"iki",
|
|
600
|
+
"üç",
|
|
601
|
+
"dört",
|
|
602
|
+
"beş",
|
|
603
|
+
"altı",
|
|
604
|
+
"yedi",
|
|
605
|
+
"sekiz",
|
|
606
|
+
"dokuz",
|
|
607
|
+
];
|
|
608
|
+
const TENS = [
|
|
609
|
+
"",
|
|
610
|
+
"on",
|
|
611
|
+
"yirmi",
|
|
612
|
+
"otuz",
|
|
613
|
+
"kırk",
|
|
614
|
+
"elli",
|
|
615
|
+
"altmış",
|
|
616
|
+
"yetmiş",
|
|
617
|
+
"seksen",
|
|
618
|
+
"doksan",
|
|
619
|
+
];
|
|
620
|
+
// The largest safe amount is about 90 trilyon lira, so the list never runs out.
|
|
621
|
+
const SCALES = ["", "bin", "milyon", "milyar", "trilyon"];
|
|
622
|
+
const groupToWords = (group) => {
|
|
623
|
+
const hundreds = Math.floor(group / 100);
|
|
624
|
+
const tens = Math.floor((group % 100) / 10);
|
|
625
|
+
const ones = group % 10;
|
|
626
|
+
const words = [];
|
|
627
|
+
// Turkish says "yüz", never "bir yüz".
|
|
628
|
+
if (hundreds > 1)
|
|
629
|
+
words.push(ONES[hundreds]);
|
|
630
|
+
if (hundreds > 0)
|
|
631
|
+
words.push("yüz");
|
|
632
|
+
if (tens > 0)
|
|
633
|
+
words.push(TENS[tens]);
|
|
634
|
+
if (ones > 0)
|
|
635
|
+
words.push(ONES[ones]);
|
|
636
|
+
return words;
|
|
637
|
+
};
|
|
638
|
+
const numberToWords = (value) => {
|
|
639
|
+
if (value === 0)
|
|
640
|
+
return "sıfır";
|
|
641
|
+
const words = [];
|
|
642
|
+
let remaining = value;
|
|
643
|
+
for (let scale = 0; remaining > 0; scale += 1) {
|
|
644
|
+
const group = remaining % 1000;
|
|
645
|
+
remaining = (remaining - group) / 1000;
|
|
646
|
+
if (group === 0)
|
|
647
|
+
continue;
|
|
648
|
+
const groupWords = groupToWords(group);
|
|
649
|
+
// "bin", never "bir bin"; every larger scale keeps its "bir" ("bir milyon").
|
|
650
|
+
if (scale === 1 && group === 1)
|
|
651
|
+
groupWords.length = 0;
|
|
652
|
+
if (scale > 0)
|
|
653
|
+
groupWords.push(SCALES[scale]);
|
|
654
|
+
words.unshift(...groupWords);
|
|
655
|
+
}
|
|
656
|
+
return words.join(" ");
|
|
657
|
+
};
|
|
658
|
+
/**
|
|
659
|
+
* Writes an amount held in whole kuruş in Turkish words, the way an invoice spells it:
|
|
660
|
+
* 349050 → "üç bin dört yüz doksan Türk lirası elli kuruş".
|
|
661
|
+
*/
|
|
662
|
+
const moneyToWords = (kurus) => {
|
|
663
|
+
assertKurus(kurus, "moneyToWords");
|
|
664
|
+
const absolute = Math.abs(kurus);
|
|
665
|
+
const kurusPart = absolute % 100;
|
|
666
|
+
const liraPart = (absolute - kurusPart) / 100;
|
|
667
|
+
const parts = [];
|
|
668
|
+
if (kurus < 0)
|
|
669
|
+
parts.push("eksi");
|
|
670
|
+
// An amount below one lira is read by its kuruş alone; zero still needs a unit to stand on.
|
|
671
|
+
if (liraPart > 0 || kurusPart === 0) {
|
|
672
|
+
parts.push(`${numberToWords(liraPart)} Türk lirası`);
|
|
673
|
+
}
|
|
674
|
+
if (kurusPart > 0)
|
|
675
|
+
parts.push(`${numberToWords(kurusPart)} kuruş`);
|
|
676
|
+
return parts.join(" ");
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
/** Returns the storage form of a valid Turkish IBAN (upper case, no spaces), or `null`. */
|
|
680
|
+
const normalizeIban = (value) => {
|
|
681
|
+
assertString(value, "normalizeIban");
|
|
682
|
+
return parseIban(value);
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
/** Returns a Turkish phone number in E.164 form ("+905321234567"), or `null`. */
|
|
686
|
+
const normalizePhone = (value) => {
|
|
687
|
+
assertString(value, "normalizePhone");
|
|
688
|
+
const phone = parsePhone(value);
|
|
689
|
+
return phone === null ? null : `+90${phone.national}`;
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
const DOTTED_DATE_PATTERN = /^(\d{1,2})\.(\d{1,2})\.(\d{4})$/;
|
|
693
|
+
/**
|
|
694
|
+
* Reads a "dd.MM.yyyy" date into the ISO calendar date "yyyy-MM-dd", or returns `null` when
|
|
695
|
+
* the text is not a date or names a day that does not exist (31.02.2026).
|
|
696
|
+
*/
|
|
697
|
+
const parseDate = (text) => {
|
|
698
|
+
assertString(text, "parseDate");
|
|
699
|
+
const match = DOTTED_DATE_PATTERN.exec(text.trim());
|
|
700
|
+
if (match === null)
|
|
701
|
+
return null;
|
|
702
|
+
const day = Number(match[1]);
|
|
703
|
+
const month = Number(match[2]);
|
|
704
|
+
const year = Number(match[3]);
|
|
705
|
+
if (!isRealDate(year, month, day))
|
|
706
|
+
return null;
|
|
707
|
+
return `${padNumber(year, 4)}-${padNumber(month, 2)}-${padNumber(day, 2)}`;
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
// sign, currency, sign, lira (grouped by dots or plain), kuruş after a comma, currency.
|
|
711
|
+
// The lira part takes dots only as complete thousands groups: "1.250" is 1250 lira, while
|
|
712
|
+
// "1250.50" is not read at all, because guessing what that dot means would invent money.
|
|
713
|
+
const MONEY_PATTERN = /^([+\-−]?)(₺|[Tt][Ll]|[Tt][Rr][Yy])?([+\-−]?)([1-9]\d{0,2}(?:\.\d{3})+|\d+)(?:,(\d{1,2}))?(₺|[Tt][Ll]|[Tt][Rr][Yy])?$/;
|
|
714
|
+
const DOTS = /\./g;
|
|
715
|
+
/** Reads a Turkish-formatted amount into whole kuruş: "3.490,50" → 349050, or `null`. */
|
|
716
|
+
const parseMoney = (text) => {
|
|
717
|
+
assertString(text, "parseMoney");
|
|
718
|
+
const match = MONEY_PATTERN.exec(stripWhitespace(text));
|
|
719
|
+
if (match === null)
|
|
720
|
+
return null;
|
|
721
|
+
// The two sign groups always take part in a match, as an empty string when no sign is written.
|
|
722
|
+
const outerSign = match[1];
|
|
723
|
+
const innerSign = match[3];
|
|
724
|
+
const lira = match[4];
|
|
725
|
+
const kurus = match[5] ?? "";
|
|
726
|
+
if (outerSign !== "" && innerSign !== "")
|
|
727
|
+
return null;
|
|
728
|
+
if (match[2] !== undefined && match[6] !== undefined)
|
|
729
|
+
return null;
|
|
730
|
+
// Digits are joined as text and converted once, so no float arithmetic touches the amount.
|
|
731
|
+
const amount = Number(lira.replace(DOTS, "") + kurus.padEnd(2, "0"));
|
|
732
|
+
if (!Number.isSafeInteger(amount))
|
|
733
|
+
return null;
|
|
734
|
+
const sign = outerSign + innerSign;
|
|
735
|
+
// A negative zero would print as "-0" in some places and equal 0 in others.
|
|
736
|
+
return sign !== "" && sign !== "+" && amount !== 0 ? -amount : amount;
|
|
737
|
+
};
|
|
738
|
+
|
|
739
|
+
const NON_SLUG_CHARACTERS = /[^a-z0-9]+/g;
|
|
740
|
+
// After the pass above a slug holds single hyphens only, so at most one sits at each end.
|
|
741
|
+
const EDGE_HYPHENS = /^-|-$/g;
|
|
742
|
+
/** Builds a URL and file name safe slug: "Şırnak İl Özel İdaresi" → "sirnak-il-ozel-idaresi". */
|
|
743
|
+
const slugify = (text) => {
|
|
744
|
+
assertString(text, "slugify");
|
|
745
|
+
return foldText(text)
|
|
746
|
+
.replace(NON_SLUG_CHARACTERS, "-")
|
|
747
|
+
.replace(EDGE_HYPHENS, "");
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
// A letter that starts a word: nothing before it, or something that is not a letter, a digit,
|
|
751
|
+
// a combining mark (a decomposed "ü" must not split its word) or an apostrophe ("Ankara'da").
|
|
752
|
+
const WORD_START = /(?<![\p{L}\p{N}\p{M}'’])\p{L}/gu;
|
|
753
|
+
/** Writes every word with a Turkish upper-case first letter and lower-case rest. */
|
|
754
|
+
const toTitleCase = (text) => {
|
|
755
|
+
assertString(text, "toTitleCase");
|
|
756
|
+
return toLowerCase(text).replace(WORD_START, toUpperCase);
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
exports.DAY_NAMES = DAY_NAMES;
|
|
760
|
+
exports.MONTH_NAMES = MONTH_NAMES;
|
|
761
|
+
exports.PROVINCES = PROVINCES;
|
|
762
|
+
exports.capitalize = capitalize;
|
|
763
|
+
exports.compareTurkish = compareTurkish;
|
|
764
|
+
exports.foldText = foldText;
|
|
765
|
+
exports.formatDate = formatDate;
|
|
766
|
+
exports.formatDateTime = formatDateTime;
|
|
767
|
+
exports.formatIban = formatIban;
|
|
768
|
+
exports.formatMoney = formatMoney;
|
|
769
|
+
exports.formatPhone = formatPhone;
|
|
770
|
+
exports.getDayName = getDayName;
|
|
771
|
+
exports.getMonthName = getMonthName;
|
|
772
|
+
exports.getPhoneType = getPhoneType;
|
|
773
|
+
exports.getProvinceByCode = getProvinceByCode;
|
|
774
|
+
exports.getProvinceByName = getProvinceByName;
|
|
775
|
+
exports.isValidIban = isValidIban;
|
|
776
|
+
exports.isValidLicensePlate = isValidLicensePlate;
|
|
777
|
+
exports.isValidPhone = isValidPhone;
|
|
778
|
+
exports.isValidTckn = isValidTckn;
|
|
779
|
+
exports.isValidVkn = isValidVkn;
|
|
780
|
+
exports.moneyToWords = moneyToWords;
|
|
781
|
+
exports.normalizeIban = normalizeIban;
|
|
782
|
+
exports.normalizePhone = normalizePhone;
|
|
783
|
+
exports.parseDate = parseDate;
|
|
784
|
+
exports.parseMoney = parseMoney;
|
|
785
|
+
exports.slugify = slugify;
|
|
786
|
+
exports.toLowerCase = toLowerCase;
|
|
787
|
+
exports.toTitleCase = toTitleCase;
|
|
788
|
+
exports.toUpperCase = toUpperCase;
|
|
789
|
+
//# sourceMappingURL=index.cjs.map
|