Update Language\L10n class and drop all legacy code

new Langauge\GetLocale::setLocale() for getting new type lang info from
session, etc
L10n class call chnage of parameters:
NEW: locale, domain, path
OLD: locale, path, domain, legacy(bool)

Temporary auto detect for possible path/domain switch if domain value
has slash inside

Rename all local files to names matching locale folder
en_US -> en

Delete lang folders with symlinks as they are no longer used

Update all header files and class Backend\Admin, ACL\Login,
Output\Form\Generate, Template\SmartyExtend with new language order:
call ::setLocale() afer login class

Update missing test translation strings in all po files

Update phpUnit tests to match all new changes
This commit is contained in:
Clemens Schwaighofer
2022-04-15 13:29:41 +09:00
parent c181a83b48
commit fed67e990d
37 changed files with 889 additions and 645 deletions

View File

@@ -167,13 +167,10 @@ class Login
* constructor, does ALL, opens db, works through connection checks, closes itself
* @param \CoreLibs\DB\IO $db Database connection class
* @param \CoreLibs\Debug\Logging $log Logging class
* @param \CoreLibs\Language\L10n|null $l10n l10n language class
* if null, auto set
*/
public function __construct(
\CoreLibs\DB\IO $db,
\CoreLibs\Debug\Logging $log,
?\CoreLibs\Language\L10n $l10n = null
\CoreLibs\Debug\Logging $log
) {
// log login data for this class only
$log->setLogPer('class', true);
@@ -215,19 +212,6 @@ class Login
// or need to pass it back
// to the continue AJAX class for output back to the user
$this->login_is_ajax_page = isset($GLOBALS['AJAX_PAGE']) && $GLOBALS['AJAX_PAGE'] ? true : false;
// set the default lang
$locale = 'en_US.UTF-8';
$lang = 'en_utf8';
if (Session::getSessionId() !== false && !empty($_SESSION['DEFAULT_LANG'])) {
$lang = $_SESSION['DEFAULT_LANG'];
$locale = $_SESSION['DEFAULT_LOCALE'];
} else {
$lang = defined('SITE_LANG') && !empty(SITE_LANG) ?
SITE_LANG : DEFAULT_LANG;
$locale = defined('SITE_LOCALE') && !empty(SITE_LOCALE) ?
SITE_LOCALE : DEFAULT_LOCALE;
}
$this->l = $l10n ?? new \CoreLibs\Language\L10n($lang);
// if we have a search path we need to set it, to use the correct DB to login
// check what schema to use. if there is a login schema use this, else check
@@ -300,6 +284,24 @@ class Login
$this->loginCheckPermissions();
// logsout user
$this->loginLogoutUser();
// ** LANGUAGE SET AFTER LOGIN **
// set the locale
if (Session::getSessionId() !== false && !empty($_SESSION['DEFAULT_LANG'])) {
$locale = $_SESSION['DEFAULT_LOCALE'] ?? '';
} else {
$locale = defined('SITE_LOCALE') && !empty(SITE_LOCALE) ?
SITE_LOCALE :
/** @phpstan-ignore-next-line DEFAULT_LOCALE could be empty */
(defined('DEFAULT_LOCALE') && !empty(DEFAULT_LOCALE) ?
DEFAULT_LOCALE : 'en.UTF-8');
}
// set domain
if (defined('CONTENT_PATH') && !empty(CONTENT_PATH)) {
$domain = str_replace('/', '', CONTENT_PATH);
} else {
$domain = 'admin';
}
$this->l = new \CoreLibs\Language\L10n($locale, $domain);
// if the password change flag is okay, run the password change method
if ($this->password_change) {
$this->loginPasswordChange();

View File

@@ -114,15 +114,16 @@ class Backend
// CONSTRUCTOR / DECONSTRUCTOR |====================================>
/**
* main class constructor
* @param \CoreLibs\DB\IO $db Database connection class
* @param \CoreLibs\Debug\Logging $log Logging class
* @param \CoreLibs\Language\L10n|null $l10n l10n language class
* if null, auto set
* @param \CoreLibs\DB\IO $db Database connection class
* @param \CoreLibs\Debug\Logging $log Logging class
* @param \CoreLibs\Language\L10n $l10n l10n language class
* @param array<string,string> $locale locale data read from setLocale
*/
public function __construct(
\CoreLibs\DB\IO $db,
\CoreLibs\Debug\Logging $log,
?\CoreLibs\Language\L10n $l10n = null
\CoreLibs\Language\L10n $l10n,
array $locale
) {
// set to log not per class
$log->setLogPer('class', false);
@@ -130,10 +131,15 @@ class Backend
$this->log = $log;
// attach db class
$this->db = $db;
// TODO lang create outside of class
$this->setLangEncoding();
// get the language sub class & init it
$this->l = $l10n ?? new \CoreLibs\Language\L10n($this->lang);
$this->l = $l10n;
// parse and read, legacy stuff
$this->encoding = $locale['encoding'];
$this->lang = $locale['lang'];
// get first part from lang
$this->lang_short = explode('_', $locale['lang'])[0];
$this->domain = $this->l->getDomain();
$this->lang_dir = $this->l->getBaseLocalePath();
// set the page name
$this->page_name = \CoreLibs\Get\System::getPageName();
@@ -159,29 +165,6 @@ class Backend
// NO OP
}
// INTERNAL METHODS |===============================================>
/**
* set the language encoding and language settings
* use $OVERRIDE_LANG to override all language settings
* the default charset from _SESSION login or from
* config DEFAULT ENCODING
* the lang full name for mo loading from _SESSION login
* or SITE LANG or DEFAULT LANG from config
* creates short lang (only first two chars) from the lang
* @return void
*/
private function setLangEncoding(): void
{
list (
$this->encoding,
$this->lang,
$this->lang_short,
$this->domain,
$this->lang_dir
) = \CoreLibs\Language\GetSettings::setLangEncoding();
}
// PUBLIC METHODS |=================================================>
/**

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__

View File

@@ -30,6 +30,7 @@ class GetSettings
* @param string|null $locale A valid locale name
* @param string|null $path A valid path where the mo files will be based
* @return array<int|string,string> Settings as array/dictionary
* @deprecated Use CoreLibs\Language\GetLocale::setLocale()
*/
public static function setLangEncoding(
?string $locale = null,

View File

@@ -3,34 +3,26 @@
/*********************************************************************
* AUTHOR: Clemens Schwaighofer
* CREATED: 2004/11/18
* VERSION: 1.0.0
* 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.
* 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
*
* PUBLIC VARIABLES
*
* PRIVATE VARIABLES
*
* VERSION 3.0 (2022/4) removes all old folder layout and uses standard gettext
* PUBLIC METHODS
* __: returns string (translated or original if not found)
* __e: echos out string (translated or original if not found)
* __n: should return plural. never tested this.
*
* PRIVATE METHODS
* __ : returns string (translated or original if not found)
* __n : plural string
* __p : string with context
* __pb: 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)
*********************************************************************/
// TODO: default path change to <base>/lang/LC_MESSAGES/domain.encoding.mo
// for example: lang: ja_JP.UTF-8, domain: admin
// <base>/ja_JP/LC_MESSAGES/admin.UTF-8.mo
// OLD: includes/lang/admin/ja_utf8.mo
// NEW: includes/lang/ja_JP/LC_MESSAGES/admin.UTF-8.mo
// or fallback: includes/lang/ja/LC_MESSAGES/admin.UTF-8.mo
declare(strict_types=1);
namespace CoreLibs\Language;
@@ -48,15 +40,20 @@ class L10n
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 */
@@ -74,20 +71,24 @@ class L10n
*
* @param string $locale language name, default empty string
* will return self instance
* @param string $path path, if empty fallback on default internal path
* @param string $domain override CONTENT_PATH . $encoding name for mo file
* @param bool $legacy default true, if set to true, will look in the old
* folder format lang/ CONTENT_PATH / $lang . mo
* @param string $path path, if empty fallback on default internal path
*/
public function __construct(
string $locale = '',
string $path = '',
string $domain = '',
bool $legacy = true
string $path = '',
) {
// load the mo file if locale is not empty
if (!empty($locale)) {
$this->getTranslator($locale, $path, $domain, $legacy);
// 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);
}
}
@@ -116,43 +117,18 @@ class L10n
require_once __DIR__ . '/l10n_functions.php';
}
/**
* legacy loader name for getTranslator
* instead of returning the GetTextReader object it returns
* true or false for successful load.
* NOTE: some time down the road this will be deprecated
*
* @param string $locale
* @param string $path
* @param string $domain
* @param bool $legacy
* @return bool Returns true for successfull load, false for error
*/
public function l10nReloadMOfile(
string $locale,
string $path = '',
string $domain = '',
bool $legacy = true
): bool {
$this->getTranslator($locale, $path, $domain, $legacy);
return $this->load_failure ? false : true;
}
/**
* loads the mo file base on path, locale and domain set
*
* @param string $locale language name (optional), fallback is en
* @param string $path path, if empty fallback on default internal path
* @param string $domain override CONTENT_PATH . $encoding name for mo file
* @param bool $legacy default true, if set to true, will look in the old
* folder format lang/ CONTENT_PATH / $lang . mo
* @param string $path path, if empty fallback on default internal path
* @return GetTextReader the main gettext reader object
*/
public function getTranslator(
string $locale = '',
string $path = '',
string $domain = '',
bool $legacy = false
string $path = ''
): GetTextReader {
// set local if not from parameter
if (empty($locale)) {
@@ -169,48 +145,34 @@ class L10n
$old_base_locale_path = $this->base_locale_path;
$old_base_content_path = $this->base_content_path;
// legacy or new type
// legacy will use the old lang/content/file.mo type as default
// if path is not set, also locale is the file name
// for new type it follows the gettext spec and path is just the
// base folder where the mo files will be searched
if ($legacy === true) {
if (!is_dir($path)) {
$this->base_locale_path = BASE . INCLUDES . LANG;
$this->base_content_path = CONTENT_PATH;
$path = $this->base_locale_path . $this->base_content_path;
}
$this->mofile = $path . $locale . ".mo";
// 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 {
// if new 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)) {
break;
}
$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)) {
break;
}
}
@@ -273,6 +235,34 @@ class L10n
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
@@ -294,28 +284,16 @@ class L10n
return $locale_list;
}
// is matching regex
if (
!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
)
) {
// not matching, return as is
$locale_detail = L10n::parseLocale($locale);
// all null = nothing mached, return locale as is
if ($locale_detail === array_filter($locale_detail, 'is_null')) {
return [$locale];
}
// do matching run
$lang = $matches['lang'] ?? null;
$country = $matches['country'] ?? null;
$charset = $matches['charset'] ?? null;
$modifier = $matches['modifier'] ?? null;
// 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)

View File

@@ -299,23 +299,39 @@ class Generate extends \CoreLibs\DB\Extended\ArrayIO
/**
* construct form generator
* @param array<mixed> $db_config db config array
* @param \CoreLibs\Debug\Logging|null $log Logging class
* @param \CoreLibs\Language\L10n|null $l10n l10n language class
* if null, auto set
* @param array<mixed> $db_config db config array, mandatory
* @param \CoreLibs\Debug\Logging|null $log Logging class, null auto set
* @param \CoreLibs\Language\L10n|null $l10n l10n language class, null auto set
* @param array<string,string>|null $locale locale array from ::setLocale,
* null auto set
*/
public function __construct(
array $db_config,
\CoreLibs\Debug\Logging $log = null,
?\CoreLibs\Language\L10n $l10n = null
?\CoreLibs\Debug\Logging $log = null,
?\CoreLibs\Language\L10n $l10n = null,
?array $locale = null
) {
global $table_arrays;
// replace any non valid variable names
// TODO extract only alphanumeric and _ after . to _ replacement
$this->my_page_name = str_replace(['.'], '_', System::getPageName(System::NO_EXTENSION));
$this->setLangEncoding();
// if pass on locale is null
if ($locale === null) {
$locale = \CoreLibs\Language\GetLocale::setLocale();
}
// init the language class
$this->l = $l10n ?? new \CoreLibs\Language\L10n($this->lang);
$this->l = $l10n ?? new \CoreLibs\Language\L10n(
$locale['locale'],
$locale['domain'],
$locale['path'],
);
// legacy lang vars set
$this->encoding = $locale['encoding'];
$this->lang = $locale['lang'];
// get first part from lang
$this->lang_short = explode('_', $locale['lang'])[0];
$this->domain = $this->l->getDomain();
$this->lang_dir = $this->l->getBaseLocalePath();
// load config array
// get table array definitions for current page name
@@ -443,29 +459,6 @@ class Generate extends \CoreLibs\DB\Extended\ArrayIO
parent::__destruct();
}
// INTERNAL METHODS |===============================================>
/**
* ORIGINAL in \CoreLibs\Admin\Backend
* set the language encoding and language settings
* the default charset from _SESSION login or from
* config DEFAULT ENCODING
* the lang full name for mo loading from _SESSION login
* or SITE LANG or DEFAULT LANG from config
* creates short lang (only first two chars) from the lang
* @return void
*/
private function setLangEncoding(): void
{
list (
$this->encoding,
$this->lang,
$this->lang_short,
$this->domain,
$this->lang_dir
) = \CoreLibs\Language\GetSettings::setLangEncoding();
}
// PUBLIC METHODS |=================================================>
/**

View File

@@ -154,50 +154,36 @@ class SmartyExtend extends \Smarty
* constructor class, just sets the language stuff
* calls L10 for pass on internaly in smarty
* also registers the getvar caller plugin
* @param \CoreLibs\Language\L10n|null $l10n l10n language class
* if null, auto set
* @param \CoreLibs\Language\L10n $l10n l10n language class
* @param array<string,string> $locale locale data read from setLocale
*/
public function __construct(?\CoreLibs\Language\L10n $l10n = null)
public function __construct(\CoreLibs\Language\L10n $l10n, array $locale)
{
// call basic smarty
// or Smarty::__construct();
parent::__construct();
// set lang vars
$this->setLangEncoding();
// iinit lang
$this->l10n = $l10n ?? new \CoreLibs\Language\L10n($this->lang);
// Smarty 3.x
// $this->registerPlugin('modifier', 'getvar', [&$this, 'get_template_vars']);
$this->l10n = $l10n;
// opt load functions so we can use legacy init for smarty run perhaps
$this->l10n->loadFunctions();
// parse and read, legacy stuff
$this->encoding = $locale['encoding'];
$this->lang = $locale['lang'];
// get first part from lang
$this->lang_short = explode('_', $locale['lang'])[0];
$this->domain = $this->l10n->getDomain();
$this->lang_dir = $this->l10n->getBaseLocalePath();
// register smarty variable
$this->registerPlugin('modifier', 'getvar', [&$this, 'getTemplateVars']);
$this->page_name = pathinfo($_SERVER["PHP_SELF"])['basename'];
$this->page_name = \CoreLibs\Get\System::getPageName();
// set internal settings
$this->CACHE_ID = defined('CACHE_ID') ? CACHE_ID : '';
$this->COMPILE_ID = defined('COMPILE_ID') ? COMPILE_ID : '';
}
/**
* ORIGINAL in \CoreLibs\Admin\Backend
* set the language encoding and language settings
* the default charset from _SESSION login or from
* config DEFAULT ENCODING
* the lang full name for mo loading from _SESSION login
* or SITE LANG or DEFAULT LANG from config
* creates short lang (only first two chars) from the lang
* @return void
*/
private function setLangEncoding(): void
{
list (
$this->encoding,
$this->lang,
$this->lang_short,
$this->domain,
$this->lang_dir
) = \CoreLibs\Language\GetSettings::setLangEncoding();
}
/**
* @return void
*/