Diff
checker
文本
文本
圖像
文檔
Excel
文件夾
Legal
Enterprise
桌面版
定價
登入
下載 Diffchecker 桌面版
比較文本
尋找兩個文字檔案之間的差異
工具
歷史
即時編輯器
摺疊未變更行
關閉換行
檢視
拆分
統一
比對精度
智能
單詞
字符
語法突出顯示
選擇語法
忽略
文字轉換
前往第一個差異
編輯輸入
Diffchecker Desktop
執行Diffchecker最安全的方式。取得Diffchecker桌面應用程式:您的差異永遠不會離開您的電腦!
取得桌面版
Untitled diff
建立於
8 年前
差異永不過期
清除
匯出
分享
解釋
15 刪除
行
總計
刪除
字符
總計
刪除
要繼續使用此功能,請升級到
Diff
checker
Pro
查看價格
728 行
全部複製
17 新增
行
總計
新增
字符
總計
新增
要繼續使用此功能,請升級到
Diff
checker
Pro
查看價格
731 行
全部複製
<?php
<?php
/**
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
*
* Licensed under The MIT License
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
* Redistributions of files must retain the above copyright notice.
*
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
*/
namespace Cake\Shell\Task;
namespace Cake\Shell\Task;
use Cake\Console\Shell;
use Cake\Console\Shell;
use Cake\Core\App;
use Cake\Core\App;
use Cake\Core\Plugin;
use Cake\Core\Plugin;
use Cake\Filesystem\File;
use Cake\Filesystem\File;
use Cake\Filesystem\Folder;
use Cake\Filesystem\Folder;
use Cake\Utility\Inflector;
use Cake\Utility\Inflector;
/**
/**
* Language string extractor
* Language string extractor
*/
*/
class ExtractTask extends Shell
class ExtractTask extends Shell
{
{
/**
/**
* Paths to use when looking for strings
* Paths to use when looking for strings
*
*
* @var array
* @var array
*/
*/
protected $_paths = [];
protected $_paths = [];
/**
/**
* Files from where to extract
* Files from where to extract
*
*
* @var array
* @var array
*/
*/
protected $_files = [];
protected $_files = [];
/**
/**
* Merge all domain strings into the default.pot file
* Merge all domain strings into the default.pot file
*
*
* @var bool
* @var bool
*/
*/
protected $_merge = false;
protected $_merge = false;
/**
/**
複製
已複製
複製
已複製
* Use relative paths in the pot files rather than full path
*
* @var bool
*/
protected $_relativePaths = false;
/**
* Current file being processed
* Current file being processed
*
*
* @var string|null
* @var string|null
*/
*/
protected $_file;
protected $_file;
/**
/**
* Contains all content waiting to be write
* Contains all content waiting to be write
*
*
* @var array
* @var array
*/
*/
protected $_storage = [];
protected $_storage = [];
/**
/**
* Extracted tokens
* Extracted tokens
*
*
* @var array
* @var array
*/
*/
protected $_tokens = [];
protected $_tokens = [];
/**
/**
* Extracted strings indexed by domain.
* Extracted strings indexed by domain.
*
*
* @var array
* @var array
*/
*/
protected $_translations = [];
protected $_translations = [];
/**
/**
* Destination path
* Destination path
*
*
* @var string|null
* @var string|null
*/
*/
protected $_output;
protected $_output;
/**
/**
* An array of directories to exclude.
* An array of directories to exclude.
*
*
* @var array
* @var array
*/
*/
protected $_exclude = [];
protected $_exclude = [];
/**
/**
* Holds the validation string domain to use for validation messages when extracting
* Holds the validation string domain to use for validation messages when extracting
*
*
* @var string
* @var string
*/
*/
protected $_validationDomain = 'default';
protected $_validationDomain = 'default';
/**
/**
* Holds whether this call should extract the CakePHP Lib messages
* Holds whether this call should extract the CakePHP Lib messages
*
*
* @var bool
* @var bool
*/
*/
protected $_extractCore = false;
protected $_extractCore = false;
/**
/**
* No welcome message.
* No welcome message.
*
*
* @return void
* @return void
*/
*/
protected function _welcome()
protected function _welcome()
{
{
}
}
/**
/**
* Method to interact with the User and get path selections.
* Method to interact with the User and get path selections.
*
*
* @return void
* @return void
*/
*/
protected function _getPaths()
protected function _getPaths()
{
{
$defaultPath = APP;
$defaultPath = APP;
while (true) {
while (true) {
$currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None'];
$currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None'];
$message = sprintf(
$message = sprintf(
"Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one",
"Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one",
implode(', ', $currentPaths)
implode(', ', $currentPaths)
);
);
$response = $this->in($message, null, $defaultPath);
$response = $this->in($message, null, $defaultPath);
if (strtoupper($response) === 'Q') {
if (strtoupper($response) === 'Q') {
$this->err('Extract Aborted');
$this->err('Extract Aborted');
$this->_stop();
$this->_stop();
return;
return;
}
}
if (strtoupper($response) === 'D' && count($this->_paths)) {
if (strtoupper($response) === 'D' && count($this->_paths)) {
$this->out();
$this->out();
return;
return;
}
}
if (strtoupper($response) === 'D') {
if (strtoupper($response) === 'D') {
$this->warn('No directories selected. Please choose a directory.');
$this->warn('No directories selected. Please choose a directory.');
} elseif (is_dir($response)) {
} elseif (is_dir($response)) {
$this->_paths[] = $response;
$this->_paths[] = $response;
$defaultPath = 'D';
$defaultPath = 'D';
} else {
} else {
$this->err('The directory path you supplied was not found. Please try again.');
$this->err('The directory path you supplied was not found. Please try again.');
}
}
$this->out();
$this->out();
}
}
}
}
/**
/**
* Execution method always used for tasks
* Execution method always used for tasks
*
*
* @return void
* @return void
*/
*/
public function main()
public function main()
{
{
if (!empty($this->params['exclude'])) {
if (!empty($this->params['exclude'])) {
$this->_exclude = explode(',', $this->params['exclude']);
$this->_exclude = explode(',', $this->params['exclude']);
}
}
if (isset($this->params['files']) && !is_array($this->params['files'])) {
if (isset($this->params['files']) && !is_array($this->params['files'])) {
$this->_files = explode(',', $this->params['files']);
$this->_files = explode(',', $this->params['files']);
}
}
if (isset($this->params['paths'])) {
if (isset($this->params['paths'])) {
$this->_paths = explode(',', $this->params['paths']);
$this->_paths = explode(',', $this->params['paths']);
} elseif (isset($this->params['plugin'])) {
} elseif (isset($this->params['plugin'])) {
$plugin = Inflector::camelize($this->params['plugin']);
$plugin = Inflector::camelize($this->params['plugin']);
if (!Plugin::loaded($plugin)) {
if (!Plugin::loaded($plugin)) {
Plugin::load($plugin);
Plugin::load($plugin);
}
}
$this->_paths = [Plugin::classPath($plugin)];
$this->_paths = [Plugin::classPath($plugin)];
$this->params['plugin'] = $plugin;
$this->params['plugin'] = $plugin;
} else {
} else {
$this->_getPaths();
$this->_getPaths();
}
}
if (isset($this->params['extract-core'])) {
if (isset($this->params['extract-core'])) {
$this->_extractCore = !(strtolower($this->params['extract-core']) === 'no');
$this->_extractCore = !(strtolower($this->params['extract-core']) === 'no');
} else {
} else {
$response = $this->in('Would you like to extract the messages from the CakePHP core?', ['y', 'n'], 'n');
$response = $this->in('Would you like to extract the messages from the CakePHP core?', ['y', 'n'], 'n');
$this->_extractCore = strtolower($response) === 'y';
$this->_extractCore = strtolower($response) === 'y';
}
}
if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) {
if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) {
$this->_exclude = array_merge($this->_exclude, App::path('Plugin'));
$this->_exclude = array_merge($this->_exclude, App::path('Plugin'));
}
}
if (!empty($this->params['validation-domain'])) {
if (!empty($this->params['validation-domain'])) {
$this->_validationDomain = $this->params['validation-domain'];
$this->_validationDomain = $this->params['validation-domain'];
}
}
if ($this->_extractCore) {
if ($this->_extractCore) {
$this->_paths[] = CAKE;
$this->_paths[] = CAKE;
}
}
if (isset($this->params['output'])) {
if (isset($this->params['output'])) {
$this->_output = $this->params['output'];
$this->_output = $this->params['output'];
} elseif (isset($this->params['plugin'])) {
} elseif (isset($this->params['plugin'])) {
$this->_output = $this->_paths[0] . 'Locale';
$this->_output = $this->_paths[0] . 'Locale';
} else {
} else {
$message = "What is the path you would like to output?\n[Q]uit";
$message = "What is the path you would like to output?\n[Q]uit";
while (true) {
while (true) {
$response = $this->in($message, null, rtrim($this->_paths[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'Locale');
$response = $this->in($message, null, rtrim($this->_paths[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'Locale');
if (strtoupper($response) === 'Q') {
if (strtoupper($response) === 'Q') {
$this->err('Extract Aborted');
$this->err('Extract Aborted');
$this->_stop();
$this->_stop();
return;
return;
}
}
if ($this->_isPathUsable($response)) {
if ($this->_isPathUsable($response)) {
$this->_output = $response . DIRECTORY_SEPARATOR;
$this->_output = $response . DIRECTORY_SEPARATOR;
break;
break;
}
}
$this->err('');
$this->err('');
$this->err(
$this->err(
'<error>The directory path you supplied was ' .
'<error>The directory path you supplied was ' .
'not found. Please try again.</error>'
'not found. Please try again.</error>'
);
);
$this->out();
$this->out();
}
}
}
}
if (isset($this->params['merge'])) {
if (isset($this->params['merge'])) {
$this->_merge = !(strtolower($this->params['merge']) === 'no');
$this->_merge = !(strtolower($this->params['merge']) === 'no');
} else {
} else {
$this->out();
$this->out();
$response = $this->in('Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], 'n');
$response = $this->in('Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], 'n');
$this->_merge = strtolower($response) === 'y';
$this->_merge = strtolower($response) === 'y';
}
}
複製
已複製
複製
已複製
if (isset($this->params['relative-paths'])) {
$this->_relativePaths = !(strtolower($this->params['relative-paths']) === 'no');
}
if (empty($this->_files)) {
if (empty($this->_files)) {
$this->_searchFiles();
$this->_searchFiles();
}
}
$this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
if (!$this->_isPathUsable($this->_output)) {
if (!$this->_isPathUsable($this->_output)) {
$this->err(sprintf('The output directory %s was not found or writable.', $this->_output));
$this->err(sprintf('The output directory %s was not found or writable.', $this->_output));
$this->_stop();
$this->_stop();
return;
return;
}
}
$this->_extract();
$this->_extract();
}
}
/**
/**
* Add a translation to the internal translations property
* Add a translation to the internal translations property
*
*
* Takes care of duplicate translations
* Takes care of duplicate translations
*
*
* @param string $domain The domain
* @param string $domain The domain
* @param string $msgid The message string
* @param string $msgid The message string
* @param array $details Context and plural form if any, file and line references
* @param array $details Context and plural form if any, file and line references
* @return void
* @return void
*/
*/
protected function _addTranslation($domain, $msgid, $details = [])
protected function _addTranslation($domain, $msgid, $details = [])
{
{
$context = isset($details['msgctxt']) ? $details['msgctxt'] : '';
$context = isset($details['msgctxt']) ? $details['msgctxt'] : '';
if (empty($this->_translations[$domain][$msgid][$context])) {
if (empty($this->_translations[$domain][$msgid][$context])) {
$this->_translations[$domain][$msgid][$context] = [
$this->_translations[$domain][$msgid][$context] = [
'msgid_plural' => false
'msgid_plural' => false
];
];
}
}
if (isset($details['msgid_plural'])) {
if (isset($details['msgid_plural'])) {
$this->_translations[$domain][$msgid][$context]['msgid_plural'] = $details['msgid_plural'];
$this->_translations[$domain][$msgid][$context]['msgid_plural'] = $details['msgid_plural'];
}
}
if (isset($details['file'])) {
if (isset($details['file'])) {
$line = isset($details['line']) ? $details['line'] : 0;
$line = isset($details['line']) ? $details['line'] : 0;
$this->_translations[$domain][$msgid][$context]['references'][$details['file']][] = $line;
$this->_translations[$domain][$msgid][$context]['references'][$details['file']][] = $line;
}
}
}
}
/**
/**
* Extract text
* Extract text
*
*
* @return void
* @return void
*/
*/
protected function _extract()
protected function _extract()
{
{
$this->out();
$this->out();
$this->out();
$this->out();
$this->out('Extracting...');
$this->out('Extracting...');
$this->hr();
$this->hr();
$this->out('Paths:');
$this->out('Paths:');
foreach ($this->_paths as $path) {
foreach ($this->_paths as $path) {
$this->out(' ' . $path);
$this->out(' ' . $path);
}
}
$this->out('Output Directory: ' . $this->_output);
$this->out('Output Directory: ' . $this->_output);
$this->hr();
$this->hr();
$this->_extractTokens();
$this->_extractTokens();
$this->_buildFiles();
$this->_buildFiles();
$this->_writeFiles();
$this->_writeFiles();
$this->_paths = $this->_files = $this->_storage = [];
$this->_paths = $this->_files = $this->_storage = [];
$this->_translations = $this->_tokens = [];
$this->_translations = $this->_tokens = [];
$this->out();
$this->out();
$this->out('Done.');
$this->out('Done.');
}
}
/**
/**
* Gets the option parser instance and configures it.
* Gets the option parser instance and configures it.
*
*
* @return \Cake\Console\ConsoleOptionParser
* @return \Cake\Console\ConsoleOptionParser
*/
*/
public function getOptionParser()
public function getOptionParser()
{
{
$parser = parent::getOptionParser();
$parser = parent::getOptionParser();
$parser->setDescription(
$parser->setDescription(
'CakePHP Language String Extraction:'
'CakePHP Language String Extraction:'
)->addOption('app', [
)->addOption('app', [
'help' => 'Directory where your application is located.'
'help' => 'Directory where your application is located.'
])->addOption('paths', [
])->addOption('paths', [
'help' => 'Comma separated list of paths.'
'help' => 'Comma separated list of paths.'
複製
已複製
複製
已複製
])->addOption('relative-paths', [
'help' => 'Use relative paths in the .pot file',
'choices' => ['yes', 'no']
])->addOption('merge', [
])->addOption('merge', [
'help' => 'Merge all domain strings into the default.po file.',
'help' => 'Merge all domain strings into the default.po file.',
'choices' => ['yes', 'no']
'choices' => ['yes', 'no']
])->addOption('output', [
])->addOption('output', [
'help' => 'Full path to output directory.'
'help' => 'Full path to output directory.'
])->addOption('files', [
])->addOption('files', [
'help' => 'Comma separated list of files.'
'help' => 'Comma separated list of files.'
])->addOption('exclude-plugins', [
])->addOption('exclude-plugins', [
'boolean' => true,
'boolean' => true,
'default' => true,
'default' => true,
'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.'
'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.'
])->addOption('plugin', [
])->addOption('plugin', [
'help' => 'Extracts tokens only from the plugin specified and puts the result in the plugin\'s Locale directory.'
'help' => 'Extracts tokens only from the plugin specified and puts the result in the plugin\'s Locale directory.'
])->addOption('ignore-model-validation', [
])->addOption('ignore-model-validation', [
'boolean' => true,
'boolean' => true,
'default' => false,
'default' => false,
'help' => 'Ignores validation messages in the $validate property.' .
'help' => 'Ignores validation messages in the $validate property.' .
' If this flag is not set and the command is run from the same app directory,' .
' If this flag is not set and the command is run from the same app directory,' .
' all messages in model validation rules will be extracted as tokens.'
' all messages in model validation rules will be extracted as tokens.'
])->addOption('validation-domain', [
])->addOption('validation-domain', [
'help' => 'If set to a value, the localization domain to be used for model validation messages.'
'help' => 'If set to a value, the localization domain to be used for model validation messages.'
])->addOption('exclude', [
])->addOption('exclude', [
'help' => 'Comma separated list of directories to exclude.' .
'help' => 'Comma separated list of directories to exclude.' .
' Any path containing a path segment with the provided values will be skipped. E.g. test,vendors'
' Any path containing a path segment with the provided values will be skipped. E.g. test,vendors'
])->addOption('overwrite', [
])->addOption('overwrite', [
'boolean' => true,
'boolean' => true,
'default' => false,
'default' => false,
'help' => 'Always overwrite existing .pot files.'
'help' => 'Always overwrite existing .pot files.'
])->addOption('extract-core', [
])->addOption('extract-core', [
'help' => 'Extract messages from the CakePHP core libs.',
'help' => 'Extract messages from the CakePHP core libs.',
'choices' => ['yes', 'no']
'choices' => ['yes', 'no']
])->addOption('no-location', [
])->addOption('no-location', [
'boolean' => true,
'boolean' => true,
'default' => false,
'default' => false,
'help' => 'Do not write file locations for each extracted message.',
'help' => 'Do not write file locations for each extracted message.',
]);
]);
return $parser;
return $parser;
}
}
/**
/**
* Extract tokens out of all files to be processed
* Extract tokens out of all files to be processed
*
*
* @return void
* @return void
*/
*/
protected function _extractTokens()
protected function _extractTokens()
{
{
/** @var \Cake\Shell\Helper\ProgressHelper $progress */
/** @var \Cake\Shell\Helper\ProgressHelper $progress */
$progress = $this->helper('progress');
$progress = $this->helper('progress');
$progress->init(['total' => count($this->_files)]);
$progress->init(['total' => count($this->_files)]);
$isVerbose = $this->param('verbose');
$isVerbose = $this->param('verbose');
foreach ($this->_files as $file) {
foreach ($this->_files as $file) {
$this->_file = $file;
$this->_file = $file;
if ($isVerbose) {
if ($isVerbose) {
$this->out(sprintf('Processing %s...', $file), 1, Shell::VERBOSE);
$this->out(sprintf('Processing %s...', $file), 1, Shell::VERBOSE);
}
}
$code = file_get_contents($file);
$code = file_get_contents($file);
$allTokens = token_get_all($code);
$allTokens = token_get_all($code);
$this->_tokens = [];
$this->_tokens = [];
foreach ($allTokens as $token) {
foreach ($allTokens as $token) {
if (!is_array($token) || ($token[0] !== T_WHITESPACE && $token[0] !== T_INLINE_HTML)) {
if (!is_array($token) || ($token[0] !== T_WHITESPACE && $token[0] !== T_INLINE_HTML)) {
$this->_tokens[] = $token;
$this->_tokens[] = $token;
}
}
}
}
unset($allTokens);
unset($allTokens);
$this->_parse('__', ['singular']);
$this->_parse('__', ['singular']);
$this->_parse('__n', ['singular', 'plural']);
$this->_parse('__n', ['singular', 'plural']);
$this->_parse('__d', ['domain', 'singular']);
$this->_parse('__d', ['domain', 'singular']);
$this->_parse('__dn', ['domain', 'singular', 'plural']);
$this->_parse('__dn', ['domain', 'singular', 'plural']);
$this->_parse('__x', ['context', 'singular']);
$this->_parse('__x', ['context', 'singular']);
$this->_parse('__xn', ['context', 'singular', 'plural']);
$this->_parse('__xn', ['context', 'singular', 'plural']);
$this->_parse('__dx', ['domain', 'context', 'singular']);
$this->_parse('__dx', ['domain', 'context', 'singular']);
$this->_parse('__dxn', ['domain', 'context', 'singular', 'plural']);
$this->_parse('__dxn', ['domain', 'context', 'singular', 'plural']);
if (!$isVerbose) {
if (!$isVerbose) {
$progress->increment(1);
$progress->increment(1);
$progress->draw();
$progress->draw();
}
}
}
}
}
}
/**
/**
* Parse tokens
* Parse tokens
*
*
* @param string $functionName Function name that indicates translatable string (e.g: '__')
* @param string $functionName Function name that indicates translatable string (e.g: '__')
* @param array $map Array containing what variables it will find (e.g: domain, singular, plural)
* @param array $map Array containing what variables it will find (e.g: domain, singular, plural)
* @return void
* @return void
*/
*/
protected function _parse($functionName, $map)
protected function _parse($functionName, $map)
{
{
$count = 0;
$count = 0;
$tokenCount = count($this->_tokens);
$tokenCount = count($this->_tokens);
while (($tokenCount - $count) > 1) {
while (($tokenCount - $count) > 1) {
$countToken = $this->_tokens[$count];
$countToken = $this->_tokens[$count];
$firstParenthesis = $this->_tokens[$count + 1];
$firstParenthesis = $this->_tokens[$count + 1];
if (!is_array($countToken)) {
if (!is_array($countToken)) {
$count++;
$count++;
continue;
continue;
}
}
list($type, $string, $line) = $countToken;
list($type, $string, $line) = $countToken;
if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) {
if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) {
$position = $count;
$position = $count;
$depth = 0;
$depth = 0;
while (!$depth) {
while (!$depth) {
if ($this->_tokens[$position] === '(') {
if ($this->_tokens[$position] === '(') {
$depth++;
$depth++;
} elseif ($this->_tokens[$position] === ')') {
} elseif ($this->_tokens[$position] === ')') {
$depth--;
$depth--;
}
}
$position++;
$position++;
}
}
$mapCount = count($map);
$mapCount = count($map);
$strings = $this->_getStrings($position, $mapCount);
$strings = $this->_getStrings($position, $mapCount);
if ($mapCount === count($strings)) {
if ($mapCount === count($strings)) {
$singular = null;
$singular = null;
extract(array_combine($map, $strings));
extract(array_combine($map, $strings));
$domain = isset($domain) ? $domain : 'default';
$domain = isset($domain) ? $domain : 'default';
$details = [
$details = [
'file' => $this->_file,
'file' => $this->_file,
'line' => $line,
'line' => $line,
];
];
複製
已複製
複製
已複製
if ($this->_relativePaths) {
$details['file'] = '.' . str_replace(ROOT, '', $details['file']);
}
if (isset($plural)) {
if (isset($plural)) {
$details['msgid_plural'] = $plural;
$details['msgid_plural'] = $plural;
}
}
if (isset($context)) {
if (isset($context)) {
$details['msgctxt'] = $context;
$details['msgctxt'] = $context;
}
}
$this->_addTranslation($domain, $singular, $details);
$this->_addTranslation($domain, $singular, $details);
} elseif (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) {
} elseif (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) {
$this->_markerError($this->_file, $line, $functionName, $count);
$this->_markerError($this->_file, $line, $functionName, $count);
}
}
}
}
$count++;
$count++;
}
}
}
}
/**
/**
* Build the translate template file contents out of obtained strings
* Build the translate template file contents out of obtained strings
*
*
* @return void
* @return void
*/
*/
protected function _buildFiles()
protected function _buildFiles()
{
{
$paths = $this->_paths;
$paths = $this->_paths;
$paths[] = realpath(APP) . DIRECTORY_SEPARATOR;
$paths[] = realpath(APP) . DIRECTORY_SEPARATOR;
usort($paths, function ($a, $b) {
usort($paths, function ($a, $b) {
return strlen($a) - strlen($b);
return strlen($a) - strlen($b);
});
});
foreach ($this->_translations as $domain => $translations) {
foreach ($this->_translations as $domain => $translations) {
foreach ($translations as $msgid => $contexts) {
foreach ($translations as $msgid => $contexts) {
foreach ($contexts as $context => $details) {
foreach ($contexts as $context => $details) {
$plural = $details['msgid_plural'];
$plural = $details['msgid_plural'];
$files = $details['references'];
$files = $details['references'];
$occurrences = [];
$occurrences = [];
foreach ($files as $file => $lines) {
foreach ($files as $file => $lines) {
$lines = array_unique($lines);
$lines = array_unique($lines);
$occurrences[] = $file . ':' . implode(';', $lines);
$occurrences[] = $file . ':' . implode(';', $lines);
}
}
$occurrences = implode("\n#: ", $occurrences);
$occurrences = implode("\n#: ", $occurrences);
$header = '';
$header = '';
if (!$this->param('no-location')) {
if (!$this->param('no-location')) {
$header = '#: ' . str_replace(DIRECTORY_SEPARATOR, '/', str_replace($paths, '', $occurrences)) . "\n";
$header = '#: ' . str_replace(DIRECTORY_SEPARATOR, '/', str_replace($paths, '', $occurrences)) . "\n";
}
}
$sentence = '';
$sentence = '';
if ($context !== '') {
if ($context !== '') {
$sentence .= "msgctxt \"{$context}\"\n";
$sentence .= "msgctxt \"{$context}\"\n";
}
}
if ($plural === false) {
if ($plural === false) {
$sentence .= "msgid \"{$msgid}\"\n";
$sentence .= "msgid \"{$msgid}\"\n";
$sentence .= "msgstr \"\"\n\n";
$sentence .= "msgstr \"\"\n\n";
} else {
} else {
$sentence .= "msgid \"{$msgid}\"\n";
$sentence .= "msgid \"{$msgid}\"\n";
$sentence .= "msgid_plural \"{$plural}\"\n";
$sentence .= "msgid_plural \"{$plural}\"\n";
$sentence .= "msgstr[0] \"\"\n";
$sentence .= "msgstr[0] \"\"\n";
$sentence .= "msgstr[1] \"\"\n\n";
$sentence .= "msgstr[1] \"\"\n\n";
}
}
if ($domain !== 'default' && $this->_merge) {
if ($domain !== 'default' && $this->_merge) {
$this->_store('default', $header, $sentence);
$this->_store('default', $header, $sentence);
} else {
} else {
$this->_store($domain, $header, $sentence);
$this->_store($domain, $header, $sentence);
}
}
}
}
}
}
}
}
}
}
/**
/**
* Prepare a file to be stored
* Prepare a file to be stored
*
*
* @param string $domain The domain
* @param string $domain The domain
* @param string $header The header content.
* @param string $header The header content.
* @param string $sentence The sentence to store.
* @param string $sentence The sentence to store.
* @return void
* @return void
*/
*/
protected function _store($domain, $header, $sentence)
protected function _store($domain, $header, $sentence)
{
{
if (!isset($this->_storage[$domain])) {
if (!isset($this->_storage[$domain])) {
$this->_storage[$domain] = [];
$this->_storage[$domain] = [];
}
}
if (!isset($this->_storage[$domain][$sentence])) {
if (!isset($this->_storage[$domain][$sentence])) {
$this->_storage[$domain][$sentence] = $header;
$this->_storage[$domain][$sentence] = $header;
} else {
} else {
$this->_storage[$domain][$sentence] .= $header;
$this->_storage[$domain][$sentence] .= $header;
}
}
}
}
/**
/**
* Write the files that need to be stored
* Write the files that need to be stored
*
*
* @return void
* @return void
*/
*/
protected function _writeFiles()
protected function _writeFiles()
{
{
$overwriteAll = false;
$overwriteAll = false;
if (!empty($this->params['overwrite'])) {
if (!empty($this->params['overwrite'])) {
$overwriteAll = true;
$overwriteAll = true;
}
}
foreach ($this->_storage as $domain => $sentences) {
foreach ($this->_storage as $domain => $sentences) {
$output = $this->_writeHeader();
$output = $this->_writeHeader();
foreach ($sentences as $sentence => $header) {
foreach ($sentences as $sentence => $header) {
$output .= $header . $sentence;
$output .= $header . $sentence;
}
}
// Remove vendor prefix if present.
// Remove vendor prefix if present.
$slashPosition = strpos($domain, '/');
$slashPosition = strpos($domain, '/');
if ($slashPosition !== false) {
if ($slashPosition !== false) {
$domain = substr($domain, $slashPosition + 1);
$domain = substr($domain, $slashPosition + 1);
}
}
$filename = str_replace('/', '_', $domain) . '.pot';
$filename = str_replace('/', '_', $domain) . '.pot';
$File = new File($this->_output . $filename);
$File = new File($this->_output . $filename);
$response = '';
$response = '';
while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
$this->out();
$this->out();
$response = $this->in(
$response = $this->in(
sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename),
sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename),
['y', 'n', 'a'],
['y', 'n', 'a'],
'y'
'y'
);
);
if (strtoupper($response) === 'N') {
if (strtoupper($response) === 'N') {
$response = '';
$response = '';
while (!$response) {
while (!$response) {
$response = $this->in('What would you like to name this file?', null, 'new_' . $filename);
$response = $this->in('What would you like to name this file?', null, 'new_' . $filename);
$File = new File($this->_output . $response);
$File = new File($this->_output . $response);
$filename = $response;
$filename = $response;
}
}
} elseif (strtoupper($response) === 'A') {
} elseif (strtoupper($response) === 'A') {
$overwriteAll = true;
$overwriteAll = true;
}
}
}
}
$File->write($output);
$File->write($output);
$File->close();
$File->close();
}
}
}
}
/**
/**
* Build the translation template header
* Build the translation template header
*
*
* @return string Translation template header
* @return string Translation template header
*/
*/
protected function _writeHeader()
protected function _writeHeader()
{
{
$output = "# LANGUAGE translation of CakePHP Application\n";
$output = "# LANGUAGE translation of CakePHP Application\n";
$output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
$output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
$output .= "#\n";
$output .= "#\n";
$output .= "#, fuzzy\n";
$output .= "#, fuzzy\n";
$output .= "msgid \"\"\n";
$output .= "msgid \"\"\n";
$output .= "msgstr \"\"\n";
$output .= "msgstr \"\"\n";
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
$output .= '"POT-Creation-Date: ' . date('Y-m-d H:iO') . "\\n\"\n";
$output .= '"POT-Creation-Date: ' . date('Y-m-d H:iO') . "\\n\"\n";
$output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
$output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"MIME-Version: 1.0\\n\"\n";
$output .= "\"MIME-Version: 1.0\\n\"\n";
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
$output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
$output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
return $output;
return $output;
}
}
/**
/**
* Get the strings from the position forward
* Get the strings from the position forward
*
*
* @param int $position Actual position on tokens array
* @param int $position Actual position on tokens array
* @param int $target Number of strings to extract
* @param int $target Number of strings to extract
* @return array Strings extracted
* @return array Strings extracted
*/
*/
protected function _getStrings(&$position, $target)
protected function _getStrings(&$position, $target)
{
{
$strings = [];
$strings = [];
$count = count($strings);
$count = count($strings);
while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position][0] == T_LNUMBER)) {
while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position][0] == T_LNUMBER)) {
$count = count($strings);
$count = count($strings);
if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->_tokens[$position + 1] === '.') {
if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->_tokens[$position + 1] === '.') {
$string = '';
$string = '';
while ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position] === '.') {
while ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position] === '.') {
if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
$string .= $this->_formatString($this->_tokens[$position][1]);
$string .= $this->_formatString($this->_tokens[$position][1]);
}
}
$position++;
$position++;
}
}
$strings[] = $string;
$strings[] = $string;
} elseif ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
} elseif ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
$strings[] = $this->_formatString($this->_tokens[$position][1]);
$strings[] = $this->_formatString($this->_tokens[$position][1]);
} elseif ($this->_tokens[$position][0] == T_LNUMBER) {
} elseif ($this->_tokens[$position][0] == T_LNUMBER) {
$strings[] = $this->_tokens[$position][1];
$strings[] = $this->_tokens[$position][1];
}
}
$position++;
$position++;
}
}
return $strings;
return $strings;
}
}
/**
/**
* Format a string to be added as a translatable string
* Format a string to be added as a translatable string
*
*
* @param string $string String to format
* @param string $string String to format
* @return string Formatted string
* @return string Formatted string
*/
*/
protected function _formatString($string)
protected function _formatString($string)
{
{
$quote = substr($string, 0, 1);
$quote = substr($string, 0, 1);
$string = substr($string, 1, -1);
$string = substr($string, 1, -1);
if ($quote === '"') {
if ($quote === '"') {
$string = stripcslashes($string);
$string = stripcslashes($string);
} else {
} else {
$string = strtr($string, ["\\'" => "'", '\\\\' => '\\']);
$string = strtr($string, ["\\'" => "'", '\\\\' => '\\']);
}
}
$string = str_replace("\r\n", "\n", $string);
$string = str_replace("\r\n", "\n", $string);
return addcslashes($string, "\0..\37\\\"");
return addcslashes($string, "\0..\37\\\"");
}
}
/**
/**
* Indicate an invalid marker on a processed file
* Indicate an invalid marker on a processed file
*
*
* @param string $file File where invalid marker resides
* @param string $file File where invalid marker resides
* @param int $line Line number
* @param int $line Line number
* @param string $marker Marker found
* @param string $marker Marker found
* @param int $count Count
* @param int $count Count
* @return void
* @return void
*/
*/
protected function _markerError($file, $line, $marker, $count)
protected function _markerError($file, $line, $marker, $count)
{
{
$this->err(sprintf("Invalid marker content in %s:%s\n* %s(", $file, $line, $marker));
$this->err(sprintf("Invalid marker content in %s:%s\n* %s(", $file, $line, $marker));
$count += 2;
$count += 2;
$tokenCount = count($this->_tokens);
$tokenCount = count($this->_tokens);
$parenthesis = 1;
$parenthesis = 1;
while ((($tokenCount - $count) > 0) && $parenthesis) {
while ((($tokenCount - $count) > 0) && $parenthesis) {
if (is_array($this->_tokens[$count])) {
if (is_array($this->_tokens[$count])) {
$this->err($this->_tokens[$count][1], false);
$this->err($this->_tokens[$count][1], false);
} else {
} else {
$this->err($this->_tokens[$count], false);
$this->err($this->_tokens[$count], false);
if ($this->_tokens[$count] === '(') {
if ($this->_tokens[$count] === '(') {
$parenthesis++;
$parenthesis++;
}
}
if ($this->_tokens[$count] === ')') {
if ($this->_tokens[$count] === ')') {
$parenthesis--;
$parenthesis--;
}
}
}
}
$count++;
$count++;
}
}
$this->err("\n", true);
$this->err("\n", true);
}
}
/**
/**
* Search files that may contain translatable strings
* Search files that may contain translatable strings
*
*
* @return void
* @return void
*/
*/
protected function _searchFiles()
protected function _searchFiles()
{
{
$pattern = false;
$pattern = false;
if (!empty($this->_exclude)) {
if (!empty($this->_exclude)) {
$exclude = [];
$exclude = [];
foreach ($this->_exclude as $e) {
foreach ($this->_exclude as $e) {
if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) {
if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) {
$e = DIRECTORY_SEPARATOR . $e;
$e = DIRECTORY_SEPARATOR . $e;
}
}
複製
已複製
複製
已複製
$exclude[] = preg_quote
($e, '/');
$exclude[] = preg_quote
}
$pattern = '/' . implode('|', $exclude) . '/';
}
foreach ($this->_paths as $path) {
$path = realpath($path) . DIRECTORY_SEPARATOR;
$Folder = new Folder($path);
$files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true);
if (!empty($pattern)) {
$files = preg_grep($pattern, $files, PREG_GREP_INVERT);
$files = array_values($files);
}
$this->_files = array_merge($this->_files, $files);
}
$this->_files = array_unique($this->
已保存差異
原始文本
開啟檔案
<?php /** * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 1.2.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\Shell\Task; use Cake\Console\Shell; use Cake\Core\App; use Cake\Core\Plugin; use Cake\Filesystem\File; use Cake\Filesystem\Folder; use Cake\Utility\Inflector; /** * Language string extractor */ class ExtractTask extends Shell { /** * Paths to use when looking for strings * * @var array */ protected $_paths = []; /** * Files from where to extract * * @var array */ protected $_files = []; /** * Merge all domain strings into the default.pot file * * @var bool */ protected $_merge = false; /** * Current file being processed * * @var string|null */ protected $_file; /** * Contains all content waiting to be write * * @var array */ protected $_storage = []; /** * Extracted tokens * * @var array */ protected $_tokens = []; /** * Extracted strings indexed by domain. * * @var array */ protected $_translations = []; /** * Destination path * * @var string|null */ protected $_output; /** * An array of directories to exclude. * * @var array */ protected $_exclude = []; /** * Holds the validation string domain to use for validation messages when extracting * * @var string */ protected $_validationDomain = 'default'; /** * Holds whether this call should extract the CakePHP Lib messages * * @var bool */ protected $_extractCore = false; /** * No welcome message. * * @return void */ protected function _welcome() { } /** * Method to interact with the User and get path selections. * * @return void */ protected function _getPaths() { $defaultPath = APP; while (true) { $currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None']; $message = sprintf( "Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one", implode(', ', $currentPaths) ); $response = $this->in($message, null, $defaultPath); if (strtoupper($response) === 'Q') { $this->err('Extract Aborted'); $this->_stop(); return; } if (strtoupper($response) === 'D' && count($this->_paths)) { $this->out(); return; } if (strtoupper($response) === 'D') { $this->warn('No directories selected. Please choose a directory.'); } elseif (is_dir($response)) { $this->_paths[] = $response; $defaultPath = 'D'; } else { $this->err('The directory path you supplied was not found. Please try again.'); } $this->out(); } } /** * Execution method always used for tasks * * @return void */ public function main() { if (!empty($this->params['exclude'])) { $this->_exclude = explode(',', $this->params['exclude']); } if (isset($this->params['files']) && !is_array($this->params['files'])) { $this->_files = explode(',', $this->params['files']); } if (isset($this->params['paths'])) { $this->_paths = explode(',', $this->params['paths']); } elseif (isset($this->params['plugin'])) { $plugin = Inflector::camelize($this->params['plugin']); if (!Plugin::loaded($plugin)) { Plugin::load($plugin); } $this->_paths = [Plugin::classPath($plugin)]; $this->params['plugin'] = $plugin; } else { $this->_getPaths(); } if (isset($this->params['extract-core'])) { $this->_extractCore = !(strtolower($this->params['extract-core']) === 'no'); } else { $response = $this->in('Would you like to extract the messages from the CakePHP core?', ['y', 'n'], 'n'); $this->_extractCore = strtolower($response) === 'y'; } if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) { $this->_exclude = array_merge($this->_exclude, App::path('Plugin')); } if (!empty($this->params['validation-domain'])) { $this->_validationDomain = $this->params['validation-domain']; } if ($this->_extractCore) { $this->_paths[] = CAKE; } if (isset($this->params['output'])) { $this->_output = $this->params['output']; } elseif (isset($this->params['plugin'])) { $this->_output = $this->_paths[0] . 'Locale'; } else { $message = "What is the path you would like to output?\n[Q]uit"; while (true) { $response = $this->in($message, null, rtrim($this->_paths[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'Locale'); if (strtoupper($response) === 'Q') { $this->err('Extract Aborted'); $this->_stop(); return; } if ($this->_isPathUsable($response)) { $this->_output = $response . DIRECTORY_SEPARATOR; break; } $this->err(''); $this->err( '<error>The directory path you supplied was ' . 'not found. Please try again.</error>' ); $this->out(); } } if (isset($this->params['merge'])) { $this->_merge = !(strtolower($this->params['merge']) === 'no'); } else { $this->out(); $response = $this->in('Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], 'n'); $this->_merge = strtolower($response) === 'y'; } if (empty($this->_files)) { $this->_searchFiles(); } $this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; if (!$this->_isPathUsable($this->_output)) { $this->err(sprintf('The output directory %s was not found or writable.', $this->_output)); $this->_stop(); return; } $this->_extract(); } /** * Add a translation to the internal translations property * * Takes care of duplicate translations * * @param string $domain The domain * @param string $msgid The message string * @param array $details Context and plural form if any, file and line references * @return void */ protected function _addTranslation($domain, $msgid, $details = []) { $context = isset($details['msgctxt']) ? $details['msgctxt'] : ''; if (empty($this->_translations[$domain][$msgid][$context])) { $this->_translations[$domain][$msgid][$context] = [ 'msgid_plural' => false ]; } if (isset($details['msgid_plural'])) { $this->_translations[$domain][$msgid][$context]['msgid_plural'] = $details['msgid_plural']; } if (isset($details['file'])) { $line = isset($details['line']) ? $details['line'] : 0; $this->_translations[$domain][$msgid][$context]['references'][$details['file']][] = $line; } } /** * Extract text * * @return void */ protected function _extract() { $this->out(); $this->out(); $this->out('Extracting...'); $this->hr(); $this->out('Paths:'); foreach ($this->_paths as $path) { $this->out(' ' . $path); } $this->out('Output Directory: ' . $this->_output); $this->hr(); $this->_extractTokens(); $this->_buildFiles(); $this->_writeFiles(); $this->_paths = $this->_files = $this->_storage = []; $this->_translations = $this->_tokens = []; $this->out(); $this->out('Done.'); } /** * Gets the option parser instance and configures it. * * @return \Cake\Console\ConsoleOptionParser */ public function getOptionParser() { $parser = parent::getOptionParser(); $parser->setDescription( 'CakePHP Language String Extraction:' )->addOption('app', [ 'help' => 'Directory where your application is located.' ])->addOption('paths', [ 'help' => 'Comma separated list of paths.' ])->addOption('merge', [ 'help' => 'Merge all domain strings into the default.po file.', 'choices' => ['yes', 'no'] ])->addOption('output', [ 'help' => 'Full path to output directory.' ])->addOption('files', [ 'help' => 'Comma separated list of files.' ])->addOption('exclude-plugins', [ 'boolean' => true, 'default' => true, 'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.' ])->addOption('plugin', [ 'help' => 'Extracts tokens only from the plugin specified and puts the result in the plugin\'s Locale directory.' ])->addOption('ignore-model-validation', [ 'boolean' => true, 'default' => false, 'help' => 'Ignores validation messages in the $validate property.' . ' If this flag is not set and the command is run from the same app directory,' . ' all messages in model validation rules will be extracted as tokens.' ])->addOption('validation-domain', [ 'help' => 'If set to a value, the localization domain to be used for model validation messages.' ])->addOption('exclude', [ 'help' => 'Comma separated list of directories to exclude.' . ' Any path containing a path segment with the provided values will be skipped. E.g. test,vendors' ])->addOption('overwrite', [ 'boolean' => true, 'default' => false, 'help' => 'Always overwrite existing .pot files.' ])->addOption('extract-core', [ 'help' => 'Extract messages from the CakePHP core libs.', 'choices' => ['yes', 'no'] ])->addOption('no-location', [ 'boolean' => true, 'default' => false, 'help' => 'Do not write file locations for each extracted message.', ]); return $parser; } /** * Extract tokens out of all files to be processed * * @return void */ protected function _extractTokens() { /** @var \Cake\Shell\Helper\ProgressHelper $progress */ $progress = $this->helper('progress'); $progress->init(['total' => count($this->_files)]); $isVerbose = $this->param('verbose'); foreach ($this->_files as $file) { $this->_file = $file; if ($isVerbose) { $this->out(sprintf('Processing %s...', $file), 1, Shell::VERBOSE); } $code = file_get_contents($file); $allTokens = token_get_all($code); $this->_tokens = []; foreach ($allTokens as $token) { if (!is_array($token) || ($token[0] !== T_WHITESPACE && $token[0] !== T_INLINE_HTML)) { $this->_tokens[] = $token; } } unset($allTokens); $this->_parse('__', ['singular']); $this->_parse('__n', ['singular', 'plural']); $this->_parse('__d', ['domain', 'singular']); $this->_parse('__dn', ['domain', 'singular', 'plural']); $this->_parse('__x', ['context', 'singular']); $this->_parse('__xn', ['context', 'singular', 'plural']); $this->_parse('__dx', ['domain', 'context', 'singular']); $this->_parse('__dxn', ['domain', 'context', 'singular', 'plural']); if (!$isVerbose) { $progress->increment(1); $progress->draw(); } } } /** * Parse tokens * * @param string $functionName Function name that indicates translatable string (e.g: '__') * @param array $map Array containing what variables it will find (e.g: domain, singular, plural) * @return void */ protected function _parse($functionName, $map) { $count = 0; $tokenCount = count($this->_tokens); while (($tokenCount - $count) > 1) { $countToken = $this->_tokens[$count]; $firstParenthesis = $this->_tokens[$count + 1]; if (!is_array($countToken)) { $count++; continue; } list($type, $string, $line) = $countToken; if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) { $position = $count; $depth = 0; while (!$depth) { if ($this->_tokens[$position] === '(') { $depth++; } elseif ($this->_tokens[$position] === ')') { $depth--; } $position++; } $mapCount = count($map); $strings = $this->_getStrings($position, $mapCount); if ($mapCount === count($strings)) { $singular = null; extract(array_combine($map, $strings)); $domain = isset($domain) ? $domain : 'default'; $details = [ 'file' => $this->_file, 'line' => $line, ]; if (isset($plural)) { $details['msgid_plural'] = $plural; } if (isset($context)) { $details['msgctxt'] = $context; } $this->_addTranslation($domain, $singular, $details); } elseif (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) { $this->_markerError($this->_file, $line, $functionName, $count); } } $count++; } } /** * Build the translate template file contents out of obtained strings * * @return void */ protected function _buildFiles() { $paths = $this->_paths; $paths[] = realpath(APP) . DIRECTORY_SEPARATOR; usort($paths, function ($a, $b) { return strlen($a) - strlen($b); }); foreach ($this->_translations as $domain => $translations) { foreach ($translations as $msgid => $contexts) { foreach ($contexts as $context => $details) { $plural = $details['msgid_plural']; $files = $details['references']; $occurrences = []; foreach ($files as $file => $lines) { $lines = array_unique($lines); $occurrences[] = $file . ':' . implode(';', $lines); } $occurrences = implode("\n#: ", $occurrences); $header = ''; if (!$this->param('no-location')) { $header = '#: ' . str_replace(DIRECTORY_SEPARATOR, '/', str_replace($paths, '', $occurrences)) . "\n"; } $sentence = ''; if ($context !== '') { $sentence .= "msgctxt \"{$context}\"\n"; } if ($plural === false) { $sentence .= "msgid \"{$msgid}\"\n"; $sentence .= "msgstr \"\"\n\n"; } else { $sentence .= "msgid \"{$msgid}\"\n"; $sentence .= "msgid_plural \"{$plural}\"\n"; $sentence .= "msgstr[0] \"\"\n"; $sentence .= "msgstr[1] \"\"\n\n"; } if ($domain !== 'default' && $this->_merge) { $this->_store('default', $header, $sentence); } else { $this->_store($domain, $header, $sentence); } } } } } /** * Prepare a file to be stored * * @param string $domain The domain * @param string $header The header content. * @param string $sentence The sentence to store. * @return void */ protected function _store($domain, $header, $sentence) { if (!isset($this->_storage[$domain])) { $this->_storage[$domain] = []; } if (!isset($this->_storage[$domain][$sentence])) { $this->_storage[$domain][$sentence] = $header; } else { $this->_storage[$domain][$sentence] .= $header; } } /** * Write the files that need to be stored * * @return void */ protected function _writeFiles() { $overwriteAll = false; if (!empty($this->params['overwrite'])) { $overwriteAll = true; } foreach ($this->_storage as $domain => $sentences) { $output = $this->_writeHeader(); foreach ($sentences as $sentence => $header) { $output .= $header . $sentence; } // Remove vendor prefix if present. $slashPosition = strpos($domain, '/'); if ($slashPosition !== false) { $domain = substr($domain, $slashPosition + 1); } $filename = str_replace('/', '_', $domain) . '.pot'; $File = new File($this->_output . $filename); $response = ''; while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') { $this->out(); $response = $this->in( sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename), ['y', 'n', 'a'], 'y' ); if (strtoupper($response) === 'N') { $response = ''; while (!$response) { $response = $this->in('What would you like to name this file?', null, 'new_' . $filename); $File = new File($this->_output . $response); $filename = $response; } } elseif (strtoupper($response) === 'A') { $overwriteAll = true; } } $File->write($output); $File->close(); } } /** * Build the translation template header * * @return string Translation template header */ protected function _writeHeader() { $output = "# LANGUAGE translation of CakePHP Application\n"; $output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n"; $output .= "#\n"; $output .= "#, fuzzy\n"; $output .= "msgid \"\"\n"; $output .= "msgstr \"\"\n"; $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; $output .= '"POT-Creation-Date: ' . date('Y-m-d H:iO') . "\\n\"\n"; $output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n"; $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n"; $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n"; $output .= "\"MIME-Version: 1.0\\n\"\n"; $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n"; return $output; } /** * Get the strings from the position forward * * @param int $position Actual position on tokens array * @param int $target Number of strings to extract * @return array Strings extracted */ protected function _getStrings(&$position, $target) { $strings = []; $count = count($strings); while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position][0] == T_LNUMBER)) { $count = count($strings); if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->_tokens[$position + 1] === '.') { $string = ''; while ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position] === '.') { if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) { $string .= $this->_formatString($this->_tokens[$position][1]); } $position++; } $strings[] = $string; } elseif ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) { $strings[] = $this->_formatString($this->_tokens[$position][1]); } elseif ($this->_tokens[$position][0] == T_LNUMBER) { $strings[] = $this->_tokens[$position][1]; } $position++; } return $strings; } /** * Format a string to be added as a translatable string * * @param string $string String to format * @return string Formatted string */ protected function _formatString($string) { $quote = substr($string, 0, 1); $string = substr($string, 1, -1); if ($quote === '"') { $string = stripcslashes($string); } else { $string = strtr($string, ["\\'" => "'", '\\\\' => '\\']); } $string = str_replace("\r\n", "\n", $string); return addcslashes($string, "\0..\37\\\""); } /** * Indicate an invalid marker on a processed file * * @param string $file File where invalid marker resides * @param int $line Line number * @param string $marker Marker found * @param int $count Count * @return void */ protected function _markerError($file, $line, $marker, $count) { $this->err(sprintf("Invalid marker content in %s:%s\n* %s(", $file, $line, $marker)); $count += 2; $tokenCount = count($this->_tokens); $parenthesis = 1; while ((($tokenCount - $count) > 0) && $parenthesis) { if (is_array($this->_tokens[$count])) { $this->err($this->_tokens[$count][1], false); } else { $this->err($this->_tokens[$count], false); if ($this->_tokens[$count] === '(') { $parenthesis++; } if ($this->_tokens[$count] === ')') { $parenthesis--; } } $count++; } $this->err("\n", true); } /** * Search files that may contain translatable strings * * @return void */ protected function _searchFiles() { $pattern = false; if (!empty($this->_exclude)) { $exclude = []; foreach ($this->_exclude as $e) { if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) { $e = DIRECTORY_SEPARATOR . $e; } $exclude[] = preg_quote($e, '/'); } $pattern = '/' . implode('|', $exclude) . '/'; } foreach ($this->_paths as $path) { $path = realpath($path) . DIRECTORY_SEPARATOR; $Folder = new Folder($path); $files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true); if (!empty($pattern)) { $files = preg_grep($pattern, $files, PREG_GREP_INVERT); $files = array_values($files); } $this->_files = array_merge($this->_files, $files); } $this->_files = array_unique($this->_files); } /** * Returns whether this execution is meant to extract string only from directories in folder represented by the * APP constant, i.e. this task is extracting strings from same application. * * @return bool */ protected function _isExtractingApp() { return $this->_paths === [APP]; } /** * Checks whether or not a given path is usable for writing. * * @param string $path Path to folder * @return bool true if it exists and is writable, false otherwise */ protected function _isPathUsable($path) { if (!is_dir($path)) { mkdir($path, 0770, true); } return is_dir($path) && is_writable($path); } }
更改後文本
開啟檔案
<?php /** * CakePHP(tm) : Rapid Development Framework (https://cakephp.org) * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) * @link https://cakephp.org CakePHP(tm) Project * @since 1.2.0 * @license https://opensource.org/licenses/mit-license.php MIT License */ namespace Cake\Shell\Task; use Cake\Console\Shell; use Cake\Core\App; use Cake\Core\Plugin; use Cake\Filesystem\File; use Cake\Filesystem\Folder; use Cake\Utility\Inflector; /** * Language string extractor */ class ExtractTask extends Shell { /** * Paths to use when looking for strings * * @var array */ protected $_paths = []; /** * Files from where to extract * * @var array */ protected $_files = []; /** * Merge all domain strings into the default.pot file * * @var bool */ protected $_merge = false; /** * Use relative paths in the pot files rather than full path * * @var bool */ protected $_relativePaths = false; /** * Current file being processed * * @var string|null */ protected $_file; /** * Contains all content waiting to be write * * @var array */ protected $_storage = []; /** * Extracted tokens * * @var array */ protected $_tokens = []; /** * Extracted strings indexed by domain. * * @var array */ protected $_translations = []; /** * Destination path * * @var string|null */ protected $_output; /** * An array of directories to exclude. * * @var array */ protected $_exclude = []; /** * Holds the validation string domain to use for validation messages when extracting * * @var string */ protected $_validationDomain = 'default'; /** * Holds whether this call should extract the CakePHP Lib messages * * @var bool */ protected $_extractCore = false; /** * No welcome message. * * @return void */ protected function _welcome() { } /** * Method to interact with the User and get path selections. * * @return void */ protected function _getPaths() { $defaultPath = APP; while (true) { $currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None']; $message = sprintf( "Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one", implode(', ', $currentPaths) ); $response = $this->in($message, null, $defaultPath); if (strtoupper($response) === 'Q') { $this->err('Extract Aborted'); $this->_stop(); return; } if (strtoupper($response) === 'D' && count($this->_paths)) { $this->out(); return; } if (strtoupper($response) === 'D') { $this->warn('No directories selected. Please choose a directory.'); } elseif (is_dir($response)) { $this->_paths[] = $response; $defaultPath = 'D'; } else { $this->err('The directory path you supplied was not found. Please try again.'); } $this->out(); } } /** * Execution method always used for tasks * * @return void */ public function main() { if (!empty($this->params['exclude'])) { $this->_exclude = explode(',', $this->params['exclude']); } if (isset($this->params['files']) && !is_array($this->params['files'])) { $this->_files = explode(',', $this->params['files']); } if (isset($this->params['paths'])) { $this->_paths = explode(',', $this->params['paths']); } elseif (isset($this->params['plugin'])) { $plugin = Inflector::camelize($this->params['plugin']); if (!Plugin::loaded($plugin)) { Plugin::load($plugin); } $this->_paths = [Plugin::classPath($plugin)]; $this->params['plugin'] = $plugin; } else { $this->_getPaths(); } if (isset($this->params['extract-core'])) { $this->_extractCore = !(strtolower($this->params['extract-core']) === 'no'); } else { $response = $this->in('Would you like to extract the messages from the CakePHP core?', ['y', 'n'], 'n'); $this->_extractCore = strtolower($response) === 'y'; } if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) { $this->_exclude = array_merge($this->_exclude, App::path('Plugin')); } if (!empty($this->params['validation-domain'])) { $this->_validationDomain = $this->params['validation-domain']; } if ($this->_extractCore) { $this->_paths[] = CAKE; } if (isset($this->params['output'])) { $this->_output = $this->params['output']; } elseif (isset($this->params['plugin'])) { $this->_output = $this->_paths[0] . 'Locale'; } else { $message = "What is the path you would like to output?\n[Q]uit"; while (true) { $response = $this->in($message, null, rtrim($this->_paths[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'Locale'); if (strtoupper($response) === 'Q') { $this->err('Extract Aborted'); $this->_stop(); return; } if ($this->_isPathUsable($response)) { $this->_output = $response . DIRECTORY_SEPARATOR; break; } $this->err(''); $this->err( '<error>The directory path you supplied was ' . 'not found. Please try again.</error>' ); $this->out(); } } if (isset($this->params['merge'])) { $this->_merge = !(strtolower($this->params['merge']) === 'no'); } else { $this->out(); $response = $this->in('Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], 'n'); $this->_merge = strtolower($response) === 'y'; } if (isset($this->params['relative-paths'])) { $this->_relativePaths = !(strtolower($this->params['relative-paths']) === 'no'); } if (empty($this->_files)) { $this->_searchFiles(); } $this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; if (!$this->_isPathUsable($this->_output)) { $this->err(sprintf('The output directory %s was not found or writable.', $this->_output)); $this->_stop(); return; } $this->_extract(); } /** * Add a translation to the internal translations property * * Takes care of duplicate translations * * @param string $domain The domain * @param string $msgid The message string * @param array $details Context and plural form if any, file and line references * @return void */ protected function _addTranslation($domain, $msgid, $details = []) { $context = isset($details['msgctxt']) ? $details['msgctxt'] : ''; if (empty($this->_translations[$domain][$msgid][$context])) { $this->_translations[$domain][$msgid][$context] = [ 'msgid_plural' => false ]; } if (isset($details['msgid_plural'])) { $this->_translations[$domain][$msgid][$context]['msgid_plural'] = $details['msgid_plural']; } if (isset($details['file'])) { $line = isset($details['line']) ? $details['line'] : 0; $this->_translations[$domain][$msgid][$context]['references'][$details['file']][] = $line; } } /** * Extract text * * @return void */ protected function _extract() { $this->out(); $this->out(); $this->out('Extracting...'); $this->hr(); $this->out('Paths:'); foreach ($this->_paths as $path) { $this->out(' ' . $path); } $this->out('Output Directory: ' . $this->_output); $this->hr(); $this->_extractTokens(); $this->_buildFiles(); $this->_writeFiles(); $this->_paths = $this->_files = $this->_storage = []; $this->_translations = $this->_tokens = []; $this->out(); $this->out('Done.'); } /** * Gets the option parser instance and configures it. * * @return \Cake\Console\ConsoleOptionParser */ public function getOptionParser() { $parser = parent::getOptionParser(); $parser->setDescription( 'CakePHP Language String Extraction:' )->addOption('app', [ 'help' => 'Directory where your application is located.' ])->addOption('paths', [ 'help' => 'Comma separated list of paths.' ])->addOption('relative-paths', [ 'help' => 'Use relative paths in the .pot file', 'choices' => ['yes', 'no'] ])->addOption('merge', [ 'help' => 'Merge all domain strings into the default.po file.', 'choices' => ['yes', 'no'] ])->addOption('output', [ 'help' => 'Full path to output directory.' ])->addOption('files', [ 'help' => 'Comma separated list of files.' ])->addOption('exclude-plugins', [ 'boolean' => true, 'default' => true, 'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.' ])->addOption('plugin', [ 'help' => 'Extracts tokens only from the plugin specified and puts the result in the plugin\'s Locale directory.' ])->addOption('ignore-model-validation', [ 'boolean' => true, 'default' => false, 'help' => 'Ignores validation messages in the $validate property.' . ' If this flag is not set and the command is run from the same app directory,' . ' all messages in model validation rules will be extracted as tokens.' ])->addOption('validation-domain', [ 'help' => 'If set to a value, the localization domain to be used for model validation messages.' ])->addOption('exclude', [ 'help' => 'Comma separated list of directories to exclude.' . ' Any path containing a path segment with the provided values will be skipped. E.g. test,vendors' ])->addOption('overwrite', [ 'boolean' => true, 'default' => false, 'help' => 'Always overwrite existing .pot files.' ])->addOption('extract-core', [ 'help' => 'Extract messages from the CakePHP core libs.', 'choices' => ['yes', 'no'] ])->addOption('no-location', [ 'boolean' => true, 'default' => false, 'help' => 'Do not write file locations for each extracted message.', ]); return $parser; } /** * Extract tokens out of all files to be processed * * @return void */ protected function _extractTokens() { /** @var \Cake\Shell\Helper\ProgressHelper $progress */ $progress = $this->helper('progress'); $progress->init(['total' => count($this->_files)]); $isVerbose = $this->param('verbose'); foreach ($this->_files as $file) { $this->_file = $file; if ($isVerbose) { $this->out(sprintf('Processing %s...', $file), 1, Shell::VERBOSE); } $code = file_get_contents($file); $allTokens = token_get_all($code); $this->_tokens = []; foreach ($allTokens as $token) { if (!is_array($token) || ($token[0] !== T_WHITESPACE && $token[0] !== T_INLINE_HTML)) { $this->_tokens[] = $token; } } unset($allTokens); $this->_parse('__', ['singular']); $this->_parse('__n', ['singular', 'plural']); $this->_parse('__d', ['domain', 'singular']); $this->_parse('__dn', ['domain', 'singular', 'plural']); $this->_parse('__x', ['context', 'singular']); $this->_parse('__xn', ['context', 'singular', 'plural']); $this->_parse('__dx', ['domain', 'context', 'singular']); $this->_parse('__dxn', ['domain', 'context', 'singular', 'plural']); if (!$isVerbose) { $progress->increment(1); $progress->draw(); } } } /** * Parse tokens * * @param string $functionName Function name that indicates translatable string (e.g: '__') * @param array $map Array containing what variables it will find (e.g: domain, singular, plural) * @return void */ protected function _parse($functionName, $map) { $count = 0; $tokenCount = count($this->_tokens); while (($tokenCount - $count) > 1) { $countToken = $this->_tokens[$count]; $firstParenthesis = $this->_tokens[$count + 1]; if (!is_array($countToken)) { $count++; continue; } list($type, $string, $line) = $countToken; if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) { $position = $count; $depth = 0; while (!$depth) { if ($this->_tokens[$position] === '(') { $depth++; } elseif ($this->_tokens[$position] === ')') { $depth--; } $position++; } $mapCount = count($map); $strings = $this->_getStrings($position, $mapCount); if ($mapCount === count($strings)) { $singular = null; extract(array_combine($map, $strings)); $domain = isset($domain) ? $domain : 'default'; $details = [ 'file' => $this->_file, 'line' => $line, ]; if ($this->_relativePaths) { $details['file'] = '.' . str_replace(ROOT, '', $details['file']); } if (isset($plural)) { $details['msgid_plural'] = $plural; } if (isset($context)) { $details['msgctxt'] = $context; } $this->_addTranslation($domain, $singular, $details); } elseif (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) { $this->_markerError($this->_file, $line, $functionName, $count); } } $count++; } } /** * Build the translate template file contents out of obtained strings * * @return void */ protected function _buildFiles() { $paths = $this->_paths; $paths[] = realpath(APP) . DIRECTORY_SEPARATOR; usort($paths, function ($a, $b) { return strlen($a) - strlen($b); }); foreach ($this->_translations as $domain => $translations) { foreach ($translations as $msgid => $contexts) { foreach ($contexts as $context => $details) { $plural = $details['msgid_plural']; $files = $details['references']; $occurrences = []; foreach ($files as $file => $lines) { $lines = array_unique($lines); $occurrences[] = $file . ':' . implode(';', $lines); } $occurrences = implode("\n#: ", $occurrences); $header = ''; if (!$this->param('no-location')) { $header = '#: ' . str_replace(DIRECTORY_SEPARATOR, '/', str_replace($paths, '', $occurrences)) . "\n"; } $sentence = ''; if ($context !== '') { $sentence .= "msgctxt \"{$context}\"\n"; } if ($plural === false) { $sentence .= "msgid \"{$msgid}\"\n"; $sentence .= "msgstr \"\"\n\n"; } else { $sentence .= "msgid \"{$msgid}\"\n"; $sentence .= "msgid_plural \"{$plural}\"\n"; $sentence .= "msgstr[0] \"\"\n"; $sentence .= "msgstr[1] \"\"\n\n"; } if ($domain !== 'default' && $this->_merge) { $this->_store('default', $header, $sentence); } else { $this->_store($domain, $header, $sentence); } } } } } /** * Prepare a file to be stored * * @param string $domain The domain * @param string $header The header content. * @param string $sentence The sentence to store. * @return void */ protected function _store($domain, $header, $sentence) { if (!isset($this->_storage[$domain])) { $this->_storage[$domain] = []; } if (!isset($this->_storage[$domain][$sentence])) { $this->_storage[$domain][$sentence] = $header; } else { $this->_storage[$domain][$sentence] .= $header; } } /** * Write the files that need to be stored * * @return void */ protected function _writeFiles() { $overwriteAll = false; if (!empty($this->params['overwrite'])) { $overwriteAll = true; } foreach ($this->_storage as $domain => $sentences) { $output = $this->_writeHeader(); foreach ($sentences as $sentence => $header) { $output .= $header . $sentence; } // Remove vendor prefix if present. $slashPosition = strpos($domain, '/'); if ($slashPosition !== false) { $domain = substr($domain, $slashPosition + 1); } $filename = str_replace('/', '_', $domain) . '.pot'; $File = new File($this->_output . $filename); $response = ''; while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') { $this->out(); $response = $this->in( sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename), ['y', 'n', 'a'], 'y' ); if (strtoupper($response) === 'N') { $response = ''; while (!$response) { $response = $this->in('What would you like to name this file?', null, 'new_' . $filename); $File = new File($this->_output . $response); $filename = $response; } } elseif (strtoupper($response) === 'A') { $overwriteAll = true; } } $File->write($output); $File->close(); } } /** * Build the translation template header * * @return string Translation template header */ protected function _writeHeader() { $output = "# LANGUAGE translation of CakePHP Application\n"; $output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n"; $output .= "#\n"; $output .= "#, fuzzy\n"; $output .= "msgid \"\"\n"; $output .= "msgstr \"\"\n"; $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; $output .= '"POT-Creation-Date: ' . date('Y-m-d H:iO') . "\\n\"\n"; $output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n"; $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n"; $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n"; $output .= "\"MIME-Version: 1.0\\n\"\n"; $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n"; return $output; } /** * Get the strings from the position forward * * @param int $position Actual position on tokens array * @param int $target Number of strings to extract * @return array Strings extracted */ protected function _getStrings(&$position, $target) { $strings = []; $count = count($strings); while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position][0] == T_LNUMBER)) { $count = count($strings); if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->_tokens[$position + 1] === '.') { $string = ''; while ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position] === '.') { if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) { $string .= $this->_formatString($this->_tokens[$position][1]); } $position++; } $strings[] = $string; } elseif ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) { $strings[] = $this->_formatString($this->_tokens[$position][1]); } elseif ($this->_tokens[$position][0] == T_LNUMBER) { $strings[] = $this->_tokens[$position][1]; } $position++; } return $strings; } /** * Format a string to be added as a translatable string * * @param string $string String to format * @return string Formatted string */ protected function _formatString($string) { $quote = substr($string, 0, 1); $string = substr($string, 1, -1); if ($quote === '"') { $string = stripcslashes($string); } else { $string = strtr($string, ["\\'" => "'", '\\\\' => '\\']); } $string = str_replace("\r\n", "\n", $string); return addcslashes($string, "\0..\37\\\""); } /** * Indicate an invalid marker on a processed file * * @param string $file File where invalid marker resides * @param int $line Line number * @param string $marker Marker found * @param int $count Count * @return void */ protected function _markerError($file, $line, $marker, $count) { $this->err(sprintf("Invalid marker content in %s:%s\n* %s(", $file, $line, $marker)); $count += 2; $tokenCount = count($this->_tokens); $parenthesis = 1; while ((($tokenCount - $count) > 0) && $parenthesis) { if (is_array($this->_tokens[$count])) { $this->err($this->_tokens[$count][1], false); } else { $this->err($this->_tokens[$count], false); if ($this->_tokens[$count] === '(') { $parenthesis++; } if ($this->_tokens[$count] === ')') { $parenthesis--; } } $count++; } $this->err("\n", true); } /** * Search files that may contain translatable strings * * @return void */ protected function _searchFiles() { $pattern = false; if (!empty($this->_exclude)) { $exclude = []; foreach ($this->_exclude as $e) { if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) { $e = DIRECTORY_SEPARATOR . $e; } $exclude[] = preg_quote($e, '/'); } $pattern = '/' . implode('|', $exclude) . '/'; } foreach ($this->_paths as $path) { $path = realpath($path) . DIRECTORY_SEPARATOR; $Folder = new Folder($path); $files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true); if (!empty($pattern)) { $files = preg_grep($pattern, $files, PREG_GREP_INVERT); $files = array_values($files); } $this->_files = array_merge($this->_files, $files); } $this->_files = array_unique($this->_files); } /** * Returns whether this execution is meant to extract string only from directories in folder represented by the * APP constant, i.e. this task is extracting strings from same application. * * @return bool */ protected function _isExtractingApp() { return $this->_paths === [APP]; } /** * Checks whether or not a given path is usable for writing. * * @param string $path Path to folder * @return bool true if it exists and is writable, false otherwise */ protected function _isPathUsable($path) { if (!is_dir($path)) { mkdir($path, 0770, true); } return is_dir($path) && is_writable($path); } }
尋找差異