Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
ExternalMessageSourceStateImporter.php
1<?php
2declare( strict_types = 1 );
3
12
13use Config;
15use JobQueueGroup;
20use MessageIndex;
22use Psr\Log\LoggerInterface;
23use RuntimeException;
24use Title;
25use function wfWarn;
26
29 private $config;
31 private $groupSynchronizationCache;
33 private $jobQueueGroup;
35 private $logger;
37 private $messageIndex;
38
39 public function __construct(
40 Config $config,
41 GroupSynchronizationCache $groupSynchronizationCache,
42 JobQueueGroup $jobQueueGroup,
43 LoggerInterface $logger,
44 MessageIndex $messageIndex
45 ) {
46 $this->config = $config;
47 $this->groupSynchronizationCache = $groupSynchronizationCache;
48 $this->jobQueueGroup = $jobQueueGroup;
49 $this->logger = $logger;
50 $this->messageIndex = $messageIndex;
51 }
52
58 public function importSafe( array $changeData, string $name ): array {
59 $processed = [];
60 $skipped = [];
61 $jobs = [];
62
63 $groupSyncCacheEnabled = $this->config->get( 'TranslateGroupSynchronizationCache' );
64
65 foreach ( $changeData as $groupId => $changesForGroup ) {
66 $group = MessageGroups::getGroup( $groupId );
67 if ( !$group ) {
68 unset( $changeData[$groupId] );
69 continue;
70 }
71
72 if ( !$group instanceof FileBasedMessageGroup ) {
73 $this->logger->warning(
74 '[ExternalMessageSourceStateImporter] Expected FileBasedMessageGroup, ' .
75 'but got {class} for group {groupId}',
76 [
77 'class' => get_class( $group ),
78 'groupId' => $groupId
79 ]
80 );
81 unset( $changeData[$groupId] );
82 continue;
83 }
84
85 $processed[$groupId] = [];
86 $languages = $changesForGroup->getLanguages();
87 $groupJobs = [];
88
89 $groupSafeLanguages = self::identifySafeLanguages( $group, $changesForGroup );
90
91 foreach ( $languages as $language ) {
92 if ( !$groupSafeLanguages[ $language ] ) {
93 $skipped[$groupId] = true;
94 continue;
95 }
96
97 $additions = $changesForGroup->getAdditions( $language );
98 if ( $additions === [] ) {
99 continue;
100 }
101
102 [ $groupLanguageJobs, $groupProcessed ] = $this->createMessageUpdateJobs(
103 $group, $additions, $language
104 );
105
106 $groupJobs = array_merge( $groupJobs, $groupLanguageJobs );
107 $processed[$groupId][$language] = $groupProcessed;
108
109 $changesForGroup->removeChangesForLanguage( $language );
110 $group->getMessageGroupCache( $language )->create();
111 }
112
113 // Mark the skipped group as in review
114 if ( $groupSyncCacheEnabled && isset( $skipped[$groupId] ) ) {
115 $this->groupSynchronizationCache->markGroupAsInReview( $groupId );
116 }
117
118 if ( $groupJobs !== [] ) {
119 if ( $groupSyncCacheEnabled ) {
120 $this->updateGroupSyncInfo( $groupId, $groupJobs );
121 }
122 $jobs = array_merge( $jobs, $groupJobs );
123 }
124 }
125
126 // Remove groups where everything was imported
127 $changeData = array_filter( $changeData, static function ( MessageSourceChange $change ) {
128 return $change->getAllModifications() !== [];
129 } );
130
131 // Remove groups with no imports
132 $processed = array_filter( $processed );
133
134 $file = MessageChangeStorage::getCdbPath( $name );
135 MessageChangeStorage::writeChanges( $changeData, $file );
136 $this->jobQueueGroup->push( $jobs );
137
138 return [
139 'processed' => $processed,
140 'skipped' => $skipped,
141 'name' => $name,
142 ];
143 }
144
146 private function createMessageUpdateJobs(
148 array $additions,
149 string $language
150 ): array {
151 $groupId = $group->getId();
152 $jobs = [];
153 $processed = 0;
154 foreach ( $additions as $addition ) {
155 $namespace = $group->getNamespace();
156 $name = "{$addition['key']}/$language";
157
158 $title = Title::makeTitleSafe( $namespace, $name );
159 if ( !$title ) {
160 wfWarn( "Invalid title for group $groupId key {$addition['key']}" );
161 continue;
162 }
163
164 $jobs[] = MessageUpdateJob::newJob( $title, $addition['content'] );
165 $processed++;
166 }
167
168 return [ $jobs, $processed ];
169 }
170
175 private function updateGroupSyncInfo( string $groupId, array $groupJobs ): void {
176 $messageParams = [];
177 $groupMessageKeys = [];
178 foreach ( $groupJobs as $job ) {
179 $messageParams[] = MessageUpdateParameter::createFromJob( $job );
180 // Ensure there are no duplicates as the same key may be present in
181 // multiple languages
182 $groupMessageKeys[( new MessageHandle( $job->getTitle() ) )->getKey()] = true;
183 }
184
185 $group = MessageGroups::getGroup( $groupId );
186 if ( $group === null ) {
187 // How did we get here? This should never happen.
188 throw new RuntimeException( "Did not find group $groupId" );
189 }
190
191 $this->messageIndex->storeInterim( $group, array_keys( $groupMessageKeys ) );
192
193 $this->groupSynchronizationCache->addMessages( $groupId, ...$messageParams );
194 $this->groupSynchronizationCache->markGroupForSync( $groupId );
195
196 $this->logger->info(
197 '[ExternalMessageSourceStateImporter] Synchronization started for {groupId}',
198 [ 'groupId' => $groupId ]
199 );
200 }
201
206 private static function identifySafeLanguages(
208 MessageSourceChange $changesForGroup
209 ): array {
210 $sourceLanguage = $group->getSourceLanguage();
211 $safeLanguagesMap = [];
212 $modifiedLanguages = $changesForGroup->getLanguages();
213
214 // Set all languages to not safe to start with.
215 $safeLanguagesMap[ $sourceLanguage ] = false;
216 foreach ( $modifiedLanguages as $language ) {
217 $safeLanguagesMap[ $language ] = false;
218 }
219
220 if ( !$changesForGroup->hasOnly( $sourceLanguage, MessageSourceChange::ADDITION ) ) {
221 return $safeLanguagesMap;
222 }
223
224 $sourceLanguageKeyCache = [];
225 foreach ( $changesForGroup->getAdditions( $sourceLanguage ) as $change ) {
226 if ( $change['content'] === '' ) {
227 return $safeLanguagesMap;
228 }
229
230 $sourceLanguageKeyCache[ $change['key'] ] = true;
231 }
232
233 $safeLanguagesMap[ $sourceLanguage ] = true;
234
235 $groupNamespace = $group->getNamespace();
236
237 // Remove source language from the modifiedLanguage list if present since it's already processed.
238 // The $sourceLanguageKeyCache will only have values if sourceLanguage has safe changes.
239 if ( $sourceLanguageKeyCache ) {
240 array_splice( $modifiedLanguages, array_search( $sourceLanguage, $modifiedLanguages ), 1 );
241 }
242
243 foreach ( $modifiedLanguages as $language ) {
244 if ( !$changesForGroup->hasOnly( $language, MessageSourceChange::ADDITION ) ) {
245 continue;
246 }
247
248 foreach ( $changesForGroup->getAdditions( $language ) as $change ) {
249 if ( $change['content'] === '' ) {
250 continue 2;
251 }
252
253 $msgKey = $change['key'];
254
255 if ( !isset( $sourceLanguageKeyCache[ $msgKey ] ) ) {
256 // This is either a new external translation which is not added in the same sync
257 // as the source language key, or this translation does not have a corresponding
258 // definition. We will check the message index to determine which of the two.
259 $sourceHandle = new MessageHandle( Title::makeTitle( $groupNamespace, $msgKey ) );
260 $sourceLanguageKeyCache[ $msgKey ] = $sourceHandle->isValid();
261 }
262
263 if ( !$sourceLanguageKeyCache[ $msgKey ] ) {
264 continue 2;
265 }
266 }
267
268 $safeLanguagesMap[ $language ] = true;
269 }
270
271 return $safeLanguagesMap;
272 }
273}
This class implements default behavior for file based message groups.
Class is use to track the changes made when importing messages from the remote sources using processM...
getNamespace()
Returns the namespace where messages are placed.
Factory class for accessing message groups individually by id or all of them as an list.
static getGroup( $id)
Fetch a message group by id.
Class for pointing to messages, like Title class is for titles.
Creates a database of keys in all groups, so that namespace and key can be used to get the groups the...
Job for updating translation pages when translation or message definition changes.
Finds external changes for file based message groups.