mirror of
https://git.beihong.wang/wangbeihong/blog-source.git
synced 2026-04-23 19:03:04 +08:00
initial
This commit is contained in:
407
var/Typecho/I18n/GetText.php
Executable file
407
var/Typecho/I18n/GetText.php
Executable file
@@ -0,0 +1,407 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\I18n;
|
||||
|
||||
/*
|
||||
Copyright (c) 2003 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
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* This file is part of PHP-gettext
|
||||
*
|
||||
* @author Danilo Segan <danilo@kvota.net>, Nico Kaiser <nico@siriux.net>
|
||||
* @category typecho
|
||||
* @package I18n
|
||||
*/
|
||||
class GetText
|
||||
{
|
||||
//public:
|
||||
public int $error = 0; // public variable that holds error code (0 if no error)
|
||||
|
||||
//private:
|
||||
private int $BYTE_ORDER = 0; // 0: low endian, 1: big endian
|
||||
|
||||
private $STREAM = null;
|
||||
|
||||
private bool $short_circuit = false;
|
||||
|
||||
private bool $enable_cache = false;
|
||||
|
||||
private ?int $originals = null; // offset of original table
|
||||
|
||||
private ?int $translations = null; // offset of translation table
|
||||
|
||||
private ?string $pluralHeader = null; // cache header field for plural forms
|
||||
|
||||
private int $total = 0; // total string count
|
||||
|
||||
private ?array $table_originals = null; // table for original strings (offsets)
|
||||
|
||||
private ?array $table_translations = null; // table for translated strings (offsets)
|
||||
|
||||
private ?array $cache_translations = null; // original -> translation mapping
|
||||
|
||||
|
||||
/* Methods */
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $file file name
|
||||
* @param boolean $enable_cache Enable or disable caching of strings (default on)
|
||||
*/
|
||||
public function __construct(string $file, bool $enable_cache = true)
|
||||
{
|
||||
// If there isn't a StreamReader, turn on short circuit mode.
|
||||
if (!file_exists($file)) {
|
||||
$this->short_circuit = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Caching can be turned off
|
||||
$this->enable_cache = $enable_cache;
|
||||
$this->STREAM = @fopen($file, 'rb');
|
||||
|
||||
$unpacked = unpack('c', $this->read(4));
|
||||
$magic = array_shift($unpacked);
|
||||
|
||||
if (-34 == $magic) {
|
||||
$this->BYTE_ORDER = 0;
|
||||
} elseif (-107 == $magic) {
|
||||
$this->BYTE_ORDER = 1;
|
||||
} else {
|
||||
$this->error = 1; // not MO file
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Do we care about revision? We should.
|
||||
$revision = $this->readInt();
|
||||
|
||||
$this->total = $this->readInt();
|
||||
$this->originals = $this->readInt();
|
||||
$this->translations = $this->readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string
|
||||
*
|
||||
* @access public
|
||||
* @param string $string to be translated
|
||||
* @param integer|null $num found string number
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
public function translate($string, ?int &$num): string
|
||||
{
|
||||
if ($this->short_circuit) {
|
||||
return $string;
|
||||
}
|
||||
$this->loadTables();
|
||||
|
||||
if ($this->enable_cache) {
|
||||
// Caching enabled, get translated string from cache
|
||||
if (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext
|
||||
*
|
||||
* @access public
|
||||
* @param string single
|
||||
* @param string plural
|
||||
* @param string number
|
||||
* @param integer|null $num found string number
|
||||
* @return string plural form
|
||||
*/
|
||||
public function ngettext($single, $plural, $number, ?int &$num): string
|
||||
{
|
||||
$number = intval($number);
|
||||
|
||||
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 (!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] ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭文件句柄
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
fclose($this->STREAM);
|
||||
}
|
||||
|
||||
/**
|
||||
* read
|
||||
*
|
||||
* @param mixed $count
|
||||
* @access private
|
||||
* @return false|string
|
||||
*/
|
||||
private function read($count)
|
||||
{
|
||||
$count = abs($count);
|
||||
|
||||
if ($count > 0) {
|
||||
return fread($this->STREAM, $count);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a 32bit Integer from the Stream
|
||||
*
|
||||
* @access private
|
||||
* @return Integer from the Stream
|
||||
*/
|
||||
private function readInt(): int
|
||||
{
|
||||
$end = unpack($this->BYTE_ORDER == 0 ? 'V' : 'N', $this->read(4));
|
||||
return array_shift($end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private function loadTables()
|
||||
{
|
||||
if (
|
||||
is_array($this->cache_translations) &&
|
||||
is_array($this->table_originals) &&
|
||||
is_array($this->table_translations)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* get original and translations tables */
|
||||
fseek($this->STREAM, $this->originals);
|
||||
$this->table_originals = $this->readIntArray($this->total * 2);
|
||||
fseek($this->STREAM, $this->translations);
|
||||
$this->table_translations = $this->readIntArray($this->total * 2);
|
||||
|
||||
if ($this->enable_cache) {
|
||||
$this->cache_translations = ['' => null];
|
||||
/* read all strings in the cache */
|
||||
for ($i = 0; $i < $this->total; $i++) {
|
||||
if ($this->table_originals[$i * 2 + 1] > 0) {
|
||||
fseek($this->STREAM, $this->table_originals[$i * 2 + 2]);
|
||||
$original = fread($this->STREAM, $this->table_originals[$i * 2 + 1]);
|
||||
fseek($this->STREAM, $this->table_translations[$i * 2 + 2]);
|
||||
$translation = fread($this->STREAM, $this->table_translations[$i * 2 + 1]);
|
||||
$this->cache_translations[$original] = $translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an array of Integers from the Stream
|
||||
*
|
||||
* @param int $count How many elements should be read
|
||||
* @return array of Integers
|
||||
*/
|
||||
private function readIntArray(int $count): array
|
||||
{
|
||||
return unpack(($this->BYTE_ORDER == 0 ? 'V' : 'N') . $count, $this->read(4 * $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Binary search for string
|
||||
*
|
||||
* @access private
|
||||
* @param string $string
|
||||
* @param int $start (internally used in recursive function)
|
||||
* @param int $end (internally used in recursive function)
|
||||
* @return int string number (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 { // The string is in the lower half
|
||||
return $this->findString($string, $half, $end);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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];
|
||||
$offset = $this->table_originals[$num * 2 + 2];
|
||||
if (!$length) {
|
||||
return '';
|
||||
}
|
||||
fseek($this->STREAM, $offset);
|
||||
$data = fread($this->STREAM, $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];
|
||||
$offset = $this->table_translations[$num * 2 + 2];
|
||||
if (!$length) {
|
||||
return '';
|
||||
}
|
||||
fseek($this->STREAM, $offset);
|
||||
$data = fread($this->STREAM, $length);
|
||||
return (string)$data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects which plural form to take
|
||||
*
|
||||
* @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", $n, $string);
|
||||
$string = str_replace('plural', "\$plural", $string);
|
||||
|
||||
$total = 0;
|
||||
$plural = 0;
|
||||
|
||||
eval("$string");
|
||||
if ($plural >= $total) {
|
||||
$plural = $total - 1;
|
||||
}
|
||||
return $plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!is_string($this->pluralHeader)) {
|
||||
if ($this->enable_cache) {
|
||||
$header = $this->cache_translations[""];
|
||||
} else {
|
||||
$header = $this->getTranslationString(0);
|
||||
}
|
||||
|
||||
if (!is_null($header) && preg_match("/plural\-forms: ([^\n]*)\n/i", $header, $regs)) {
|
||||
$expr = $regs[1];
|
||||
} else {
|
||||
$expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
|
||||
}
|
||||
$this->pluralHeader = $expr;
|
||||
}
|
||||
return $this->pluralHeader;
|
||||
}
|
||||
}
|
||||
102
var/Typecho/I18n/GetTextMulti.php
Executable file
102
var/Typecho/I18n/GetTextMulti.php
Executable file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Typecho\I18n;
|
||||
|
||||
/**
|
||||
* 用于解决一个多个mo文件带来的读写问题
|
||||
* 我们重写了一个文件读取类
|
||||
*
|
||||
* @author qining
|
||||
* @category typecho
|
||||
* @package I18n
|
||||
*/
|
||||
class GetTextMulti
|
||||
{
|
||||
/**
|
||||
* 所有的文件读写句柄
|
||||
*
|
||||
* @access private
|
||||
* @var GetText[]
|
||||
*/
|
||||
private array $handlers = [];
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @access public
|
||||
* @param string $fileName 语言文件名
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(string $fileName)
|
||||
{
|
||||
$this->addFile($fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加一个语言文件
|
||||
*
|
||||
* @access public
|
||||
* @param string $fileName 语言文件名
|
||||
* @return void
|
||||
*/
|
||||
public function addFile(string $fileName)
|
||||
{
|
||||
$this->handlers[] = new GetText($fileName, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a string
|
||||
*
|
||||
* @access public
|
||||
* @param string $string string to be translated
|
||||
* @return string translated string (or original, if not found)
|
||||
*/
|
||||
public function translate(string $string): string
|
||||
{
|
||||
foreach ($this->handlers as $handle) {
|
||||
$string = $handle->translate($string, $count);
|
||||
if (- 1 != $count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plural version of gettext
|
||||
*
|
||||
* @access public
|
||||
* @param string $single single
|
||||
* @param string $plural plural
|
||||
* @param int $number number
|
||||
* @return string translated plural form
|
||||
*/
|
||||
public function ngettext(string $single, string $plural, int $number): string
|
||||
{
|
||||
$count = - 1;
|
||||
|
||||
foreach ($this->handlers as $handler) {
|
||||
$string = $handler->ngettext($single, $plural, $number, $count);
|
||||
if (- 1 != $count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭所有句柄
|
||||
*
|
||||
* @access public
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
foreach ($this->handlers as $handler) {
|
||||
/** 显示的释放内存 */
|
||||
unset($handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user