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: 28:
29: ob_start();
30:
31: $singularReturn = __('Singular string return __()', true);
32: $singularEcho = __('Singular string echo __()');
33:
34: $pluralReturn = __n('% apple in the bowl (plural string return __n())', '% apples in the blowl (plural string 2 return __n())', 3, true);
35: $pluralEcho = __n('% apple in the bowl (plural string 2 echo __n())', '% apples in the blowl (plural string 2 echo __n()', 3);
36:
37: $singularDomainReturn = __d('controllers', 'Singular string domain lookup return __d()', true);
38: $singularDomainEcho = __d('controllers', 'Singular string domain lookup echo __d()');
39:
40: $pluralDomainReturn = __dn('controllers', '% pears in the bowl (plural string domain lookup return __dn())', '% pears in the blowl (plural string domain lookup return __dn())', 3, true);
41: $pluralDomainEcho = __dn('controllers', '% pears in the bowl (plural string domain lookup echo __dn())', '% pears in the blowl (plural string domain lookup echo __dn())', 3);
42:
43: $singularDomainCategoryReturn = __dc('controllers', 'Singular string domain and category lookup return __dc()', 5, true);
44: $singularDomainCategoryEcho = __dc('controllers', 'Singular string domain and category lookup echo __dc()', 5);
45:
46: $pluralDomainCategoryReturn = __dcn('controllers', '% apple in the bowl (plural string 1 domain and category lookup return __dcn())', '% apples in the blowl (plural string 2 domain and category lookup return __dcn())', 3, 5, true);
47: $pluralDomainCategoryEcho = __dcn('controllers', '% apple in the bowl (plural string 1 domain and category lookup echo __dcn())', '% apples in the blowl (plural string 2 domain and category lookup echo __dcn())', 3, 5);
48:
49: $categoryReturn = __c('Category string lookup line return __c()', 5, true);
50: $categoryEcho = __c('Category string lookup line echo __c()', 5);
51:
52: ob_end_clean();
53: 54: 55: 56: 57: 58:
59: class ExtractTask extends Shell{
60: 61: 62: 63: 64: 65:
66: var $path = null;
67: 68: 69: 70: 71: 72:
73: var $files = array();
74: 75: 76: 77: 78: 79:
80: var $__filename = 'default';
81: 82: 83: 84: 85: 86:
87: var $__oneFile = true;
88: 89: 90: 91: 92: 93:
94: var $__file = null;
95: 96: 97: 98: 99: 100:
101: var $__tokens = array();
102: 103: 104: 105: 106: 107:
108: var $__strings = array();
109: 110: 111: 112: 113: 114:
115: var $__fileVersions = array();
116: 117: 118: 119: 120: 121:
122: var $__output = null;
123: 124: 125: 126: 127:
128: function execute() {
129: if (isset($this->params['files']) && !is_array($this->params['files'])) {
130: $this->files = explode(',', $this->params['files']);
131: }
132: if (isset($this->params['path'])) {
133: $this->path = $this->params['path'];
134: } else {
135: $response = '';
136: while ($response == '') {
137: $response = $this->in("What is the full path you would like to extract?\nExample: " . $this->params['root'] . DS . "myapp\n[Q]uit", null, $this->params['working']);
138: if (strtoupper($response) === 'Q') {
139: $this->out('Extract Aborted');
140: $this->_stop();
141: }
142: }
143:
144: if (is_dir($response)) {
145: $this->path = $response;
146: } else {
147: $this->err('The directory path you supplied was not found. Please try again.');
148: $this->execute();
149: }
150: }
151:
152: if (isset($this->params['debug'])) {
153: $this->path = ROOT;
154: $this->files = array(__FILE__);
155: }
156:
157: if (isset($this->params['output'])) {
158: $this->__output = $this->params['output'];
159: } else {
160: $response = '';
161: while ($response == '') {
162: $response = $this->in("What is the full path you would like to output?\nExample: " . $this->path . DS . "locale\n[Q]uit", null, $this->path . DS . "locale");
163: if (strtoupper($response) === 'Q') {
164: $this->out('Extract Aborted');
165: $this->_stop();
166: }
167: }
168:
169: if (is_dir($response)) {
170: $this->__output = $response . DS;
171: } else {
172: $this->err('The directory path you supplied was not found. Please try again.');
173: $this->execute();
174: }
175: }
176:
177: if (empty($this->files)) {
178: $this->files = $this->__searchDirectory();
179: }
180: $this->__extract();
181: }
182: 183: 184: 185: 186:
187: function __extract() {
188: $this->out('');
189: $this->out('');
190: $this->out(__('Extracting...', true));
191: $this->hr();
192: $this->out(__('Path: ', true). $this->path);
193: $this->out(__('Output Directory: ', true). $this->__output);
194: $this->hr();
195:
196: $response = '';
197: $filename = '';
198: while ($response == '') {
199: $response = $this->in(__('Would you like to merge all translations into one file?', true), array('y','n'), 'y');
200: if (strtolower($response) == 'n') {
201: $this->__oneFile = false;
202: } else {
203: while ($filename == '') {
204: $filename = $this->in(__('What should we name this file?', true), null, $this->__filename);
205: if ($filename == '') {
206: $this->out(__('The filesname you supplied was empty. Please try again.', true));
207: }
208: }
209: $this->__filename = $filename;
210: }
211: }
212: $this->__extractTokens();
213: }
214: 215: 216: 217: 218:
219: function help() {
220: $this->out(__('CakePHP Language String Extraction:', true));
221: $this->hr();
222: $this->out(__('The Extract script generates .pot file(s) with translations', true));
223: $this->out(__('By default the .pot file(s) will be place in the locale directory of -app', true));
224: $this->out(__('By default -app is ROOT/app', true));
225: $this->hr();
226: $this->out(__('usage: cake i18n extract [command] [path...]', true));
227: $this->out('');
228: $this->out(__('commands:', true));
229: $this->out(__(' -app [path...]: directory where your application is located', true));
230: $this->out(__(' -root [path...]: path to install', true));
231: $this->out(__(' -core [path...]: path to cake directory', true));
232: $this->out(__(' -path [path...]: Full path to directory to extract strings', true));
233: $this->out(__(' -output [path...]: Full path to output directory', true));
234: $this->out(__(' -files: [comma separated list of files, full path to file is needed]', true));
235: $this->out(__(' cake i18n extract help: Shows this help message.', true));
236: $this->out(__(' -debug: Perform self test.', true));
237: $this->out('');
238: }
239: 240: 241: 242: 243:
244: function __extractTokens() {
245: foreach ($this->files as $file) {
246: $this->__file = $file;
247: $this->out(sprintf(__('Processing %s...', true), $file));
248:
249: $code = file_get_contents($file);
250:
251: $this->__findVersion($code, $file);
252: $allTokens = token_get_all($code);
253: $this->__tokens = array();
254: $lineNumber = 1;
255:
256: foreach ($allTokens as $token) {
257: if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) {
258: if (is_array($token)) {
259: $token[] = $lineNumber;
260: }
261: $this->__tokens[] = $token;
262: }
263:
264: if (is_array($token)) {
265: $lineNumber += count(explode("\n", $token[1])) - 1;
266: } else {
267: $lineNumber += count(explode("\n", $token)) - 1;
268: }
269: }
270: unset($allTokens);
271: $this->basic();
272: $this->basic('__c');
273: $this->extended();
274: $this->extended('__dc', 2);
275: $this->extended('__n', 0, true);
276: $this->extended('__dn', 2, true);
277: $this->extended('__dcn', 4, true);
278: }
279: $this->__buildFiles();
280: $this->__writeFiles();
281: $this->out('Done.');
282: }
283: 284: 285: 286: 287: 288:
289: function basic($functionName = '__') {
290: $count = 0;
291: $tokenCount = count($this->__tokens);
292:
293: while (($tokenCount - $count) > 3) {
294: list($countToken, $parenthesis, $middle, $right) = array($this->__tokens[$count], $this->__tokens[$count + 1], $this->__tokens[$count + 2], $this->__tokens[$count + 3]);
295: if (!is_array($countToken)) {
296: $count++;
297: continue;
298: }
299:
300: list($type, $string, $line) = $countToken;
301: if (($type == T_STRING) && ($string == $functionName) && ($parenthesis == '(')) {
302:
303: if (in_array($right, array(')', ','))
304: && (is_array($middle) && ($middle[0] == T_CONSTANT_ENCAPSED_STRING))) {
305:
306: if ($this->__oneFile === true) {
307: $this->__strings[$this->__formatString($middle[1])][$this->__file][] = $line;
308: } else {
309: $this->__strings[$this->__file][$this->__formatString($middle[1])][] = $line;
310: }
311: } else {
312: $this->__markerError($this->__file, $line, $functionName, $count);
313: }
314: }
315: $count++;
316: }
317: }
318: 319: 320: 321: 322: 323: 324: 325:
326: function extended($functionName = '__d', $shift = 0, $plural = false) {
327: $count = 0;
328: $tokenCount = count($this->__tokens);
329:
330: while (($tokenCount - $count) > 7) {
331: list($countToken, $firstParenthesis) = array($this->__tokens[$count], $this->__tokens[$count + 1]);
332: if (!is_array($countToken)) {
333: $count++;
334: continue;
335: }
336:
337: list($type, $string, $line) = $countToken;
338: if (($type == T_STRING) && ($string == $functionName) && ($firstParenthesis == '(')) {
339: $position = $count;
340: $depth = 0;
341:
342: while ($depth == 0) {
343: if ($this->__tokens[$position] == '(') {
344: $depth++;
345: } elseif ($this->__tokens[$position] == ')') {
346: $depth--;
347: }
348: $position++;
349: }
350:
351: if ($plural) {
352: $end = $position + $shift + 7;
353:
354: if ($this->__tokens[$position + $shift + 5] === ')') {
355: $end = $position + $shift + 5;
356: }
357:
358: if (empty($shift)) {
359: list($singular, $firstComma, $plural, $seoncdComma, $endParenthesis) = array($this->__tokens[$position], $this->__tokens[$position + 1], $this->__tokens[$position + 2], $this->__tokens[$position + 3], $this->__tokens[$end]);
360: $condition = ($seoncdComma == ',');
361: } else {
362: list($domain, $firstComma, $singular, $seoncdComma, $plural, $comma3, $endParenthesis) = array($this->__tokens[$position], $this->__tokens[$position + 1], $this->__tokens[$position + 2], $this->__tokens[$position + 3], $this->__tokens[$position + 4], $this->__tokens[$position + 5], $this->__tokens[$end]);
363: $condition = ($comma3 == ',');
364: }
365: $condition = $condition &&
366: (is_array($singular) && ($singular[0] == T_CONSTANT_ENCAPSED_STRING)) &&
367: (is_array($plural) && ($plural[0] == T_CONSTANT_ENCAPSED_STRING));
368: } else {
369: if ($this->__tokens[$position + $shift + 5] === ')') {
370: $comma = $this->__tokens[$position + $shift + 3];
371: $end = $position + $shift + 5;
372: } else {
373: $comma = null;
374: $end = $position + $shift + 3;
375: }
376:
377: list($domain, $firstComma, $text, $seoncdComma, $endParenthesis) = array($this->__tokens[$position], $this->__tokens[$position + 1], $this->__tokens[$position + 2], $comma, $this->__tokens[$end]);
378: $condition = ($seoncdComma == ',' || $seoncdComma === null) &&
379: (is_array($domain) && ($domain[0] == T_CONSTANT_ENCAPSED_STRING)) &&
380: (is_array($text) && ($text[0] == T_CONSTANT_ENCAPSED_STRING));
381: }
382:
383: if (($endParenthesis == ')') && $condition) {
384: if ($this->__oneFile === true) {
385: if ($plural) {
386: $this->__strings[$this->__formatString($singular[1]) . "\0" . $this->__formatString($plural[1])][$this->__file][] = $line;
387: } else {
388: $this->__strings[$this->__formatString($text[1])][$this->__file][] = $line;
389: }
390: } else {
391: if ($plural) {
392: $this->__strings[$this->__file][$this->__formatString($singular[1]) . "\0" . $this->__formatString($plural[1])][] = $line;
393: } else {
394: $this->__strings[$this->__file][$this->__formatString($text[1])][] = $line;
395: }
396: }
397: } else {
398: $this->__markerError($this->__file, $line, $functionName, $count);
399: }
400: }
401: $count++;
402: }
403: }
404: 405: 406: 407: 408:
409: function __buildFiles() {
410: foreach ($this->__strings as $str => $fileInfo) {
411: $output = '';
412: $occured = $fileList = array();
413:
414: if ($this->__oneFile === true) {
415: foreach ($fileInfo as $file => $lines) {
416: $occured[] = "$file:" . implode(';', $lines);
417:
418: if (isset($this->__fileVersions[$file])) {
419: $fileList[] = $this->__fileVersions[$file];
420: }
421: }
422: $occurances = implode("\n#: ", $occured);
423: $occurances = str_replace($this->path, '', $occurances);
424: $output = "#: $occurances\n";
425: $filename = $this->__filename;
426:
427: if (strpos($str, "\0") === false) {
428: $output .= "msgid \"$str\"\n";
429: $output .= "msgstr \"\"\n";
430: } else {
431: list($singular, $plural) = explode("\0", $str);
432: $output .= "msgid \"$singular\"\n";
433: $output .= "msgid_plural \"$plural\"\n";
434: $output .= "msgstr[0] \"\"\n";
435: $output .= "msgstr[1] \"\"\n";
436: }
437: $output .= "\n";
438: } else {
439: foreach ($fileInfo as $file => $lines) {
440: $filename = $str;
441: $occured = array("$str:" . implode(';', $lines));
442:
443: if (isset($this->__fileVersions[$str])) {
444: $fileList[] = $this->__fileVersions[$str];
445: }
446: $occurances = implode("\n#: ", $occured);
447: $occurances = str_replace($this->path, '', $occurances);
448: $output .= "#: $occurances\n";
449:
450: if (strpos($file, "\0") === false) {
451: $output .= "msgid \"$file\"\n";
452: $output .= "msgstr \"\"\n";
453: } else {
454: list($singular, $plural) = explode("\0", $file);
455: $output .= "msgid \"$singular\"\n";
456: $output .= "msgid_plural \"$plural\"\n";
457: $output .= "msgstr[0] \"\"\n";
458: $output .= "msgstr[1] \"\"\n";
459: }
460: $output .= "\n";
461: }
462: }
463: $this->__store($filename, $output, $fileList);
464: }
465: }
466: 467: 468: 469: 470: 471: 472: 473: 474: 475:
476: function __store($file = 0, $input = 0, $fileList = array(), $get = 0) {
477: static $storage = array();
478:
479: if (!$get) {
480: if (isset($storage[$file])) {
481: $storage[$file][1] = array_unique(array_merge($storage[$file][1], $fileList));
482: $storage[$file][] = $input;
483: } else {
484: $storage[$file] = array();
485: $storage[$file][0] = $this->__writeHeader();
486: $storage[$file][1] = $fileList;
487: $storage[$file][2] = $input;
488: }
489: } else {
490: return $storage;
491: }
492: }
493: 494: 495: 496: 497:
498: function __writeFiles() {
499: $output = $this->__store(0, 0, array(), 1);
500: $output = $this->__mergeFiles($output);
501:
502: foreach ($output as $file => $content) {
503: $tmp = str_replace(array($this->path, '.php','.ctp','.thtml', '.inc','.tpl' ), '', $file);
504: $tmp = str_replace(DS, '.', $tmp);
505: $file = str_replace('.', '-', $tmp) .'.pot';
506: $fileList = $content[1];
507:
508: unset($content[1]);
509:
510: $fileList = str_replace(array($this->path), '', $fileList);
511:
512: if (count($fileList) > 1) {
513: $fileList = "Generated from files:\n# " . implode("\n# ", $fileList);
514: } elseif (count($fileList) == 1) {
515: $fileList = 'Generated from file: ' . implode('', $fileList);
516: } else {
517: $fileList = 'No version information was available in the source files.';
518: }
519:
520: if (is_file($this->__output . $file)) {
521: $response = '';
522: while ($response == '') {
523: $response = $this->in("\n\nError: ".$file . ' already exists in this location. Overwrite?', array('y','n', 'q'), 'n');
524: if (strtoupper($response) === 'Q') {
525: $this->out('Extract Aborted');
526: $this->_stop();
527: } elseif (strtoupper($response) === 'N') {
528: $response = '';
529: while ($response == '') {
530: $response = $this->in("What would you like to name this file?\nExample: new_" . $file, null, "new_" . $file);
531: $file = $response;
532: }
533: }
534: }
535: }
536: $fp = fopen($this->__output . $file, 'w');
537: fwrite($fp, str_replace('--VERSIONS--', $fileList, implode('', $content)));
538: fclose($fp);
539: }
540: }
541: 542: 543: 544: 545: 546: 547:
548: function __mergeFiles($output) {
549: foreach ($output as $file => $content) {
550: if (count($content) <= 1 && $file != $this->__filename) {
551: @$output[$this->__filename][1] = array_unique(array_merge($output[$this->__filename][1], $content[1]));
552:
553: if (!isset($output[$this->__filename][0])) {
554: $output[$this->__filename][0] = $content[0];
555: }
556: unset($content[0]);
557: unset($content[1]);
558:
559: foreach ($content as $msgid) {
560: $output[$this->__filename][] = $msgid;
561: }
562: unset($output[$file]);
563: }
564: }
565: return $output;
566: }
567: 568: 569: 570: 571: 572:
573: function __writeHeader() {
574: $output = "# LANGUAGE translation of CakePHP Application\n";
575: $output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
576: $output .= "# --VERSIONS--\n";
577: $output .= "#\n";
578: $output .= "#, fuzzy\n";
579: $output .= "msgid \"\"\n";
580: $output .= "msgstr \"\"\n";
581: $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
582: $output .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
583: $output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
584: $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
585: $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
586: $output .= "\"MIME-Version: 1.0\\n\"\n";
587: $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
588: $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
589: $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
590: return $output;
591: }
592: 593: 594: 595: 596: 597: 598:
599: function __findVersion($code, $file) {
600: $header = '$Id' . ':';
601: if (preg_match('/\\' . $header . ' [\\w.]* ([\\d]*)/', $code, $versionInfo)) {
602: $version = str_replace(ROOT, '', 'Revision: ' . $versionInfo[1] . ' ' .$file);
603: $this->__fileVersions[$file] = $version;
604: }
605: }
606: 607: 608: 609: 610: 611: 612:
613: function __formatString($string) {
614: $quote = substr($string, 0, 1);
615: $string = substr($string, 1, -1);
616: if ($quote == '"') {
617: $string = stripcslashes($string);
618: } else {
619: $string = strtr($string, array("\\'" => "'", "\\\\" => "\\"));
620: }
621: $string = str_replace("\r\n", "\n", $string);
622: return addcslashes($string, "\0..\37\\\"");
623: }
624: 625: 626: 627: 628: 629: 630: 631: 632:
633: function __markerError($file, $line, $marker, $count) {
634: $this->out("Invalid marker content in $file:$line\n* $marker(", true);
635: $count += 2;
636: $tokenCount = count($this->__tokens);
637: $parenthesis = 1;
638:
639: while ((($tokenCount - $count) > 0) && $parenthesis) {
640: if (is_array($this->__tokens[$count])) {
641: $this->out($this->__tokens[$count][1], false);
642: } else {
643: $this->out($this->__tokens[$count], false);
644: if ($this->__tokens[$count] == '(') {
645: $parenthesis++;
646: }
647:
648: if ($this->__tokens[$count] == ')') {
649: $parenthesis--;
650: }
651: }
652: $count++;
653: }
654: $this->out("\n", true);
655: }
656: 657: 658: 659: 660: 661: 662:
663: function __searchDirectory($path = null) {
664: if ($path === null) {
665: $path = $this->path .DS;
666: }
667: $files = glob("$path*.{php,ctp,thtml,inc,tpl}", GLOB_BRACE);
668: $dirs = glob("$path*", GLOB_ONLYDIR);
669:
670: $files = $files ? $files : array();
671: $dirs = $dirs ? $dirs : array();
672:
673: foreach ($dirs as $dir) {
674: if (!preg_match("!(^|.+/)(CVS|.svn)$!", $dir)) {
675: $files = array_merge($files, $this->__searchDirectory("$dir" . DS));
676: if (($id = array_search($dir . DS . 'extract.php', $files)) !== FALSE) {
677: unset($files[$id]);
678: }
679: }
680: }
681: return $files;
682: }
683: }
684: ?>