Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
JavaFormat.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\FileFormatSupport;
5
11use RuntimeException;
12use TextContent;
13
24
25 private string $keySeparator;
26
27 public function __construct( FileBasedMessageGroup $group ) {
28 parent::__construct( $group );
29 $this->keySeparator = $this->extra['keySeparator'] ?? '=';
30 }
31
32 public function supportsFuzzy(): string {
33 return 'write';
34 }
35
36 public function getFileExtensions(): array {
37 return [ '.properties' ];
38 }
39
41 public function readFromVariable( string $data ): array {
42 $data = TextContent::normalizeLineEndings( $data );
43 $lines = array_map( 'ltrim', explode( "\n", $data ) );
44 $authors = $messages = [];
45 $lineContinuation = false;
46
47 $key = '';
48 $value = '';
49 foreach ( $lines as $line ) {
50 if ( $lineContinuation ) {
51 $lineContinuation = false;
52 $valuecont = $line;
53 $valuecont = str_replace( '\n', "\n", $valuecont );
54 $value .= $valuecont;
55 } else {
56 if ( $line === '' ) {
57 continue;
58 }
59
60 if ( $line[0] === '#' || $line[0] === '!' ) {
61 $match = [];
62 $ok = preg_match( '/#\s*Author:\s*(.*)/', $line, $match );
63
64 if ( $ok ) {
65 $authors[] = $match[1];
66 }
67
68 continue;
69 }
70
71 if ( !str_contains( $line, $this->keySeparator ) ) {
72 throw new RuntimeException( "Line without separator '{$this->keySeparator}': $line." );
73 }
74
75 [ $key, $value ] = $this->readRow( $line, $this->keySeparator );
76 if ( $key === '' ) {
77 throw new RuntimeException( "Empty key in line $line." );
78 }
79 }
80
81 // @todo This doesn't handle the pathological case of even number of trailing \
82 if ( strlen( $value ) && $value[strlen( $value ) - 1] === "\\" ) {
83 $value = substr( $value, 0, strlen( $value ) - 1 );
84 $lineContinuation = true;
85 } else {
86 $messages[$key] = ltrim( $value );
87 }
88 }
89
90 $messages = $this->group->getMangler()->mangleArray( $messages );
91
92 return [
93 'AUTHORS' => $authors,
94 'MESSAGES' => $messages,
95 ];
96 }
97
98 protected function writeReal( MessageCollection $collection ): string {
99 $header = $this->doHeader( $collection );
100 $header .= $this->doAuthors( $collection );
101 $header .= "\n";
102
103 $output = '';
104 $mangler = $this->group->getMangler();
105
107 foreach ( $collection as $key => $message ) {
108 $value = $message->translation() ?? '';
109 if ( $value === '' ) {
110 continue;
111 }
112
113 $value = str_replace( TRANSLATE_FUZZY, '', $value );
114
115 // Just to give an overview of translation quality.
116 if ( $message->hasTag( 'fuzzy' ) ) {
117 $output .= "# Fuzzy\n";
118 }
119
120 $key = $mangler->unmangle( $key );
121 $output .= $this->writeRow( $key, $value );
122 }
123
124 if ( $output ) {
125 return $header . $output;
126 }
127
128 return '';
129 }
130
132 public function writeRow( string $key, string $value ): string {
133 /* Keys containing the separator need escaping. Also escape comment
134 * characters, though strictly they would only need escaping when
135 * they are the first character. Plus the escape character itself. */
136 $key = addcslashes( $key, "#!{$this->keySeparator}\\" );
137 // Make sure we do not slip newlines trough... it would be fatal.
138 $value = str_replace( "\n", '\\n', $value );
139
140 return "$key{$this->keySeparator}$value\n";
141 }
142
147 public function readRow( string $line, string $sep ): array {
148 if ( !str_contains( $line, '\\' ) ) {
149 /* Nothing appears to be escaped in this line.
150 * Just read the key and the value. */
151 [ $key, $value ] = explode( $sep, $line, 2 );
152 } else {
153 /* There might be escaped separators in the key.
154 * Using slower method to find the separator. */
155
156 /* Make the key default to empty instead of value, because
157 * empty key causes error on callers, while empty value
158 * wouldn't. */
159 $key = '';
160 $value = $line;
161
162 /* Find the first unescaped separator. Example:
163 * First line is the string being read, second line is the
164 * value of $escaped after having read the above character.
165 *
166 * ki\ts\\s\=a = koira
167 * 0010010010000
168 * ^ Not separator because $escaped was true
169 * ^ Split the string into key and value here
170 */
171
172 $len = strlen( $line );
173 $escaped = false;
174 for ( $i = 0; $i < $len; $i++ ) {
175 $char = $line[$i];
176 if ( $char === '\\' ) {
177 $escaped = !$escaped;
178 } elseif ( $escaped ) {
179 $escaped = false;
180 } elseif ( $char === $sep ) {
181 $key = substr( $line, 0, $i );
182 // Excluding the separator character from the value
183 $value = substr( $line, $i + 1 );
184 break;
185 }
186 }
187 }
188
189 /* We usually don't want to expand things like \t in values since
190 * translators cannot easily input those. But in keys we do.
191 * \n is exception we do handle in values. */
192 $key = trim( $key );
193 $key = stripcslashes( $key );
194 $value = ltrim( $value );
195 $value = str_replace( '\n', "\n", $value );
196
197 return [ $key, $value ];
198 }
199
200 private function doHeader( MessageCollection $collection ): string {
201 if ( isset( $this->extra['header'] ) ) {
202 $output = $this->extra['header'];
203 } else {
204 global $wgSitename;
205
206 $code = $collection->code;
207 $name = Utilities::getLanguageName( $code );
208 $native = Utilities::getLanguageName( $code, $code );
209 $output = "# Messages for $name ($native)\n";
210 $output .= "# Exported from $wgSitename\n";
211 }
212
213 return $output;
214 }
215
216 private function doAuthors( MessageCollection $collection ): string {
217 $output = '';
218 $authors = $collection->getAuthors();
219 $authors = $this->filterAuthors( $authors, $collection->code );
220
221 foreach ( $authors as $author ) {
222 $output .= "# Author: $author\n";
223 }
224
225 return $output;
226 }
227
228 public static function getExtraSchema(): array {
229 return [
230 'root' => [
231 '_type' => 'array',
232 '_children' => [
233 'FILES' => [
234 '_type' => 'array',
235 '_children' => [
236 'header' => [
237 '_type' => 'text',
238 ],
239 'keySeparator' => [
240 '_type' => 'text',
241 ],
242 ]
243 ]
244 ]
245 ]
246 ];
247 }
248}
249
250class_alias( JavaFormat::class, 'JavaFFS' );
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'), $services->get( 'Translate:MessageIndex'));}, 'Translate:FileFormatFactory'=> static function(MediaWikiServices $services):FileFormatFactory { return new FileFormatFactory( $services->getObjectFactory());}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getDBLoadBalancer(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, '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:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, '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:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore($services->getDBLoadBalancerFactory());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getDBLoadBalancer());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleImporter'=> static function(MediaWikiServices $services):TranslatableBundleImporter { return new TranslatableBundleImporter($services->getWikiImporterFactory(), $services->get( 'Translate:TranslatablePageParser'), $services->getRevisionLookup());}, '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:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, '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(), $services->get( 'Translate:RevTagStore'), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'),);}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnection(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(), $services->getDBLoadBalancer());}, '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
This class implements default behavior for file based message groups.
JavaFormat class implements support for Java properties files.
readRow(string $line, string $sep)
Parses non-empty properties file row to key and value.
supportsFuzzy()
Query the capabilities of this FFS.
static getExtraSchema()
Return a data structure that will be merged with the base schema.
writeRow(string $key, string $value)
Writes well-formed properties file row with key and value.
getFileExtensions()
Return the commonly used file extensions for these formats.
A very basic FileFormatSupport module that implements some basic functionality and a simple binary ba...
This file contains the class for core message collections implementation.
Interface for message objects used by MessageCollection.
Definition Message.php:13
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:31
Message groups are usually configured in YAML, though the actual storage format does not matter,...