Merge remote-tracking branch 'upstream/develop' into update-self
[friendica.git/.git] / src / Core / Console / PhpToPo.php
1 <?php
2
3 namespace Friendica\Core\Console;
4
5 /**
6  * Read a strings.php file and create messages.po in the same directory
7  *
8  * @author Hypolite Petovan <mrpetovan@gmail.com>
9  */
10 class PhpToPo extends \Asika\SimpleConsole\Console
11 {
12
13         protected $helpOptions = ['h', 'help', '?'];
14
15         private $normBaseMsgIds = [];
16         const NORM_REGEXP = "|[\\\]|";
17
18         protected function getHelp()
19         {
20                 $help = <<<HELP
21 console php2po - Generate a messages.po file from a strings.php file
22 Usage
23         bin/console php2po [-p <n>] [--base <file>] <path/to/strings.php> [-h|--help|-?] [-v]
24
25 Description
26         Read a strings.php file and create the according messages.po in the same directory
27
28 Options
29         -p <n>        Number of plural forms. Default: 2
30         --base <file> Path to base messages.po file. Default: util/messages.po
31         -h|--help|-?  Show help information
32         -v            Show more debug information.
33 HELP;
34                 return $help;
35         }
36
37         protected function doExecute()
38         {
39                 if ($this->getOption('v')) {
40                         $this->out('Class: ' . __CLASS__);
41                         $this->out('Arguments: ' . var_export($this->args, true));
42                         $this->out('Options: ' . var_export($this->options, true));
43                 }
44
45                 if (count($this->args) == 0) {
46                         $this->out($this->getHelp());
47                         return 0;
48                 }
49
50                 if (count($this->args) > 1) {
51                         throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
52                 }
53
54                 $a = get_app();
55
56                 $phpfile = realpath($this->getArgument(0));
57
58                 if (!file_exists($phpfile)) {
59                         throw new \RuntimeException('Supplied file path doesn\'t exist.');
60                 }
61
62                 if (!is_writable(dirname($phpfile))) {
63                         throw new \RuntimeException('Supplied directory isn\'t writable.');
64                 }
65
66                 $pofile = dirname($phpfile) . DIRECTORY_SEPARATOR . 'messages.po';
67
68                 // start !
69                 include_once($phpfile);
70
71                 $out = '';
72                 $out .= "# FRIENDICA Distributed Social Network\n";
73                 $out .= "# Copyright (C) 2010, 2011, 2012, 2013 the Friendica Project\n";
74                 $out .= "# This file is distributed under the same license as the Friendica package.\n";
75                 $out .= "# \n";
76                 $out .= 'msgid ""' . "\n";
77                 $out .= 'msgstr ""' . "\n";
78                 $out .= '"Project-Id-Version: friendica\n"' . "\n";
79                 $out .= '"Report-Msgid-Bugs-To: \n"' . "\n";
80                 $out .= '"POT-Creation-Date: ' . date("Y-m-d H:i:sO") . '\n"' . "\n";
81                 $out .= '"MIME-Version: 1.0\n"' . "\n";
82                 $out .= '"Content-Type: text/plain; charset=UTF-8\n"' . "\n";
83                 $out .= '"Content-Transfer-Encoding: 8bit\n"' . "\n";
84
85                 // search for plural info
86                 $lang = "";
87                 $lang_logic = "";
88                 $lang_pnum = $this->getOption('p', 2);
89
90                 $infile = file($phpfile);
91                 foreach ($infile as $l) {
92                         $l = trim($l);
93                         if ($this->startsWith($l, 'function string_plural_select_')) {
94                                 $lang = str_replace('function string_plural_select_', '', str_replace('($n){', '', $l));
95                         }
96                         if ($this->startsWith($l, 'return')) {
97                                 $lang_logic = str_replace('$', '', trim(str_replace('return ', '', $l), ';'));
98                                 break;
99                         }
100                 }
101
102                 $this->out('Language: ' . $lang);
103                 $this->out('Plural forms: ' . $lang_pnum);
104                 $this->out('Plural forms: ' . $lang_logic);
105
106                 $out .= sprintf('"Language: %s\n"', $lang) . "\n";
107                 $out .= sprintf('"Plural-Forms: nplurals=%s; plural=%s;\n"', $lang_pnum, $lang_logic) . "\n";
108                 $out .= "\n";
109
110                 $base_path = $this->getOption('base', 'util' . DIRECTORY_SEPARATOR . 'messages.po');
111
112                 // load base messages.po and extract msgids
113                 $base_msgids = [];
114                 $base_f = file($base_path);
115                 if (!$base_f) {
116                         throw new \RuntimeException('The base ' . $base_path . ' file is missing or unavailable to read.');
117                 }
118
119                 $this->out('Loading base file ' . $base_path . '...');
120
121                 $_f = 0;
122                 $_mid = "";
123                 $_mids = [];
124                 foreach ($base_f as $l) {
125                         $l = trim($l);
126
127                         if ($this->startsWith($l, 'msgstr')) {
128                                 if ($_mid != '""') {
129                                         $base_msgids[$_mid] = $_mids;
130                                         $this->normBaseMsgIds[preg_replace(self::NORM_REGEXP, "", $_mid)] = $_mid;
131                                 }
132
133                                 $_f = 0;
134                                 $_mid = "";
135                                 $_mids = [];
136                         }
137
138                         if ($this->startsWith($l, '"') && $_f == 2) {
139                                 $_mids[count($_mids) - 1] .= "\n" . $l;
140                         }
141                         if ($this->startsWith($l, 'msgid_plural ')) {
142                                 $_f = 2;
143                                 $_mids[] = str_replace('msgid_plural ', '', $l);
144                         }
145
146                         if ($this->startsWith($l, '"') && $_f == 1) {
147                                 $_mid .= "\n" . $l;
148                                 $_mids[count($_mids) - 1] .= "\n" . $l;
149                         }
150                         if ($this->startsWith($l, 'msgid ')) {
151                                 $_f = 1;
152                                 $_mid = str_replace('msgid ', '', $l);
153                                 $_mids = [$_mid];
154                         }
155                 }
156
157                 $this->out('Creating ' . $pofile . '...');
158
159                 // create msgid and msgstr
160                 $warnings = "";
161                 foreach ($a->strings as $key => $str) {
162                         $msgid = $this->massageString($key);
163
164                         if (preg_match("|%[sd0-9](\$[sn])*|", $msgid)) {
165                                 $out .= "#, php-format\n";
166                         }
167                         $msgid = $this->findOriginalMsgId($msgid);
168                         $out .= 'msgid ' . $msgid . "\n";
169
170                         if (is_array($str)) {
171                                 if (array_key_exists($msgid, $base_msgids) && isset($base_msgids[$msgid][1])) {
172                                         $out .= 'msgid_plural ' . $base_msgids[$msgid][1] . "\n";
173                                 } else {
174                                         $out .= 'msgid_plural ' . $msgid . "\n";
175                                         $warnings .= "[W] No source plural form for msgid:\n" . str_replace("\n", "\n\t", $msgid) . "\n\n";
176                                 }
177                                 foreach ($str as $n => $msgstr) {
178                                         $out .= 'msgstr[' . $n . '] ' . $this->massageString($msgstr) . "\n";
179                                 }
180                         } else {
181                                 $out .= 'msgstr ' . $this->massageString($str) . "\n";
182                         }
183
184                         $out .= "\n";
185                 }
186
187                 if (!file_put_contents($pofile, $out)) {
188                         throw new \RuntimeException('Unable to write to ' . $pofile);
189                 }
190
191                 if ($warnings != '') {
192                         $this->out($warnings);
193                 }
194
195                 return 0;
196         }
197
198         private function startsWith($haystack, $needle)
199         {
200                 // search backwards starting from haystack length characters from the end
201                 return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE;
202         }
203
204         /**
205          * Get a string and retun a message.po ready text
206          * - replace " with \"
207          * - replace tab char with \t
208          * - manage multiline strings
209          */
210         private function massageString($str)
211         {
212                 $str = str_replace('\\', '\\\\', $str);
213                 $str = str_replace('"', '\"', $str);
214                 $str = str_replace("\t", '\t', $str);
215                 $str = str_replace("\n", '\n"' . "\n" . '"', $str);
216                 if (strpos($str, "\n") !== false && $str[0] !== '"') {
217                         $str = '"' . "\n" . $str;
218                 }
219
220                 $str = preg_replace("|\n([^\"])|", "\n\"$1", $str);
221                 return sprintf('"%s"', $str);
222         }
223
224         private function findOriginalMsgId($str)
225         {
226                 $norm_str = preg_replace(self::NORM_REGEXP, "", $str);
227                 if (array_key_exists($norm_str, $this->normBaseMsgIds)) {
228                         return $this->normBaseMsgIds[$norm_str];
229                 }
230
231                 return $str;
232         }
233
234 }