summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Staudacher <andy.st@gmail.com>2009-02-19 07:20:26 +0000
committerAndy Staudacher <andy.st@gmail.com>2009-02-19 07:20:26 +0000
commitd47e4c9b43aca6092c1555c392ae072d449bb888 (patch)
tree239b119acc5c05b86bff806e6a4abee82937e3d3
parentdcc42328b12839b43ca52058eeab1106e094e8e1 (diff)
Adding a l10n scanner - scans for t() and t2() calls, inserts the extracted messages into incoming_translations.
See: https://apps.sourceforge.net/trac/gallery/ticket/74
-rw-r--r--core/libraries/L10n_Scanner.php194
1 files changed, 194 insertions, 0 deletions
diff --git a/core/libraries/L10n_Scanner.php b/core/libraries/L10n_Scanner.php
new file mode 100644
index 00000000..87fdd7e3
--- /dev/null
+++ b/core/libraries/L10n_Scanner.php
@@ -0,0 +1,194 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2008 Bharat Mediratta
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * Scans all source code for messages that need to be localized.
+ */
+class L10n_Scanner_Core {
+ // Based on Drupal's potx module, originally written by:
+ // G‡bor Hojtsy http://drupal.org/user/4166
+
+ private static $_instance;
+
+ private $_index_keys;
+
+ private function __construct() {}
+
+ static function instance() {
+ if (self::$_instance == null) {
+ self::$_instance = new L10n_Scanner_Core();
+ }
+
+ return self::$_instance;
+ }
+
+ // TODO(andy_st): Report progress via callback
+ function update_index() {
+ // Load the current index into memory
+ $this->_index_keys = array();
+ foreach (Database::instance()
+ ->select("key")
+ ->from("incoming_translations")
+ ->where(array("locale" => "root"))
+ ->get()
+ ->as_array() as $row) {
+ $this->_index_keys[$row->key] = true;
+ }
+
+ // Index all files
+ $dir = new L10n_Scanner_File_Filter_Iterator(
+ new RecursiveIteratorIterator(
+ new L10n_Scanner_Directory_Filter_Iterator(
+ new RecursiveDirectoryIterator(DOCROOT))));
+ foreach ($dir as $file) {
+ $this->_scan_file($file, $this);
+ }
+ }
+
+ function process_message($message) {
+ $key = I18n::get_message_key($message);
+ if (!isset($this->_index_keys[$key])) {
+ $entry = ORM::factory("incoming_translation");
+ $entry->key = $key;
+ $entry->message = serialize($message);
+ $entry->locale = 'root';
+ $entry->save();
+
+ $this->_index_keys[$key] = true;
+ }
+ }
+
+ private function _scan_file($file, &$message_handler) {
+ $code = file_get_contents($file);
+ $raw_tokens = token_get_all($code);
+ unset($code);
+
+ $tokens = array();
+ $func_token_list = array("t" => array(), "t2" => array());
+ $token_number = 0;
+ // Filter out HTML / whitespace, and build a lookup for global function calls.
+ foreach ($raw_tokens as $token) {
+ if ((!is_array($token)) || (($token[0] != T_WHITESPACE) && ($token[0] != T_INLINE_HTML))) {
+ if (is_array($token)) {
+ if ($token[0] == T_STRING && in_array($token[1], array("t", "t2"))) {
+ $func_token_list[$token[1]][] = $token_number;
+ }
+ }
+ $tokens[] = $token;
+ $token_number++;
+ }
+ }
+ unset($raw_tokens);
+
+ if (!empty($func_token_list["t"])) {
+ $this->_parse_t_calls($tokens, $func_token_list["t"], $message_handler);
+ }
+ if (!empty($func_token_list["t2"])) {
+ $this->_parse_plural_calls($tokens, $func_token_list["t2"], $message_handler);
+ }
+ }
+
+ private function _parse_t_calls(&$tokens, &$call_list, &$message_handler) {
+ foreach ($call_list as $index) {
+ $function_name = $tokens[$index++];
+ $parens = $tokens[$index++];
+ $first_param = $tokens[$index++];
+ $next_token = $tokens[$index];
+
+ if ($parens == "(") {
+ if (in_array($next_token, array(")", ","))
+ && (is_array($first_param) && ($first_param[0] == T_CONSTANT_ENCAPSED_STRING))) {
+ $message = self::_escape_quoted_string($first_param[1]);
+ $message_handler->process_message($message);
+ } else {
+ // t() found, but inside is something which is not a string literal.
+ // TODO(andy_st): Call status callback with error filename/line.
+ }
+ }
+ }
+ }
+
+ private function _parse_plural_calls(&$tokens, &$call_list, &$message_handler) {
+ foreach ($call_list as $index) {
+ $function_name = $tokens[$index++];
+ $parens = $tokens[$index++];
+ $first_param = $tokens[$index++];
+ $first_separator = $tokens[$index++];
+ $second_param = $tokens[$index++];
+ $next_token = $tokens[$index];
+
+ if ($parens == "(") {
+ if ($first_separator == "," && $next_token == ","
+ && is_array($first_param) && $first_param[0] == T_CONSTANT_ENCAPSED_STRING
+ && is_array($second_param) && $second_param[0] == T_CONSTANT_ENCAPSED_STRING) {
+ $singular = self::_escape_quoted_string($first_param[1]);
+ $plural = self::_escape_quoted_string($first_param[1]);
+ $message_handler->process_message(array("one" => $singular,
+ "other" => $plural));
+ } else {
+ // t2() found, but inside is something which is not a string literal.
+ // TODO(andy_st): Call status callback with error filename/line.
+ }
+ }
+ }
+ }
+
+ /**
+ * Escape quotes in a strings depending on the surrounding
+ * quote type used.
+ *
+ * @param $str The strings to escape
+ */
+ private static function _escape_quoted_string($str) {
+ $quo = substr($str, 0, 1);
+ $str = substr($str, 1, -1);
+ if ($quo == '"') {
+ $str = stripcslashes($str);
+ } else {
+ $str = strtr($str, array("\\'" => "'", "\\\\" => "\\"));
+ }
+ return addcslashes($str, "\0..\37\\\"");
+ }
+}
+
+class L10n_Scanner_Directory_Filter_Iterator extends RecursiveFilterIterator {
+ function accept() {
+ if ($this->getInnerIterator()->isFile()) {
+ return true;
+ }
+
+ // Skip anything that doesn't need to be localized.
+ $folder = $this->getInnerIterator()->getFilename();
+ $path_name = $this->getInnerIterator()->getPathName();
+ return !(
+ $folder == ".svn" ||
+ $folder == "tests" ||
+ strpos($path_name, DOCROOT . "var") !== false ||
+ strpos($path_name . "/", SYSPATH) !== false);
+ }
+}
+
+class L10n_Scanner_File_Filter_Iterator extends FilterIterator {
+ function accept() {
+ // Skip anything that doesn't need to be localized.
+ $filename = $this->getInnerIterator()->getFilename();
+ return substr($filename, -4, 4) == ".php";
+ }
+} \ No newline at end of file