25 private $wikiPageFactory;
27 public function __construct( WikiPageFactory $wikiPageFactory ) {
28 $this->wikiPageFactory = $wikiPageFactory;
32 public function parseFile(
string $csvFilePath ): Status {
33 if ( !file_exists( $csvFilePath ) || !is_file( $csvFilePath ) ) {
34 return Status::newFatal(
35 "CSV file path '$csvFilePath' does not exist, is not readable or is not a file"
39 $indexedLanguageCodes = [];
40 $currentRowCount = -1;
43 'emptyTitleRows' => [],
44 'invalidTitleRows' => [],
45 'groupNotFoundRows' => []
48 $csvFileContent =
new SplFileObject( $csvFilePath,
'r' );
49 while ( !$csvFileContent->eof() ) {
54 $csvRow = $csvFileContent->fgetcsv();
55 if ( $this->isCsvRowEmpty( $csvRow ) ) {
59 if ( $currentRowCount === 0 ) {
61 $status = $this->getLanguagesFromHeader( $csvRow );
62 if ( !$status->isGood() ) {
66 $indexedLanguageCodes = $status->getValue();
70 $rowData = [
'translations' => [] ];
71 $messageTitle = isset( $csvRow[0] ) ? trim( $csvRow[0] ) : null;
72 if ( !$messageTitle ) {
73 $invalidRows[
'emptyTitleRows'][] = $currentRowCount + 1;
77 $handle = $this->getMessageHandleIfValid( $messageTitle );
78 if ( $handle ===
null ) {
79 $invalidRows[
'invalidTitleRows'][] = $currentRowCount + 1;
84 $group = $handle->getGroup();
86 $invalidRows[
'groupNotFoundRows'][] = $currentRowCount + 1;
90 $sourceLanguage = $group->getSourceLanguage();
92 $rowData[
'messageTitle'] = $messageTitle;
93 foreach ( $indexedLanguageCodes as $languageCode => $index ) {
94 if ( $sourceLanguage === $languageCode ) {
98 $rowData[
'translations'][$languageCode] = $csvRow[$index] ??
null;
100 $importData[] = $rowData;
103 $status =
new Status();
104 if ( $invalidRows[
'emptyTitleRows'] ) {
106 'Empty message titles found on row(s): ' . implode(
',', $invalidRows[
'emptyTitleRows'] )
110 if ( $invalidRows[
'invalidTitleRows'] ) {
112 'Invalid message title(s) found on row(s): ' . implode(
',', $invalidRows[
'invalidTitleRows'] )
116 if ( $invalidRows[
'groupNotFoundRows'] ) {
118 'Group not found for message(s) on row(s) ' . implode(
',', $invalidRows[
'invalidTitleRows'] )
122 if ( !$status->isGood() ) {
126 return Status::newGood( $importData );
131 array $messagesWithTranslations,
132 Authority $authority,
134 ?callable $progressReporter =
null
136 $commentStoreComment = CommentStoreComment::newUnsavedComment( $comment );
139 $importStatus =
new Status();
140 $failedStatuses = [];
141 $currentTranslation = 0;
142 foreach ( $messagesWithTranslations as $messageTranslation ) {
143 $messageTitleText = $messageTranslation[
'messageTitle'];
144 $messageTitle = Title::newFromText( $messageTitleText );
147 $translationImportStatuses = [];
150 $translations = $messageTranslation[
'translations'];
151 foreach ( $translations as $languageCode => $translation ) {
153 if ( $translation ===
null || trim( $translation ) ===
'' ) {
157 $translationTitle = $messageHandle->getTitleForLanguage( $languageCode );
160 $updater = $this->wikiPageFactory->newFromTitle( $translationTitle )
161 ->newPageUpdater( $authority );
162 $content = ContentHandler::makeContent( $translation, $translationTitle );
163 $updater->setContent( SlotRecord::MAIN, $content );
164 $updater->setFlags( EDIT_FORCE_BOT );
165 $updater->saveRevision( $commentStoreComment );
167 $status = $updater->getStatus();
168 $translationImportStatuses[] = $status;
169 if ( !$status->isOK() ) {
170 $failedStatuses[ $translationTitle->getPrefixedText() ] = $status;
174 ++$currentTranslation;
175 if ( $progressReporter ) {
179 $translationImportStatuses,
180 count( $messagesWithTranslations ),
186 if ( $failedStatuses ) {
187 foreach ( $failedStatuses as $failedStatus ) {
188 $importStatus->merge( $failedStatus );
191 $importStatus->setResult(
false, $failedStatuses );
194 return $importStatus;
197 private function getLanguagesFromHeader( array $csvHeader ): Status {
198 if ( count( $csvHeader ) < 2 ) {
199 return Status::newFatal(
200 'CSV has < 2 columns. Assuming that there are no languages to import'
204 $languageCodesInHeader = array_slice( $csvHeader, 2 );
205 if ( $languageCodesInHeader === [] ) {
206 return Status::newFatal(
'No languages found for import' );
209 $invalidLanguageCodes = [];
210 $indexedLanguageCodes = [];
212 $originalLanguageIndex = 2;
213 foreach ( $languageCodesInHeader as $languageCode ) {
214 if ( !TranslateUtils::isSupportedLanguageCode( strtolower( $languageCode ) ) ) {
215 $invalidLanguageCodes[] = $languageCode;
218 $indexedLanguageCodes[ strtolower( $languageCode ) ] = $originalLanguageIndex;
220 ++$originalLanguageIndex;
223 if ( $invalidLanguageCodes ) {
224 return Status::newFatal(
225 'Invalid language codes detected in CSV header: ' . implode(
', ', $invalidLanguageCodes )
229 return Status::newGood( $indexedLanguageCodes );
232 private function getMessageHandleIfValid(
string $messageTitle ): ?
MessageHandle {
233 $title = Title::newFromText( $messageTitle );
234 if ( $title ===
null ) {
239 if ( $handle->isValid() ) {
246 private function isCsvRowEmpty( array $csvRow ): bool {
247 return count( $csvRow ) === 1 && ( $csvRow[0] === null || trim( $csvRow[0] ) ===
'' );
return[ 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), MessageIndex::singleton());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), new RevTagStore(), $services->getDBLoadBalancer());}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array