From 1d0234550e869ce71da1fbc5693a94cc8ca58473 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Sun, 5 Jul 2015 21:48:13 -0600 Subject: [PATCH 1/6] first whack at providing language yaml support --- system/src/Grav/Common/Config/Config.php | 70 +++++++++++++++++++ .../src/Grav/Common/Config/ConfigFinder.php | 12 ++++ 2 files changed, 82 insertions(+) diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php index f2c6abcbc..adea8c72c 100644 --- a/system/src/Grav/Common/Config/Config.php +++ b/system/src/Grav/Common/Config/Config.php @@ -60,6 +60,12 @@ class Config extends Data '' => ['user://themes'], ] ], + 'languages' => [ + 'type' => 'ReadOnlyStream', + 'prefixes' => [ + '' => ['user://languages', 'system://languages'], + ] + ], 'cache' => [ 'type' => 'Stream', 'prefixes' => [ @@ -85,12 +91,14 @@ class Config extends Data protected $blueprintFiles = []; protected $configFiles = []; + protected $languageFiles = []; protected $checksum; protected $timestamp; protected $configLookup; protected $blueprintLookup; protected $pluginLookup; + protected $languagesLookup; protected $finder; protected $environment; @@ -163,9 +171,11 @@ class Config extends Data $this->configLookup = $locator->findResources('config://'); $this->blueprintLookup = $locator->findResources('blueprints://config'); $this->pluginLookup = $locator->findResources('plugins://'); + $this->languagesLookup = $locator->findResources('languages://'); $this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master'); $this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master'); + $this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master'); $this->initializeLocator($locator); } @@ -312,6 +322,58 @@ class Config extends Data $this->items = $cache['data']; } + protected function loadCompiledLanguages($languages, $plugins, $filename = null) + { + $filename = $filename + ? CACHE_DIR . 'compiled/languages/' . $filename . '-' . $this->environment . '.php' + : CACHE_DIR . 'compiled/languages/' . $checksum . '-' . $this->environment . '.php'; + $file = PhpFile::instance($filename); + $cache = $file->exists() ? $file->content() : null; + $class = get_class($this); + $checksum = $this->checksum(); + + if ( + !is_array($cache) + || !isset($cache['checksum']) + || !isset($cache['@class']) + || $cache['@class'] != $class + ) { + $this->messages[] = 'No cached languages, compiling new configuration..'; + } else if ($cache['checksum'] !== $checksum) { + $this->messages[] = 'Languages checksum mismatch, reloading languages..'; + } else { + $this->messages[] = 'Languages checksum matches, using cached version.'; + + $this->items = $cache['data']; + return; + } + + $languageFiles = $this->finder->locateLanguageFiles($languages, $plugins); + + // Attempt to lock the file for writing. + $file->lock(false); + + // Load languages. + foreach ($languageFiles as $files) { + $this->loadLanguagesFiles($files); + } + $cache = [ + '@class' => $class, + 'timestamp' => time(), + 'checksum' => $checksum, + 'data' => $this->toArray() + ]; + + // If compiled file wasn't already locked by another process, save it. + if ($file->locked() !== false) { + $this->messages[] = 'Saving compiled configuration.'; + $file->save($cache); + $file->unlock(); + } + + $this->items = $cache['data']; + } + /** * Load blueprints. * @@ -338,6 +400,14 @@ class Config extends Data } } + public function loadLanguagesFiles(array $files) + { + foreach ($files as $name => $item) { + $file = CompiledYamlFile::instance($item['file']); + $this->set($name, $file->content(), '/'); + } + } + /** * Initialize resource locator by using the configuration. * diff --git a/system/src/Grav/Common/Config/ConfigFinder.php b/system/src/Grav/Common/Config/ConfigFinder.php index 243ddc5aa..f13a339f1 100644 --- a/system/src/Grav/Common/Config/ConfigFinder.php +++ b/system/src/Grav/Common/Config/ConfigFinder.php @@ -49,6 +49,18 @@ class ConfigFinder return $list; } + public function locateLanguageFiles(array $languages, array $plugins) + { + $list = []; + foreach (array_reverse($plugins) as $folder) { + $list += $this->detectInFolder($folder, 'languages'); + } + foreach (array_reverse($languages) as $folder) { + $list += $this->detectRecursive($folder); + } + return $list; + } + /** * Get all locations for a single configuration file. * From 046136bc37c7a0ebd44236d15e32442f977026a1 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 6 Jul 2015 17:19:22 -0600 Subject: [PATCH 2/6] improved with plugin merge --- system/src/Grav/Common/Config/Config.php | 144 ++++++++++-------- .../src/Grav/Common/Config/ConfigFinder.php | 34 ++++- system/src/Grav/Common/Config/Languages.php | 25 +++ system/src/Grav/Common/Language.php | 4 +- 4 files changed, 141 insertions(+), 66 deletions(-) create mode 100644 system/src/Grav/Common/Config/Languages.php diff --git a/system/src/Grav/Common/Config/Config.php b/system/src/Grav/Common/Config/Config.php index adea8c72c..f1d2d611b 100644 --- a/system/src/Grav/Common/Config/Config.php +++ b/system/src/Grav/Common/Config/Config.php @@ -63,7 +63,7 @@ class Config extends Data 'languages' => [ 'type' => 'ReadOnlyStream', 'prefixes' => [ - '' => ['user://languages', 'system://languages'], + '' => ['user://languages', 'system/languages'], ] ], 'cache' => [ @@ -104,6 +104,8 @@ class Config extends Data protected $environment; protected $messages = []; + protected $languages; + public function __construct(array $setup = array(), Grav $grav = null, $environment = null) { $this->grav = $grav ?: Grav::instance(); @@ -171,45 +173,62 @@ class Config extends Data $this->configLookup = $locator->findResources('config://'); $this->blueprintLookup = $locator->findResources('blueprints://config'); $this->pluginLookup = $locator->findResources('plugins://'); - $this->languagesLookup = $locator->findResources('languages://'); + $this->loadCompiledBlueprints($this->blueprintLookup, $this->pluginLookup, 'master'); $this->loadCompiledConfig($this->configLookup, $this->pluginLookup, 'master'); - $this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master'); + + // process languages if supported + if ($this->get('languages', false)) { + $this->languagesLookup = $locator->findResources('languages://'); + $this->loadCompiledLanguages($this->languagesLookup, $this->pluginLookup, 'master'); + } $this->initializeLocator($locator); } public function checksum() { - $checkBlueprints = $this->get('system.cache.check.blueprints', false); - $checkConfig = $this->get('system.cache.check.config', true); - $checkSystem = $this->get('system.cache.check.system', true); + if (empty($this->checksum)) { + $checkBlueprints = $this->get('system.cache.check.blueprints', false); + $checkLanguages = $this->get('system.cache.check.languages', false); + $checkConfig = $this->get('system.cache.check.config', true); + $checkSystem = $this->get('system.cache.check.system', true); - if (!$checkBlueprints && !$checkConfig && !$checkSystem) { - $this->messages[] = 'Skip configuration timestamp check.'; - return false; + if (!$checkBlueprints && !!$checkLanguages && $checkConfig && !$checkSystem) { + $this->messages[] = 'Skip configuration timestamp check.'; + return false; + } + + // Generate checksum according to the configuration settings. + if (!$checkConfig) { + $this->messages[] = 'Check configuration timestamps from system.yaml files.'; + // Just check changes in system.yaml files and ignore all the other files. + $cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : []; + } else { + $this->messages[] = 'Check configuration timestamps from all configuration files.'; + // Check changes in all configuration files. + $cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup); + } + + if ($checkBlueprints) { + $this->messages[] = 'Check blueprint timestamps from all blueprint files.'; + $cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup); + } else { + $cb = []; + } + + if ($checkLanguages) { + $this->messages[] = 'Check language timestamps from all language files.'; + $cl = $this->finder->locateLanguageFiles($this->languagesLookup, $this->pluginLookup); + } else { + $cl = []; + } + + $this->checksum = md5(json_encode([$cc, $cb, $cl])); } - // Generate checksum according to the configuration settings. - if (!$checkConfig) { - $this->messages[] = 'Check configuration timestamps from system.yaml files.'; - // Just check changes in system.yaml files and ignore all the other files. - $cc = $checkSystem ? $this->finder->locateConfigFile($this->configLookup, 'system') : []; - } else { - $this->messages[] = 'Check configuration timestamps from all configuration files.'; - // Check changes in all configuration files. - $cc = $this->finder->locateConfigFiles($this->configLookup, $this->pluginLookup); - } - - if ($checkBlueprints) { - $this->messages[] = 'Check blueprint timestamps from all blueprint files.'; - $cb = $this->finder->locateBlueprintFiles($this->blueprintLookup, $this->pluginLookup); - } else { - $cb = []; - } - - return md5(json_encode([$cc, $cb])); + return $this->checksum; } protected function autoDetectEnvironmentConfig($items) @@ -324,54 +343,50 @@ class Config extends Data protected function loadCompiledLanguages($languages, $plugins, $filename = null) { + $checksum = md5(json_encode($languages)); $filename = $filename ? CACHE_DIR . 'compiled/languages/' . $filename . '-' . $this->environment . '.php' : CACHE_DIR . 'compiled/languages/' . $checksum . '-' . $this->environment . '.php'; $file = PhpFile::instance($filename); $cache = $file->exists() ? $file->content() : null; + $languageFiles = $this->finder->locateLanguageFiles($languages, $plugins); + $checksum .= ':' . md5(json_encode($languageFiles)); $class = get_class($this); - $checksum = $this->checksum(); + // Load real file if cache isn't up to date (or is invalid). if ( !is_array($cache) || !isset($cache['checksum']) || !isset($cache['@class']) + || $cache['checksum'] != $checksum || $cache['@class'] != $class ) { - $this->messages[] = 'No cached languages, compiling new configuration..'; - } else if ($cache['checksum'] !== $checksum) { - $this->messages[] = 'Languages checksum mismatch, reloading languages..'; + // Attempt to lock the file for writing. + $file->lock(false); + + // Load languages. + $this->languages = new Languages; + foreach ($languageFiles as $files) { + $this->loadLanguagesFiles($files); + } + + $this->languages->reformat(); + + $cache = [ + '@class' => $class, + 'checksum' => $checksum, + 'files' => $languageFiles, + 'data' => $this->languages->toArray() + ]; + // If compiled file wasn't already locked by another process, save it. + if ($file->locked() !== false) { + $this->messages[] = 'Saving compiled languages.'; + $file->save($cache); + $file->unlock(); + } } else { - $this->messages[] = 'Languages checksum matches, using cached version.'; - - $this->items = $cache['data']; - return; + $this->languages = new Languages($cache['data']); } - - $languageFiles = $this->finder->locateLanguageFiles($languages, $plugins); - - // Attempt to lock the file for writing. - $file->lock(false); - - // Load languages. - foreach ($languageFiles as $files) { - $this->loadLanguagesFiles($files); - } - $cache = [ - '@class' => $class, - 'timestamp' => time(), - 'checksum' => $checksum, - 'data' => $this->toArray() - ]; - - // If compiled file wasn't already locked by another process, save it. - if ($file->locked() !== false) { - $this->messages[] = 'Saving compiled configuration.'; - $file->save($cache); - $file->unlock(); - } - - $this->items = $cache['data']; } /** @@ -404,7 +419,7 @@ class Config extends Data { foreach ($files as $name => $item) { $file = CompiledYamlFile::instance($item['file']); - $this->set($name, $file->content(), '/'); + $this->languages->join($name, $file->content(), '/'); } } @@ -450,4 +465,9 @@ class Config extends Data return $schemes; } + + public function getLanguages() + { + return $this->languages; + } } diff --git a/system/src/Grav/Common/Config/ConfigFinder.php b/system/src/Grav/Common/Config/ConfigFinder.php index f13a339f1..0e0e228d6 100644 --- a/system/src/Grav/Common/Config/ConfigFinder.php +++ b/system/src/Grav/Common/Config/ConfigFinder.php @@ -53,7 +53,7 @@ class ConfigFinder { $list = []; foreach (array_reverse($plugins) as $folder) { - $list += $this->detectInFolder($folder, 'languages'); + $list += $this->detectLanguagesInFolder($folder, 'languages'); } foreach (array_reverse($languages) as $folder) { $list += $this->detectRecursive($folder); @@ -102,11 +102,39 @@ class ConfigFinder $list = []; if (is_dir($folder)) { - $iterator = new \DirectoryIterator($folder); + $iterator = new \FilesystemIterator($folder); /** @var \DirectoryIterator $directory */ foreach ($iterator as $directory) { - if (!$directory->isDir() || $directory->isDot()) { + if (!$directory->isDir()) { + continue; + } + + $name = $directory->getBasename(); + $find = ($lookup ?: $name) . '.yaml'; + $filename = "{$path}/{$name}/$find"; + + if (file_exists($filename)) { + $list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)]; + } + } + } + + return [$path => $list]; + } + + protected function detectLanguagesInFolder($folder, $lookup = null) + { + $path = trim(Folder::getRelativePath($folder), '/'); + + $list = []; + + if (is_dir($folder)) { + $iterator = new \FilesystemIterator($folder); + + /** @var \DirectoryIterator $directory */ + foreach ($iterator as $directory) { + if (!$directory->isDir()) { continue; } diff --git a/system/src/Grav/Common/Config/Languages.php b/system/src/Grav/Common/Config/Languages.php new file mode 100644 index 000000000..03f589aa7 --- /dev/null +++ b/system/src/Grav/Common/Config/Languages.php @@ -0,0 +1,25 @@ +items['plugins'])) { + foreach ($this->items['plugins'] as $plugin => $data) { + $this->items = array_merge_recursive($this->items, $data); + } + unset($this->items['plugins']); + } + } + +} diff --git a/system/src/Grav/Common/Language.php b/system/src/Grav/Common/Language.php index 77c5c8892..f277e1802 100644 --- a/system/src/Grav/Common/Language.php +++ b/system/src/Grav/Common/Language.php @@ -272,6 +272,8 @@ class Language * @return string */ public function getTranslation($lang, $key) { - return $this->config->get('languages.'.$lang.'.'.$key, null); + $languages = $this->config->getLanguages(); + + return $languages->get($lang.'.'.$key, null); } } From 1c90ee6db339050d9157ce10d1d81e67b1e76578 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 6 Jul 2015 20:47:04 -0600 Subject: [PATCH 3/6] more support for translations in plugins/themes --- system/src/Grav/Common/Config/ConfigFinder.php | 2 +- system/src/Grav/Common/Config/Languages.php | 8 +++++--- system/src/Grav/Common/Themes.php | 8 +++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/system/src/Grav/Common/Config/ConfigFinder.php b/system/src/Grav/Common/Config/ConfigFinder.php index 0e0e228d6..7d79b765b 100644 --- a/system/src/Grav/Common/Config/ConfigFinder.php +++ b/system/src/Grav/Common/Config/ConfigFinder.php @@ -143,7 +143,7 @@ class ConfigFinder $filename = "{$path}/{$name}/$find"; if (file_exists($filename)) { - $list["plugins/{$name}"] = ['file' => $filename, 'modified' => filemtime($filename)]; + $list["plugins"] = ['file' => $filename, 'modified' => filemtime($filename)]; } } } diff --git a/system/src/Grav/Common/Config/Languages.php b/system/src/Grav/Common/Config/Languages.php index 03f589aa7..c0141292e 100644 --- a/system/src/Grav/Common/Config/Languages.php +++ b/system/src/Grav/Common/Config/Languages.php @@ -15,11 +15,13 @@ class Languages extends Data public function reformat() { if (isset($this->items['plugins'])) { - foreach ($this->items['plugins'] as $plugin => $data) { - $this->items = array_merge_recursive($this->items, $data); - } + $this->items = array_merge_recursive($this->items, $this->items['plugins']); unset($this->items['plugins']); } } + public function mergeRecursive(array $data) + { + $this->items = array_merge_recursive($this->items, $data); + } } diff --git a/system/src/Grav/Common/Themes.php b/system/src/Grav/Common/Themes.php index ab4696a88..dc4810f10 100644 --- a/system/src/Grav/Common/Themes.php +++ b/system/src/Grav/Common/Themes.php @@ -221,7 +221,13 @@ class Themes extends Iterator protected function loadConfiguration($name, Config $config) { $themeConfig = CompiledYamlFile::instance("themes://{$name}/{$name}" . YAML_EXT)->content(); - $config->joinDefaults("themes.{$name}", $themeConfig); + + if ($this->grav['language']->enabled()) { + $languages = CompiledYamlFile::instance("themes://{$name}/languages". YAML_EXT)->content(); + if ($languages) { + $config->getLanguages()->mergeRecursive($languages); + } + } } } From 3ab1feb7b197af10cb72b8908a156e412836a532 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 6 Jul 2015 20:53:04 -0600 Subject: [PATCH 4/6] handle array for values --- system/src/Grav/Common/Language.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/system/src/Grav/Common/Language.php b/system/src/Grav/Common/Language.php index f277e1802..c1dfbc8fd 100644 --- a/system/src/Grav/Common/Language.php +++ b/system/src/Grav/Common/Language.php @@ -274,6 +274,11 @@ class Language public function getTranslation($lang, $key) { $languages = $this->config->getLanguages(); - return $languages->get($lang.'.'.$key, null); + $translation = $languages->get($lang.'.'.$key, null); + if (is_array($translation)) { + return (string) array_shift($translation); + } + + return $translation; } } From 3a7426ed0463e800f41b2a00a6a56b98a723867d Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 6 Jul 2015 21:08:45 -0600 Subject: [PATCH 5/6] more robust logic --- system/src/Grav/Common/Uri.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/system/src/Grav/Common/Uri.php b/system/src/Grav/Common/Uri.php index 2bd667fa1..65d4df667 100644 --- a/system/src/Grav/Common/Uri.php +++ b/system/src/Grav/Common/Uri.php @@ -95,9 +95,13 @@ class Uri $uri = $language->setActiveFromUri($uri); // redirect to language specific homepage if configured to do so - if ($uri == '/' && $language->enabled() && $config->get('system.languages.home.redirect', true) && !$language->getActive()) { - $prefix = $config->get('system.languages.home.include_lang', true) ? $language->getDefault() . '/' : ''; - $grav->redirect($prefix . Pages::getHomeRoute()); + if ($uri == '/' && $language->enabled() && !$language->getActive()) { + if ($config->get('system.languages.home_redirect.include_route', true)) { + $prefix = $config->get('system.languages.home_redirect.include_lang', true) ? $language->getDefault() . '/' : ''; + $grav->redirect($prefix . Pages::getHomeRoute()); + } elseif ($config->get('system.languages.home_redirect.include_lang', true)) { + $grav->redirect($language->getDefault() . '/'); + } } // split the URL and params From e172f5c82df0ee78657ed9aee1e27f15ea8b8168 Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Mon, 6 Jul 2015 21:20:15 -0600 Subject: [PATCH 6/6] add toggle for translations.fallback --- system/src/Grav/Common/Language.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/system/src/Grav/Common/Language.php b/system/src/Grav/Common/Language.php index c1dfbc8fd..5dd4d5b04 100644 --- a/system/src/Grav/Common/Language.php +++ b/system/src/Grav/Common/Language.php @@ -248,6 +248,13 @@ class Language $lookup = array_shift($args); if ($this->enabled() && $lookup) { + + if ($this->config->get('system.languages.translations.fallback', true)) { + $languages = $this->getFallbackLanguages(); + } else { + $languages = (array) $this->getDefault(); + } + foreach ($this->getFallbackLanguages() as $lang) { $translation = $this->getTranslation($lang, $lookup);