diff --git a/library/Icinga/Application/ApplicationBootstrap.php b/library/Icinga/Application/ApplicationBootstrap.php index c404cd222..8ee4151bb 100755 --- a/library/Icinga/Application/ApplicationBootstrap.php +++ b/library/Icinga/Application/ApplicationBootstrap.php @@ -31,13 +31,11 @@ namespace Icinga\Application; use \DateTimeZone; use \Exception; -use \Zend_Loader_Autoloader; use \Icinga\Application\Modules\Manager as ModuleManager; -use \Icinga\Application\Platform; use \Icinga\Application\Config; -use \Icinga\Exception\ProgrammingError; use \Icinga\Exception\ConfigurationError; use \Icinga\Util\DateTimeFactory; +use \Icinga\Util\Translator; use Icinga\Data\ResourceFactory; @@ -427,4 +425,23 @@ abstract class ApplicationBootstrap DateTimeFactory::setConfig(array('timezone' => $tz)); return $this; } + + /** + * Setup internationalization using gettext + * + * Uses the language defined in the global config or the default one + * + * @return self + */ + protected function setupInternationalization() + { + Translator::setupLocale($this->config->global->get('language', 'en_US')); + + $localeDir = $this->getApplicationDir('locale'); + if (file_exists($localeDir) && is_dir($localeDir)) { + Translator::registerDomain('icinga', $localeDir); + } + + return $this; + } } diff --git a/library/Icinga/Application/Cli.php b/library/Icinga/Application/Cli.php index d73980675..6c8f64afa 100644 --- a/library/Icinga/Application/Cli.php +++ b/library/Icinga/Application/Cli.php @@ -31,7 +31,6 @@ namespace Icinga\Application; use Icinga\Application\Platform; use Icinga\Application\ApplicationBootstrap; -use Icinga\Application\Modules\Manager as ModuleManager; use Icinga\Cli\Params; use Icinga\Cli\Loader; use Icinga\Cli\Screen; @@ -63,12 +62,12 @@ class Cli extends ApplicationBootstrap { $this->assertRunningOnCli(); $this->setupConfig() - ->parseBasicParams() - ->fixLoggingConfig() - ->setupErrorHandling() - ->setupResourceFactory() - ->setupModuleManager() - ; + ->setupInternationalization() + ->parseBasicParams() + ->fixLoggingConfig() + ->setupErrorHandling() + ->setupResourceFactory() + ->setupModuleManager(); } protected function fixLoggingConfig() diff --git a/library/Icinga/Application/Modules/Module.php b/library/Icinga/Application/Modules/Module.php index b7a2ccd33..d1c435d52 100644 --- a/library/Icinga/Application/Modules/Module.php +++ b/library/Icinga/Application/Modules/Module.php @@ -35,6 +35,7 @@ use Icinga\Application\ApplicationBootstrap; use Icinga\Application\Config; use Icinga\Application\Icinga; use Icinga\Application\Logger; +use Icinga\Util\Translator; use Icinga\Web\Hook; /** @@ -422,7 +423,7 @@ class Module protected function registerLocales() { if (file_exists($this->localedir) && is_dir($this->localedir)) { - bindtextdomain($this->name, $this->localedir); + Translator::registerDomain($this->name, $this->localedir); } return $this; } diff --git a/library/Icinga/Application/Web.php b/library/Icinga/Application/Web.php index ad4c58d3c..0714fee64 100644 --- a/library/Icinga/Application/Web.php +++ b/library/Icinga/Application/Web.php @@ -50,6 +50,7 @@ use Icinga\User\Preferences\SessionStore; use Icinga\Util\DateTimeFactory; use Icinga\Session\Session as BaseSession; use Icinga\Web\Session; +use Icinga\Util\Translator; /** * Use this if you want to make use of Icinga functionality in other web projects @@ -116,10 +117,10 @@ class Web extends ApplicationBootstrap ->setupResourceFactory() ->setupSession() ->setupUser() + ->setupInternationalization() ->setupTimezone() ->setupRequest() ->setupZendMvc() - ->setupTranslation() ->setupModuleManager() ->loadEnabledModules() ->setupRoute() @@ -168,25 +169,6 @@ class Web extends ApplicationBootstrap return $this->viewRenderer; } - /** - * Load translations - * - * @return self - */ - private function setupTranslation() - { - // AuthManager::getInstance()->getSession()->language; - $locale = null; - if (!$locale) { - $locale = 'en_US'; - } - putenv('LC_ALL=' . $locale . '.UTF-8'); - setlocale(LC_ALL, $locale . '.UTF-8'); - bindtextdomain('icinga', $this->getApplicationDir() . '/locale'); - textdomain('icinga'); - return $this; - } - /** * Dispatch public interface */ @@ -449,4 +431,30 @@ class Web extends ApplicationBootstrap DateTimeFactory::setConfig(array('timezone' => $tz)); return $this; } + + /** + * Setup internationalization using gettext + * + * Uses the preferred user language or the configured default and system default, respectively. + * + * @return self + */ + protected function setupInternationalization() + { + parent::setupInternationalization(); + $userLocale = $this->user === null ? null : $this->user->getPreferences()->get('app.language'); + + if ($userLocale) { + try { + Translator::setupLocale($userLocale); + } catch (Exception $error) { + Logger::error( + 'Cannot set locale "' . $userLocale . '" configured in ' . + 'preferences of user "' . $this->user->getUsername() . '"' + ); + } + } + + return $this; + } } diff --git a/library/Icinga/Application/functions.php b/library/Icinga/Application/functions.php index 77b401746..b600028d6 100755 --- a/library/Icinga/Application/functions.php +++ b/library/Icinga/Application/functions.php @@ -27,31 +27,25 @@ */ // {{{ICINGA_LICENSE_HEADER}}} -if (function_exists('_')) { - function t($messageId = null) +use \Icinga\Util\Translator; + +if (extension_loaded('gettext')) { + function t($messageId) { - $msg = _($messageId); - if (! $msg) { - return $messageId; - } - return $msg; + return Translator::translate($messageId, 'icinga'); } - function mt($domain, $messageId = null) + function mt($domain, $messageId) { - $msg = dgettext($domain, $messageId); - if (! $msg) { - return $messageId; - } - return $msg; + return Translator::translate($messageId, $domain); } } else { - function t($messageId = null) + function t($messageId) { return $messageId; } - function mt($domain, $messageId = null) + function mt($domain, $messageId) { return $messageId; } diff --git a/library/Icinga/Cli/Command.php b/library/Icinga/Cli/Command.php index 6e68e6aad..6a977760e 100644 --- a/library/Icinga/Cli/Command.php +++ b/library/Icinga/Cli/Command.php @@ -2,8 +2,8 @@ namespace Icinga\Cli; -use Icinga\Cli\Loader; use Icinga\Cli\Screen; +use Icinga\Util\Translator; use Icinga\Application\ApplicationBootstrap as App; use Exception; @@ -48,6 +48,21 @@ abstract class Command return $this->trace; } + /** + * Translate a string + * + * Autoselects the module domain, if any, and falls back to the global one if no translation could be found. + * + * @param string $text The string to translate + * + * @return string The translated string + */ + public function translate($text) + { + $domain = $this->moduleName === null ? 'icinga' : $this->moduleName; + return Translator::translate($text, $domain); + } + public function fail($msg) { throw new Exception($msg); diff --git a/library/Icinga/Util/Translator.php b/library/Icinga/Util/Translator.php new file mode 100644 index 000000000..c38a3a0b4 --- /dev/null +++ b/library/Icinga/Util/Translator.php @@ -0,0 +1,108 @@ + + * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 + * @author Icinga Development Team + * + */ +// {{{ICINGA_LICENSE_HEADER}}} + +namespace Icinga\Util; + +use \Exception; + +/** + * Helper class to ease internationalization when using gettext + */ +class Translator +{ + /** + * The default gettext domain used as fallback + */ + const DEFAULT_DOMAIN = 'icinga'; + + /** + * Known gettext domains + * + * @var array + */ + private static $knownDomains = array(); + + /** + * Translate a string + * + * Falls back to the default domain in case the string cannot be translated using the given domain + * + * @param string $text The string to translate + * @param string $domain The primary domain to use + * + * @return string The translated string + * + * @throws Exception In case the given domain is unknown + */ + public static function translate($text, $domain) + { + if ($domain !== self::DEFAULT_DOMAIN && !in_array($domain, self::$knownDomains)) { + throw new Exception("Cannot translate string '$text' with unknown domain '$domain'"); + } + + $res = dgettext($domain, $text); + if ($res === $text && $domain !== self::DEFAULT_DOMAIN) { + return dgettext(self::DEFAULT_DOMAIN, $text); + } + return $res; + } + + /** + * Register a new gettext domain + * + * @param string $name The name of the domain to register + * @param string $directory The directory where message catalogs can be found + * + * @throws Exception In case the domain was not successfully registered + */ + public static function registerDomain($name, $directory) + { + if (bindtextdomain($name, $directory) === false) { + throw new Exception("Cannot register domain '$name' with path '$directory'"); + } + bind_textdomain_codeset($name, 'UTF-8'); + self::$knownDomains[] = $name; + } + + /** + * Set the locale to use + * + * @param string $localeName The name of the locale to use + * + * @throws Exception In case the locale's name is invalid + */ + public static function setupLocale($localeName) + { + if (setlocale(LC_ALL, $localeName . '.UTF-8') === false) { + throw new Exception("Cannot set locale '$localeName.UTF-8' for category 'LC_ALL'"); + } + putenv('LC_ALL=' . $localeName . '.UTF-8'); // Failsafe, Win and Unix + putenv('LANG=' . $localeName . '.UTF-8'); // Windows fix, untested + } +} diff --git a/library/Icinga/Web/Controller/ActionController.php b/library/Icinga/Web/Controller/ActionController.php index 1e95e36b1..2cd581604 100755 --- a/library/Icinga/Web/Controller/ActionController.php +++ b/library/Icinga/Web/Controller/ActionController.php @@ -32,17 +32,13 @@ namespace Icinga\Web\Controller; use \Exception; use \Zend_Controller_Action; use \Zend_Controller_Request_Abstract; -use \Zend_Controller_Front; use \Zend_Controller_Response_Abstract; use \Zend_Controller_Action_HelperBroker; -use \Zend_Layout; use Icinga\Authentication\Manager as AuthManager; use Icinga\Application\Benchmark; -use Icinga\Application\Config; -use Icinga\Web\Notification; +use Icinga\Util\Translator; use Icinga\Web\Widget\Tabs; use Icinga\Web\Url; -use Icinga\Web\Request; /** * Base class for all core action controllers @@ -152,15 +148,19 @@ class ActionController extends Zend_Controller_Action } /** - * Translate the given string with the global translation catalog + * Translate a string * - * @param string $string The string that should be translated + * Autoselects the module domain, if any, and falls back to the global one if no translation could be found. * - * @return string + * @param string $text The string to translate + * + * @return string The translated string */ - public function translate($string) + public function translate($text) { - return t($string); + $module = $this->getRequest()->getModuleName(); + $domain = $module === 'default' ? 'icinga' : $module; + return Translator::translate($text, $domain); } /** @@ -212,7 +212,6 @@ class ActionController extends Zend_Controller_Action $this->_helper->Redirector->gotoUrlAndExit($url); } - /** * Detect whether the current request requires changes in the layout and apply them before rendering *