MediaWiki master
importExtensionMessages.php
Go to the documentation of this file.
1<?php
2
6
7// @codeCoverageIgnoreStart
8require_once __DIR__ . '/../Maintenance.php';
9// @codeCoverageIgnoreEnd
10
13 private $extensionDir;
15 private $extName;
17 private $excludedMsgs;
19 private $outDir;
21 private $coreDataCache;
22
23 public function __construct() {
24 parent::__construct();
25 $this->addArg( 'extension', 'The extension name' );
26 $this->addOption( 'outdir',
27 'The output directory, default MW_INSTALL_PATH/languages/i18n', false, true );
28 }
29
30 public function execute() {
31 $this->init();
32
33 $this->outDir = $this->getOption( 'outdir', MW_INSTALL_PATH . '/languages/i18n' );
34 if ( !file_exists( $this->outDir ) ) {
35 mkdir( $this->outDir, 0777, true );
36 }
37
38 $this->extName = $this->getArg();
39 $extJsonPath = $this->extensionDir . "/{$this->extName}/extension.json";
40 $extJson = file_get_contents( $extJsonPath );
41 if ( $extJson === false ) {
42 $this->fatalError( "Unable to open \"$extJsonPath\"" );
43 }
44 $extData = json_decode( $extJson, true, flags: JSON_THROW_ON_ERROR );
45 if ( !is_array( $extData ) || array_is_list( $extData ) ) {
46 $this->fatalError( "\"$extJsonPath\" contains unexpected data, expected a JSON object" );
47 }
48
49 $this->excludedMsgs = [];
50 foreach ( [ 'namemsg', 'descriptionmsg' ] as $key ) {
51 if ( isset( $extData[$key] ) ) {
52 // @phan-suppress-next-line PhanTypeMismatchProperty False positive, see T436482
53 $this->excludedMsgs[] = $extData[$key];
54 }
55 }
56
57 foreach ( $this->getMessagesDirs( $extData ) as $dir ) {
58 $this->processDir( $dir );
59 }
60 }
61
62 private function init() {
63 $services = $this->getServiceContainer();
64 $config = $services->getMainConfig();
65 $this->extensionDir = $config->get( MainConfigNames::ExtensionDirectory );
66 }
67
68 private function getMessagesDirs( array $extData ): array {
69 if ( isset( $extData['MessagesDirs'] ) ) {
70 $messagesDirs = [];
71 foreach ( $extData['MessagesDirs'] as $dirs ) {
72 if ( is_array( $dirs ) ) {
73 foreach ( $dirs as $dir ) {
74 $messagesDirs[] = $dir;
75 }
76 } else {
77 $messagesDirs[] = $dirs;
78 }
79 }
80 } else {
81 $messagesDirs = [ 'i18n' ];
82 }
83 return $messagesDirs;
84 }
85
86 private function processDir( string $dir ) {
87 $path = $this->extensionDir . "/{$this->extName}/$dir";
88
89 foreach ( new DirectoryIterator( $path ) as $file ) {
90 if ( !$file->isDot() && str_ends_with( $file->getFilename(), '.json' ) ) {
91 $this->processFile(
92 substr( $file->getFilename(), 0, -5 ),
93 $file->getPathname()
94 );
95 }
96 }
97 }
98
99 private function processFile( string $lang, string $extI18nPath ) {
100 $extJson = file_get_contents( $extI18nPath );
101 if ( $extJson === false ) {
102 $this->error( "Unable to read i18n file \"$extI18nPath\"" );
103 return;
104 }
105 $extData = json_decode( $extJson, true, flags: JSON_THROW_ON_ERROR );
106 $coreData = $this->getCoreData( $lang );
107
108 if ( isset( $extData['@metadata']['authors'] ) ) {
109 $authors = array_unique( array_merge(
110 $coreData['@metadata']['authors'] ?? [],
111 $extData['@metadata']['authors']
112 ) );
113 // Fix numeric authors
114 foreach ( $authors as &$author ) {
115 $author = (string)$author;
116 }
117 sort( $authors );
118 $coreData['@metadata']['authors'] = $authors;
119 }
120
121 foreach ( $extData as $name => $value ) {
122 if ( str_starts_with( $name, '@' ) ) {
123 continue;
124 }
125 if ( in_array( $name, $this->excludedMsgs ) ) {
126 continue;
127 }
128 $coreData[$name] = $value;
129 }
130
131 $this->setCoreData( $lang, $coreData );
132 }
133
135 private function getCoreData( string $lang ) {
136 if ( !isset( $this->coreDataCache[$lang] ) ) {
137 $corePath = MW_INSTALL_PATH . "/languages/i18n/$lang.json";
138 // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
139 $coreJson = @file_get_contents( $corePath );
140 if ( $coreJson === false ) {
141 $this->error( "Warning: discarding extension localisation " .
142 "for language \"$lang\" not present in core" );
143 // Do not write to coreDataCache -- suppress creation of the core file.
144 return [];
145 }
146 $this->coreDataCache[$lang] = json_decode( $coreJson, true, flags: JSON_THROW_ON_ERROR );
147 }
148 return $this->coreDataCache[$lang];
149 }
150
155 private function setCoreData( string $lang, $data ) {
156 if ( !isset( $this->coreDataCache[$lang] ) ) {
157 // Non-existent file, do not create
158 return;
159 }
160
161 $this->coreDataCache[$lang] = $data;
162 $outPath = "{$this->outDir}/$lang.json";
163 if ( !file_put_contents(
164 $outPath,
165 FormatJson::encode( $data, "\t", FormatJson::ALL_OK ) . "\n"
166 ) ) {
167 $this->error( "Unable to write core i18n file \"$outPath\"" );
168 }
169 }
170}
171
172// @codeCoverageIgnoreStart
173$maintClass = ImportExtensionMessages::class;
174require_once RUN_MAINTENANCE_IF_MAIN;
175// @codeCoverageIgnoreEnd
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
__construct()
Default constructor.
JSON formatter wrapper class.
A class containing constants representing the names of configuration variables.
Abstract maintenance class for quickly writing and churning out maintenance scripts with minimal effo...
addArg( $arg, $description, $required=true, $multi=false)
Add some args that are needed.
getArg( $argId=0, $default=null)
Get an argument.
fatalError( $msg, $exitCode=1)
Output a message and terminate the current script.
addOption( $name, $description, $required=false, $withArg=false, $shortName=false, $multiOccurrence=false)
Add a parameter to the script.
getOption( $name, $default=null)
Get an option, or return the default.
getServiceContainer()
Returns the main service container.