11import 'package:romanize/romanize.dart' ;
22
3+ /// Specifies the romanization system to use for Arabic.
4+ enum ArabicSystem {
5+ /// The ALA-LC (American Library Association – Library of Congress) romanization.
6+ ///
7+ /// This is a common academic standard that uses digraphs (e.g., 'sh', 'th', 'kh')
8+ /// and is generally easier for English speakers to read.
9+ ///
10+ /// Example: 'شمس' -> 'shams'
11+ alaLc,
12+
13+ /// The DIN 31635 standard (Deutsches Institut für Normung).
14+ ///
15+ /// This system provides a strict 1-to-1 mapping using diacritics (e.g., 'š', 'ṯ', 'ḫ').
16+ /// It is widely used in Arabic studies and Germany.
17+ ///
18+ /// Example: 'شمس' -> 'šams'
19+ din31635,
20+
21+ /// The Buckwalter Transliteration.
22+ ///
23+ /// A strict 1-to-1 mapping using only ASCII characters. It is heavily used
24+ /// in Natural Language Processing (NLP) and data storage.
25+ ///
26+ /// Example: 'شمس' -> '$ms'
27+ buckwalter,
28+ }
29+
330/// A romanizer for Arabic script.
431///
532/// Supports transliteration of Arabic characters into Latin script using
6- /// a standard transliteration table based on ISO 233 and DIN 31635.
33+ /// various standards (ALA-LC, DIN 31635, Buckwalter) .
734class ArabicRomanizer extends Romanizer {
8- const ArabicRomanizer () : super (language: 'arabic' );
9-
10- /// Transliteration map for Arabic to Latin characters.
35+ /// Creates an Arabic romanizer with the specified [system] .
1136 ///
12- /// Based on ISO 233 and DIN 31635 standards, covering most common Arabic
13- /// characters and their romanized equivalents.
14- static const Map <String , String > _transliterationMap = {
15- // Basic Arabic alphabet
16- 'ا' : 'a' , // Alef
17- 'أ' : 'a' , // Alef with Hamza above
18- 'إ' : 'i' , // Alef with Hamza below
19- 'آ' : 'ā' , // Alef Madda
20- 'ٱ' : 'a' , // Alef Wasla
37+ /// Defaults to [ArabicSystem.alaLc] .
38+ const ArabicRomanizer ({this .system = ArabicSystem .alaLc})
39+ : super (language: 'arabic' );
40+
41+ final ArabicSystem system;
42+
43+ // --- Mappings ---
44+
45+ // ALA-LC (Base/Current)
46+ static const Map <String , String > _alaLcMap = {
47+ // Note: 'ا' (Alef) is handled dynamically in the romanize method
48+ // to distinguish between initial 'a' and medial/final 'ā'.
49+ // We map it to 'ā' here as the default for lookup if logic falls through.
50+ 'ا' : 'ā' ,
51+ 'أ' : 'a' , 'إ' : 'i' , 'آ' : 'ā' , 'ٱ' : 'a' ,
52+ 'ب' : 'b' , 'ت' : 't' , 'ث' : 'th' , 'ج' : 'j' , 'ح' : 'ḥ' , 'خ' : 'kh' ,
53+ 'د' : 'd' , 'ذ' : 'dh' , 'ر' : 'r' , 'ز' : 'z' , 'س' : 's' , 'ش' : 'sh' ,
54+ 'ص' : 'ṣ' , 'ض' : 'ḍ' , 'ط' : 'ṭ' , 'ظ' : 'ẓ' , 'ع' : 'ʿ' , 'غ' : 'gh' ,
55+ 'ف' : 'f' , 'ق' : 'q' , 'ك' : 'k' , 'ل' : 'l' , 'م' : 'm' , 'ن' : 'n' ,
56+ 'ه' : 'h' , 'ة' : 'h' , 'و' : 'w' , 'ؤ' : 'u' , 'ي' : 'y' , 'ى' : 'ā' ,
57+ 'ئ' : 'i' , 'ء' : 'ʾ' ,
58+ 'َ' : 'a' , 'ُ' : 'u' , 'ِ' : 'i' ,
59+ 'ً' : 'an' , 'ٌ' : 'un' , 'ٍ' : 'in' ,
60+ 'ّ' : '' , 'ْ' : '' , 'ٰ' : 'ā' ,
61+ '٠' : '0' , '١' : '1' , '٢' : '2' , '٣' : '3' , '٤' : '4' ,
62+ '٥' : '5' , '٦' : '6' , '٧' : '7' , '٨' : '8' , '٩' : '9' ,
63+ };
64+
65+ // DIN 31635 (Uses diacritics for digraphs)
66+ static const Map <String , String > _din31635Map = {
67+ 'ا' : 'ā' ,
68+ 'أ' : 'ʾ' ,
69+ 'إ' : 'ʾ' ,
70+ 'آ' : 'ʾā' ,
71+ 'ٱ' : 'hw' ,
2172 'ب' : 'b' ,
2273 'ت' : 't' ,
23- 'ث' : 'th ' ,
24- 'ج' : 'j ' ,
74+ 'ث' : 'ṯ ' ,
75+ 'ج' : 'ǧ ' ,
2576 'ح' : 'ḥ' ,
26- 'خ' : 'kh ' ,
77+ 'خ' : 'ḫ ' ,
2778 'د' : 'd' ,
28- 'ذ' : 'dh ' ,
79+ 'ذ' : 'ḏ ' ,
2980 'ر' : 'r' ,
3081 'ز' : 'z' ,
3182 'س' : 's' ,
32- 'ش' : 'sh ' ,
83+ 'ش' : 'š ' ,
3384 'ص' : 'ṣ' ,
3485 'ض' : 'ḍ' ,
3586 'ط' : 'ṭ' ,
3687 'ظ' : 'ẓ' ,
3788 'ع' : 'ʿ' ,
38- 'غ' : 'gh ' ,
89+ 'غ' : 'ġ ' ,
3990 'ف' : 'f' ,
4091 'ق' : 'q' ,
4192 'ك' : 'k' ,
4293 'ل' : 'l' ,
4394 'م' : 'm' ,
4495 'ن' : 'n' ,
4596 'ه' : 'h' ,
46- 'ة' : 'h' , // Teh Marbuta
97+ 'ة' : 'h' ,
4798 'و' : 'w' ,
48- 'ؤ' : 'u' , // Waw with Hamza
99+ 'ؤ' : 'u' ,
49100 'ي' : 'y' ,
50- 'ى' : 'ā' , // Alef Maksura
51- 'ئ' : 'i' , // Yeh with Hamza
52- 'ء' : 'ʾ' , // Hamza
53- // Arabic diacritics (usually omitted in romanization but included for completeness)
54- 'َ' : 'a' , // Fatha
55- 'ُ' : 'u' , // Damma
56- 'ِ' : 'i' , // Kasra
57- 'ً' : 'an' , // Tanwin Fath
58- 'ٌ' : 'un' , // Tanwin Damm
59- 'ٍ' : 'in' , // Tanwin Kasr
60- 'ّ' : '' , // Shadda (gemination, usually handled contextually)
61- 'ْ' : '' , // Sukun (no vowel, usually omitted)
62- 'ٰ' : 'ā' , // Superscript Alef
63- // Arabic numbers
101+ 'ى' : 'ā' ,
102+ 'ئ' : 'ʾ' ,
103+ 'ء' : 'ʾ' ,
104+ 'َ' : 'a' ,
105+ 'ُ' : 'u' ,
106+ 'ِ' : 'i' ,
107+ 'ً' : 'an' ,
108+ 'ٌ' : 'un' ,
109+ 'ٍ' : 'in' ,
110+ 'ّ' : '' ,
111+ 'ْ' : '' ,
112+ 'ٰ' : 'ā' ,
113+ '٠' : '0' ,
114+ '١' : '1' ,
115+ '٢' : '2' ,
116+ '٣' : '3' ,
117+ '٤' : '4' ,
118+ '٥' : '5' ,
119+ '٦' : '6' ,
120+ '٧' : '7' ,
121+ '٨' : '8' ,
122+ '٩' : '9' ,
123+ };
124+
125+ // Buckwalter (ASCII only)
126+ static const Map <String , String > _buckwalterMap = {
127+ 'ا' : 'A' ,
128+ 'أ' : '>' ,
129+ 'إ' : '<' ,
130+ 'آ' : '|' ,
131+ 'ٱ' : '{' ,
132+ 'ب' : 'b' ,
133+ 'ت' : 't' ,
134+ 'ث' : 'v' ,
135+ 'ج' : 'j' ,
136+ 'ح' : 'H' ,
137+ 'خ' : 'x' ,
138+ 'د' : 'd' ,
139+ 'ذ' : '*' ,
140+ 'ر' : 'r' ,
141+ 'ز' : 'z' ,
142+ 'س' : 's' ,
143+ 'ش' : '\$ ' ,
144+ 'ص' : 'S' ,
145+ 'ض' : 'D' ,
146+ 'ط' : 'T' ,
147+ 'ظ' : 'Z' ,
148+ 'ع' : 'E' ,
149+ 'غ' : 'g' ,
150+ 'ف' : 'f' ,
151+ 'ق' : 'q' ,
152+ 'ك' : 'k' ,
153+ 'ل' : 'l' ,
154+ 'م' : 'm' ,
155+ 'ن' : 'n' ,
156+ 'ه' : 'h' ,
157+ 'ة' : 'p' ,
158+ 'و' : 'w' ,
159+ 'ؤ' : '&' ,
160+ 'ي' : 'y' ,
161+ 'ى' : 'Y' ,
162+ 'ئ' : '}' ,
163+ 'ء' : '\' ' ,
164+ 'َ' : 'a' ,
165+ 'ُ' : 'u' ,
166+ 'ِ' : 'i' ,
167+ 'ً' : 'F' ,
168+ 'ٌ' : 'N' ,
169+ 'ٍ' : 'K' ,
170+ 'ّ' : '~' ,
171+ 'ْ' : 'o' ,
172+ 'ٰ' : '`' ,
64173 '٠' : '0' ,
65174 '١' : '1' ,
66175 '٢' : '2' ,
@@ -77,50 +186,52 @@ class ArabicRomanizer extends Romanizer {
77186 r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\u0870-\u089F\uFB50-\uFDFF\uFE70-\uFEFF]' ,
78187 );
79188
80- /// Converts a given Arabic string to its Romanized form.
81- ///
82- /// This method transliterates Arabic characters into their Latin
83- /// equivalents using a standard transliteration table. Non-Arabic
84- /// characters (such as Latin letters, numbers, and punctuation) are
85- /// preserved as-is.
86- ///
87- /// Example:
88- /// ```dart
89- /// final romanizer = ArabicRomanizer();
90- /// final result = romanizer.romanize('أنا العربي');
91- /// print(result); // ana al'arabi
92- /// ```
93189 @override
94190 String romanize (String input) {
95191 final buffer = StringBuffer ();
96- for (final char in input.runes) {
192+ final map = switch (system) {
193+ ArabicSystem .alaLc => _alaLcMap,
194+ ArabicSystem .din31635 => _din31635Map,
195+ ArabicSystem .buckwalter => _buckwalterMap,
196+ };
197+
198+ final runes = input.runes.toList ();
199+
200+ for (int i = 0 ; i < runes.length; i++ ) {
201+ final char = runes[i];
97202 final charString = String .fromCharCode (char);
98- buffer.write (_transliterationMap[charString] ?? charString);
203+
204+ // Special handling for ALA-LC Alef 'ا'
205+ if (system == ArabicSystem .alaLc && charString == 'ا' ) {
206+ final isStartOfWord = i == 0 || _isSeparator (runes[i - 1 ]);
207+ if (isStartOfWord) {
208+ buffer.write ('a' );
209+ } else {
210+ buffer.write ('ā' );
211+ }
212+ continue ;
213+ }
214+
215+ buffer.write (map[charString] ?? charString);
99216 }
100217 return buffer.toString ();
101218 }
102219
103- /// Validates if the input string contains Arabic characters.
104- ///
105- /// Returns `true` if the input contains any Arabic characters from
106- /// the Unicode Arabic blocks.
107- ///
108- /// Example:
109- /// ```dart
110- /// final romanizer = ArabicRomanizer();
111- /// print(romanizer.isValid('أنا')); // true
112- /// print(romanizer.isValid('Hello')); // false
113- /// print(romanizer.isValid('أنا Hello')); // true (mixed content)
114- /// ```
220+ /// Check for whitespace or common punctuation
221+ bool _isSeparator (int charCode) {
222+ return charCode == 32 || // Space
223+ charCode == 10 || // Newline
224+ charCode == 9 || // Tab
225+ charCode == 13 || // CR
226+ charCode == 46 || // .
227+ charCode == 44 || // ,
228+ charCode == 1548 || // ، (Arabic comma)
229+ charCode == 63 || // ?
230+ charCode == 1567 ; // ؟ (Arabic Question mark)
231+ }
232+
115233 @override
116234 bool isValid (String input) {
117- // Check for Arabic characters in Unicode ranges:
118- // - Arabic: U+0600–U+06FF
119- // - Arabic Supplement: U+0750–U+077F
120- // - Arabic Extended-A: U+08A0–U+08FF
121- // - Arabic Extended-B: U+0870–U+089F
122- // - Arabic Presentation Forms-A: U+FB50–U+FDFF
123- // - Arabic Presentation Forms-B: U+FE70–U+FEFF
124235 return _arabicPattern.hasMatch (input);
125236 }
126237}
0 commit comments