22 protected $stringComparator;
26 $this->stringComparator = $stringComparator;
54 $processAll = $languages === self::ALL_LANGUAGES;
59 Utilities::getLanguageNames(
'en' );
61 $languages = array_keys( $languages );
62 } elseif ( !is_array( $languages ) ) {
63 throw new InvalidArgumentException(
'Invalid input given for $languages' );
71 $index = array_search( $sourceLanguage, $languages );
72 if ( $processAll || $index !==
false ) {
73 unset( $languages[$index] );
74 $this->processLanguage( $group, $sourceLanguage, $changes );
77 foreach ( $languages as $language ) {
78 $this->processLanguage( $group, $language, $changes );
84 protected function processLanguage(
87 $cache = $group->getMessageGroupCache( $language );
89 if ( !$cache->isValid( $reason ) ) {
90 $this->addMessageUpdateChanges( $group, $language, $changes, $reason, $cache );
127 $wiki->filter(
'hastranslation',
false );
128 $wiki->loadTranslations();
129 $wikiKeys = $wiki->getMessageKeys();
133 $ffs = $group->getFFS();
134 if ( $language === $sourceLanguage && !$ffs->exists( $language ) ) {
136 throw new RuntimeException(
"Source message file for {$group->getId()} does not exist: $path" );
139 $file = $ffs->read( $language );
142 if ( $file ===
false ) {
147 if ( !isset( $file[
'MESSAGES'] ) ) {
148 $id = $group->
getId();
149 $ffsClass = get_class( $ffs );
151 error_log(
"$id has an FFS ($ffsClass) - it didn't return cake for $language" );
156 $fileKeys = array_keys( $file[
'MESSAGES'] );
158 $common = array_intersect( $fileKeys, $wikiKeys );
160 $supportsFuzzy = $ffs->supportsFuzzy();
161 $changesToRemove = [];
163 foreach ( $common as $key ) {
164 $sourceContent = $file[
'MESSAGES'][$key];
166 $wikiMessage = $wiki[$key];
167 $wikiContent = $wikiMessage->translation();
171 $wikiContent = str_replace( TRANSLATE_FUZZY,
'', $wikiContent );
174 if ( $supportsFuzzy ===
'yes' && $wikiMessage->hasTag(
'fuzzy' ) ) {
175 $wikiContent = TRANSLATE_FUZZY . $wikiContent;
178 if ( $ffs->isContentEqual( $sourceContent, $wikiContent ) ) {
186 if ( $reason !== MessageGroupCache::NO_CACHE ) {
187 $cacheContent = $cache->get( $key );
194 !$ffs->isContentEqual( $wikiContent, $cacheContent ) &&
195 $ffs->isContentEqual( $sourceContent, $cacheContent )
201 if ( $language !== $sourceLanguage ) {
206 if ( $renameMsg !==
null ) {
211 $this->addNonSourceRenames(
212 $changes, $key, $renameMsg[
'key'], $sourceContent, $wikiContent, $language
214 $changesToRemove[] = $key;
218 $changes->
addChange( $language, $key, $sourceContent );
223 $added = array_diff( $fileKeys, $wikiKeys );
224 foreach ( $added as $key ) {
225 $sourceContent = $file[
'MESSAGES'][$key];
226 $changes->
addAddition( $language, $key, $sourceContent );
233 if ( $reason !== MessageGroupCache::NO_CACHE ) {
234 $deleted = array_diff( $wikiKeys, $fileKeys );
235 foreach ( $deleted as $key ) {
236 if ( $cache->get( $key ) ===
false ) {
241 $changes->
addDeletion( $language, $key, $wiki[$key]->translation() );
245 if ( $language === $sourceLanguage ) {
246 $this->findAndMarkSourceRenames( $changes, $language );
249 $this->checkNonSourceAdditionsForRename(
250 $changes, $sourceLanguage, $language, $wiki, $wikiKeys
264 private function checkNonSourceAdditionsForRename(
268 if ( $additions === [] ) {
272 $additionsToRemove = [];
273 $deletionsToRemove = [];
274 foreach ( $additions as $addedMsg ) {
275 $addedMsgKey = $addedMsg[
'key'];
279 $sourceLanguage, $addedMsgKey, [ MessageSourceChange::RENAME ]
282 if ( $renamedSourceMsg ===
null ) {
288 $deletedSource = $changes->
getMatchedMessage( $sourceLanguage, $renamedSourceMsg[
'key'] );
289 if ( $deletedSource ===
null ) {
292 $deletedMsgKey = $deletedSource[
'key'];
294 $targetLanguage, $deletedMsgKey, [ MessageSourceChange::DELETION ]
300 if ( $deletedMsg ===
null ) {
302 if ( in_array( $deletedMsgKey, $wikiKeys ) ) {
303 $content = $wiki[ $deletedMsgKey ]->translation();
306 'key' => $deletedMsgKey,
307 'content' => $content
311 $similarityPercent = $this->stringComparator->getSimilarity(
312 $addedMsg[
'content'], $deletedMsg[
'content']
316 'key' => $addedMsgKey,
317 'content' => $addedMsg[
'content']
319 'key' => $deletedMsgKey,
320 'content' => $deletedMsg[
'content']
321 ], $similarityPercent );
323 $deletionsToRemove[] = $deletedMsgKey;
324 $additionsToRemove[] = $addedMsgKey;
338 private function findAndMarkSourceRenames(
MessageSourceChange $changes, $sourceLanguage ) {
344 if ( $deletions === [] || $additions === [] ) {
350 $potentialRenames = [];
351 foreach ( $additions as $addedMsg ) {
352 $addedMsgKey = $addedMsg[
'key'];
354 foreach ( $deletions as $deletedMsg ) {
355 $similarityPercent = $this->stringComparator->getSimilarity(
356 $addedMsg[
'content'], $deletedMsg[
'content']
360 $potentialRenames[ $addedMsgKey .
'|' . $deletedMsg[
'key'] ] = $similarityPercent;
365 $this->matchRenames( $changes, $potentialRenames, $sourceLanguage );
377 private function addNonSourceRenames(
382 'content' => $sourceContent
387 'content' => $wikiContent
390 $similarityPercent = $this->stringComparator->getSimilarity(
391 $sourceContent, $wikiContent
393 $changes->
addRename( $language, $addedMsg, $removedMsg, $similarityPercent );
406 private function matchRenames(
MessageSourceChange $changes, array $trackRename, $language ) {
407 arsort( $trackRename, SORT_NUMERIC );
409 $alreadyRenamed = $additionsToRemove = $deletionsToRemove = [];
410 foreach ( $trackRename as $key => $similarityPercent ) {
411 [ $addKey, $deleteKey ] = explode(
'|', $key, 2 );
412 if ( isset( $alreadyRenamed[ $addKey ] ) || isset( $alreadyRenamed[ $deleteKey ] ) ) {
418 $alreadyRenamed[ $addKey ] = 1;
419 $alreadyRenamed[ $deleteKey ] = 1;
421 $addMsg = $changes->
findMessage( $language, $addKey, [ MessageSourceChange::ADDITION ] );
422 $deleteMsg = $changes->
findMessage( $language, $deleteKey, [ MessageSourceChange::DELETION ] );
424 $changes->
addRename( $language, $addMsg, $deleteMsg, $similarityPercent );
427 $additionsToRemove[] = $addMsg[
'key'];
429 $deletionsToRemove[] = $deleteMsg[
'key'];