Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
59.21% covered (warning)
59.21%
45 / 76
22.22% covered (danger)
22.22%
2 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
JsonFormat
60.00% covered (warning)
60.00%
45 / 75
22.22% covered (danger)
22.22%
2 / 9
49.22
0.00% covered (danger)
0.00%
0 / 1
 isValid
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getFileExtensions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 readFromVariable
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
4.00
 writeReal
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
6.01
 generateFile
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
3.01
 getFlattener
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
 isContentEqual
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getExtraSchema
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\Translate\FileFormatSupport;
5
6use FileBasedMessageGroup;
7use MediaWiki\Extension\Translate\MessageLoading\Message;
8use MediaWiki\Extension\Translate\MessageLoading\MessageCollection;
9use MediaWiki\Extension\Translate\MessageProcessing\ArrayFlattener;
10use MediaWiki\Json\FormatJson;
11
12/**
13 * JsonFormat implements a message format where messages are encoded
14 * as key-value pairs in JSON objects. The format is extended to
15 * support author information under the special @metadata key.
16 *
17 * @author Niklas Laxström
18 * @license GPL-2.0-or-later
19 * @ingroup FileFormatSupport
20 */
21class JsonFormat extends SimpleFormat {
22    private ?ArrayFlattener $flattener;
23
24    public static function isValid( string $data ): bool {
25        return is_array( FormatJson::decode( $data, /*as array*/true ) );
26    }
27
28    public function __construct( FileBasedMessageGroup $group ) {
29        parent::__construct( $group );
30        $this->flattener = $this->getFlattener();
31    }
32
33    public function getFileExtensions(): array {
34        return [ '.json' ];
35    }
36
37    /** @return array Parsed data. */
38    public function readFromVariable( string $data ): array {
39        $messages = (array)FormatJson::decode( $data, /*as array*/true );
40        $authors = [];
41        $metadata = [];
42
43        if ( isset( $messages['@metadata']['authors'] ) ) {
44            $authors = (array)$messages['@metadata']['authors'];
45            unset( $messages['@metadata']['authors'] );
46        }
47
48        if ( isset( $messages['@metadata'] ) ) {
49            $metadata = $messages['@metadata'];
50        }
51
52        unset( $messages['@metadata'] );
53
54        if ( $this->flattener ) {
55            $messages = $this->flattener->flatten( $messages );
56        }
57
58        $messages = $this->group->getMangler()->mangleArray( $messages );
59
60        return [
61            'MESSAGES' => $messages,
62            'AUTHORS' => $authors,
63            'EXTRA' => [ 'METADATA' => $metadata ],
64        ];
65    }
66
67    protected function writeReal( MessageCollection $collection ): string {
68        $template = $this->read( $collection->getLanguage() ) ?: [];
69        $authors = $this->filterAuthors( $collection->getAuthors(), $collection->getLanguage() );
70        $messages = [];
71
72        /** @var Message $m */
73        foreach ( $collection as $key => $m ) {
74            $value = $m->translation();
75            if ( $value === null ) {
76                continue;
77            }
78
79            if ( $m->hasTag( 'fuzzy' ) ) {
80                $value = str_replace( TRANSLATE_FUZZY, '', $value );
81            }
82
83            $messages[$key] = $value;
84        }
85
86        // Do not create files without translations
87        if ( $messages === [] ) {
88            return '';
89        }
90
91        $template['MESSAGES'] = $messages;
92        $template['AUTHORS'] = $authors;
93
94        return $this->generateFile( $template );
95    }
96
97    public function generateFile( array $template ): string {
98        $messages = $template['MESSAGES'];
99        $authors = $template['AUTHORS'];
100
101        if ( $this->flattener ) {
102            $messages = $this->flattener->unflatten( $messages );
103        }
104
105        $mangler = $this->group->getMangler();
106        $messages = $mangler->unmangleArray( $messages );
107
108        if ( $this->extra['includeMetadata'] ?? true ) {
109            $metadata = $template['EXTRA']['METADATA'] ?? [];
110            $metadata['authors'] = $authors;
111
112            $messages = [ '@metadata' => $metadata ] + $messages;
113        }
114
115        return FormatJson::encode( $messages, "\t", FormatJson::ALL_OK ) . "\n";
116    }
117
118    private function getFlattener(): ?ArrayFlattener {
119        if ( !isset( $this->extra['nestingSeparator'] ) ) {
120            return null;
121        }
122
123        $parseCLDRPlurals = $this->extra['parseCLDRPlurals'] ?? false;
124
125        return new ArrayFlattener( $this->extra['nestingSeparator'], $parseCLDRPlurals );
126    }
127
128    public function isContentEqual( ?string $a, ?string $b ): bool {
129        if ( $this->flattener ) {
130            return $this->flattener->compareContent( $a, $b );
131        } else {
132            return parent::isContentEqual( $a, $b );
133        }
134    }
135
136    public static function getExtraSchema(): array {
137        return [
138            'root' => [
139                '_type' => 'array',
140                '_children' => [
141                    'FILES' => [
142                        '_type' => 'array',
143                        '_children' => [
144                            'nestingSeparator' => [
145                                '_type' => 'text',
146                            ],
147                            'parseCLDRPlurals' => [
148                                '_type' => 'boolean',
149                            ],
150                            'includeMetadata' => [
151                                '_type' => 'boolean',
152                            ]
153                        ]
154                    ]
155                ]
156            ]
157        ];
158    }
159}
160
161class_alias( JsonFormat::class, 'JsonFFS' );