2016-08-07 11:52:49 +02:00
|
|
|
<?php
|
|
|
|
|
2017-05-09 18:12:15 +02:00
|
|
|
namespace Shaarli;
|
|
|
|
|
|
|
|
use Gettext\GettextTranslator;
|
|
|
|
use Gettext\Translations;
|
|
|
|
use Gettext\Translator;
|
|
|
|
use Gettext\TranslatorInterface;
|
|
|
|
use Shaarli\Config\ConfigManager;
|
|
|
|
|
2016-08-07 11:52:49 +02:00
|
|
|
/**
|
2017-05-09 18:12:15 +02:00
|
|
|
* Class Languages
|
|
|
|
*
|
|
|
|
* Load Shaarli translations using 'gettext/gettext'.
|
|
|
|
* This class allows to either use PHP gettext extension, or a PHP implementation of gettext,
|
|
|
|
* with a fixed language, or dynamically using autoLocale().
|
2016-08-07 11:52:49 +02:00
|
|
|
*
|
2017-05-09 18:12:15 +02:00
|
|
|
* Translation files PO/MO files follow gettext standard and must be placed under:
|
|
|
|
* <translation path>/<language>/LC_MESSAGES/<domain>.[po|mo]
|
2016-08-07 11:52:49 +02:00
|
|
|
*
|
2017-05-09 18:12:15 +02:00
|
|
|
* Pros/cons:
|
|
|
|
* - gettext extension is faster
|
|
|
|
* - gettext is very system dependent (PHP extension, the locale must be installed, and web server reloaded)
|
2016-08-07 11:52:49 +02:00
|
|
|
*
|
2017-05-09 18:12:15 +02:00
|
|
|
* Settings:
|
|
|
|
* - translation.mode:
|
|
|
|
* - auto: use default setting (PHP implementation)
|
|
|
|
* - php: use PHP implementation
|
|
|
|
* - gettext: use gettext wrapper
|
|
|
|
* - translation.language:
|
|
|
|
* - auto: use autoLocale() and the language change according to user HTTP headers
|
|
|
|
* - fixed language: e.g. 'fr'
|
|
|
|
* - translation.extensions:
|
|
|
|
* - domain => translation_path: allow plugins and themes to extend the defaut extension
|
|
|
|
* The domain must be unique, and translation path must be relative, and contains the tree mentioned above.
|
|
|
|
*
|
|
|
|
* @package Shaarli
|
2016-08-07 11:52:49 +02:00
|
|
|
*/
|
2017-05-09 18:12:15 +02:00
|
|
|
class Languages
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Core translations domain
|
|
|
|
*/
|
2020-11-08 15:02:45 +01:00
|
|
|
public const DEFAULT_DOMAIN = 'shaarli';
|
2017-05-09 18:12:15 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var TranslatorInterface
|
|
|
|
*/
|
|
|
|
protected $translator;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $language;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var ConfigManager
|
|
|
|
*/
|
|
|
|
protected $conf;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Languages constructor.
|
|
|
|
*
|
2017-05-25 13:26:05 +02:00
|
|
|
* @param string $language lang determined by autoLocale(), can be overridden.
|
2017-05-09 18:12:15 +02:00
|
|
|
* @param ConfigManager $conf instance.
|
|
|
|
*/
|
|
|
|
public function __construct($language, $conf)
|
|
|
|
{
|
|
|
|
$this->conf = $conf;
|
|
|
|
$confLanguage = $this->conf->get('translation.language', 'auto');
|
2018-01-31 12:39:17 +01:00
|
|
|
// Auto mode or invalid parameter, use the detected language.
|
|
|
|
// If the detected language is invalid, it doesn't matter, it will use English.
|
2017-05-09 18:12:15 +02:00
|
|
|
if ($confLanguage === 'auto' || ! $this->isValidLanguage($confLanguage)) {
|
|
|
|
$this->language = substr($language, 0, 5);
|
|
|
|
} else {
|
|
|
|
$this->language = $confLanguage;
|
|
|
|
}
|
|
|
|
|
2020-09-22 20:25:47 +02:00
|
|
|
if (
|
|
|
|
! extension_loaded('gettext')
|
2017-05-09 18:12:15 +02:00
|
|
|
|| in_array($this->conf->get('translation.mode', 'auto'), ['auto', 'php'])
|
|
|
|
) {
|
|
|
|
$this->initPhpTranslator();
|
|
|
|
} else {
|
|
|
|
$this->initGettextTranslator();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register default functions (e.g. '__()') to use our Translator
|
|
|
|
$this->translator->register();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the translator using php gettext extension (gettext dependency act as a wrapper).
|
|
|
|
*/
|
2018-10-13 00:19:03 +02:00
|
|
|
protected function initGettextTranslator()
|
2017-05-09 18:12:15 +02:00
|
|
|
{
|
|
|
|
$this->translator = new GettextTranslator();
|
|
|
|
$this->translator->setLanguage($this->language);
|
|
|
|
$this->translator->loadDomain(self::DEFAULT_DOMAIN, 'inc/languages');
|
|
|
|
|
2018-02-26 22:53:00 +01:00
|
|
|
// Default extension translation from the current theme
|
2020-09-22 20:25:47 +02:00
|
|
|
$themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') . '/' . $this->conf->get('theme') . '/language';
|
2018-02-26 22:53:00 +01:00
|
|
|
if (is_dir($themeTransFolder)) {
|
|
|
|
$this->translator->loadDomain($this->conf->get('theme'), $themeTransFolder, false);
|
|
|
|
}
|
|
|
|
|
2017-05-09 18:12:15 +02:00
|
|
|
foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
|
|
|
|
if ($domain !== self::DEFAULT_DOMAIN) {
|
|
|
|
$this->translator->loadDomain($domain, $translationPath, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the translator using a PHP implementation of gettext.
|
|
|
|
*
|
|
|
|
* Note that if language po file doesn't exist, errors are ignored (e.g. not installed language).
|
|
|
|
*/
|
|
|
|
protected function initPhpTranslator()
|
|
|
|
{
|
|
|
|
$this->translator = new Translator();
|
|
|
|
$translations = new Translations();
|
|
|
|
// Core translations
|
|
|
|
try {
|
2020-11-08 15:02:45 +01:00
|
|
|
$translations = $translations->addFromPoFile(
|
|
|
|
'inc/languages/' . $this->language . '/LC_MESSAGES/shaarli.po'
|
|
|
|
);
|
2017-05-09 18:12:15 +02:00
|
|
|
$translations->setDomain('shaarli');
|
|
|
|
$this->translator->loadTranslations($translations);
|
2018-10-13 00:19:03 +02:00
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
}
|
2017-05-09 18:12:15 +02:00
|
|
|
|
2018-02-26 22:53:00 +01:00
|
|
|
// Default extension translation from the current theme
|
|
|
|
$theme = $this->conf->get('theme');
|
2020-09-22 20:25:47 +02:00
|
|
|
$themeTransFolder = rtrim($this->conf->get('raintpl_tpl'), '/') . '/' . $theme . '/language';
|
2018-02-26 22:53:00 +01:00
|
|
|
if (is_dir($themeTransFolder)) {
|
|
|
|
try {
|
|
|
|
$translations = Translations::fromPoFile(
|
2020-09-22 20:25:47 +02:00
|
|
|
$themeTransFolder . '/' . $this->language . '/LC_MESSAGES/' . $theme . '.po'
|
2018-02-26 22:53:00 +01:00
|
|
|
);
|
|
|
|
$translations->setDomain($theme);
|
|
|
|
$this->translator->loadTranslations($translations);
|
2018-10-13 00:19:03 +02:00
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
}
|
2018-02-26 22:53:00 +01:00
|
|
|
}
|
2017-05-09 18:12:15 +02:00
|
|
|
|
|
|
|
// Extension translations (plugins, themes, etc.).
|
|
|
|
foreach ($this->conf->get('translation.extensions', []) as $domain => $translationPath) {
|
|
|
|
if ($domain === self::DEFAULT_DOMAIN) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2018-10-13 01:40:04 +02:00
|
|
|
$extension = Translations::fromPoFile(
|
2020-09-22 20:25:47 +02:00
|
|
|
$translationPath . $this->language . '/LC_MESSAGES/' . $domain . '.po'
|
2018-10-13 01:40:04 +02:00
|
|
|
);
|
2017-05-09 18:12:15 +02:00
|
|
|
$extension->setDomain($domain);
|
|
|
|
$this->translator->loadTranslations($extension);
|
2018-10-13 00:19:03 +02:00
|
|
|
} catch (\InvalidArgumentException $e) {
|
|
|
|
}
|
2017-05-09 18:12:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if a language string is valid.
|
|
|
|
*
|
|
|
|
* @param string $language e.g. 'fr' or 'en_US'
|
|
|
|
*
|
|
|
|
* @return bool true if valid, false otherwise
|
|
|
|
*/
|
|
|
|
protected function isValidLanguage($language)
|
|
|
|
{
|
|
|
|
return preg_match('/^[a-z]{2}(_[A-Z]{2})?/', $language) === 1;
|
2016-08-07 11:52:49 +02:00
|
|
|
}
|
2017-05-25 13:26:05 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the list of available languages for Shaarli.
|
|
|
|
*
|
|
|
|
* @return array List of available languages, with their label.
|
|
|
|
*/
|
|
|
|
public static function getAvailableLanguages()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
'auto' => t('Automatic'),
|
2020-08-27 15:00:33 +02:00
|
|
|
'de' => t('German'),
|
2017-05-25 13:26:05 +02:00
|
|
|
'en' => t('English'),
|
|
|
|
'fr' => t('French'),
|
2020-08-27 15:00:33 +02:00
|
|
|
'jp' => t('Japanese'),
|
2017-05-25 13:26:05 +02:00
|
|
|
];
|
|
|
|
}
|
2016-08-07 11:52:49 +02:00
|
|
|
}
|