Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
75.68% |
56 / 74 |
|
44.44% |
4 / 9 |
CRAP | |
0.00% |
0 / 1 |
| MniConverter | |
76.71% |
56 / 73 |
|
44.44% |
4 / 9 |
81.57 | |
0.00% |
0 / 1 |
| isBeginning | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| isEndOfWord | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
| mteiToBengali | |
80.39% |
41 / 51 |
|
0.00% |
0 / 1 |
48.89 | |||
| transliterate | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| getMainCode | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getLanguageVariants | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getVariantsFallbacks | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
| loadDefaultTables | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
| translate | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * @license GPL-2.0-or-later |
| 4 | * @file MniConverter.php |
| 5 | * @author Nokib Sarkar |
| 6 | * @author Haoreima |
| 7 | */ |
| 8 | |
| 9 | namespace MediaWiki\Language\Converters; |
| 10 | |
| 11 | use MediaWiki\Language\LanguageConverterSpecific; |
| 12 | use Wikimedia\ReplacementArray; |
| 13 | |
| 14 | /** |
| 15 | * Meitei specific converter routines. |
| 16 | * |
| 17 | * @ingroup Languages |
| 18 | */ |
| 19 | class MniConverter extends LanguageConverterSpecific { |
| 20 | private const O = 'ꯑ'; |
| 21 | private const OO = 'ꯑꯣ'; |
| 22 | private const U = 'ꯎ'; |
| 23 | private const EE = 'ꯑꯤ'; |
| 24 | private const YA = 'ꯌ'; |
| 25 | private const Y_ = 'য'; |
| 26 | private const WA = 'ꯋ'; |
| 27 | private const BA = 'ꯕ'; |
| 28 | private const NA_ = 'ꯟ'; |
| 29 | private const NA = 'ꯅ'; |
| 30 | private const DIACRITIC_AA = 'ꯥ'; |
| 31 | private const HALANTA = '꯭'; |
| 32 | private const SKIP = ''; |
| 33 | private const PERIOD = '꯫'; |
| 34 | private const PA_ = 'ꯞ'; |
| 35 | private const DIACRITICS_WITH_O = [ |
| 36 | 'ꯣ' => 'ো', |
| 37 | 'ꯤ' => 'ী', |
| 38 | 'ꯥ' => 'া', |
| 39 | 'ꯦ' => 'ে', |
| 40 | 'ꯧ' => 'ৌ', |
| 41 | 'ꯩ' => 'ৈ', |
| 42 | 'ꯪ' => 'ং', |
| 43 | ]; |
| 44 | private const CONJUGATE_WITH_O = [ |
| 45 | 'ꯑꯣ' => 'ও', |
| 46 | 'ꯑꯤ' => 'ঈ', |
| 47 | 'ꯑꯥ' => 'আ', |
| 48 | 'ꯑꯦ' => 'এ', |
| 49 | 'ꯑꯧ' => 'ঔ', |
| 50 | 'ꯑꯩ' => 'ঐ', |
| 51 | 'ꯑꯪ' => 'অং', |
| 52 | ]; |
| 53 | private const NOT_WEIRD_AFTER_NA_ = [ 'ꯇ', 'ꯊ', 'ꯗ', 'ꯙ', 'ꯟ', 'ꯕ', 'ꯌ', 'ꯁ' ]; |
| 54 | private const NUMERALS = [ |
| 55 | '꯰' => '০', |
| 56 | '꯱' => '১', |
| 57 | '꯲' => '২', |
| 58 | '꯳' => '৩', |
| 59 | '꯴' => '৪', |
| 60 | '꯵' => '৫', |
| 61 | '꯶' => '৬', |
| 62 | '꯷' => '৭', |
| 63 | '꯸' => '৮', |
| 64 | '꯹' => '৯', |
| 65 | ]; |
| 66 | private const HALANTA_CONSONANTS = [ |
| 67 | 'ꯟ' => 'ন্', |
| 68 | 'ꯛ' => 'ক্', |
| 69 | 'ꯝ' => 'ম্', |
| 70 | 'ꯡ' => 'ং', |
| 71 | 'ꯜ' => 'ল্', |
| 72 | 'ꯠ' => 'ৎ', |
| 73 | 'ꯞ' => 'প্', |
| 74 | ]; |
| 75 | private const HALANTA_CONSONANTS_TO_NORMAL = [ |
| 76 | 'ꯟ' => 'ন', |
| 77 | 'ꯛ' => 'ক', |
| 78 | 'ꯝ' => 'ম', |
| 79 | 'ꯡ' => 'ং', |
| 80 | 'ꯜ' => 'ল', |
| 81 | 'ꯠ' => 'ৎ', |
| 82 | 'ꯞ' => 'প', |
| 83 | ]; |
| 84 | private const NON_WORD_CHARACTER_PATTERN = "/[\s꯫\p{P}<>=\-\|$+^~]+?/u"; |
| 85 | private const CONSONANTS = self::HALANTA_CONSONANTS + [ |
| 86 | 'ꯀ' => 'ক', |
| 87 | 'ꯈ' => 'খ', |
| 88 | 'ꯒ' => 'গ', |
| 89 | 'ꯘ' => 'ঘ', |
| 90 | 'ꯉ' => 'ঙ', |
| 91 | 'ꯆ' => 'চ', |
| 92 | 'ꯖ' => 'জ', |
| 93 | 'ꯓ' => 'ঝ', |
| 94 | 'ꯇ' => 'ত', |
| 95 | 'ꯊ' => 'থ', |
| 96 | 'ꯗ' => 'দ', |
| 97 | 'ꯙ' => 'ধ', |
| 98 | 'ꯅ' => 'ন', |
| 99 | 'ꯄ' => 'প', |
| 100 | 'ꯐ' => 'ফ', |
| 101 | 'ꯕ' => 'ব', |
| 102 | 'ꯚ' => 'ভ', |
| 103 | 'ꯃ' => 'ম', |
| 104 | 'ꯌ' => 'য়', |
| 105 | 'ꯔ' => 'র', |
| 106 | 'ꯂ' => 'ল', |
| 107 | 'ꯋ' => 'ৱ', |
| 108 | 'ꫩ' => 'শ', |
| 109 | 'ꫪ' => 'ষ', |
| 110 | 'ꯁ' => 'স', |
| 111 | 'ꯍ' => 'হ', |
| 112 | ]; |
| 113 | private const VOWELS = [ |
| 114 | 'ꯑ' => 'অ', |
| 115 | 'ꯏ' => 'ই', |
| 116 | 'ꯎ' => 'উ', |
| 117 | 'ꯢ' => 'ই', |
| 118 | 'ꯨ' => 'ু', |
| 119 | ]; |
| 120 | private const MTEI_TO_BENG_MAP_EXTRA = [ |
| 121 | '꯫' => '।', |
| 122 | '꯭' => '্', |
| 123 | ]; |
| 124 | private const MTEI_TO_BENG_MAP = |
| 125 | self::VOWELS + |
| 126 | self::DIACRITICS_WITH_O + |
| 127 | self::CONJUGATE_WITH_O + |
| 128 | self::CONSONANTS + |
| 129 | self::NUMERALS + |
| 130 | self::MTEI_TO_BENG_MAP_EXTRA; |
| 131 | |
| 132 | private function isBeginning( int $position, string $text ): bool { |
| 133 | $at_first = $position === 0; |
| 134 | return $at_first || preg_match( self::NON_WORD_CHARACTER_PATTERN, $text[$position - 1] ); |
| 135 | } |
| 136 | |
| 137 | private function isEndOfWord( string $char ): bool { |
| 138 | if ( $char === self::PERIOD ) { |
| 139 | return true; |
| 140 | } |
| 141 | $status = preg_match( self::NON_WORD_CHARACTER_PATTERN, $char, $matches ); |
| 142 | return count( $matches ) > 0; |
| 143 | } |
| 144 | |
| 145 | private function mteiToBengali( string $text ): iterable { |
| 146 | $chars = mb_str_split( $text ); |
| 147 | $l = count( $chars ); |
| 148 | $i = 0; |
| 149 | while ( $i < $l ) { |
| 150 | $char = $chars[$i]; |
| 151 | if ( |
| 152 | $char === self::O && |
| 153 | $i + 1 < $l && |
| 154 | array_key_exists( $chars[ $i + 1 ], self::DIACRITICS_WITH_O ) |
| 155 | ) { |
| 156 | /** |
| 157 | * We have only 3 true vowels, |
| 158 | * ꯑ(a), ꯏ(i), ꯎ (u) |
| 159 | * Others are just extension from "a" by mixing with diacritics |
| 160 | */ |
| 161 | yield self::CONJUGATE_WITH_O[$char . $chars[ $i + 1 ]]; |
| 162 | $i++; |
| 163 | } elseif ( |
| 164 | $char === self::HALANTA && |
| 165 | $i > 0 && |
| 166 | array_key_exists( $chars[ $i - 1 ], self::HALANTA_CONSONANTS ) |
| 167 | ) { |
| 168 | // Remove halanta if the consonant has halanta already |
| 169 | yield self::SKIP; |
| 170 | } elseif ( |
| 171 | array_key_exists( $char, self::HALANTA_CONSONANTS ) && |
| 172 | ( $i === $l - 1 || ( $i + 1 < $l && |
| 173 | $this->isEndOfWord( $chars[ $i + 1 ] ) |
| 174 | ) ) |
| 175 | ) { |
| 176 | // Remove halanta if this is the last character of the word |
| 177 | yield self::HALANTA_CONSONANTS_TO_NORMAL[$char]; |
| 178 | } elseif ( $char === self::YA && |
| 179 | $i > 0 && $chars[ $i - 1 ] === self::HALANTA ) { |
| 180 | // য + ্ = য় |
| 181 | yield self::Y_; |
| 182 | } elseif ( |
| 183 | $char === self::WA && |
| 184 | $i - 2 >= 0 && $chars[ $i - 1 ] === self::HALANTA && |
| 185 | array_key_exists( $chars[ $i - 2 ], self::CONSONANTS ) |
| 186 | ) { |
| 187 | // ব + ্ + র = ব্র |
| 188 | yield self::CONSONANTS[self::BA]; |
| 189 | } elseif ( |
| 190 | $char === self::PA_ && $i + 1 < $l && $chars[ $i + 1 ] === 'ꯀ' |
| 191 | ) { |
| 192 | // do not conjugate with halanta if it's followed by "ক" |
| 193 | yield self::HALANTA_CONSONANTS_TO_NORMAL[$char]; |
| 194 | } elseif ( |
| 195 | $char === self::NA_ && |
| 196 | $i + 1 < $l && |
| 197 | !in_array( $chars[ $i + 1 ], self::NOT_WEIRD_AFTER_NA_ ) && |
| 198 | array_key_exists( $chars[ $i + 1 ], self::CONSONANTS ) |
| 199 | ) { |
| 200 | /** |
| 201 | * ন্ / ণ্ + any consonant |
| 202 | * (except, ট, ঠ, ড, ঢ, , ত, থ, দ, ধ, ন, ব, য, য়) = weird |
| 203 | * Any consonant + ্ + ন = maybe ok |
| 204 | */ |
| 205 | yield self::MTEI_TO_BENG_MAP[self::NA]; |
| 206 | $i++; |
| 207 | continue; |
| 208 | } elseif ( $char === self::U && !$this->isBeginning( $i, $text ) ) { |
| 209 | // উ/ঊ in the middle of words are often replaced by ও |
| 210 | yield self::MTEI_TO_BENG_MAP[self::OO]; |
| 211 | } elseif ( $char === self::O && |
| 212 | $i + 2 < $l && $chars[$i + 1] === self::EE[0] && $chars[ $i + 2 ] === self::EE[1] ) { |
| 213 | /** |
| 214 | * Instead of হাঈবা, people love to use হায়বা. |
| 215 | * But this is only in the case when ee or ya is |
| 216 | * in the middle of the words, |
| 217 | * never to do it if it's in the beginning. |
| 218 | */ |
| 219 | yield self::MTEI_TO_BENG_MAP[self::YA]; |
| 220 | } elseif ( |
| 221 | !array_key_exists( $char, self::HALANTA_CONSONANTS ) && |
| 222 | array_key_exists( $char, self::CONSONANTS ) && |
| 223 | ( $i === $l - 1 || ( $i + 1 < $l && |
| 224 | $this->isEndOfWord( $chars[ $i + 1 ] ) |
| 225 | ) ) |
| 226 | ) { |
| 227 | // Consonants without halantas should end with diacritics of aa sound every time. |
| 228 | yield self::MTEI_TO_BENG_MAP[$char] . self::MTEI_TO_BENG_MAP[self::DIACRITIC_AA]; |
| 229 | } else { |
| 230 | yield ( |
| 231 | array_key_exists( $char, self::MTEI_TO_BENG_MAP ) ? |
| 232 | self::MTEI_TO_BENG_MAP[$char] : $char |
| 233 | ); |
| 234 | } |
| 235 | $i++; |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | public function transliterate( string $text ): string { |
| 240 | $transliterated = ''; |
| 241 | foreach ( $this->mteiToBengali( $text ) as $char ) { |
| 242 | $transliterated .= $char; |
| 243 | } |
| 244 | return $transliterated; |
| 245 | } |
| 246 | |
| 247 | public function getMainCode(): string { |
| 248 | return 'mni'; |
| 249 | } |
| 250 | |
| 251 | public function getLanguageVariants(): array { |
| 252 | return [ 'mni', 'mni-beng' ]; |
| 253 | } |
| 254 | |
| 255 | public function getVariantsFallbacks(): array { |
| 256 | return [ |
| 257 | 'mni-beng' => 'mni' |
| 258 | ]; |
| 259 | } |
| 260 | |
| 261 | protected function loadDefaultTables(): array { |
| 262 | return [ |
| 263 | 'mni' => new ReplacementArray(), |
| 264 | 'mni-beng' => new ReplacementArray(), |
| 265 | ]; |
| 266 | } |
| 267 | |
| 268 | /** |
| 269 | * Transliterates text into Bangla Script. This allows developers to test the language variants |
| 270 | * functionality and user interface without having to switch wiki language away from default. |
| 271 | * This method also processes custom conversion rules to allow testing these parts of the |
| 272 | * language converter as well. |
| 273 | * |
| 274 | * @param string $text |
| 275 | * @param string $toVariant |
| 276 | * @return string |
| 277 | */ |
| 278 | public function translate( $text, $toVariant ) { |
| 279 | if ( $toVariant === 'mni-beng' ) { |
| 280 | return $this->transliterate( $text ); |
| 281 | } |
| 282 | return $text; |
| 283 | } |
| 284 | } |
| 285 | |
| 286 | /** @deprecated class alias since 1.46 */ |
| 287 | class_alias( MniConverter::class, 'MniConverter' ); |