9namespace MediaWiki\Extension\Translate\MessageSync;
11use InvalidArgumentException;
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';
32 private const SIMILARITY_THRESHOLD = 0.9;
38 protected $addFunctionMap;
43 protected $removeFunctionMap;
47 $this->changes = $changes;
48 $this->addFunctionMap = [
49 self::ADDITION => [ $this,
'addAddition' ],
50 self::DELETION => [ $this,
'addDeletion' ],
51 self::CHANGE => [ $this,
'addChange' ]
54 $this->removeFunctionMap = [
55 self::ADDITION => [ $this,
'removeAdditions' ],
56 self::DELETION => [ $this,
'removeDeletions' ],
57 self::CHANGE => [ $this,
'removeChanges' ]
67 public function addChange( $language, $key, $content ) {
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']
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']
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 )
126 $languageChanges =
null;
127 if ( isset( $this->changes[ $language ] ) ) {
128 $languageChanges = &$this->changes[ $language ];
130 if ( $languageChanges !==
null && isset( $languageChanges[
'rename' ][ $msgKey ] ) ) {
131 $languageChanges[
'rename' ][ $msgKey ][
'previous_state' ] = $state;
142 $this->changes[$language][$type][] = [
144 'content' => $content,
183 public function findMessage( $language, $key, $possibleStates = [], &$modificationType =
null ) {
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 );
190 if ( $possibleStates === [] ) {
191 $possibleStates = [ self::ADDITION, self::CHANGE, self::DELETION, self::RENAME ];
194 foreach ( $allChanges as $type => $modifications ) {
195 if ( !in_array( $type, $possibleStates ) ) {
199 if ( $type === self::RENAME ) {
200 if ( isset( $modifications[$key] ) ) {
201 $modificationType = $type;
202 return $modifications[$key];
207 foreach ( $modifications as $modification ) {
208 $currentKey = $modification[
'key'];
209 if ( $currentKey === $key ) {
210 $modificationType = $type;
211 return $modification;
216 $modificationType =
null;
227 $msg = $this->
findMessage( $languageCode, $msgKey, [ self::RENAME ] );
228 if ( $msg ===
null ) {
232 if ( $matchedMsg ===
null ) {
237 $this->
removeRenames( $languageCode, [ $matchedMsg[
'key'], $msg[
'key'] ] );
239 $matchedMsgState = $matchedMsg[
'previous_state' ];
240 $msgState = $msg[
'previous_state' ];
243 if ( $matchedMsgState !== self::NONE ) {
244 if ( $matchedMsgState === self::CHANGE ) {
245 $matchedMsg[
'key'] = $msg[
'key'];
248 $this->addFunctionMap[ $matchedMsgState ],
251 $matchedMsg[
'content']
255 if ( $msgState !== self::NONE ) {
256 if ( $msgState === self::CHANGE ) {
257 $msg[
'key'] = $matchedMsg[
'key'];
260 $this->addFunctionMap[ $msgState ],
277 foreach ( $renames as $key => &$rename ) {
278 $rename[
'key'] = $key;
290 return $this->changes[$language][$type] ?? [];
299 $this->removeModification( $language, self::ADDITION, $keysToRemove );
308 $this->removeModification( $language, self::DELETION, $keysToRemove );
317 $this->removeModification( $language, self::CHANGE, $keysToRemove );
326 $this->removeModification( $language, self::RENAME, $keysToRemove );
337 $callable = $this->removeFunctionMap[ $type ] ??
null;
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."
346 call_user_func( $callable, $language, $keysToRemove );
354 unset( $this->changes[ $language ] );
357 protected function removeModification( $language, $type, $keysToRemove =
null ) {
358 if ( !isset( $this->changes[$language][$type] ) ) {
362 if ( $keysToRemove ===
null ) {
363 unset( $this->changes[$language][$type] );
366 if ( $keysToRemove === [] ) {
370 if ( $type === self::RENAME ) {
371 $this->changes[$language][$type] =
372 array_diff_key( $this->changes[$language][$type], array_flip( $keysToRemove ) );
374 $this->changes[$language][$type] = array_filter(
375 $this->changes[$language][$type],
376 static function ( $change ) use ( $keysToRemove ) {
377 return !in_array( $change[
'key'], $keysToRemove,
true );
388 return $this->changes;
397 return $this->changes[$language] ?? [];
406 return new self( $changesData );
414 return array_keys( $this->changes );
429 $hasOnlyAdditions = $hasOnlyRenames =
430 $hasOnlyChanges = $hasOnlyDeletions =
true;
433 $hasOnlyAdditions = $hasOnlyRenames = $hasOnlyChanges =
false;
437 $hasOnlyDeletions = $hasOnlyAdditions = $hasOnlyChanges =
false;
441 $hasOnlyAdditions = $hasOnlyRenames = $hasOnlyDeletions =
false;
445 $hasOnlyDeletions = $hasOnlyRenames = $hasOnlyChanges =
false;
448 if ( $type === self::DELETION ) {
449 $response = $hasOnlyDeletions;
450 } elseif ( $type === self::RENAME ) {
451 $response = $hasOnlyRenames;
452 } elseif ( $type === self::CHANGE ) {
453 $response = $hasOnlyChanges;
454 } elseif ( $type === self::ADDITION ) {
455 $response = $hasOnlyAdditions;
457 throw new InvalidArgumentException(
"Unknown $type passed." );
471 $msg = $this->
findMessage( $languageCode, $key, [ self::RENAME ] );
473 return isset( $msg[
'previous_state'] ) && in_array( $msg[
'previous_state'], $types );
485 return $this->changes[ $languageCode ][ self::RENAME ][ $matchedKey ] ??
null;
498 return $this->changes[ $languageCode ][ self::RENAME ][ $key ][
'matched_to' ] ??
null;
508 $msg = $this->
findMessage( $languageCode, $key, [ self::RENAME ] );
510 return $msg[
'similarity' ] ??
null;
519 public function isEqual( $languageCode, $key ) {
520 $msg = $this->
findMessage( $languageCode, $key, [ self::RENAME ] );
532 $msg = $this->
findMessage( $languageCode, $key, [ self::RENAME ] );
542 return $similarity >= self::SIMILARITY_THRESHOLD;
551 return $similarity === 1;