Add src files for CoreLibs composer all package
This commit is contained in:
58
src/Language/Core/CachedFileReader.php
Normal file
58
src/Language/Core/CachedFileReader.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
Copyright (c) 2003, 2005, 2006, 2009 Danilo Segan <danilo@kvota.net>.
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Language\Core;
|
||||
|
||||
// Preloads entire file in memory first, then creates a StringReader
|
||||
// over it (it assumes knowledge of StringReader internals)
|
||||
class CachedFileReader extends \CoreLibs\Language\Core\StringReader
|
||||
{
|
||||
/** @var int */
|
||||
public $error = 0;
|
||||
/** @var string */
|
||||
public $fd_str = '';
|
||||
|
||||
/**
|
||||
* Undocumented function
|
||||
*
|
||||
* @param string $filename
|
||||
*/
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
parent::__construct();
|
||||
if (file_exists($filename)) {
|
||||
$fd = fopen($filename, 'rb');
|
||||
if (!is_resource($fd)) {
|
||||
$this->error = 3; // Cannot read file, probably permissions
|
||||
} else {
|
||||
$this->fd_str = fread($fd, filesize($filename) ?: 0) ?: '';
|
||||
fclose($fd);
|
||||
}
|
||||
} else {
|
||||
$this->error = 2; // File doesn't exist
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
135
src/Language/Core/FileReader.php
Normal file
135
src/Language/Core/FileReader.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
Copyright (c) 2003, 2005, 2006, 2009 Danilo Segan <danilo@kvota.net>.
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Language\Core;
|
||||
|
||||
class FileReader
|
||||
{
|
||||
/** @var int */
|
||||
public $fr_pos;
|
||||
/** @var resource|bool */
|
||||
public $fr_fd;
|
||||
/** @var int */
|
||||
public $fr_length;
|
||||
/** @var int */
|
||||
public $error = 0;
|
||||
|
||||
/**
|
||||
* file read constructor
|
||||
*
|
||||
* @param string $filename file name to load
|
||||
*/
|
||||
public function __construct(string $filename)
|
||||
{
|
||||
if (file_exists($filename)) {
|
||||
$this->fr_length = filesize($filename) ?: 0;
|
||||
$this->fr_pos = 0;
|
||||
$this->fr_fd = fopen($filename, 'rb');
|
||||
if (!is_resource($this->fr_fd)) {
|
||||
$this->error = 3; // Cannot read file, probably permissions
|
||||
}
|
||||
} else {
|
||||
$this->error = 2; // File doesn't exist
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* read byte data length
|
||||
*
|
||||
* @param int $bytes how many bytes to read
|
||||
* @return string read data as string
|
||||
*/
|
||||
public function read(int $bytes): string
|
||||
{
|
||||
if (!$bytes || !is_resource($this->fr_fd)) {
|
||||
return '';
|
||||
}
|
||||
fseek($this->fr_fd, $this->fr_pos);
|
||||
|
||||
// PHP 5.1.1 does not read more than 8192 bytes in one fread()
|
||||
// the discussions at PHP Bugs suggest it's the intended behaviour
|
||||
$data = '';
|
||||
while ($bytes > 0) {
|
||||
$chunk = fread($this->fr_fd, $bytes);
|
||||
if ($chunk === false) {
|
||||
break;
|
||||
}
|
||||
$data .= $chunk;
|
||||
$bytes -= strlen($chunk);
|
||||
}
|
||||
$this->fr_pos = ftell($this->fr_fd) ?: 0;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* seek to a position in the file
|
||||
*
|
||||
* @param int $pos position where to go to
|
||||
* @return int file position after seek done
|
||||
*/
|
||||
public function seekto(int $pos): int
|
||||
{
|
||||
if (!is_resource($this->fr_fd)) {
|
||||
return 0;
|
||||
}
|
||||
fseek($this->fr_fd, $pos);
|
||||
$this->fr_pos = ftell($this->fr_fd) ?: 0;
|
||||
return $this->fr_pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current position in file
|
||||
*
|
||||
* @return int current position in bytes
|
||||
*/
|
||||
public function currentpos(): int
|
||||
{
|
||||
return $this->fr_pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* file length/size
|
||||
*
|
||||
* @return int file size in bytes
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return $this->fr_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* close open file handler
|
||||
*
|
||||
* @return void has no return
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if (is_resource($this->fr_fd)) {
|
||||
fclose($this->fr_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
546
src/Language/Core/GetTextReader.php
Normal file
546
src/Language/Core/GetTextReader.php
Normal file
@@ -0,0 +1,546 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
Copyright (c) 2003, 2009 Danilo Segan <danilo@kvota.net>.
|
||||
Copyright (c) 2005 Nico Kaiser <nico@siriux.net>
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Language\Core;
|
||||
|
||||
/**
|
||||
* Provides a simple gettext replacement that works independently from
|
||||
* the system's gettext abilities.
|
||||
* It can read MO files and use them for translating strings.
|
||||
* The files are passed to gettext_reader as a Stream (see streams.php)
|
||||
*
|
||||
* This version has the ability to cache all strings and translations to
|
||||
* speed up the string lookup.
|
||||
* While the cache is enabled by default, it can be switched off with the
|
||||
* second parameter in the constructor (e.g. whenusing very large MO files
|
||||
* that you don't want to keep in memory)
|
||||
*/
|
||||
class GetTextReader
|
||||
{
|
||||
// public:
|
||||
/** @var int */
|
||||
public $error = 0; // public variable that holds error code (0 if no error)
|
||||
|
||||
// private:
|
||||
/** @var int */
|
||||
private $BYTEORDER = 0; // 0: low endian, 1: big endian
|
||||
/** @var FileReader */
|
||||
private $STREAM;
|
||||
/** @var bool */
|
||||
private $short_circuit = false;
|
||||
/** @var bool */
|
||||
private $enable_cache = false;
|
||||
/** @var int */
|
||||
private $originals = 0; // offset of original table
|
||||
/** @var int */
|
||||
private $translations = 0; // offset of translation table
|
||||
/** @var string */
|
||||
private $pluralheader = ''; // cache header field for plural forms
|
||||
/** @var int */
|
||||
private $total = 0; // total string count
|
||||
/** @var array<mixed>|null */
|
||||
private $table_originals = null; // table for original strings (offsets)
|
||||
/** @var array<mixed>|null */
|
||||
private $table_translations = null; // table for translated strings (offsets)
|
||||
/** @var array<mixed> */
|
||||
private $cache_translations = []; // original -> translation mapping
|
||||
|
||||
/* Methods */
|
||||
|
||||
/**
|
||||
* Reads a 32bit Integer from the Stream
|
||||
*
|
||||
* @access private
|
||||
* @return int Integer from the Stream
|
||||
*/
|
||||
private function readint(): int
|
||||
{
|
||||
if ($this->BYTEORDER == 0) {
|
||||
// low endian
|
||||
$input = unpack('V', $this->STREAM->read(4)) ?: [];
|
||||
} else {
|
||||
// big endian
|
||||
$input = unpack('N', $this->STREAM->read(4)) ?: [];
|
||||
}
|
||||
return array_shift($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* read bytes
|
||||
*
|
||||
* @param int $bytes byte length to read
|
||||
* @return string return data, possible string
|
||||
*/
|
||||
public function read(int $bytes): string
|
||||
{
|
||||
return $this->STREAM->read($bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an array of Integers from the Stream
|
||||
*
|
||||
* @param int $count How many elements should be read
|
||||
* @return array<mixed> Array of Integers
|
||||
*/
|
||||
public function readintarray(int $count): array
|
||||
{
|
||||
if ($this->BYTEORDER == 0) {
|
||||
// low endian
|
||||
return unpack('V' . $count, $this->STREAM->read(4 * $count)) ?: [];
|
||||
} else {
|
||||
// big endian
|
||||
return unpack('N' . $count, $this->STREAM->read(4 * $count)) ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param FileReader|bool $Reader the StreamReader object
|
||||
* @param bool $enable_cache Enable or disable caching
|
||||
* of strings (default on)
|
||||
*/
|
||||
public function __construct($Reader, bool $enable_cache = true)
|
||||
{
|
||||
// If there isn't a StreamReader, turn on short circuit mode.
|
||||
if ((!is_object($Reader) && !$Reader) || (is_object($Reader) && $Reader->error)) {
|
||||
$this->short_circuit = true;
|
||||
return;
|
||||
}
|
||||
// bail out for sure if this is not an objet here
|
||||
if (!is_object($Reader)) {
|
||||
$this->short_circuit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Caching can be turned off
|
||||
$this->enable_cache = $enable_cache;
|
||||
|
||||
$MAGIC1 = "\x95\x04\x12\xde";
|
||||
$MAGIC2 = "\xde\x12\x04\x95";
|
||||
|
||||
$this->STREAM = $Reader;
|
||||
$magic = $this->read(4);
|
||||
if ($magic == $MAGIC1) {
|
||||
$this->BYTEORDER = 1;
|
||||
} elseif ($magic == $MAGIC2) {
|
||||
$this->BYTEORDER = 0;
|
||||
} else {
|
||||
$this->error = 1; // not MO file
|
||||
}
|
||||
|
||||
// FIXME: Do we care about revision? We should.
|
||||
$revision = $this->readint();
|
||||
|
||||
$this->total = $this->readint();
|
||||
$this->originals = $this->readint();
|
||||
$this->translations = $this->readint();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current short circuit, equals to no translator running
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getShortCircuit(): bool
|
||||
{
|
||||
return $this->short_circuit;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current cache enabled status
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getEnableCache(): bool
|
||||
{
|
||||
return $this->enable_cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the translation tables from the MO file into the cache
|
||||
* If caching is enabled, also loads all strings into a cache
|
||||
* to speed up translation lookups
|
||||
*
|
||||
* @access private
|
||||
* @return void
|
||||
*/
|
||||
private function loadTables(): void
|
||||
{
|
||||
if (
|
||||
is_array($this->cache_translations) &&
|
||||
is_array($this->table_originals) &&
|
||||
is_array($this->table_translations)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* get original and translations tables */
|
||||
if (!is_array($this->table_originals)) {
|
||||
$this->STREAM->seekto($this->originals);
|
||||
$this->table_originals = $this->readintarray($this->total * 2);
|
||||
}
|
||||
if (!is_array($this->table_translations)) {
|
||||
$this->STREAM->seekto($this->translations);
|
||||
$this->table_translations = $this->readintarray($this->total * 2);
|
||||
}
|
||||
|
||||
if ($this->enable_cache) {
|
||||
$this->cache_translations = [];
|
||||
/* read all strings in the cache */
|
||||
for ($i = 0; $i < $this->total; $i++) {
|
||||
$this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
|
||||
$original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
|
||||
$this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
|
||||
$translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
|
||||
$this->cache_translations[$original] = $translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string from the "originals" table
|
||||
*
|
||||
* @access private
|
||||
* @param int $num Offset number of original string
|
||||
* @return string Requested string if found, otherwise ''
|
||||
*/
|
||||
private function getOriginalString(int $num): string
|
||||
{
|
||||
$length = $this->table_originals[$num * 2 + 1] ?? 0;
|
||||
$offset = $this->table_originals[$num * 2 + 2] ?? 0;
|
||||
if (!$length) {
|
||||
return '';
|
||||
}
|
||||
$this->STREAM->seekto($offset);
|
||||
$data = $this->STREAM->read($length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string from the "translations" table
|
||||
*
|
||||
* @access private
|
||||
* @param int $num Offset number of original string
|
||||
* @return string Requested string if found, otherwise ''
|
||||
*/
|
||||
private function getTranslationString(int $num): string
|
||||
{
|
||||
$length = $this->table_translations[$num * 2 + 1] ?? 0;
|
||||
$offset = $this->table_translations[$num * 2 + 2] ?? 0;
|
||||
if (!$length) {
|
||||
return '';
|
||||
}
|
||||
$this->STREAM->seekto($offset);
|
||||
$data = $this->STREAM->read($length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search for string
|
||||
*
|
||||
* @access private
|
||||
* @param string $string string to find
|
||||
* @param int $start (internally used in recursive function)
|
||||
* @param int $end (internally used in recursive function)
|
||||
* @return int (offset in originals table)
|
||||
*/
|
||||
private function findString(string $string, int $start = -1, int $end = -1): int
|
||||
{
|
||||
if (($start == -1) or ($end == -1)) {
|
||||
// findString is called with only one parameter, set start end end
|
||||
$start = 0;
|
||||
$end = $this->total;
|
||||
}
|
||||
if (abs($start - $end) <= 1) {
|
||||
// We're done, now we either found the string, or it doesn't exist
|
||||
$txt = $this->getOriginalString($start);
|
||||
if ($string == $txt) {
|
||||
return $start;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} elseif ($start > $end) {
|
||||
// start > end -> turn around and start over
|
||||
return $this->findString($string, $end, $start);
|
||||
} else {
|
||||
// Divide table in two parts
|
||||
$half = (int)(($start + $end) / 2);
|
||||
$cmp = strcmp($string, $this->getOriginalString($half));
|
||||
if ($cmp == 0) {
|
||||
// string is exactly in the middle => return it
|
||||
return $half;
|
||||
} elseif ($cmp < 0) {
|
||||
// The string is in the upper half
|
||||
return $this->findString($string, $start, $half);
|
||||
} else {
|
||||
// Translateshe string is in the lower half
|
||||
return $this->findString($string, $half, $end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string
|
||||
*
|
||||
* @access public
|
||||
* @param string $string to be translated
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
public function translate(string $string): string
|
||||
{
|
||||
if ($this->short_circuit) {
|
||||
return $string;
|
||||
}
|
||||
$this->loadTables();
|
||||
|
||||
if ($this->enable_cache) {
|
||||
// Caching enabled, get translated string from cache
|
||||
if (
|
||||
is_array($this->cache_translations) &&
|
||||
array_key_exists($string, $this->cache_translations)
|
||||
) {
|
||||
return $this->cache_translations[$string];
|
||||
} else {
|
||||
return $string;
|
||||
}
|
||||
} else {
|
||||
// Caching not enabled, try to find string
|
||||
$num = $this->findString($string);
|
||||
if ($num == -1) {
|
||||
return $string;
|
||||
} else {
|
||||
return $this->getTranslationString($num);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize plural form expression for use in PHP eval call.
|
||||
*
|
||||
* @access private
|
||||
* @param string $expr an expression to match
|
||||
* @return string sanitized plural form expression
|
||||
*/
|
||||
private function sanitizePluralExpression(string $expr): string
|
||||
{
|
||||
// Get rid of disallowed characters.
|
||||
$expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
|
||||
|
||||
// Add parenthesis for tertiary '?' operator.
|
||||
$expr .= ';';
|
||||
$res = '';
|
||||
$p = 0;
|
||||
$expr_len = strlen($expr);
|
||||
for ($i = 0; $i < $expr_len; $i++) {
|
||||
$ch = $expr[$i];
|
||||
switch ($ch) {
|
||||
case '?':
|
||||
$res .= ' ? (';
|
||||
$p++;
|
||||
break;
|
||||
case ':':
|
||||
$res .= ') : (';
|
||||
break;
|
||||
case ';':
|
||||
$res .= str_repeat(')', $p) . ';';
|
||||
$p = 0;
|
||||
break;
|
||||
default:
|
||||
$res .= $ch;
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse full PO header and extract only plural forms line.
|
||||
*
|
||||
* @access private
|
||||
* @param string $header header search in plurals
|
||||
* @return string verbatim plural form header field
|
||||
*/
|
||||
private function extractPluralFormsHeaderFromPoHeader(string $header): string
|
||||
{
|
||||
if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs)) {
|
||||
$expr = $regs[2];
|
||||
} else {
|
||||
$expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
|
||||
}
|
||||
return $expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get possible plural forms from MO header
|
||||
*
|
||||
* @access private
|
||||
* @return string plural form header
|
||||
*/
|
||||
private function getPluralForms(): string
|
||||
{
|
||||
// lets assume message number 0 is header
|
||||
// this is true, right?
|
||||
$this->loadTables();
|
||||
|
||||
// cache header field for plural forms
|
||||
if (empty($this->pluralheader) || !is_string($this->pluralheader)) {
|
||||
if ($this->enable_cache) {
|
||||
$header = $this->cache_translations[''];
|
||||
} else {
|
||||
$header = $this->getTranslationString(0);
|
||||
}
|
||||
$expr = $this->extractPluralFormsHeaderFromPoHeader($header);
|
||||
$this->pluralheader = $this->sanitizePluralExpression($expr);
|
||||
}
|
||||
return $this->pluralheader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects which plural form to take
|
||||
*
|
||||
* @access private
|
||||
* @param int $n count
|
||||
* @return int array index of the right plural form
|
||||
*/
|
||||
private function selectString(int $n): int
|
||||
{
|
||||
$string = $this->getPluralForms();
|
||||
$string = str_replace('nplurals', "\$total", $string);
|
||||
$string = str_replace("n", (string)$n, $string);
|
||||
$string = str_replace('plural', "\$plural", $string);
|
||||
|
||||
$total = 0;
|
||||
$plural = 0;
|
||||
|
||||
// FIXME use Symfony\Component\ExpressionLanguage\ExpressionLanguage or similar
|
||||
eval("$string");
|
||||
/** @phpstan-ignore-next-line 0 >= 0 is always true*/
|
||||
if ($plural >= $total) {
|
||||
$plural = $total - 1;
|
||||
}
|
||||
return (int)$plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* wrapper for translate() method
|
||||
*
|
||||
* @access public
|
||||
* @param string $string
|
||||
* @return string
|
||||
*/
|
||||
public function gettext(string $string): string
|
||||
{
|
||||
return $this->translate($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext
|
||||
*
|
||||
* @access public
|
||||
* @param string $single
|
||||
* @param string $plural
|
||||
* @param int $number
|
||||
* @return string plural form
|
||||
*/
|
||||
public function ngettext(string $single, string $plural, int $number): string
|
||||
{
|
||||
if ($this->short_circuit) {
|
||||
if ($number != 1) {
|
||||
return $plural;
|
||||
} else {
|
||||
return $single;
|
||||
}
|
||||
}
|
||||
|
||||
// find out the appropriate form
|
||||
$select = $this->selectString($number);
|
||||
|
||||
// this should contains all strings separated by NULLs
|
||||
$key = $single . chr(0) . $plural;
|
||||
|
||||
if ($this->enable_cache) {
|
||||
if (is_array($this->cache_translations) && !array_key_exists($key, $this->cache_translations)) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->cache_translations[$key];
|
||||
$list = explode(chr(0), $result);
|
||||
return $list[$select];
|
||||
}
|
||||
} else {
|
||||
$num = $this->findString($key);
|
||||
if ($num == -1) {
|
||||
return ($number != 1) ? $plural : $single;
|
||||
} else {
|
||||
$result = $this->getTranslationString($num);
|
||||
$list = explode(chr(0), $result);
|
||||
return $list[$select];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* p get text
|
||||
*
|
||||
* @param string $context [description]
|
||||
* @param string $msgid [description]
|
||||
* @return string [description]
|
||||
*/
|
||||
public function pgettext(string $context, string $msgid): string
|
||||
{
|
||||
$key = $context . chr(4) . $msgid;
|
||||
$ret = $this->translate($key);
|
||||
if (strpos($ret, "\004") !== false) {
|
||||
return $msgid;
|
||||
} else {
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* np get text
|
||||
*
|
||||
* @param string $context [description]
|
||||
* @param string $singular [description]
|
||||
* @param string $plural [description]
|
||||
* @param int $number [description]
|
||||
* @return string [description]
|
||||
*/
|
||||
public function npgettext(
|
||||
string $context,
|
||||
string $singular,
|
||||
string $plural,
|
||||
int $number
|
||||
): string {
|
||||
$key = $context . chr(4) . $singular;
|
||||
$ret = $this->ngettext($key, $plural, $number);
|
||||
if (strpos($ret, "\004") !== false) {
|
||||
return $singular;
|
||||
} else {
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
82
src/Language/Core/StreamReader.php
Normal file
82
src/Language/Core/StreamReader.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
Copyright (c) 2003, 2005, 2006, 2009 Danilo Segan <danilo@kvota.net>.
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Language\Core;
|
||||
|
||||
// Simple class to wrap file streams, string streams, etc.
|
||||
// seek is essential, and it should be byte stream
|
||||
class StreamReader
|
||||
{
|
||||
/**
|
||||
* constructor, empty
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
// empty
|
||||
}
|
||||
|
||||
/**
|
||||
* should return a string [FIXME: perhaps return array of bytes?]
|
||||
*
|
||||
* @param int $bytes bytes to read
|
||||
* @return bool dummy false
|
||||
*/
|
||||
public function read(int $bytes): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* should return new position
|
||||
*
|
||||
* @param int $position seek to position
|
||||
* @return bool dummy false
|
||||
*/
|
||||
public function seekto(int $position): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns current position
|
||||
*
|
||||
* @return bool dummy false
|
||||
*/
|
||||
public function currentpos(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns length of entire stream (limit for seekto()s)
|
||||
*
|
||||
* @return bool dummy false
|
||||
*/
|
||||
public function length(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
98
src/Language/Core/StringReader.php
Normal file
98
src/Language/Core/StringReader.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
Copyright (c) 2003, 2005, 2006, 2009 Danilo Segan <danilo@kvota.net>.
|
||||
|
||||
This file is part of PHP-gettext.
|
||||
|
||||
PHP-gettext is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
PHP-gettext is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with PHP-gettext; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Language\Core;
|
||||
|
||||
class StringReader
|
||||
{
|
||||
/** @var int */
|
||||
public $sr_pos;
|
||||
/** @var string */
|
||||
public $sr_str;
|
||||
|
||||
/**
|
||||
* constructor for string reader
|
||||
*
|
||||
* @param string $str basic string
|
||||
*/
|
||||
public function __construct(string $str = '')
|
||||
{
|
||||
$this->sr_str = $str;
|
||||
$this->sr_pos = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* read bytes in string
|
||||
*
|
||||
* @param int $bytes bytes to read in string
|
||||
* @return string data read in length of bytes as string
|
||||
*/
|
||||
public function read(int $bytes): string
|
||||
{
|
||||
$data = substr($this->sr_str, $this->sr_pos, $bytes);
|
||||
$this->sr_pos += $bytes;
|
||||
if (strlen($this->sr_str) < $this->sr_pos) {
|
||||
$this->sr_pos = strlen($this->sr_str);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* go to position in string
|
||||
*
|
||||
* @param int $pos position in string
|
||||
* @return int new position in string after seek
|
||||
*/
|
||||
public function seekto(int $pos): int
|
||||
{
|
||||
$this->sr_pos = $pos;
|
||||
if (strlen($this->sr_str) < $this->sr_pos) {
|
||||
$this->sr_pos = strlen($this->sr_str);
|
||||
}
|
||||
return $this->sr_pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current position in string
|
||||
*
|
||||
* @return int position in string
|
||||
*/
|
||||
public function currentpos(): int
|
||||
{
|
||||
return $this->sr_pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* get length of string
|
||||
*
|
||||
* @return int return length of assigned string
|
||||
*/
|
||||
public function length(): int
|
||||
{
|
||||
return strlen($this->sr_str);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
127
src/Language/Encoding.php
Normal file
127
src/Language/Encoding.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* deprecated function calls
|
||||
* Language\Encoding::__mbMimeEncode -> Convert\MimeEncode::__mbMimeEncode
|
||||
* Langauge\Encoding::checkConvertEncoding -> Check\Encoding::checkConvertEncoding
|
||||
* Langauge\Encoding::setErrorChar -> Check\Encoding::setErrorChar
|
||||
* Langauge\Encoding::getErrorChar -> Check\Encoding::getErrorChar
|
||||
* Langauge\Encoding::convertEncoding -> Convert\Encoding::convertEncoding
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Language;
|
||||
|
||||
class Encoding
|
||||
{
|
||||
/**
|
||||
* wrapper function for mb mime convert
|
||||
* for correct conversion with long strings
|
||||
*
|
||||
* @param string $string string to encode
|
||||
* @param string $encoding target encoding
|
||||
* @param string $line_break default line break is \r\n
|
||||
* @return string encoded string
|
||||
* @deprecated Use \CoreLibs\Convert\MimeEncode::__mbMimeEncode();
|
||||
*/
|
||||
public static function __mbMimeEncode(
|
||||
string $string,
|
||||
string $encoding,
|
||||
string $line_break = "\r\n"
|
||||
): string {
|
||||
return \CoreLibs\Convert\MimeEncode::__mbMimeEncode($string, $encoding, $line_break);
|
||||
}
|
||||
|
||||
/**
|
||||
* set error char
|
||||
*
|
||||
* @param string|int|null $string The character to use to represent
|
||||
* error chars
|
||||
* "long" for long, "none" for none
|
||||
* or a valid code point in int
|
||||
* like 0x2234 (8756, ∴)
|
||||
* default character is ? (63)
|
||||
* if null is set then "none"
|
||||
* @return void
|
||||
* @deprecated Use \CoreLibs\Check\Encoding::setErrorChar();
|
||||
*/
|
||||
public static function setErrorChar($string): void
|
||||
{
|
||||
\CoreLibs\Check\Encoding::setErrorChar($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current set error character
|
||||
*
|
||||
* @param bool $return_substitute_func if set to true return the set
|
||||
* character from the php function
|
||||
* directly
|
||||
* @return string|int Set error character
|
||||
* @deprecated Use \CoreLibs\Check\Encoding::getErrorChar();
|
||||
*/
|
||||
public static function getErrorChar(bool $return_substitute_func = false)
|
||||
{
|
||||
return \CoreLibs\Check\Encoding::getErrorChar($return_substitute_func);
|
||||
}
|
||||
|
||||
/**
|
||||
* test if a string can be safely convert between encodings.
|
||||
* mostly utf8 to shift jis
|
||||
* the default compare has a possibility of failure, especially with windows
|
||||
* it is recommended to the following in the script which uses this method:
|
||||
* mb_substitute_character(0x2234);
|
||||
* $class->mb_error_char = '∴';
|
||||
* if check to Shift JIS
|
||||
* if check to ISO-2022-JP
|
||||
* if check to ISO-2022-JP-MS
|
||||
* set three dots (∴) as wrong character for correct convert error detect
|
||||
* (this char is used, because it is one of the least used ones)
|
||||
*
|
||||
* @param string $string string to test
|
||||
* @param string $from_encoding encoding of string to test
|
||||
* @param string $to_encoding target encoding
|
||||
* @return bool|array<string> false if no error or
|
||||
* array with failed characters
|
||||
* @deprecated Use \CoreLibs\Check\Encoding::checkConvertEncoding();
|
||||
*/
|
||||
public static function checkConvertEncoding(
|
||||
string $string,
|
||||
string $from_encoding,
|
||||
string $to_encoding
|
||||
) {
|
||||
return \CoreLibs\Check\Encoding::checkConvertEncoding($string, $from_encoding, $to_encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* detects the source encoding of the string and if doesn't match
|
||||
* to the given target encoding it convert is
|
||||
* if source encoding is set and auto check is true (default) a second
|
||||
* check is done so that the source string encoding actually matches
|
||||
* will be skipped if source encoding detection is ascii
|
||||
*
|
||||
* @param string $string string to convert
|
||||
* @param string $to_encoding target encoding
|
||||
* @param string $source_encoding optional source encoding, will try to auto detect
|
||||
* @param bool $auto_check default true, if source encoding is set
|
||||
* check that the source is actually matching
|
||||
* to what we sav the source is
|
||||
* @return string encoding converted string
|
||||
* @deprecated Use \CoreLibs\Convert\Encoding::convertEncoding();
|
||||
*/
|
||||
public static function convertEncoding(
|
||||
string $string,
|
||||
string $to_encoding,
|
||||
string $source_encoding = '',
|
||||
bool $auto_check = true
|
||||
): string {
|
||||
return \CoreLibs\Convert\Encoding::convertEncoding(
|
||||
$string,
|
||||
$to_encoding,
|
||||
$source_encoding,
|
||||
$auto_check
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
117
src/Language/GetLocale.php
Normal file
117
src/Language/GetLocale.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Internal function for getting locale and encodig settings
|
||||
* used for new locale layout
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Language;
|
||||
|
||||
class GetLocale
|
||||
{
|
||||
/**
|
||||
* returns locale, lang, domain, encoding, path
|
||||
* from either parameter set or from sessions/config variables
|
||||
*
|
||||
* @param string|null $locale override auto detect
|
||||
* @param string|null $domain override domain
|
||||
* @param string|null $encoding override encoding
|
||||
* @param string|null $path override path
|
||||
* @return array<string,string> locale, domain, encoding, path
|
||||
*/
|
||||
public static function setLocale(
|
||||
?string $locale = null,
|
||||
?string $domain = null,
|
||||
?string $encoding = null,
|
||||
?string $path = null
|
||||
): array {
|
||||
// locale must match at least basic rules
|
||||
if (
|
||||
empty($locale) ||
|
||||
!preg_match("/^[-A-Za-z0-9_.@]+$/", $locale)
|
||||
) {
|
||||
if (!empty($_SESSION['DEFAULT_LOCALE'])) {
|
||||
// parse from session (logged in)
|
||||
$locale = $_SESSION['DEFAULT_LOCALE'];
|
||||
} else {
|
||||
// else parse from site locale
|
||||
$locale = defined('SITE_LOCALE') && !empty(SITE_LOCALE) ?
|
||||
SITE_LOCALE :
|
||||
// else parse from default, if not 'en'
|
||||
/** @phpstan-ignore-next-line DEFAULT_LOCALE could be empty */
|
||||
(defined('DEFAULT_LOCALE') && !empty(DEFAULT_LOCALE) ?
|
||||
DEFAULT_LOCALE : 'en');
|
||||
}
|
||||
}
|
||||
// if domain is set, must be alphanumeric, if not unset
|
||||
if (
|
||||
empty($domain) ||
|
||||
!preg_match("/^\w+$/", $domain)
|
||||
) {
|
||||
// if no domain is set, fall back to content path
|
||||
$domain = str_replace('/', '', CONTENT_PATH);
|
||||
}
|
||||
// check that override encoding matches locale encoding
|
||||
// if locale encoding is set
|
||||
preg_match('/(?:\\.(?P<charset>[-A-Za-z0-9_]+))/', $locale, $matches);
|
||||
$locale_encoding = $matches['charset'] ?? null;
|
||||
if (
|
||||
// empty encoding
|
||||
empty($encoding) ||
|
||||
// not valid encoding
|
||||
!preg_match("/^[-A-Za-z0-9_]+$/", $encoding) ||
|
||||
// locale encoding set and not matching to encoding
|
||||
(!empty($locale_encoding) && $encoding != $locale_encoding)
|
||||
) {
|
||||
if (!empty($locale_encoding)) {
|
||||
$encoding = strtoupper($locale_encoding);
|
||||
} elseif (!empty($_SESSION['DEFAULT_CHARSET'])) {
|
||||
// else set from session
|
||||
$encoding = $_SESSION['DEFAULT_CHARSET'];
|
||||
} else {
|
||||
// else set from site encoding
|
||||
$encoding = defined('SITE_ENCODING') && !empty(SITE_ENCODING) ?
|
||||
SITE_ENCODING :
|
||||
// or default encoding, if not 'UTF-8'
|
||||
/** @phpstan-ignore-next-line DEFAULT_LOCALE could be empty */
|
||||
(defined('DEFAULT_ENCODING') && !empty(DEFAULT_ENCODING) ?
|
||||
DEFAULT_ENCODING : 'UTF-8');
|
||||
}
|
||||
}
|
||||
// path checks if set, if not valid path unset to default BASE path
|
||||
if (
|
||||
empty($path) ||
|
||||
!is_dir($path)
|
||||
) {
|
||||
$path = BASE . INCLUDES . LOCALE;
|
||||
}
|
||||
// extract lang & country from locale string, else set to en
|
||||
if (
|
||||
preg_match(
|
||||
// lang
|
||||
'/^(?P<lang>[a-z]{2,3})'
|
||||
// country code
|
||||
. '(?:_(?P<country>[A-Z]{2}))?/',
|
||||
$locale,
|
||||
$matches
|
||||
)
|
||||
) {
|
||||
$lang = ($matches['lang'] ?? 'en')
|
||||
// add country only if set
|
||||
. (!empty($matches['country']) ? '_' . $matches['country'] : '');
|
||||
} else {
|
||||
$lang = 'en';
|
||||
}
|
||||
return [
|
||||
'locale' => $locale,
|
||||
'lang' => $lang,
|
||||
'domain' => $domain,
|
||||
'encoding' => $encoding,
|
||||
'path' => $path,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// __END__
|
||||
663
src/Language/L10n.php
Normal file
663
src/Language/L10n.php
Normal file
@@ -0,0 +1,663 @@
|
||||
<?php
|
||||
|
||||
/*********************************************************************
|
||||
* AUTHOR: Clemens Schwaighofer
|
||||
* CREATED: 2004/11/18
|
||||
* VERSION: 3.0.0
|
||||
* RELEASED LICENSE: GNU GPL 3
|
||||
* SHORT DESCRIPTION:
|
||||
* init class for gettext. Original was just a function &
|
||||
* var setting include for wordpress.
|
||||
* I changed that to a class to be more portable with my style of coding
|
||||
* VERSION 3.0 (2022/4) removes all old folder layout and uses standard gettext
|
||||
* PUBLIC METHODS
|
||||
* __ : returns string (translated or original if not found)
|
||||
* __n : plural string
|
||||
* __p : string with context
|
||||
* __np: string with context and plural
|
||||
*
|
||||
* HISTORY:
|
||||
* 2022/4/15 (cs) drop all old folder layout support, new folder base
|
||||
* in locale with standard gettext layout of
|
||||
* locale/LC_MESSAGES/domain.mo
|
||||
* 2005/10/17 (cs) made an on the fly switch method (reload of lang)
|
||||
*********************************************************************/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace CoreLibs\Language;
|
||||
|
||||
use CoreLibs\Language\Core\FileReader;
|
||||
use CoreLibs\Language\Core\GetTextReader;
|
||||
|
||||
class L10n
|
||||
{
|
||||
/** @var string the current locale */
|
||||
private $locale = '';
|
||||
/** @var string the SET locale as WHERE the domain file is */
|
||||
private $locale_set = '';
|
||||
/** @var string the default selected/active domain */
|
||||
private $domain = '';
|
||||
/** @var array<string,array<string,GetTextReader>> locale > domain = translator */
|
||||
private $domains = [];
|
||||
/** @var array<string,string> bound paths for domains */
|
||||
private $paths = ['' => './'];
|
||||
|
||||
// files
|
||||
/** @var string the full path to the mo file to loaded */
|
||||
private $mofile = '';
|
||||
/** @var string base path to search level */
|
||||
private $base_locale_path = '';
|
||||
/** @var string dynamic set path to where the mo file is actually */
|
||||
private $base_content_path = '';
|
||||
|
||||
// errors
|
||||
/** @var bool if load of mo file was unsuccessful */
|
||||
private $load_failure = false;
|
||||
|
||||
// object holders
|
||||
/** @var FileReader|bool reader class for file reading, false for short circuit */
|
||||
private $input = false;
|
||||
/** @var GetTextReader reader class for MO data */
|
||||
private $l10n;
|
||||
/**
|
||||
* @static
|
||||
* @var L10n self class
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
/**
|
||||
* class constructor call for language getstring
|
||||
* if locale is not empty will load translation
|
||||
* else getTranslator needs to be called
|
||||
*
|
||||
* @param string $locale language name, default empty string
|
||||
* will return self instance
|
||||
* @param string $domain override CONTENT_PATH . $encoding name for mo file
|
||||
* @param string $path path, if empty fallback on default internal path
|
||||
*/
|
||||
public function __construct(
|
||||
string $locale = '',
|
||||
string $domain = '',
|
||||
string $path = ''
|
||||
) {
|
||||
// auto load language only if at least locale and domain is set
|
||||
if (!empty($locale) && !empty($domain)) {
|
||||
// check hack if domain and path is switched
|
||||
// Note this can be removed in future versions
|
||||
if (strstr($domain, DIRECTORY_SEPARATOR) !== false) {
|
||||
$_domain = $path;
|
||||
$path = $domain;
|
||||
$domain = $_domain;
|
||||
}
|
||||
$this->getTranslator($locale, $domain, $path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the singleton L10n object.
|
||||
* For function wrapper use
|
||||
*
|
||||
* @return L10n object
|
||||
*/
|
||||
public static function getInstance(): L10n
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
if (empty(self::$instance)) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads global localization functions.
|
||||
* prefixed with double underscore
|
||||
* eg: gettext -> __gettext
|
||||
*/
|
||||
public static function loadFunctions(): void
|
||||
{
|
||||
require_once __DIR__ . '/l10n_functions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* loads the mo file base on path, locale and domain set
|
||||
*
|
||||
* @param string $locale language name (optional), fallback is en
|
||||
* @param string $domain override CONTENT_PATH . $encoding name for mo file
|
||||
* @param string $path path, if empty fallback on default internal path
|
||||
* @return GetTextReader the main gettext reader object
|
||||
*/
|
||||
public function getTranslator(
|
||||
string $locale = '',
|
||||
string $domain = '',
|
||||
string $path = ''
|
||||
): GetTextReader {
|
||||
// set local if not from parameter
|
||||
if (empty($locale)) {
|
||||
$locale = $this->locale;
|
||||
}
|
||||
// set domain if not given
|
||||
if (empty($domain)) {
|
||||
$domain = $this->domain;
|
||||
}
|
||||
// store old settings
|
||||
$old_mofile = $this->mofile;
|
||||
$old_lang = $this->locale;
|
||||
$old_lang_set = $this->locale_set;
|
||||
$old_domain = $this->domain;
|
||||
$old_base_locale_path = $this->base_locale_path;
|
||||
$old_base_content_path = $this->base_content_path;
|
||||
|
||||
// if path is a dir
|
||||
// 1) from a previous set domain
|
||||
// 2) from method option as is
|
||||
// 3) fallback if BASE/INCLUDES/LOCALE set
|
||||
// 4) current dir
|
||||
if (!empty($this->paths[$domain]) && is_dir($this->paths[$domain])) {
|
||||
$this->base_locale_path = $this->paths[$domain];
|
||||
} elseif (is_dir($path)) {
|
||||
$this->base_locale_path = $path;
|
||||
} elseif (
|
||||
defined('BASE') && defined('INCLUDES') && defined('LOCALE')
|
||||
) {
|
||||
// set fallback base path if constant set
|
||||
$this->base_locale_path = BASE . INCLUDES . LOCALE;
|
||||
} else {
|
||||
$this->base_locale_path = './';
|
||||
}
|
||||
// now we loop over lang compositions to get the base path
|
||||
// then we check
|
||||
$locales = $this->listLocales($locale);
|
||||
foreach ($locales as $_locale) {
|
||||
$this->base_content_path = $_locale . DIRECTORY_SEPARATOR
|
||||
. 'LC_MESSAGES' . DIRECTORY_SEPARATOR;
|
||||
$this->mofile = $this->base_locale_path
|
||||
. $this->base_content_path
|
||||
. $domain . '.mo';
|
||||
if (file_exists($this->mofile)) {
|
||||
$this->locale_set = $_locale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check if get a readable mofile
|
||||
if (is_readable($this->mofile)) {
|
||||
// locale and domain current wanted
|
||||
$this->locale = $locale;
|
||||
$this->domain = $domain;
|
||||
// set empty domains path with current locale
|
||||
if (empty($this->domains[$locale])) {
|
||||
$this->domains[$locale] = [];
|
||||
}
|
||||
// store current base path (without locale, etc)
|
||||
if (empty($this->paths[$domain])) {
|
||||
$this->paths[$domain] = $this->base_locale_path;
|
||||
}
|
||||
// file reader and mo reader
|
||||
$this->input = new FileReader($this->mofile);
|
||||
$this->l10n = new GetTextReader($this->input);
|
||||
// if short circuit is true, we failed to have a translator loaded
|
||||
$this->load_failure = $this->l10n->getShortCircuit();
|
||||
// below is not used at the moment, but can be to avoid reloading
|
||||
$this->domains[$this->locale][$domain] = $this->l10n;
|
||||
} elseif (!empty($old_mofile)) {
|
||||
// mo file not readable
|
||||
$this->load_failure = true;
|
||||
// else fall back to the old ones
|
||||
$this->mofile = $old_mofile;
|
||||
$this->locale = $old_lang;
|
||||
$this->locale_set = $old_lang_set;
|
||||
$this->domain = $old_domain;
|
||||
$this->base_locale_path = $old_base_locale_path;
|
||||
$this->base_content_path = $old_base_content_path;
|
||||
} else {
|
||||
// mo file not readable, no previous mo file set, set short circuit
|
||||
$this->load_failure = true;
|
||||
// dummy
|
||||
$this->l10n = new GetTextReader($this->input);
|
||||
}
|
||||
return $this->l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* return current set GetTextReader or return the one for given
|
||||
* domain name if set
|
||||
* This can be used to access all the public methods from the
|
||||
* GetTextReader
|
||||
*
|
||||
* @param string $domain optional domain name
|
||||
* @return GetTextReader
|
||||
*/
|
||||
public function getTranslatorClass(string $domain = ''): GetTextReader
|
||||
{
|
||||
if (!empty($domain) && !empty($this->domains[$this->locale][$domain])) {
|
||||
return $this->domains[$this->locale][$domain];
|
||||
}
|
||||
// if null return short circuit version
|
||||
if ($this->l10n === null) {
|
||||
return new GetTextReader($this->input);
|
||||
}
|
||||
return $this->l10n;
|
||||
}
|
||||
|
||||
/**
|
||||
* parse the locale string for further processing
|
||||
*
|
||||
* @param string $locale Locale to parse
|
||||
* @return array<string,string|null> array with lang, country, charset, modifier
|
||||
*/
|
||||
public static function parseLocale(string $locale = ''): array
|
||||
{
|
||||
preg_match(
|
||||
// language code
|
||||
'/^(?P<lang>[a-z]{2,3})'
|
||||
// country code
|
||||
. '(?:_(?P<country>[A-Z]{2}))?'
|
||||
// charset
|
||||
. '(?:\\.(?P<charset>[-A-Za-z0-9_]+))?'
|
||||
// @ modifier
|
||||
. '(?:@(?P<modifier>[-A-Za-z0-9_]+))?$/',
|
||||
$locale,
|
||||
$matches
|
||||
);
|
||||
return [
|
||||
'lang' => $matches['lang'] ?? null,
|
||||
'country' => $matches['country'] ?? null,
|
||||
'charset' => $matches['charset'] ?? null,
|
||||
'modifier' => $matches['modifier'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* original:
|
||||
* vendor/phpmyadmin/motranslator/src/Loader.php
|
||||
*
|
||||
* Returns array with all possible locale combinations based on the
|
||||
* given locale name
|
||||
*
|
||||
* I.e. for sr_CS.UTF-8@latin, look through all of
|
||||
* sr_CS.UTF-8@latin, sr_CS@latin, sr@latin, sr_CS.UTF-8, sr_CS, sr.
|
||||
*
|
||||
* @param string $locale Locale string
|
||||
* @return array<string> List of locale path parts that can be possible
|
||||
*/
|
||||
public static function listLocales(string $locale): array
|
||||
{
|
||||
$locale_list = [];
|
||||
|
||||
if (empty($locale)) {
|
||||
return $locale_list;
|
||||
}
|
||||
// is matching regex
|
||||
$locale_detail = L10n::parseLocale($locale);
|
||||
// all null = nothing mached, return locale as is
|
||||
if ($locale_detail === array_filter($locale_detail, 'is_null')) {
|
||||
return [$locale];
|
||||
}
|
||||
// write to innteral vars
|
||||
$lang = $locale_detail['lang'];
|
||||
$country = $locale_detail['country'];
|
||||
$charset = $locale_detail['charset'];
|
||||
$modifier = $locale_detail['modifier'];
|
||||
// we need to add all possible cominations from not null set
|
||||
// entries to the list, from longest to shortest
|
||||
// %s_%s.%s@%s (lang _ country . encoding @ suffix)
|
||||
// %s_%s@%s (lang _ country @ suffix)
|
||||
// %s@%s (lang @ suffix)
|
||||
// %s_%s.%s (lang _ country . encoding)
|
||||
// %s_%s (lang _ country)
|
||||
// %s (lang)
|
||||
|
||||
// if lang is set
|
||||
if ($lang) {
|
||||
// modifier group
|
||||
if ($modifier) {
|
||||
if ($country) {
|
||||
if ($charset) {
|
||||
array_push(
|
||||
$locale_list,
|
||||
sprintf('%s_%s.%s@%s', $lang, $country, $charset, $modifier)
|
||||
);
|
||||
}
|
||||
|
||||
array_push(
|
||||
$locale_list,
|
||||
sprintf('%s_%s@%s', $lang, $country, $modifier)
|
||||
);
|
||||
} elseif ($charset) {
|
||||
array_push(
|
||||
$locale_list,
|
||||
sprintf('%s.%s@%s', $lang, $charset, $modifier)
|
||||
);
|
||||
}
|
||||
|
||||
array_push(
|
||||
$locale_list,
|
||||
sprintf('%s@%s', $lang, $modifier)
|
||||
);
|
||||
}
|
||||
// country group
|
||||
if ($country) {
|
||||
if ($charset) {
|
||||
array_push(
|
||||
$locale_list,
|
||||
sprintf('%s_%s.%s', $lang, $country, $charset)
|
||||
);
|
||||
}
|
||||
|
||||
array_push(
|
||||
$locale_list,
|
||||
sprintf('%s_%s', $lang, $country)
|
||||
);
|
||||
} elseif ($charset) {
|
||||
array_push(
|
||||
$locale_list,
|
||||
sprintf('%s.%s', $lang, $charset)
|
||||
);
|
||||
}
|
||||
// lang only
|
||||
array_push($locale_list, $lang);
|
||||
}
|
||||
|
||||
// If the locale name doesn't match POSIX style, just include it as-is.
|
||||
if (!in_array($locale, $locale_list)) {
|
||||
array_push($locale_list, $locale);
|
||||
}
|
||||
|
||||
return $locale_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to detect the locale set in the following order:
|
||||
* - globals: LOCALE
|
||||
* - globals: LANG
|
||||
* - env: LC_ALL
|
||||
* - env: LC_MESSAGES
|
||||
* - env: LANG
|
||||
* if nothing set, returns 'en' as default
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function detectLocale(): string
|
||||
{
|
||||
// globals
|
||||
foreach (['LOCALE', 'LANG'] as $global) {
|
||||
if (!empty($GLOBALS[$global])) {
|
||||
return $GLOBALS[$global];
|
||||
}
|
||||
}
|
||||
// enviroment
|
||||
foreach (['LC_ALL', 'LC_MESSAGES', 'LANG'] as $env) {
|
||||
$locale = getenv($env);
|
||||
if ($locale !== false && !empty($locale)) {
|
||||
return $locale;
|
||||
}
|
||||
}
|
||||
return 'en';
|
||||
}
|
||||
|
||||
/************
|
||||
* INTERNAL VAR SET/GET
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets the path for a domain.
|
||||
* must be set before running getTranslator (former l10nReloadMOfile)
|
||||
*
|
||||
* @param string $domain Domain name
|
||||
* @param string $path Path where to find locales
|
||||
*/
|
||||
public function setTextDomain(string $domain, string $path): void
|
||||
{
|
||||
$this->paths[$domain] = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* return set path for given domain
|
||||
* if not found return false
|
||||
*
|
||||
* @param string $domain
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getTextDomain(string $domain)
|
||||
{
|
||||
return $this->paths[$domain] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the default domain.
|
||||
*
|
||||
* @param string $domain Domain name
|
||||
*/
|
||||
public function setDomain(string $domain): void
|
||||
{
|
||||
$this->domain = $domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* return current set domain name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDomain(): string
|
||||
{
|
||||
return $this->domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets a requested locale.
|
||||
*
|
||||
* @param string $locale Locale name
|
||||
* @return string Set or current locale
|
||||
*/
|
||||
public function setLocale(string $locale): string
|
||||
{
|
||||
if (!empty($locale)) {
|
||||
$this->locale = $locale;
|
||||
}
|
||||
return $this->locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current set locale (want locale)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocale(): string
|
||||
{
|
||||
return $this->locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* current set locale where mo file is located
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocaleSet(): string
|
||||
{
|
||||
return $this->locale_set;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current set language
|
||||
*
|
||||
* @return string current set language string
|
||||
* @deprecated Use getLocale()
|
||||
*/
|
||||
public function __getLang(): string
|
||||
{
|
||||
return $this->getLocale();
|
||||
}
|
||||
|
||||
/**
|
||||
* get current set mo file
|
||||
*
|
||||
* @return string current set mo language file
|
||||
*/
|
||||
public function getMoFile(): string
|
||||
{
|
||||
return $this->mofile;
|
||||
}
|
||||
|
||||
/**
|
||||
* get current set mo file
|
||||
*
|
||||
* @return string current set mo language file
|
||||
* @deprecated Use getMoFile()
|
||||
*/
|
||||
public function __getMoFile(): string
|
||||
{
|
||||
return $this->getMoFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current base path in which we search
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseLocalePath(): string
|
||||
{
|
||||
return $this->base_locale_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* the path below the base path to where the mo file is located
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getBaseContentPath(): string
|
||||
{
|
||||
return $this->base_content_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the current load error status
|
||||
* if true then the mo file failed to load
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getLoadError(): bool
|
||||
{
|
||||
return $this->load_failure;
|
||||
}
|
||||
|
||||
/************
|
||||
* TRANSLATION METHODS
|
||||
*/
|
||||
|
||||
/**
|
||||
* translates a string and returns translated text
|
||||
*
|
||||
* @param string $text text to translate
|
||||
* @return string translated text
|
||||
*/
|
||||
public function __(string $text): string
|
||||
{
|
||||
// fallback passthrough
|
||||
if ($this->l10n === null) {
|
||||
return $text;
|
||||
}
|
||||
return $this->l10n->translate($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* prints translated string out to the screen
|
||||
* @param string $text text to translate
|
||||
* @return void has no return
|
||||
* @deprecated use echo __() instead
|
||||
*/
|
||||
public function __e(string $text): void
|
||||
{
|
||||
// fallback passthrough
|
||||
if ($this->l10n === null) {
|
||||
echo $text;
|
||||
}
|
||||
echo $this->l10n->translate($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the plural form.
|
||||
*
|
||||
* @param string $single string for single word
|
||||
* @param string $plural string for plural word
|
||||
* @param int $number number value
|
||||
* @return string translated plural string
|
||||
*/
|
||||
public function __n(string $single, string $plural, int $number): string
|
||||
{
|
||||
// in case nothing got set yet, this is fallback
|
||||
if ($this->l10n === null) {
|
||||
return $number > 1 ? $plural : $single;
|
||||
}
|
||||
return $this->l10n->ngettext($single, $plural, $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* context translation via msgctxt
|
||||
*
|
||||
* @param string $context context string
|
||||
* @param string $text text to translate
|
||||
* @return string
|
||||
*/
|
||||
public function __p(string $context, string $text): string
|
||||
{
|
||||
if ($this->l10n === null) {
|
||||
return $text;
|
||||
}
|
||||
return $this->l10n->pgettext($context, $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* context translation via msgctxt
|
||||
*
|
||||
* @param string $context context string
|
||||
* @param string $single string for single word
|
||||
* @param string $plural string for plural word
|
||||
* @param int $number number value
|
||||
* @return string
|
||||
*/
|
||||
public function __np(string $context, string $single, string $plural, int $number): string
|
||||
{
|
||||
if ($this->l10n === null) {
|
||||
return $number > 1 ? $plural : $single;
|
||||
}
|
||||
return $this->l10n->npgettext($context, $single, $plural, $number);
|
||||
}
|
||||
|
||||
// alias functions to mimic gettext calls
|
||||
|
||||
/**
|
||||
* alias for gettext,
|
||||
* calls __
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
* @deprecated Use __()
|
||||
*/
|
||||
public function gettext(string $text): string
|
||||
{
|
||||
return $this->__($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* alias for ngettext
|
||||
* calls __n
|
||||
*
|
||||
* @param string $single
|
||||
* @param string $plural
|
||||
* @param int $number
|
||||
* @return string
|
||||
* @deprecated Use __n()
|
||||
*/
|
||||
public function ngettext(string $single, string $plural, int $number): string
|
||||
{
|
||||
return $this->__n($single, $plural, $number);
|
||||
}
|
||||
|
||||
// TODO: dgettext(string $domain, string $message): string
|
||||
// TODO: dngettext(string $domain, string $singular, string $plural, int $count): string
|
||||
// TODO: dpgettext(string $domain, string $message, int $category): string
|
||||
// TODO: dpngettext(string $domain, string $singular, string $plural, int $count, int $category): string
|
||||
}
|
||||
|
||||
// __END__
|
||||
216
src/Language/l10n_functions.php
Normal file
216
src/Language/l10n_functions.php
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
/*********************************************************************
|
||||
* Original: https://github.com/phpmyadmin/motranslator
|
||||
* Has the same function names, but uses a different base system
|
||||
* setlocale -> setLocale
|
||||
* bindtextdomain -> setTextDomain
|
||||
* textdomain -> setDomain
|
||||
*********************************************************************/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use CoreLibs\Language\L10n as Loader;
|
||||
|
||||
/**
|
||||
* Sets a requested locale.
|
||||
*
|
||||
* @param int $category Locale category, ignored
|
||||
* @param string $locale Locale name
|
||||
*
|
||||
* @return string Set or current locale
|
||||
*/
|
||||
function _setlocale(int $category, string $locale): string
|
||||
{
|
||||
return Loader::getInstance()->setLocale($locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path for a domain.
|
||||
*
|
||||
* @param string $domain Domain name
|
||||
* @param string $path Path where to find locales
|
||||
*/
|
||||
function _bindtextdomain(string $domain, string $path): void
|
||||
{
|
||||
Loader::getInstance()->setTextDomain($domain, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dummy compatibility function, MoTranslator assumes
|
||||
* everything is using same character set on input and
|
||||
* output.
|
||||
*
|
||||
* Generally it is wise to output in UTF-8 and have
|
||||
* mo files in UTF-8.
|
||||
*
|
||||
* @param string $domain Domain where to set character set
|
||||
* @param string $codeset Character set to set
|
||||
*/
|
||||
function _bind_textdomain_codeset(string $domain, string $codeset): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default domain.
|
||||
*
|
||||
* @param string $domain Domain name
|
||||
*/
|
||||
function _textdomain(string $domain): void
|
||||
{
|
||||
Loader::getInstance()->setDomain($domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string.
|
||||
*
|
||||
* @param string $msgid String to be translated
|
||||
*
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
function _gettext(string $msgid): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator()->gettext(
|
||||
$msgid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string, alias for _gettext.
|
||||
*
|
||||
* @param string $msgid String to be translated
|
||||
*
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
function __(string $msgid): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator()->gettext(
|
||||
$msgid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext.
|
||||
*
|
||||
* @param string $msgid Single form
|
||||
* @param string $msgidPlural Plural form
|
||||
* @param int $number Number of objects
|
||||
*
|
||||
* @return string translated plural form
|
||||
*/
|
||||
function _ngettext(string $msgid, string $msgidPlural, int $number): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator()->ngettext(
|
||||
$msgid,
|
||||
$msgidPlural,
|
||||
$number
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate with context.
|
||||
*
|
||||
* @param string $msgctxt Context
|
||||
* @param string $msgid String to be translated
|
||||
*
|
||||
* @return string translated plural form
|
||||
*/
|
||||
function _pgettext(string $msgctxt, string $msgid): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator()->pgettext(
|
||||
$msgctxt,
|
||||
$msgid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of pgettext.
|
||||
*
|
||||
* @param string $msgctxt Context
|
||||
* @param string $msgid Single form
|
||||
* @param string $msgidPlural Plural form
|
||||
* @param int $number Number of objects
|
||||
*
|
||||
* @return string translated plural form
|
||||
*/
|
||||
function _npgettext(string $msgctxt, string $msgid, string $msgidPlural, int $number): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator()->npgettext(
|
||||
$msgctxt,
|
||||
$msgid,
|
||||
$msgidPlural,
|
||||
$number
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string.
|
||||
*
|
||||
* @param string $domain Domain to use
|
||||
* @param string $msgid String to be translated
|
||||
*
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
function _dgettext(string $domain, string $msgid): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator('', '', $domain)->gettext(
|
||||
$msgid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext.
|
||||
*
|
||||
* @param string $domain Domain to use
|
||||
* @param string $msgid Single form
|
||||
* @param string $msgidPlural Plural form
|
||||
* @param int $number Number of objects
|
||||
*
|
||||
* @return string translated plural form
|
||||
*/
|
||||
function _dngettext(string $domain, string $msgid, string $msgidPlural, int $number): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator('', '', $domain)->ngettext(
|
||||
$msgid,
|
||||
$msgidPlural,
|
||||
$number
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate with context.
|
||||
*
|
||||
* @param string $domain Domain to use
|
||||
* @param string $msgctxt Context
|
||||
* @param string $msgid String to be translated
|
||||
*
|
||||
* @return string translated plural form
|
||||
*/
|
||||
function _dpgettext(string $domain, string $msgctxt, string $msgid): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator('', '', $domain)->pgettext(
|
||||
$msgctxt,
|
||||
$msgid
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of pgettext.
|
||||
*
|
||||
* @param string $domain Domain to use
|
||||
* @param string $msgctxt Context
|
||||
* @param string $msgid Single form
|
||||
* @param string $msgidPlural Plural form
|
||||
* @param int $number Number of objects
|
||||
*
|
||||
* @return string translated plural form
|
||||
*/
|
||||
function _dnpgettext(string $domain, string $msgctxt, string $msgid, string $msgidPlural, int $number): string
|
||||
{
|
||||
return Loader::getInstance()->getTranslator('', '', $domain)->npgettext(
|
||||
$msgctxt,
|
||||
$msgid,
|
||||
$msgidPlural,
|
||||
$number
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user