Initial Commit

This commit is contained in:
2019-11-21 12:25:31 +01:00
commit f4aabcb9b1
13959 changed files with 787761 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Illuminate\Filesystem\Filesystem;
use Illuminate\View\Compilers\BladeCompiler;
/**
* Class to get gettext strings from blade.php files returning arrays
*/
class Blade extends Extractor implements ExtractorInterface
{
/**
* {@inheritDoc}
*/
public static function fromString($string, Translations $translations = null, $file = '')
{
$bladeCompiler = new BladeCompiler(new Filesystem(), null);
$string = $bladeCompiler->compileString($string);
return PhpCode::fromString($string, $translations, $file);
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Gettext\Extractors;
use Exception;
use InvalidArgumentException;
use Gettext\Translations;
abstract class Extractor
{
/**
* Extract the translations from a file
*
* @param array|string $file A path of a file or files
* @param null|Translations $translations The translations instance to append the new translations.
*
* @return Translations
*/
public static function fromFile($file, Translations $translations = null)
{
if ($translations === null) {
$translations = new Translations();
}
foreach (self::getFiles($file) as $file) {
static::fromString(self::readFile($file), $translations, $file);
}
return $translations;
}
/**
* Checks and returns all files
*
* @param string|array $file The file/s
*
* @return array The file paths
*/
protected static function getFiles($file)
{
if (empty($file)) {
throw new InvalidArgumentException('There is not any file defined');
}
if (is_string($file)) {
if (!is_file($file)) {
throw new InvalidArgumentException("'$file' is not a valid file");
}
if (!is_readable($file)) {
throw new InvalidArgumentException("'$file' is not a readable file");
}
return array($file);
}
if (is_array($file)) {
$files = array();
foreach ($file as $f) {
$files = array_merge($files, self::getFiles($f));
}
return $files;
}
throw new InvalidArgumentException('The first argumet must be string or array');
}
/**
* Reads and returns the content of a file
*
* @param string $file
*
* @return string
*/
protected static function readFile($file)
{
$length = filesize($file);
if (!($fd = fopen($file, 'rb'))) {
throw new Exception("Cannot read the file '$file', probably permissions");
}
$content = $length ? fread($fd, $length) : '';
fclose($fd);
return $content;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
interface ExtractorInterface
{
/**
* Extract the translations from a file
*
* @param array|string $file A path of a file or files
* @param null|Translations $translations The translations instance to append the new translations.
*
* @return Translations
*/
public static function fromFile($file, Translations $translations = null);
/**
* Parses a string and append the translations found in the Translations instance
*
* @param string $string
* @param Translations|null $translations
* @param string $file The file path to insert the reference
*
* @return Translations
*/
public static function fromString($string, Translations $translations = null, $file = '');
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
/**
* Class to get gettext strings from json files
*/
class Jed extends PhpArray implements ExtractorInterface
{
/**
* {@inheritDoc}
*/
public static function fromString($string, Translations $translations = null, $file = '')
{
if ($translations === null) {
$translations = new Translations();
}
$content = json_decode($string);
return PhpArray::handleArray($content, $translations);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\JsFunctionsScanner;
/**
* Class to get gettext strings from javascript files
*/
class JsCode extends Extractor implements ExtractorInterface
{
public static $functions = array(
'__' => '__',
'n__' => 'n__',
'p__' => 'p__',
);
/**
* {@inheritDoc}
*/
public static function fromString($string, Translations $translations = null, $file = '')
{
if ($translations === null) {
$translations = new Translations();
}
$functions = new JsFunctionsScanner($string);
$functions->saveGettextFunctions(self::$functions, $translations, $file);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
/**
* Class to get gettext strings from plain json
*/
class JsonDictionary extends Extractor implements ExtractorInterface
{
/**
* {@inheritDoc}
*/
public static function fromString($string, Translations $translations = null, $file = '')
{
if ($translations === null) {
$translations = new Translations();
}
if (($entries = json_decode($string, true))) {
foreach ($entries as $original => $translation) {
$translations->insert(null, $original)->setTranslation($translation);
}
}
return $translations;
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\StringReader;
/**
* Class to get gettext strings from .mo files
*/
class Mo extends Extractor implements ExtractorInterface
{
const MAGIC1 = -1794895138;
const MAGIC2 = -569244523;
const MAGIC3 = 2500072158;
/**
* {@inheritDoc}
*/
public static function fromString($string, Translations $translations = null, $file = '')
{
if ($translations === null) {
$translations = new Translations();
}
$stream = new StringReader($string);
$magic = self::readInt($stream, 'V');
if (($magic === self::MAGIC1) || ($magic === self::MAGIC3)) { //to make sure it works for 64-bit platforms
$byteOrder = 'V'; //low endian
} elseif ($magic === (self::MAGIC2 & 0xFFFFFFFF)) {
$byteOrder = 'N'; //big endian
} else {
throw new \Exception('Not MO file');
}
self::readInt($stream, $byteOrder);
$total = self::readInt($stream, $byteOrder); //total string count
$originals = self::readInt($stream, $byteOrder); //offset of original table
$tran = self::readInt($stream, $byteOrder); //offset of translation table
$stream->seekto($originals);
$table_originals = self::readIntArray($stream, $byteOrder, $total * 2);
$stream->seekto($tran);
$table_translations = self::readIntArray($stream, $byteOrder, $total * 2);
for ($i = 0; $i < $total; $i++) {
$stream->seekto($table_originals[$i * 2 + 2]);
$original = $stream->read($table_originals[$i * 2 + 1]);
$stream->seekto($table_translations[$i * 2 + 2]);
$translated = $stream->read($table_translations[$i * 2 + 1]);
if ($original === '') {
// Headers
foreach (explode("\n", $translated) as $headerLine) {
if ($headerLine !== '') {
$headerChunks = preg_split('/:\s*/', $headerLine, 2);
$translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : '');
}
}
} else {
$chunks = explode("\x04", $original, 2);
if (isset($chunks[1])) {
$context = $chunks[0];
$original = $chunks[1];
} else {
$context = '';
}
$chunks = explode("\x00", $original, 2);
if (isset($chunks[1])) {
$original = $chunks[0];
$plural = $chunks[1];
} else {
$plural = '';
}
$translation = $translations->insert($context, $original, $plural);
if ($translated !== '') {
if ($plural === '') {
$translation->setTranslation($translated);
} else {
foreach (explode("\x00", $translated) as $pluralIndex => $pluralValue) {
if ($pluralIndex === 0) {
$translation->setTranslation($pluralValue);
} else {
$translation->setPluralTranslation($pluralValue, $pluralIndex - 1);
}
}
}
}
}
}
return $translations;
}
/**
* @param StringReader $stream
* @param string $byteOrder
*/
private static function readInt(StringReader $stream, $byteOrder)
{
if (($read = $stream->read(4)) === false) {
return false;
}
$read = unpack($byteOrder, $read);
return array_shift($read);
}
/**
* @param StringReader $stream
* @param string $byteOrder
* @param int $count
*/
private static function readIntArray(StringReader $stream, $byteOrder, $count)
{
return unpack($byteOrder.$count, $stream->read(4 * $count));
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Gettext\Extractors;
use Exception;
use Gettext\Translations;
/**
* Class to get gettext strings from php files returning arrays
*/
class PhpArray extends Extractor implements ExtractorInterface
{
/**
* Extract the translations from a file
*
* @param array|string $file A path of a file or files
* @param null|Translations $translations The translations instance to append the new translations.
*
* @return Translations
*/
public static function fromFile($file, Translations $translations = null)
{
if ($translations === null) {
$translations = new Translations();
}
foreach (self::getFiles($file) as $file) {
self::handleArray(include($file), $translations);
}
return $translations;
}
/**
* {@inheritDoc}
*/
public static function fromString($string, Translations $translations = null, $file = '')
{
throw new Exception("PhpArray::fromString() cannot be called. Use PhpArray::fromFile()");
}
/**
* Handle an array of translations and append to the Translations instance
*
* @param array $content
* @param Translations $translations
*/
public static function handleArray(array $content, Translations $translations)
{
$content = $content['messages'];
$translations_info = isset($content['']) ? $content[''] : null;
unset($content['']);
if (isset($translations_info['domain'])) {
$translations->setDomain($translations_info['domain']);
}
$context_glue = '\u0004';
foreach ($content as $key => $message) {
$key = explode($context_glue, $key);
$context = isset($key[1]) ? array_shift($key) : '';
$original = array_shift($key);
$plural = array_shift($message);
$translation = array_shift($message);
$plural_translation = array_shift($message);
$entry = $translations->insert($context, $original, $plural);
$entry->setTranslation($translation);
$entry->setPluralTranslation($plural_translation);
}
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Utils\PhpFunctionsScanner;
/**
* Class to get gettext strings from php files returning arrays
*/
class PhpCode extends Extractor implements ExtractorInterface
{
public static $functions = array(
'__' => '__',
'__e' => '__',
'n__' => 'n__',
'n__e' => 'n__',
'p__' => 'p__',
'p__e' => 'p__',
);
/**
* {@inheritDoc}
*/
public static function fromString($string, Translations $translations = null, $file = '')
{
if ($translations === null) {
$translations = new Translations();
}
$functions = new PhpFunctionsScanner($string);
$functions->saveGettextFunctions(self::$functions, $translations, $file);
}
}

View File

@@ -0,0 +1,224 @@
<?php
namespace Gettext\Extractors;
use Gettext\Translations;
use Gettext\Translation;
/**
* Class to get gettext strings from php files returning arrays
*/
class Po extends Extractor implements ExtractorInterface
{
/**
* Parses a .po file and append the translations found in the Translations instance
*
* {@inheritDoc}
*/
public static function fromString($string, Translations $translations = null, $file = '')
{
if ($translations === null) {
$translations = new Translations();
}
$lines = explode("\n", $string);
$i = 0;
$translation = new Translation('', '');
for ($n = count($lines); $i < $n; $i++) {
$line = trim($lines[$i]);
$line = self::fixMultiLines($line, $lines, $i);
if ($line === '') {
if ($translation->is('', '')) {
self::parseHeaders($translation->getTranslation(), $translations);
} elseif ($translation->hasOriginal()) {
$translations[] = $translation;
}
$translation = new Translation('', '');
continue;
}
$splitLine = preg_split('/\s+/', $line, 2);
$key = $splitLine[0];
$data = isset($splitLine[1]) ? $splitLine[1] : '';
switch ($key) {
case '#':
$translation->addComment($data);
$append = null;
break;
case '#.':
$translation->addExtractedComment($data);
$append = null;
break;
case '#,':
foreach (array_map('trim', explode(',', trim($data))) as $value) {
$translation->addFlag($value);
}
$append = null;
break;
case '#:':
foreach (preg_split('/\s+/', trim($data)) as $value) {
if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) {
$translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null);
}
}
$append = null;
break;
case 'msgctxt':
$translation = $translation->getClone(self::clean($data));
$append = 'Context';
break;
case 'msgid':
$translation = $translation->getClone(null, self::clean($data));
$append = 'Original';
break;
case 'msgid_plural':
$translation->setPlural(self::clean($data));
$append = 'Plural';
break;
case 'msgstr':
case 'msgstr[0]':
$translation->setTranslation(self::clean($data));
$append = 'Translation';
break;
case 'msgstr[1]':
$translation->setPluralTranslation(self::clean($data), 0);
$append = 'PluralTranslation';
break;
default:
if (strpos($key, 'msgstr[') === 0) {
$translation->setPluralTranslation(self::clean($data), intval(substr($key, 7, -1)) - 1);
$append = 'PluralTranslation';
break;
}
if (isset($append)) {
if ($append === 'Context') {
$translation = $translation->getClone($translation->getContext()."\n".self::clean($data));
break;
}
if ($append === 'Original') {
$translation = $translation->getClone(null, $translation->getOriginal()."\n".self::clean($data));
break;
}
if ($append === 'PluralTranslation') {
$key = count($translation->getPluralTranslation()) - 1;
$translation->setPluralTranslation($translation->getPluralTranslation($key)."\n".self::clean($data), $key);
break;
}
$getMethod = 'get'.$append;
$setMethod = 'set'.$append;
$translation->$setMethod($translation->$getMethod()."\n".self::clean($data));
}
break;
}
}
if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) {
$translations[] = $translation;
}
return $translations;
}
/**
* Checks if it is a header definition line. Useful for distguishing between header definitions
* and possible continuations of a header entry
*
* @param string $line Line to parse
* @return boolean
*/
private static function isHeaderDefinition($line)
{
return (bool) preg_match('/^[\w-]+:/', $line);
}
/**
* Parse the po headers
*
* @param string $headers
* @param Translations $translations
*
* @return boolean
*/
private static function parseHeaders($headers, Translations $translations)
{
$headers = explode("\n", $headers);
$currentHeader = null;
foreach ($headers as $line) {
$line = self::clean($line);
if (self::isHeaderDefinition($line)) {
$header = array_map('trim', explode(':', $line, 2));
$currentHeader = $header[0];
$translations->setHeader($currentHeader, $header[1]);
} else {
$entry = $translations->getHeader($currentHeader);
$translations->setHeader($currentHeader, $entry.$line);
}
}
}
/**
* Cleans the strings. Removes quotes, "\n", "\t", etc
*
* @param string $str
*
* @return string
*/
private static function clean($str)
{
if (!$str) {
return '';
}
if ($str[0] === '"') {
$str = substr($str, 1, -1);
}
return str_replace(array('\\n', '\\"', '\\t', '\\\\'), array("\n", '"', "\t", '\\'), $str);
}
/**
* Gets one string from multiline strings
*
* @param string $line
* @param array $lines
* @param integer &$i
*
* @return string
*/
private static function fixMultiLines($line, array $lines, &$i)
{
for ($j = $i; $j<count($lines); $j++) {
if (substr($line, -1, 1) == '"'
&& isset($lines[$j+1])
&& substr(trim($lines[$j+1]), 0, 1) == '"'
) {
$line = substr($line, 0, -1).substr(trim($lines[$j+1]), 1);
} else {
$i = $j;
break;
}
}
return $line;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
abstract class Generator
{
/**
* Saves the translations in a file
*
* @param Translations $translations
* @param string $file
*
* @return boolean
*/
public static function toFile(Translations $translations, $file)
{
$content = static::toString($translations);
if (file_put_contents($file, $content) === false) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
interface GeneratorInterface
{
/**
* Saves the translations in a file
*
* @param Translations $translations
* @param string $file
*
* @return boolean
*/
public static function toFile(Translations $translations, $file);
/**
* Generates a string with the translations ready to save in a file
*
* @param Translations $translations
*
* @return string
*/
public static function toString(Translations $translations);
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
class Jed extends Generator implements GeneratorInterface
{
/**
* {@parentDoc}
*/
public static function toString(Translations $translations)
{
$array = PhpArray::toArray($translations);
return json_encode($array);
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
class JsonDictionary extends Generator implements GeneratorInterface
{
/**
* {@parentDoc}
*/
public static function toString(Translations $translations)
{
$array = PhpArray::toArray($translations);
//for a simple json translation dictionary, one domain is supported
$values = current($array);
// remove meta / header data
if (array_key_exists('', $values)) {
unset($values['']);
}
//map to a simple json dictionary (no plurals)
return json_encode(
array_filter(
array_map(function ($val) {
return $val[1];
}, $values)
)
);
}
}

View File

@@ -0,0 +1,131 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
class Mo extends Generator implements GeneratorInterface
{
public static $includeEmptyTranslations = false;
/**
* {@parentDoc}
*/
public static function toString(Translations $translations)
{
$array = array();
$headers = '';
foreach ($translations->getHeaders() as $headerName => $headerValue) {
$headers .= "$headerName: $headerValue\n";
}
if ($headers !== '') {
$array[''] = $headers;
}
foreach ($translations as $translation) {
if (!$translation->hasTranslation() && !static::$includeEmptyTranslations) {
continue;
}
if ($translation->hasContext()) {
$originalString = $translation->getContext()."\x04".$translation->getOriginal();
} else {
$originalString = $translation->getOriginal();
}
$array[$originalString] = $translation;
}
ksort($array);
$numEntries = count($array);
$originalsTable = '';
$translationsTable = '';
$originalsIndex = array();
$translationsIndex = array();
foreach ($array as $originalString => $translation) {
if (is_string($translation)) {
// Headers
$translationString = $translation;
} else {
/* @var $translation \Gettext\Translation */
if ($translation->hasPlural()) {
$originalString .= "\x00".$translation->getPlural();
}
$translationString = $translation->getTranslation();
if ($translation->hasPluralTranslation()) {
$translationString .= "\x00".implode("\x00", $translation->getPluralTranslation());
}
}
$originalsIndex[] = array('relativeOffset' => strlen($originalsTable), 'length' => strlen($originalString));
$originalsTable .= $originalString."\x00";
$translationsIndex[] = array('relativeOffset' => strlen($translationsTable), 'length' => strlen($translationString));
$translationsTable .= $translationString."\x00";
}
// Offset of table with the original strings index: right after the header (which is 7 words)
$originalsIndexOffset = 7 * 4;
// Size of table with the original strings index
$originalsIndexSize = $numEntries * (4 + 4);
// Offset of table with the translation strings index: right after the original strings index table
$translationsIndexOffset = $originalsIndexOffset + $originalsIndexSize;
// Size of table with the translation strings index
$translationsIndexSize = $numEntries * (4 + 4);
// Hashing table starts after the header and after the index table
$originalsStringsOffset = $translationsIndexOffset + $translationsIndexSize;
// Translations start after the keys
$translationsStringsOffset = $originalsStringsOffset + strlen($originalsTable);
// Let's generate the .mo file binary data
$mo = '';
// Magic number
$mo .= pack('L', 0x950412de);
// File format revision
$mo .= pack('L', 0);
// Number of strings
$mo .= pack('L', $numEntries);
// Offset of table with original strings
$mo .= pack('L', $originalsIndexOffset);
// Offset of table with translation strings
$mo .= pack('L', $translationsIndexOffset);
// Size of hashing table: we don't use it.
$mo .= pack('L', 0);
// Offset of hashing table: it would start right after the translations index table
$mo .= pack('L', $translationsIndexOffset + $translationsIndexSize);
// Write the lengths & offsets of the original strings
foreach ($originalsIndex as $info) {
$mo .= pack('L', $info['length']);
$mo .= pack('L', $originalsStringsOffset + $info['relativeOffset']);
}
// Write the lengths & offsets of the translated strings
foreach ($translationsIndex as $info) {
$mo .= pack('L', $info['length']);
$mo .= pack('L', $translationsStringsOffset + $info['relativeOffset']);
}
// Write original strings
$mo .= $originalsTable;
// Write translation strings
$mo .= $translationsTable;
return $mo;
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
class PhpArray extends Generator implements GeneratorInterface
{
/**
* {@inheritDoc}
*/
public static function toString(Translations $translations)
{
$array = self::toArray($translations);
return '<?php return '.var_export($array, true).'; ?>';
}
/**
* Generates an array with the translations
*
* @param Translations $translations
*
* @return array
*/
public static function toArray(Translations $translations)
{
$array = array();
$context_glue = "\004";
foreach ($translations as $translation) {
$key = ($translation->hasContext() ? $translation->getContext().$context_glue : '').$translation->getOriginal();
$entry = array($translation->getPlural(), $translation->getTranslation());
if ($translation->hasPluralTranslation()) {
$entry = array_merge($entry, $translation->getPluralTranslation());
}
$array[$key] = $entry;
}
$domain = $translations->getDomain() ?: 'messages';
$lang = $translations->getLanguage() ?: 'en';
$fullArray = array(
$domain => array(
'' => array(
'domain' => $domain,
'lang' => $lang,
'plural-forms' => 'nplurals=2; plural=(n != 1);',
),
),
);
if ($translations->getHeader('Plural-Forms') !== null) {
$fullArray[$domain]['']['plural-forms'] = $translations->getHeader('Plural-Forms');
}
$fullArray[$domain] = array_merge($fullArray[$domain], $array);
return $fullArray;
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace Gettext\Generators;
use Gettext\Translations;
class Po extends Generator implements GeneratorInterface
{
/**
* {@parentDoc}
*/
public static function toString(Translations $translations)
{
$lines = array('msgid ""', 'msgstr ""');
$headers = $translations->getHeaders();
$headers['PO-Revision-Date'] = date('c');
foreach ($headers as $name => $value) {
$lines[] = '"'.$name.': '.$value.'\\n"';
}
$lines[] = '';
//Translations
foreach ($translations as $translation) {
if ($translation->hasComments()) {
foreach ($translation->getComments() as $comment) {
$lines[] = '# '.$comment;
}
}
if ($translation->hasExtractedComments()) {
foreach ($translation->getExtractedComments() as $comment) {
$lines[] = '#. '.$comment;
}
}
if ($translation->hasReferences()) {
foreach ($translation->getReferences() as $reference) {
$lines[] = '#: '.$reference[0].(!is_null($reference[1]) ? ':'.$reference[1] : null);
}
}
if ($translation->hasFlags()) {
$lines[] = '#, '.implode(',', $translation->getFlags());
}
if ($translation->hasContext()) {
$lines[] = 'msgctxt '.self::quote($translation->getContext());
}
self::addLines($lines, 'msgid', $translation->getOriginal());
if ($translation->hasPlural()) {
self::addLines($lines, 'msgid_plural', $translation->getPlural());
self::addLines($lines, 'msgstr[0]', $translation->getTranslation());
foreach ($translation->getPluralTranslation() as $k => $v) {
self::addLines($lines, 'msgstr['.($k + 1).']', $v);
}
} else {
self::addLines($lines, 'msgstr', $translation->getTranslation());
}
$lines[] = '';
}
return implode("\n", $lines);
}
/**
* Escapes and adds double quotes to a string
*
* @param string $string
*
* @return string
*/
private static function quote($string)
{
return '"'.str_replace(array('\\', "\r", "\n", "\t", '"'), array('\\\\', '', '\n', '\t', '\\"'), $string).'"';
}
/**
* Escapes and adds double quotes to a string
*
* @param string $string
*
* @return string
*/
private static function multilineQuote($string)
{
$lines = explode("\n", $string);
$last = count($lines) - 1;
foreach ($lines as $k => $line) {
if ($k === $last) {
$lines[$k] = self::quote($line);
} else {
$lines[$k] = self::quote($line."\n");
}
}
return $lines;
}
/**
* Add one or more lines depending whether the string is multiline or not
*
* @param array &$lines
* @param string $name
* @param string $value
*/
private static function addLines(array &$lines, $name, $value)
{
$newLines = self::multilineQuote($value);
if (count($newLines) === 1) {
$lines[] = $name.' '.$newLines[0];
} else {
$lines[] = $name.' ""';
foreach ($newLines as $line) {
$lines[] = $line;
}
}
}
}

View File

@@ -0,0 +1,478 @@
<?php
namespace Gettext;
/**
* Class to manage a translation string
*/
class Translation
{
protected $context;
protected $original;
protected $translation = '';
protected $plural;
protected $pluralTranslation = array();
protected $references = array();
protected $comments = array();
protected $extractedComments = array();
protected $flags = array();
protected $translationCount;
/**
* Generates the id of a translation (context + glue + original)
*
* @param string $context
* @param string $original
*
* @return string
*/
public static function generateId($context, $original)
{
return "{$context}\004{$original}";
}
/**
* Construct
*
* @param string $context The context of the translation
* @param string $original The original string
* @param string $plural The original plural string
*/
public function __construct($context, $original, $plural = '')
{
$this->context = (string) $context;
$this->original = (string) $original;
$this->setPlural($plural);
}
/**
* Clones this translation
*
* @param null|string $context Optional new context
* @param null|string $original Optional new original
*/
public function getClone($context = null, $original = null)
{
$new = clone $this;
if ($context !== null) {
$new->context = (string) $context;
}
if ($original !== null) {
$new->original = (string) $original;
}
return $new;
}
/**
* Returns the id of this translation
*
* @return string
*/
public function getId()
{
return static::generateId($this->context, $this->original);
}
/**
* Checks whether the translation matches with the arguments
*
* @param string $context
* @param string $original
*
* @return boolean
*/
public function is($context, $original = '')
{
return (($this->context === $context) && ($this->original === $original)) ? true : false;
}
/**
* Gets the original string
*
* @return string
*/
public function getOriginal()
{
return $this->original;
}
/**
* Checks if the original string is empty or not
*
* @return boolean
*/
public function hasOriginal()
{
return ($this->original !== '') ? true : false;
}
/**
* Sets the translation string
*
* @param string $translation
*/
public function setTranslation($translation)
{
$this->translation = (string) $translation;
}
/**
* Gets the translation string
*
* @return string
*/
public function getTranslation()
{
return $this->translation;
}
/**
* Checks if the translation string is empty or not
*
* @return boolean
*/
public function hasTranslation()
{
return ($this->translation !== '') ? true : false;
}
/**
* Sets the plural translation string
*
* @param string $plural
*/
public function setPlural($plural)
{
$this->plural = (string) $plural;
$this->normalizeTranslationCount();
}
/**
* Gets the plural translation string
*
* @return string
*/
public function getPlural()
{
return $this->plural;
}
/**
* Checks if the plural translation string is empty or not
*
* @return boolean
*/
public function hasPlural()
{
return ($this->plural !== '') ? true : false;
}
/**
* Set a new plural translation
*
* @param string $plural The plural string to add
* @param integer $key The key of the plural translation.
*/
public function setPluralTranslation($plural, $key = 0)
{
$this->pluralTranslation[$key] = $plural;
$this->normalizeTranslationCount();
}
/**
* Gets one or all plural translations
*
* @param integer|null $key The key to return. If is null, return all translations
*
* @return string|array
*/
public function getPluralTranslation($key = null)
{
if ($key === null) {
return $this->pluralTranslation;
}
return isset($this->pluralTranslation[$key]) ? (string) $this->pluralTranslation[$key] : '';
}
/**
* Checks if there are any plural translation
*
* @return boolean
*/
public function hasPluralTranslation()
{
return implode('', $this->pluralTranslation) !== '';
}
/**
* Removes all plural translations
*/
public function deletePluralTranslation()
{
$this->pluralTranslation = array();
$this->normalizeTranslationCount();
}
/**
* Set the number of singular + plural translations allowed
*
* @param integer $count
*/
public function setTranslationCount($count)
{
$this->translationCount = is_null($count) ? null : intval($count);
$this->normalizeTranslationCount();
}
/**
* Returns the number of singular + plural translations
* Returns null if this Translation is not a plural one
*
* @return integer|null
*/
public function getTranslationCount()
{
return $this->hasPlural() ? $this->translationCount : null;
}
/**
* Normalizes the translation count
*/
protected function normalizeTranslationCount()
{
if ($this->translationCount === null) {
return;
}
if ($this->hasPlural()) {
$allowed = $this->translationCount - 1;
$current = count($this->pluralTranslation);
if ($allowed > $current) {
$this->pluralTranslation = $this->pluralTranslation + array_fill(0, $allowed, '');
} elseif ($current > $allowed) {
$this->pluralTranslation = array_slice($this->pluralTranslation, 0, $allowed);
}
} else {
$this->pluralTranslation = array();
}
}
/**
* Gets the context of this translation
*
* @return string
*/
public function getContext()
{
return $this->context;
}
/**
* Checks if the context is empty or not
*
* @return boolean
*/
public function hasContext()
{
return (isset($this->context) && ($this->context !== '')) ? true : false;
}
/**
* Adds a new reference for this translation
*
* @param string $filename The file path where the translation has been found
* @param null|integer $line The line number where the translation has been found
*/
public function addReference($filename, $line = null)
{
$key = "{$filename}:{$line}";
$this->references[$key] = array($filename, $line);
}
/**
* Checks if the translation has any reference
*
* @return boolean
*/
public function hasReferences()
{
return !empty($this->references);
}
/**
* Return all references for this translation
*
* @return array
*/
public function getReferences()
{
return array_values($this->references);
}
/**
* Removes all references
*/
public function deleteReferences()
{
$this->references = array();
}
/**
* Adds a new comment for this translation
*
* @param string $comment
*/
public function addComment($comment)
{
$this->comments[] = $comment;
}
/**
* Checks if the translation has any comment
*
* @return boolean
*/
public function hasComments()
{
return isset($this->comments[0]);
}
/**
* Returns all comments for this translation
*
* @return array
*/
public function getComments()
{
return $this->comments;
}
/**
* Removes all comments
*/
public function deleteComments()
{
$this->comments = array();
}
/**
* Adds a new extracted comment for this translation
*
* @param string $comment
*/
public function addExtractedComment($comment)
{
$this->extractedComments[] = $comment;
}
/**
* Checks if the translation has any extracted comment
*
* @return boolean
*/
public function hasExtractedComments()
{
return isset($this->extractedComments[0]);
}
/**
* Returns all extracted comments for this translation
*
* @return array
*/
public function getExtractedComments()
{
return $this->extractedComments;
}
/**
* Removes all extracted comments
*/
public function deleteExtractedComments()
{
$this->extractedComments = array();
}
/**
* Adds a new flat for this translation
*
* @param string $flag
*/
public function addFlag($flag)
{
$this->flags[] = $flag;
}
/**
* Checks if the translation has any flag
*
* @return boolean
*/
public function hasFlags()
{
return isset($this->flags[0]);
}
/**
* Returns all extracted flags for this translation
*
* @return array
*/
public function getFlags()
{
return $this->flags;
}
/**
* Removes all flags
*/
public function deleteFlags()
{
$this->flags = array();
}
/**
* Merges this translation with other translation
*
* @param Translation $translation The translation to merge with
* @param integer|null $method One or various Translations::MERGE_* constants to define how to merge the translations
*/
public function mergeWith(Translation $translation, $method = null)
{
if ($method === null) {
$method = Translations::$mergeDefault;
}
if (!$this->hasTranslation()) {
$this->setTranslation($translation->getTranslation());
}
if (($method & Translations::MERGE_PLURAL) && !$this->hasPlural()) {
$this->setPlural($translation->getPlural());
}
if ($this->hasPlural() && !$this->hasPluralTranslation() && $translation->hasPluralTranslation()) {
$this->pluralTranslation = $translation->getPluralTranslation();
}
if ($method & Translations::MERGE_REFERENCES) {
foreach ($translation->getReferences() as $reference) {
$this->addReference($reference[0], $reference[1]);
}
}
if ($method & Translations::MERGE_COMMENTS) {
$this->comments = array_values(array_unique(array_merge($translation->getComments(), $this->comments)));
$this->extractedComments = array_values(array_unique(array_merge($translation->getExtractedComments(), $this->extractedComments)));
$this->flags = array_values(array_unique(array_merge($translation->getFlags(), $this->flags)));
}
}
}

View File

@@ -0,0 +1,381 @@
<?php
namespace Gettext;
use Gettext\Languages\Language;
/**
* Class to manage a collection of translations
*/
class Translations extends \ArrayObject
{
const MERGE_ADD = 1;
const MERGE_REMOVE = 2;
const MERGE_HEADERS = 4;
const MERGE_REFERENCES = 8;
const MERGE_COMMENTS = 16;
const MERGE_LANGUAGE = 32;
const MERGE_PLURAL = 64;
const HEADER_LANGUAGE = 'Language';
const HEADER_PLURAL = 'Plural-Forms';
const HEADER_DOMAIN = 'X-Domain';
public static $mergeDefault = 93; // self::MERGE_ADD | self::MERGE_HEADERS | self::MERGE_COMMENTS | self::MERGE_REFERENCES | self::MERGE_PLURAL
private $headers;
private $translationCount;
/**
* @see \ArrayObject::__construct()
*/
public function __construct($input = array(), $flags = 0, $iterator_class = 'ArrayIterator')
{
$this->headers = array(
'Project-Id-Version' => '',
'Report-Msgid-Bugs-To' => '',
'Last-Translator' => '',
'Language-Team' => '',
'MIME-Version' => '1.0',
'Content-Type' => 'text/plain; charset=UTF-8',
'Content-Transfer-Encoding' => '8bit',
'POT-Creation-Date' => date('c'),
'PO-Revision-Date' => date('c'),
);
$this->headers[self::HEADER_LANGUAGE] = '';
parent::__construct($input, $flags, $iterator_class);
}
/**
* Magic method to create new instances using extractors
* For example: Translations::fromMoFile($filename);
*
* @return Translations
*/
public static function __callStatic($name, $arguments)
{
if (!preg_match('/^from(\w+)(File|String)$/i', $name, $matches)) {
throw new \Exception("The method $name does not exists");
}
return call_user_func_array('Gettext\\Extractors\\'.$matches[1].'::from'.$matches[2], $arguments);
}
/**
* Magic method to export the translations to a specific format
* For example: $translations->toMoFile($filename);
*
* @return bool|string
*/
public function __call($name, $arguments)
{
if (!preg_match('/^to(\w+)(File|String)$/i', $name, $matches)) {
throw new \Exception("The method $name does not exists");
}
array_unshift($arguments, $this);
return call_user_func_array('Gettext\\Generators\\'.$matches[1].'::to'.$matches[2], $arguments);
}
/**
* Magic method to clone each translation on clone the translations object
*/
public function __clone()
{
$array = array();
foreach ($this as $key => $translation) {
$array[$key] = clone $translation;
}
$this->exchangeArray($array);
}
/**
* Control the new translations added
*
* @param mixed $index
* @param mixed $value
*
* @throws InvalidArgumentException If the value is not an instance of Gettext\Translation
*
* @return Translation
*/
public function offsetSet($index, $value)
{
if (!($value instanceof Translation)) {
throw new \InvalidArgumentException('Only instances of Gettext\\Translation must be added to a Gettext\\Translations');
}
$id = $value->getId();
if ($this->offsetExists($id)) {
$this[$id]->mergeWith($value);
$this[$id]->setTranslationCount($this->translationCount);
return $this[$id];
}
$value->setTranslationCount($this->translationCount);
parent::offsetSet($id, $value);
return $value;
}
/**
* Set the plural definition
*
* @param integer $count
* @param string $rule
*/
public function setPluralForms($count, $rule)
{
$this->setHeader(self::HEADER_PLURAL, "nplurals={$count}; plural={$rule};");
}
/**
* Returns the parsed plural definition
*
* @param null|array [count, rule]
*/
public function getPluralForms()
{
$header = $this->getHeader(self::HEADER_PLURAL);
if ($header && preg_match('/^nplurals\s*=\s*(\d+)\s*;\s*plural\s*=\s*([^;]+)\s*;$/', $header, $matches)) {
return array(intval($matches[1]), $matches[2]);
}
}
/**
* Set a new header.
*
* @param string $name
* @param string $value
*/
public function setHeader($name, $value)
{
$name = trim($name);
$this->headers[$name] = trim($value);
if ($name === self::HEADER_PLURAL) {
if ($forms = $this->getPluralForms()) {
$this->translationCount = $forms[0];
foreach ($this as $t) {
$t->setTranslationCount($this->translationCount);
}
} else {
$this->translationCount = null;
}
}
}
/**
* Returns a header value
*
* @param string $name
*
* @return null|string
*/
public function getHeader($name)
{
return isset($this->headers[$name]) ? $this->headers[$name] : null;
}
/**
* Returns all header for this translations
*
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Removes all headers
*/
public function deleteHeaders()
{
$this->headers = array();
}
/**
* Removes one header
*
* @param string $name
*/
public function deleteHeader($name)
{
unset($this->headers[$name]);
}
/**
* Returns the language value
*
* @return string $language
*/
public function getLanguage()
{
return $this->getHeader(self::HEADER_LANGUAGE);
}
/**
* Sets the language and the plural forms
*
* @param string $language
*
* @return boolean Returns true if the plural rules has been updated, false if $language hasn't been recognized
*/
public function setLanguage($language)
{
$this->setHeader(self::HEADER_LANGUAGE, trim($language));
if (($info = Language::getById($language))) {
$this->setPluralForms(count($info->categories), $info->formula);
return true;
}
return false;
}
/**
* Checks whether the language is empty or not
*
* @return boolean
*/
public function hasLanguage()
{
$language = $this->getLanguage();
return (is_string($language) && ($language !== '')) ? true : false;
}
/**
* Set a new domain for this translations
*
* @param string $domain
*/
public function setDomain($domain)
{
$this->setHeader(self::HEADER_DOMAIN, trim($domain));
}
/**
* Returns the domain
*
* @return string
*/
public function getDomain()
{
return $this->getHeader(self::HEADER_DOMAIN);
}
/**
* Checks whether the domain is empty or not
*
* @return boolean
*/
public function hasDomain()
{
$domain = $this->getDomain();
return (is_string($domain) && ($domain !== '')) ? true : false;
}
/**
* Search for a specific translation
*
* @param string|Translation $context The context of the translation or a translation instance
* @param string $original The original string
*
* @return Translation|false
*/
public function find($context, $original = '')
{
if ($context instanceof Translation) {
$id = $context->getId();
} else {
$id = Translation::generateId($context, $original);
}
return $this->offsetExists($id) ? $this[$id] : false;
}
/**
* Creates and insert/merges a new translation
*
* @param string $context The translation context
* @param string $original The translation original string
* @param string $plural The translation original plural string
*
* @return Translation The translation created
*/
public function insert($context, $original, $plural = '')
{
return $this->offsetSet(null, new Translation($context, $original, $plural));
}
/**
* Merges this translations with other translations
*
* @param Translations $translations The translations instance to merge with
* @param integer|null $method One or various Translations::MERGE_* constants to define how to merge the translations
*/
public function mergeWith(Translations $translations, $method = null)
{
if ($method === null) {
$method = self::$mergeDefault;
}
if ($method & self::MERGE_HEADERS) {
foreach ($translations->getHeaders() as $name => $value) {
if (!$this->getHeader($name)) {
$this->setHeader($name, $value);
}
}
}
$add = (boolean) ($method & self::MERGE_ADD);
foreach ($translations as $entry) {
if (($existing = $this->find($entry))) {
$existing->mergeWith($entry, $method);
} elseif ($add) {
$this[] = clone $entry;
}
}
if ($method & self::MERGE_REMOVE) {
$filtered = array();
foreach ($this as $entry) {
if ($translations->find($entry)) {
$filtered[] = $entry;
}
}
$this->exchangeArray($filtered);
}
if ($method & self::MERGE_LANGUAGE) {
$language = $translations->getLanguage();
$pluralForm = $translations->getPluralForms();
if (!$pluralForm) {
if ($language) {
$this->setLanguage($language);
}
} else {
if ($language) {
$this->setHeader(self::HEADER_LANGUAGE, $language);
}
$this->setPluralForms($pluralForm[0], $pluralForm[1]);
}
}
}
}

View File

@@ -0,0 +1,308 @@
<?php
namespace Gettext;
use Gettext\Generators\PhpArray;
class Translator
{
public static $current;
private $domain;
private $dictionary = array();
private $context_glue = "\004";
private $plurals = array();
/**
* Set a translation instance as global, to use it with the gettext functions
*
* @param Translator $translator
*/
public static function initGettextFunctions(Translator $translator)
{
self::$current = $translator;
include_once __DIR__.'/translator_functions.php';
}
/**
* Loads translation from a Translations instance, a file on an array
*
* @param Translations|string|array $translations
*
* @return Translator
*/
public function loadTranslations($translations)
{
if (is_object($translations) && $translations instanceof Translations) {
$translations = PhpArray::toArray($translations);
} elseif (is_string($translations) && is_file($translations)) {
$translations = include $translations;
} elseif (!is_array($translations)) {
throw new \InvalidArgumentException('Invalid Translator: only arrays, files or instance of Translations are allowed');
}
foreach ($translations as $translation) {
$this->addTranslations($translation);
}
return $this;
}
/**
* Set new translations to the dictionary
*
* @param array $translations
*/
public function addTranslations(array $translations)
{
$info = isset($translations['']) ? $translations[''] : null;
unset($translations['']);
$domain = isset($info['domain']) ? $info['domain'] : 'messages';
//Set the first domain loaded as default domain
if (!$this->domain) {
$this->domain = $domain;
}
if (!isset($this->dictionary[$domain])) {
// If a plural form is set we extract those values
$pluralForms = empty($info['plural-forms']) ? 'nplurals=2; plural=(n != 1)' : $info['plural-forms'];
list($count, $code) = explode(';', $pluralForms, 2);
// extract just the expression turn 'n' into a php variable '$n'.
// Slap on a return keyword and semicolon at the end.
$this->plurals[$domain] = array(
'count' => (int) str_replace('nplurals=', '', $count),
'code' => str_replace('plural=', 'return ', str_replace('n', '$n', $code)).';',
);
$this->dictionary[$domain] = $translations;
} else {
$this->dictionary[$domain] = array_replace_recursive($this->dictionary[$domain], $translations);
}
}
/**
* Clear all translations
*/
public function clearTranslations()
{
$this->dictionary = array();
}
/**
* Search and returns a translation
*
* @param string $domain
* @param string $context
* @param string $original
*
* @return array
*/
public function getTranslation($domain, $context, $original)
{
$key = isset($context) ? $context.$this->context_glue.$original : $original;
return isset($this->dictionary[$domain][$key]) ? $this->dictionary[$domain][$key] : false;
}
/**
* Gets a translation using the original string
*
* @param string $original
*
* @return string
*/
public function gettext($original)
{
return $this->dpgettext($this->domain, null, $original);
}
/**
* Gets a translation checking the plural form
*
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
public function ngettext($original, $plural, $value)
{
return $this->dnpgettext($this->domain, null, $original, $plural, $value);
}
/**
* Gets a translation checking the domain and the plural form
*
* @param string $domain
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
public function dngettext($domain, $original, $plural, $value)
{
return $this->dnpgettext($domain, null, $original, $plural, $value);
}
/**
* Gets a translation checking the context and the plural form
*
* @param string $context
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
public function npgettext($context, $original, $plural, $value)
{
return $this->dnpgettext($this->domain, $context, $original, $plural, $value);
}
/**
* Gets a translation checking the context
*
* @param string $context
* @param string $original
*
* @return string
*/
public function pgettext($context, $original)
{
return $this->dpgettext($this->domain, $context, $original);
}
/**
* Gets a translation checking the domain
*
* @param string $domain
* @param string $original
*
* @return string
*/
public function dgettext($domain, $original)
{
return $this->dpgettext($domain, null, $original);
}
/**
* Gets a translation checking the domain and context
*
* @param string $domain
* @param string $context
* @param string $original
*
* @return string
*/
public function dpgettext($domain, $context, $original)
{
$translation = $this->getTranslation($domain, $context, $original);
if (isset($translation[1]) && $translation[1] !== '') {
return $translation[1];
}
return $original;
}
/**
* Gets a translation checking the domain, the context and the plural form
*
* @param string $domain
* @param string $context
* @param string $original
* @param string $plural
* @param string $value
*/
public function dnpgettext($domain, $context, $original, $plural, $value)
{
$key = $this->isPlural($domain, $value);
$translation = $this->getTranslation($domain, $context, $original);
if (isset($translation[$key]) && $translation[$key] !== '') {
return $translation[$key];
}
return ($key === 1) ? $original : $plural;
}
/**
* Executes the plural decision code given the number to decide which
* plural version to take.
*
* @param string $domain
* @param string $n
* @return int
*/
public function isPlural($domain, $n)
{
//Not loaded domain, use a fallback
if (!isset($this->plurals[$domain])) {
return $n == 1 ? 1 : 2;
}
if (!isset($this->plurals[$domain]['function'])) {
$this->plurals[$domain]['function'] = create_function('$n', self::fixTerseIfs($this->plurals[$domain]['code']));
}
if ($this->plurals[$domain]['count'] <= 2) {
return (call_user_func($this->plurals[$domain]['function'], $n)) ? 2 : 1;
}
// We need to +1 because while (GNU) gettext codes assume 0 based,
// this gettext actually stores 1 based.
return (call_user_func($this->plurals[$domain]['function'], $n)) + 1;
}
/**
* This function will recursively wrap failure states in brackets if they contain a nested terse if
*
* This because PHP can not handle nested terse if's unless they are wrapped in brackets.
*
* This code probably only works for the gettext plural decision codes.
*
* return ($n==1 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);
* becomes
* return ($n==1 ? 0 : ($n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2));
*
* @param string $code the terse if string
* @param bool $inner If inner is true we wrap it in brackets
* @return string A formatted terse If that PHP can work with.
*/
private static function fixTerseIfs($code, $inner = false)
{
/**
* (?P<expression>[^?]+) Capture everything up to ? as 'expression'
* \? ?
* (?P<success>[^:]+) Capture everything up to : as 'success'
* : :
* (?P<failure>[^;]+) Capture everything up to ; as 'failure'
*/
preg_match('/(?P<expression>[^?]+)\?(?P<success>[^:]+):(?P<failure>[^;]+)/', $code, $matches);
// If no match was found then no terse if was present
if (!isset($matches[0])) {
return $code;
}
$expression = $matches['expression'];
$success = $matches['success'];
$failure = $matches['failure'];
// Go look for another terse if in the failure state.
$failure = self::fixTerseIfs($failure, true);
$code = $expression.' ? '.$success.' : '.$failure;
if ($inner) {
return "($code)";
}
// note the semicolon. We need that for executing the code.
return "$code;";
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Gettext\Utils;
use Exception;
use Gettext\Translations;
abstract class FunctionsScanner
{
/**
* Scan and returns the functions and the arguments
*
* @return array
*/
abstract public function getFunctions();
/**
* Search for specific functions and create translations
*
* @param array $functions The gettext functions to search
* @param Translations $translations The translations instance where save the values
* @param string $file The filename used to the reference
*/
public function saveGettextFunctions(array $functions, Translations $translations, $file = '')
{
foreach ($this->getFunctions() as $function) {
list($name, $line, $args) = $function;
if (!isset($functions[$name])) {
continue;
}
switch ($functions[$name]) {
case '__':
if (!isset($args[0])) {
continue 2;
}
$original = $args[0];
$translation = $translations->insert('', $original);
break;
case 'n__':
if (!isset($args[1])) {
continue 2;
}
$original = $args[0];
$plural = $args[1];
$translation = $translations->insert('', $original, $plural);
break;
case 'p__':
if (!isset($args[1])) {
continue 2;
}
$context = $args[0];
$original = $args[1];
$translation = $translations->insert($context, $original);
break;
default:
throw new Exception('Not valid functions');
}
$translation->addReference($file, $line);
}
}
}

View File

@@ -0,0 +1,216 @@
<?php
namespace Gettext\Utils;
class JsFunctionsScanner extends FunctionsScanner
{
protected $code;
protected $status = array();
/**
* Constructor
*
* @param string $code The php code to scan
*/
public function __construct($code)
{
$this->code = $code;
}
/**
* {@inheritDoc}
*/
public function getFunctions()
{
$length = strlen($this->code);
$line = 1;
$buffer = '';
$functions = array();
$bufferFunctions = array();
$char = null;
for ($pos = 0; $pos < $length; $pos++) {
$prev = $char;
$char = $this->code[$pos];
$next = isset($this->code[$pos]) ? $this->code[$pos] : null;
switch ($char) {
case "\n":
++$line;
if ($this->status('line-comment')) {
$this->upStatus();
}
break;
case "/":
switch ($this->status()) {
case 'simple-quote':
case 'double-quote':
case 'line-comment':
break;
case 'block-comment':
if ($prev === '*') {
$this->upStatus();
}
break;
default:
if ($next === '/') {
$this->downStatus('line-comment');
} elseif ($next === '*') {
$this->downStatus('block-comment');
}
break;
}
break;
case "'":
switch ($this->status()) {
case 'simple-quote':
$this->upStatus();
break;
case 'line-comment':
case 'block-comment':
break;
default:
$this->downStatus('simple-quote');
break;
}
break;
case '"':
switch ($this->status()) {
case 'double-quote':
$this->upStatus();
break;
case 'line-comment':
case 'block-comment':
break;
default:
$this->downStatus('double-quote');
break;
}
break;
case '(':
switch ($this->status()) {
case 'double-quote':
case 'line-comment':
case 'block-comment':
break;
default:
if ($buffer && preg_match('/(\w+)$/', $buffer, $matches)) {
$this->downStatus('function');
array_unshift($bufferFunctions, array($matches[1], $line, array()));
$buffer = '';
continue 3;
}
break;
}
break;
case ')':
switch ($this->status()) {
case 'function':
if (($argument = self::prepareArgument($buffer))) {
$bufferFunctions[0][2][] = $argument;
}
if ($bufferFunctions) {
$functions[] = array_shift($bufferFunctions);
}
$buffer = '';
continue 3;
}
case ',':
switch ($this->status()) {
case 'function':
if (($argument = self::prepareArgument($buffer))) {
$bufferFunctions[0][2][] = $argument;
}
$buffer = '';
continue 3;
}
}
switch ($this->status()) {
case 'line-comment':
case 'block-comment':
break;
default:
$buffer .= $char;
break;
}
}
return $functions;
}
/**
* Get the current context of the scan
*
* @param null|string $match To check whether the current status is this value
*
* @return string|boolean
*/
protected function status($match = null)
{
$status = isset($this->status[0]) ? $this->status[0] : null;
if ($match) {
return ($status === $match);
}
return $status;
}
/**
* Add a new status to the stack
*
* @param string $status
*/
protected function downStatus($status)
{
array_unshift($this->status, $status);
}
/**
* Removes and return the current status
*
* @return string|null
*/
protected function upStatus()
{
return array_shift($this->status);
}
/**
* Prepares the arguments found in functions
*
* @param string $argument
*
* @return string
*/
protected static function prepareArgument($argument)
{
if ($argument && ($argument[0] === '"' || $argument[0] === "'")) {
if ($argument[0] === '"') {
$argument = str_replace('\\"', '"', $argument);
} else {
$argument = str_replace("\\'", "'", $argument);
}
return substr($argument, 1, -1);
}
}
}

View File

@@ -0,0 +1,64 @@
<?php
namespace Gettext\Utils;
class PhpFunctionsScanner extends FunctionsScanner
{
protected $tokens;
/**
* Constructor
*
* @param string $code The php code to scan
*/
public function __construct($code)
{
$this->tokens = token_get_all($code);
}
/**
* {@inheritDoc}
*/
public function getFunctions()
{
$count = count($this->tokens);
$bufferFunctions = array();
$functions = array();
for ($k = 0; $k < $count; $k++) {
$value = $this->tokens[$k];
//close the current function
if (is_string($value)) {
if ($value === ')' && isset($bufferFunctions[0])) {
$functions[] = array_shift($bufferFunctions);
}
continue;
}
//add an argument to the current function
if (isset($bufferFunctions[0]) && ($value[0] === T_CONSTANT_ENCAPSED_STRING)) {
$val = $value[1];
if ($val[0] === '"') {
$val = str_replace('\\"', '"', $val);
} else {
$val = str_replace("\\'", "'", $val);
}
$bufferFunctions[0][2][] = substr($val, 1, -1);
continue;
}
//new function found
if (($value[0] === T_STRING) && is_string($this->tokens[$k + 1]) && ($this->tokens[$k + 1] === '(')) {
array_unshift($bufferFunctions, array($value[1], $value[2], array()));
$k++;
continue;
}
}
return $functions;
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Gettext\Utils;
class StringReader
{
public $pos;
public $str;
public $strlen;
/**
* Constructor
*
* @param string $str The string to read
*/
public function __construct($str)
{
$this->str = $str;
$this->strlen = strlen($this->str);
}
/**
* Read and returns a part of the string
*
* @param int $bytes The number of bytes to read
*
* @return string
*/
public function read($bytes)
{
$data = substr($this->str, $this->pos, $bytes);
$this->seekto($this->pos + $bytes);
return $data;
}
/**
* Move the cursor to a specific position
*
* @param int $pos The amount of bytes to move
*
* @return int The new position
*/
public function seekto($pos)
{
$this->pos = ($this->strlen < $pos) ? $this->strlen : $pos;
return $this->pos;
}
}

View File

@@ -0,0 +1,12 @@
<?php
spl_autoload_register(function ($class) {
if (strpos($class, 'Gettext\\') !== 0) {
return;
}
$file = __DIR__.str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('Gettext'))).'.php';
if (is_file($file)) {
require_once $file;
}
});

View File

@@ -0,0 +1,192 @@
<?php
use Gettext\Translator;
/**
* Returns the translation of a string
*
* @param string $original
*
* @return string
*/
function __($original)
{
$text = Translator::$current->gettext($original);
if (func_num_args() === 1) {
return $text;
}
$args = array_slice(func_get_args(), 1);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the singular/plural translation of a string
*
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
function n__($original, $plural, $value)
{
$text = Translator::$current->ngettext($original, $plural, $value);
if (func_num_args() === 3) {
return $text;
}
$args = array_slice(func_get_args(), 3);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the translation of a string in a specific context
*
* @param string $context
* @param string $original
*
* @return string
*/
function p__($context, $original)
{
$text = Translator::$current->pgettext($context, $original);
if (func_num_args() === 2) {
return $text;
}
$args = array_slice(func_get_args(), 2);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the translation of a string in a specific domain
*
* @param string $domain
* @param string $original
*
* @return string
*/
function d__($domain, $original)
{
$text = Translator::dgettext($domain, $original);
if (func_num_args() === 2) {
return $text;
}
$args = array_slice(func_get_args(), 2);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the translation of a string in a specific domain and context
*
* @param string $domain
* @param string $context
* @param string $original
*
* @return string
*/
function dp__($domain, $context, $original)
{
$text = Translator::dpgettext($domain, $context, $original);
if (func_num_args() === 3) {
return $text;
}
$args = array_slice(func_get_args(), 3);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Returns the singular/plural translation of a string in a specific domain and context
*
* @param string $domain
* @param string $context
* @param string $original
* @param string $plural
* @param string $value
*
* @return string
*/
function dnp__($domain, $context, $original, $plural, $value)
{
$text = Translator::dnpgettext($domain, $context, $original, $plural, $value);
if (func_num_args() === 5) {
return $text;
}
$args = array_slice(func_get_args(), 5);
return vsprintf($text, is_array($args[0]) ? $args[0] : $args);
}
/**
* Prints function result
*
* @see __
*/
function __e()
{
echo call_user_func_array('__', func_get_args());
}
/**
* Prints function result
*
* @see n__
*/
function n__e()
{
echo call_user_func_array('n__', func_get_args());
}
/**
* Prints function result
*
* @see p__
*/
function p__e()
{
echo call_user_func_array('p__', func_get_args());
}
/**
* Prints function result
*
* @see d__
*/
function d__e()
{
echo call_user_func_array('d__', func_get_args());
}
/**
* Prints function result
*
* @see dp__
*/
function dp__e()
{
echo call_user_func_array('dp__', func_get_args());
}
/**
* Prints function result
*
* @see dnp__
*/
function dnp__e()
{
echo call_user_func_array('dnp__', func_get_args());
}