Total Complexity | 40 |
Total Lines | 362 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like GettextShell often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use GettextShell, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
23 | class GettextShell extends Shell |
||
24 | { |
||
25 | /** |
||
26 | * Get the option parser for this shell. |
||
27 | * |
||
28 | * @return \Cake\Console\ConsoleOptionParser |
||
29 | */ |
||
30 | public function getOptionParser() |
||
57 | } |
||
58 | |||
59 | /** |
||
60 | * The Po results |
||
61 | * |
||
62 | * @var array |
||
63 | */ |
||
64 | protected $poResult = []; |
||
65 | |||
66 | /** |
||
67 | * The template paths |
||
68 | * |
||
69 | * @var array |
||
70 | */ |
||
71 | protected $templatePaths = []; |
||
72 | |||
73 | /** |
||
74 | * The locale path |
||
75 | * |
||
76 | * @var string |
||
77 | */ |
||
78 | protected $localePath = null; |
||
79 | |||
80 | /** |
||
81 | * PO file name |
||
82 | * |
||
83 | * @var string |
||
84 | */ |
||
85 | protected $poName = 'default.po'; |
||
86 | |||
87 | /** |
||
88 | * Update gettext po files |
||
89 | * |
||
90 | * @return void |
||
91 | */ |
||
92 | public function update() : void |
||
93 | { |
||
94 | $resCmd = []; |
||
95 | exec('which msgmerge 2>&1', $resCmd); |
||
96 | if (empty($resCmd[0])) { |
||
97 | $this->out('ERROR: msgmerge not available. Please install gettext utilities.'); |
||
98 | |||
99 | return; |
||
100 | } |
||
101 | |||
102 | $this->out('Updating .pot and .po files...'); |
||
103 | |||
104 | $this->setupPaths(); |
||
105 | foreach ($this->templatePaths as $path) { |
||
106 | $this->out(sprintf('Search in: %s', $path)); |
||
107 | $this->parseDir($path); |
||
108 | } |
||
109 | |||
110 | $this->out('Creating master .pot file'); |
||
111 | $this->writeMasterPot(); |
||
112 | $this->ttagExtract(); |
||
113 | |||
114 | $this->hr(); |
||
115 | $this->out('Merging master .pot with current .po files'); |
||
116 | $this->hr(); |
||
117 | |||
118 | $this->writePoFiles(); |
||
119 | |||
120 | $this->out('Done'); |
||
121 | } |
||
122 | |||
123 | /** |
||
124 | * Setup template paths and locale path |
||
125 | * |
||
126 | * @return void |
||
127 | */ |
||
128 | private function setupPaths() : void |
||
129 | { |
||
130 | $basePath = getcwd(); |
||
131 | if (isset($this->params['app'])) { |
||
132 | $f = new Folder($this->params['app']); |
||
133 | $basePath = $f->path; |
||
134 | } elseif (isset($this->params['plugin'])) { |
||
135 | $f = new Folder(sprintf('%s/plugins/%s', getcwd(), $this->params['plugin'])); |
||
136 | $basePath = $f->path; |
||
137 | $this->poName = $this->params['plugin'] . ".po"; |
||
138 | } |
||
139 | |||
140 | $this->templatePaths = [ |
||
141 | $basePath . '/src', |
||
142 | $basePath . '/config', |
||
143 | ]; |
||
144 | $this->localePath = $basePath . '/src/Locale'; |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * Write `master.pot` file |
||
149 | * |
||
150 | * @return void |
||
151 | */ |
||
152 | private function writeMasterPot() : void |
||
153 | { |
||
154 | $potFilename = sprintf('%s/master.pot', $this->localePath); |
||
155 | $this->out(sprintf('Writing new .pot file: %s', $potFilename)); |
||
156 | $pot = new File($potFilename, true); |
||
157 | $pot->write($this->header('pot')); |
||
158 | sort($this->poResult); |
||
159 | foreach ($this->poResult as $res) { |
||
160 | if (!empty($res)) { |
||
161 | $pot->write(sprintf('%smsgid "%s"%smsgstr ""%s', "\n", $res, "\n", "\n")); |
||
162 | } |
||
163 | } |
||
164 | $pot->close(); |
||
165 | } |
||
166 | |||
167 | /** |
||
168 | * Write `.po` files |
||
169 | * |
||
170 | * @return void |
||
171 | */ |
||
172 | private function writePoFiles() : void |
||
173 | { |
||
174 | $header = $this->header('po'); |
||
175 | $potFilename = sprintf('%s/master.pot', $this->localePath); |
||
176 | $folder = new Folder($this->localePath); |
||
177 | $ls = $folder->read(); |
||
178 | foreach ($ls[0] as $loc) { |
||
179 | if ($loc[0] != '.') { // only "regular" dirs... |
||
180 | $this->out(sprintf('Language: %s', $loc)); |
||
181 | $poFile = sprintf('%s/%s/%s', $this->localePath, $loc, $this->poName); |
||
182 | if (!file_exists($poFile)) { |
||
183 | $newPoFile = new File($poFile, true); |
||
184 | $newPoFile->write($header); |
||
185 | $newPoFile->close(); |
||
186 | } |
||
187 | $this->out(sprintf('Merging %s', $poFile)); |
||
188 | $mergeCmd = sprintf('msgmerge --backup=off -N -U %s %s', $poFile, $potFilename); |
||
189 | exec($mergeCmd); |
||
190 | $this->analyzePoFile($poFile); |
||
191 | $this->hr(); |
||
192 | } |
||
193 | } |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * Header lines for po/pot file |
||
198 | * |
||
199 | * @param string $type The file type (can be 'po', 'pot') |
||
200 | * @return string |
||
201 | */ |
||
202 | private function header(string $type = 'po') : string |
||
203 | { |
||
204 | $result = sprintf('msgid ""%smsgstr ""%s', "\n", "\n"); |
||
205 | $contents = [ |
||
206 | 'po' => [ |
||
207 | 'Project-Id-Version' => 'BEdita 4', |
||
208 | 'POT-Creation-Date' => date("Y-m-d H:i:s"), |
||
209 | 'PO-Revision-Date' => '', |
||
210 | 'Last-Translator' => '', |
||
211 | 'Language-Team' => 'BEdita I18N & I10N Team', |
||
212 | 'Language' => '', |
||
213 | 'MIME-Version' => '1.0', |
||
214 | 'Content-Transfer-Encoding' => '8bit', |
||
215 | 'Plural-Forms' => 'nplurals=2; plural=(n != 1);', |
||
216 | 'Content-Type' => 'text/plain; charset=utf-8', |
||
217 | ], |
||
218 | 'pot' => [ |
||
219 | 'Project-Id-Version' => 'BEdita 4', |
||
220 | 'POT-Creation-Date' => date("Y-m-d H:i:s"), |
||
221 | 'MIME-Version' => '1.0', |
||
222 | 'Content-Transfer-Encoding' => '8bit', |
||
223 | 'Language-Team' => 'BEdita I18N & I10N Team', |
||
224 | 'Plural-Forms' => 'nplurals=2; plural=(n != 1);', |
||
225 | 'Content-Type' => 'text/plain; charset=utf-8', |
||
226 | ], |
||
227 | ]; |
||
228 | foreach ($contents[$type] as $k => $v) { |
||
229 | $result .= sprintf('"%s: %s \n"', $k, $v) . "\n"; |
||
230 | } |
||
231 | |||
232 | return $result; |
||
233 | } |
||
234 | |||
235 | /** |
||
236 | * Analyze po file and translate it |
||
237 | * |
||
238 | * @param string $filename The po file name |
||
239 | * @return void |
||
240 | */ |
||
241 | private function analyzePoFile($filename) : void |
||
242 | { |
||
243 | $lines = file($filename); |
||
244 | $numItems = $numNotTranslated = 0; |
||
245 | foreach ($lines as $k => $l) { |
||
246 | if (strpos($l, 'msgid "') === 0) { |
||
247 | $numItems++; |
||
248 | } |
||
249 | if (strpos($l, 'msgstr ""') === 0) { |
||
250 | if (!isset($lines[$k + 1])) { |
||
251 | $numNotTranslated++; |
||
252 | } elseif (strpos($lines[$k + 1], '"') !== 0) { |
||
253 | $numNotTranslated++; |
||
254 | } |
||
255 | } |
||
256 | } |
||
257 | $translated = $numItems - $numNotTranslated; |
||
258 | $percent = 0; |
||
259 | if ($numItems > 0) { |
||
260 | $percent = number_format(($translated * 100.) / $numItems, 1); |
||
261 | } |
||
262 | $this->out(sprintf('Translated %s of items - %s %', $translated, $numItems, $percent)); |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * "fix" string - strip slashes, escape and convert new lines to \n |
||
267 | * |
||
268 | * @param string $str The string |
||
269 | * @return string The new string |
||
270 | */ |
||
271 | private function fixString($str) : string |
||
278 | } |
||
279 | |||
280 | /** |
||
281 | * Parse file and rips gettext strings |
||
282 | * |
||
283 | * @param string $file The file name |
||
284 | * @param string $extension The file extension |
||
285 | * @return void |
||
286 | */ |
||
287 | private function parseFile($file, $extension) |
||
288 | { |
||
289 | $content = file_get_contents($file); |
||
290 | if (empty($content)) { |
||
291 | return; |
||
292 | } |
||
293 | |||
294 | if ($extension === 'twig' || $extension === 'php') { |
||
295 | $this->parseContent($content); |
||
296 | } |
||
297 | } |
||
298 | |||
299 | /** |
||
300 | * Parse file content and put i18n data in poResult array |
||
301 | * |
||
302 | * @param string $content The file content |
||
303 | * @return void |
||
304 | */ |
||
305 | private function parseContent($content) : void |
||
325 | } |
||
326 | } |
||
327 | } |
||
328 | |||
329 | /** |
||
330 | * Parse a directory |
||
331 | * |
||
332 | * @param string $dir The directory |
||
333 | * @return void |
||
334 | */ |
||
335 | private function parseDir($dir) : void |
||
346 | } |
||
347 | } |
||
348 | } |
||
349 | } |
||
350 | } |
||
351 | |||
352 | /** |
||
353 | * Extract translations from javascript files using ttag, if available. |
||
354 | * |
||
355 | * @return void |
||
356 | */ |
||
357 | private function ttagExtract() : void |
||
387 |