622 lines
16 KiB
PHP
622 lines
16 KiB
PHP
<?php namespace App\LaravelGettext;
|
|
|
|
use RecursiveDirectoryIterator;
|
|
use RecursiveIteratorIterator;
|
|
use App\LaravelGettext\Config\Models\Config;
|
|
use App\LaravelGettext\Exceptions\DirectoryNotFoundException;
|
|
use App\LaravelGettext\Exceptions\FileCreationException;
|
|
use App\LaravelGettext\Exceptions\LocaleFileNotFoundException;
|
|
|
|
class FileSystem
|
|
{
|
|
/**
|
|
* Package configuration model
|
|
*
|
|
* @var Config
|
|
*/
|
|
protected $configuration;
|
|
|
|
/**
|
|
* File system base path
|
|
* All paths will be relative to this
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $basePath;
|
|
|
|
/**
|
|
* Storage path for file generation
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $storagePath;
|
|
|
|
/**
|
|
* Storage directory name for view compilation
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $storageContainer;
|
|
|
|
/**
|
|
* The folder name in which the language files are stored
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $folderName;
|
|
|
|
/**
|
|
* @param Config $config
|
|
* @param $basePath
|
|
* @param $storagePath
|
|
*/
|
|
public function __construct(Config $config, $basePath, $storagePath)
|
|
{
|
|
$this->configuration = $config;
|
|
$this->basePath = $basePath;
|
|
|
|
$this->storagePath = $storagePath;
|
|
$this->storageContainer = "framework";
|
|
$this->folderName = 'i18n';
|
|
}
|
|
|
|
/**
|
|
* Build views in order to parse php files
|
|
*
|
|
* @param Array $viewPaths
|
|
* @param String $domain
|
|
*
|
|
* @return Boolean status
|
|
*/
|
|
/**
|
|
* Build views in order to parse php files
|
|
*
|
|
* @param array $viewPaths
|
|
* @param string $domain
|
|
* @return bool
|
|
* @throws FileCreationException
|
|
*/
|
|
public function compileViews(array $viewPaths, $domain)
|
|
{
|
|
// Check the output directory
|
|
$targetDir = $this->storagePath . DIRECTORY_SEPARATOR . $this->storageContainer;
|
|
|
|
if (!file_exists($targetDir)) {
|
|
$this->createDirectory($targetDir);
|
|
}
|
|
|
|
// Domain separation
|
|
$domainDir = $targetDir . DIRECTORY_SEPARATOR . $domain;
|
|
$this->clearDirectory($domainDir);
|
|
$this->createDirectory($domainDir);
|
|
|
|
foreach ($viewPaths as $path) {
|
|
$path = $this->basePath . DIRECTORY_SEPARATOR . $path;
|
|
|
|
if (!$realPath = realPath($path)) {
|
|
throw new Exceptions\DirectoryNotFoundException("Failed to resolve $path, please check that it exists");
|
|
}
|
|
|
|
$fs = new \Illuminate\Filesystem\Filesystem($path);
|
|
$files = $fs->allFiles($realPath);
|
|
|
|
$compiler = new \Illuminate\View\Compilers\BladeCompiler($fs, $domainDir);
|
|
|
|
foreach ($files as $file) {
|
|
$filePath = $file->getRealPath();
|
|
$compiler->setPath($filePath);
|
|
|
|
$contents = $compiler->compileString($fs->get($filePath));
|
|
|
|
$compiledPath = $compiler->getCompiledPath($compiler->getPath());
|
|
|
|
$fs->put(
|
|
$compiledPath . '.php',
|
|
$contents
|
|
);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Constructs and returns the full path to the translation files
|
|
*
|
|
* @param null $append
|
|
* @return string
|
|
*/
|
|
public function getDomainPath($append = null)
|
|
{
|
|
$path = [
|
|
$this->basePath,
|
|
$this->configuration->getTranslationsPath(),
|
|
$this->folderName,
|
|
];
|
|
|
|
if (!is_null($append)) {
|
|
array_push($path, $append);
|
|
}
|
|
|
|
return implode(DIRECTORY_SEPARATOR, $path);
|
|
}
|
|
|
|
/**
|
|
* Creates a configured .po file on $path
|
|
* If PHP are not able to create the file the content will be returned instead
|
|
*
|
|
* @param string $path
|
|
* @param string $locale
|
|
* @param string $domain
|
|
* @param bool|true $write
|
|
* @return int|string
|
|
*/
|
|
public function createPOFile($path, $locale, $domain, $write = true)
|
|
{
|
|
$project = $this->configuration->getProject();
|
|
$timestamp = date("Y-m-d H:iO");
|
|
$translator = $this->configuration->getTranslator();
|
|
$encoding = $this->configuration->getEncoding();
|
|
|
|
$relativePath = $this->configuration->getRelativePath();
|
|
|
|
$keywords = implode(';', $this->configuration->getKeywordsList());
|
|
|
|
$template = 'msgid ""' . "\n";
|
|
$template .= 'msgstr ""' . "\n";
|
|
$template .= '"Project-Id-Version: ' . $project . '\n' . "\"\n";
|
|
$template .= '"POT-Creation-Date: ' . $timestamp . '\n' . "\"\n";
|
|
$template .= '"PO-Revision-Date: ' . $timestamp . '\n' . "\"\n";
|
|
$template .= '"Last-Translator: ' . $translator . '\n' . "\"\n";
|
|
$template .= '"Language-Team: ' . $translator . '\n' . "\"\n";
|
|
$template .= '"Language: ' . $locale . '\n' . "\"\n";
|
|
$template .= '"MIME-Version: 1.0' . '\n' . "\"\n";
|
|
$template .= '"Content-Type: text/plain; charset=' . $encoding . '\n' . "\"\n";
|
|
$template .= '"Content-Transfer-Encoding: 8bit' . '\n' . "\"\n";
|
|
$template .= '"X-Generator: Poedit 1.5.4' . '\n' . "\"\n";
|
|
$template .= '"X-Poedit-KeywordsList: ' . $keywords . '\n' . "\"\n";
|
|
$template .= '"X-Poedit-Basepath: ' . $relativePath . '\n' . "\"\n";
|
|
$template .= '"X-Poedit-SourceCharset: ' . $encoding . '\n' . "\"\n";
|
|
|
|
// Source paths
|
|
$sourcePaths = $this->configuration->getSourcesFromDomain($domain);
|
|
|
|
// Compiled views on paths
|
|
if (count($sourcePaths)) {
|
|
|
|
// View compilation
|
|
$this->compileViews($sourcePaths, $domain);
|
|
array_push($sourcePaths, $this->getStorageForDomain($domain));
|
|
|
|
$i = 0;
|
|
|
|
foreach ($sourcePaths as $sourcePath) {
|
|
$template .= '"X-Poedit-SearchPath-' . $i . ': ' . $sourcePath . '\n' . "\"\n";
|
|
$i++;
|
|
}
|
|
|
|
}
|
|
|
|
if (!$write) {
|
|
return $template . "\n";
|
|
}
|
|
|
|
// File creation
|
|
$file = fopen($path, "w");
|
|
$result = fwrite($file, $template);
|
|
fclose($file);
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Validate if the directory can be created
|
|
*
|
|
* @param $path
|
|
* @throws FileCreationException
|
|
*/
|
|
protected function createDirectory($path)
|
|
{
|
|
if (!file_exists($path) && !mkdir($path)) {
|
|
throw new FileCreationException(
|
|
sprintf('Can\'t create the directory: %s', $path)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a new locale directory + .po file
|
|
*
|
|
* @param String $localePath
|
|
* @param String $locale
|
|
* @throws FileCreationException
|
|
*/
|
|
public function addLocale($localePath, $locale)
|
|
{
|
|
$data = array(
|
|
$localePath,
|
|
"LC_MESSAGES"
|
|
);
|
|
|
|
if (!file_exists($localePath)) {
|
|
$this->createDirectory($localePath);
|
|
}
|
|
|
|
if ($this->configuration->getCustomLocale()) {
|
|
$data[1] = 'C';
|
|
|
|
$gettextPath = implode(DIRECTORY_SEPARATOR, $data);
|
|
if (!file_exists($gettextPath)) {
|
|
$this->createDirectory($gettextPath);
|
|
}
|
|
|
|
$data[2] = 'LC_MESSAGES';
|
|
}
|
|
|
|
$gettextPath = implode(DIRECTORY_SEPARATOR, $data);
|
|
if (!file_exists($gettextPath)) {
|
|
$this->createDirectory($gettextPath);
|
|
}
|
|
|
|
|
|
// File generation for each domain
|
|
foreach ($this->configuration->getAllDomains() as $domain) {
|
|
$data[3] = $domain . ".po";
|
|
|
|
$localePOPath = implode(DIRECTORY_SEPARATOR, $data);
|
|
|
|
if (!$this->createPOFile($localePOPath, $locale, $domain)) {
|
|
throw new FileCreationException(
|
|
sprintf('Can\'t create the file: %s', $localePOPath)
|
|
);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Update the .po file headers by domain
|
|
* (mainly source-file paths)
|
|
*
|
|
* @param $localePath
|
|
* @param $locale
|
|
* @param $domain
|
|
* @return bool
|
|
* @throws LocaleFileNotFoundException
|
|
*/
|
|
public function updateLocale($localePath, $locale, $domain)
|
|
{
|
|
$data = [
|
|
$localePath,
|
|
"LC_MESSAGES",
|
|
$domain . ".po",
|
|
];
|
|
|
|
if ($this->configuration->getCustomLocale()) {
|
|
$customLocale = array('C');
|
|
array_splice($data, 1, 0, $customLocale);
|
|
}
|
|
|
|
$localePOPath = implode(DIRECTORY_SEPARATOR, $data);
|
|
|
|
if (!file_exists($localePOPath) || !$localeContents = file_get_contents($localePOPath)) {
|
|
throw new LocaleFileNotFoundException(
|
|
sprintf('Can\'t read %s verify your locale structure', $localePOPath)
|
|
);
|
|
}
|
|
|
|
$newHeader = $this->createPOFile(
|
|
$localePOPath,
|
|
$locale,
|
|
$domain,
|
|
false
|
|
);
|
|
|
|
// Header replacement
|
|
$localeContents = preg_replace('/^([^#])+:?/', $newHeader, $localeContents);
|
|
|
|
if (!file_put_contents($localePOPath, $localeContents)) {
|
|
throw new LocaleFileNotFoundException(
|
|
sprintf('Can\'t write on %s', $localePOPath)
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Return the relative path from a file or directory to anothe
|
|
*
|
|
* @param string $from
|
|
* @param string $to
|
|
* @return string
|
|
* @author Laurent Goussard
|
|
*/
|
|
public function getRelativePath($from, $to)
|
|
{
|
|
// Compatibility fixes for Windows paths
|
|
$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
|
|
$to = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
|
|
$from = str_replace('\\', '/', $from);
|
|
$to = str_replace('\\', '/', $to);
|
|
|
|
$from = explode('/', $from);
|
|
$to = explode('/', $to);
|
|
$relPath = $to;
|
|
|
|
foreach ($from as $depth => $dir) {
|
|
if ($dir !== $to[$depth]) {
|
|
// Number of remaining directories
|
|
$remaining = count($from) - $depth;
|
|
|
|
if ($remaining > 1) {
|
|
// Add traversals up to first matching directory
|
|
$padLength = (count($relPath) + $remaining - 1) * -1;
|
|
|
|
$relPath = array_pad(
|
|
$relPath,
|
|
$padLength,
|
|
'..'
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
$relPath[0] = './' . $relPath[0];
|
|
}
|
|
|
|
array_shift($relPath);
|
|
}
|
|
|
|
return implode('/', $relPath);
|
|
|
|
}
|
|
|
|
/**
|
|
* Checks the required directory
|
|
* Optionally checks each local directory, if $checkLocales is true
|
|
*
|
|
* @param bool|false $checkLocales
|
|
* @return bool
|
|
* @throws DirectoryNotFoundException
|
|
*/
|
|
public function checkDirectoryStructure($checkLocales = false)
|
|
{
|
|
// Application base path
|
|
if (!file_exists($this->basePath)) {
|
|
throw new Exceptions\DirectoryNotFoundException(
|
|
sprintf(
|
|
'Missing root path directory: %s, check the \'base-path\' key in your configuration.',
|
|
$this->basePath
|
|
)
|
|
);
|
|
}
|
|
|
|
// Domain path
|
|
$domainPath = $this->getDomainPath();
|
|
|
|
// Translation files domain path
|
|
if (!file_exists($domainPath)) {
|
|
throw new Exceptions\DirectoryNotFoundException(
|
|
sprintf(
|
|
'Missing base required directory: %s, remember to run \'artisan gettext:create\' the first time',
|
|
$domainPath
|
|
)
|
|
);
|
|
}
|
|
|
|
if (!$checkLocales) {
|
|
return true;
|
|
}
|
|
|
|
foreach ($this->configuration->getSupportedLocales() as $locale) {
|
|
// Default locale is not needed
|
|
if ($locale == $this->configuration->getLocale()) {
|
|
continue;
|
|
}
|
|
|
|
$localePath = $this->getDomainPath($locale);
|
|
|
|
if (!file_exists($localePath)) {
|
|
throw new Exceptions\DirectoryNotFoundException(
|
|
sprintf(
|
|
'Missing locale required directory: %s, maybe you forgot to run \'artisan gettext:update\'',
|
|
$locale
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Creates the localization directories and files by domain
|
|
*
|
|
* @return array
|
|
* @throws FileCreationException
|
|
*/
|
|
public function generateLocales()
|
|
{
|
|
// Application base path
|
|
if (!file_exists($this->getDomainPath())) {
|
|
$this->createDirectory($this->getDomainPath());
|
|
}
|
|
$localePaths = [];
|
|
|
|
// Locale directories
|
|
foreach ($this->configuration->getSupportedLocales() as $locale) {
|
|
$localePath = $this->getDomainPath($locale);
|
|
|
|
if (!file_exists($localePath)) {
|
|
// Locale directory is created
|
|
$this->addLocale($localePath, $locale);
|
|
|
|
array_push($localePaths, $localePath);
|
|
|
|
}
|
|
}
|
|
|
|
return $localePaths;
|
|
}
|
|
|
|
|
|
/**
|
|
* Gets the package configuration model.
|
|
*
|
|
* @return Config
|
|
*/
|
|
public function getConfiguration()
|
|
{
|
|
return $this->configuration;
|
|
}
|
|
|
|
/**
|
|
* Set the package configuration model
|
|
*
|
|
* @param Config $configuration
|
|
* @return $this
|
|
*/
|
|
public function setConfiguration(Config $configuration)
|
|
{
|
|
$this->configuration = $configuration;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the filesystem base path
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getBasePath()
|
|
{
|
|
return $this->basePath;
|
|
}
|
|
|
|
/**
|
|
* Set the filesystem base path
|
|
*
|
|
* @param $basePath
|
|
* @return $this
|
|
*/
|
|
public function setBasePath($basePath)
|
|
{
|
|
$this->basePath = $basePath;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the storage path
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getStoragePath()
|
|
{
|
|
return $this->storagePath;
|
|
}
|
|
|
|
/**
|
|
* Set the storage path
|
|
*
|
|
* @param $storagePath
|
|
* @return $this
|
|
*/
|
|
public function setStoragePath($storagePath)
|
|
{
|
|
$this->storagePath = $storagePath;
|
|
return $this;
|
|
}
|
|
|
|
/**
|
|
* Get the full path for domain storage directory
|
|
*
|
|
* @param $domain
|
|
* @return String
|
|
*/
|
|
public function getStorageForDomain($domain)
|
|
{
|
|
$domainPath = $this->storagePath .
|
|
DIRECTORY_SEPARATOR .
|
|
$this->storageContainer .
|
|
DIRECTORY_SEPARATOR .
|
|
$domain;
|
|
|
|
return $this->getRelativePath($this->basePath, $domainPath);
|
|
}
|
|
|
|
/**
|
|
* Removes the directory contents recursively
|
|
*
|
|
* @param string $path
|
|
* @return null|boolean
|
|
*/
|
|
public static function clearDirectory($path)
|
|
{
|
|
if (!file_exists($path)) {
|
|
return null;
|
|
}
|
|
|
|
$files = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
|
|
RecursiveIteratorIterator::CHILD_FIRST
|
|
);
|
|
|
|
foreach ($files as $fileinfo) {
|
|
// if the file isn't a .gitignore file we should remove it.
|
|
if ($fileinfo->getFilename() !== '.gitignore') {
|
|
$todo = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
|
|
$todo($fileinfo->getRealPath());
|
|
}
|
|
}
|
|
|
|
// since the folder now contains a .gitignore we can't remove it
|
|
//rmdir($path);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the folder name
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getFolderName()
|
|
{
|
|
return $this->folderName;
|
|
}
|
|
|
|
/**
|
|
* Set the folder name
|
|
*
|
|
* @param $folderName
|
|
*/
|
|
public function setFolderName($folderName)
|
|
{
|
|
$this->folderName = $folderName;
|
|
}
|
|
|
|
/**
|
|
* Returns the full path for a .po/.mo file from its domain and locale
|
|
*
|
|
* @param $locale
|
|
* @param $domain
|
|
*
|
|
* @param string $type
|
|
*
|
|
* @return string
|
|
*/
|
|
public function makeFilePath($locale, $domain, $type = 'po')
|
|
{
|
|
$filePath = implode(
|
|
DIRECTORY_SEPARATOR, [
|
|
$locale,
|
|
'LC_MESSAGES',
|
|
$domain . "." . $type
|
|
]
|
|
);
|
|
|
|
return $this->getDomainPath($filePath);
|
|
}
|
|
|
|
}
|