1: <?php
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: class ExtractTask extends Shell {
28:
29: 30: 31: 32: 33: 34:
35: var $__paths = array();
36:
37: 38: 39: 40: 41: 42:
43: var $__files = array();
44:
45: 46: 47: 48: 49: 50:
51: var $__merge = false;
52:
53: 54: 55: 56: 57: 58:
59: var $__file = null;
60:
61: 62: 63: 64: 65: 66:
67: var $__storage = array();
68:
69: 70: 71: 72: 73: 74:
75: var $__tokens = array();
76:
77: 78: 79: 80: 81: 82:
83: var $__strings = array();
84:
85: 86: 87: 88: 89: 90:
91: var $__output = null;
92:
93: 94: 95: 96: 97: 98:
99: function execute() {
100: if (isset($this->params['files']) && !is_array($this->params['files'])) {
101: $this->__files = explode(',', $this->params['files']);
102: }
103: if (isset($this->params['paths'])) {
104: $this->__paths = explode(',', $this->params['paths']);
105: } else {
106: $defaultPath = $this->params['working'];
107: $message = sprintf(__("What is the full path you would like to extract?\nExample: %s\n[Q]uit [D]one", true), $this->params['root'] . DS . 'myapp');
108: while (true) {
109: $response = $this->in($message, null, $defaultPath);
110: if (strtoupper($response) === 'Q') {
111: $this->out(__('Extract Aborted', true));
112: $this->_stop();
113: } elseif (strtoupper($response) === 'D') {
114: $this->out();
115: break;
116: } elseif (is_dir($response)) {
117: $this->__paths[] = $response;
118: $defaultPath = 'D';
119: } else {
120: $this->err(__('The directory path you supplied was not found. Please try again.', true));
121: }
122: $this->out();
123: }
124: }
125:
126: if (isset($this->params['output'])) {
127: $this->__output = $this->params['output'];
128: } else {
129: $message = sprintf(__("What is the full path you would like to output?\nExample: %s\n[Q]uit", true), $this->__paths[0] . DS . 'locale');
130: while (true) {
131: $response = $this->in($message, null, $this->__paths[0] . DS . 'locale');
132: if (strtoupper($response) === 'Q') {
133: $this->out(__('Extract Aborted', true));
134: $this->_stop();
135: } elseif (is_dir($response)) {
136: $this->__output = $response . DS;
137: break;
138: } else {
139: $this->err(__('The directory path you supplied was not found. Please try again.', true));
140: }
141: $this->out();
142: }
143: }
144:
145: if (isset($this->params['merge'])) {
146: $this->__merge = !(strtolower($this->params['merge']) === 'no');
147: } else {
148: $this->out();
149: $response = $this->in(sprintf(__('Would you like to merge all domains strings into the default.pot file?', true)), array('y', 'n'), 'n');
150: $this->__merge = strtolower($response) === 'y';
151: }
152:
153: if (empty($this->__files)) {
154: $this->__searchFiles();
155: }
156: $this->__extract();
157: }
158:
159: 160: 161: 162: 163: 164:
165: function __extract() {
166: $this->out();
167: $this->out();
168: $this->out(__('Extracting...', true));
169: $this->hr();
170: $this->out(__('Paths:', true));
171: foreach ($this->__paths as $path) {
172: $this->out(' ' . $path);
173: }
174: $this->out(__('Output Directory: ', true) . $this->__output);
175: $this->hr();
176: $this->__extractTokens();
177: $this->__buildFiles();
178: $this->__writeFiles();
179: $this->__paths = $this->__files = $this->__storage = array();
180: $this->__strings = $this->__tokens = array();
181: $this->out();
182: $this->out(__('Done.', true));
183: }
184:
185: 186: 187: 188: 189: 190:
191: function help() {
192: $this->out(__('CakePHP Language String Extraction:', true));
193: $this->hr();
194: $this->out(__('The Extract script generates .pot file(s) with translations', true));
195: $this->out(__('By default the .pot file(s) will be place in the locale directory of -app', true));
196: $this->out(__('By default -app is ROOT/app', true));
197: $this->hr();
198: $this->out(__('Usage: cake i18n extract <command> <param1> <param2>...', true));
199: $this->out();
200: $this->out(__('Params:', true));
201: $this->out(__(' -app [path...]: directory where your application is located', true));
202: $this->out(__(' -root [path...]: path to install', true));
203: $this->out(__(' -core [path...]: path to cake directory', true));
204: $this->out(__(' -paths [comma separated list of paths, full path is needed]', true));
205: $this->out(__(' -merge [yes|no]: Merge all domains strings into the default.pot file', true));
206: $this->out(__(' -output [path...]: Full path to output directory', true));
207: $this->out(__(' -files: [comma separated list of files, full path to file is needed]', true));
208: $this->out();
209: $this->out(__('Commands:', true));
210: $this->out(__(' cake i18n extract help: Shows this help message.', true));
211: $this->out();
212: }
213:
214: 215: 216: 217: 218: 219:
220: function __extractTokens() {
221: foreach ($this->__files as $file) {
222: $this->__file = $file;
223: $this->out(sprintf(__('Processing %s...', true), $file));
224:
225: $code = file_get_contents($file);
226: $allTokens = token_get_all($code);
227: $this->__tokens = array();
228: $lineNumber = 1;
229:
230: foreach ($allTokens as $token) {
231: if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) {
232: if (is_array($token)) {
233: $token[] = $lineNumber;
234: }
235: $this->__tokens[] = $token;
236: }
237:
238: if (is_array($token)) {
239: $lineNumber += count(explode("\n", $token[1])) - 1;
240: } else {
241: $lineNumber += count(explode("\n", $token)) - 1;
242: }
243: }
244: unset($allTokens);
245: $this->__parse('__', array('singular'));
246: $this->__parse('__n', array('singular', 'plural'));
247: $this->__parse('__d', array('domain', 'singular'));
248: $this->__parse('__c', array('singular'));
249: $this->__parse('__dc', array('domain', 'singular'));
250: $this->__parse('__dn', array('domain', 'singular', 'plural'));
251: $this->__parse('__dcn', array('domain', 'singular', 'plural'));
252: }
253: }
254:
255: 256: 257: 258: 259: 260: 261: 262:
263: function __parse($functionName, $map) {
264: $count = 0;
265: $tokenCount = count($this->__tokens);
266:
267: while (($tokenCount - $count) > 1) {
268: list($countToken, $firstParenthesis) = array($this->__tokens[$count], $this->__tokens[$count + 1]);
269: if (!is_array($countToken)) {
270: $count++;
271: continue;
272: }
273:
274: list($type, $string, $line) = $countToken;
275: if (($type == T_STRING) && ($string == $functionName) && ($firstParenthesis == '(')) {
276: $position = $count;
277: $depth = 0;
278:
279: while ($depth == 0) {
280: if ($this->__tokens[$position] == '(') {
281: $depth++;
282: } elseif ($this->__tokens[$position] == ')') {
283: $depth--;
284: }
285: $position++;
286: }
287:
288: $mapCount = count($map);
289: $strings = $this->__getStrings($position, $mapCount);
290:
291: if ($mapCount == count($strings)) {
292: extract(array_combine($map, $strings));
293: $domain = isset($domain) ? $domain : 'default';
294: $string = isset($plural) ? $singular . "\0" . $plural : $singular;
295: $this->__strings[$domain][$string][$this->__file][] = $line;
296: } else {
297: $this->__markerError($this->__file, $line, $functionName, $count);
298: }
299: }
300: $count++;
301: }
302: }
303:
304: 305: 306: 307: 308: 309: 310:
311: function __getStrings($position, $target) {
312: $strings = array();
313: while (count($strings) < $target && ($this->__tokens[$position] == ',' || $this->__tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING)) {
314: $condition1 = ($this->__tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->__tokens[$position+1] == '.');
315: $condition2 = ($this->__tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->__tokens[$position+1][0] == T_COMMENT);
316: if ($condition1 || $condition2) {
317: $string = '';
318: while ($this->__tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->__tokens[$position][0] == T_COMMENT || $this->__tokens[$position] == '.') {
319: if ($this->__tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
320: $string .= $this->__formatString($this->__tokens[$position][1]);
321: }
322: $position++;
323: }
324: if ($this->__tokens[$position][0] == T_COMMENT || $this->__tokens[$position] == ',' || $this->__tokens[$position] == ')') {
325: $strings[] = $string;
326: }
327: } else if ($this->__tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
328: $strings[] = $this->__formatString($this->__tokens[$position][1]);
329: }
330: $position++;
331: }
332: return $strings;
333: }
334:
335: 336: 337: 338: 339: 340:
341: function __buildFiles() {
342: foreach ($this->__strings as $domain => $strings) {
343: foreach ($strings as $string => $files) {
344: $occurrences = array();
345: foreach ($files as $file => $lines) {
346: $occurrences[] = $file . ':' . implode(';', $lines);
347: }
348: $occurrences = implode("\n#: ", $occurrences);
349: $header = '#: ' . str_replace($this->__paths, '', $occurrences) . "\n";
350:
351: if (strpos($string, "\0") === false) {
352: $sentence = "msgid \"{$string}\"\n";
353: $sentence .= "msgstr \"\"\n\n";
354: } else {
355: list($singular, $plural) = explode("\0", $string);
356: $sentence = "msgid \"{$singular}\"\n";
357: $sentence .= "msgid_plural \"{$plural}\"\n";
358: $sentence .= "msgstr[0] \"\"\n";
359: $sentence .= "msgstr[1] \"\"\n\n";
360: }
361:
362: $this->__store($domain, $header, $sentence);
363: if ($domain != 'default' && $this->__merge) {
364: $this->__store('default', $header, $sentence);
365: }
366: }
367: }
368: }
369:
370: 371: 372: 373: 374: 375:
376: function __store($domain, $header, $sentence) {
377: if (!isset($this->__storage[$domain])) {
378: $this->__storage[$domain] = array();
379: }
380: if (!isset($this->__storage[$domain][$sentence])) {
381: $this->__storage[$domain][$sentence] = $header;
382: } else {
383: $this->__storage[$domain][$sentence] .= $header;
384: }
385: }
386:
387: 388: 389: 390: 391: 392:
393: function __writeFiles() {
394: $overwriteAll = false;
395: foreach ($this->__storage as $domain => $sentences) {
396: $output = $this->__writeHeader();
397: foreach ($sentences as $sentence => $header) {
398: $output .= $header . $sentence;
399: }
400:
401: $filename = $domain . '.pot';
402: $File = new File($this->__output . $filename);
403: $response = '';
404: while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
405: $this->out();
406: $response = $this->in(sprintf(__('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', true), $filename), array('y', 'n', 'a'), 'y');
407: if (strtoupper($response) === 'N') {
408: $response = '';
409: while ($response == '') {
410: $response = $this->in(sprintf(__("What would you like to name this file?\nExample: %s", true), 'new_' . $filename), null, 'new_' . $filename);
411: $File = new File($this->__output . $response);
412: $filename = $response;
413: }
414: } elseif (strtoupper($response) === 'A') {
415: $overwriteAll = true;
416: }
417: }
418: $File->write($output);
419: $File->close();
420: }
421: }
422:
423: 424: 425: 426: 427: 428:
429: function __writeHeader() {
430: $output = "# LANGUAGE translation of CakePHP Application\n";
431: $output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
432: $output .= "#\n";
433: $output .= "#, fuzzy\n";
434: $output .= "msgid \"\"\n";
435: $output .= "msgstr \"\"\n";
436: $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
437: $output .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
438: $output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
439: $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
440: $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
441: $output .= "\"MIME-Version: 1.0\\n\"\n";
442: $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
443: $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
444: $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
445: return $output;
446: }
447:
448: 449: 450: 451: 452: 453: 454:
455: function __formatString($string) {
456: $quote = substr($string, 0, 1);
457: $string = substr($string, 1, -1);
458: if ($quote == '"') {
459: $string = stripcslashes($string);
460: } else {
461: $string = strtr($string, array("\\'" => "'", "\\\\" => "\\"));
462: }
463: $string = str_replace("\r\n", "\n", $string);
464: return addcslashes($string, "\0..\37\\\"");
465: }
466:
467: 468: 469: 470: 471: 472: 473: 474: 475: 476:
477: function __markerError($file, $line, $marker, $count) {
478: $this->out(sprintf(__("Invalid marker content in %s:%s\n* %s(", true), $file, $line, $marker), true);
479: $count += 2;
480: $tokenCount = count($this->__tokens);
481: $parenthesis = 1;
482:
483: while ((($tokenCount - $count) > 0) && $parenthesis) {
484: if (is_array($this->__tokens[$count])) {
485: $this->out($this->__tokens[$count][1], false);
486: } else {
487: $this->out($this->__tokens[$count], false);
488: if ($this->__tokens[$count] == '(') {
489: $parenthesis++;
490: }
491:
492: if ($this->__tokens[$count] == ')') {
493: $parenthesis--;
494: }
495: }
496: $count++;
497: }
498: $this->out("\n", true);
499: }
500:
501: 502: 503: 504: 505: 506:
507: function __searchFiles() {
508: foreach ($this->__paths as $path) {
509: $Folder = new Folder($path);
510: $files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true);
511: $this->__files = array_merge($this->__files, $files);
512: }
513: }
514: }
515: