* Implemented a provider to clean up and compile LL files
authorChristian Opitz <christian.opitz@netresearch.de>
Sun, 7 Oct 2012 20:32:09 +0000 (20:32 +0000)
committerChristian Opitz <christian.opitz@netresearch.de>
Fri, 31 May 2013 13:22:24 +0000 (15:22 +0200)
* Added an argument $absolute to getPath - when given the existing part of the path will be ignored from cleaning up

provider/class.abstract.php
provider/class.lang.php [new file with mode: 0644]

index 8189ce4..6f7bafc 100644 (file)
@@ -346,9 +346,10 @@ abstract class tx_t3build_provider_abstract {
      * @param string $mask
      * @param array $vars
      * @param string $renameMode
+     * @param boolean $absolute
      * @return string
      */
-    protected function getPath($mask, $vars, $renameMode = 'camelCase')
+    protected function getPath($mask, $vars, $renameMode = 'camelCase', $absolute = false)
     {
         $replace = array();
         foreach ($vars as $key => $value) {
@@ -358,6 +359,25 @@ abstract class tx_t3build_provider_abstract {
         if (preg_match('/\$\{([^\}]*)\}/', $path, $res)) {
             $this->_die('Unknown var "'.$res[1].'" in path mask');
         }
+
+        $pre = '';
+        if ($absolute) {
+            $parts = preg_split('#\s*[\\/]+\s*#', $path);
+            $rest = array();
+            while (count($parts)) {
+                $file = implode('/', $parts);
+                if (file_exists($file)) {
+                    if (!count($rest) && is_file($file)) {
+                        return $file;
+                    }
+                    $pre = $file.'/';
+                    $path = implode('/', $rest);
+                    break;
+                }
+                array_unshift($rest, array_pop($parts));
+            }
+        }
+
         $path = strtolower($path);
         $path = str_replace(':', '-', $path);
         $path = preg_replace('#[^A-Za-z0-9/\-_\.]+#', ' ', $path);
@@ -374,6 +394,6 @@ abstract class tx_t3build_provider_abstract {
                 $uc = $ucPart != $part;
             }
         }
-        return $path;
+        return $pre.$path;
     }
 }
diff --git a/provider/class.lang.php b/provider/class.lang.php
new file mode 100644 (file)
index 0000000..60dd844
--- /dev/null
@@ -0,0 +1,237 @@
+<?php\r
+/**\r
+ * Provider to clean locallang XML source files and compile\r
+ * them for TYPO3 in order to enable locales and avoid duplicates\r
+ * for them in the source file. Source can f.i. look like this:\r
+ * <code title="Source">\r
+ * <?xml version="1.0" encoding="utf-8" standalone="yes" ?>\r
+ * <T3locallang>\r
+ *     <meta type="array">\r
+ *             <description>Language labels for Contact Form</description>\r
+ *     </meta>\r
+ *     <data type="array">\r
+ *             <languageKey index="de_DE" type="array">\r
+ *                     <label index="hello">Hallo</label>\r
+ *                     <label index="country">Deutschland</label>\r
+ *             </languageKey>\r
+ *             <languageKey index="de_AT" type="array">\r
+ *                     <label index="country">Österreich</label>\r
+ *             </languageKey>\r
+ *     </data>\r
+ * </T3locallang>\r
+ * </code>\r
+ *\r
+ * <code title="Compilation result">\r
+ * <?xml version="1.0" encoding="utf-8" standalone="yes" ?>\r
+ * <T3locallang>\r
+ *     <meta type="array">\r
+ *             <description>Language labels for Contact Form</description>\r
+ *     </meta>\r
+ *     <data type="array">\r
+ *             <languageKey index="de" type="array">\r
+ *                     <label index="hello">Hallo</label>\r
+ *                     <label index="country">Deutschland</label>\r
+ *             </languageKey>\r
+ *             <languageKey index="at" type="array">\r
+ *                     <label index="hello">Hallo</label>\r
+ *                     <label index="country">Österreich</label>\r
+ *             </languageKey>\r
+ *     </data>\r
+ * </T3locallang>\r
+ * </code>\r
+ *\r
+ * @package t3build\r
+ * @author Christian Opitz <co@netzelf.de>\r
+ *\r
+ */\r
+class tx_t3build_provider_lang extends tx_t3build_provider_abstract\r
+{\r
+    /**\r
+     * The path to the file to clean and compile (relative to typo3 root,\r
+     * maybe prefixed with EXT:) - will be filled with the compiled XML\r
+     * by default (@see --cleaned-name and --compiled-name)\r
+     * @arg\r
+     * @required\r
+     * @var string\r
+     */\r
+    protected $file;\r
+\r
+       /**\r
+        * The name of the file in which to write the cleaned XML for\r
+        * developers/translators - if it exists, it will used as source.\r
+        * Can hold pathinfo (php.net/manual/en/function.pathinfo.php)\r
+        * parts as variables.\r
+        * @arg\r
+        * @var string\r
+        */\r
+       protected $cleanedFile = '${dirname}/${filename}.source.${extension}';\r
+\r
+       /**\r
+        * The name of the file in which to write the cleaned XML for\r
+        * developers/translators - if it exists, it will used as source.\r
+        * Can hold pathinfo (php.net/manual/en/function.pathinfo.php)\r
+        * parts as variables.\r
+        * @arg\r
+        * @var string\r
+        */\r
+       protected $compiledFile = '${dirname}/${basename}';\r
+\r
+       /**\r
+        * If enabled, empty labels will be tracked as missing\r
+        * @arg\r
+        * @var boolean\r
+        */\r
+       protected $emptyIsMissing = true;\r
+\r
+       /**\r
+        * The language which keys will be used to clean up the other languages\r
+        * @arg\r
+        * @var boolean\r
+        */\r
+       protected $master = 'first';\r
+\r
+       protected $languages = array();\r
+\r
+       protected $labels = array();\r
+\r
+       protected $missing = array();\r
+\r
+       protected $processed = array();\r
+\r
+       public function langAction()\r
+       {\r
+               $this->file = t3lib_div::getFileAbsFileName($this->file);\r
+               if (!file_exists($this->file)) {\r
+                   $this->_die('File %s does not exist', $this->file);\r
+               }\r
+\r
+               $pathinfo = pathinfo($this->file);\r
+               foreach (array('cleaned', 'compiled') as $type) {\r
+                   $this->{$type.'File'} = $this->getPath($this->{$type.'File'}, $pathinfo, 'camelCase', true);\r
+               }\r
+\r
+               $this->read();\r
+               $this->prepare();\r
+               $this->process();\r
+               $this->write();\r
+\r
+       if (count($this->missing)) {\r
+            $this->_echo('Missing translations:');\r
+            foreach ($this->missing as $code => $indexes) {\r
+                foreach ($indexes as $index) {\r
+                    $this->_echo($code.': '.$index);\r
+                };\r
+            }\r
+        }\r
+       }\r
+\r
+       protected function read()\r
+       {\r
+       $xml = simplexml_load_file(file_exists($this->cleanedFile) ? $this->cleanedFile : $this->file);\r
+        foreach ($xml->data[0]->languageKey as $languageKey) {\r
+            $parts = explode('_', strtolower((string) $languageKey['index']), 2);\r
+            if (count($parts) === 2 && $parts[0] != $parts[1]) {\r
+                $code = $parts[1];\r
+                $this->languages[$code] = $parts[0];\r
+            } else {\r
+                $code = $parts[0];\r
+            }\r
+            if ($code == 'default') {\r
+                $code = 'en';\r
+            }\r
+            if ($this->master == 'first') {\r
+                $this->master = $code;\r
+            }\r
+            $this->labels[$code] = array();\r
+            foreach ($languageKey->label as $label) {\r
+                $this->labels[$code][(string) $label['index']] = trim((string) $label);\r
+            }\r
+        }\r
+       }\r
+\r
+       protected function prepare()\r
+       {\r
+           $orderedLabels = array();\r
+           foreach ($this->labels as $code => $labels) {\r
+               if (array_key_exists($code, $this->languages) && !array_key_exists($this->languages[$code], $this->labels)) {\r
+                   $orderedLabels[$this->languages[$code]] = $labels;\r
+               }\r
+               $orderedLabels[$code] = $labels;\r
+           }\r
+           $this->labels = $orderedLabels;\r
+       }\r
+\r
+       protected function process()\r
+       {\r
+           $master = array_pop(explode('_', strtolower($this->master)));\r
+           if (!array_key_exists($master, $this->labels)) {\r
+               $this->_die('Master language %s not found as langKey', $master);\r
+           }\r
+       $cleaned = array($master => $this->labels[$master]);\r
+        $compiled = $cleaned;\r
+        $missing = array();\r
+        foreach ($this->labels as $code => $labels) {\r
+            if ($code == $master) {\r
+                continue;\r
+            }\r
+            $cleaned[$code] = array();\r
+            $countryCode = array_key_exists($code, $this->languages) ? $this->languages[$code] : $code;\r
+            foreach (array_keys($this->labels[$master]) as $index) {\r
+                $label = array_key_exists($index, $labels) ? $labels[$index] : null;\r
+                $fallback = null;\r
+                if ($countryCode != $code) {\r
+                    if (array_key_exists($countryCode, $this->labels) && array_key_exists($index, $this->labels[$countryCode])) {\r
+                        $fallback = $this->labels[$countryCode][$index];\r
+                        if ($label == $fallback) {\r
+                            $label = null;\r
+                        }\r
+                    }\r
+                    if ($label !== null) {\r
+                        $cleaned[$code][$index] = $label;\r
+                    }\r
+                } else {\r
+                    $cleaned[$code][$index] = $label;\r
+                    if ($label === null || ($this->emptyIsMissing && !$label)) {\r
+                        if (!array_key_exists($code, $this->missing)) {\r
+                            $this->missing[$code] = array();\r
+                        }\r
+                        $this->missing[$code][$index] = $index;\r
+                    }\r
+                }\r
+                if ($label !== null || $fallback !== null) {\r
+                    $compiled[$code][$index] = $label !== null ? $label : $fallback;\r
+                }\r
+            }\r
+        }\r
+        $this->processed = compact('cleaned', 'compiled');\r
+       }\r
+\r
+       protected function write()\r
+       {\r
+        foreach ($this->processed as $type => $language) {\r
+            $content = array();\r
+            $content[] = '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>';\r
+            $content[] = '<T3locallang>';\r
+            $content[] = '     <meta type="array">';\r
+            $content[] = '             <description>Language labels for Contact Form</description>';\r
+            $content[] = '     </meta>';\r
+            $content[] = '     <data type="array">';\r
+            foreach ($language as $code => $labels) {\r
+                $content[] = '         <languageKey index="'.$code.($type == 'cleaned' && array_key_exists($code, $this->languages) ? '_'.strtoupper($this->languages[$code]) : '').'" type="array">';\r
+                foreach ($labels as $index => $label) {\r
+                    $cdata = strpos($label, '<') !== false;\r
+                    if ($type == 'cleaned' && array_key_exists($code, $this->missing) && array_key_exists($index, $this->missing[$code])) {\r
+                        $label = '<!-- missing -->';\r
+                    }\r
+                    $content[] = '             <label index="'.$index.'">'.($cdata ? '<![CDATA['.$label.']]>' : $label).'</label>';\r
+                }\r
+                $content[] = '         </languageKey>';\r
+            }\r
+            $content[] = '     </data>';\r
+            $content[] = '</T3locallang>';\r
+\r
+            $this->_echo('Writing %s XML to %s', $type, $this->{$type.'File'});\r
+            file_put_contents($this->{$type.'File'}, implode("\n", $content));\r
+        }\r
+       }\r
+}\r