38 protected string $language;
39 protected string $format;
40 protected string $groupId;
41 private TitleFormatter $titleFormatter;
42 private ParserFactory $parserFactory;
43 private StatusFormatter $statusFormatter;
45 private const VALID_FORMATS = [
'export-as-po',
'export-to-file',
'export-as-csv' ];
47 public function __construct(
48 TitleFormatter $titleFormatter,
49 ParserFactory $parserFactory,
50 FormatterFactory $formatterFactory
52 parent::__construct(
'ExportTranslations' );
53 $this->titleFormatter = $titleFormatter;
54 $this->parserFactory = $parserFactory;
55 $this->statusFormatter = $formatterFactory->getStatusFormatter( $this );
60 $out = $this->getOutput();
61 $request = $this->getRequest();
62 $lang = $this->getLanguage();
66 $this->groupId = $request->getText(
'group', $par ??
'' );
67 $this->language = $request->getVal(
'language', $lang->getCode() );
68 $this->format = $request->getText(
'format' );
71 $out->addModules(
'ext.translate.special.exporttranslations' );
73 if ( $this->groupId ) {
74 $status = $this->checkInput();
75 if ( !$status->isGood() ) {
76 $out->wrapWikiTextAsInterface(
78 $this->statusFormatter->getWikiText( $status )
83 $status = $this->doExport();
84 if ( !$status->isGood() ) {
86 Html::errorBox( $this->statusFormatter->getHTML( $status, [
'lang' => $lang ] ) )
92 private function outputForm(): void {
98 'label-message' =>
'translate-page-group',
99 'options' => $this->getGroupOptions(),
100 'default' => $this->groupId,
105 'name' =>
'language',
107 'label-message' =>
'translate-page-language',
108 'options' => $this->getLanguageOptions(),
109 'default' => $this->language,
115 'label-message' =>
'translate-export-form-format',
117 'options' => $this->getFormatOptions(),
118 'default' => $this->format,
121 HTMLForm::factory(
'ooui', $fields, $this->getContext() )
123 ->setId(
'mw-export-message-group-form' )
124 ->setWrapperLegendMsg(
'translate-page-settings-legend' )
125 ->setSubmitTextMsg(
'translate-submit' )
127 ->displayForm(
false );
130 private function getGroupOptions(): array {
131 $groups = MessageGroups::getAllGroups();
132 uasort( $groups, [ MessageGroups::class,
'groupLabelSort' ] );
135 foreach ( $groups as $id => $group ) {
136 if ( !$group->exists() ) {
140 $options[$group->getLabel()] = $id;
147 private function getLanguageOptions(): array {
148 $languages = Utilities::getLanguageNames(
'en' );
150 foreach ( $languages as $code => $name ) {
151 $options[
"$code - $name"] = $code;
158 private function getFormatOptions(): array {
160 foreach ( self::VALID_FORMATS as $format ) {
162 $options[ $this->msg(
"translate-taskui-$format" )->escaped() ] = $format;
167 private function checkInput(): Status {
168 $status = Status::newGood();
170 $msgGroup = MessageGroups::getGroup( $this->groupId );
171 if ( $msgGroup ===
null ) {
172 $status->fatal(
'translate-page-no-such-group' );
173 } elseif ( MessageGroups::isDynamic( $msgGroup ) ) {
174 $status->fatal(
'translate-export-not-supported' );
177 $langNames = Utilities::getLanguageNames(
'en' );
178 if ( !isset( $langNames[$this->language] ) ) {
179 $status->fatal(
'translate-page-no-such-language' );
188 && !in_array( $this->format, self::VALID_FORMATS )
190 $status->fatal(
'translate-export-invalid-format' );
193 if ( $this->format ===
'export-to-file'
196 $status->fatal(
'translate-export-format-notsupported' );
199 if ( $msgGroup && !MessageGroups::isDynamic( $msgGroup ) ) {
200 $size = count( $msgGroup->getKeys() );
201 if ( $size > self::MAX_EXPORT_SIZE ) {
203 'translate-export-group-too-large',
204 Message::numParam( self::MAX_EXPORT_SIZE )
212 private function doExport(): Status {
213 $out = $this->getOutput();
214 $group = MessageGroups::getGroup( $this->groupId );
215 $collection = $this->setupCollection( $group );
217 switch ( $this->format ) {
223 $fileFormat = $group->getFFS();
226 if ( !$fileFormat instanceof GettextFormat ) {
231 $fileFormat =
new GettextFormat( $group );
234 $fileFormat->setOfflineMode(
true );
236 $filename =
"{$group->getId()}_{$this->language}.po";
237 $this->sendExportHeaders( $filename );
239 echo $fileFormat->writeIntoVariable( $collection );
242 case 'export-to-file':
246 throw new LogicException(
247 "'export-to-file' requested for a non FileBasedMessageGroup {$group->getId()}"
251 $messages = $group->getFFS()->writeIntoVariable( $collection );
253 if ( $messages ===
'' ) {
254 return Status::newFatal(
'translate-export-format-file-empty' );
258 $filename = basename( $group->getSourceFilePath( $collection->getLanguage() ) );
259 $this->sendExportHeaders( $filename );
263 case 'export-as-csv':
265 $filename =
"{$group->getId()}_{$this->language}.csv";
266 $this->sendExportHeaders( $filename );
267 $this->exportCSV( $collection, $group->getSourceLanguage() );
273 return Status::newFatal(
'translate-export-format-notsupported' );
276 $translatablePage = TranslatablePage::newFromTitle( $group->getTitle() );
277 $translationPage = $translatablePage->getTranslationPage( $collection->getLanguage() );
279 $translationPage->filterMessageCollection( $collection );
280 $text = $translationPage->generateSourceFromMessageCollection(
281 $this->parserFactory->getInstance(),
285 $displayTitle = $translatablePage->getPageDisplayTitle( $this->language );
286 if ( $displayTitle ) {
287 $text =
"{{DISPLAYTITLE:$displayTitle}}$text";
290 $box = Html::element(
292 [
'id' =>
'wpTextbox',
'rows' => 40, ],
295 $out->addHTML( $box );
299 return Status::newGood();
302 private function setupCollection(
MessageGroup $group ): MessageCollection {
303 $collection = $group->initCollection( $this->language );
306 $translateDocCode = $this->getConfig()->get(
'TranslateDocumentationLanguageCode' );
307 if ( $this->language !== $translateDocCode
310 $collection->filter( MessageCollection::FILTER_IGNORED, MessageCollection::EXCLUDE_MATCHING );
313 $collection->loadTranslations();
319 private function sendExportHeaders(
string $fileName ): void {
320 $response = $this->getRequest()->response();
321 $response->header(
'Content-Type: text/plain; charset=UTF-8' );
322 $response->header(
"Content-Disposition: attachment; filename=\"$fileName\"" );
325 private function exportCSV( MessageCollection $collection,
string $sourceLanguageCode ): void {
326 $fp = fopen(
'php://output',
'w' );
327 $exportingSourceLanguage = $sourceLanguageCode === $this->language;
330 $this->msg(
'translate-export-csv-message-title' )->text(),
331 $this->msg(
'translate-export-csv-definition' )->text()
334 if ( !$exportingSourceLanguage ) {
335 $header[] = $this->language;
338 fputcsv( $fp, $header );
340 foreach ( $collection->keys() as $messageKey => $titleValue ) {
341 $message = $collection[ $messageKey ];
342 $prefixedTitleText = $this->titleFormatter->getPrefixedText( $titleValue );
344 $handle =
new MessageHandle( Title::newFromText( $prefixedTitleText ) );
345 $sourceLanguageTitle = $handle->getTitleForLanguage( $sourceLanguageCode );
347 $row = [ $sourceLanguageTitle->getPrefixedText(), $message->definition() ];
349 if ( !$exportingSourceLanguage ) {
350 $row[] = $message->translation();
353 fputcsv( $fp, $row );
359 protected function getGroupName() {
360 return 'translation';