21 protected $stringComparator;
25 $this->stringComparator = $stringComparator;
55 if ( $languages === self::ALL_LANGUAGES ) {
60 if ( $languages ===
null ) {
61 $languages = Utilities::getLanguageNames(
'en' );
64 $languages = array_keys( $languages );
65 } elseif ( !is_array( $languages ) ) {
66 throw new InvalidArgumentException(
'Invalid input given for $languages' );
74 $index = array_search( $sourceLanguage, $languages );
75 if ( $processAll || $index !==
false ) {
76 unset( $languages[$index] );
77 $this->processLanguage( $group, $sourceLanguage, $changes );
80 foreach ( $languages as $language ) {
81 $this->processLanguage( $group, $language, $changes );
87 protected function processLanguage(
90 $cache = $group->getMessageGroupCache( $language );
92 if ( !$cache->isValid( $reason ) ) {
93 $this->addMessageUpdateChanges( $group, $language, $changes, $reason, $cache );
130 $wiki->filter(
'hastranslation',
false );
131 $wiki->loadTranslations();
132 $wikiKeys = $wiki->getMessageKeys();
136 $ffs = $group->getFFS();
137 if ( $language === $sourceLanguage && !$ffs->exists( $language ) ) {
139 throw new RuntimeException(
"Source message file for {$group->getId()} does not exist: $path" );
142 $file = $ffs->read( $language );
145 if ( $file ===
false ) {
150 if ( !isset( $file[
'MESSAGES'] ) ) {
151 $id = $group->
getId();
152 $ffsClass = get_class( $ffs );
154 error_log(
"$id has an FFS ($ffsClass) - it didn't return cake for $language" );
159 $fileKeys = array_keys( $file[
'MESSAGES'] );
161 $common = array_intersect( $fileKeys, $wikiKeys );
163 $supportsFuzzy = $ffs->supportsFuzzy();
164 $changesToRemove = [];
166 foreach ( $common as $key ) {
167 $sourceContent = $file[
'MESSAGES'][$key];
169 $wikiMessage = $wiki[$key];
170 $wikiContent = $wikiMessage->translation();
174 $wikiContent = str_replace( TRANSLATE_FUZZY,
'', $wikiContent );
177 if ( $supportsFuzzy ===
'yes' && $wikiMessage->hasTag(
'fuzzy' ) ) {
178 $wikiContent = TRANSLATE_FUZZY . $wikiContent;
181 if ( $ffs->isContentEqual( $sourceContent, $wikiContent ) ) {
189 if ( $reason !== MessageGroupCache::NO_CACHE ) {
190 $cacheContent = $cache->get( $key );
197 !$ffs->isContentEqual( $wikiContent, $cacheContent ) &&
198 $ffs->isContentEqual( $sourceContent, $cacheContent )
204 if ( $language !== $sourceLanguage ) {
209 if ( $renameMsg !==
null ) {
214 $this->addNonSourceRenames(
215 $changes, $key, $renameMsg[
'key'], $sourceContent, $wikiContent, $language
217 $changesToRemove[] = $key;
221 $changes->
addChange( $language, $key, $sourceContent );
226 $added = array_diff( $fileKeys, $wikiKeys );
227 foreach ( $added as $key ) {
228 $sourceContent = $file[
'MESSAGES'][$key];
229 $changes->
addAddition( $language, $key, $sourceContent );
236 if ( $reason !== MessageGroupCache::NO_CACHE ) {
237 $deleted = array_diff( $wikiKeys, $fileKeys );
238 foreach ( $deleted as $key ) {
239 if ( $cache->get( $key ) ===
false ) {
244 $changes->
addDeletion( $language, $key, $wiki[$key]->translation() );
248 if ( $language === $sourceLanguage ) {
249 $this->findAndMarkSourceRenames( $changes, $language );
252 $this->checkNonSourceAdditionsForRename(
253 $changes, $sourceLanguage, $language, $wiki, $wikiKeys
267 private function checkNonSourceAdditionsForRename(
271 if ( $additions === [] ) {
275 $additionsToRemove = [];
276 $deletionsToRemove = [];
277 foreach ( $additions as $addedMsg ) {
278 $addedMsgKey = $addedMsg[
'key'];
282 $sourceLanguage, $addedMsgKey, [ MessageSourceChange::RENAME ]
285 if ( $renamedSourceMsg ===
null ) {
291 $deletedSource = $changes->
getMatchedMessage( $sourceLanguage, $renamedSourceMsg[
'key'] );
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 list( $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'];