| 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 |