Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
JavaFormat.php
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\FileFormatSupport;
5
7use MediaWiki\Content\TextContent;
12use RuntimeException;
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 ( str_ends_with( $value, "\\" ) ) {
83 $value = substr( $value, 0, -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 // Just to give an overview of translation quality.
114 if ( $message->hasTag( 'fuzzy' ) ) {
115 $output .= "# Fuzzy\n";
116 }
117
118 $key = $mangler->unmangle( $key );
119 $value = str_replace( TRANSLATE_FUZZY, '', $value );
120 $output .= $this->writeRow( $key, $value );
121 }
122
123 if ( $output ) {
124 return $header . $output;
125 }
126
127 return '';
128 }
129
131 public function writeRow( string $key, string $value ): string {
132 /* Keys containing the separator need escaping. Also escape comment
133 * characters, though strictly they would only need escaping when
134 * they are the first character. Plus the escape character itself. */
135 $key = addcslashes( $key, "#!{$this->keySeparator}\\" );
136 // Make sure we do not slip newlines trough... it would be fatal.
137 $value = str_replace( "\n", '\\n', $value );
138
139 return "$key{$this->keySeparator}$value\n";
140 }
141
146 public function readRow( string $line, string $sep ): array {
147 if ( !str_contains( $line, '\\' ) ) {
148 /* Nothing appears to be escaped in this line.
149 * Just read the key and the value. */
150 [ $key, $value ] = explode( $sep, $line, 2 );
151 } else {
152 /* There might be escaped separators in the key.
153 * Using slower method to find the separator. */
154
155 /* Make the key default to empty instead of value, because
156 * empty key causes error on callers, while empty value
157 * wouldn't. */
158 $key = '';
159 $value = $line;
160
161 /* Find the first unescaped separator. Example:
162 * First line is the string being read, second line is the
163 * value of $escaped after having read the above character.
164 *
165 * ki\ts\\s\=a = koira
166 * 0010010010000
167 * ^ Not separator because $escaped was true
168 * ^ Split the string into key and value here
169 */
170
171 $len = strlen( $line );
172 $escaped = false;
173 for ( $i = 0; $i < $len; $i++ ) {
174 $char = $line[$i];
175 if ( $char === '\\' ) {
176 $escaped = !$escaped;
177 } elseif ( $escaped ) {
178 $escaped = false;
179 } elseif ( $char === $sep ) {
180 $key = substr( $line, 0, $i );
181 // Excluding the separator character from the value
182 $value = substr( $line, $i + 1 );
183 break;
184 }
185 }
186 }
187
188 /* We usually don't want to expand things like \t in values since
189 * translators cannot easily input those. But in keys we do.
190 * \n is exception we do handle in values. */
191 $key = trim( $key );
192 $key = stripcslashes( $key );
193 $value = ltrim( $value );
194 $value = str_replace( '\n', "\n", $value );
195
196 return [ $key, $value ];
197 }
198
199 private function doHeader( MessageCollection $collection ): string {
200 if ( isset( $this->extra['header'] ) ) {
201 $output = $this->extra['header'];
202 } else {
203 global $wgSitename;
204
205 $code = $collection->code;
206 $name = Utilities::getLanguageName( $code );
207 $native = Utilities::getLanguageName( $code, $code );
208 $output = "# Messages for $name ($native)\n";
209 $output .= "# Exported from $wgSitename\n";
210 }
211
212 return $output;
213 }
214
215 private function doAuthors( MessageCollection $collection ): string {
216 $output = '';
217 $authors = $collection->getAuthors();
218 $authors = $this->filterAuthors( $authors, $collection->code );
219
220 foreach ( $authors as $author ) {
221 $output .= "# Author: $author\n";
222 }
223
224 return $output;
225 }
226
227 public static function getExtraSchema(): array {
228 return [
229 'root' => [
230 '_type' => 'array',
231 '_children' => [
232 'FILES' => [
233 '_type' => 'array',
234 '_children' => [
235 'header' => [
236 '_type' => 'text',
237 ],
238 'keySeparator' => [
239 '_type' => 'text',
240 ],
241 ]
242 ]
243 ]
244 ]
245 ];
246 }
247}
248
249class_alias( JavaFormat::class, 'JavaFFS' );
return[ 'Translate:AggregateGroupManager'=> static function(MediaWikiServices $services):AggregateGroupManager { return new AggregateGroupManager($services->getTitleFactory(), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:AggregateGroupMessageGroupFactory'=> static function(MediaWikiServices $services):AggregateGroupMessageGroupFactory { return new AggregateGroupMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'));}, '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:ExternalMessageSourceStateComparator'=> static function(MediaWikiServices $services):ExternalMessageSourceStateComparator { return new ExternalMessageSourceStateComparator(new SimpleStringComparator(), $services->getRevisionLookup(), $services->getPageStore());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance(LogNames::GROUP_SYNCHRONIZATION), $services->get( 'Translate:MessageIndex'), $services->getTitleFactory(), $services->get( 'Translate:MessageGroupSubscription'), new ServiceOptions(ExternalMessageSourceStateImporter::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:FileBasedMessageGroupFactory'=> static function(MediaWikiServices $services):FileBasedMessageGroupFactory { return new FileBasedMessageGroupFactory(new MessageGroupConfigurationParser(), $services->getContentLanguageCode() ->toString(), new ServiceOptions(FileBasedMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, '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:HookDefinedMessageGroupFactory'=> static function(MediaWikiServices $services):HookDefinedMessageGroupFactory { return new HookDefinedMessageGroupFactory( $services->get( 'Translate:HookRunner'));}, 'Translate:HookRunner'=> static function(MediaWikiServices $services):HookRunner { return new HookRunner( $services->getHookContainer());}, 'Translate:MessageBundleDependencyPurger'=> static function(MediaWikiServices $services):MessageBundleDependencyPurger { return new MessageBundleDependencyPurger( $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:MessageBundleMessageGroupFactory'=> static function(MediaWikiServices $services):MessageBundleMessageGroupFactory { return new MessageBundleMessageGroupFactory($services->get( 'Translate:MessageGroupMetadata'), new ServiceOptions(MessageBundleMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore($services->get( 'Translate:RevTagStore'), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:MessageBundleTranslationLoader'=> static function(MediaWikiServices $services):MessageBundleTranslationLoader { return new MessageBundleTranslationLoader( $services->getLanguageFallback());}, 'Translate:MessageGroupMetadata'=> static function(MediaWikiServices $services):MessageGroupMetadata { return new MessageGroupMetadata( $services->getConnectionProvider());}, 'Translate:MessageGroupReviewStore'=> static function(MediaWikiServices $services):MessageGroupReviewStore { return new MessageGroupReviewStore($services->getConnectionProvider(), $services->get( 'Translate:HookRunner'));}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getLinkRenderer(), $services->get( 'Translate:MessageGroupReviewStore'), $services->get( 'Translate:MessageGroupMetadata'), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageGroupSubscription'=> static function(MediaWikiServices $services):MessageGroupSubscription { return new MessageGroupSubscription($services->get( 'Translate:MessageGroupSubscriptionStore'), $services->getJobQueueGroup(), $services->getUserIdentityLookup(), LoggerFactory::getInstance(LogNames::GROUP_SUBSCRIPTION), new ServiceOptions(MessageGroupSubscription::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:MessageGroupSubscriptionHookHandler'=> static function(MediaWikiServices $services):?MessageGroupSubscriptionHookHandler { if(! $services->getExtensionRegistry() ->isLoaded( 'Echo')) { return null;} return new MessageGroupSubscriptionHookHandler($services->get( 'Translate:MessageGroupSubscription'), $services->getUserFactory());}, 'Translate:MessageGroupSubscriptionStore'=> static function(MediaWikiServices $services):MessageGroupSubscriptionStore { return new MessageGroupSubscriptionStore( $services->getConnectionProvider());}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=(array) $services->getMainConfig() ->get( 'TranslateMessageIndex');$class=array_shift( $params);$implementationMap=['HashMessageIndex'=> HashMessageIndex::class, 'CDBMessageIndex'=> CDBMessageIndex::class, 'DatabaseMessageIndex'=> DatabaseMessageIndex::class, 'hash'=> HashMessageIndex::class, 'cdb'=> CDBMessageIndex::class, 'database'=> DatabaseMessageIndex::class,];$messageIndexStoreClass=$implementationMap[$class] ?? $implementationMap['database'];return new MessageIndex(new $messageIndexStoreClass, $services->getMainWANObjectCache(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), LoggerFactory::getInstance(LogNames::MAIN), $services->getMainObjectStash(), $services->getConnectionProvider(), new ServiceOptions(MessageIndex::SERVICE_OPTIONS, $services->getMainConfig()),);}, '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->getConnectionProvider(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:RevTagStore'=> static function(MediaWikiServices $services):RevTagStore { return new RevTagStore( $services->getConnectionProvider());}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleDeleter'=> static function(MediaWikiServices $services):TranslatableBundleDeleter { return new TranslatableBundleDeleter($services->getMainObjectStash(), $services->getJobQueueGroup(), $services->get( 'Translate:SubpageListBuilder'), $services->get( 'Translate:TranslatableBundleFactory'));}, 'Translate:TranslatableBundleExporter'=> static function(MediaWikiServices $services):TranslatableBundleExporter { return new TranslatableBundleExporter($services->get( 'Translate:SubpageListBuilder'), $services->getWikiExporterFactory(), $services->getConnectionProvider());}, '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(), $services->getNamespaceInfo(), $services->getTitleFactory(), $services->getFormatterFactory());}, '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->getConnectionProvider(), $services->getObjectCacheFactory(), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getConnectionProvider() ->getPrimaryDatabase(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageMarker'=> static function(MediaWikiServices $services):TranslatablePageMarker { return new TranslatablePageMarker($services->getConnectionProvider(), $services->getJobQueueGroup(), $services->getLinkRenderer(), MessageGroups::singleton(), $services->get( 'Translate:MessageIndex'), $services->getTitleFormatter(), $services->getTitleParser(), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:TranslatablePageStateStore'), $services->get( 'Translate:TranslationUnitStoreFactory'), $services->get( 'Translate:MessageGroupMetadata'), $services->getWikiPageFactory(), $services->get( 'Translate:TranslatablePageView'), $services->get( 'Translate:MessageGroupSubscription'), $services->getFormatterFactory(), $services->get( 'Translate:HookRunner'),);}, 'Translate:TranslatablePageMessageGroupFactory'=> static function(MediaWikiServices $services):TranslatablePageMessageGroupFactory { return new TranslatablePageMessageGroupFactory(new ServiceOptions(TranslatablePageMessageGroupFactory::SERVICE_OPTIONS, $services->getMainConfig()),);}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStateStore'=> static function(MediaWikiServices $services):TranslatablePageStateStore { return new TranslatablePageStateStore($services->get( 'Translate:PersistentCache'), $services->getPageStore());}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), $services->get( 'Translate:RevTagStore'), $services->getConnectionProvider(), $services->get( 'Translate:TranslatableBundleStatusStore'), $services->get( 'Translate:TranslatablePageParser'), $services->get( 'Translate:MessageGroupMetadata'));}, 'Translate:TranslatablePageView'=> static function(MediaWikiServices $services):TranslatablePageView { return new TranslatablePageView($services->getConnectionProvider(), $services->get( 'Translate:TranslatablePageStateStore'), new ServiceOptions(TranslatablePageView::SERVICE_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslateSandbox'=> static function(MediaWikiServices $services):TranslateSandbox { return new TranslateSandbox($services->getUserFactory(), $services->getConnectionProvider(), $services->getPermissionManager(), $services->getAuthManager(), $services->getUserGroupManager(), $services->getActorStore(), $services->getUserOptionsManager(), $services->getJobQueueGroup(), $services->get( 'Translate:HookRunner'), new ServiceOptions(TranslateSandbox::CONSTRUCTOR_OPTIONS, $services->getMainConfig()));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { return new TranslationStashStorage( $services->getConnectionProvider() ->getPrimaryDatabase());}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory(), $services->getConnectionProvider());}, '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);}, 'Translate:WorkflowStatesMessageGroupLoader'=> static function(MediaWikiServices $services):WorkflowStatesMessageGroupLoader { return new WorkflowStatesMessageGroupLoader(new ServiceOptions(WorkflowStatesMessageGroupLoader::CONSTRUCTOR_OPTIONS, $services->getMainConfig()),);},]
@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:29
Message groups are usually configured in YAML, though the actual storage format does not matter,...