266 lines
7.7 KiB
PHP
Executable File
266 lines
7.7 KiB
PHP
Executable File
<?php
|
|
|
|
/**
|
|
* Glue to connect one or more translation/locale systems to the rest
|
|
*
|
|
* @author Hanne Moa, UNINETT AS. <hanne.moa@uninett.no>
|
|
* @package SimpleSAMLphp
|
|
*/
|
|
|
|
namespace SimpleSAML\Locale;
|
|
|
|
use Gettext\Translations;
|
|
use Gettext\Translator;
|
|
|
|
class Localization
|
|
{
|
|
/**
|
|
* The configuration to use.
|
|
*
|
|
* @var \SimpleSAML_Configuration
|
|
*/
|
|
private $configuration;
|
|
|
|
/**
|
|
* The default gettext domain.
|
|
*/
|
|
const DEFAULT_DOMAIN = 'messages';
|
|
|
|
/**
|
|
* Old internationalization backend included in SimpleSAMLphp.
|
|
*/
|
|
const SSP_I18N_BACKEND = 'SimpleSAMLphp';
|
|
|
|
/**
|
|
* An internationalization backend implemented purely in PHP.
|
|
*/
|
|
const GETTEXT_I18N_BACKEND = 'gettext/gettext';
|
|
|
|
/**
|
|
* The default locale directory
|
|
*/
|
|
private $localeDir;
|
|
|
|
/**
|
|
* Where specific domains are stored
|
|
*/
|
|
private $localeDomainMap = array();
|
|
|
|
/**
|
|
* Pointer to currently active translator
|
|
*/
|
|
private $translator;
|
|
|
|
/**
|
|
* Pointer to current Language
|
|
*/
|
|
private $language;
|
|
|
|
/**
|
|
* Language code representing the current Language
|
|
*/
|
|
private $langcode;
|
|
|
|
|
|
/**
|
|
* The language backend to use
|
|
*/
|
|
public $i18nBackend;
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param \SimpleSAML_Configuration $configuration Configuration object
|
|
*/
|
|
public function __construct(\SimpleSAML_Configuration $configuration)
|
|
{
|
|
$this->configuration = $configuration;
|
|
$this->localeDir = $this->configuration->resolvePath('locales');
|
|
$this->language = new Language($configuration);
|
|
$this->langcode = $this->language->getPosixLanguage($this->language->getLanguage());
|
|
$this->i18nBackend = $this->configuration->getString('language.i18n.backend', self::SSP_I18N_BACKEND);
|
|
$this->setupL10N();
|
|
}
|
|
|
|
|
|
/**
|
|
* Dump the default locale directory
|
|
*/
|
|
public function getLocaleDir()
|
|
{
|
|
return $this->localeDir;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the default locale dir for a specific module aka. domain
|
|
*
|
|
* @param string $domain Name of module/domain
|
|
*/
|
|
public function getDomainLocaleDir($domain)
|
|
{
|
|
$localeDir = $this->configuration->resolvePath('modules') . '/' . $domain . '/locales';
|
|
return $localeDir;
|
|
}
|
|
|
|
|
|
/*
|
|
* Add a new translation domain from a module
|
|
* (We're assuming that each domain only exists in one place)
|
|
*
|
|
* @param string $module Module name
|
|
* @param string $localeDir Absolute path if the module is housed elsewhere
|
|
*/
|
|
public function addModuleDomain($module, $localeDir = null)
|
|
{
|
|
if (!$localeDir) {
|
|
$localeDir = $this->getDomainLocaleDir($module);
|
|
}
|
|
$this->addDomain($localeDir, $module);
|
|
}
|
|
|
|
|
|
/*
|
|
* Add a new translation domain
|
|
* (We're assuming that each domain only exists in one place)
|
|
*
|
|
* @param string $localeDir Location of translations
|
|
* @param string $domain Domain at location
|
|
*/
|
|
public function addDomain($localeDir, $domain)
|
|
{
|
|
$this->localeDomainMap[$domain] = $localeDir;
|
|
\SimpleSAML\Logger::debug("Localization: load domain '$domain' at '$localeDir'");
|
|
$this->loadGettextGettextFromPO($domain);
|
|
}
|
|
|
|
/*
|
|
* Get and check path of localization file
|
|
*
|
|
* @param string $domain Name of localization domain
|
|
* @throws Exception If the path does not exist even for the default, fallback language
|
|
*/
|
|
public function getLangPath($domain = self::DEFAULT_DOMAIN)
|
|
{
|
|
$langcode = explode('_', $this->langcode);
|
|
$langcode = $langcode[0];
|
|
$localeDir = $this->localeDomainMap[$domain];
|
|
$langPath = $localeDir.'/'.$langcode.'/LC_MESSAGES/';
|
|
\SimpleSAML\Logger::debug("Trying langpath for '$langcode' as '$langPath'");
|
|
if (is_dir($langPath) && is_readable($langPath)) {
|
|
return $langPath;
|
|
}
|
|
|
|
// Some langcodes have aliases..
|
|
$alias = $this->language->getLanguageCodeAlias($langcode);
|
|
if (isset($alias)) {
|
|
$langPath = $localeDir.'/'.$alias.'/LC_MESSAGES/';
|
|
\SimpleSAML\Logger::debug("Trying langpath for alternative '$alias' as '$langPath'");
|
|
if (is_dir($langPath) && is_readable($langPath)) {
|
|
return $langPath;
|
|
}
|
|
}
|
|
|
|
// Language not found, fall back to default
|
|
$defLangcode = $this->language->getDefaultLanguage();
|
|
$langPath = $localeDir.'/'.$defLangcode.'/LC_MESSAGES/';
|
|
if (is_dir($langPath) && is_readable($langPath)) {
|
|
// Report that the localization for the preferred language is missing
|
|
$error = "Localization not found for langcode '$langcode' at '$langPath', falling back to langcode '".
|
|
$defLangcode."'";
|
|
\SimpleSAML\Logger::error($_SERVER['PHP_SELF'].' - '.$error);
|
|
return $langPath;
|
|
}
|
|
|
|
// Locale for default language missing even, error out
|
|
$error = "Localization directory missing/broken for langcode '$langcode' and domain '$domain'";
|
|
\SimpleSAML\Logger::critical($_SERVER['PHP_SELF'].' - '.$error);
|
|
throw new \Exception($error);
|
|
}
|
|
|
|
|
|
/**
|
|
* Setup the translator
|
|
*/
|
|
private function setupTranslator()
|
|
{
|
|
$this->translator = new Translator();
|
|
$this->translator->register();
|
|
}
|
|
|
|
|
|
/**
|
|
* Load translation domain from Gettext/Gettext using .po
|
|
*
|
|
* Note: Since Twig I18N does not support domains, all loaded files are
|
|
* merged. Use contexts if identical strings need to be disambiguated.
|
|
*
|
|
* @param string $domain Name of domain
|
|
* @param boolean $catchException Whether to catch an exception on error or return early
|
|
*
|
|
* @throws \Exception If something is wrong with the locale file for the domain and activated language
|
|
*/
|
|
private function loadGettextGettextFromPO($domain = self::DEFAULT_DOMAIN, $catchException = true)
|
|
{
|
|
try {
|
|
$langPath = $this->getLangPath($domain);
|
|
} catch (\Exception $e) {
|
|
$error = "Something went wrong when trying to get path to language file, cannot load domain '$domain'.";
|
|
\SimpleSAML\Logger::error($_SERVER['PHP_SELF'].' - '.$error);
|
|
if ($catchException) {
|
|
// bail out!
|
|
return;
|
|
} else {
|
|
throw $e;
|
|
}
|
|
}
|
|
$poFile = $domain.'.po';
|
|
$poPath = $langPath.$poFile;
|
|
if (file_exists($poPath) && is_readable($poPath)) {
|
|
$translations = Translations::fromPoFile($poPath);
|
|
$this->translator->loadTranslations($translations);
|
|
} else {
|
|
$error = "Localization file '$poFile' not found in '$langPath', falling back to default";
|
|
\SimpleSAML\Logger::error($_SERVER['PHP_SELF'].' - '.$error);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Test to check if backend is set to default
|
|
*
|
|
* (if false: backend unset/there's an error)
|
|
*/
|
|
public function isI18NBackendDefault()
|
|
{
|
|
if ($this->i18nBackend === $this::SSP_I18N_BACKEND) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/**
|
|
* Set up L18N if configured or fallback to old system
|
|
*/
|
|
private function setupL10N()
|
|
{
|
|
if ($this->i18nBackend === self::SSP_I18N_BACKEND) {
|
|
\SimpleSAML\Logger::debug("Localization: using old system");
|
|
return;
|
|
}
|
|
|
|
$this->setupTranslator();
|
|
// setup default domain
|
|
$this->addDomain($this->localeDir, self::DEFAULT_DOMAIN);
|
|
}
|
|
|
|
/**
|
|
* Show which domains are registered
|
|
*/
|
|
public function getRegisteredDomains()
|
|
{
|
|
return $this->localeDomainMap;
|
|
}
|
|
}
|