diff --git a/examples/lang.php b/examples/lang.php new file mode 100644 index 0000000..73b6937 --- /dev/null +++ b/examples/lang.php @@ -0,0 +1,33 @@ + dirname(__DIR__) . '/resources/langs', + 'langs' => ['en', 'zh-CN'] +]); + +$translator->scanAndLoad(); + +$translator['user.test'] = 'new value'; + +vd( + $translator['user.test'], + $translator->tl('user.test'), + $translator['zh-CN.user.test'], + $translator['keyNotExist'], + $translator['user.keyNotExist'] +); + +//vd($translator->all()); + +vd($translator->getLangData(null, false)); +//vd($translator->getLangData('zh-CN', false)); \ No newline at end of file diff --git a/lib/sws/BaseSws.php b/lib/sws/BaseSws.php index 95adf22..9bdf677 100644 --- a/lib/sws/BaseSws.php +++ b/lib/sws/BaseSws.php @@ -97,6 +97,17 @@ public static function lang() return self::$app->get('lang'); } + /** + * @param string $key + * @param array $args + * @param null $lang + * @return array|string + */ + public static function tl($key, array $args = [], $lang = null) + { + return self::$app->get('lang')->tl($key, $args, $lang); + } + /******************************************************************************* * request context ******************************************************************************/ diff --git a/lib/sws/Components/Language.php b/lib/sws/Components/Language.php deleted file mode 100644 index c99a9bd..0000000 --- a/lib/sws/Components/Language.php +++ /dev/null @@ -1,384 +0,0 @@ - array ] - */ - private $data = []; - - /** - * The base path language directory. - * @var string - */ - private $basePath; - - /** - * default file name. - * @var string - */ - private $defaultFile = 'default'; - - /** - * the language file type. more see File::FORMAT_* - * @var string - */ - private $fileType = File::FORMAT_PHP; - - /** - * level separator char. - * e.g: - * $language->tran('app.createPage'); - * @var string - */ - private $separator = '.'; - - /** - * e.g. - * [ - * 'user' => '/xx/yy/zh-cn/user.yml' - * 'admin' => '/xx/yy/zh-cn/admin.yml' - * ] - * @var array - */ - private $langFiles = []; - - /** - * loaded language file list. - * @var array - */ - private $loadedFiles = []; - - /** - * whether ignore not exists lang file when addLangFile() - * @var bool - */ - private $ignoreError = false; - - const DEFAULT_FILE_KEY = '__default'; - - /** - * Language constructor. - * @param array $config - */ - public function __construct(array $config = []) - { - - } - - /** - * {@inheritdoc} - * @see self::translate() - */ - public function trans($key, array $args = [], $default = '') - { - return $this->translate($key, $args, $default); - } - - /** - * {@inheritdoc} - * @see self::translate() - */ - public function tl($key, array $args = [], $default = '') - { - return $this->translate($key, $args, $default); - } - - /** - * - * @param string|bool $key 'site-name' or 'user.login' - * @param array $args - * @param string $default - * @param null|string $lang - * @return array|string - */ - public function translate(string $key, array $args = [], $default = '', $lang = null) - { - if (!$key || !is_string($key)) { - throw new \InvalidArgumentException('A lack of parameters or type error.'); - } - - $lang = $lang ?: $this->lang; - - if (!$langData = $this->getLangData($lang, false)) { - throw new \InvalidArgumentException('No language data of the lang: ' . $lang); - } - - $value = Arr::getByPath($langData, $key, $default, $this->separator); - - // no translate text - if ($value === '' || $value === null) { - return ucfirst(Str::toSnakeCase(str_replace(['-', '_', '.'], ' ', $key), ' ')); - } - - // $args is not empty - if ($args) { - array_unshift($args, $value); - - return sprintf(...$args); - } - - return $value; - } - - /** - * @param string $lang - * @param bool $toIterator - * @return array|\ArrayIterator|null - */ - public function getLangData(string $lang, $toIterator = true) - { - if (isset($this->data[$lang])) { - return $toIterator ? new \ArrayIterator($this->data[$lang]) : $this->data[$lang]; - } - - return null; - } - - /** - * @param string $key - * @param null|string $lang - * @return bool - */ - public function has(string $key, $lang = null) - { - $lang = $lang ?: $this->lang; - - if (!$langData = $this->getLangData($lang, false)) { - return false; - } - - return Arr::getByPath($langData, $key, null, $this->separator) !== null; - } - - /********************************************************************************* - * getter/setter - *********************************************************************************/ - - /** - * Allow quick access default file translate by `$lang->key`, - * is equals to `$lang->tl('key')`. - * @param string $name - * @return mixed|string - * @throws \InvalidArgumentException - */ - public function __get($name) - { - return $this->translate($name); - } - - /** - * @param $key - * @param $name - */ - public function __set($key, $name) - { - // - } - - public function __isset($key) - { - return $this->has($key); - } - - /** - * Allow quick access default file translate by `$lang->key()`, - * is equals to `$lang->tl('key')`. - * @param string $name - * @param array $args - * @return mixed|string - * @throws \InvalidArgumentException - */ - public function __call($name, $args) - { - return $this->translate($name); - } - - /** - * @return string - */ - public function getLang() - { - return $this->lang; - } - - /** - * @param string $lang - */ - public function setLang($lang) - { - $this->lang = trim($lang); - } - - /** - * @return string - */ - public function getBasePath() - { - return $this->basePath; - } - - /** - * @param string|array $path - */ - public function setBasePath($path) - { - if ($path && is_dir($path)) { - $this->basePath = $path; - } - } - - /** - * @return array - */ - public function getData() - { - return $this->data; - } - - /** - * @return array - */ - public function getLangFiles() - { - return $this->langFiles; - } - - /** - * @param array $langFiles - * @throws \inhere\exceptions\InvalidArgumentException - * @throws \inhere\exceptions\NotFoundException - */ - public function setLangFiles(array $langFiles) - { - foreach ($langFiles as $fileKey => $file) { - $this->addLangFile($file, is_numeric($fileKey) ? '' : $fileKey); - } - } - - /** - * @param bool $full - * @return string - */ - public function getDefaultFile($full = false) - { - return $full ? $this->getLangFile(self::DEFAULT_FILE_KEY) : $this->defaultFile; - } - - /** - * @return string - */ - public function getFallbackLang() - { - return $this->fallbackLang; - } - - /** - * @param string $fallbackLang - */ - public function setFallbackLang($fallbackLang) - { - $this->fallbackLang = $fallbackLang; - } - - /** - * @return string - */ - public function getFileType() - { - return $this->fileType; - } - - /** - * @param string $fileType - */ - public function setFileType($fileType) - { - if (in_array($fileType, self::$formats, true)) { - $this->fileType = $fileType; - } - } - - /** - * @return string - */ - public function getSeparator() - { - return $this->separator; - } - - /** - * @param string $separator - */ - public function setSeparator($separator) - { - $this->separator = $separator; - } - - /** - * @return array - */ - public function getLoadedFiles() - { - return $this->loadedFiles; - } - - /** - * @return bool - */ - public function isIgnoreError() - { - return $this->ignoreError; - } - - /** - * @param bool $ignoreError - */ - public function setIgnoreError($ignoreError) - { - $this->ignoreError = (bool)$ignoreError; - } - - /** - * @return array - */ - public static function getFormats(): array - { - return self::$formats; - } -} diff --git a/lib/sws/Memory/FileLogHandler.php b/lib/sws/Memory/FileLogHandler.php new file mode 100644 index 0000000..47e3250 --- /dev/null +++ b/lib/sws/Memory/FileLogHandler.php @@ -0,0 +1,433 @@ + 'EMERGENCY', + self::ERROR => 'ERROR', + self::WARN => 'WARNING', + self::INFO => 'INFO', + self::PROC_INFO => 'PROC_INFO', + self::WORKER_INFO => 'WORKER_INFO', + self::DEBUG => 'DEBUG', + self::CRAZY => 'CRAZY', + ]; + + /** + * ProcessLogger constructor. + * @param array $config + */ + public function __construct(array $config = []) + { + foreach ($config as $prop => $value) { + $this->$prop = $value; + } + + $this->init(); + } + + /** + * init + */ + protected function init() + { + $this->fileHandle = null; + $this->level = (int)$this->level; + $this->logThreshold = (int)$this->logThreshold; + $this->toSyslog = (bool)$this->toSyslog; + $this->toConsole = (bool)$this->toConsole; + + if ($this->file === 'syslog') { + $this->file = null; + $this->toSyslog = true; + } + + if ($this->spiltType && !in_array($this->spiltType, [self::SPLIT_DAY, self::SPLIT_HOUR], true)) { + $this->spiltType = self::SPLIT_DAY; + } + + // open Log File + $this->open(); + } + + /** + * Debug log + * @param string $msg + * @param array $data + */ + public function debug($msg, array $data = []) + { + $this->log($msg, self::DEBUG, $data); + } + + /** + * Exception log + * @param \Throwable $e + * @param string $preMsg + */ + public function ex(\Throwable $e, $preMsg = '') + { + $preMsg = $preMsg ? "$preMsg " : ''; + + $this->log(sprintf( + "{$preMsg}Exception: %s On %s Line %s\nCode Trace:\n%s", + $e->getMessage(), + $e->getFile(), + $e->getLine(), + $e->getTraceAsString() + ), self::ERROR); + } + + /** + * Logs data to disk or stdout + * @param string $msg + * @param int $level + * @param array $data + * @return bool + */ + public function log($msg, $level = self::INFO, array $data = []) + { + if (is_numeric($level) && $level > $this->level) { + return true; + } + + $strData = $data ? json_encode($data) : ''; + + if ($this->toSyslog) { + return $this->sysLog($msg . ' ' . $strData, $level); + } + + $label = self::getLevelName($level); + $ds = FormatHelper::microTime(microtime(true)); + + // [$this->getPidRole():$this->pid] $msg + $logString = sprintf("[%s] [%s] %s %s\n", $ds, $label, trim($msg), $strData); + + // if not in daemon, print log to \STDOUT + if (!$this->toConsole) { + $this->stdout($logString, false); + } + + if ($this->fileHandle) { + $this->count++; + $this->cache[] = CliHelper::clearColor($logString); + + if ($this->count >= $this->logThreshold || $this->fileIsChanged()) { + $this->flush(); + } + } + + return true; + } + + /** + * flush + */ + public function flush() + { + if (!$this->cache) { + return true; + } + + $string = ''; + + foreach ($this->cache as $log) { + $string .= $log; + } + + if ($string) { + $this->updateLogFile(); + + fwrite($this->fileHandle, $string); + } + + $this->count = 0; + $this->cache = []; + + return true; + } + + protected function fileIsChanged() + { + if (!$this->fileHandle || !($file = $this->file)) { + return false; + } + + if (!$this->spiltType) { + return false; + } + + $str = $this->getLogFileDate(); + + return !strpos($file, '_' . $str); + } + + /** + * update the log file name. If 'log_split' is not empty and manager running to long time. + */ + protected function updateLogFile() + { + // update file. $dtStr is '_Y-m-d' or '_Y-m-d_H' + if ($this->fileIsChanged()) { + fclose($this->fileHandle); + + $logFile = $this->genLogFile(true); + $this->file = $logFile; + $this->fileHandle = @fopen($logFile, 'ab'); + + if (!$this->fileHandle) { + $this->stderr("Could not open the log file {$logFile}"); + } + } + } + + /** + * Opens the log file. If already open, closes it first. + */ + public function open() + { + if ($logFile = $this->genLogFile(true)) { + if ($this->fileHandle) { + fclose($this->fileHandle); + } + + $this->file = $logFile; + $this->fileHandle = @fopen($logFile, 'ab'); + + if (!$this->fileHandle) { + $this->stderr("Could not open the log file {$logFile}"); + } + } + } + + /** + * close + */ + public function close() + { + // close logFileHandle + if ($this->fileHandle) { + $this->flush(); + fclose($this->fileHandle); + + $this->fileHandle = null; + } + } + + /** + * gen real LogFile + * @param bool $createDir + * @return string + */ + public function genLogFile($createDir = false) + { + // log split type + if (!($type = $this->spiltType) || !($file = $this->file)) { + return $this->file; + } + + $info = pathinfo($file); + $dir = $info['dirname']; + $name = $info['filename'] ?? 'gw-manager'; + $ext = $info['extension'] ?? 'log'; + + if ($createDir) { + Directory::mkdir($dir, 0775); + } + + $str = $this->getLogFileDate(); + + return "{$dir}/{$name}_{$str}.{$ext}"; + } + + /** + * @return string + */ + public function getLogFileDate() + { + $str = ''; + + if ($this->spiltType === self::SPLIT_DAY) { + $str = date('Y-m-d'); + } elseif ($this->spiltType === self::SPLIT_HOUR) { + $str = date('Y-m-d_H'); + } + + return $str; + } + + /** + * Logs data to stdout + * @param string $text + * @param bool $nl + * @param bool|int $quit + */ + protected function stdout($text, $nl = true, $quit = false) + { + CliHelper::stdout($text, $nl, $quit); + } + + /** + * Logs data to stderr + * @param string $text + * @param bool $nl + * @param bool|int $quit + */ + protected function stderr($text, $nl = true, $quit = -200) + { + CliHelper::stderr($text, $nl, $quit); + } + + /** + * Logs data to the syslog + * @param string $msg + * @param int $level + * @return bool + */ + protected function sysLog($msg, $level) + { + switch ($level) { + case self::EMERG: + $priority = LOG_EMERG; + break; + case self::ERROR: + $priority = LOG_ERR; + break; + case self::WARN: + $priority = LOG_WARNING; + break; + case self::DEBUG: + $priority = LOG_DEBUG; + break; + case self::INFO: + case self::PROC_INFO: + case self::WORKER_INFO: + default: + $priority = LOG_INFO; + break; + } + + if (!$ret = syslog($priority, $msg)) { + $this->stderr("Unable to write to syslog\n"); + } + + return $ret; + } + + /** + * @return array + */ + public static function getLevels() + { + return self::$levels; + } + + /** + * @param int|string $level + * @return string + */ + public static function getLevelName($level) + { + if (is_numeric($level)) { + return self::$levels[$level] ?? 'Unknown'; + } + + return strtoupper($level); + } + + /** + * getFile + * @return string + */ + public function getFile() + { + return $this->file; + } + + /** + * __destruct + */ + public function __destruct() + { + $this->close(); + } +} diff --git a/lib/sws/Memory/Language.php b/lib/sws/Memory/Language.php new file mode 100644 index 0000000..065ac3d --- /dev/null +++ b/lib/sws/Memory/Language.php @@ -0,0 +1,434 @@ +translate($key, $args, $lang); + } + + /** + * {@inheritdoc} + * @see self::translate() + */ + public function trans($key, array $args = [], $lang = null) + { + return $this->translate($key, $args, $lang); + } + + /** + * get translate message text by key + * @param string $key 'site-name' or 'user.login' + * @param array $args + * @param null|string $lang + * @return array|string + */ + public function translate(string $key, array $args = [], $lang = null) + { + if (!$key || !is_string($key)) { + throw new \InvalidArgumentException('A lack of parameters or type error.'); + } + + list($lang, $key) = $this->parseKey($key, $lang); + + if (!$langData = $this->getLangData($lang, false)) { + // if ignore error + if ($this->ignoreError) { + return ucfirst(Str::toSnakeCase(str_replace(['-', '_', '.'], ' ', $key), ' ')); + } + + throw new \InvalidArgumentException('No language data of the lang: ' . $lang); + } + + $value = Arr::getByPath($langData, $key, null, $this->separator); + + // no translate text + if ($value === '' || $value === null) { + return ucfirst(Str::toSnakeCase(str_replace(['-', '_', '.'], ' ', $key), ' ')); + } + + // $args is not empty + if ($args) { + array_unshift($args, $value); + + return sprintf(...$args); + } + + return $value; + } + + /** + * @param string $lang + * @param bool $toIterator + * @return array|\ArrayIterator|null + */ + public function getLangData(string $lang = null, $toIterator = true) + { + $lang = $lang ?: $this->lang; + + if (isset($this->data[$lang])) { + return $toIterator ? new \ArrayIterator($this->data[$lang]) : $this->data[$lang]; + } + + return null; + } + + /** + * @param string $key + * @param null|string $lang + * @return bool + */ + public function hasKeyOfLang(string $key, $lang = null) + { + list($lang, $key) = $this->parseKey($key, $lang); + + if (!$langData = $this->getLangData($lang, false)) { + return false; + } + + return Arr::getByPath($langData, $key, null, $this->separator) !== null; + } + + /********************************************************************************* + * register lang data + *********************************************************************************/ + + /** + * @var bool + */ + private $scanned = false; + + /** + * scan basePath And Load data + */ + public function scanAndLoad() + { + if ($this->scanned) { + return $this; + } + + $files = Directory::getFiles($this->basePath, $this->format, true); + $sfx = '.' . $this->format; + $langs = array_flip($this->langs); + + foreach ($files as $file) { + // $lang: file at basePath e.g {basePath}/en.php + $prefix = $lang = basename($file, $sfx); + + // file at lang dir. e.g {basePath}/en/user.php + if ($p = strpos($file, '/')) { + $lang = substr($file, 0, $p); + } + + // is registered lang name + if (isset($langs[$lang])) { + $filePath = $this->basePath . '/' . $file; +// $this->loadLangFile($lang, $filePath, $this->format) + + $this->loadedFiles[] = $filePath; + $this->set("{$lang}.{$prefix}", self::read($filePath, $this->format)); + } + } + + $this->scanned = true; + return $this; + } + + /** + * @param string $lang + * @param string $file + * @param null|string $format + */ + public function loadLangFile(string $lang, $file, $format = null) + { + if (!is_file($file)) { + throw new \InvalidArgumentException("the lang file: $file is not exists"); + } + + $format = $format ?: $this->format; + $prefix = basename($file, '.' . $format); + + $this->loadedFiles[] = $file; + $this->set("{$lang}.{$prefix}", self::read($file, $format)); + } + + /** + * @param string $lang + * @param array $data + * @return $this + */ + public function addLangData(string $lang, array $data) + { + if ($langData = $this->getLangData($lang, false)) { + $this->bindData($langData, $data); + } else { + $langData = $data; + } + + $this->data[$lang] = $langData; + + if (!$this->isLang($lang)) { + $this->langs[] = $lang; + } + + return $this; + } + + /********************************************************************************* + * override methods + *********************************************************************************/ + + /** + * {@inheritdoc} + */ + public function exists($key) + { + return $this->hasKeyOfLang($key); + } + + /** + * {@inheritdoc} + */ + public function add($name, $value) + { + throw new \RuntimeException('The method [add] has been disabled'); + } + + /** + * {@inheritdoc} + */ + public function set($key, $value) + { + list($lang, $key) = $this->parseKey($key); + + $langData = $this->getLangData($lang, false); + + Arr::setByPath($langData, $key, $value, $this->separator); + + $this->data[$lang] = $langData; + + return $this; + } + + /** + * @param string $key + * @param null $default + * @return mixed + */ + public function get(string $key, $default = null) + { + return $this->translate($key); + } + + /********************************************************************************* + * helper + *********************************************************************************/ + + /** + * @param string $key + * @param string $lang + * @return array + */ + private function parseKey($key, $lang = null) + { + if ($lang) { + return [$lang, $key]; + } + + if (strpos($key, $this->separator)) { + $info = explode($this->separator, $key, 2); + + if ($this->isLang($info[0])) { + return $info; + } + } + + return [$this->lang, $key]; + } + + /********************************************************************************* + * getter/setter + *********************************************************************************/ + + /** + * @param string $lang + * @return bool + */ + public function hasLang($lang) + { + return $this->isLang($lang); + } + public function isLang($lang) + { + return $lang && in_array($lang, $this->langs,true); + } + + /** + * @return string + */ + public function getLang() + { + return $this->lang; + } + + /** + * @param string $lang + */ + public function setLang($lang) + { + $this->lang = trim($lang); + } + + /** + * @return string + */ + public function getBasePath() + { + return $this->basePath; + } + + /** + * @param string|array $path + */ + public function setBasePath($path) + { + if ($path && is_dir($path)) { + $this->basePath = $path; + } else { + throw new \InvalidArgumentException("The language files path: $path is not exists."); + } + } + + /** + * @return string + */ + public function getFormat() + { + return $this->format; + } + + /** + * @param string $format + */ + public function setFormat($format) + { + if (in_array($format, self::$formats, true)) { + $this->format = $format; + } + } + + /** + * @return array + */ + public function getLoadedFiles() + { + return $this->loadedFiles; + } + + /** + * @return bool + */ + public function isIgnoreError() + { + return $this->ignoreError; + } + + /** + * @param bool $ignoreError + */ + public function setIgnoreError($ignoreError) + { + $this->ignoreError = (bool)$ignoreError; + } + + /** + * @return \string[] + */ + public function getLangs(): array + { + return $this->langs; + } + + /** + * @param \string[] $langs + */ + public function setLangs(array $langs) + { + $this->langs = $langs; + } +} diff --git a/resources/langs/en/response.php b/resources/langs/en/response.php index 0411a7e..10accbc 100644 --- a/resources/langs/en/response.php +++ b/resources/langs/en/response.php @@ -6,4 +6,8 @@ * Time: 14:25 */ -return []; \ No newline at end of file +use App\Helpers\ResCode; + +return [ + ResCode::OK => 'successful', +]; \ No newline at end of file diff --git a/resources/langs/en/default.php b/resources/langs/en/user.php similarity index 100% rename from resources/langs/en/default.php rename to resources/langs/en/user.php diff --git a/resources/langs/zh-CN/default.php b/resources/langs/zh-CN/user.php similarity index 100% rename from resources/langs/zh-CN/default.php rename to resources/langs/zh-CN/user.php