42 private const VALID_FORMATS = [
'export-as-po',
'export-to-file',
'export-as-csv' ];
44 public function __construct() {
45 parent::__construct(
'ExportTranslations' );
50 $out = $this->getOutput();
51 $request = $this->getRequest();
52 $lang = $this->getLanguage();
56 $this->groupId = $request->getText(
'group', $par ??
'' );
57 $this->language = $request->getVal(
'language', $lang->getCode() );
58 $this->format = $request->getText(
'format' );
62 if ( $this->groupId ) {
63 $status = $this->checkInput();
64 if ( !$status->isGood() ) {
65 $out->wrapWikiTextAsInterface(
67 $status->getWikiText(
false,
false, $lang )
76 private function outputForm(): void {
82 'label-message' =>
'translate-page-group',
83 'options' => $this->getGroupOptions(),
84 'default' => $this->groupId,
91 'label-message' =>
'translate-page-language',
92 'options' => $this->getLanguageOptions(),
93 'default' => $this->language,
99 'label-message' =>
'translate-export-form-format',
101 'options' => $this->getFormatOptions(),
102 'default' => $this->format,
105 HTMLForm::factory(
'ooui', $fields, $this->getContext() )
107 ->setWrapperLegendMsg(
'translate-page-settings-legend' )
108 ->setSubmitTextMsg(
'translate-submit' )
110 ->displayForm(
false );
113 private function getGroupOptions(): array {
114 $selected = $this->groupId;
115 $groups = MessageGroups::getAllGroups();
116 uasort( $groups, [ MessageGroups::class,
'groupLabelSort' ] );
119 foreach ( $groups as $id => $group ) {
120 if ( !$group->exists()
121 || ( MessageGroups::getPriority( $group ) ===
'discouraged' && $id !== $selected )
126 $options[$group->getLabel()] = $id;
133 private function getLanguageOptions(): array {
136 foreach ( $languages as $code => $name ) {
137 $options[
"$code - $name"] = $code;
144 private function getFormatOptions(): array {
146 foreach ( self::VALID_FORMATS as $format ) {
148 $options[ $this->msg(
"translate-taskui-$format" )->escaped() ] = $format;
153 private function checkInput(): Status {
154 $status = Status::newGood();
157 if ( $msgGroup ===
null ) {
158 $status->fatal(
'translate-page-no-such-group' );
159 } elseif ( MessageGroups::isDynamic( $msgGroup ) ) {
160 $status->fatal(
'translate-export-not-supported' );
164 if ( !isset( $langNames[$this->language] ) ) {
165 $status->fatal(
'translate-page-no-such-language' );
174 && !in_array( $this->format, self::VALID_FORMATS )
176 $status->fatal(
'translate-export-invalid-format' );
179 if ( $this->format ===
'export-to-file'
182 $status->fatal(
'translate-export-format-notsupported' );
185 if ( $msgGroup && !MessageGroups::isDynamic( $msgGroup ) ) {
186 $size = count( $msgGroup->getKeys() );
187 if ( $size > self::MAX_EXPORT_SIZE ) {
189 'translate-export-group-too-large',
190 Message::numParam( self::MAX_EXPORT_SIZE )
198 private function doExport(): void {
199 $out = $this->getOutput();
201 $collection = $this->setupCollection( $group );
203 switch ( $this->format ) {
209 $ffs = $group->getFFS();
220 $ffs->setOfflineMode(
true );
222 $filename =
"{$group->getId()}_{$this->language}.po";
223 $this->sendExportHeaders( $filename );
225 echo $ffs->writeIntoVariable( $collection );
228 case 'export-to-file':
234 throw new LogicException(
235 "'export-to-file' requested for a non FileBasedMessageGroup {$group->getId()}"
239 $filename = basename( $group->getSourceFilePath( $collection->getLanguage() ) );
240 $this->sendExportHeaders( $filename );
242 echo $group->getFFS()->writeIntoVariable( $collection );
245 case 'export-as-csv':
247 $filename =
"{$group->getId()}_{$this->language}.csv";
248 $this->sendExportHeaders( $filename );
249 $this->exportCSV( $collection, $group->getSourceLanguage() );
258 $translatablePage = TranslatablePage::newFromTitle( $group->getTitle() );
259 $translationPage = $translatablePage->getTranslationPage( $collection->getLanguage() );
261 $translationPage->filterMessageCollection( $collection );
262 $messages = $translationPage->extractMessages( $collection );
263 $text = $translationPage->generateSourceFromTranslations( $messages );
265 $displayTitle = $translatablePage->getPageDisplayTitle( $this->language );
266 if ( $displayTitle ) {
267 $text =
"{{DISPLAYTITLE:$displayTitle}}$text";
270 $box = Html::element(
272 [
'id' =>
'wpTextbox',
'rows' => 40, ],
275 $out->addHTML( $box );
281 $collection = $group->initCollection( $this->language );
284 $translateDocCode = $this->getConfig()->get(
'TranslateDocumentationLanguageCode' );
285 if ( $this->language !== $translateDocCode
288 $collection->filter(
'ignored' );
291 $collection->loadTranslations();
297 private function sendExportHeaders(
string $fileName ): void {
298 $response = $this->getRequest()->response();
299 $response->header(
'Content-Type: text/plain; charset=UTF-8' );
300 $response->header(
"Content-Disposition: attachment; filename=\"$fileName\"" );
303 private function exportCSV(
MessageCollection $collection,
string $sourceLanguageCode ): void {
304 $fp = fopen(
'php://output',
'w' );
305 $exportingSourceLanguage = $sourceLanguageCode === $this->language;
308 $this->msg(
'translate-export-csv-message-title' )->text(),
309 $this->msg(
'translate-export-csv-definition' )->text()
312 if ( !$exportingSourceLanguage ) {
313 $header[] = $this->language;
316 fputcsv( $fp, $header );
318 $titleFormatter = MediaWikiServices::getInstance()->getTitleFormatter();
320 foreach ( $collection->
keys() as $messageKey => $titleValue ) {
321 $message = $collection[ $messageKey ];
322 $prefixedTitleText = $titleFormatter->getPrefixedText( $titleValue );
324 $handle =
new MessageHandle( Title::newFromText( $prefixedTitleText ) );
325 $sourceLanguageTitle = $handle->getTitleForLanguage( $sourceLanguageCode );
327 $row = [ $sourceLanguageTitle->getPrefixedText(), $message->definition() ];
329 if ( !$exportingSourceLanguage ) {
330 $row[] = $message->translation();
333 fputcsv( $fp, $row );
339 protected function getGroupName() {
340 return 'translation';
New-style FFS class that implements support for gettext file format.
Wraps the translatable page sections into a message group.