summaryrefslogtreecommitdiff
blob: bd759e8fec76fa4bf0a1a2bc3744fe1d0100b9bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
<?php
/**
 * Support for the AMD i18n message file format (used by require.js and Dojo). See:
 * http://requirejs.org/docs/api.html#i18n
 *
 * A limitation is that it only accepts json compatible structures inside the define
 * wrapper function. For example the following example is not ok since there are no
 * quotation marks around the keys:
 * define({
 *   key1: "somevalue",
 *   key2: "anothervalue"
 * });
 *
 * Instead it should look like:
 * define({
 *   "key1": "somevalue",
 *   "key2": "anothervalue"
 * });
 *
 * It also supports the top-level bundle with a root construction and language indicators.
 * The following example will give the same messages as above:
 * define({
 *   "root": {
 *      "key1": "somevalue",
 *      "key2": "anothervalue"
 *   },
 *   "sv": true
 * });
 *
 * Note that it does not support exporting with the root construction, there is only support
 * for reading it. However, this is not a serious limitation as Translatewiki doesn't export
 * the base language.
 *
 * @file
 * @author Matthias Palmér
 * @copyright Copyright © 2011-2015, MetaSolutions AB
 * @license GPL-2.0-or-later
 */

/**
 * AmdFFS implements a message format where messages are encoded
 * as key-value pairs in JSON objects wrapped in a define call.
 *
 * @ingroup FFS
 * @since 2015.02
 */
class AmdFFS extends SimpleFFS {

	/**
	 * @param string $data
	 * @return bool
	 */
	public static function isValid( $data ) {
		$data = self::extractMessagePart( $data );
		return is_array( FormatJson::decode( $data, /*as array*/true ) );
	}

	public function getFileExtensions() {
		return [ '.js' ];
	}

	/**
	 * @param string $data
	 * @return array Parsed data.
	 */
	public function readFromVariable( $data ) {
		$authors = self::extractAuthors( $data );
		$data = self::extractMessagePart( $data );
		$messages = (array)FormatJson::decode( $data, /*as array*/true );
		$metadata = [];

		// Take care of regular language bundles, as well as the root bundle.
		if ( isset( $messages['root'] ) ) {
			$messages = $this->group->getMangler()->mangle( $messages['root'] );
		} else {
			$messages = $this->group->getMangler()->mangle( $messages );
		}

		return [
			'MESSAGES' => $messages,
			'AUTHORS' => $authors,
			'METADATA' => $metadata,
		];
	}

	/**
	 * @param MessageCollection $collection
	 * @return string
	 */
	protected function writeReal( MessageCollection $collection ) {
		$messages = [];
		$mangler = $this->group->getMangler();

		/** @var ThinMessage $m */
		foreach ( $collection as $key => $m ) {
			$value = $m->translation();
			if ( $value === null ) {
				continue;
			}

			if ( $m->hasTag( 'fuzzy' ) ) {
				$value = str_replace( TRANSLATE_FUZZY, '', $value );
			}

			$key = $mangler->unmangle( $key );
			$messages[$key] = $value;
		}

		// Do not create empty files
		if ( !count( $messages ) ) {
			return '';
		}
		$header = $this->header( $collection->code, $collection->getAuthors() );
		return $header . FormatJson::encode( $messages, "\t", FormatJson::UTF8_OK ) . ");\n";
	}

	/**
	 * @param string $data
	 * @return string of JSON
	 */
	private static function extractMessagePart( $data ) {
		// Find the start and end of the data section (enclosed in the define function call).
		$dataStart = strpos( $data, 'define(' ) + 6;
		$dataEnd = strrpos( $data, ')' );

		// Strip everything outside of the data section.
		return substr( $data, $dataStart + 1, $dataEnd - $dataStart - 1 );
	}

	/**
	 * @param string $data
	 * @return array
	 */
	private static function extractAuthors( $data ) {
		preg_match_all( '~\n \*  - (.+)~', $data, $result );
		return $result[1];
	}

	/**
	 * @param string $code
	 * @param array $authors
	 * @return string
	 */
	private function header( $code, $authors ) {
		global $wgSitename;

		$name = TranslateUtils::getLanguageName( $code );
		$authorsList = $this->authorsList( $authors );

		return <<<EOT
/**
 * Messages for $name
 * Exported from $wgSitename
 *
{$authorsList}
 */
define(
EOT;
	}

	/**
	 * @param string[] $authors
	 * @return string
	 */
	private function authorsList( array $authors ) {
		if ( $authors === [] ) {
			return '';
		}

		$prefix = ' *  - ';
		$authorList = implode( "\n$prefix", $authors );
		return " * Translators:\n$prefix$authorList";
	}
}