19 protected $stringComparator;
23 $this->stringComparator = $stringComparator;
53 if ( $languages === self::ALL_LANGUAGES ) {
58 if ( $languages ===
null ) {
59 $languages = TranslateUtils::getLanguageNames(
'en' );
62 $languages = array_keys( $languages );
63 } elseif ( !is_array( $languages ) ) {
64 throw new InvalidArgumentException(
'Invalid input given for $languages' );
72 $index = array_search( $sourceLanguage, $languages );
73 if ( $processAll || $index !==
false ) {
74 unset( $languages[$index] );
75 $this->processLanguage( $group, $sourceLanguage, $changes );
78 foreach ( $languages as $language ) {
79 $this->processLanguage( $group, $language, $changes );
85 protected function processLanguage(
88 $cache = $group->getMessageGroupCache( $language );
90 if ( !$cache->isValid( $reason ) ) {
128 $wiki->filter(
'hastranslation',
false );
129 $wiki->loadTranslations();
130 $wikiKeys = $wiki->getMessageKeys();
134 $ffs = $group->getFFS();
135 if ( $language === $sourceLanguage && !$ffs->exists( $language ) ) {
137 throw new RuntimeException(
"Source message file for {$group->getId()} does not exist: $path" );
140 $file = $ffs->read( $language );
143 if ( $file ===
false ) {
148 if ( !isset( $file[
'MESSAGES'] ) ) {
149 $id = $group->
getId();
150 $ffsClass = get_class( $ffs );
152 error_log(
"$id has an FFS ($ffsClass) - it didn't return cake for $language" );
157 $fileKeys = array_keys( $file[
'MESSAGES'] );
159 $common = array_intersect( $fileKeys, $wikiKeys );
161 $supportsFuzzy = $ffs->supportsFuzzy();
162 $changesToRemove = [];
164 foreach ( $common as $key ) {
165 $sourceContent = $file[
'MESSAGES'][$key];
167 $wikiMessage = $wiki[$key];
168 $wikiContent = $wikiMessage->translation();
172 $wikiContent = str_replace( TRANSLATE_FUZZY,
'', $wikiContent );
175 if ( $supportsFuzzy ===
'yes' && $wikiMessage->hasTag(
'fuzzy' ) ) {
176 $wikiContent = TRANSLATE_FUZZY . $wikiContent;
179 if ( $ffs->isContentEqual( $sourceContent, $wikiContent ) ) {
187 if ( $reason !== MessageGroupCache::NO_CACHE ) {
188 $cacheContent = $cache->get( $key );
195 !$ffs->isContentEqual( $wikiContent, $cacheContent ) &&
196 $ffs->isContentEqual( $sourceContent, $cacheContent )
202 if ( $language !== $sourceLanguage ) {
207 if ( $renameMsg !==
null ) {
212 $this->addNonSourceRenames(
213 $changes, $key, $renameMsg[
'key'], $sourceContent, $wikiContent, $language
215 $changesToRemove[] = $key;
219 $changes->
addChange( $language, $key, $sourceContent );
224 $added = array_diff( $fileKeys, $wikiKeys );
225 foreach ( $added as $key ) {
226 $sourceContent = $file[
'MESSAGES'][$key];
227 $changes->
addAddition( $language, $key, $sourceContent );
234 if ( $reason !== MessageGroupCache::NO_CACHE ) {
235 $deleted = array_diff( $wikiKeys, $fileKeys );
236 foreach ( $deleted as $key ) {
237 if ( $cache->get( $key ) ===
false ) {
242 $changes->
addDeletion( $language, $key, $wiki[$key]->translation() );
246 if ( $language === $sourceLanguage ) {
247 $this->findAndMarkSourceRenames( $changes, $language );
250 $this->checkNonSourceAdditionsForRename(
251 $changes, $sourceLanguage, $language, $wiki, $wikiKeys
265 private function checkNonSourceAdditionsForRename(
269 if ( $additions === [] ) {
273 $additionsToRemove = [];
274 $deletionsToRemove = [];
275 foreach ( $additions as $addedMsg ) {
276 $addedMsgKey = $addedMsg[
'key'];
280 $sourceLanguage, $addedMsgKey, [ MessageSourceChange::RENAME ]
283 if ( $renamedSourceMsg ===
null ) {
289 $deletedSource = $changes->
getMatchedMessage( $sourceLanguage, $renamedSourceMsg[
'key'] );
290 $deletedMsgKey = $deletedSource[
'key'];
292 $targetLanguage, $deletedMsgKey, [ MessageSourceChange::DELETION ]
298 if ( $deletedMsg ===
null ) {
300 if ( array_search( $deletedMsgKey, $wikiKeys ) !==
false ) {
301 $content = $wiki[ $deletedMsgKey ]->translation();
304 'key' => $deletedMsgKey,
305 'content' => $content
309 $similarityPercent = $this->stringComparator->getSimilarity(
310 $addedMsg[
'content'], $deletedMsg[
'content']
314 'key' => $addedMsgKey,
315 'content' => $addedMsg[
'content']
317 'key' => $deletedMsgKey,
318 'content' => $deletedMsg[
'content']
319 ], $similarityPercent );
321 $deletionsToRemove[] = $deletedMsgKey;
322 $additionsToRemove[] = $addedMsgKey;
336 private function findAndMarkSourceRenames(
MessageSourceChange $changes, $sourceLanguage ) {
342 if ( $deletions === [] || $additions === [] ) {
348 $potentialRenames = [];
349 foreach ( $additions as $addedMsg ) {
350 $addedMsgKey = $addedMsg[
'key'];
352 foreach ( $deletions as $deletedMsg ) {
353 $similarityPercent = $this->stringComparator->getSimilarity(
354 $addedMsg[
'content'], $deletedMsg[
'content']
358 $potentialRenames[ $addedMsgKey .
'|' . $deletedMsg[
'key'] ] = $similarityPercent;
363 $this->matchRenames( $changes, $potentialRenames, $sourceLanguage );
375 private function addNonSourceRenames(
380 'content' => $sourceContent
385 'content' => $wikiContent
388 $similarityPercent = $this->stringComparator->getSimilarity(
389 $sourceContent, $wikiContent
391 $changes->
addRename( $language, $addedMsg, $removedMsg, $similarityPercent );
404 private function matchRenames(
MessageSourceChange $changes, array $trackRename, $language ) {
405 arsort( $trackRename, SORT_NUMERIC );
407 $alreadyRenamed = $additionsToRemove = $deletionsToRemove = [];
408 foreach ( $trackRename as $key => $similarityPercent ) {
409 list( $addKey, $deleteKey ) = explode(
'|', $key, 2 );
410 if ( isset( $alreadyRenamed[ $addKey ] ) || isset( $alreadyRenamed[ $deleteKey ] ) ) {
416 $alreadyRenamed[ $addKey ] = 1;
417 $alreadyRenamed[ $deleteKey ] = 1;
419 $addMsg = $changes->
findMessage( $language, $addKey, [ MessageSourceChange::ADDITION ] );
420 $deleteMsg = $changes->
findMessage( $language, $deleteKey, [ MessageSourceChange::DELETION ] );
422 $changes->
addRename( $language, $addMsg, $deleteMsg, $similarityPercent );
425 $additionsToRemove[] = $addMsg[
'key'];
427 $deletionsToRemove[] = $deleteMsg[
'key'];