Browse Source

Added a PoFile loader

Jose Lorenzo Rodriguez 11 years ago
parent
commit
bfd685cc1e

+ 134 - 0
src/I18n/Loader/PoFileLoader.php

@@ -0,0 +1,134 @@
+<?php
+
+namespace Cake\I18n\Loader;
+
+/**
+ * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
+ * @copyright Copyright (c) 2012, Clemens Tolboom
+ * @copyright Copyright (c) 2014, Fabien Potencier https://github.com/symfony/Translation/blob/master/LICENSE
+ */
+class PoFileLoader  {
+
+/**
+ * Parses portable object (PO) format.
+ *
+ * From http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
+ * we should be able to parse files having:
+ *
+ * white-space
+ * #  translator-comments
+ * #. extracted-comments
+ * #: reference...
+ * #, flag...
+ * #| msgid previous-untranslated-string
+ * msgid untranslated-string
+ * msgstr translated-string
+ *
+ * extra or different lines are:
+ *
+ * #| msgctxt previous-context
+ * #| msgid previous-untranslated-string
+ * msgctxt context
+ *
+ * #| msgid previous-untranslated-string-singular
+ * #| msgid_plural previous-untranslated-string-plural
+ * msgid untranslated-string-singular
+ * msgid_plural untranslated-string-plural
+ * msgstr[0] translated-string-case-0
+ * ...
+ * msgstr[N] translated-string-case-n
+ *
+ * The definition states:
+ * - white-space and comments are optional.
+ * - msgid "" that an empty singleline defines a header.
+ *
+ * This parser sacrifices some features of the reference implementation the
+ * differences to that implementation are as follows.
+ * - No support for comments spanning multiple lines.
+ * - Translator and extracted comments are treated as being the same type.
+ * - Message IDs are allowed to have other encodings as just US-ASCII.
+ *
+ * Items with an empty id are ignored.
+ *
+ * @param string $resource
+ *
+ * @return array
+ */
+	public function parse($resource) {
+		$stream = fopen($resource, 'r');
+
+		$defaults = [
+			'ids' => [],
+			'translated' => null
+		];
+
+		$messages = [];
+		$item = $defaults;
+
+		while ($line = fgets($stream)) {
+			$line = trim($line);
+
+			if ($line === '') {
+				// Whitespace indicated current item is done
+				$this->_addMessage($messages, $item);
+				$item = $defaults;
+			} elseif (substr($line, 0, 7) === 'msgid "') {
+				// We start a new msg so save previous
+				$this->_addMessage($messages, $item);
+				$item = $defaults;
+				$item['ids']['singular'] = substr($line, 7, -1);
+			} elseif (substr($line, 0, 8) === 'msgstr "') {
+				$item['translated'] = substr($line, 8, -1);
+			} elseif ($line[0] === '"') {
+				$continues = isset($item['translated']) ? 'translated' : 'ids';
+
+				if (is_array($item[$continues])) {
+					end($item[$continues]);
+					$item[$continues][key($item[$continues])] .= substr($line, 1, -1);
+				} else {
+					$item[$continues] .= substr($line, 1, -1);
+				}
+			} elseif (substr($line, 0, 14) === 'msgid_plural "') {
+				$item['ids']['plural'] = substr($line, 14, -1);
+			} elseif (substr($line, 0, 7) === 'msgstr[') {
+				$size = strpos($line, ']');
+				$item['translated'][(int)substr($line, 7, 1)] = substr($line, $size + 3, -1);
+			}
+
+		}
+		// save last item
+		$this->_addMessage($messages, $item);
+		fclose($stream);
+
+		return $messages;
+	}
+
+/**
+ * Saves a translation item to the messages.
+ *
+ * @param array $messages
+ * @param array $item
+ * @return void
+ */
+	protected function _addMessage(array &$messages, array $item) {
+		if (is_array($item['translated'])) {
+			$messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated'][0]);
+			if (isset($item['ids']['plural'])) {
+				$plurals = $item['translated'];
+				// PO are by definition indexed so sort by index.
+				ksort($plurals);
+				// Make sure every index is filled.
+				end($plurals);
+				$count = key($plurals);
+				// Fill missing spots with '-'.
+				$empties = array_fill(0, $count + 1, '');
+				$plurals += $empties;
+				ksort($plurals);
+				$messages[stripcslashes($item['ids']['plural'])] = stripcslashes(implode('&&&', $plurals));
+			}
+		} elseif (!empty($item['ids']['singular'])) {
+			$messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated']);
+		}
+	}
+
+}

