├── Examples ├── testfile.phtml ├── PHP.php ├── NetteLatte.php ├── GettextExtractor.php ├── testfile.php ├── console.php └── NetteGettextExtractor.php ├── Readme.txt ├── Filters ├── iFilter.php ├── NetteLatteFilter.php └── PHPFilter.php ├── NetteGettextExtractor.php └── GettextExtractor.php /Examples/testfile.phtml: -------------------------------------------------------------------------------- 1 |
PHPFilter test"; 10 | var_dump($data); 11 | echo ""; -------------------------------------------------------------------------------- /Examples/NetteLatte.php: -------------------------------------------------------------------------------- 1 | extract(dirname(__FILE__) . '/testfile.phtml'); 8 | 9 | echo "
NetteLatteFilter test\n"; 10 | var_dump($data); 11 | echo ""; -------------------------------------------------------------------------------- /Examples/GettextExtractor.php: -------------------------------------------------------------------------------- 1 | '; 4 | echo 'GettextExtractor test'; 5 | 6 | require_once dirname(__FILE__) . '/../GettextExtractor.php'; 7 | 8 | $ge = new GettextExtractor(); 9 | 10 | $ge->scan(array('testfile.php', 'testfile.phtml')); 11 | 12 | $ge->save('output.po'); 13 | 14 | echo ''; -------------------------------------------------------------------------------- /Examples/testfile.php: -------------------------------------------------------------------------------- 1 | translate("I see %d little indians!", 10); 4 | 5 | echo _("Escaping some \"fancy\" text"); 6 | 7 | 8 | // PHPFilter Nette Framework integration 9 | $form = new Form(); 10 | $form->addText('name', 'Your name:'); 11 | $form->addSubmit('ok', 'Send') 12 | ->onClick[] = 'OkClicked'; // nebo 'OkClickHandler' 13 | $form->addSubmit('cancel', 'Cancel'); 14 | 15 | 16 | ?> -------------------------------------------------------------------------------- /Examples/console.php: -------------------------------------------------------------------------------- 1 | scan($inputResource); 13 | 14 | $ge->save($outputFile); 15 | 16 | echo 'OK!'; 17 | 18 | //var_dump($data); -------------------------------------------------------------------------------- /Examples/NetteGettextExtractor.php: -------------------------------------------------------------------------------- 1 | '; 4 | echo 'NetteGettextExtractor test'; 5 | 6 | require_once dirname(__FILE__) . '/../NetteGettextExtractor.php'; 7 | 8 | $ge = new NetteGettextExtractor(); 9 | 10 | $ge->setupForms(); 11 | $ge->setupDataGrid(); 12 | 13 | // $ge->scan(APP_DIR); 14 | $ge->scan(array('testfile.php', 'testfile.phtml')); 15 | 16 | // $ge->save(APP_DIR . '/locale/application.po'); 17 | $ge->save('output_nette.po'); 18 | 19 | echo ''; -------------------------------------------------------------------------------- /Filters/iFilter.php: -------------------------------------------------------------------------------- 1 | prefixes = array_flip($this->prefixes); 38 | } 39 | 40 | /** 41 | * Includes a prefix to match in { } 42 | * @param string $prefix 43 | * @return NetteLatteFilter 44 | */ 45 | public function addPrefix($prefix) { 46 | $this->prefixes[$prefix] = TRUE; 47 | return $this; 48 | } 49 | 50 | /** 51 | * Excludes a prefix from { } 52 | * @param string $prefix 53 | * @return NetteLatteFilter 54 | */ 55 | public function removePrefix($prefix) { 56 | unset($this->prefixes[$prefix]); 57 | return $this; 58 | } 59 | 60 | /** 61 | * Removes backslashes from before primes and double primes in primed or double primed strings respectively 62 | * @return string 63 | */ 64 | public function fixEscaping($string) 65 | { 66 | $prime = substr($string, 0, 1); 67 | $string = str_replace('\\' . $prime, $prime, $string); 68 | 69 | return $string; 70 | } 71 | 72 | /** 73 | * Parses given file and returns found gettext phrases 74 | * @param string $file 75 | * @return array 76 | */ 77 | public function extract($file) 78 | { 79 | $pInfo = pathinfo($file); 80 | if (!count($this->prefixes)) return; 81 | $data = array(); 82 | // parse file by lines 83 | foreach (file($file) as $line => $contents) { 84 | $prefixes = join('|', array_keys($this->prefixes)); 85 | // match all {!_ ... } or {_ ... } tags if prefixes are "!_" and "_" 86 | preg_match_all(str_replace('__PREFIXES__', $prefixes, self::LATTE_REGEX), $contents, $matches); 87 | 88 | if (empty($matches)) continue; 89 | if (empty($matches[2])) continue; 90 | 91 | foreach ($matches[2] as $m) { 92 | // strips trailing apostrophes or double quotes 93 | $data[substr($this->fixEscaping($m), 1, -1)][] = $pInfo['basename'] . ':' . $line; 94 | } 95 | } 96 | return $data; 97 | } 98 | } -------------------------------------------------------------------------------- /Filters/PHPFilter.php: -------------------------------------------------------------------------------- 1 | 1, 25 | '_' => 1 26 | ); 27 | 28 | /** 29 | * Includes a function to parse gettext phrases from 30 | * @param $functionName 31 | * @param $argumentPosition 32 | * @return PHPFilter 33 | */ 34 | public function addFunction($functionName, $argumentPosition = 1) 35 | { 36 | $this->functions[$functionName] = ceil((int) $argumentPosition); 37 | return $this; 38 | } 39 | 40 | /** 41 | * Excludes a function from the function list 42 | * @param $functionName 43 | * @return PHPFilter 44 | */ 45 | public function removeFunction($functionName) 46 | { 47 | unset($this->functions[$functionName]); 48 | return $this; 49 | } 50 | 51 | /** 52 | * Excludes all functions from the function list 53 | * @return PHPFilter 54 | */ 55 | public function removeAllFunctions() 56 | { 57 | $this->functions = array(); 58 | return $this; 59 | } 60 | 61 | /** 62 | * Removes backslashes from before primes and double primes in primed or double primed strings respectively 63 | * @return string 64 | */ 65 | public function fixEscaping($string) 66 | { 67 | $prime = substr($string, 0, 1); 68 | $string = str_replace('\\' . $prime, $prime, $string); 69 | 70 | return $string; 71 | } 72 | 73 | /** 74 | * Parses given file and returns found gettext phrases 75 | * @param string $file 76 | * @return array 77 | */ 78 | public function extract($file) 79 | { 80 | $pInfo = pathinfo($file); 81 | $data = array(); 82 | $tokens = token_get_all(file_get_contents($file)); 83 | $next = false; 84 | foreach ($tokens as $c) 85 | { 86 | if(is_array($c)) { 87 | if ($c[0] != T_STRING && $c[0] != T_CONSTANT_ENCAPSED_STRING) continue; 88 | if ($c[0] == T_STRING && isset($this->functions[$c[1]])) { 89 | $next = $this->functions[$c[1]]; 90 | continue; 91 | } 92 | if ($c[0] == T_CONSTANT_ENCAPSED_STRING && $next == 1) { 93 | $data[substr($this->fixEscaping($c[1]), 1, -1)][] = $pInfo['basename'] . ':' . $c[2]; 94 | $next = false; 95 | } 96 | } else { 97 | if ($c == ')') $next = false; 98 | if ($c == ',' && $next != false) $next -= 1; 99 | } 100 | } 101 | return $data; 102 | } 103 | } -------------------------------------------------------------------------------- /NetteGettextExtractor.php: -------------------------------------------------------------------------------- 1 | 1, 33 | '_' => 1, 34 | // form translations 35 | 'setText' => 1, 36 | 'setEmptyValue' => 1, 37 | 'addButton' => 2, 38 | 'addCheckbox' => 2, 39 | 'addError' => 1, 40 | 'addFile' => 2, 41 | 'addGroup' => 1, 42 | 'addImage' => 3, 43 | 'addMultiSelect' => 2, 44 | 'addPassword' => 2, 45 | 'addRadioList' => 2, 46 | 'addRule' => 2, 47 | 'addSelect' => 2, 48 | 'addSubmit' => 2, 49 | 'addText' => 2, 50 | 'addTextArea' => 2 51 | ); 52 | 53 | /** 54 | * Setup mandatory filters 55 | * @param string|bool $logToFile 56 | */ 57 | public function __construct($logToFile = FALSE) 58 | { 59 | parent::__construct($logToFile); 60 | 61 | // Clean up... 62 | $this->removeAllFilters(); 63 | 64 | // Set basic filters 65 | $this->setFilter('php', 'PHP') 66 | ->setFilter('phtml', 'PHP') 67 | ->setFilter('phtml', 'NetteLatte') 68 | ->setFilter('latte', 'NetteLatte') 69 | ->setFilter('latte', 'PHP'); 70 | 71 | $php = $this->getFilter('PHP'); 72 | $php->addFunction('translate') 73 | ->addFunction('_'); 74 | 75 | $this->getFilter('NetteLatte')->addPrefix('!_')->addPrefix('_'); 76 | } 77 | 78 | /** 79 | * Optional setup of Forms translations 80 | * @return NetteGettextExtractor 81 | */ 82 | public function setupForms() 83 | { 84 | $php = $this->getFilter('PHP'); 85 | $php->addFunction('setText') 86 | ->addFunction('setEmptyValue') 87 | ->addFunction('setValue') 88 | ->addFunction('addButton', 2) 89 | ->addFunction('addCheckbox', 2) 90 | ->addFunction('addError') 91 | ->addFunction('addFile', 2) 92 | ->addFunction('addGroup') 93 | ->addFunction('addImage', 3) 94 | ->addFunction('addmultiSelect', 2) 95 | ->addFunction('addPassword', 2) 96 | ->addFunction('addRadioList', 2) 97 | ->addFunction('addRule', 2) 98 | ->addFunction('addSelect', 2) 99 | ->addFunction('addSubmit', 2) 100 | ->addFunction('addText', 2) 101 | ->addFunction('addTextArea', 2); 102 | 103 | return $this; 104 | } 105 | 106 | /** 107 | * Optional setup of DataGrid component translations 108 | * @return NetteGettextExtractor 109 | */ 110 | public function setupDataGrid() 111 | { 112 | $php = $this->getFilter('PHP'); 113 | $php->addFunction('addColumn', 2) 114 | ->addFunction('addNumericColumn', 2) 115 | ->addFunction('addDateColumn', 2) 116 | ->addFunction('addCheckboxColumn', 2) 117 | ->addFunction('addImageColumn', 2) 118 | ->addFunction('addPositionColumn', 2) 119 | ->addFunction('addActionColumn') 120 | ->addFunction('addAction'); 121 | 122 | return $this; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /GettextExtractor.php: -------------------------------------------------------------------------------- 1 | array('PHP'), 39 | 'phtml' => array('PHP', 'NetteLatte') 40 | ); 41 | /** @var array */ 42 | protected $comments = array( 43 | 'Gettext keys exported by GettextExtractor' 44 | ); 45 | /** @var array */ 46 | protected $meta = array( 47 | 'Content-Type' => 'text/plain; charset=UTF-8', 48 | 'Plural-Forms' => 'nplurals=2; plural=(n != 1);' 49 | ); 50 | /** @var array */ 51 | protected $data = array(); 52 | /** @var array */ 53 | protected $filterStore = array(); 54 | 55 | /** 56 | * Log setup 57 | * @param string|bool $logToFile Bool or path of custom log file 58 | */ 59 | public function __construct($logToFile = false) 60 | { 61 | if (is_string($logToFile)) { // custom log file 62 | $this->logHandler = fopen($logToFile, "w"); 63 | } elseif ($logToFile) { // default log file 64 | $this->logHandler = fopen(dirname(__FILE__) . self::LOG_FILE, "w"); 65 | } 66 | } 67 | 68 | /** 69 | * Close the log hangdler if needed 70 | */ 71 | public function __destruct() 72 | { 73 | if (is_resource($this->logHandler)) fclose($this->logHandler); 74 | } 75 | 76 | /** 77 | * Writes messages into log or dumps them on screen 78 | * @param string $message 79 | */ 80 | public function log($message) 81 | { 82 | if (is_resource($this->logHandler)) { 83 | fwrite($this->logHandler, $message . "\n"); 84 | } else { 85 | echo $message . "\n"; 86 | } 87 | } 88 | 89 | /** 90 | * Exception factory 91 | * @param string $message 92 | * @throws Exception 93 | */ 94 | protected function throwException($message) 95 | { 96 | $message = $message ? $message : 'Something unexpected occured. See GettextExtractor log for details'; 97 | $this->log($message); 98 | //echo $message; 99 | throw new Exception($message); 100 | } 101 | 102 | /** 103 | * Scans given files or directories and extracts gettext keys from the content 104 | * @param string|array $resource 105 | * @return GettetExtractor 106 | */ 107 | public function scan($resource) 108 | { 109 | $this->inputFiles = array(); 110 | if (!is_array($resource)) $resource = array($resource); 111 | foreach ($resource as $item) { 112 | $this->log("Scanning '$item'"); 113 | $this->_scan($item); 114 | } 115 | $this->_extract($this->inputFiles); 116 | return $this; 117 | } 118 | 119 | /** 120 | * Scans given files or directories (recursively) and stores extracted gettext keys in a buffer 121 | * @param string $resource File or directory 122 | */ 123 | protected function _scan($resource) 124 | { 125 | if (!is_dir($resource) && !is_file($resource)) { 126 | $this->throwException("Resource '$resource' is not a directory or file"); 127 | } 128 | 129 | if (is_file($resource)) { 130 | $this->inputFiles[] = realpath($resource); 131 | return; 132 | } 133 | 134 | // It's a directory 135 | $resource = realpath($resource); 136 | if (!$resource) return; 137 | $iterator = dir($resource); 138 | if (!$iterator) return; 139 | 140 | while (FALSE !== ($entry = $iterator->read())) { 141 | if ($entry == '.' || $entry == '..') continue; 142 | if ($entry[0] == '.') continue; // do not browse into .git directories 143 | 144 | $path = $resource . '/' . $entry; 145 | if (!is_readable($path)) continue; 146 | 147 | if (is_dir($path)) { 148 | $this->_scan($path); 149 | continue; 150 | } 151 | 152 | if (is_file($path)) { 153 | $info = pathinfo($path); 154 | if (!isset($info['extension'])) continue; // "lockfile" has no extension.. raises notice 155 | if (!isset($this->filters[$info['extension']])) continue; 156 | $this->inputFiles[] = realpath($path); 157 | } 158 | } 159 | 160 | $iterator->close(); 161 | 162 | } 163 | 164 | /** 165 | * Extracts gettext keys from input files 166 | * @param array $inputFiles 167 | * @return array 168 | */ 169 | protected function _extract($inputFiles) 170 | { 171 | $inputFiles = array_unique($inputFiles); 172 | foreach ($inputFiles as $inputFile) 173 | { 174 | if (!file_exists($inputFile)) { 175 | $this->throwException('ERROR: Invalid input file specified: ' . $inputFile); 176 | } 177 | if (!is_readable($inputFile)) { 178 | $this->throwException('ERROR: Input file is not readable: ' . $inputFile); 179 | } 180 | 181 | $this->log('Extracting data from file ' . $inputFile); 182 | foreach ($this->filters as $extension => $filters) 183 | { 184 | // Check file extension 185 | $info = pathinfo($inputFile); 186 | if ($info['extension'] !== $extension) continue; 187 | 188 | $this->log('Processing file ' . $inputFile); 189 | 190 | foreach ($filters as $filterName) 191 | { 192 | $filter = $this->getFilter($filterName); 193 | $filterData = $filter->extract($inputFile); 194 | $this->log(' Filter ' . $filterName . ' applied'); 195 | $this->data = array_merge_recursive($this->data, $filterData); 196 | } 197 | } 198 | } 199 | return $this->data; 200 | } 201 | 202 | /** 203 | * Gets an instance of a GettextExtractor filter 204 | * @param string $filter 205 | * @return iFilter 206 | */ 207 | public function getFilter($filter) 208 | { 209 | $filter = $filter . 'Filter'; 210 | 211 | if (isset($this->filterStore[$filter])) return $this->filterStore[$filter]; 212 | 213 | if (!class_exists($filter)) { 214 | $filter_file = dirname(__FILE__) . '/Filters/' . $filter . ".php"; 215 | if (!file_exists($filter_file)) { 216 | $this->throwException('ERROR: Filter file ' . $filter_file . ' not found'); 217 | } 218 | require_once $filter_file; 219 | if (!class_exists($filter)) { 220 | $this->throwException('ERROR: Class ' . $filter . ' not found'); 221 | } 222 | } 223 | 224 | $this->filterStore[$filter] = new $filter; 225 | $this->log('Filter ' . $filter . ' loaded'); 226 | return $this->filterStore[$filter]; 227 | } 228 | 229 | /** 230 | * Assigns a filter to an extension 231 | * @param string $extension 232 | * @param string $filter 233 | * @return GettextExtractor 234 | */ 235 | public function setFilter($extension, $filter) 236 | { 237 | if (isset($this->filters[$extension]) && in_array($filter, $this->filters[$extension])) return $this; 238 | $this->filters[$extension][] = $filter; 239 | return $this; 240 | } 241 | 242 | /** 243 | * Removes all filter settings in case we want to define a brand new one 244 | * @return GettextExtractor 245 | */ 246 | public function removeAllFilters() 247 | { 248 | $this->filters = array(); 249 | return $this; 250 | } 251 | 252 | /** 253 | * Adds a comment to the top of the output file 254 | * @param string $value 255 | * @return GettextExtractor 256 | */ 257 | public function addComment($value) { 258 | $this->comments[] = $value; 259 | return $this; 260 | } 261 | 262 | /** 263 | * Gets a value of a meta key 264 | * @param string $key 265 | */ 266 | public function getMeta($key) 267 | { 268 | return isset($this->meta[$key]) ? $this->meta[$key] : NULL; 269 | } 270 | 271 | /** 272 | * Sets a value of a meta key 273 | * @param string $key 274 | * @param string $value 275 | * @return GettextExtractor 276 | */ 277 | public function setMeta($key, $value) 278 | { 279 | $this->meta[$key] = $value; 280 | return $this; 281 | } 282 | 283 | /** 284 | * Saves extracted data into gettext file 285 | * @param string $outputFile 286 | * @param array $data 287 | * @return GettextExtractor 288 | */ 289 | public function save($outputFile, $data = null) 290 | { 291 | $data = $data ? $data : $this->data; 292 | 293 | // Output file permission check 294 | if (file_exists($outputFile) && !is_writable($outputFile)) { 295 | $this->throwException('ERROR: Output file is not writable!'); 296 | } 297 | 298 | $handle = fopen($outputFile, "w"); 299 | 300 | fwrite($handle, $this->formatData($data)); 301 | 302 | fclose($handle); 303 | 304 | $this->log("Output file '$outputFile' created"); 305 | 306 | return $this; 307 | } 308 | 309 | /** 310 | * Formats fetched data to gettext syntax 311 | * @param array $data 312 | * @return string 313 | */ 314 | protected function formatData($data) 315 | { 316 | $output = array(); 317 | foreach ($this->comments as $comment) { 318 | $output[] = '# ' . $comment; 319 | } 320 | $output[] = '# Created: ' . date('c'); 321 | $output[] = 'msgid ""'; 322 | $output[] = 'msgstr ""'; 323 | foreach ($this->meta as $key => $value) { 324 | $output[] = '"' . $key . ': ' . $value . '\n"'; 325 | } 326 | $output[] = ''; 327 | 328 | ksort($data); 329 | 330 | foreach ($data as $key => $files) 331 | { 332 | ksort($files); 333 | foreach ($files as $file) 334 | $output[] = '# ' . $file; 335 | $output[] = 'msgid "' . $this->addSlashes($key) . '"'; 336 | /*if (preg_match($this->pluralMatchRegexp, $key, $matches)) { // TODO: really export plurals? deprecated for now 337 | $output[] = 'msgid_plural "' . addslashes($key) . '"'; 338 | //$output[] = 'msgid_plural ""'; 339 | $output[] = 'msgstr[0] "' . addslashes($key) . '"'; 340 | $output[] = 'msgstr[1] "' . addslashes($key) . '"'; 341 | } else { 342 | $output[] = 'msgstr "' . addslashes($key) . '"'; 343 | }*/ 344 | $output[] = 'msgstr "' . $this->addSlashes($key) . '"'; 345 | $output[] = ''; 346 | } 347 | 348 | return join("\n", $output); 349 | } 350 | 351 | /** 352 | * Escape a sring not to break the gettext syntax 353 | * @param string $string 354 | * @return string 355 | */ 356 | public function addSlashes($string) 357 | { 358 | return addcslashes($string, self::ESCAPE_CHARS); 359 | } 360 | 361 | } --------------------------------------------------------------------------------