Skip to content
Browse files

MINOR Initial commit

  • Loading branch information...
0 parents commit 06c4d4b24330ef3390c3e8d8ddb4b6d467b35da3 @chillu committed
Showing with 287 additions and 0 deletions.
  1. +24 −0 LICENSE
  2. +31 −0 README.md
  3. 0 _config.php
  4. +200 −0 code/i18nYMLConverter.php
  5. +32 −0 code/i18nYMLConverterTask.php
24 LICENSE
@@ -0,0 +1,24 @@
+* Copyright (c) 2011, Ingo Schommer
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions are met:
+* * Redistributions of source code must retain the above copyright
+* notice, this list of conditions and the following disclaimer.
+* * Redistributions in binary form must reproduce the above copyright
+* notice, this list of conditions and the following disclaimer in the
+* documentation and/or other materials provided with the distribution.
+* * Neither the name of the <organization> nor the
+* names of its contributors may be used to endorse or promote products
+* derived from this software without specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY Ingo Schommer. ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+* DISCLAIMED. IN NO EVENT SHALL Silverstripe Ltd. BE LIABLE FOR ANY
+* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 README.md
@@ -0,0 +1,31 @@
+# i18n YML Format Converter #
+
+## Overview ##
+
+Converts SilverStripe's language files from a PHP array (the `$lang` global)
+to a YML file adhereing to the Rails 2.2 i18n conventions.
+This allows us to import the files into getlocalization.com,
+and export the translations from there again.
+
+Also normalizes the locale names a bit,
+in anticipation of allowing translation fallbacks
+in SS3 through the `Zend_Translate` framework.
+For example, 'en_US.php' becomes 'en.yml'.
+
+Removes duplicate translations, and moves outdated
+translations between sapphire and cms.
+
+## Maintainers ##
+
+ * Ingo Schommer (ingo at silverstripe dot com)
+
+## Usage ##
+
+All modules: `sake dev/tasks/i18nYMLConverterTask`
+Single module: `sake dev/tasks/i18nYMLConverterTask module=<mymodule>`
+
+By default, the newly created
+
+## TODO ##
+
+ * Write custom Zend_Translate backend to deal with the YML files in SilverStripe
0 _config.php
No changes.
200 code/i18nYMLConverter.php
@@ -0,0 +1,200 @@
+<?php
+class i18nYMLConverter {
+
+ /**
+ * @var String
+ * The absolute base path to a SilverStripe web root.
+ */
+ public $basePath;
+
+ /**
+ * @var String
+ * Needs to have the same directory structure as the actual project.
+ * @todo Create matching directory structure automatically.
+ */
+ public $baseSavePath;
+
+ public $langFolder = 'lang';
+
+ /**
+ * @var String YML indentation
+ */
+ public $indent = ' ';
+
+ /**
+ * @var Array Replaces certain locales,
+ * e.g. to make them more generic.
+ * In addition to built-in logic which replaces "double"
+ * locales with their simpler main locale, e.g "fr_FR" becomes "fr".
+ */
+ public $localeMap = array(
+ 'en_US' => 'en',
+ 'eo-XX' => 'eo',
+ );
+
+ public $retainTranslationsForMissingMaster = false;
+
+ /**
+ * @param $locale
+ */
+ function __construct() {
+ $this->basePath = Director::baseFolder();
+ $this->baseSavePath = Director::baseFolder();
+ }
+
+ function run($restrictToModules = null) {
+ $modules = array();
+
+ // A master string tables array (one mst per module)
+ $entitiesByModule = array();
+
+ //Search for and process existent modules, or use the passed one instead
+ if($restrictToModules && count($restrictToModules)) {
+ foreach($restrictToModules as $restrictToModule) {
+ $modules[] = basename($restrictToModule);
+ }
+ } else {
+ $modules = scandir($this->basePath);
+ }
+
+ foreach($modules as $module) {
+ // Only search for calls in folder with a _config.php file
+ // (which means they are modules, including themes folder)
+ $isValidModuleFolder = (
+ is_dir("$this->basePath/$module")
+ && is_file("$this->basePath/$module/_config.php")
+ && substr($module,0,1) != '.'
+ );
+
+ if(!$isValidModuleFolder) continue;
+
+ // we store the master string tables
+ $processedEntities = $this->processModule($module);
+ }
+ }
+
+ /**
+ * We can't simply query the already loaded $lang global as that doesn't
+ * distinguish between modules any longer.
+ *
+ * @param String
+ * @return array
+ */
+ function getTranslationsByModule($module) {
+ global $lang;
+
+ $return = array();
+ $files = new GlobIterator("$this->basePath/$module/$this->langFolder/*.php");
+ foreach($files as $file) {
+ $fileLocale = preg_replace('/\.php/', '', $file->getFileName());
+
+ // Overwrite global, but needs the en_US key because of the array_merge() call in the lang files.
+ $lang = array('en_US' => array());
+ $lang[$fileLocale] = array();
+ require($file->getPathName());
+ if($fileLocale != 'en_US') unset($lang['en_US']);
+
+ $return[$fileLocale] = $lang[$fileLocale];
+ }
+
+ return $return;
+ }
+
+ function processModule($module) {
+ $translations = $this->getTranslationsByModule($module);
+ $master = $translations['en_US'];
+
+ // Special case: Move translations according to location of master entity,
+ // mainly to fix up the file migration between modules during the 3.0 release.
+ // Adds a bit of duplicated parsing effort, but its a one off process anyway.
+ if(in_array($module, array('sapphire', 'cms')) ) {
+ $otherModule = ($module == 'cms') ? 'sapphire' : 'cms';
+ $otherTranslations = $this->getTranslationsByModule($otherModule);
+ $otherMaster = $otherMasterLocales['en_US'];
+ foreach($otherTranslations as $locale => $namespaces) {
+ if($locale == 'en_US') continue;
+ foreach($namespaces as $namespace => $entities) {
+ foreach($entities as $id => $entity) {
+ // If translated entity in other module is contained in master lang for
+ // currently processed module, move it there instead.
+ if(isset($master[$namespace][$id])) {
+ if(!isset($translations[$locale][$namespace])) $translations[$locale][$namespace] = array();
+ $translations[$locale][$namespace][$id] = $entity;
+ // Note: Translations for non-existent master keys are cleaned up further down
+ }
+
+ // Check for duplicates: If a translation exists in both sapphire and cms,
+ // remove it from the cms module. We assume entity values are the same,
+ // otherwise they'd override each other anyway. Technically it doesn't matter
+ // for SilverStripe in which module the translation is contained, but we avoid
+ // unnecessary work for translators by removing duplicates.
+
+ // TODO Removes too many entities (e.g. TableListField_PageControls.ss from both modules)
+ // if($module == 'cms' && $otherModule == 'sapphire') {
+ // if(
+ // isset($translations[$locale][$namespace][$id])
+ // && isset($otherTranslations[$locale][$namespace][$id])
+ // ) {
+ // unset($translations[$locale][$namespace][$id]);
+ // if(!$translations[$locale][$namespace]) unset($translations[$locale][$namespace]);
+ // }
+ // }
+ }
+ }
+ }
+ }
+
+ foreach($translations as $locale => $namespaces) {
+ $yml = '';
+
+ if(@$this->localeMap[$locale]) {
+ $locale = $this->localeMap[$locale];
+ } else {
+ // if first part matches second part, assume base locale
+ $parts = explode('_', $locale);
+ if(count($parts) == 2 && strtolower($parts[0]) == strtolower($parts[1])) {
+ $locale = strtolower($parts[0]);
+ }
+ }
+
+ ksort($namespaces);
+
+ $yml = "$locale:\n";
+ foreach($namespaces as $namespace => $entities) {
+ if(!$namespace) continue;
+ if(!$entities) continue;
+
+ // Skip namespaces missing in master lang
+ if(!$this->retainTranslationsForMissingMaster && !isset($master[$namespace])) continue;
+
+ $yml .= str_repeat($this->indent, 1) . "$namespace:\n";
+
+ ksort($entities);
+ foreach($entities as $id => $entity) {
+ // Skip entity identifiers missing in master lang
+ if(!$this->retainTranslationsForMissingMaster && !isset($master[$namespace][$id])) continue;
+
+ if(is_array($entity)) {
+ $trans = $entity[0];
+ $context = $entity[2];
+ } else {
+ $trans = $entity;
+ $context = null;
+ }
+ if($context) $yml .= str_repeat($this->indent, 2) . "# " . str_replace(array("\n", "\r"), '', $context) . "\n";
+
+ if(preg_match('/[\n\r]/', $trans)) {
+ $yml .= str_repeat($this->indent, 2) . "$id: |\n";
+ $blockIndent = str_repeat($this->indent, 2) . str_pad('', strlen($id)+3, ' ');
+ $yml .= $blockIndent . implode("\n" . $blockIndent, explode("\n", $trans));
+ } else {
+ $trans = str_replace(array('"'), array('\"'), $trans); // quote strings
+ $yml .= str_repeat($this->indent, 2) . "$id: \"$trans\"\n";
+ }
+ }
+ }
+ file_put_contents("$this->baseSavePath/$module/$this->langFolder/$locale.yml", $yml);
+ }
+ }
+
+}
32 code/i18nYMLConverterTask.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @package sapphire
+ * @subpackage tasks
+ */
+class i18nYMLConverterTask extends BuildTask {
+
+ protected $title = "i18n format converter task";
+
+ protected $description = "";
+
+ function init() {
+ parent::init();
+
+ $canAccess = (Director::isDev() || Director::is_cli() || Permission::check("ADMIN"));
+ if(!$canAccess) return Security::permissionFailure($this);
+ }
+
+ /**
+ * This is the main method to build the master string tables with the original strings.
+ * It will search for existent modules that use the i18n feature, parse the _t() calls
+ * and write the resultant files in the lang folder of each module.
+ *
+ * @uses DataObject->collectI18nStatics()
+ */
+ public function run($request) {
+ increase_time_limit_to();
+ $c = new i18nYMLConverter();
+ $restrictModules = ($request->getVar('module')) ? explode(',', $request->getVar('module')) : null;
+ return $c->run($restrictModules);
+ }
+}

0 comments on commit 06c4d4b

Please sign in to comment.
Something went wrong with that request. Please try again.