+ 58 - 0
tests/TestCase/I18n/Loader/PoFileLoaderTest.php

@@ -0,0 +1,58 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\I18n\Loader;
+
+use Cake\I18n\Loader\PoFileLoader;
+use Cake\TestSuite\TestCase;
+
+/**
+ * Tests the PoFileLoader
+ *
+ */
+class PoFileLoaderTest extends TestCase {
+
+/**
+ * Tests parsing a file with plurals and message context
+ *
+ * @return void
+ */
+	public function testParse() {
+		$parser = new PoFileLoader;
+		$file = APP . 'Locale' . DS . 'rule_1_po' . DS . 'LC_MESSAGES' . DS . 'default.po';
+		$messages = $parser->parse($file);
+		$this->assertCount(5, $messages);
+		$expected = [
+			'Plural Rule 1' => 'Plural Rule 1 (translated)',
+			'%d = 1' => '%d = 1 (translated)',
+			'%d = 0 or > 1' => '%d = 1 (translated)&&&%d = 0 or > 1 (translated)',
+			'%-5d = 1' => '%-5d = 1 (translated)',
+			'%-5d = 0 or > 1' => '%-5d = 1 (translated)&&&&&&&&&&&&%-5d = 0 or > 1 (translated)'
+		];
+		$this->assertEquals($expected, $messages);
+	}
+
+/**
+ * Tests parsing a file with multiline keys and values
+ *
+ * @return void
+ */
+	public function testParseMultiLine() {
+		$parser = new PoFileLoader;
+		$file = APP . 'Locale' . DS . 'po' . DS . 'LC_MESSAGES' . DS . 'default.po';
+		$messages = $parser->parse($file);
+		$this->assertCount(12, $messages);
+		$this->assertTextEquals("v\nsecond line", $messages["valid\nsecond line"]);
+	}
+}

+ 1 - 1
tests/test_app/TestApp/Locale/po/LC_MESSAGES/default.po

@@ -73,4 +73,4 @@ msgstr[1] "%d is 2-4\n"
 "This is the forth line."
 
 msgid "this is a \"quoted string\""
-msgstr "this is a \"quoted string\" (translated)"
+msgstr "this is a \"quoted string\" (translated)"

+ 3 - 1
tests/test_app/TestApp/Locale/rule_1_po/LC_MESSAGES/default.po

@@ -12,9 +12,11 @@ msgstr ""
 "X-Poedit-Language: Two Forms of Plurals\n"
 "X-Poedit-SourceCharset: utf-8\n"
 
+msgctxt "This is the context"
 msgid "Plural Rule 1"
 msgstr "Plural Rule 1 (translated)"
 
+msgctxt "Another Context"
 msgid "%d = 1"
 msgid_plural "%d = 0 or > 1"
 msgstr[0] "%d = 1 (translated)"
@@ -23,7 +25,7 @@ msgstr[1] "%d = 0 or > 1 (translated)"
 msgid "%-5d = 1"
 msgid_plural "%-5d = 0 or > 1"
 msgstr[0] "%-5d = 1 (translated)"
-msgstr[1] "%-5d = 0 or > 1 (translated)"
+msgstr[4] "%-5d = 0 or > 1 (translated)"
 
 #~ msgid "Plural-Forms 1"
 #~ msgstr "Plural-Forms 1 (translated)"