mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #10685 from xini/fix-i18n-collect-themes
Update i18nTextCollector to collect strings also from themes
This commit is contained in:
commit
7210ac8cd3
@ -3,6 +3,7 @@
|
|||||||
namespace SilverStripe\i18n\Messages;
|
namespace SilverStripe\i18n\Messages;
|
||||||
|
|
||||||
use SilverStripe\Assets\Filesystem;
|
use SilverStripe\Assets\Filesystem;
|
||||||
|
use SilverStripe\Core\Path;
|
||||||
use SilverStripe\i18n\i18n;
|
use SilverStripe\i18n\i18n;
|
||||||
use Symfony\Component\Yaml\Dumper;
|
use Symfony\Component\Yaml\Dumper;
|
||||||
use SilverStripe\i18n\Messages\Symfony\ModuleYamlLoader;
|
use SilverStripe\i18n\Messages\Symfony\ModuleYamlLoader;
|
||||||
@ -43,17 +44,17 @@ class YamlWriter implements Writer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create folder for lang files
|
// Create folder for lang files
|
||||||
$langFolder = $path . '/lang';
|
$langFolder = Path::join($path, 'lang');
|
||||||
if (!file_exists($langFolder ?? '')) {
|
if (!file_exists($langFolder ?? '')) {
|
||||||
Filesystem::makeFolder($langFolder);
|
Filesystem::makeFolder($langFolder);
|
||||||
touch($langFolder . '/_manifest_exclude');
|
touch(Path::join($langFolder, '_manifest_exclude'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// De-normalise messages and convert to yml
|
// De-normalise messages and convert to yml
|
||||||
$content = $this->getYaml($messages, $locale);
|
$content = $this->getYaml($messages, $locale);
|
||||||
|
|
||||||
// Open the English file and write the Master String Table
|
// Open the English file and write the Master String Table
|
||||||
$langFile = $langFolder . '/' . $locale . '.yml';
|
$langFile = Path::join($langFolder, $locale . '.yml');
|
||||||
if ($fh = fopen($langFile ?? '', "w")) {
|
if ($fh = fopen($langFile ?? '', "w")) {
|
||||||
fwrite($fh, $content ?? '');
|
fwrite($fh, $content ?? '');
|
||||||
fclose($fh);
|
fclose($fh);
|
||||||
|
@ -12,6 +12,7 @@ use SilverStripe\Core\Injector\Injectable;
|
|||||||
use SilverStripe\Core\Manifest\ClassLoader;
|
use SilverStripe\Core\Manifest\ClassLoader;
|
||||||
use SilverStripe\Core\Manifest\Module;
|
use SilverStripe\Core\Manifest\Module;
|
||||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||||
|
use SilverStripe\Core\Path;
|
||||||
use SilverStripe\Dev\Debug;
|
use SilverStripe\Dev\Debug;
|
||||||
use SilverStripe\Control\Director;
|
use SilverStripe\Control\Director;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
@ -50,6 +51,8 @@ class i18nTextCollector
|
|||||||
{
|
{
|
||||||
use Injectable;
|
use Injectable;
|
||||||
|
|
||||||
|
private const THEME_PREFIX = 'themes:';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default (master) locale
|
* Default (master) locale
|
||||||
*
|
*
|
||||||
@ -100,6 +103,13 @@ class i18nTextCollector
|
|||||||
*/
|
*/
|
||||||
protected $fileExtensions = ['php', 'ss'];
|
protected $fileExtensions = ['php', 'ss'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all modules and themes
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $modulesAndThemes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $locale
|
* @param $locale
|
||||||
*/
|
*/
|
||||||
@ -179,6 +189,8 @@ class i18nTextCollector
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$modules = $this->getModulesAndThemes();
|
||||||
|
|
||||||
// Write each module language file
|
// Write each module language file
|
||||||
foreach ($entitiesByModule as $moduleName => $entities) {
|
foreach ($entitiesByModule as $moduleName => $entities) {
|
||||||
// Skip empty translations
|
// Skip empty translations
|
||||||
@ -188,7 +200,7 @@ class i18nTextCollector
|
|||||||
|
|
||||||
// Clean sorting prior to writing
|
// Clean sorting prior to writing
|
||||||
ksort($entities);
|
ksort($entities);
|
||||||
$module = ModuleLoader::inst()->getManifest()->getModule($moduleName);
|
$module = $modules[$moduleName];
|
||||||
$this->write($module, $entities);
|
$this->write($module, $entities);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,9 +227,9 @@ class i18nTextCollector
|
|||||||
// Restrict modules we update to just the specified ones (if any passed)
|
// Restrict modules we update to just the specified ones (if any passed)
|
||||||
if (!empty($restrictToModules)) {
|
if (!empty($restrictToModules)) {
|
||||||
// Normalise module names
|
// Normalise module names
|
||||||
$modules = array_filter(array_map(function ($name) {
|
$allModules = $this->getModulesAndThemes();
|
||||||
$module = ModuleLoader::inst()->getManifest()->getModule($name);
|
$modules = array_filter(array_map(function ($name) use ($allModules) {
|
||||||
return $module ? $module->getName() : null;
|
return array_key_exists($name, $allModules) ? $this->getModuleName($name, $allModules[$name]) : null;
|
||||||
}, $restrictToModules ?? []));
|
}, $restrictToModules ?? []));
|
||||||
// Remove modules
|
// Remove modules
|
||||||
foreach (array_diff(array_keys($entitiesByModule ?? []), $modules) as $module) {
|
foreach (array_diff(array_keys($entitiesByModule ?? []), $modules) as $module) {
|
||||||
@ -375,10 +387,10 @@ class i18nTextCollector
|
|||||||
protected function mergeWithExisting($entitiesByModule)
|
protected function mergeWithExisting($entitiesByModule)
|
||||||
{
|
{
|
||||||
// For each module do a simple merge of the default yml with these strings
|
// For each module do a simple merge of the default yml with these strings
|
||||||
|
$modules = $this->getModulesAndThemes();
|
||||||
foreach ($entitiesByModule as $module => $messages) {
|
foreach ($entitiesByModule as $module => $messages) {
|
||||||
// Load existing localisations
|
// Load existing localisations
|
||||||
$masterFile = ModuleLoader::inst()->getManifest()->getModule($module)->getPath() .
|
$masterFile = Path::join($modules[$module]->getPath(), 'lang', $this->defaultLocale . '.yml');
|
||||||
DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->defaultLocale . '.yml';
|
|
||||||
$existingMessages = $this->getReader()->read($this->defaultLocale, $masterFile);
|
$existingMessages = $this->getReader()->read($this->defaultLocale, $masterFile);
|
||||||
|
|
||||||
// Merge
|
// Merge
|
||||||
@ -401,11 +413,11 @@ class i18nTextCollector
|
|||||||
{
|
{
|
||||||
// A master string tables array (one mst per module)
|
// A master string tables array (one mst per module)
|
||||||
$entitiesByModule = [];
|
$entitiesByModule = [];
|
||||||
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
$modules = $this->getModulesAndThemes();
|
||||||
foreach ($modules as $module) {
|
foreach ($modules as $moduleName => $module) {
|
||||||
// we store the master string tables
|
// we store the master string tables
|
||||||
$processedEntities = $this->processModule($module);
|
$processedEntities = $this->processModule($module);
|
||||||
$moduleName = $module->getName();
|
$moduleName = $this->getModuleName($moduleName, $module);
|
||||||
if (isset($entitiesByModule[$moduleName])) {
|
if (isset($entitiesByModule[$moduleName])) {
|
||||||
$entitiesByModule[$moduleName] = array_merge_recursive(
|
$entitiesByModule[$moduleName] = array_merge_recursive(
|
||||||
$entitiesByModule[$moduleName],
|
$entitiesByModule[$moduleName],
|
||||||
@ -450,6 +462,37 @@ class i18nTextCollector
|
|||||||
return $entitiesByModule;
|
return $entitiesByModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all modules and themes installed, including app. Uses the format of
|
||||||
|
* the @link ModuleLoader manifest for themes as well.
|
||||||
|
* Themes can be references with "themes:{theme name}".
|
||||||
|
*/
|
||||||
|
private function getModulesAndThemes(): array
|
||||||
|
{
|
||||||
|
if (!$this->modulesAndThemes) {
|
||||||
|
$modules = ModuleLoader::inst()->getManifest()->getModules();
|
||||||
|
// load themes as modules
|
||||||
|
$themes = array_diff(scandir(THEMES_PATH), ['..', '.']);
|
||||||
|
if ($themes) {
|
||||||
|
foreach ($themes as $theme) {
|
||||||
|
if (is_dir(Path::join(THEMES_PATH, $theme))) {
|
||||||
|
$modules[self::THEME_PREFIX . $theme] = new Module(Path::join(THEMES_PATH, $theme), BASE_PATH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->modulesAndThemes = $modules;
|
||||||
|
}
|
||||||
|
return $this->modulesAndThemes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the module or theme
|
||||||
|
*/
|
||||||
|
private function getModuleName(string $origName, Module $module): string
|
||||||
|
{
|
||||||
|
return strpos($origName, self::THEME_PREFIX) === 0 ? $origName : $module->getName();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write entities to a module
|
* Write entities to a module
|
||||||
*
|
*
|
||||||
@ -462,7 +505,7 @@ class i18nTextCollector
|
|||||||
$this->getWriter()->write(
|
$this->getWriter()->write(
|
||||||
$entities,
|
$entities,
|
||||||
$this->defaultLocale,
|
$this->defaultLocale,
|
||||||
$this->baseSavePath . DIRECTORY_SEPARATOR . $module->getRelativePath()
|
Path::join($this->baseSavePath, $module->getRelativePath())
|
||||||
);
|
);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -517,25 +560,25 @@ class i18nTextCollector
|
|||||||
$modulePath = $module->getPath();
|
$modulePath = $module->getPath();
|
||||||
|
|
||||||
// Search all .ss files in themes
|
// Search all .ss files in themes
|
||||||
if (stripos($module->getRelativePath() ?? '', 'themes' . DIRECTORY_SEPARATOR) === 0) {
|
if (stripos($module->getRelativePath() ?? '', self::THEME_PREFIX) === 0) {
|
||||||
return $this->getFilesRecursive($modulePath, null, 'ss');
|
return $this->getFilesRecursive($modulePath, null, 'ss');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If non-standard module structure, search all root files
|
// If non-standard module structure, search all root files
|
||||||
if (!is_dir($modulePath . DIRECTORY_SEPARATOR . 'code') && !is_dir($modulePath . DIRECTORY_SEPARATOR . 'src')) {
|
if (!is_dir(Path::join($modulePath, 'code')) && !is_dir(Path::join($modulePath, 'src'))) {
|
||||||
return $this->getFilesRecursive($modulePath);
|
return $this->getFilesRecursive($modulePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get code files
|
// Get code files
|
||||||
if (is_dir($modulePath . DIRECTORY_SEPARATOR . 'src')) {
|
if (is_dir(Path::join($modulePath, 'src'))) {
|
||||||
$files = $this->getFilesRecursive($modulePath . DIRECTORY_SEPARATOR . 'src', null, 'php');
|
$files = $this->getFilesRecursive(Path::join($modulePath, 'src'), null, 'php');
|
||||||
} else {
|
} else {
|
||||||
$files = $this->getFilesRecursive($modulePath . DIRECTORY_SEPARATOR . 'code', null, 'php');
|
$files = $this->getFilesRecursive(Path::join($modulePath, 'code'), null, 'php');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search for templates in this module
|
// Search for templates in this module
|
||||||
if (is_dir($modulePath . DIRECTORY_SEPARATOR . 'templates')) {
|
if (is_dir(Path::join($modulePath, 'templates'))) {
|
||||||
$templateFiles = $this->getFilesRecursive($modulePath . DIRECTORY_SEPARATOR . 'templates', null, 'ss');
|
$templateFiles = $this->getFilesRecursive(Path::join($modulePath, 'templates'), null, 'ss');
|
||||||
} else {
|
} else {
|
||||||
$templateFiles = $this->getFilesRecursive($modulePath, null, 'ss');
|
$templateFiles = $this->getFilesRecursive($modulePath, null, 'ss');
|
||||||
}
|
}
|
||||||
@ -987,8 +1030,6 @@ class i18nTextCollector
|
|||||||
return "{$namespace}.{$entity}";
|
return "{$namespace}.{$entity}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function that searches for potential files (templates and code) to be parsed
|
* Helper function that searches for potential files (templates and code) to be parsed
|
||||||
*
|
*
|
||||||
@ -1004,7 +1045,7 @@ class i18nTextCollector
|
|||||||
$fileList = [];
|
$fileList = [];
|
||||||
}
|
}
|
||||||
// Skip ignored folders
|
// Skip ignored folders
|
||||||
if (is_file($folder . DIRECTORY_SEPARATOR . '_manifest_exclude') || preg_match($folderExclude ?? '', $folder ?? '')) {
|
if (is_file(Path::join($folder, '_manifest_exclude')) || preg_match($folderExclude ?? '', $folder ?? '')) {
|
||||||
return $fileList;
|
return $fileList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,15 +3,14 @@
|
|||||||
namespace SilverStripe\i18n\Tests;
|
namespace SilverStripe\i18n\Tests;
|
||||||
|
|
||||||
use SilverStripe\Assets\Filesystem;
|
use SilverStripe\Assets\Filesystem;
|
||||||
use SilverStripe\Core\Config\Config;
|
|
||||||
use SilverStripe\Core\Manifest\ModuleLoader;
|
use SilverStripe\Core\Manifest\ModuleLoader;
|
||||||
|
use SilverStripe\Core\Manifest\ModuleManifest;
|
||||||
|
use SilverStripe\Core\Path;
|
||||||
use SilverStripe\Dev\SapphireTest;
|
use SilverStripe\Dev\SapphireTest;
|
||||||
use SilverStripe\i18n\i18n;
|
use SilverStripe\i18n\i18n;
|
||||||
use SilverStripe\i18n\Messages\YamlWriter;
|
use SilverStripe\i18n\Messages\YamlWriter;
|
||||||
use SilverStripe\i18n\Tests\i18nTest\TestDataObject;
|
|
||||||
use SilverStripe\i18n\Tests\i18nTextCollectorTest\Collector;
|
use SilverStripe\i18n\Tests\i18nTextCollectorTest\Collector;
|
||||||
use SilverStripe\i18n\TextCollection\i18nTextCollector;
|
use SilverStripe\i18n\TextCollection\i18nTextCollector;
|
||||||
use SilverStripe\Security\Member;
|
|
||||||
|
|
||||||
class i18nTextCollectorTest extends SapphireTest
|
class i18nTextCollectorTest extends SapphireTest
|
||||||
{
|
{
|
||||||
@ -734,6 +733,34 @@ PHP;
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testCollectFromTheme()
|
||||||
|
{
|
||||||
|
// create new module manifest only containing the test theme
|
||||||
|
$moduleManifest = new ModuleManifest($this->alternateBasePath);
|
||||||
|
$moduleManifest->addModule(Path::join($this->alternateBasePath, 'themes', 'testtheme1'));
|
||||||
|
$this->pushModuleManifest($moduleManifest);
|
||||||
|
|
||||||
|
$c = i18nTextCollector::create();
|
||||||
|
$entities = $c->collect();
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('testtheme1', $entities);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
[
|
||||||
|
'i18nTestTheme1.LAYOUTTEMPLATE' => 'Theme1 Layout Template',
|
||||||
|
'i18nTestTheme1.MAINTEMPLATE' => 'Theme1 Main Template',
|
||||||
|
'i18nTestTheme1.ss.LAYOUTTEMPLATENONAMESPACE' => 'Theme1 Layout Template no namespace',
|
||||||
|
'i18nTestTheme1.ss.REPLACEMENTNONAMESPACE' => 'Theme1 My replacement no namespace: {replacement}',
|
||||||
|
'i18nTestTheme1Include.REPLACEMENTINCLUDENAMESPACE' => 'Theme1 My include replacement: {replacement}',
|
||||||
|
'i18nTestTheme1Include.WITHNAMESPACE' => 'Theme1 Include Entity with Namespace',
|
||||||
|
'i18nTestTheme1Include.ss.NONAMESPACE' => 'Theme1 Include Entity without Namespace',
|
||||||
|
'i18nTestTheme1Include.ss.REPLACEMENTINCLUDENONAMESPACE' => 'Theme1 My include replacement no namespace: {replacement}',
|
||||||
|
'i18nTestTheme1.REPLACEMENTNAMESPACE' => 'Theme1 My replacement: {replacement}',
|
||||||
|
],
|
||||||
|
$entities['testtheme1']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test that duplicate keys are resolved to the appropriate modules
|
* Test that duplicate keys are resolved to the appropriate modules
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user