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); } }
查找差异