Skip to content

Commit

Permalink
Merge pull request #1563 from mvdriel/fix-i18n-extract
Browse files Browse the repository at this point in the history
Extract category strings to separate .po files
  • Loading branch information
markstory committed Aug 30, 2013
2 parents c1ae41d + 256e1f6 commit 824a6f7
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 73 deletions.
165 changes: 92 additions & 73 deletions lib/Cake/Console/Command/Task/ExtractTask.php
Expand Up @@ -44,7 +44,7 @@ class ExtractTask extends AppShell {
protected $_files = array();

/**
* Merge all domains string into the default.pot file
* Merge all domain and category strings into the default.pot file
*
* @var boolean
*/
Expand Down Expand Up @@ -72,7 +72,7 @@ class ExtractTask extends AppShell {
protected $_tokens = array();

/**
* Extracted strings indexed by domain.
* Extracted strings indexed by category and domain.
*
* @var array
*/
Expand Down Expand Up @@ -222,7 +222,7 @@ public function execute() {
$this->_merge = !(strtolower($this->params['merge']) === 'no');
} else {
$this->out();
$response = $this->in(__d('cake_console', 'Would you like to merge all domains strings into the default.pot file?'), array('y', 'n'), 'n');
$response = $this->in(__d('cake_console', 'Would you like to merge all domain and category strings into the default.pot file?'), array('y', 'n'), 'n');
$this->_merge = strtolower($response) === 'y';
}

Expand All @@ -244,28 +244,29 @@ public function execute() {
*
* Takes care of duplicate translations
*
* @param string $category
* @param string $domain
* @param string $msgid
* @param array $details
* @return void
*/
protected function _addTranslation($domain, $msgid, $details = array()) {
if (empty($this->_translations[$domain][$msgid])) {
$this->_translations[$domain][$msgid] = array(
protected function _addTranslation($category, $domain, $msgid, $details = array()) {
if (empty($this->_translations[$category][$domain][$msgid])) {
$this->_translations[$category][$domain][$msgid] = array(
'msgid_plural' => false
);
}

if (isset($details['msgid_plural'])) {
$this->_translations[$domain][$msgid]['msgid_plural'] = $details['msgid_plural'];
$this->_translations[$category][$domain][$msgid]['msgid_plural'] = $details['msgid_plural'];
}

if (isset($details['file'])) {
$line = 0;
if (isset($details['line'])) {
$line = $details['line'];
}
$this->_translations[$domain][$msgid]['references'][$details['file']][] = $line;
$this->_translations[$category][$domain][$msgid]['references'][$details['file']][] = $line;
}
}

Expand Down Expand Up @@ -307,7 +308,7 @@ public function getOptionParser() {
->addOption('app', array('help' => __d('cake_console', 'Directory where your application is located.')))
->addOption('paths', array('help' => __d('cake_console', 'Comma separated list of paths.')))
->addOption('merge', array(
'help' => __d('cake_console', 'Merge all domain strings into the default.po file.'),
'help' => __d('cake_console', 'Merge all domain and category strings into the default.po file.'),
'choices' => array('yes', 'no')
))
->addOption('output', array('help' => __d('cake_console', 'Full path to output directory.')))
Expand Down Expand Up @@ -368,22 +369,23 @@ protected function _extractTokens() {
$this->_parse('__', array('singular'));
$this->_parse('__n', array('singular', 'plural'));
$this->_parse('__d', array('domain', 'singular'));
$this->_parse('__c', array('singular'));
$this->_parse('__dc', array('domain', 'singular'));
$this->_parse('__c', array('singular', 'category'));
$this->_parse('__dc', array('domain', 'singular', 'category'));
$this->_parse('__dn', array('domain', 'singular', 'plural'));
$this->_parse('__dcn', array('domain', 'singular', 'plural'));
$this->_parse('__dcn', array('domain', 'singular', 'plural', 'count', 'category'));
}
}

/**
* 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)
* @param array $map Array containing what variables it will find (e.g: category, domain, singular, plural)
* @return void
*/
protected function _parse($functionName, $map) {
$count = 0;
$categories = array('LC_ALL', 'LC_COLLATE', 'LC_CTYPE', 'LC_MONETARY', 'LC_NUMERIC', 'LC_TIME', 'LC_MESSAGES');
$tokenCount = count($this->_tokens);

while (($tokenCount - $count) > 1) {
Expand Down Expand Up @@ -413,6 +415,9 @@ protected function _parse($functionName, $map) {

if ($mapCount == count($strings)) {
extract(array_combine($map, $strings));
$category = isset($category) ? $category : 6;
$category = intval($category);
$categoryName = $categories[$category];
$domain = isset($domain) ? $domain : 'default';
$details = array(
'file' => $this->_file,
Expand All @@ -421,7 +426,7 @@ protected function _parse($functionName, $map) {
if (isset($plural)) {
$details['msgid_plural'] = $plural;
}
$this->_addTranslation($domain, $singular, $details);
$this->_addTranslation($categoryName, $domain, $singular, $details);
} else {
$this->_markerError($this->_file, $line, $functionName, $count);
}
Expand Down Expand Up @@ -500,7 +505,7 @@ protected function _extractPluginValidationMessages($plugin = null) {
* @param string $domain default domain to bind the validations to
* @return void
*/
protected function _processValidationRules($field, $rules, $file, $domain) {
protected function _processValidationRules($field, $rules, $file, $domain, $category = 'LC_MESSAGES') {
if (!is_array($rules)) {
return;
}
Expand All @@ -526,7 +531,7 @@ protected function _processValidationRules($field, $rules, $file, $domain) {
'file' => $file,
'line' => 'validation for field ' . $field
);
$this->_addTranslation($domain, $msgid, $details);
$this->_addTranslation($category, $domain, $msgid, $details);
}
}
}
Expand All @@ -539,31 +544,33 @@ protected function _processValidationRules($field, $rules, $file, $domain) {
protected function _buildFiles() {
$paths = $this->_paths;
$paths[] = realpath(APP) . DS;
foreach ($this->_translations as $domain => $translations) {
foreach ($translations as $msgid => $details) {
$plural = $details['msgid_plural'];
$files = $details['references'];
$occurrences = array();
foreach ($files as $file => $lines) {
$lines = array_unique($lines);
$occurrences[] = $file . ':' . implode(';', $lines);
}
$occurrences = implode("\n#: ", $occurrences);
$header = '#: ' . str_replace(DS, '/', str_replace($paths, '', $occurrences)) . "\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";
}
foreach ($this->_translations as $category => $domains) {
foreach ($domains as $domain => $translations) {
foreach ($translations as $msgid => $details) {
$plural = $details['msgid_plural'];
$files = $details['references'];
$occurrences = array();
foreach ($files as $file => $lines) {
$lines = array_unique($lines);
$occurrences[] = $file . ':' . implode(';', $lines);
}
$occurrences = implode("\n#: ", $occurrences);
$header = '#: ' . str_replace(DS, '/', str_replace($paths, '', $occurrences)) . "\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";
}

$this->_store($domain, $header, $sentence);
if ($domain !== 'default' && $this->_merge) {
$this->_store('default', $header, $sentence);
$this->_store($category, $domain, $header, $sentence);
if (($category !== 'LC_MESSAGES' || $domain !== 'default') && $this->_merge) {
$this->_store('LC_MESSAGES', 'default', $header, $sentence);
}
}
}
}
Expand All @@ -572,19 +579,23 @@ protected function _buildFiles() {
/**
* Prepare a file to be stored
*
* @param string $category
* @param string $domain
* @param string $header
* @param string $sentence
* @return void
*/
protected function _store($domain, $header, $sentence) {
if (!isset($this->_storage[$domain])) {
$this->_storage[$domain] = array();
protected function _store($category, $domain, $header, $sentence) {
if (!isset($this->_storage[$category])) {
$this->_storage[$category] = array();
}
if (!isset($this->_storage[$category][$domain])) {
$this->_storage[$category][$domain] = array();
}
if (!isset($this->_storage[$domain][$sentence])) {
$this->_storage[$domain][$sentence] = $header;
if (!isset($this->_storage[$category][$domain][$sentence])) {
$this->_storage[$category][$domain][$sentence] = $header;
} else {
$this->_storage[$domain][$sentence] .= $header;
$this->_storage[$category][$domain][$sentence] .= $header;
}
}

Expand All @@ -598,36 +609,42 @@ protected function _writeFiles() {
if (!empty($this->params['overwrite'])) {
$overwriteAll = true;
}
foreach ($this->_storage as $category => $domains) {
foreach ($domains as $domain => $sentences) {
$output = $this->_writeHeader();
foreach ($sentences as $sentence => $header) {
$output .= $header . $sentence;
}

foreach ($this->_storage as $domain => $sentences) {
$output = $this->_writeHeader();
foreach ($sentences as $sentence => $header) {
$output .= $header . $sentence;
}

$filename = $domain . '.pot';
$File = new File($this->_output . $filename);
$response = '';
while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
$this->out();
$response = $this->in(
__d('cake_console', 'Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename),
array('y', 'n', 'a'),
'y'
);
if (strtoupper($response) === 'N') {
$response = '';
while (!$response) {
$response = $this->in(__d('cake_console', "What would you like to name this file?"), null, 'new_' . $filename);
$File = new File($this->_output . $response);
$filename = $response;
$filename = $domain . '.pot';
if ($category === 'LC_MESSAGES') {
$File = new File($this->_output . $filename);
} else {
new Folder($this->_output . $category, true);
$File = new File($this->_output . $category . DS . $filename);
}
$response = '';
while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
$this->out();
$response = $this->in(
__d('cake_console', 'Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename),
array('y', 'n', 'a'),
'y'
);
if (strtoupper($response) === 'N') {
$response = '';
while (!$response) {
$response = $this->in(__d('cake_console', "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;
}
} elseif (strtoupper($response) === 'A') {
$overwriteAll = true;
}
$File->write($output);
$File->close();
}
$File->write($output);
$File->close();
}
}

Expand Down Expand Up @@ -665,7 +682,7 @@ protected function _writeHeader() {
protected function _getStrings(&$position, $target) {
$strings = array();
$count = count($strings);
while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING)) {
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 = '';
Expand All @@ -678,6 +695,8 @@ protected function _getStrings(&$position, $target) {
$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++;
}
Expand Down
26 changes: 26 additions & 0 deletions lib/Cake/Test/Case/Console/Command/Task/ExtractTaskTest.php
Expand Up @@ -178,6 +178,32 @@ public function testExecute() {
$this->assertRegExp($pattern, $result);
}

/**
* testExtractCategory method
*
* @return void
*/
public function testExtractCategory() {
$this->Task->interactive = false;

$this->Task->params['paths'] = CAKE . 'Test' . DS . 'test_app' . DS . 'View' . DS . 'Pages';
$this->Task->params['output'] = $this->path . DS;
$this->Task->params['extract-core'] = 'no';
$this->Task->params['merge'] = 'no';
$this->Task->expects($this->never())->method('err');
$this->Task->expects($this->any())->method('in')
->will($this->returnValue('y'));
$this->Task->expects($this->never())->method('_stop');

$this->Task->execute();
$this->assertTrue(file_exists($this->path . DS . 'LC_TIME' . DS . 'default.pot'));

$result = file_get_contents($this->path . DS . 'default.pot');

$pattern = '/\#: .*extract\.ctp:31\n/';
$this->assertNotRegExp($pattern, $result);
}

/**
* test exclusions
*
Expand Down
3 changes: 3 additions & 0 deletions lib/Cake/Test/test_app/View/Pages/extract.ctp
Expand Up @@ -26,3 +26,6 @@ __('Hot features!'
. ' Just look at the name...It\'s Cake'
. "\n - Active, Friendly Community:"
. ' Join us #cakephp on IRC. We\'d love to help you get started');

// Category
echo __c('You have a new message (category: LC_TIME).', 5);

0 comments on commit 824a6f7

Please sign in to comment.