Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
75.68% covered (warning)
75.68%
56 / 74
44.44% covered (danger)
44.44%
4 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
MniConverter
76.71% covered (warning)
76.71%
56 / 73
44.44% covered (danger)
44.44%
4 / 9
81.57
0.00% covered (danger)
0.00%
0 / 1
 isBeginning
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 isEndOfWord
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 mteiToBengali
80.39% covered (warning)
80.39%
41 / 51
0.00% covered (danger)
0.00%
0 / 1
48.89
 transliterate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getMainCode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLanguageVariants
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getVariantsFallbacks
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 loadDefaultTables
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 translate
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
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
9namespace MediaWiki\Language\Converters;
10
11use MediaWiki\Language\LanguageConverterSpecific;
12use Wikimedia\ReplacementArray;
13
14/**
15 * Meitei specific converter routines.
16 *
17 * @ingroup Languages
18 */
19class 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 */
287class_alias( MniConverter::class, 'MniConverter' );