From 00f66e201f5b7ee1a4cd4118f2236008ddfeeea3 Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Fri, 22 Jun 2012 12:13:43 +0200 Subject: [PATCH] ENHANCEMENT getlocalization build support Squashed commits (sorry, too hard to untangle): - Moved descriptions from custom "phing help" target to the more standard 'phing -l' command, which keeps the descriptions in one place, rather than duplicating them between xml comments, "description" attrs and the "help" target. - Prefixed helper targets to make it clear that they're internal (= require temporary properties to work) --- build.properties.default | 6 + build.xml | 155 +++++++++++++++------ tools/UpdateTranslationsTask.php | 227 +++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+), 41 deletions(-) create mode 100644 build.properties.default create mode 100644 tools/UpdateTranslationsTask.php diff --git a/build.properties.default b/build.properties.default new file mode 100644 index 00000000..fbd41f73 --- /dev/null +++ b/build.properties.default @@ -0,0 +1,6 @@ +getlocalization.framework.project = sapphire +getlocalization.framework.user = silverstripe +getlocalization.framework.password = +getlocalization.cms.project = silverstripe_cms +getlocalization.cms.user = silverstripe +getlocalization.cms.password = \ No newline at end of file diff --git a/build.xml b/build.xml index 326a1d68..1950b7b9 100644 --- a/build.xml +++ b/build.xml @@ -16,6 +16,7 @@ phing help + @@ -28,22 +29,19 @@ phing help + + SilverStripe Project Build ------------------------------------ This build file contains targets to assist in creating new SilverStripe builds and releases. - -Important targets: - -* archive - Creates a tar.gz or zip file from the current source, removing version control specific files -* checkout - Switches all working copies to the specified tag or branch -* tag - Creates a new git tag in all the nested working copies (optionally pushes the created tag) -* pushtags - Pushes all local tags to their respective origin repositories -* update_modules - Checks out repositories defined in the 'dependent-modules' file into the current directory -* add_module - Checks out a module at a specific repository URL -* changelog - Create a changelog.md file from the repositories specified in the 'changelog-definitions' file +Run "phing -l" to get a full list of available targets. Options: @@ -65,17 +63,16 @@ Options: - + - - + - - + - - - + - - + - @@ -108,17 +105,23 @@ Options: - + - + + - - + @@ -131,7 +134,7 @@ Options: - + Push local tags to origin? @@ -142,13 +145,15 @@ Options: - - - + + - - + @@ -179,7 +184,7 @@ Options: - + @@ -325,7 +330,8 @@ Options: - + Please choose a version @@ -350,7 +356,8 @@ Your friendly automated release script. - + @@ -358,14 +365,13 @@ Your friendly automated release script. - - + - - + @@ -391,10 +397,77 @@ Your friendly automated release script. - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tools/UpdateTranslationsTask.php b/tools/UpdateTranslationsTask.php new file mode 100644 index 00000000..3420b0ed --- /dev/null +++ b/tools/UpdateTranslationsTask.php @@ -0,0 +1,227 @@ + 'lang', + 'js' => 'javascript/lang' + ); + + /** + * @var String If set, will use existing files rather than try to download them. + */ + protected $downloadPath; + + public function getGlUser() { + return $this->glUser; + } + + public function setGlUser($newGlUser) { + $this->glUser = $newGlUser; + return $this; + } + + public function getGlPassword() { + return $this->glPassword; + } + + public function setGlPassword($newGlPassword) { + $this->glPassword = $newGlPassword; + return $this; + } + + public function setModulePath($path) { + $this->modulePath = $path; + } + + public function setDownloadPath($path) { + $this->downloadPath = $path; + } + + public function setGlProductName($name) { + $this->glProductName = $name; + } + + public function main() { + if (!is_dir($this->modulePath)) { + throw new BuildException("Invalid target directory: $this->modulePath"); + } + + $downloadPath = $this->downloadPath ? $this->downloadPath : $this->download(); + $files = $this->findFiles($downloadPath); + foreach($files as $file) { + $ext = pathinfo($file, PATHINFO_EXTENSION); + if($ext == 'yml') { + $this->processYmlFile($file); + } elseif($ext == 'js') { + $this->processJavascriptFile($file); + } else { + throw new LogicException(sprintf('Unknown extension: %s', $ext)); + } + } + + } + + /** + * @return File path to a folder structure containing translation files + */ + protected function download() { + $tmpFolder = tempnam(sys_get_temp_dir(), $this->glProductName . '-'); + $tmpFilePath = $tmpFolder . '.zip'; + rename($tmpFolder, $tmpFilePath); + $url = sprintf(self::$url_translations, $this->glProductName); + + $this->log(sprintf("Downloading $url to $tmpFilePath")); + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_USERPWD, $this->glUser. ":" . $this->glPassword); + $data = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if($code >= 400) { + throw new BuildException(sprintf( + 'Error downloading %s: %s %s', + $url, + $code, + $data + )); + } + if(curl_error($ch)) { + throw new BuildException(sprintf( + 'Error downloading %s: %s (#%s)', + $url, + curl_error($ch), + curl_errno($ch) + )); + } + + curl_close($ch); + file_put_contents($tmpFilePath, $data); + + $this->log(sprintf("Extracting to $tmpFolder")); + $this->exec("unzip $tmpFilePath -d $tmpFolder"); + + return $tmpFolder; + } + + /** + * @param String Absolute path to a folder structure containing translation files + * @return Array with file paths + */ + protected function findFiles($path) { + // Recursively find files with certain extensions. + // Can't use glob() since its non-recursive. + // Directory structure doesn't matter here. + $files = array(); + $matches = new RegexIterator( + new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path) + ), + '/^.+\.(yml$|js)/i', + RecursiveRegexIterator::GET_MATCH + ); + foreach($matches as $match) $files[] = $match[0]; + return $files; + } + + protected function processYmlFile($file) { + $this->log(sprintf("Processing $file")); + + // Rename locale to correct convention (underscored rather than dashed). + // GL wants filenames to adhere to its saved locales, but SS framework + // can't easily change to that format for backwards compat reasons, so we need to convert. + // The passed in file name doesn't really matter here, only the contained locale. + // By convention, the first line in the YAML file is always the locale used, as a YAML "root key". + $content = file_get_contents($file); + preg_match('/^([\w-_]*):/', $content, $matches); + $locale = $matches[1]; + $locale = str_replace('-', '_', $locale); + $locale = str_replace(':', '', $locale); + + // Convert faulty multiline double quoted string YAML + // to block format, in order to allow the YAML Parser to open it later + // TODO Remove once getlocalization.com has fixed their output format (see support.getlocalization.com #2022) + $isBlock = false; + $blockIndex = -1; + $lines = explode(PHP_EOL, $content); + $keyedLineRegex = '/^\s*[\w\d-_]*:\s*/'; + $leadingQuoteRegex = '/^\s*\"/'; + $trailingQuoteRegex = '/[^\\\\]\"$/'; + foreach($lines as $i => $line) { + preg_match($keyedLineRegex, $line, $matches); + $key = $matches ? $matches[0] : null; + $val = trim(preg_replace($keyedLineRegex, '', $line)); + // If its a multiline double quoted string (no unescaped closing quote) + if($val && $line != '"' && preg_match($leadingQuoteRegex, $val) && !preg_match($trailingQuoteRegex, $val)) { + $isBlock = true; + $blockIndex = $i; + } elseif($key) { + $isBlock = false; + $blockIndex = -1; + } else { + $lines[$blockIndex] .= $line; + unset($lines[$i]); + } + } + $content = implode(PHP_EOL, $lines); + + // Parse YML as a sanity check, + // and reorder alphabetically by key to ensure consistent diffs. + require_once '../framework/thirdparty/zend_translate_railsyaml/library/Translate/Adapter/thirdparty/sfYaml/lib/sfYaml.php'; + require_once '../framework/thirdparty/zend_translate_railsyaml/library/Translate/Adapter/thirdparty/sfYaml/lib/sfYamlParser.php'; + require_once '../framework/thirdparty/zend_translate_railsyaml/library/Translate/Adapter/thirdparty/sfYaml/lib/sfYamlDumper.php'; + $yamlHandler = new sfYaml(); + $yml = $yamlHandler->parse($content); + if(isset($yml[$locale]) && is_array($yml[$locale])) { + ksort($yml[$locale]); + foreach($yml[$locale] as $k => &$v) { + if(is_array($v)) ksort($v); + } + } + $content = $yamlHandler->dump($yml, 99); // don't inline first levels + + // Save into correct path, overwriting existing files + $path = $this->modulePath . '/' . $this->langFolders['yml'] . '/' . $locale . '.yml'; + $this->log("Saving to $path"); + file_put_contents($path, $content); + } + + protected function processJavascriptFile($file) { + $this->log(sprintf("Processing $file")); + + $locale = pathinfo($file, PATHINFO_FILENAME); + $locale = str_replace('-', '_', $locale); + + // Save into correct path, overwriting existing files + $path = $this->modulePath . '/' . $this->langFolders['js'] . '/' . $locale . '.yml'; + $this->log("Saving to $path"); + file_put_contents($path, file_get_contents($file)); + } +} + + +?> \ No newline at end of file