Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/matomo-org/matomo.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthieu Napoli <matthieu@mnapoli.fr>2014-12-29 10:03:26 +0300
committerMatthieu Napoli <matthieu@mnapoli.fr>2015-01-05 05:24:45 +0300
commitde06fd66ee7d01832ff08f13d58e21a9d7a510c7 (patch)
tree07421e1a36621c77a22d0254c2511b48d2cc0e4f
parentcb68ad1ede299ce826989858171456b82a6f7ee5 (diff)
Refactored the way translations are loaded
Translations are now lazily loaded instead of being explicitely loaded by other components (e.g. the front controller, the plugin manager, etc...). Now other parties only configure "directories" where the translator can find translations. That makes the translator decoupled from the rest of Piwik.
-rw-r--r--CHANGELOG.md5
-rw-r--r--config/environment/dev.php8
-rw-r--r--config/global.php3
-rw-r--r--core/Container/ContainerFactory.php6
-rw-r--r--core/FrontController.php6
-rw-r--r--core/Plugin/Manager.php14
-rw-r--r--core/Translate.php15
-rw-r--r--core/Translation/Loader/JsonFileLoader.php65
-rw-r--r--core/Translation/Loader/LoaderCache.php66
-rw-r--r--core/Translation/Loader/LoaderInterface.php23
-rw-r--r--core/Translation/Translator.php353
-rw-r--r--plugins/LanguagesManager/Commands/CompareKeys.php2
-rw-r--r--plugins/LanguagesManager/LanguagesManager.php34
-rw-r--r--tests/PHPUnit/Unit/Translation/Loader/JsonFileLoaderTest.php56
-rw-r--r--tests/PHPUnit/Unit/Translation/Loader/LoaderCacheTest.php72
-rw-r--r--tests/PHPUnit/Unit/Translation/Loader/fixtures/dir1/en.json6
-rw-r--r--tests/PHPUnit/Unit/Translation/Loader/fixtures/dir1/fr.json5
-rw-r--r--tests/PHPUnit/Unit/Translation/Loader/fixtures/dir2/en.json6
-rw-r--r--tests/PHPUnit/Unit/Translation/TranslatorTest.php98
19 files changed, 538 insertions, 305 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4317add11b..75e5b5e458 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
This is a changelog for Piwik platform developers. All changes for our HTTP API's, Plugins, Themes, etc will be listed here.
+## Piwik 2.11.0
+
+### Breaking Changes
+* The event `User.getLanguage` has been removed.
+
## Piwik 2.10.0
### Breaking Changes
diff --git a/config/environment/dev.php b/config/environment/dev.php
new file mode 100644
index 0000000000..c695c23890
--- /dev/null
+++ b/config/environment/dev.php
@@ -0,0 +1,8 @@
+<?php
+
+return array(
+
+ // Disable translation cache
+ 'Piwik\Translation\Loader\LoaderInterface' => DI\object('Piwik\Translation\Loader\JsonFileLoader'),
+
+);
diff --git a/config/global.php b/config/global.php
index 4289a9c973..33d8dc33c5 100644
--- a/config/global.php
+++ b/config/global.php
@@ -158,4 +158,7 @@ return array(
return '%level% %tag%[%datetime%] %message%';
}),
+ 'Piwik\Translation\Loader\LoaderInterface' => DI\object('Piwik\Translation\Loader\LoaderCache')
+ ->constructor(DI\link('Piwik\Translation\Loader\JsonFileLoader')),
+
);
diff --git a/core/Container/ContainerFactory.php b/core/Container/ContainerFactory.php
index f0414452b7..bf3b157ba8 100644
--- a/core/Container/ContainerFactory.php
+++ b/core/Container/ContainerFactory.php
@@ -12,6 +12,7 @@ use DI\Container;
use DI\ContainerBuilder;
use Doctrine\Common\Cache\ArrayCache;
use Piwik\Config;
+use Piwik\Development;
/**
* Creates a configured DI container.
@@ -55,6 +56,11 @@ class ContainerFactory
// Global config
$builder->addDefinitions(PIWIK_USER_PATH . '/config/global.php');
+ // Development config
+ if (Development::isEnabled()) {
+ $builder->addDefinitions(PIWIK_USER_PATH . '/config/environment/dev.php');
+ }
+
// User config
if (file_exists(PIWIK_USER_PATH . '/config/config.php')) {
$builder->addDefinitions(PIWIK_USER_PATH . '/config/config.php');
diff --git a/core/FrontController.php b/core/FrontController.php
index 8e7c91a0bc..89e91b943b 100644
--- a/core/FrontController.php
+++ b/core/FrontController.php
@@ -324,17 +324,13 @@ class FrontController extends Singleton
$tmpPath . '/templates_c/',
);
- Translate::loadEnglishTranslation();
-
Filechecks::dieIfDirectoriesNotWritable($directoriesToCheck);
$this->handleMaintenanceMode();
$this->handleProfiler();
$this->handleSSLRedirection();
- /** @var Translator $translator */
- $translator = StaticContainer::getContainer()->get('Piwik\Translation\Translator');
- $translator->loadPluginsTranslations('en');
+ Plugin\Manager::getInstance()->loadPluginTranslations();
Plugin\Manager::getInstance()->loadActivatedPlugins();
if ($exceptionToThrow) {
diff --git a/core/Plugin/Manager.php b/core/Plugin/Manager.php
index 7d7de22ccc..675bc63f0f 100644
--- a/core/Plugin/Manager.php
+++ b/core/Plugin/Manager.php
@@ -569,8 +569,6 @@ class Manager extends Singleton
*/
public function loadAllPluginsAndGetTheirInfo()
{
- $language = Translate::getLanguageToLoad();
-
/** @var Translator $translator */
$translator = StaticContainer::getContainer()->get('Piwik\Translation\Translator');
@@ -596,7 +594,7 @@ class Manager extends Singleton
'uninstallable' => true,
);
} else {
- $translator->loadPluginTranslations($pluginName, $language);
+ $translator->addDirectory(self::getPluginsDirectory() . $pluginName . '/lang');
$this->loadPlugin($pluginName);
$info = array(
'activated' => $this->isPluginActivated($pluginName),
@@ -607,7 +605,6 @@ class Manager extends Singleton
$plugins[$pluginName] = $info;
}
- $translator->loadPluginsTranslations();
$loadedPlugins = $this->getLoadedPlugins();
foreach ($loadedPlugins as $oPlugin) {
@@ -1298,6 +1295,15 @@ class Manager extends Singleton
return $sorted;
}
+
+ public function loadPluginTranslations()
+ {
+ /** @var Translator $translator */
+ $translator = StaticContainer::getContainer()->get('Piwik\Translation\Translator');
+ foreach ($this->getAllPluginsNames() as $pluginName) {
+ $translator->addDirectory(self::getPluginsDirectory() . $pluginName . '/lang');
+ }
+ }
}
/**
diff --git a/core/Translate.php b/core/Translate.php
index 6bc846ab4b..70f795245b 100644
--- a/core/Translate.php
+++ b/core/Translate.php
@@ -26,22 +26,19 @@ class Translate
*/
public static function clean($s)
{
- return self::getTranslator()->clean($s);
+ return html_entity_decode(trim($s), ENT_QUOTES, 'UTF-8');
}
public static function loadEnglishTranslation()
{
- self::getTranslator()->loadEnglishTranslation();
}
public static function unloadEnglishTranslation()
{
- self::getTranslator()->unloadEnglishTranslation();
}
public static function reloadLanguage($language = false)
{
- self::getTranslator()->reloadLanguage($language);
}
/**
@@ -52,12 +49,10 @@ class Translate
*/
public static function loadCoreTranslation($language = false)
{
- self::getTranslator()->loadCoreTranslation($language);
}
public static function mergeTranslationArray($translation)
{
- self::getTranslator()->mergeTranslationArray($translation);
}
/**
@@ -66,13 +61,13 @@ class Translate
*/
public static function getLanguageToLoad()
{
- return self::getTranslator()->getLanguageToLoad();
+ return self::getTranslator()->getCurrentLanguage();
}
/** Reset the cached language to load. Used in tests. */
public static function reset()
{
- self::getTranslator()->reset();
+ self::getTranslator()->setCurrentLanguage(null);
}
/**
@@ -81,12 +76,12 @@ class Translate
*/
public static function getLanguageLoaded()
{
- return self::getTranslator()->getLanguageLoaded();
+ return self::getTranslator()->getCurrentLanguage();
}
public static function getLanguageDefault()
{
- return self::getTranslator()->getLanguageDefault();
+ return self::getTranslator()->getDefaultLanguage();
}
/**
diff --git a/core/Translation/Loader/JsonFileLoader.php b/core/Translation/Loader/JsonFileLoader.php
new file mode 100644
index 0000000000..f80b940528
--- /dev/null
+++ b/core/Translation/Loader/JsonFileLoader.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Translation\Loader;
+
+use Exception;
+use Piwik\Common;
+
+/**
+ * Loads translations from JSON files.
+ */
+class JsonFileLoader implements LoaderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($language, array $directories)
+ {
+ if (empty($language)) {
+ return array();
+ }
+
+ $translations = array();
+
+ foreach ($directories as $directory) {
+ $filename = $directory . '/' . $language . '.json';
+
+ if (! file_exists($filename)) {
+ continue;
+ }
+
+ $translations = array_replace_recursive(
+ $translations,
+ $this->loadFile($filename)
+ );
+ }
+
+ return $translations;
+ }
+
+ private function loadFile($filename)
+ {
+ $data = file_get_contents($filename);
+ $translations = json_decode($data, true);
+
+ if (is_null($translations) && Common::hasJsonErrorOccurred()) {
+ throw new \Exception(sprintf(
+ 'Not able to load translation file %s: %s',
+ $filename,
+ Common::getLastJsonError()
+ ));
+ }
+
+ if (!is_array($translations)) {
+ return array();
+ }
+
+ return $translations;
+ }
+}
diff --git a/core/Translation/Loader/LoaderCache.php b/core/Translation/Loader/LoaderCache.php
new file mode 100644
index 0000000000..8ad7dc1ad9
--- /dev/null
+++ b/core/Translation/Loader/LoaderCache.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Translation\Loader;
+
+use Piwik\Cache;
+
+/**
+ * Caches the translations loaded by another loader.
+ */
+class LoaderCache implements LoaderInterface
+{
+ /**
+ * @var LoaderInterface
+ */
+ private $loader;
+
+ /**
+ * @var Cache\Lazy
+ */
+ private $cache;
+
+ public function __construct(LoaderInterface $loader, Cache\Lazy $cache = null)
+ {
+ $this->loader = $loader;
+ // TODO DI
+ $this->cache = $cache ?: Cache::getLazyCache();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function load($language, array $directories)
+ {
+ if (empty($language)) {
+ return array();
+ }
+
+ $cacheKey = $this->getCacheKey($language, $directories);
+
+ $translations = $this->cache->fetch($cacheKey);
+
+ if (empty($translations) || !is_array($translations)) {
+ $translations = $this->loader->load($language, $directories);
+
+ $this->cache->save($cacheKey, $translations, 43200); // ttl=12hours
+ }
+
+ return $translations;
+ }
+
+ private function getCacheKey($language, array $directories)
+ {
+ $cacheKey = 'Translations-' . $language . '-';
+
+ // in case loaded plugins change (ie Tests vs Tracker vs UI etc)
+ $cacheKey .= sha1(implode('', $directories));
+
+ return $cacheKey;
+ }
+}
diff --git a/core/Translation/Loader/LoaderInterface.php b/core/Translation/Loader/LoaderInterface.php
new file mode 100644
index 0000000000..9aae71d45b
--- /dev/null
+++ b/core/Translation/Loader/LoaderInterface.php
@@ -0,0 +1,23 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Translation\Loader;
+
+/**
+ * Loads translations.
+ */
+interface LoaderInterface
+{
+ /**
+ * @param string $language
+ * @param mixed[] $directories Directories containing translation files.
+ * @throws \Exception The translation file was not found
+ * @return string[] Translations.
+ */
+ public function load($language, array $directories);
+}
diff --git a/core/Translation/Translator.php b/core/Translation/Translator.php
index 6968bbfc84..7233882cd6 100644
--- a/core/Translation/Translator.php
+++ b/core/Translation/Translator.php
@@ -8,16 +8,10 @@
namespace Piwik\Translation;
-use Exception;
-use Piwik\Cache;
-use Piwik\Common;
use Piwik\Config;
-use Piwik\Development;
-use Piwik\Filesystem;
use Piwik\Piwik;
use Piwik\Plugin;
-use Piwik\Plugin\Manager;
-use Piwik\SettingsServer;
+use Piwik\Translation\Loader\LoaderInterface;
/**
* Translates messages.
@@ -27,190 +21,91 @@ use Piwik\SettingsServer;
class Translator
{
/**
- * Contains the translated messages.
+ * Contains the translated messages, indexed by the language name.
*
* @var array
*/
private $translations = array();
/**
- * @var string|null
+ * @var string
*/
- private $languageToLoad;
+ private $currentLanguage;
/**
- * @var bool
+ * @var string
*/
- private $loadedLanguage = false;
+ private $fallback = 'en';
/**
- * Returns an internationalized string using a translation ID. If a translation
- * cannot be found for the ID, the ID is returned.
+ * Directories containing the translations to load.
*
- * @param string $translationId Translation ID, eg, `'General_Date'`.
- * @param array|string|int $args `sprintf` arguments to be applied to the internationalized
- * string.
- * @return string The translated string or `$translationId`.
- * @api
+ * @var string[]
*/
- public function translate($translationId, $args = array())
- {
- if (!is_array($args)) {
- $args = array($args);
- }
-
- if (strpos($translationId, "_") !== false) {
- list($plugin, $key) = explode("_", $translationId, 2);
- if (isset($this->translations[$plugin]) && isset($this->translations[$plugin][$key])) {
- $translationId = $this->translations[$plugin][$key];
- }
- }
- if (count($args) == 0) {
- return $translationId;
- }
- return vsprintf($translationId, $args);
- }
+ private $directories = array();
/**
- * Clean a string that may contain HTML special chars, single/double quotes, HTML entities, leading/trailing whitespace
- *
- * @param string $s
- * @return string
+ * @var LoaderInterface
*/
- public function clean($s)
- {
- return html_entity_decode(trim($s), ENT_QUOTES, 'UTF-8');
- }
+ private $loader;
- public function loadEnglishTranslation()
+ public function __construct(LoaderInterface $loader, array $directories = null)
{
- $this->loadCoreTranslationFile('en');
- }
+ $this->loader = $loader;
+ $this->currentLanguage = $this->getDefaultLanguage();
- public function unloadEnglishTranslation()
- {
- $this->translations = array();
- }
-
- public function reloadLanguage($language = false)
- {
- if (empty($language)) {
- $language = $this->getLanguageToLoad();
+ if ($directories === null) {
+ // TODO should be moved out of this class
+ $directories = array(PIWIK_INCLUDE_PATH . '/lang');
}
- $this->unloadEnglishTranslation();
- $this->loadEnglishTranslation();
- $this->loadCoreTranslation($language);
- $this->loadPluginsTranslations($language);
+ $this->directories = $directories;
}
/**
- * Reads the specified code translation file in memory.
+ * Returns an internationalized string using a translation ID. If a translation
+ * cannot be found for the ID, the ID is returned.
*
- * @param bool|string $language 2 letter language code. If not specified, will detect current user translation, or load default translation.
- * @return void
+ * @param string $translationId Translation ID, eg, `General_Date`.
+ * @param array|string|int $args `sprintf` arguments to be applied to the internationalized
+ * string.
+ * @return string The translated string or `$translationId`.
+ * @api
*/
- public function loadCoreTranslation($language = false)
- {
- if (empty($language)) {
- $language = $this->getLanguageToLoad();
- }
- if ($this->loadedLanguage == $language) {
- return;
- }
- $this->loadCoreTranslationFile($language);
- }
-
- private function loadCoreTranslationFile($language)
+ public function translate($translationId, $args = array())
{
- if (empty($language)) {
- return;
- }
+ $args = is_array($args) ? $args : array($args);
- $path = PIWIK_INCLUDE_PATH . '/lang/' . $language . '.json';
- if (!Filesystem::isValidFilename($language) || !is_readable($path)) {
- throw new Exception(Piwik::translate('General_ExceptionLanguageFileNotFound', array($language)));
+ if (strpos($translationId, "_") !== false) {
+ list($plugin, $key) = explode("_", $translationId, 2);
+ $translationId = $this->getTranslation($translationId, $this->currentLanguage, $plugin, $key);
}
- $data = file_get_contents($path);
- $translations = json_decode($data, true);
- $this->mergeTranslationArray($translations);
- $this->setLocale();
- $this->loadedLanguage = $language;
- }
- public function mergeTranslationArray($translation)
- {
- if (empty($translation)) {
- return;
+ if (count($args) == 0) {
+ return $translationId;
}
- // we could check that no string overlap here
- $this->translations = array_replace_recursive($this->translations, $translation);
+ return vsprintf($translationId, $args);
}
/**
- * @return string the language filename prefix, eg 'en' for english
- * @throws exception if the language set is not a valid filename
+ * @return string
*/
- public function getLanguageToLoad()
+ public function getCurrentLanguage()
{
- if (is_null($this->languageToLoad)) {
- $lang = Common::getRequestVar('language', '', 'string');
-
- /**
- * Triggered when the current user's language is requested.
- *
- * By default the current language is determined by the **language** query
- * parameter. Plugins can override this logic by subscribing to this event.
- *
- * **Example**
- *
- * public function getLanguage(&$lang)
- * {
- * $client = new My3rdPartyAPIClient();
- * $thirdPartyLang = $client->getLanguageForUser(Piwik::getCurrentUserLogin());
- *
- * if (!empty($thirdPartyLang)) {
- * $lang = $thirdPartyLang;
- * }
- * }
- *
- * @param string &$lang The language that should be used for the current user. Will be
- * initialized to the value of the **language** query parameter.
- */
- Piwik::postEvent('User.getLanguage', array(&$lang));
-
- $this->languageToLoad = $lang;
- }
-
- return $this->languageToLoad;
+ return $this->currentLanguage;
}
/**
- * Reset the cached language to load. Used in tests.
+ * @param string $language
*/
- public function reset()
- {
- $this->languageToLoad = null;
- }
-
- private function isALanguageLoaded()
+ public function setCurrentLanguage($language)
{
- return !empty($this->translations);
+ $this->currentLanguage = $language;
}
/**
- * Either the name of the currently loaded language such as 'en' or 'de' or null if no language is loaded at all.
- * @return bool|string
+ * @return string The default configured language.
*/
- public function getLanguageLoaded()
- {
- if (!$this->isALanguageLoaded()) {
- return null;
- }
-
- return $this->loadedLanguage;
- }
-
- public function getLanguageDefault()
+ public function getDefaultLanguage()
{
return Config::getInstance()->General['default_language'];
}
@@ -220,12 +115,10 @@ class Translator
*/
public function getJavascriptTranslations()
{
- $translations = & $this->translations;
-
$clientSideTranslations = array();
- foreach ($this->getClientSideTranslationKeys() as $key) {
- list($plugin, $stringName) = explode("_", $key, 2);
- $clientSideTranslations[$key] = $translations[$plugin][$stringName];
+ foreach ($this->getClientSideTranslationKeys() as $id) {
+ list($plugin, $key) = explode('_', $id, 2);
+ $clientSideTranslations[$id] = $this->getTranslation($id, $this->currentLanguage, $plugin, $key);
}
$js = 'var translations = ' . json_encode($clientSideTranslations) . ';';
@@ -268,16 +161,20 @@ class Translator
}
/**
- * Set locale
+ * Add a directory containing translations.
*
- * @see http://php.net/setlocale
+ * @param string $directory
*/
- private function setLocale()
+ public function addDirectory($directory)
{
- $locale = $this->translations['General']['Locale'];
- $locale_variant = str_replace('UTF-8', 'UTF8', $locale);
- setlocale(LC_ALL, $locale, $locale_variant);
- setlocale(LC_CTYPE, '');
+ if (isset($this->directories[$directory])) {
+ return;
+ }
+ // index by name to avoid duplicates
+ $this->directories[$directory] = $directory;
+
+ // clear currently loaded translations to force reloading them
+ $this->translations = array();
}
/**
@@ -286,11 +183,7 @@ class Translator
*/
public function findTranslationKeyForTranslation($translation)
{
- if (empty($this->translations)) {
- return null;
- }
-
- foreach ($this->translations as $key => $translations) {
+ foreach ($this->getAllTranslations() as $key => $translations) {
$possibleKey = array_search($translation, $translations);
if (!empty($possibleKey)) {
return $key . '_' . $possibleKey;
@@ -301,135 +194,45 @@ class Translator
}
/**
- * Load translations for loaded plugins
+ * Returns all the translation messages loaded.
*
- * @param bool|string $language Optional language code
+ * @return array
*/
- public function loadPluginsTranslations($language = false)
+ public function getAllTranslations()
{
- if (empty($language)) {
- $language = $this->getLanguageToLoad();
- }
-
- $pluginManager = Manager::getInstance();
-
- $cacheKey = 'PluginTranslations';
-
- if (!empty($language)) {
- $cacheKey .= '-' . trim($language);
- }
+ $this->loadTranslations($this->currentLanguage);
- if (!empty($this->loadedPlugins)) {
- // makes sure to create a translation in case loaded plugins change (ie Tests vs Tracker vs UI etc)
- $cacheKey .= '-' . md5(implode('', $pluginManager->getLoadedPluginsName()));
+ if (!isset($this->translations[$this->currentLanguage])) {
+ return array();
}
- $cache = Cache::getLazyCache();
- $translations = $cache->fetch($cacheKey);
-
- if (!empty($translations) &&
- is_array($translations) &&
- !Development::isEnabled()) { // TODO remove this one here once we have environments in DI
-
- $this->mergeTranslationArray($translations);
- return;
- }
-
- $translations = array();
- $pluginNames = Manager::getAllPluginsNames();
-
- foreach ($pluginNames as $pluginName) {
- if ($pluginManager->isPluginLoaded($pluginName) ||
- $pluginManager->isPluginBundledWithCore($pluginName)) {
-
- $this->loadPluginTranslations($pluginName, $language);
-
- if (isset($this->translations[$pluginName])) {
- $translations[$pluginName] = $this->translations[$pluginName];
- }
- }
- }
-
- $cache->save($cacheKey, $translations, 43200); // ttl=12hours
+ return $this->translations[$this->currentLanguage];
}
- /**
- * Load translation
- *
- * @param Plugin|string $plugin
- * @param string $language
- * @throws \Exception
- * @return bool whether the translation was found and loaded
- */
- public function loadPluginTranslations($plugin, $language)
- {
- // we are in Tracker mode if Loader is not (yet) loaded
- if (SettingsServer::isTrackerApiRequest()) {
- return false;
- }
-
- $pluginName = ($plugin instanceof Plugin) ? $plugin->getPluginName() : $plugin;
- $path = Manager::getPluginsDirectory() . $pluginName . '/lang/%s.json';
-
- $defaultLangPath = sprintf($path, $language);
- $defaultEnglishLangPath = sprintf($path, 'en');
-
- $translationsLoaded = false;
+ private function getTranslation($id, $lang, $plugin, $key)
+ {
+ $this->loadTranslations($lang);
- // merge in english translations as default first
- if (file_exists($defaultEnglishLangPath)) {
- $translations = $this->getTranslationsFromFile($defaultEnglishLangPath);
- $translationsLoaded = true;
- if (isset($translations[$pluginName])) {
- // only merge translations of plugin - prevents overwritten strings
- $this->mergeTranslationArray(array($pluginName => $translations[$pluginName]));
- }
+ if (isset($this->translations[$lang][$plugin])
+ && isset($this->translations[$lang][$plugin][$key])
+ ) {
+ return $this->translations[$lang][$plugin][$key];
}
- // merge in specific language translations (to overwrite english defaults)
- if (!empty($language) &&
- $defaultEnglishLangPath != $defaultLangPath &&
- file_exists($defaultLangPath)) {
-
- $translations = $this->getTranslationsFromFile($defaultLangPath);
- $translationsLoaded = true;
- if (isset($translations[$pluginName])) {
- // only merge translations of plugin - prevents overwritten strings
- $this->mergeTranslationArray(array($pluginName => $translations[$pluginName]));
- }
+ // fallback
+ if ($lang !== $this->fallback) {
+ return $this->getTranslation($id, $this->fallback, $plugin, $key);
}
- return $translationsLoaded;
+ return $id;
}
- /**
- * @param string $pathToTranslationFile
- * @throws \Exception
- * @return mixed
- */
- private function getTranslationsFromFile($pathToTranslationFile)
+ private function loadTranslations($language)
{
- $data = file_get_contents($pathToTranslationFile);
- $translations = json_decode($data, true);
-
- if (is_null($translations) && Common::hasJsonErrorOccurred()) {
- $jsonError = Common::getLastJsonError();
-
- $message = sprintf('Not able to load translation file %s: %s', $pathToTranslationFile, $jsonError);
-
- throw new \Exception($message);
+ if (empty($language) || isset($this->translations[$language])) {
+ return;
}
- return $translations;
- }
-
- /**
- * Returns all the translation messages loaded.
- *
- * @return array
- */
- public function getAllTranslations()
- {
- return $this->translations;
+ $this->translations[$language] = $this->loader->load($language, $this->directories);
}
}
diff --git a/plugins/LanguagesManager/Commands/CompareKeys.php b/plugins/LanguagesManager/Commands/CompareKeys.php
index df41351e64..60364ac39d 100644
--- a/plugins/LanguagesManager/Commands/CompareKeys.php
+++ b/plugins/LanguagesManager/Commands/CompareKeys.php
@@ -52,7 +52,7 @@ class CompareKeys extends TranslationBase
/** @var Translator $translator */
$translator = StaticContainer::getContainer()->get('Piwik\Translation\Translator');
- $translator->reloadLanguage('en');
+ $translator->setCurrentLanguage('en');
$availableTranslations = $translator->getAllTranslations();
$categories = array_unique(array_merge(array_keys($englishFromOTrance), array_keys($availableTranslations)));
diff --git a/plugins/LanguagesManager/LanguagesManager.php b/plugins/LanguagesManager/LanguagesManager.php
index 6a0fcf10e1..711de26648 100644
--- a/plugins/LanguagesManager/LanguagesManager.php
+++ b/plugins/LanguagesManager/LanguagesManager.php
@@ -12,10 +12,12 @@ namespace Piwik\Plugins\LanguagesManager;
use Exception;
use Piwik\Common;
use Piwik\Config;
+use Piwik\Container\StaticContainer;
use Piwik\Cookie;
use Piwik\Db;
use Piwik\Piwik;
use Piwik\Translate;
+use Piwik\Translation\Translator;
use Piwik\View;
/**
@@ -29,12 +31,12 @@ class LanguagesManager extends \Piwik\Plugin
public function getListHooksRegistered()
{
return array(
- 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
- 'AssetManager.getJavaScriptFiles' => 'getJsFiles',
- 'User.getLanguage' => 'getLanguageToLoad',
- 'UsersManager.deleteUser' => 'deleteUserLanguage',
- 'Template.topBar' => 'addLanguagesManagerToOtherTopBar',
- 'Template.jsGlobalVariables' => 'jsGlobalVariables'
+ 'AssetManager.getStylesheetFiles' => 'getStylesheetFiles',
+ 'AssetManager.getJavaScriptFiles' => 'getJsFiles',
+ 'Request.dispatchCoreAndPluginUpdatesScreen' => 'initLanguage',
+ 'UsersManager.deleteUser' => 'deleteUserLanguage',
+ 'Template.topBar' => 'addLanguagesManagerToOtherTopBar',
+ 'Template.jsGlobalVariables' => 'jsGlobalVariables'
);
}
@@ -89,14 +91,26 @@ class LanguagesManager extends \Piwik\Plugin
return $view->render();
}
- function getLanguageToLoad(&$language)
+ public function initLanguage()
{
+ /** @var Translator $translator */
+ $translator = StaticContainer::getContainer()->get('Piwik\Translation\Translator');
+
+ $language = Common::getRequestVar('language', '', 'string');
if (empty($language)) {
- $language = self::getLanguageCodeForCurrentUser();
+ $userLanguage = self::getLanguageCodeForCurrentUser();
+ if (API::getInstance()->isLanguageAvailable($userLanguage)) {
+ $language = $userLanguage;
+ }
}
- if (!API::getInstance()->isLanguageAvailable($language)) {
- $language = Translate::getLanguageDefault();
+ if (!empty($language) && API::getInstance()->isLanguageAvailable($language)) {
+ $translator->setCurrentLanguage($language);
}
+
+ $locale = $translator->translate('General_Locale');
+ $localeVariant = str_replace('UTF-8', 'UTF8', $locale);
+ setlocale(LC_ALL, $locale, $localeVariant);
+ setlocale(LC_CTYPE, '');
}
public function deleteUserLanguage($userLogin)
diff --git a/tests/PHPUnit/Unit/Translation/Loader/JsonFileLoaderTest.php b/tests/PHPUnit/Unit/Translation/Loader/JsonFileLoaderTest.php
new file mode 100644
index 0000000000..d1770c491d
--- /dev/null
+++ b/tests/PHPUnit/Unit/Translation/Loader/JsonFileLoaderTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Tests\Unit\Translation\Loader;
+
+use Piwik\Translation\Loader\JsonFileLoader;
+
+/**
+ * @group Translation
+ */
+class JsonFileLoaderTest extends \PHPUnit_Framework_TestCase
+{
+ public function test_shouldLoadJsonFile()
+ {
+ $loader = new JsonFileLoader();
+ $translations = $loader->load('en', array(__DIR__ . '/fixtures/dir1'));
+
+ $expected = array(
+ 'General' => array(
+ 'test1' => 'Hello',
+ 'test2' => 'Hello',
+ ),
+ );
+
+ $this->assertEquals($expected, $translations);
+ }
+
+ public function test_shouldIgnoreMissingFiles()
+ {
+ $loader = new JsonFileLoader();
+ $translations = $loader->load('foo', array(__DIR__ . '/fixtures/dir1'));
+
+ $this->assertEquals(array(), $translations);
+ }
+
+ public function test_shouldMergeTranslations_ifLoadingMultipleFiles()
+ {
+ $loader = new JsonFileLoader();
+ $translations = $loader->load('en', array(__DIR__ . '/fixtures/dir1', __DIR__ . '/fixtures/dir2'));
+
+ $expected = array(
+ 'General' => array(
+ 'test1' => 'Hello',
+ 'test2' => 'Hello 2', // the second file should overwrite the first one
+ 'test3' => 'Hello 3',
+ ),
+ );
+
+ $this->assertEquals($expected, $translations);
+ }
+}
diff --git a/tests/PHPUnit/Unit/Translation/Loader/LoaderCacheTest.php b/tests/PHPUnit/Unit/Translation/Loader/LoaderCacheTest.php
new file mode 100644
index 0000000000..2b00333396
--- /dev/null
+++ b/tests/PHPUnit/Unit/Translation/Loader/LoaderCacheTest.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Tests\Unit\Translation\Loader;
+
+use Piwik\Cache\Backend\ArrayCache;
+use Piwik\Cache\Lazy;
+use Piwik\Translation\Loader\LoaderCache;
+
+/**
+ * @group Translation
+ */
+class LoaderCacheTest extends \PHPUnit_Framework_TestCase
+{
+ public function test_shouldNotLoad_ifInCache()
+ {
+ $cache = $this->getMock('Piwik\Cache\Lazy', array(), array(), '', false);
+ $cache->expects($this->any())
+ ->method('fetch')
+ ->willReturn(array('translations!'));
+ $wrappedLoader = $this->getMockForAbstractClass('Piwik\Translation\Loader\LoaderInterface');
+ $wrappedLoader->expects($this->never())
+ ->method('load');
+
+ $loader = new LoaderCache($wrappedLoader, $cache);
+ $translations = $loader->load('en', array('foo'));
+
+ $this->assertEquals(array('translations!'), $translations);
+ }
+
+ public function test_shouldLoad_ifNotInCache()
+ {
+ $cache = $this->getMock('Piwik\Cache\Lazy', array(), array(), '', false);
+ $cache->expects($this->any())
+ ->method('fetch')
+ ->willReturn(null);
+ $wrappedLoader = $this->getMockForAbstractClass('Piwik\Translation\Loader\LoaderInterface');
+ $wrappedLoader->expects($this->once())
+ ->method('load')
+ ->with('en', array('foo'))
+ ->willReturn(array('translations!'));
+
+ $loader = new LoaderCache($wrappedLoader, $cache);
+ $translations = $loader->load('en', array('foo'));
+
+ $this->assertEquals(array('translations!'), $translations);
+ }
+
+ public function test_shouldReLoad_ifDifferentDirectories()
+ {
+ $cache = new Lazy(new ArrayCache());
+
+ $wrappedLoader = $this->getMockForAbstractClass('Piwik\Translation\Loader\LoaderInterface');
+ $wrappedLoader->expects($this->exactly(2))
+ ->method('load')
+ ->willReturn(array('translations!'));
+
+ $loader = new LoaderCache($wrappedLoader, $cache);
+
+ // Should call the wrapped loader only once
+ $loader->load('en', array('foo'));
+ $loader->load('en', array('foo'));
+
+ // Should call the wrapped loader a second time
+ $loader->load('en', array('foo', 'bar'));
+ }
+}
diff --git a/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir1/en.json b/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir1/en.json
new file mode 100644
index 0000000000..f7518b863d
--- /dev/null
+++ b/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir1/en.json
@@ -0,0 +1,6 @@
+{
+ "General": {
+ "test1": "Hello",
+ "test2": "Hello"
+ }
+} \ No newline at end of file
diff --git a/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir1/fr.json b/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir1/fr.json
new file mode 100644
index 0000000000..b1a2f0aca9
--- /dev/null
+++ b/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir1/fr.json
@@ -0,0 +1,5 @@
+{
+ "General": {
+ "test1": "Bonjour"
+ }
+} \ No newline at end of file
diff --git a/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir2/en.json b/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir2/en.json
new file mode 100644
index 0000000000..592e802d36
--- /dev/null
+++ b/tests/PHPUnit/Unit/Translation/Loader/fixtures/dir2/en.json
@@ -0,0 +1,6 @@
+{
+ "General": {
+ "test2": "Hello 2",
+ "test3": "Hello 3"
+ }
+} \ No newline at end of file
diff --git a/tests/PHPUnit/Unit/Translation/TranslatorTest.php b/tests/PHPUnit/Unit/Translation/TranslatorTest.php
new file mode 100644
index 0000000000..0e97223e7d
--- /dev/null
+++ b/tests/PHPUnit/Unit/Translation/TranslatorTest.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * Piwik - free/libre analytics platform
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ */
+
+namespace Piwik\Tests\Unit\Translation\Loader;
+
+use Piwik\Translation\Loader\JsonFileLoader;
+use Piwik\Translation\Translator;
+
+/**
+ * @group Translation
+ */
+class TranslatorTest extends \PHPUnit_Framework_TestCase
+{
+ public function test_translate_shouldReturnTranslationId_ifNoTranslationFound()
+ {
+ $loader = $this->createLoader();
+ $translator = new Translator($loader, array());
+
+ $this->assertEquals('General_foo', $translator->translate('General_foo'));
+ }
+
+ public function test_translate_shouldReturnTranslation()
+ {
+ $loader = $this->createLoader(array(
+ 'General' => array(
+ 'foo' => 'Hello world',
+ ),
+ ));
+ $translator = new Translator($loader, array());
+
+ $this->assertEquals('Hello world', $translator->translate('General_foo'));
+ }
+
+ public function test_translate_shouldReplacePlaceholders()
+ {
+ $loader = $this->createLoader(array(
+ 'General' => array(
+ 'foo' => 'Hello %s',
+ ),
+ ));
+ $translator = new Translator($loader, array());
+
+ $this->assertEquals('Hello John', $translator->translate('General_foo', 'John'));
+ }
+
+ public function test_translate_withADifferentLanguage()
+ {
+ $translator = new Translator(new JsonFileLoader(), array(__DIR__ . '/Loader/fixtures/dir1'));
+
+ $this->assertEquals('Hello', $translator->translate('General_test1'));
+
+ $translator->setCurrentLanguage('fr');
+ $this->assertEquals('fr', $translator->getCurrentLanguage());
+ $this->assertEquals('Bonjour', $translator->translate('General_test1'));
+ }
+
+ public function test_translate_shouldFallback_ifTranslationNotFound()
+ {
+ $translator = new Translator(new JsonFileLoader(), array(__DIR__ . '/Loader/fixtures/dir1'));
+ $translator->setCurrentLanguage('fr');
+ $this->assertEquals('Hello', $translator->translate('General_test2'));
+ }
+
+ public function test_addDirectory_shouldImportNewTranslations()
+ {
+ $translator = new Translator(new JsonFileLoader(), array(__DIR__ . '/Loader/fixtures/dir1'));
+ // translation not found
+ $this->assertEquals('General_test3', $translator->translate('General_test3'));
+
+ $translator->addDirectory(__DIR__ . '/Loader/fixtures/dir2');
+ // translation is now found
+ $this->assertEquals('Hello 3', $translator->translate('General_test3'));
+ }
+
+ public function test_addDirectory_shouldImportOverExistingTranslations()
+ {
+ $translator = new Translator(new JsonFileLoader(), array(__DIR__ . '/Loader/fixtures/dir1'));
+ $this->assertEquals('Hello', $translator->translate('General_test2'));
+
+ $translator->addDirectory(__DIR__ . '/Loader/fixtures/dir2');
+ $this->assertEquals('Hello 2', $translator->translate('General_test2'));
+ }
+
+ private function createLoader(array $translations = array())
+ {
+ $loader = $this->getMockForAbstractClass('Piwik\Translation\Loader\LoaderInterface');
+ $loader->expects($this->any())
+ ->method('load')
+ ->willReturn($translations);
+
+ return $loader;
+ }
+}