Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageSourceChange.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Extension\Translate\MessageSync;
10
11use InvalidArgumentException;
12
25 protected $changes = [];
26 public const ADDITION = 'addition';
27 public const CHANGE = 'change';
28 public const DELETION = 'deletion';
29 public const RENAME = 'rename';
30 public const NONE = 'none';
31
32 private const SIMILARITY_THRESHOLD = 0.9;
33
38 protected $addFunctionMap;
43 protected $removeFunctionMap;
44
46 public function __construct( $changes = [] ) {
47 $this->changes = $changes;
48 $this->addFunctionMap = [
49 self::ADDITION => [ $this, 'addAddition' ],
50 self::DELETION => [ $this, 'addDeletion' ],
51 self::CHANGE => [ $this, 'addChange' ]
52 ];
53
54 $this->removeFunctionMap = [
55 self::ADDITION => [ $this, 'removeAdditions' ],
56 self::DELETION => [ $this, 'removeDeletions' ],
57 self::CHANGE => [ $this, 'removeChanges' ]
58 ];
59 }
60
67 public function addChange( $language, $key, $content ) {
68 $this->addModification( $language, self::CHANGE, $key, $content );
69 }
70
77 public function addAddition( $language, $key, $content ) {
78 $this->addModification( $language, self::ADDITION, $key, $content );
79 }
80
87 public function addDeletion( $language, $key, $content ) {
88 $this->addModification( $language, self::DELETION, $key, $content );
89 }
90
98 public function addRename( $language, $addedMessage, $deletedMessage, $similarity = 0 ) {
99 $this->changes[$language][self::RENAME][$addedMessage['key']] = [
100 'content' => $addedMessage['content'],
101 'similarity' => $similarity,
102 'matched_to' => $deletedMessage['key'],
103 'previous_state' => self::ADDITION,
104 'key' => $addedMessage['key']
105 ];
106
107 $this->changes[$language][self::RENAME][$deletedMessage['key']] = [
108 'content' => $deletedMessage['content'],
109 'similarity' => $similarity,
110 'matched_to' => $addedMessage['key'],
111 'previous_state' => self::DELETION,
112 'key' => $deletedMessage['key']
113 ];
114 }
115
116 public function setRenameState( $language, $msgKey, $state ) {
117 $possibleStates = [ self::ADDITION, self::CHANGE, self::DELETION,
118 self::NONE, self::RENAME ];
119 if ( !in_array( $state, $possibleStates ) ) {
120 throw new InvalidArgumentException(
121 "Invalid state passed - '$state'. Possible states - "
122 . implode( ', ', $possibleStates )
123 );
124 }
125
126 $languageChanges = null;
127 if ( isset( $this->changes[ $language ] ) ) {
128 $languageChanges = &$this->changes[ $language ];
129 }
130 if ( $languageChanges !== null && isset( $languageChanges[ 'rename' ][ $msgKey ] ) ) {
131 $languageChanges[ 'rename' ][ $msgKey ][ 'previous_state' ] = $state;
132 }
133 }
134
141 protected function addModification( $language, $type, $key, $content ) {
142 $this->changes[$language][$type][] = [
143 'key' => $key,
144 'content' => $content,
145 ];
146 }
147
153 public function getChanges( $language ) {
154 return $this->getModification( $language, self::CHANGE );
155 }
156
162 public function getDeletions( $language ) {
163 return $this->getModification( $language, self::DELETION );
164 }
165
171 public function getAdditions( $language ) {
172 return $this->getModification( $language, self::ADDITION );
173 }
174
183 public function findMessage( $language, $key, $possibleStates = [], &$modificationType = null ) {
184 $allChanges = [];
185 $allChanges[self::ADDITION] = $this->getAdditions( $language );
186 $allChanges[self::DELETION] = $this->getDeletions( $language );
187 $allChanges[self::CHANGE] = $this->getChanges( $language );
188 $allChanges[self::RENAME] = $this->getRenames( $language );
189
190 if ( $possibleStates === [] ) {
191 $possibleStates = [ self::ADDITION, self::CHANGE, self::DELETION, self::RENAME ];
192 }
193
194 foreach ( $allChanges as $type => $modifications ) {
195 if ( !in_array( $type, $possibleStates ) ) {
196 continue;
197 }
198
199 if ( $type === self::RENAME ) {
200 if ( isset( $modifications[$key] ) ) {
201 $modificationType = $type;
202 return $modifications[$key];
203 }
204 continue;
205 }
206
207 foreach ( $modifications as $modification ) {
208 $currentKey = $modification['key'];
209 if ( $currentKey === $key ) {
210 $modificationType = $type;
211 return $modification;
212 }
213 }
214 }
215
216 $modificationType = null;
217 return null;
218 }
219
226 public function breakRename( $languageCode, $msgKey ) {
227 $msg = $this->findMessage( $languageCode, $msgKey, [ self::RENAME ] );
228 if ( $msg === null ) {
229 return null;
230 }
231 $matchedMsg = $this->getMatchedMessage( $languageCode, $msg['key'] );
232 if ( $matchedMsg === null ) {
233 return null;
234 }
235
236 // Remove them from the renames array
237 $this->removeRenames( $languageCode, [ $matchedMsg['key'], $msg['key'] ] );
238
239 $matchedMsgState = $matchedMsg[ 'previous_state' ];
240 $msgState = $msg[ 'previous_state' ];
241
242 // Add them to the changes under the appropriate state
243 if ( $matchedMsgState !== self::NONE ) {
244 if ( $matchedMsgState === self::CHANGE ) {
245 $matchedMsg['key'] = $msg['key'];
246 }
247 call_user_func(
248 $this->addFunctionMap[ $matchedMsgState ],
249 $languageCode,
250 $matchedMsg['key'],
251 $matchedMsg['content']
252 );
253 }
254
255 if ( $msgState !== self::NONE ) {
256 if ( $msgState === self::CHANGE ) {
257 $msg['key'] = $matchedMsg['key'];
258 }
259 call_user_func(
260 $this->addFunctionMap[ $msgState ],
261 $languageCode,
262 $msg['key'],
263 $msg['content']
264 );
265 }
266
267 return $msgState;
268 }
269
275 public function getRenames( $language ) {
276 $renames = $this->getModification( $language, self::RENAME );
277 foreach ( $renames as $key => &$rename ) {
278 $rename['key'] = $key;
279 }
280
281 return $renames;
282 }
283
289 protected function getModification( $language, $type ) {
290 return $this->changes[$language][$type] ?? [];
291 }
292
298 public function removeAdditions( $language, $keysToRemove ) {
299 $this->removeModification( $language, self::ADDITION, $keysToRemove );
300 }
301
307 public function removeDeletions( $language, $keysToRemove ) {
308 $this->removeModification( $language, self::DELETION, $keysToRemove );
309 }
310
316 public function removeChanges( $language, $keysToRemove ) {
317 $this->removeModification( $language, self::CHANGE, $keysToRemove );
318 }
319
325 public function removeRenames( $language, $keysToRemove ) {
326 $this->removeModification( $language, self::RENAME, $keysToRemove );
327 }
328
336 public function removeBasedOnType( $language, $keysToRemove, $type ) {
337 $callable = $this->removeFunctionMap[ $type ] ?? null;
338
339 if ( $callable === null ) {
340 throw new InvalidArgumentException( 'Type should be one of ' .
341 implode( ', ', [ self::ADDITION, self::CHANGE, self::DELETION ] ) .
342 ". Invalid type $type passed."
343 );
344 }
345
346 call_user_func( $callable, $language, $keysToRemove );
347 }
348
353 public function removeChangesForLanguage( $language ) {
354 unset( $this->changes[ $language ] );
355 }
356
357 protected function removeModification( $language, $type, $keysToRemove = null ) {
358 if ( !isset( $this->changes[$language][$type] ) || $keysToRemove === [] ) {
359 return;
360 }
361
362 if ( $keysToRemove === null ) {
363 unset( $this->changes[$language][$type] );
364 return;
365 }
366
367 if ( $type === self::RENAME ) {
368 $this->changes[$language][$type] =
369 array_diff_key( $this->changes[$language][$type], array_flip( $keysToRemove ) );
370 } else {
371 $this->changes[$language][$type] = array_filter(
372 $this->changes[$language][$type],
373 static function ( $change ) use ( $keysToRemove ) {
374 return !in_array( $change['key'], $keysToRemove, true );
375 }
376 );
377 }
378 }
379
384 public function getAllModifications() {
385 return $this->changes;
386 }
387
393 public function getModificationsForLanguage( $language ) {
394 return $this->changes[$language] ?? [];
395 }
396
402 public static function loadModifications( $changesData ) {
403 return new self( $changesData );
404 }
405
410 public function getLanguages() {
411 return array_keys( $this->changes );
412 }
413
421 public function hasOnly( $language, $type ) {
422 $deletions = $this->getDeletions( $language );
423 $additions = $this->getAdditions( $language );
424 $renames = $this->getRenames( $language );
425 $changes = $this->getChanges( $language );
426 $hasOnlyAdditions = $hasOnlyRenames =
427 $hasOnlyChanges = $hasOnlyDeletions = true;
428
429 if ( $deletions ) {
430 $hasOnlyAdditions = $hasOnlyRenames = $hasOnlyChanges = false;
431 }
432
433 if ( $renames ) {
434 $hasOnlyDeletions = $hasOnlyAdditions = $hasOnlyChanges = false;
435 }
436
437 if ( $changes ) {
438 $hasOnlyAdditions = $hasOnlyRenames = $hasOnlyDeletions = false;
439 }
440
441 if ( $additions ) {
442 $hasOnlyDeletions = $hasOnlyRenames = $hasOnlyChanges = false;
443 }
444
445 if ( $type === self::DELETION ) {
446 $response = $hasOnlyDeletions;
447 } elseif ( $type === self::RENAME ) {
448 $response = $hasOnlyRenames;
449 } elseif ( $type === self::CHANGE ) {
450 $response = $hasOnlyChanges;
451 } elseif ( $type === self::ADDITION ) {
452 $response = $hasOnlyAdditions;
453 } else {
454 throw new InvalidArgumentException( "Unknown $type passed." );
455 }
456
457 return $response;
458 }
459
467 public function isPreviousState( $languageCode, $key, array $types ) {
468 $msg = $this->findMessage( $languageCode, $key, [ self::RENAME ] );
469
470 return isset( $msg['previous_state'] ) && in_array( $msg['previous_state'], $types );
471 }
472
479 public function getMatchedMessage( $languageCode, $key ) {
480 $matchedKey = $this->getMatchedKey( $languageCode, $key );
481 if ( $matchedKey ) {
482 return $this->changes[ $languageCode ][ self::RENAME ][ $matchedKey ] ?? null;
483 }
484
485 return null;
486 }
487
494 public function getMatchedKey( $languageCode, $key ) {
495 return $this->changes[ $languageCode ][ self::RENAME ][ $key ][ 'matched_to' ] ?? null;
496 }
497
504 public function getSimilarity( $languageCode, $key ) {
505 $msg = $this->findMessage( $languageCode, $key, [ self::RENAME ] );
506
507 return $msg[ 'similarity' ] ?? null;
508 }
509
516 public function isEqual( $languageCode, $key ) {
517 $msg = $this->findMessage( $languageCode, $key, [ self::RENAME ] );
518 return $msg && $this->areStringsEqual( $msg[ 'similarity' ] );
519 }
520
528 public function isSimilar( $languageCode, $key ) {
529 $msg = $this->findMessage( $languageCode, $key, [ self::RENAME ] );
530 return $msg && $this->areStringsSimilar( $msg[ 'similarity' ] );
531 }
532
538 public function areStringsSimilar( $similarity ) {
539 return $similarity >= self::SIMILARITY_THRESHOLD;
540 }
541
547 public function areStringsEqual( $similarity ) {
548 return $similarity === 1;
549 }
550}
Class is used to track the changes made when importing messages from the remote sources using importE...
addRename( $language, $addedMessage, $deletedMessage, $similarity=0)
Adds a rename under a message group for a specific language.
getLanguages()
Get all language keys with modifications under the group.
removeAdditions( $language, $keysToRemove)
Remove additions for a language under the group.
getSimilarity( $languageCode, $key)
Returns the calculated similarity for a rename.
getMatchedKey( $languageCode, $key)
Get matched rename key for a given key.
isPreviousState( $languageCode, $key, array $types)
Checks if the previous state of a renamed message matches a given value.
static loadModifications( $changesData)
Loads the changes, and returns an instance of the class.
addAddition( $language, $key, $content)
Add an addition under a message group for a specific language.
findMessage( $language, $key, $possibleStates=[], &$modificationType=null)
Finds a message with the given key across different types of modifications.
getMatchedMessage( $languageCode, $key)
Get matched rename message for a given key.
addChange( $language, $key, $content)
Add a change under a message group for a specific language.
removeRenames( $language, $keysToRemove)
Remove renames for a language under the group.
isEqual( $languageCode, $key)
Checks if a given key is equal to matched rename message.
hasOnly( $language, $type)
Determines if the group has only a certain type of change under a language.
getModificationsForLanguage( $language)
Get all for a language under the group.
getChanges( $language)
Fetch changes for a message group under a language.
isSimilar( $languageCode, $key)
Checks if a given key is similar to matched rename message.
removeDeletions( $language, $keysToRemove)
Remove deletions for a language under the group.
getAdditions( $language)
Fetch additions for a message group under a language.
areStringsEqual( $similarity)
Checks if the similarity percent passed.
getRenames( $language)
Fetch renames for a message group under a language.
removeChanges( $language, $keysToRemove)
Remove changes for a language under the group.
removeChangesForLanguage( $language)
Remove all language related changes for a group.
getDeletions( $language)
Fetch deletions for a message group under a language.
addDeletion( $language, $key, $content)
Adds a deletion under a message group for a specific language.
removeBasedOnType( $language, $keysToRemove, $type)
Remove modifications based on the type.
areStringsSimilar( $similarity)
Checks if the similarity percent passed passes the min threshold.
breakRename( $languageCode, $msgKey)
Break renames, and put messages back into their previous state.