├── readme.md ├── examples.js └── arabizeCount.js /readme.md: -------------------------------------------------------------------------------- 1 | # Arabize Count 2 | 3 | ## This is a function that returns correctly parsed Arabic numeral distinctive (تمييز العدد). It accepts the number as a required argument, and an object of optional arguments that help identify the case of the distinctive: 4 | 5 | - `sng`: the distinctive in the singular form. If not provided, it defaults to "مرة" which means "once". 6 | - `pair`: the distinctive in the pair form. If not provided, it defaults to "مرتين" which means "twice". 7 | - `plr`: the distinctive in the plural form. If not provided, it defaults to "مرات" which means "times". 8 | - `zero`: the form of the whole output if the number is 0. If not provided, it defaults to "صفر" which means "zero". 9 | - `isSngVaried`: whether the singular form is varied or prohibited from variation (مصروف أم ممنوع من الصرف). It affects the parsing sign. It's `true` by default. 10 | - `isPlrVaried`: whether the plural form is varied or prohibited from variation (مصروف أم ممنوع من الصرف). It affects the parsing sign. It's `true` by default. 11 | - `parsing`: `nominative` (مرفوع), `accusative` (منصوب), or `genitive` (مجرور). It's `null` by default which means the word is invariable (مبني غير مُعرب). Currently, it affects only the pair result. You can ignore it and pass the correct pair form if you know the correct parsing in your context. 12 | - `keepSign`: whether you want the parsing sign in the output or not. It's `true` by default. 13 | - `decimal`: number of decimal places in the numeral. 14 | - `before`: an optional previous phrase. 15 | - `after`: an optional next phrase. 16 | - `dir`: direction of reading the Arabic numeral. It affects the parsing of the distinctve in some cases. The default is `rtl` and it can be `ltr`. Currently, its effect is almost unnoticable as the numeral is written in numbers not letters. 17 | -------------------------------------------------------------------------------- /examples.js: -------------------------------------------------------------------------------- 1 | const {arabizeCount} = require('./arabizeCount'); 2 | 3 | /** 4 | * sng = singular (مفرد) 5 | * pair = مثنى 6 | * plr = plural (جمع) 7 | * before = عبارة سابقة 8 | * after = عبارة لاحقة 9 | */ 10 | 11 | [5, 50, 100].forEach(num => 12 | console.log( 13 | arabizeCount(num, { 14 | sng: 'مسلم', 15 | pair: 'مسلمان', 16 | plr: 'مسلمين', 17 | before: 'في الحي', 18 | }) 19 | ) 20 | ); 21 | 22 | // في الحي ٥ مسلمين 23 | // في الحي ٥٠ مسلمًا 24 | // في الحي ١٠٠ مسلمٍ 25 | 26 | [2, 5, 50, 100].forEach(num => 27 | console.log( 28 | arabizeCount(num, { 29 | sng: 'أفعى', 30 | pair: 'أفعيين', 31 | plr: 'أفاعي', 32 | before: 'رأيت', 33 | }) 34 | ) 35 | ); 36 | 37 | // رأيت أفعيين 38 | // رأيت ٥ أفاعٍ 39 | // رأيت ٥٠ أفعى 40 | // رأيت ١٠٠ أفعى 41 | 42 | [5, 1, 17, 100].forEach(num => 43 | console.log( 44 | arabizeCount(num, { 45 | sng: 'قاضي', 46 | pair: 'قاضيان', 47 | plr: 'قضاة', 48 | before: 'في هذا البلد', 49 | }) 50 | ) 51 | ); 52 | 53 | // في هذا البلد ٥ قضاةٍ 54 | // في هذا البلد قاضٍ 55 | // في هذا البلد ١٧ قاضيًا 56 | // في هذا البلد ١٠٠ قاضٍ 57 | 58 | /** 59 | * Varied = مصروف 60 | * Prohibited from variation = ممنوع من الصرف 61 | * isSngVaried = هل المفرد مصروف 62 | * isPlrVaried = هل الجمع مصروف 63 | */ 64 | 65 | [5, 25, 100].forEach(num => 66 | console.log( 67 | arabizeCount(num, { 68 | sng: 'صحراء', 69 | pair: 'صحراوان', 70 | plr: 'صحاري', 71 | isSngVaried: false, // كلمة "صحراء" ممنوعة 72 | // (من الصرف (مختومة بألف التأنيث الممدودة 73 | before: 'عدَّ أهل الإحصاء', 74 | after: 'على الأرض.', 75 | }) 76 | ) 77 | ); 78 | 79 | // عدَّ أهل الإحصاء ٥ صحارٍ على الأرض. 80 | // عدَّ أهل الإحصاء ٢٥ صحراءَ على الأرض. 81 | /* عدَّ أهل الإحصاء ١٠٠ صحراءَ على الأرض. 82 | (تمييز مجرور وعلامة جره الفتحة) */ 83 | 84 | /** 85 | * Varied = مصروف 86 | * Prohibited from variation = ممنوع من الصرف 87 | * isSngVaried = هل المفرد مصروف 88 | * isPlrVaried = هل الجمع مصروف 89 | */ 90 | 91 | [5, 50, 100, 0].forEach(num => 92 | console.log( 93 | arabizeCount(num, { 94 | sng: 'نتيجة', 95 | pair: 'نتيجتين', 96 | plr: 'نتائج', 97 | zero: 'لم نجد شيئًا', 98 | isPlrVaried: false, // كلمة "نتائج" ممنوعة 99 | // (من الصرف (صيغة منتهى الجموع 100 | before: 'وجدنا', 101 | }) 102 | ) 103 | ); 104 | 105 | // تمييز مجرور وعلامة جره الفتحة // وجدنا ٥ نتائجَ 106 | // وجدنا ٥٠ نتيجةً 107 | // وجدنا ١٠٠ نتيجةٍ 108 | // لم نجد شيئًا 109 | 110 | [9, 70, 200, 2].forEach(num => 111 | console.log( 112 | arabizeCount(num, { 113 | // يجب وضع الشدة على الياء 114 | // لكيلا تُحذف على أن الاسم منقوص 115 | sng: 'جنديّ', 116 | pair: 'جنديّان', 117 | plr: 'جنود', 118 | before: 'احتشد', 119 | }) 120 | ) 121 | ); 122 | 123 | // احتشد ٩ جنودٍ 124 | // احتشد ٧٠ جنديًّا 125 | // احتشد ٢٠٠ جنديٍّ 126 | // احتشد جنديّان 127 | -------------------------------------------------------------------------------- /arabizeCount.js: -------------------------------------------------------------------------------- 1 | /** 2 | * STATIC VARIABLES AND HELPER FUNCTIONS 3 | */ 4 | 5 | // الحركات والتشكيل 6 | const diacs = { 7 | f: '\u064E', // فتح 8 | d: '\u064F', // ضم 9 | k: '\u0650', // كسر 10 | s: '\u0652', // سكون 11 | fn: '\u064B', // تنوين فتح 12 | dn: '\u064C', // تنوين ضم 13 | kn: '\u064D', // تنوين كسر 14 | sh: '\u0651', // شدة 15 | }; 16 | 17 | const removeParsingSign = w => w.replace(/[\u064B-\u0650]$/, ''); // حذف علامة الإعراب 18 | const trimEndingYeh = w => w.replace(/\u064A$/g, diacs.kn); // حذف ياء الاسم المنقوص 19 | const nominativeToAccusativePlr = w => 20 | w.replace(/\u0648\u0646$/, '\u064A\u0646'); // نصب جمع المذكر السالم (أو جره) 21 | const sign = (w, sign) => 22 | w.replace(/([ء-ي\u0651])[\u064B-\u0650]?$/, `$1${sign}`); // وضع علامة الإعراب 23 | 24 | /** 25 | * MAIN FUNCTION 26 | */ 27 | const arabizeCount = ( 28 | num, 29 | { 30 | sng = 'مرة', // صيغة المفرد 31 | pair = 'مرتين', // صيغة المثنى 32 | plr = 'مرات', // صيغة الجمع 33 | zero = 'صفر', // صيغة الناتج إن كان العدد صفرًا 34 | isSngVaried = true, // هل المفرد مصروف 35 | isPlrVaried = true, // هل الجمع مصروف 36 | parsing = null, // حالة الإعراب 37 | keepSign = true, // وضع علامة الإعراب 38 | decimal = 0, // عدد الكسور المطلوبة 39 | before = '', // عبارة سابقة 40 | after = '', // عبارة لاحقة 41 | dir = 'rtl', // اتجاه قراءة العدد (أثره غير ملحوظ حتى الآن إلا قليلًا) 42 | // sngType = 'm', // for written numerals (TODO) // المعدود مذكر أم مؤنث 43 | } = {} 44 | ) => { 45 | // Numeral formatting | ضبط العدد 46 | 47 | let int = Math.floor(num); 48 | 49 | if (dir === 'ltr') { 50 | const match = `${int}`.match(/\d{1,2}$/); 51 | const idx = match.index; 52 | int = +match[0]; 53 | int = int >= 0 && int <= 2 && idx ? 100 + int : int; 54 | } 55 | 56 | // استعمال الأرقام المشرقية وضبط الكسور 57 | const formattedNum = num.toLocaleString( 58 | 'ar-EG', 59 | decimal && { 60 | minimumFractionDigits: decimal, 61 | maximumFractionDigits: decimal, 62 | } 63 | ); 64 | 65 | // Distinctive formatting | ضبط التمييز 66 | 67 | // إزالة الإعراب إن وُجد 68 | sng = removeParsingSign(sng); 69 | plr = removeParsingSign(plr); 70 | 71 | const sngDistinctive = trimEndingYeh(sng); // تمييز مفرد 72 | const plrDistinctive = nominativeToAccusativePlr(trimEndingYeh(plr)); // تمييز جمع 73 | 74 | // علامة النصب 75 | const accusativeSign = /[\u0627\u0649]$/.test(sng) 76 | ? '' 77 | : isSngVaried 78 | ? diacs.fn // تنوين نصب 79 | : diacs.f; // فتحة للممنوع من الصرف 80 | 81 | // ألف تنوين النصب 82 | const nunationAlif = 83 | /[\u0627\u0629\u0649]$/.test(sng) || !isSngVaried ? '' : '\u0627'; 84 | 85 | // علامة جر المفرد 86 | const sngGenitiveSign = /[\u0627\u0649]$/.test(sng) 87 | ? '' 88 | : isSngVaried 89 | ? diacs.kn // تنوين جر 90 | : diacs.f; // فتحة للممنوع من الصرف 91 | 92 | // علامة جر الجمع 93 | const plrGenitiveSign = /([\u0627\u0649]|\u064A\u0646)$/.test(plrDistinctive) 94 | ? '' 95 | : isPlrVaried 96 | ? diacs.kn // تنوين جر 97 | : diacs.f; // فتحة للممنوع من الصرف 98 | 99 | const plrGenitiveDistinctive = sign( 100 | plrDistinctive, 101 | keepSign ? plrGenitiveSign : '' 102 | ); // تمييز جمع مجرور 103 | const accusativeDistinctive = 104 | sign(sng, keepSign ? accusativeSign : '') + nunationAlif; // تمييز (مفرد) منصوب 105 | const sngGenitiveDistinctive = sign( 106 | sngDistinctive, 107 | keepSign ? sngGenitiveSign : '' 108 | ); // تمييز مفرد مجرور 109 | 110 | const zeroResult = num === 0 ? zero : `${formattedNum} ${sngDistinctive}`; 111 | 112 | const oneResult = 113 | parsing === 'accusative' 114 | ? accusativeDistinctive 115 | : parsing === 'genitive' 116 | ? sngGenitiveDistinctive 117 | : sngDistinctive; 118 | 119 | const twoResult = 120 | parsing === 'nominative' 121 | ? pair.replace(/[يا]ن$/, 'ان') 122 | : parsing === 'accusative' || parsing === 'genitive' 123 | ? pair.replace(/[يا]ن$/, diacs.f + 'ين') 124 | : pair; 125 | 126 | const arabicCount = 127 | int === 0 128 | ? zeroResult 129 | : int === 1 130 | ? oneResult 131 | : int === 2 132 | ? twoResult 133 | : int >= 3 && int <= 10 134 | ? `${formattedNum} ${plrGenitiveDistinctive}` 135 | : int >= 11 && int <= 99 136 | ? `${formattedNum} ${accusativeDistinctive}` 137 | : `${formattedNum} ${sngGenitiveDistinctive}`; 138 | 139 | return num 140 | ? `${before && before + ' '}${arabicCount}${after && ' ' + after}` 141 | : arabicCount; 142 | }; 143 | 144 | module.exports = { 145 | arabizeCount, 146 | }; 147 | --------------------------------------------------------------------------------