We are basically having the same configuration and pattern to this example :
[
'translator' => [
'locale' => 'en_US',
'translation_file_patterns' => [
[
'base_dir' => __DIR__ . '/../languages/phpArray',
'type' => 'phpArray',
'pattern' => '%s.php',
],
[
'base_dir' => __DIR__ . '/../languages/gettext',
'type' => 'gettext',
'pattern' => '%s.mo',
],
],
],
]
And I need to get a list of all loaded locales from the translator so I can build a language selector. We will be adding more locales later on, and I'd like to keep this dynamic, based on the available translations.
Is this possible, and how?
Ok, I found this an interesting challenge, so I made it :)
Please note that in your config, as it is in the question, you set you locale to en_US
. If you allow users to select a locale, you must update this setting dynamically (this I did not create for you ;-) )
What I created uses FilesystemCache. You could use something else if you like.
To test I created a few files in the language/
folder of a random module:
(note that above use different separators)
Required config
'caches' => [
'FilesystemCache' => [
'adapter' => [
'name' => Filesystem::class,
'options' => [
// Store cached data in this directory.
'cache_dir' => './data/cache',
// Store cached data for 1 hour.
'ttl' => 60*60*1
],
],
'plugins' => [
[
'name' => 'serializer',
'options' => [
],
],
],
],
],
'service_manager' => [
'factories' => [
LocaleOptionsManager::class => LocaleOptionsManagerFactory::class,
],
],
So, here some stuff should become clear. Using data/cache/
as the cache directory. I've also created a LocaleOptionsManager
and a Factory for it.
LocaleOptionsManager
<?php
namespace User\Service;
use Zend\Cache\Storage\StorageInterface;
class LocaleOptionsManager
{
/**
* @var StorageInterface
*/
protected $cache;
/**
* @var array
*/
protected $config;
public function __construct(StorageInterface $cache, array $config)
{
$this->setCache($cache);
$this->setConfig($config);
}
/**
* @param bool $forceCreate
*
* @return array
*/
public function __invoke(bool $forceCreate = false) : array
{
if ($forceCreate) {
// Must recreate cache - remove current locale options
$this->getCache()->removeItem('locale_options');
}
// Loads locale options from cache into $cache. Result (bool) of whether action succeeded loaded into $result
$cache = $this->getCache()->getItem('locale_options', $result);
if ($result) {
// Loading cache (above) succeeded, return cache contents
return $cache;
}
// Above loading of cache didn't succeed or didn't exist, create new cache
$options = [];
foreach ($this->getConfig() as $config) {
if (
array_key_exists('base_dir', $config)
&& isset($config['base_dir'])
&& array_key_exists('pattern', $config)
&& isset($config['pattern'])
) {
// str_replace used to replace "%s" with "*" to make it a regex pattern accepted by glob()
foreach (glob(str_replace('%s', '*', $config['base_dir'] . DIRECTORY_SEPARATOR . $config['pattern'])) as $fileName) {
// Specifically returns filename without extension - see: http://php.net/manual/en/function.pathinfo.php
$options[] = pathinfo($fileName, PATHINFO_FILENAME);
}
}
}
// Save supported locales to cache
if ($this->getCache()->setItem('locale_options', $options)) {
return $options;
}
return [];
}
/**
* @return StorageInterface
*/
public function getCache() : StorageInterface
{
return $this->cache;
}
/**
* @param StorageInterface $cache
*
* @return LocaleOptionsManager
*/
public function setCache(StorageInterface $cache) : LocaleOptionsManager
{
$this->cache = $cache;
return $this;
}
/**
* @return array
*/
public function getConfig() : array
{
return $this->config;
}
/**
* @param array $config
*/
public function setConfig(array $config) : void
{
$this->config = $config;
}
}
As you can see, the real action is in that __invoke
function. The inline comments should explain what's happening, if you have any questions, do ask!
LocaleOptionsManagerFactory
<?php
namespace User\Factory\Service;
use Interop\Container\ContainerInterface;
use User\Service\LocaleOptionsManager;
use Zend\Cache\Storage\StorageInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class LocaleOptionsManagerFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$config = $container->get('Config');
/** @var StorageInterface $cache */
$cache = $container->get('FilesystemCache');
if (
array_key_exists('translator', $config)
&& array_key_exists('translation_file_patterns', $config['translator'])
&& count($config['translator']['translation_file_patterns']) > 0
) {
return new LocaleOptionsManager($cache, $config['translator']['translation_file_patterns']);
}
return new LocaleOptionsManager($cache, []);
}
}
To make sure that not having any translator configured does not crash the application, we're giving it an empty array as the second param. If you look in the actual class, it will just set an empty array as cache (and also return that) if that's all there is.
Well, easy, like you would any other Select really, only you must supply the values.
A form constructor && init()
public function __construct($name = null, array $options = [])
{
if ( ! array_key_exists('locale_options', $options)) {
throw new Exception('Locale options not received. Are you a teapot?', 418);
}
parent::__construct($name, $options);
}
public function init()
{
$this->add(
[
'name' => 'language',
'required' => true,
'type' => Select::class,
'options' => [
'label' => _('Select language'),
'value_options' => $this->options['locale_options'],
],
]
);
// other fields
}
I use a generic Factory to generate my Forms and Fieldsets (see my github if you like), but to add these options in, I did this:
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) : AbstractForm
{
$localeOptions = $container->get(LocaleOptionsManager::class);
$options = [];
// set key/value to same string (a Select has the "key" as the 'value' in the HTML)
foreach ($localeOptions() as $option) {
$options['locale_options'][$option] = $option;
}
$this->options = $options;
// THIS IS MY GENERIC INVOKE - CREATE YOUR OWN FORM HERE
return parent::__invoke($container, $requestedName, $options);
}
All of this gives me the result in the image below: