runff 1.0 commit
This commit is contained in:
424
lib/SimpleSAML/Locale/Language.php
Executable file
424
lib/SimpleSAML/Locale/Language.php
Executable file
@@ -0,0 +1,424 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Choosing the language to localize to for our minimalistic XHTML PHP based template system.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @author Hanne Moa, UNINETT AS. <hanne.moa@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Locale;
|
||||
|
||||
use SimpleSAML\Utils\HTTP;
|
||||
|
||||
class Language
|
||||
{
|
||||
|
||||
/**
|
||||
* This is the default language map. It is used to map languages codes from the user agent to other language codes.
|
||||
*/
|
||||
private static $defaultLanguageMap = array('nb' => 'no');
|
||||
|
||||
/**
|
||||
* The configuration to use.
|
||||
*
|
||||
* @var \SimpleSAML_Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
/**
|
||||
* An array holding a list of languages available.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $availableLanguages;
|
||||
|
||||
/**
|
||||
* The language currently in use.
|
||||
*
|
||||
* @var null|string
|
||||
*/
|
||||
private $language = null;
|
||||
|
||||
/**
|
||||
* The language to use by default.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $defaultLanguage;
|
||||
|
||||
/**
|
||||
* An array holding a list of languages that are written from right to left.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $rtlLanguages;
|
||||
|
||||
/**
|
||||
* HTTP GET language parameter name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $languageParameterName;
|
||||
|
||||
/**
|
||||
* A custom function to use in order to determine the language in use.
|
||||
*
|
||||
* @var callable|null
|
||||
*/
|
||||
private $customFunction;
|
||||
|
||||
/**
|
||||
* A list of languages supported with their names localized.
|
||||
* Indexed by something that mostly resembles ISO 639-1 code,
|
||||
* with some charming SimpleSAML-specific variants...
|
||||
* that must remain before 2.0 due to backwards compatibility
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $language_names = array(
|
||||
'no' => 'Bokmål', // Norwegian Bokmål
|
||||
'nn' => 'Nynorsk', // Norwegian Nynorsk
|
||||
'se' => 'Sámegiella', // Northern Sami
|
||||
'sma' => 'Åarjelh-saemien giele', // Southern Sami
|
||||
'da' => 'Dansk', // Danish
|
||||
'en' => 'English',
|
||||
'de' => 'Deutsch', // German
|
||||
'sv' => 'Svenska', // Swedish
|
||||
'fi' => 'Suomeksi', // Finnish
|
||||
'es' => 'Español', // Spanish
|
||||
'ca' => 'Català', // Catalan
|
||||
'fr' => 'Français', // French
|
||||
'it' => 'Italiano', // Italian
|
||||
'nl' => 'Nederlands', // Dutch
|
||||
'lb' => 'Lëtzebuergesch', // Luxembourgish
|
||||
'cs' => 'Čeština', // Czech
|
||||
'sl' => 'Slovenščina', // Slovensk
|
||||
'lt' => 'Lietuvių kalba', // Lithuanian
|
||||
'hr' => 'Hrvatski', // Croatian
|
||||
'hu' => 'Magyar', // Hungarian
|
||||
'pl' => 'Język polski', // Polish
|
||||
'pt' => 'Português', // Portuguese
|
||||
'pt-br' => 'Português brasileiro', // Portuguese
|
||||
'ru' => 'русский язык', // Russian
|
||||
'et' => 'eesti keel', // Estonian
|
||||
'tr' => 'Türkçe', // Turkish
|
||||
'el' => 'ελληνικά', // Greek
|
||||
'ja' => '日本語', // Japanese
|
||||
'zh' => '简体中文', // Chinese (simplified)
|
||||
'zh-tw' => '繁體中文', // Chinese (traditional)
|
||||
'ar' => 'العربية', // Arabic
|
||||
'fa' => 'پارسی', // Persian
|
||||
'ur' => 'اردو', // Urdu
|
||||
'he' => 'עִבְרִית', // Hebrew
|
||||
'id' => 'Bahasa Indonesia', // Indonesian
|
||||
'sr' => 'Srpski', // Serbian
|
||||
'lv' => 'Latviešu', // Latvian
|
||||
'ro' => 'Românește', // Romanian
|
||||
'eu' => 'Euskara', // Basque
|
||||
'af' => 'Afrikaans', // Afrikaans
|
||||
);
|
||||
|
||||
/**
|
||||
* A mapping of SSP languages to locales
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $languagePosixMapping = array(
|
||||
'no' => 'nb_NO',
|
||||
'nn' => 'nn_NO',
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $configuration Configuration object
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $configuration)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->availableLanguages = $this->getInstalledLanguages();
|
||||
$this->defaultLanguage = $this->configuration->getString('language.default', 'en');
|
||||
$this->languageParameterName = $this->configuration->getString('language.parameter.name', 'language');
|
||||
$this->customFunction = $this->configuration->getArray('language.get_language_function', null);
|
||||
$this->rtlLanguages = $this->configuration->getArray('language.rtl', array());
|
||||
if (isset($_GET[$this->languageParameterName])) {
|
||||
$this->setLanguage(
|
||||
$_GET[$this->languageParameterName],
|
||||
$this->configuration->getBoolean('language.parameter.setcookie', true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter configured (available) languages against installed languages.
|
||||
*
|
||||
* @return array The set of languages both in 'language.available' and $this->language_names.
|
||||
*/
|
||||
private function getInstalledLanguages()
|
||||
{
|
||||
$configuredAvailableLanguages = $this->configuration->getArray('language.available', array('en'));
|
||||
$availableLanguages = array();
|
||||
foreach ($configuredAvailableLanguages as $code) {
|
||||
if (array_key_exists($code, $this->language_names) && isset($this->language_names[$code])) {
|
||||
$availableLanguages[] = $code;
|
||||
} else {
|
||||
\SimpleSAML\Logger::error("Language \"$code\" not installed. Check config.");
|
||||
}
|
||||
}
|
||||
return $availableLanguages;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rename to non-idiosyncratic language code.
|
||||
*
|
||||
* @param string $language Language code for the language to rename, if necessary.
|
||||
*
|
||||
* @return string The language code.
|
||||
*/
|
||||
public function getPosixLanguage($language)
|
||||
{
|
||||
if (isset($this->languagePosixMapping[$language])) {
|
||||
return $this->languagePosixMapping[$language];
|
||||
}
|
||||
return $language;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will set a cookie for the user's browser to remember what language was selected.
|
||||
*
|
||||
* @param string $language Language code for the language to set.
|
||||
* @param boolean $setLanguageCookie Whether to set the language cookie or not. Defaults to true.
|
||||
*/
|
||||
public function setLanguage($language, $setLanguageCookie = true)
|
||||
{
|
||||
$language = strtolower($language);
|
||||
if (in_array($language, $this->availableLanguages, true)) {
|
||||
$this->language = $language;
|
||||
if ($setLanguageCookie === true) {
|
||||
self::setLanguageCookie($language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will return the language selected by the user, or the default language. It looks first for a cached
|
||||
* language code, then checks for a language cookie, then it tries to calculate the preferred language from HTTP
|
||||
* headers.
|
||||
*
|
||||
* @return string The language selected by the user according to the processing rules specified, or the default
|
||||
* language in any other case.
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
// language is set in object
|
||||
if (isset($this->language)) {
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
// run custom getLanguage function if defined
|
||||
if (isset($this->customFunction) && is_callable($this->customFunction)) {
|
||||
$customLanguage = call_user_func($this->customFunction, $this);
|
||||
if ($customLanguage !== null && $customLanguage !== false) {
|
||||
return $customLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
// language is provided in a stored cookie
|
||||
$languageCookie = Language::getLanguageCookie();
|
||||
if ($languageCookie !== null) {
|
||||
$this->language = $languageCookie;
|
||||
return $languageCookie;
|
||||
}
|
||||
|
||||
// check if we can find a good language from the Accept-Language HTTP header
|
||||
$httpLanguage = $this->getHTTPLanguage();
|
||||
if ($httpLanguage !== null) {
|
||||
return $httpLanguage;
|
||||
}
|
||||
|
||||
// language is not set, and we get the default language from the configuration
|
||||
return $this->getDefaultLanguage();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the localized name of a language, by ISO 639-2 code.
|
||||
*
|
||||
* @param string $code The ISO 639-2 code of the language.
|
||||
*
|
||||
* @return string The localized name of the language.
|
||||
*/
|
||||
public function getLanguageLocalizedName($code)
|
||||
{
|
||||
if (array_key_exists($code, $this->language_names) && isset($this->language_names[$code])) {
|
||||
return $this->language_names[$code];
|
||||
}
|
||||
\SimpleSAML\Logger::error("Name for language \"$code\" not found. Check config.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the language parameter name.
|
||||
*
|
||||
* @return string The language parameter name.
|
||||
*/
|
||||
public function getLanguageParameterName()
|
||||
{
|
||||
return $this->languageParameterName;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method returns the preferred language for the user based on the Accept-Language HTTP header.
|
||||
*
|
||||
* @return string The preferred language based on the Accept-Language HTTP header, or null if none of the languages
|
||||
* in the header is available.
|
||||
*/
|
||||
private function getHTTPLanguage()
|
||||
{
|
||||
$languageScore = HTTP::getAcceptLanguage();
|
||||
|
||||
// for now we only use the default language map. We may use a configurable language map in the future
|
||||
$languageMap = self::$defaultLanguageMap;
|
||||
|
||||
// find the available language with the best score
|
||||
$bestLanguage = null;
|
||||
$bestScore = -1.0;
|
||||
|
||||
foreach ($languageScore as $language => $score) {
|
||||
// apply the language map to the language code
|
||||
if (array_key_exists($language, $languageMap)) {
|
||||
$language = $languageMap[$language];
|
||||
}
|
||||
|
||||
if (!in_array($language, $this->availableLanguages, true)) {
|
||||
// skip this language - we don't have it
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Some user agents use very limited precision of the quality value, but order the elements in descending
|
||||
* order. Therefore we rely on the order of the output from getAcceptLanguage() matching the order of the
|
||||
* languages in the header when two languages have the same quality.
|
||||
*/
|
||||
if ($score > $bestScore) {
|
||||
$bestLanguage = $language;
|
||||
$bestScore = $score;
|
||||
}
|
||||
}
|
||||
|
||||
return $bestLanguage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the default language according to configuration.
|
||||
*
|
||||
* @return string The default language that has been configured. Defaults to english if not configured.
|
||||
*/
|
||||
public function getDefaultLanguage()
|
||||
{
|
||||
return $this->defaultLanguage;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an alias for a language code, if any.
|
||||
*
|
||||
* @return string The alias, or null if the alias was not found.
|
||||
*/
|
||||
public function getLanguageCodeAlias($langcode)
|
||||
{
|
||||
if (isset(self::$defaultLanguageMap[$langcode])) {
|
||||
return self::$defaultLanguageMap[$langcode];
|
||||
}
|
||||
// No alias found, which is fine
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an indexed list of all languages available.
|
||||
*
|
||||
* @return array An array holding all the languages available as the keys of the array. The value for each key is
|
||||
* true in case that the language specified by that key is currently active, or false otherwise.
|
||||
*/
|
||||
public function getLanguageList()
|
||||
{
|
||||
$current = $this->getLanguage();
|
||||
$list = array_fill_keys($this->availableLanguages, false);
|
||||
$list[$current] = true;
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check whether a language is written from the right to the left or not.
|
||||
*
|
||||
* @return boolean True if the language is right-to-left, false otherwise.
|
||||
*/
|
||||
public function isLanguageRTL()
|
||||
{
|
||||
return in_array($this->getLanguage(), $this->rtlLanguages, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the user-selected language from a cookie.
|
||||
*
|
||||
* @return string|null The selected language or null if unset.
|
||||
*/
|
||||
public static function getLanguageCookie()
|
||||
{
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$availableLanguages = $config->getArray('language.available', array('en'));
|
||||
$name = $config->getString('language.cookie.name', 'language');
|
||||
|
||||
if (isset($_COOKIE[$name])) {
|
||||
$language = strtolower((string) $_COOKIE[$name]);
|
||||
if (in_array($language, $availableLanguages, true)) {
|
||||
return $language;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method will attempt to set the user-selected language in a cookie. It will do nothing if the language
|
||||
* specified is not in the list of available languages, or the headers have already been sent to the browser.
|
||||
*
|
||||
* @param string $language The language set by the user.
|
||||
*/
|
||||
public static function setLanguageCookie($language)
|
||||
{
|
||||
assert(is_string($language));
|
||||
|
||||
$language = strtolower($language);
|
||||
$config = \SimpleSAML_Configuration::getInstance();
|
||||
$availableLanguages = $config->getArray('language.available', array('en'));
|
||||
|
||||
if (!in_array($language, $availableLanguages, true) || headers_sent()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $config->getString('language.cookie.name', 'language');
|
||||
$params = array(
|
||||
'lifetime' => ($config->getInteger('language.cookie.lifetime', 60 * 60 * 24 * 900)),
|
||||
'domain' => ($config->getString('language.cookie.domain', null)),
|
||||
'path' => ($config->getString('language.cookie.path', '/')),
|
||||
'secure' => ($config->getBoolean('language.cookie.secure', false)),
|
||||
'httponly' => ($config->getBoolean('language.cookie.httponly', false)),
|
||||
);
|
||||
|
||||
HTTP::setCookie($name, $language, $params, false);
|
||||
}
|
||||
}
|
||||
265
lib/SimpleSAML/Locale/Localization.php
Executable file
265
lib/SimpleSAML/Locale/Localization.php
Executable file
@@ -0,0 +1,265 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
546
lib/SimpleSAML/Locale/Translate.php
Executable file
546
lib/SimpleSAML/Locale/Translate.php
Executable file
@@ -0,0 +1,546 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* The translation-relevant bits from our original minimalistic XHTML PHP based template system.
|
||||
*
|
||||
* @author Andreas Åkre Solberg, UNINETT AS. <andreas.solberg@uninett.no>
|
||||
* @author Hanne Moa, UNINETT AS. <hanne.moa@uninett.no>
|
||||
* @package SimpleSAMLphp
|
||||
*/
|
||||
|
||||
namespace SimpleSAML\Locale;
|
||||
|
||||
class Translate
|
||||
{
|
||||
|
||||
/**
|
||||
* The configuration to be used for this translator.
|
||||
*
|
||||
* @var \SimpleSAML_Configuration
|
||||
*/
|
||||
private $configuration;
|
||||
|
||||
private $langtext = array();
|
||||
|
||||
/**
|
||||
* Associative array of dictionaries.
|
||||
*/
|
||||
private $dictionaries = array();
|
||||
|
||||
/**
|
||||
* The default dictionary.
|
||||
*/
|
||||
private $defaultDictionary = null;
|
||||
|
||||
/**
|
||||
* The language object we'll use internally.
|
||||
*
|
||||
* @var \SimpleSAML\Locale\Language
|
||||
*/
|
||||
private $language;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \SimpleSAML_Configuration $configuration Configuration object
|
||||
* @param string|null $defaultDictionary The default dictionary where tags will come from.
|
||||
*/
|
||||
public function __construct(\SimpleSAML_Configuration $configuration, $defaultDictionary = null)
|
||||
{
|
||||
$this->configuration = $configuration;
|
||||
$this->language = new Language($configuration);
|
||||
|
||||
if ($defaultDictionary !== null && substr($defaultDictionary, -4) === '.php') {
|
||||
// TODO: drop this entire if clause for 2.0
|
||||
// for backwards compatibility - print warning
|
||||
$backtrace = debug_backtrace();
|
||||
$where = $backtrace[0]['file'].':'.$backtrace[0]['line'];
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Deprecated use of new SimpleSAML\Locale\Translate(...) at '.$where.
|
||||
'. The last parameter is now a dictionary name, which should not end in ".php".'
|
||||
);
|
||||
|
||||
$this->defaultDictionary = substr($defaultDictionary, 0, -4);
|
||||
} else {
|
||||
$this->defaultDictionary = $defaultDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the internal language object used by this translator.
|
||||
*
|
||||
* @return \SimpleSAML\Locale\Language
|
||||
*/
|
||||
public function getLanguage()
|
||||
{
|
||||
return $this->language;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method retrieves a dictionary with the name given.
|
||||
*
|
||||
* @param string $name The name of the dictionary, as the filename in the dictionary directory, without the
|
||||
* '.php' ending.
|
||||
*
|
||||
* @return array An associative array with the dictionary.
|
||||
*/
|
||||
private function getDictionary($name)
|
||||
{
|
||||
assert(is_string($name));
|
||||
|
||||
if (!array_key_exists($name, $this->dictionaries)) {
|
||||
$sepPos = strpos($name, ':');
|
||||
if ($sepPos !== false) {
|
||||
$module = substr($name, 0, $sepPos);
|
||||
$fileName = substr($name, $sepPos + 1);
|
||||
$dictDir = \SimpleSAML\Module::getModuleDir($module).'/dictionaries/';
|
||||
} else {
|
||||
$dictDir = $this->configuration->getPathValue('dictionarydir', 'dictionaries/');
|
||||
$fileName = $name;
|
||||
}
|
||||
|
||||
$this->dictionaries[$name] = $this->readDictionaryFile($dictDir.$fileName);
|
||||
}
|
||||
|
||||
return $this->dictionaries[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This method retrieves a tag as an array with language => string mappings.
|
||||
*
|
||||
* @param string $tag The tag name. The tag name can also be on the form '{<dictionary>:<tag>}', to retrieve a tag
|
||||
* from the specific dictionary.
|
||||
*
|
||||
* @return array An associative array with language => string mappings, or null if the tag wasn't found.
|
||||
*/
|
||||
public function getTag($tag)
|
||||
{
|
||||
assert(is_string($tag));
|
||||
|
||||
// first check translations loaded by the includeInlineTranslation and includeLanguageFile methods
|
||||
if (array_key_exists($tag, $this->langtext)) {
|
||||
return $this->langtext[$tag];
|
||||
}
|
||||
|
||||
// check whether we should use the default dictionary or a dictionary specified in the tag
|
||||
if (substr($tag, 0, 1) === '{' && preg_match('/^{((?:\w+:)?\w+?):(.*)}$/D', $tag, $matches)) {
|
||||
$dictionary = $matches[1];
|
||||
$tag = $matches[2];
|
||||
} else {
|
||||
$dictionary = $this->defaultDictionary;
|
||||
if ($dictionary === null) {
|
||||
// we don't have any dictionary to load the tag from
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$dictionary = $this->getDictionary($dictionary);
|
||||
if (!array_key_exists($tag, $dictionary)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $dictionary[$tag];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve the preferred translation of a given text.
|
||||
*
|
||||
* @param array $translations The translations, as an associative array with language => text mappings.
|
||||
*
|
||||
* @return string The preferred translation.
|
||||
*
|
||||
* @throws \Exception If there's no suitable translation.
|
||||
*/
|
||||
public function getPreferredTranslation($translations)
|
||||
{
|
||||
assert(is_array($translations));
|
||||
|
||||
// look up translation of tag in the selected language
|
||||
$selected_language = $this->language->getLanguage();
|
||||
if (array_key_exists($selected_language, $translations)) {
|
||||
return $translations[$selected_language];
|
||||
}
|
||||
|
||||
// look up translation of tag in the default language
|
||||
$default_language = $this->language->getDefaultLanguage();
|
||||
if (array_key_exists($default_language, $translations)) {
|
||||
return $translations[$default_language];
|
||||
}
|
||||
|
||||
// check for english translation
|
||||
if (array_key_exists('en', $translations)) {
|
||||
return $translations['en'];
|
||||
}
|
||||
|
||||
// pick the first translation available
|
||||
if (count($translations) > 0) {
|
||||
$languages = array_keys($translations);
|
||||
return $translations[$languages[0]];
|
||||
}
|
||||
|
||||
// we don't have anything to return
|
||||
throw new \Exception('Nothing to return from translation.');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate the name of an attribute.
|
||||
*
|
||||
* @param string $name The attribute name.
|
||||
*
|
||||
* @return string The translated attribute name, or the original attribute name if no translation was found.
|
||||
*/
|
||||
public function getAttributeTranslation($name)
|
||||
{
|
||||
// normalize attribute name
|
||||
$normName = strtolower($name);
|
||||
$normName = str_replace(":", "_", $normName);
|
||||
|
||||
// check for an extra dictionary
|
||||
$extraDict = $this->configuration->getString('attributes.extradictionary', null);
|
||||
if ($extraDict !== null) {
|
||||
$dict = $this->getDictionary($extraDict);
|
||||
if (array_key_exists($normName, $dict)) {
|
||||
return $this->getPreferredTranslation($dict[$normName]);
|
||||
}
|
||||
}
|
||||
|
||||
// search the default attribute dictionary
|
||||
$dict = $this->getDictionary('attributes');
|
||||
if (array_key_exists('attribute_'.$normName, $dict)) {
|
||||
return $this->getPreferredTranslation($dict['attribute_'.$normName]);
|
||||
}
|
||||
|
||||
// no translations found
|
||||
return $name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark a string for translation without translating it.
|
||||
*
|
||||
* @param string $tag A tag name to mark for translation.
|
||||
*
|
||||
* @return string The tag, unchanged.
|
||||
*/
|
||||
public static function noop($tag)
|
||||
{
|
||||
return $tag;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate a tag into the current language, with a fallback to english.
|
||||
*
|
||||
* This function is used to look up a translation tag in dictionaries, and return the translation into the current
|
||||
* language. If no translation into the current language can be found, english will be tried, and if that fails,
|
||||
* placeholder text will be returned.
|
||||
*
|
||||
* An array can be passed as the tag. In that case, the array will be assumed to be on the form (language => text),
|
||||
* and will be used as the source of translations.
|
||||
*
|
||||
* This function can also do replacements into the translated tag. It will search the translated tag for the keys
|
||||
* provided in $replacements, and replace any found occurrences with the value of the key.
|
||||
*
|
||||
* @param string|array $tag A tag name for the translation which should be looked up, or an array with
|
||||
* (language => text) mappings. The array version will go away in 2.0
|
||||
* @param array $replacements An associative array of keys that should be replaced with values in the
|
||||
* translated string.
|
||||
* @param boolean $fallbackdefault Default translation to use as a fallback if no valid translation was found.
|
||||
* @deprecated Not used in twig, gettext
|
||||
*
|
||||
* @return string The translated tag, or a placeholder value if the tag wasn't found.
|
||||
*/
|
||||
public function t(
|
||||
$tag,
|
||||
$replacements = array(),
|
||||
// TODO: remove this for 2.0. Assume true
|
||||
$fallbackdefault = true,
|
||||
// TODO: remove this for 2.0
|
||||
$oldreplacements = array(),
|
||||
// TODO: remove this for 2.0
|
||||
$striptags = false
|
||||
) {
|
||||
$backtrace = debug_backtrace();
|
||||
$where = $backtrace[0]['file'].':'.$backtrace[0]['line'];
|
||||
if (!$fallbackdefault) {
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Deprecated use of new SimpleSAML\Locale\Translate::t(...) at '.$where.
|
||||
'. This parameter will go away, the fallback will become' .
|
||||
' identical to the $tag in 2.0.'
|
||||
);
|
||||
}
|
||||
if (!is_array($replacements)) {
|
||||
// TODO: remove this entire if for 2.0
|
||||
|
||||
// old style call to t(...). Print warning to log
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Deprecated use of SimpleSAML\Locale\Translate::t(...) at '.$where.
|
||||
'. Please update the code to use the new style of parameters.'
|
||||
);
|
||||
|
||||
// for backwards compatibility
|
||||
if (!$replacements && $this->getTag($tag) === null) {
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Code which uses $fallbackdefault === FALSE should be updated to use the getTag() method instead.'
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
$replacements = $oldreplacements;
|
||||
}
|
||||
|
||||
if (is_array($tag)) {
|
||||
$tagData = $tag;
|
||||
\SimpleSAML\Logger::warning(
|
||||
'Deprecated use of new SimpleSAML\Locale\Translate::t(...) at '.$where.
|
||||
'. The $tag-parameter can only be a string in 2.0.'
|
||||
);
|
||||
} else {
|
||||
$tagData = $this->getTag($tag);
|
||||
if ($tagData === null) {
|
||||
// tag not found
|
||||
\SimpleSAML\Logger::info('Template: Looking up ['.$tag.']: not translated at all.');
|
||||
return $this->getStringNotTranslated($tag, $fallbackdefault);
|
||||
}
|
||||
}
|
||||
|
||||
$translated = $this->getPreferredTranslation($tagData);
|
||||
|
||||
foreach ($replacements as $k => $v) {
|
||||
// try to translate if no replacement is given
|
||||
if ($v == null) {
|
||||
$v = $this->t($k);
|
||||
}
|
||||
$translated = str_replace($k, $v, $translated);
|
||||
}
|
||||
return $translated;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the string that should be used when no translation was found.
|
||||
*
|
||||
* @param string $tag A name tag of the string that should be returned.
|
||||
* @param boolean $fallbacktag If set to true and string was not found in any languages, return the tag itself. If
|
||||
* false return null.
|
||||
*
|
||||
* @return string The string that should be used, or the tag name if $fallbacktag is set to false.
|
||||
*/
|
||||
private function getStringNotTranslated($tag, $fallbacktag)
|
||||
{
|
||||
if ($fallbacktag) {
|
||||
return 'not translated ('.$tag.')';
|
||||
} else {
|
||||
return $tag;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Include a translation inline instead of putting translations in dictionaries. This function is recommended to be
|
||||
* used ONLU from variable data, or when the translation is already provided by an external source, as a database
|
||||
* or in metadata.
|
||||
*
|
||||
* @param string $tag The tag that has a translation
|
||||
* @param array|string $translation The translation array
|
||||
*
|
||||
* @throws \Exception If $translation is neither a string nor an array.
|
||||
*/
|
||||
public function includeInlineTranslation($tag, $translation)
|
||||
{
|
||||
if (is_string($translation)) {
|
||||
$translation = array('en' => $translation);
|
||||
} elseif (!is_array($translation)) {
|
||||
throw new \Exception("Inline translation should be string or array. Is ".gettype($translation)." now!");
|
||||
}
|
||||
|
||||
\SimpleSAML\Logger::debug('Template: Adding inline language translation for tag ['.$tag.']');
|
||||
$this->langtext[$tag] = $translation;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Include a language file from the dictionaries directory.
|
||||
*
|
||||
* @param string $file File name of dictionary to include
|
||||
* @param \SimpleSAML_Configuration|null $otherConfig Optionally provide a different configuration object than the
|
||||
* one provided in the constructor to be used to find the directory of the dictionary. This allows to combine
|
||||
* dictionaries inside the SimpleSAMLphp main code distribution together with external dictionaries. Defaults to
|
||||
* null.
|
||||
*/
|
||||
public function includeLanguageFile($file, $otherConfig = null)
|
||||
{
|
||||
if (!empty($otherConfig)) {
|
||||
$filebase = $otherConfig->getPathValue('dictionarydir', 'dictionaries/');
|
||||
} else {
|
||||
$filebase = $this->configuration->getPathValue('dictionarydir', 'dictionaries/');
|
||||
}
|
||||
|
||||
$lang = $this->readDictionaryFile($filebase.$file);
|
||||
\SimpleSAML\Logger::debug('Template: Merging language array. Loading ['.$file.']');
|
||||
$this->langtext = array_merge($this->langtext, $lang);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a dictionary file in JSON format.
|
||||
*
|
||||
* @param string $filename The absolute path to the dictionary file, minus the .definition.json ending.
|
||||
*
|
||||
* @return array An array holding all the translations in the file.
|
||||
*/
|
||||
private function readDictionaryJSON($filename)
|
||||
{
|
||||
$definitionFile = $filename.'.definition.json';
|
||||
assert(file_exists($definitionFile));
|
||||
|
||||
$fileContent = file_get_contents($definitionFile);
|
||||
$lang = json_decode($fileContent, true);
|
||||
|
||||
if (empty($lang)) {
|
||||
\SimpleSAML\Logger::error('Invalid dictionary definition file ['.$definitionFile.']');
|
||||
return array();
|
||||
}
|
||||
|
||||
$translationFile = $filename.'.translation.json';
|
||||
if (file_exists($translationFile)) {
|
||||
$fileContent = file_get_contents($translationFile);
|
||||
$moreTrans = json_decode($fileContent, true);
|
||||
if (!empty($moreTrans)) {
|
||||
$lang = array_merge_recursive($lang, $moreTrans);
|
||||
}
|
||||
}
|
||||
|
||||
return $lang;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a dictionary file in PHP format.
|
||||
*
|
||||
* @param string $filename The absolute path to the dictionary file.
|
||||
*
|
||||
* @return array An array holding all the translations in the file.
|
||||
*/
|
||||
private function readDictionaryPHP($filename)
|
||||
{
|
||||
$phpFile = $filename.'.php';
|
||||
assert(file_exists($phpFile));
|
||||
|
||||
$lang = null;
|
||||
include($phpFile);
|
||||
if (isset($lang)) {
|
||||
return $lang;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Read a dictionary file.
|
||||
*
|
||||
* @param string $filename The absolute path to the dictionary file.
|
||||
*
|
||||
* @return array An array holding all the translations in the file.
|
||||
*/
|
||||
private function readDictionaryFile($filename)
|
||||
{
|
||||
assert(is_string($filename));
|
||||
|
||||
\SimpleSAML\Logger::debug('Template: Reading ['.$filename.']');
|
||||
|
||||
$jsonFile = $filename.'.definition.json';
|
||||
if (file_exists($jsonFile)) {
|
||||
return $this->readDictionaryJSON($filename);
|
||||
}
|
||||
|
||||
$phpFile = $filename.'.php';
|
||||
if (file_exists($phpFile)) {
|
||||
return $this->readDictionaryPHP($filename);
|
||||
}
|
||||
|
||||
\SimpleSAML\Logger::error(
|
||||
$_SERVER['PHP_SELF'].' - Template: Could not find dictionary file at ['.$filename.']'
|
||||
);
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
public static function translateSingularGettext($original)
|
||||
{
|
||||
$text = \Gettext\BaseTranslator::$current->gettext($original);
|
||||
|
||||
if (func_num_args() === 1) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$args = array_slice(func_get_args(), 1);
|
||||
|
||||
return strtr($text, is_array($args[0]) ? $args[0] : $args);
|
||||
}
|
||||
|
||||
|
||||
public static function translatePluralGettext($original, $plural, $value)
|
||||
{
|
||||
$text = \Gettext\BaseTranslator::$current->ngettext($original, $plural, $value);
|
||||
|
||||
if (func_num_args() === 3) {
|
||||
return $text;
|
||||
}
|
||||
|
||||
$args = array_slice(func_get_args(), 3);
|
||||
|
||||
return strtr($text, is_array($args[0]) ? $args[0] : $args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Pick a translation from a given array of translations for the current language.
|
||||
*
|
||||
* @param array $context An array of options. The current language must be specified as an ISO 639 code accessible
|
||||
* with the key "currentLanguage" in the array.
|
||||
* @param array $translations An array of translations. Each translation has an ISO 639 code as its key, identifying
|
||||
* the language it corresponds to.
|
||||
*
|
||||
* @return null|string The translation appropriate for the current language, or null if none found. If the
|
||||
* $context or $translations arrays are null, or $context['currentLanguage'] is not defined, null is also returned.
|
||||
*/
|
||||
public static function translateFromArray($context, $translations)
|
||||
{
|
||||
if (!is_array($translations) || $translations === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is_array($context) || !isset($context['currentLanguage'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($translations[$context['currentLanguage']])) {
|
||||
return $translations[$context['currentLanguage']];
|
||||
}
|
||||
|
||||
// we don't have a translation for the current language, load alternative priorities
|
||||
$sspcfg = \SimpleSAML_Configuration::getInstance();
|
||||
$langcfg = $sspcfg->getConfigItem('language', null);
|
||||
$priorities = array();
|
||||
if ($langcfg instanceof \SimpleSAML_Configuration) {
|
||||
$priorities = $langcfg->getArray('priorities', array());
|
||||
}
|
||||
|
||||
foreach ($priorities[$context['currentLanguage']] as $lang) {
|
||||
if (isset($translations[$lang])) {
|
||||
return $translations[$lang];
|
||||
}
|
||||
}
|
||||
|
||||
// nothing we can use, return null so that we can set a default
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user