Add src files for CoreLibs composer all package

This commit is contained in:
Clemens Schwaighofer
2023-02-16 12:47:43 +09:00
commit cfcd601b3a
76 changed files with 25404 additions and 0 deletions

View 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__

View 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__

View 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__

View 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__

View 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
View 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
View 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
View 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__

View 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
);
}