Przeglądaj źródła

[ZF-2600] Zend_Translate:

- added plural support
- ONLY for array, csv and gettext adapters
- also fixing ZF-6671, ZF-7099, ZF-6509

git-svn-id: http://framework.zend.com/svn/framework/standard/trunk@16883 44c647ce-9c0f-0410-b52a-842ac1e357ba
thomas 16 lat temu
rodzic
commit
188a3b2791

+ 3 - 0
documentation/manual/en/manual.xml.in

@@ -538,6 +538,9 @@
         <xi:include href="module_specs/Zend_Translate-Introduction.xml" />
         <xi:include href="module_specs/Zend_Translate-Introduction.xml" />
         <xi:include href="module_specs/Zend_Translate-Adapters.xml" />
         <xi:include href="module_specs/Zend_Translate-Adapters.xml" />
         <xi:include href="module_specs/Zend_Translate-Using.xml" />
         <xi:include href="module_specs/Zend_Translate-Using.xml" />
+        <xi:include href="module_specs/Zend_Translate-SourceCreation.xml" />
+        <xi:include href="module_specs/Zend_Translate-Additional.xml" />
+        <xi:include href="module_specs/Zend_Translate-Plurals.xml" />
         <xi:include href="module_specs/Zend_Translate-Migration.xml" />
         <xi:include href="module_specs/Zend_Translate-Migration.xml" />
     </chapter>
     </chapter>
     <chapter id="zend.uri">
     <chapter id="zend.uri">

+ 742 - 0
documentation/manual/en/module_specs/Zend_Translate-Additional.xml

@@ -0,0 +1,742 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.translate.additional">
+
+    <title>Additional features for translation</title>
+
+    <para>
+        There are several additional features which are supported by
+        <classname>Zend_Translate</classname>. Read here for these additional informations.
+    </para>
+
+    <sect2 id="zend.translate.additional.options">
+
+        <title>Options for adapters</title>
+
+        <para>
+            Options can be used with all adapters. Of course the options are different for all adapters.
+            You can set options when you create the adapter. Actually there is one option which is available
+            to all adapters: '<code>clear</code>' sets if translation data should be added to existing
+            one or not. Standard behaviour is to add new translation data to existing one. But the
+            translation data is only cleared for the selected language. So other languages remain
+            untouched.
+        </para>
+
+        <para>
+            You can set options temporarily when using <code>addTranslation($data, $locale, array $options = array())</code>
+            as third and optional parameter. And you can use the method <code>setOptions()</code> to
+            set the options permanently.
+        </para>
+
+        <example id="zend.translate..additional.options.example">
+            <title>Using translation options</title>
+            <programlisting role="php"><![CDATA[
+// define ':' as separator for the translation source files
+$options = array('delimiter' => ':');
+$translate = new Zend_Translate(
+    'csv',
+    '/path/to/mytranslation.csv',
+    'de',
+    $options);
+
+...
+
+// clear the defined language and use new translation data
+$options = array('clear' => true);
+$translate->addTranslation('/path/to/new.csv', 'fr', $options);
+]]></programlisting>
+        </example>
+
+        <para>
+            Here you can find all available options for the different adapters with a description of their usage:
+        </para>
+
+        <table id="zend.translate.additional.options.alloptions">
+            <title>Options for translation adapters</title>
+            <tgroup cols="4">
+                <thead>
+                    <row>
+                        <entry>Option</entry>
+                        <entry>Adapter</entry>
+                        <entry>Description</entry>
+                        <entry>Default value</entry>
+                    </row>
+                </thead>
+                <tbody>
+                    <row>
+                        <entry>clear</entry>
+                        <entry>all</entry>
+                        <entry>
+                            If set to true, the already read translations will be cleared. This can be used
+                            instead of creating a new instance when reading new translation data
+                        </entry>
+                        <entry><emphasis role="strong">false</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>disableNotices</entry>
+                        <entry>all</entry>
+                        <entry>
+                            If set to true, all notices regarding not available translations will be
+                            disabled. You should set this option to true in production environment
+                        </entry>
+                        <entry><emphasis role="strong">false</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>ignore</entry>
+                        <entry>all</entry>
+                        <entry>
+                            All directories and files beginning with this prefix will be ignored when
+                            searching for files. This value defaults to <emphasis role="strong">'.'</emphasis>
+                            which leads to the behavior that all hidden files will be ignored. Setting this
+                            value to <code>'tmp'</code> would mean that directories and files like
+                            <code>'tmpImages'</code> and <code>'tmpFiles'</code>
+                            would be ignored as well as all subsequent directories
+                        </entry>
+                        <entry><emphasis role="strong">.</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>log</entry>
+                        <entry>all</entry>
+                        <entry>
+                            An instance of <classname>Zend_Log</classname> where untranslated messages and notices will be
+                            written to
+                        </entry>
+                        <entry><emphasis role="strong">null</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>logMessage</entry>
+                        <entry>all</entry>
+                        <entry>
+                            The message which will be written into the log
+                        </entry>
+                        <entry><emphasis role="strong">Untranslated message within '%locale%': %message%</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>logUntranslated</entry>
+                        <entry>all</entry>
+                        <entry>
+                            When this option is set to true, all message IDs which can not be
+                            translated will be written into the attached log
+                        </entry>
+                        <entry><emphasis role="strong">false</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>scan</entry>
+                        <entry>all</entry>
+                        <entry>
+                            If set to null, no scanning of the directory structure will be done.
+                            If set to <classname>Zend_Translate::LOCALE_DIRECTORY</classname> the locale will be detected within the
+                            directory. If set to <classname>Zend_Translate::LOCALE_FILENAME</classname> the locale will be detected
+                            within the filename. See <xref linkend="zend.translate.additional.detection" />
+                            for details
+                        </entry>
+                        <entry><emphasis role="strong">null</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>delimiter</entry>
+                        <entry>Csv</entry>
+                        <entry>Defines which sign is used as delimiter for separating source and translation</entry>
+                        <entry><emphasis role="strong">;</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>enclosure</entry>
+                        <entry>Csv</entry>
+                        <entry>Defines the enclosure character to be used. Defaults to a doublequote</entry>
+                        <entry><emphasis role="strong">"</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>length</entry>
+                        <entry>Csv</entry>
+                        <entry>Defines the maximum length of a csv line. When set to 0 it will be detected automatically</entry>
+                        <entry><emphasis role="strong">0</emphasis></entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+
+        <para>
+            When you want to have self defined options, you are also able to use them within all adapters.
+            The <code>setOptions()</code> method can be used to define your option. <code>setOptions()</code>
+            needs an array with the options you want to set. If an given option exists it will be signed over.
+            You can define as much options as needed as they will not be checked by the adapter. Just make sure
+            not to overwrite any existing option which is used by an adapter.
+        </para>
+
+        <para>
+            To return the option you can use the <code>getOptions()</code> method. When <code>getOptions()</code>
+            is called without a parameter it will return all options set. When the optional parameter is given
+            you will only get the specified option.
+        </para>
+
+    </sect2>
+
+    <sect2 id="zend.translate.additional.languages">
+
+        <title>Handling languages</title>
+
+        <para>
+            When working with different languages there are a few methods which will be useful.
+        </para>
+
+        <para>
+            The <code>getLocale()</code> method can be used to get the currently set language. It can either hold
+            an instance of <classname>Zend_Locale</classname> or the identifier of a locale.
+        </para>
+
+        <para>
+            The <code>setLocale()</code> method sets a new standard language for translation. This prevents the
+            need of setting the optional language parameter more than once to the <code>translate()</code> method.
+            If the given language does not exist, or no translation data is available for the language,
+            <code>setLocale()</code> tries to downgrade to the language without the region if any was given.
+            A language of <code>en_US</code> would be downgraded to <code>en</code>. When even the downgraded
+            language can not be found an exception will be thrown.
+        </para>
+
+        <para>
+            The <code>isAvailable()</code> method checks if a given language is already available. It returns
+            <code>true</code> if data for the given language exist.
+        </para>
+
+        <para>
+            And finally the <code>getList()</code> method can be used to get all currently set languages for an adapter
+            returned as array.
+        </para>
+
+        <example id="zend.translate.additional.languages.example">
+            <title>Handling languages with adapters</title>
+            <programlisting role="php"><![CDATA[
+// returns the currently set language
+$actual = $translate->getLocale();
+
+// you can use the optional parameter while translating
+echo $translate->_("my_text", "fr");
+// or set a new language
+$translate->setLocale("fr");
+echo $translate->_("my_text");
+// refer to the base language
+// fr_CH will be downgraded to fr
+$translate->setLocale("fr_CH");
+echo $translate->_("my_text");
+
+// check if this language exist
+if ($translate->isAvailable("fr")) {
+    // language exists
+}
+]]></programlisting>
+        </example>
+
+        <sect3 id="zend.translate.additional.languages.automatic">
+
+            <title>Automatical handling of languages</title>
+
+            <para>
+                Note that as long as you only add new translation sources with the <code>addTranslation()</code>
+                method <classname>Zend_Translate</classname> will automatically set the best fitting language for your
+                environment when you use one of the automatic locales which are '<code>auto</code>' or '<code>browser</code>'. So
+                normally you will not need to call <code>setLocale()</code>. This should only be used in
+                conjunction with automatic source detection.
+            </para>
+
+            <para>
+                The algorithm will search for the best fitting locale depending on the user's browser and
+                your environment. See the following example for details:
+            </para>
+
+            <example id="zend.translate.additional.languages.automatic.example">
+                <title>Automatically language detection</title>
+                <programlisting role="php"><![CDATA[
+// Let's expect the browser returns these language settings:
+// HTTP_ACCEPT_LANGUAGE = "de_AT=1;fr=1;en_US=0.8";
+
+// Example 1:
+// When no fitting language is found, the message ID is returned
+$translate = new Zend_Translate(
+    'gettext',
+    'my_it.mo',
+    'auto',
+    array('scan' => Zend_Translate::LOCALE_FILENAME));
+
+// Example 2:
+// Best found fitting language is 'fr'
+$translate = new Zend_Translate(
+    'gettext',
+    'my_fr.mo',
+    'auto',
+    array('scan' => Zend_Translate::LOCALE_FILENAME));
+
+// Example 3:
+// Best found fitting language is 'de' ('de_AT' will be degraded)
+$translate = new Zend_Translate(
+    'gettext',
+    'my_de.mo',
+    'auto',
+    array('scan' => Zend_Translate::LOCALE_FILENAME));
+
+// Example 4:
+// Returns 'it' as translation source and overrides the automatic settings
+$translate = new Zend_Translate(
+    'gettext',
+    'my_it.mo',
+    'auto',
+    array('scan' => Zend_Translate::LOCALE_FILENAME));
+
+$translate->addTranslation('my_ru.mo', 'ru');
+$translate->setLocale('it_IT');
+]]></programlisting>
+            </example>
+
+            <para>
+                After setting a language manually with the <code>setLocale()</code> method the automatic
+                detection will be switched off and overridden.
+            </para>
+
+            <para>
+                If you want to use it again, you can set the language
+                <emphasis role="strong">auto</emphasis> with <code>setLocale()</code> which will reactivate
+                the automatic detection for <classname>Zend_Translate</classname>.
+            </para>
+
+            <para>
+                Since Zend Framework 1.7.0 <classname>Zend_Translate</classname> also recognises an application
+                wide locale. You can simply set a <classname>Zend_Locale</classname> instance to the registry like shown
+                below. With this notation you can forget about setting the locale manually with each instance
+                when you want to use the same locale multiple times.
+            </para>
+
+            <programlisting role="php"><![CDATA[
+// in your bootstrap file
+$locale = new Zend_Locale();
+Zend_Registry::set('Zend_Locale', $locale);
+
+// default language when requested language is not available
+$defaultlanguage = 'en';
+
+// somewhere in your application
+$translate = new Zend_Translate('gettext', 'my_de.mo');
+
+if (!$translate->isAvailable($locale->getLanguage())) {
+    // not available languages are rerouted to another language
+    $translate->setLocale($defaultlanguage);
+}
+
+$translate->getLocale();
+]]></programlisting>
+
+        </sect3>
+
+    </sect2>
+
+    <sect2 id="zend.translate.additional.detection">
+
+        <title>Automatic source detection</title>
+
+        <para>
+            <classname>Zend_Translate</classname> can detect translation sources automatically. So you don't have
+            to declare each source file manually. You can let <classname>Zend_Translate</classname> do this job and
+            scan the complete directory structure for source files.
+        </para>
+
+        <note>
+            <para>
+                Automatic source detection is available since Zend Framework version 1.5 .
+            </para>
+        </note>
+
+        <para>
+            The usage is quite the same as initiating a single translation source with one difference.
+            You must give a directory which has to be scanned instead a file.
+        </para>
+
+        <example id="zend.translate.additional.languages.directory.example">
+            <title>Scanning a directory structure for sources</title>
+            <programlisting role="php"><![CDATA[
+// assuming we have the following structure
+//  /language/
+//  /language/login/login.tmx
+//  /language/logout/logout.tmx
+//  /language/error/loginerror.tmx
+//  /language/error/logouterror.tmx
+
+$translate = new Zend_Translate('tmx', '/language');
+]]></programlisting>
+        </example>
+
+        <para>
+            So <classname>Zend_Translate</classname> does not only search the given directory, but also all subdirectories for
+            translation source files. This makes the usage quite simple. But <classname>Zend_Translate</classname> will ignore all
+            files which are not sources or which produce failures while reading the translation data. So you
+            have to make sure that all of your translation sources are correct and readable because you will
+            not get any failure if a file is bogus or can not be read.
+        </para>
+
+        <note>
+            <para>
+                Depending on how deep your directory structure is and how much files are within this structure
+                it can take a long time for <classname>Zend_Translate</classname> to complete.
+            </para>
+        </note>
+
+        <para>
+            In our example we have used the TMX format which includes the language to be used within the
+            source. But many of the other source formats are not able to include the language within the
+            file. Even this sources can be used with automatic scanning if you do some pre-requisits as
+            described below:
+        </para>
+
+        <sect3 id="zend.translate.additional.detection.directory">
+
+            <title>Language through naming directories</title>
+
+            <para>
+                One way to include automatic language detection is to name the directories related to the
+                language which is used for the sources within this directory. This is the easiest way and
+                is used for example within standard gettext implementations.
+            </para>
+
+            <para>
+                <classname>Zend_Translate</classname> needs the '<code>scan</code>' option to know that it should search the names of all
+                directories for languages. See the following example for details:
+            </para>
+
+            <example id="zend.translate.additional.detection.directory.example">
+                <title>Directory scanning for languages</title>
+                <programlisting role="php"><![CDATA[
+// assuming we have the following structure
+//  /language/
+//  /language/de/login/login.mo
+//  /language/de/error/loginerror.mo
+//  /language/en/login/login.mo
+//  /language/en/error/loginerror.mo
+
+$translate = new Zend_Translate(
+    'gettext',
+    '/language',
+    null,
+    array('scan' => Zend_Translate::LOCALE_DIRECTORY));
+]]></programlisting>
+            </example>
+
+            <note>
+                <para>
+                    This works only for adapters which do not include the language within the source file.
+                    Using this option for example with TMX will be ignored. Also language definitions within
+                    the filename will be ignored when using this option.
+                </para>
+            </note>
+
+            <note>
+                <para>
+                    You should be aware if you have several subdirectories under the same
+                    structure. Assuming we have a structure like
+                    <code>/language/module/de/en/file.mo</code>. In this case the path contains
+                    multiple strings which would be detected as locale. It could be either
+                    <code>de</code> or <code>en</code>. In such a case the behaviour is
+                    undefined and it is recommended to use file detection in such situations.
+                </para>
+            </note>
+
+        </sect3>
+
+        <sect3 id="zend.translate.additional.detection.filename">
+
+            <title>Language through filenames</title>
+
+            <para>
+                Another way to detect the language automatically is to use special filenames. You can either
+                name the complete file or parts of a file after the used language. To use this way of detection
+                you will have to set the '<code>scan</code>' option at initiation. There are several ways of naming the
+                sourcefiles which are described below:
+            </para>
+
+            <example id="zend.translate.additional.detection.filename.example">
+                <title>Filename scanning for languages</title>
+                <programlisting role="php"><![CDATA[
+// assuming we have the following structure
+//  /language/
+//  /language/login/login_en.mo
+//  /language/login/login_de.mo
+//  /language/error/loginerror_en.mo
+//  /language/error/loginerror_de.mo
+
+$translate = new Zend_Translate(
+    'gettext',
+    '/language',
+    null,
+    array('scan' => Zend_Translate::LOCALE_FILENAME));
+]]></programlisting>
+            </example>
+
+            <sect4 id="zend.translate.additional.detection.filename.complete">
+
+                <title>Complete filename</title>
+
+                <para>
+                    Having the whole file named after the language is the simplest way but only viable
+                    if you have only one file per language.
+                </para>
+
+                <programlisting><![CDATA[
+/languages/
+/languages/en.mo
+/languages/de.mo
+/languages/es.mo
+]]></programlisting>
+
+            </sect4>
+
+            <sect4 id="zend.translate.additional.detection.filename.extension">
+
+                <title>Extension of the file</title>
+
+                <para>
+                    Another simple way to use the extension of the file for language detection.
+                    But this may be confusing since you will no longer have an idea which extension the file
+                    originally had.
+                </para>
+
+                <programlisting><![CDATA[
+/languages/
+/languages/view.en
+/languages/view.de
+/languages/view.es
+]]></programlisting>
+
+            </sect4>
+
+            <sect4 id="zend.translate.additional.detection.filename.token">
+
+                <title>Filename tokens</title>
+
+                <para>
+                    <classname>Zend_Translate</classname> is also capable of detecting the language if it is included within the
+                    filename. But if you go this way you will have to separate the language with a token.
+                    There are three supported tokens which can be used: a dot '.', an underscore '_', or
+                    a hyphen '-'.
+                </para>
+
+                <programlisting><![CDATA[
+/languages/
+/languages/view_en.mo -> detects english
+/languages/view_de.mo -> detects german
+/languages/view_it.mo -> detects italian
+]]></programlisting>
+
+                <para>
+                    The first found string delimited by a token which can be interpreted as a locale will be used. See the following
+                    example for details.
+                </para>
+
+                <programlisting><![CDATA[
+/languages/
+/languages/view_en_de.mo -> detects english
+/languages/view_en_es.mo -> detects english and overwrites the first file
+/languages/view_it_it.mo -> detects italian
+]]></programlisting>
+
+                <para>
+                    All three tokens are used to detect the locale. When the filename contains multiple tokens,
+                    the first found token depends on the order of the tokens which are used. See the following
+                    example for details.
+                </para>
+
+                <programlisting><![CDATA[
+/languages/
+/languages/view_en-it.mo -> detects english because '_' will be used before '-'
+/languages/view-en_it.mo -> detects italian because '_' will be used before '-'
+/languages/view_en.it.mo -> detects italian because '.' will be used before '_'
+]]></programlisting>
+
+            </sect4>
+
+        </sect3>
+
+    </sect2>
+
+    <sect2 id="zend.translate.additional.istranslated">
+
+        <title>Checking for translations</title>
+
+        <para>
+            Normally text will be translated without any computation. But sometimes it is necessary to
+            know if a text is translated or not, therefor the <code>isTranslated()</code>
+            method can be used.
+        </para>
+
+        <para>
+            <code>isTranslated($messageId, $original = false, $locale = null)</code> takes
+            the text you want to check as its first parameter, and as optional third parameter the locale
+            for which you want to do the check. The optional second parameter declares whether translation
+            is fixed to the declared language or a lower set of translations can be used. If you have a text which
+            can be returned for 'en' but not for 'en_US' you will normally get the translation returned, but by
+            setting <code>$original</code> to true, <code>isTranslated()</code> will return false.
+        </para>
+
+        <example id="zend.translate.additional.istranslated.example">
+            <title>Checking if a text is translatable</title>
+            <programlisting role="php"><![CDATA[
+$english = array(
+    'message1' => 'Nachricht 1',
+    'message2' => 'Nachricht 2',
+    'message3' => 'Nachricht 3');
+
+$translate = new Zend_Translate('array', $english, 'de_AT');
+
+if ($translate->isTranslated('message1')) {
+    print "'message1' can be translated";
+}
+
+if (!($translate->isTranslated('message1', true, 'de'))) {
+    print "'message1' can not be translated to 'de'"
+        . " as it's available only in 'de_AT'";
+}
+
+if ($translate->isTranslated('message1', false, 'de')) {
+    print "'message1' can be translated in 'de_AT' as it falls back to 'de'";
+}
+]]></programlisting>
+        </example>
+
+    </sect2>
+
+    <sect2 id="zend.translate.additional.logging">
+
+        <title>How to log not found translations</title>
+
+        <para>
+            When you have a bigger site or you are creating the translation files manually, you often have
+            the problem that some messages are not translated. But there is an easy solution for you when you
+            are using <classname>Zend_Translate</classname>.
+        </para>
+
+        <para>
+            You have to follow two or three simple steps. First, you have to create an instance of
+            <classname>Zend_Log</classname>. Then you have to attach this instance to <classname>Zend_Translate</classname>.
+            See the following example:
+        </para>
+
+        <example id="zend.translate.additional.logging.example">
+            <title>Log translations</title>
+            <programlisting role="php"><![CDATA[
+$translate = new Zend_Translate('gettext', $path, 'de');
+
+// Create a log instance
+$writer = new Zend_Log_Writer_Stream('/path/to/file.log');
+$log    = new Zend_Log($writer);
+
+// Attach it to the translation instance
+$translate->setOptions(array(
+    'log'             => $log,
+    'logUntranslated' => true));
+
+$translate->translate('unknown string');
+]]></programlisting>
+        </example>
+
+        <para>
+            Now you will have a new notice in the log: <code>Untranslated message within 'de': unknown string</code>.
+        </para>
+
+        <note>
+            <para>
+                You should note that any translation which can not be found will be logged. This means
+                all translations when a user requests a language which is not supported. Also every request
+                for a message which can not be translated will be logged. Be aware, that 100 people
+                requesting the same translation, will result 100 logged notices.
+            </para>
+        </note>
+
+        <para>
+            This feature can not only be used to log messages but also to attach this untranslated messages
+            into an empty translation file. To do so you will have to write your own log writer which
+            writes the format you want to have and strips the prepending "Untranslated message".
+        </para>
+
+        <para>
+            You can also set the '<code>logMessage</code>' option when you want to have your own log message.
+            Use the '<code>%message%</code>' token for placing the messageId within your log message, and the
+            '<code>%locale%</code>' token for the requested locale. See the following example for a self
+            defined log message:
+        </para>
+
+        <example id="zend.translate.additional.logging.example2">
+            <title>Self defined log messages</title>
+            <programlisting role="php"><![CDATA[
+$translate = new Zend_Translate('gettext', $path, 'de');
+
+// Create a log instance
+$writer = new Zend_Log_Writer_Stream('/path/to/file.log');
+$log    = new Zend_Log($writer);
+
+// Attach it to the translation instance
+$translate->setOptions(array(
+    'log'             => $log,
+    'logMessage'      => "Missing '%message%' within locale '%locale%'",
+    'logUntranslated' => true));
+
+$translate->translate('unknown string');
+]]></programlisting>
+        </example>
+
+    </sect2>
+
+    <sect2 id="zend.translate.additional.sourcedata">
+
+        <title>Accessing source data</title>
+
+        <para>
+            Sometimes it is useful to have access to the translation source data. Therefor
+            the following two functions are provided.
+        </para>
+
+        <para>
+            The <code>getMessageIds($locale = null)</code> method returns all known message IDs as array.
+        </para>
+
+        <para>
+            The <code>getMessages($locale = null)</code> method returns the complete translation source as
+            an array. The message ID is used as key and the translation data as value.
+        </para>
+
+        <para>
+            Both methods accept an optional parameter <code>$locale</code> which, if set, returns the
+            translation data for the specified language. If this parameter is not given, the actual set
+            language will be used. Keep in mind that normally all translations should be available in all
+            languages. Which means that in a normal situation you will not have to set this parameter.
+        </para>
+
+        <para>
+            Additionally the <code>getMessages()</code> method can be used to return the complete
+            translation dictionary using the pseudo-locale 'all'. This will return all available
+            translation data for each added locale.
+        </para>
+
+        <note>
+            <para>
+                Attention: the returned array can be <emphasis role="strong">very big</emphasis>,
+                depending on the number of added locales and the amount of translation data.
+            </para>
+        </note>
+
+        <example id="zend.translate.additional.sourcedata.example">
+            <title>Handling languages with adapters</title>
+            <programlisting role="php"><![CDATA[
+// returns all known message IDs
+$messageIds = $translate->getMessageIds();
+print_r($messageIds);
+
+// or just for the specified language
+$messageIds = $translate->getMessageIds('en_US');
+print_r($messageIds);
+
+// returns all the complete translation data
+$source = $translate->getMessages();
+print_r($source);
+]]></programlisting>
+        </example>
+
+    </sect2>
+
+</sect1>
+<!--
+vim:se ts=4 sw=4 et:
+-->

+ 292 - 0
documentation/manual/en/module_specs/Zend_Translate-Plurals.xml

@@ -0,0 +1,292 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Reviewed: no -->
+<sect1 id="zend.translate.plurals">
+
+    <title>Plural notations for Translation</title>
+
+    <para>
+        As of Zend Framework 1.9, <classname>Zend_Translate</classname> is able to provide plural
+        support. Professional translation will always have the need to use plurals as they are
+        native in almost all languages.
+    </para>
+
+    <para>
+        So what are plurals? Generally spoken plurals are words which take into account numeric
+        meanings. But as you may imaging each language has it's own definition of plurals.
+        English, for example, supports one plural. We have a singular definition, for example
+        "car", which means implicit one car, and we have the plural definition, "cars" which could
+        mean more than one car but also zero cars. Other languages like russian or polish have
+        more plurals and also the rules for plurals are different.
+    </para>
+
+    <para>
+        When you want to use plurals with <classname>Zend_Translate</classname> you must not need
+        to know how the plurals are defined, only the translator must know as he does the
+        translation. The only information you need to have is the language.
+    </para>
+
+    <para>
+        There are two way for using plurals... the traditional one, which means that you use a own
+        method, and a modern one, which allows you to do plural translations with the same method
+        as normal translations.
+    </para>
+
+    <sect2 id="zend.translate.plurals.traditional">
+
+        <title>Traditional plural translations</title>
+
+        <para>
+            People who worked with gettext in past will be more common with traditional plural
+            translations. There is a own <methodname>plural()</methodname> method which can be
+            used for plural translations.
+        </para>
+
+        <example id="zend.translate.plurals.traditional.example1">
+
+            <title>Example of traditional plural translations</title>
+
+            <para>
+                The <methodname>plural()</methodname> method accepts 4 parameters. The first
+                parameter is the singular messageId, the second is the plural messageId and the
+                third is the number or amount.
+            </para>
+
+            <para>
+                The number will be used to detect the plural which has to be returned. A optional
+                forth parameter can be used to give a locale which will be used to return the
+                translation.
+            </para>
+
+            <programlisting role="php"><![CDATA[
+$translate = new Zend_Translate('gettext', '/path/to/german.mo', 'de');
+$translate->plural('Car', 'Cars', $number);
+]]></programlisting>
+
+        </example>
+
+    </sect2>
+
+    <sect2 id="zend.translate.plurals.modern">
+
+        <title>Modern plural translations</title>
+
+        <para>
+            As traditional plural translations are restricted to source code using english plurals
+            we added a new way for plural translations. It allows to use the same
+            <methodname>translate()</methodname> for standard and for plural translations.
+        </para>
+
+        <para>
+            To use plural translations with <methodname>translate()</methodname> you need to give
+            an array as messageId instead of an string. This array must have the original plural
+            messageId's, then the amount and at last an optional locale when your given messageId's
+            are not in english notation.
+        </para>
+
+        <example id="zend.translate.plurals.modern.example1">
+
+            <title>Example of modern plural translations</title>
+
+            <para>
+                When we want to translate the same plural definitions like in the previous our
+                example would have to be defined like below.
+            </para>
+
+            <programlisting role="php"><![CDATA[
+$translate = new Zend_Translate('gettext', '/path/to/german.mo', 'de');
+$translate->translate(array('Car', 'Cars', $number));
+]]></programlisting>
+
+        </example>
+
+        <para>
+            Using modern plural translations it is also possible to use any language as source
+            for messageId's.
+        </para>
+
+        <example id="zend.translate.plurals.modern.example2">
+
+            <title>Example of modern plural translations using a different source language</title>
+
+            <para>
+                Let's expect we want to use russian and let's also expect that the given
+                messageId's are russian and not english.
+            </para>
+
+            <programlisting role="php"><![CDATA[
+$translate = new Zend_Translate('gettext', '/path/to/german.mo', 'de');
+$translate->translate(array('Car', 'Cars first plural', 'Cars second plural', $number, 'ru'));
+]]></programlisting>
+
+        </example>
+
+        <para>
+            As you can see you can give more than just the one english plural. But you must give
+            the source language in this case so <classname>Zend_Translate</classname> knows which
+            plural rules it has to apply.
+        </para>
+
+        <para>
+            When you omit the plural language then english will be used per default and any
+            additional plural definition will be ignored.
+        </para>
+
+    </sect2>
+
+    <sect2 id="zend.translate.plurals.source">
+
+        <title>Plural source files</title>
+
+        <para>
+            Not all source formats support plural forms. Look into this list for details:
+        </para>
+
+        <table id="zend.translate.plurals.source.supportedadapters">
+            <title>Plural support</title>
+            <tgroup cols="4">
+                <thead>
+                    <row>
+                        <entry>Adapter</entry>
+                        <entry>Plurals supported</entry>
+                    </row>
+                </thead>
+                <tbody>
+                    <row>
+                        <entry>Array</entry>
+                        <entry><emphasis>yes</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>Csv</entry>
+                        <entry><emphasis>yes</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>Gettext</entry>
+                        <entry><emphasis>yes</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>Ini</entry>
+                        <entry><emphasis>no</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>Qt</entry>
+                        <entry><emphasis>no</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>Tbx</entry>
+                        <entry><emphasis>no</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>Tmx</entry>
+                        <entry><emphasis>no</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>Xliff</entry>
+                        <entry><emphasis>no</emphasis></entry>
+                    </row>
+                    <row>
+                        <entry>XmlTm</entry>
+                        <entry><emphasis>no</emphasis></entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+
+        <para>
+            Below you can find examples of plural defined source files.
+        </para>
+
+        <sect3 id="zend.translate.plurals.source.array">
+
+            <title>Array source with plural definitions</title>
+
+            <para>
+                An array with plural definitions has to look like the following example.
+            </para>
+
+            <programlisting role="php"><![CDATA[
+array(
+    'plural_0' => array(
+        'plural_0 (ru)',
+        'plural_1 (ru)',
+        'plural_2 (ru)',
+        'plural_3 (ru)'
+    ),
+    'plural_1' => ''
+);
+]]></programlisting>
+
+            <para>
+                In the above example <code>plural_0</code> and <code>plural_1</code> are the
+                plural definitions from the source code. And the array at <code>plural_0</code>
+                has all translated plural forms available. Take a look at the following example
+                with real content and translation from english source to german.
+            </para>
+
+            <programlisting role="php"><![CDATA[
+array(
+    'Car' => array(
+        'Auto',
+        'Autos'
+    ),
+    'Cars' => ''
+);
+]]></programlisting>
+
+            <para>
+                When your translated language supports more plural forms then simply add them to
+                the array below the first plural form. When your source language suppors more
+                plural forms, than simply add a new empty translation.
+            </para>
+
+        </sect3>
+
+        <sect3 id="zend.translate.plurals.source.csv">
+
+            <title>Csv source with plural definitions</title>
+
+            <para>
+                A csv file with plural definitions has to look like the following example.
+            </para>
+
+            <programlisting role="php"><![CDATA[
+"plural_0";"plural_0 (ru)";"plural_1 (ru)";"plural_2 (ru)";"plural_3 (ru)"
+"plural_1";
+]]></programlisting>
+
+            <para>
+                All translated plural forms have to be added after the first plural of the source
+                language. And all further plural forms of the source language have to be added
+                below but without translation. Note that you must add a delimiter to empty
+                source plurals.
+            </para>
+
+        </sect3>
+
+        <sect3 id="zend.translate.plurals.source.gettext">
+
+            <title>Gettext source with plural definitions</title>
+
+            <para>
+                Gettext sources support plural forms out of the box. There is no need for adoption
+                as the <filename>*.mo</filename> file will contain all necessary data.
+            </para>
+
+            <note>
+
+                <para>
+                    Note that gettext does not support the usage of source languages which are not
+                    using english plural forms. When you plan to use a source language which
+                    supports other plural forms like russian for example, then you can not use
+                    gettext sources.
+                </para>
+
+            </note>
+
+        </sect3>
+
+    </sect2>
+
+</sect1>
+<!--
+vim:se ts=4 sw=4 et:
+-->

+ 0 - 1010
documentation/manual/en/module_specs/Zend_Translate-Using.xml

@@ -296,1016 +296,6 @@ print $translate->_(2) . "\n";
 
 
     </sect2>
     </sect2>
 
 
-    <sect2 id="zend.translate.using.source.array">
-
-        <title>Creating Array source files</title>
-
-        <para>
-            Array source files are plain arrays. But you have to define them
-            manually since there is no tool to aid this.
-            But because they are so simple, it's the fastest way to look up
-            messages if your code works as expected. It's generally the best
-            adapter to get started with translation business.
-        </para>
-
-        <programlisting language="php"><![CDATA[
-$english = array(
-    'message1' => 'message1',
-    'message2' => 'message2',
-    'message3' => 'message3');
-
-$german = array(
-    'message1' => 'Nachricht1',
-    'message2' => 'Nachricht2',
-    'message3' => 'Nachricht3');
-
-$translate = new Zend_Translate('array', $english, 'en');
-$translate->addTranslation($deutsch, 'de');
-]]></programlisting>
-
-        <para>
-            Since release 1.5 it is also supported to have arrays included within an external file.
-            You just have to provide the filename and <classname>Zend_Translate</classname> will automatically
-            include it and look for the array. See the following example for details:
-        </para>
-
-        <programlisting language="php"><![CDATA[
-// myarray.php
-return array(
-    'message1' => 'Nachricht1',
-    'message2' => 'Nachricht2',
-    'message3' => 'Nachricht3');
-
-// controller
-$translate = new Zend_Translate('array', '/path/to/myarray.php', 'de');
-]]></programlisting>
-
-        <note>
-            <para>
-                Files which do not return an array will fail to be included.
-                Also any output within this file will be ignored and suppressed.
-            </para>
-
-        </note>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.source.gettext">
-
-        <title>Creating Gettext source files</title>
-
-        <para>
-            Gettext source files are created by GNU's gettext library.
-            There are several free tools available that can parse your
-            code files and create the needed gettext source files.
-            These have the extension <emphasis>*.mo</emphasis>
-            and they are binary files.
-            An open source tool for creating the files is
-            <ulink url="http://sourceforge.net/projects/poedit/">poEdit</ulink>.
-            This tool also supports you during the translation process itself.
-        </para>
-
-        <programlisting language="php"><![CDATA[
-// We accume that we have created the mo files and translated them
-$translate = new Zend_Translate('gettext', '/path/to/english.mo', 'en');
-$translate->addTranslation('/path/to/german.mo', 'de');
-]]></programlisting>
-
-        <para>
-            As you can see the adapters are used exactly the same way,
-            with one small difference:
-            change <emphasis>array</emphasis> to <emphasis>gettext</emphasis>. All other usages are exactly
-            the same as with all other adapters.
-            With the gettext adapter you no longer have to be aware of
-            gettext's standard directory structure,
-            bindtextdomain and textdomain.
-            Just give the path and filename to the adapter.
-        </para>
-
-        <note>
-            <para>
-                 You should always use UTF-8 as source encoding.
-                 Otherwise you will have problems when using two
-                 different source encodings.
-                 E.g. one of your source files is encoded
-                 with ISO-8815-11 and another one with CP815.
-                 You can set only one encoding for your source file,
-                 so one of your languages probably will not display correctly.
-            </para>
-            <para>
-                 UTF-8 is a portable format which supports all languages.
-                 When using UTF-8 for all languages, you will eliminate
-                 the problem of incompatible encodings.
-            </para>
-        </note>
-
-        <para>
-            Many gettext editors add adapter informations as empty translation string.
-            This is the reason why empty strings are not translated when using the
-            gettext adapter. Instead they are erased from the translation table and
-            provided by the <code>getAdapterInfo()</code> method. It will return
-            the adapter informations for all added gettext files as array using the
-            filename as key.
-        </para>
-
-        <programlisting language="php"><![CDATA[
-// Getting the adapter informations
-$translate = new Zend_Translate('gettext', '/path/to/english.mo', 'en');
-print_r($translate->getAdapterInfo());
-]]></programlisting>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.source.tmx">
-
-        <title>Creating TMX source files</title>
-
-        <para>
-            TMX source files are a new industry standard.
-            They have the advantage of being XML files and so they are
-            readable by every editor and of course by humans.
-            You can either create TMX files manually with a text editor,
-            or you can use a special tool. But most tools currently available for
-            creating TMX source files are not freely available.
-        </para>
-
-        <example id="zend.translate.using.source.tmx.example">
-            <title>Example TMX file</title>
-            <programlisting language="xml"><![CDATA[
-<?xml version="1.0" ?>
-<!DOCTYPE tmx SYSTEM "tmx14.dtd">
-<tmx version="1.4">
- <header creationtoolversion="1.0.0" datatype="winres" segtype="sentence"
-         adminlang="en-us" srclang="de-at" o-tmf="abc"
-         creationtool="XYZTool" >
- </header>
- <body>
-  <tu tuid='message1'>
-   <tuv xml:lang="de"><seg>Nachricht1</seg></tuv>
-   <tuv xml:lang="en"><seg>message1</seg></tuv>
-  </tu>
-  <tu tuid='message2'>
-   <tuv xml:lang="en"><seg>message2</seg></tuv>
-   <tuv xml:lang="de"><seg>Nachricht2</seg></tuv>
-  </tu>
-]]></programlisting>
-
-            <programlisting language="php"><![CDATA[
-$translate = new Zend_Translate('tmx', 'path/to/mytranslation.tmx', 'en');
-]]></programlisting>
-        </example>
-
-        <para>
-            TMX files can have several languages within the same file.
-            All other included languages are added automatically,
-            so you do not have to call <code>addLanguage()</code>.
-        </para>
-
-        <para>
-            If you want to have only specified languages from the source translated
-            you can set the option '<code>defined_language</code>' to <constant>TRUE</constant>.
-            With this option you can add the wished languages explicitly with
-            <code>addLanguage()</code>. The default value for this option is to add all
-            languages.
-        </para>
-    </sect2>
-
-    <sect2 id="zend.translate.using.source.csv">
-
-        <title>Creating CSV source files</title>
-
-        <para>
-            CSV source files are small and human readable.
-            If your customers want to translate their own,
-            you will probably use the CSV adapter.
-        </para>
-
-        <example id="zend.translate.using.source.csv.example">
-            <title>Example CSV file</title>
-            <programlisting language="txt"><![CDATA[
-#Example csv file
-message1;Nachricht1
-message2;Nachricht2
-]]></programlisting>
-
-            <programlisting language="php"><![CDATA[
-$translate = new Zend_Translate('csv', '/path/to/mytranslation.csv', 'de');
-$translate->addTranslation('path/to/other.csv', 'fr');
-]]></programlisting>
-        </example>
-
-        <para>
-            There are three different options for the CSV adapter.
-            You can set '<code>delimiter</code>', '<code>limit</code>' and
-            '<code>enclosure</code>'.
-        </para>
-
-        <para>
-            The default delimiter for CSV string is '<code>;</code>', but
-            with the option '<code>delimiter</code>'
-            you can decide to use another one.
-        </para>
-
-        <para>
-            The default limit for a line within a CSV file is '<code>0</code>'. This means
-            that the end of a CSV line is searched automatically. If you set
-            '<code>limit</code>' to any value, then the CSV file will be
-            read faster, but any line exceeding this limit will be truncated.
-        </para>
-
-        <para>
-            The default enclosure to use for CSV files is '<code>"</code>'. You can
-            set a different one using the option '<code>enclosure</code>'.
-        </para>
-
-        <example id="zend.translate.using.source.csv.example2">
-            <title>Second CSV file example</title>
-            <programlisting language="txt"><![CDATA[
-# Example CSV file
-"message,1",Nachricht1
-message2,"Nachricht,2"
-"message3,",Nachricht3
-]]></programlisting>
-
-            <programlisting language="php"><![CDATA[
-$translate = new Zend_Translate(
-    'csv',
-    '/path/to/mytranslation.csv',
-    'de',
-    array('delimiter' => ','));
-
-$translate->addTranslation('/path/to/other.csv', 'fr');
-]]></programlisting>
-        </example>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.source.ini">
-
-        <title>Creating INI source files</title>
-
-        <para>
-            INI source files are human readable but normally not very small as they also
-            include other data beside translations. If you have data which shall be
-            editable by your customers you can use the INI adapter.
-        </para>
-
-        <example id="zend.translate.using.source.ini.example">
-            <title>Example INI file</title>
-            <programlisting language="txt"><![CDATA[
-[Test]
-;TestPage Comment
-Message_1="Nachricht 1 (de)"
-Message_2="Nachricht 2 (de)"
-Message_3="Nachricht :3 (de)"
-]]></programlisting>
-
-            <programlisting language="php"><![CDATA[
-$translate = new Zend_Translate('ini', '/path/to/mytranslation.ini', 'de');
-$translate->addTranslation('/path/to/other.ini', 'it');
-]]></programlisting>
-        </example>
-
-        <para>
-            INI files have several restrictions. If a value in the ini file contains any
-            non-alphanumeric characters it needs to be enclosed in double-quotes (<code>"</code>).
-            There are also reserved words which must not be used as keys for ini files.
-            These include: <constant>NULL</constant>, <code>yes</code>, <code>no</code>, <constant>TRUE</constant>,
-            and <constant>FALSE</constant>. Values <constant>NULL</constant>, <code>no</code> and <constant>FALSE</constant> results
-            in <code>""</code>, <code>yes</code> and <constant>TRUE</constant> results in <code>1</code>. Characters <code>{}|&amp;~![()"</code> must not be used anywhere
-            in the key and have a special meaning in the value. Do not use them as it will
-            produce unexpected behaviour.
-        </para>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.options">
-
-        <title>Options for adapters</title>
-
-        <para>
-            Options can be used with all adapters. Of course the options are different for all adapters.
-            You can set options when you create the adapter. Actually there is one option which is available
-            to all adapters: '<code>clear</code>' sets if translation data should be added to existing
-            one or not. Standard behaviour is to add new translation data to existing one. But the
-            translation data is only cleared for the selected language. So other languages remain
-            untouched.
-        </para>
-
-        <para>
-            You can set options temporarily when using <code>addTranslation($data, $locale, array $options = array())</code>
-            as third and optional parameter. And you can use the method <code>setOptions()</code> to
-            set the options permanently.
-        </para>
-
-        <example id="zend.translate.using.options.example">
-            <title>Using translation options</title>
-            <programlisting language="php"><![CDATA[
-// define ':' as separator for the translation source files
-$options = array('delimiter' => ':');
-$translate = new Zend_Translate(
-    'csv',
-    '/path/to/mytranslation.csv',
-    'de',
-    $options);
-
-...
-
-// clear the defined language and use new translation data
-$options = array('clear' => true);
-$translate->addTranslation('/path/to/new.csv', 'fr', $options);
-]]></programlisting>
-        </example>
-
-        <para>
-            Here you can find all available options for the different adapters with a description of their usage:
-        </para>
-
-        <table id="zend.translate.using.options.alloptions">
-            <title>Options for translation adapters</title>
-            <tgroup cols="4">
-                <thead>
-                    <row>
-                        <entry>Option</entry>
-                        <entry>Adapter</entry>
-                        <entry>Description</entry>
-                        <entry>Default value</entry>
-                    </row>
-                </thead>
-                <tbody>
-                    <row>
-                        <entry>clear</entry>
-                        <entry>all</entry>
-                        <entry>
-                            If set to true, the already read translations will be cleared. This can be used
-                            instead of creating a new instance when reading new translation data
-                        </entry>
-                        <entry><emphasis>false</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>disableNotices</entry>
-                        <entry>all</entry>
-                        <entry>
-                            If set to true, all notices regarding not available translations will be
-                            disabled. You should set this option to true in production environment
-                        </entry>
-                        <entry><emphasis>false</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>ignore</entry>
-                        <entry>all</entry>
-                        <entry>
-                            All directories and files beginning with this prefix will be ignored when
-                            searching for files. This value defaults to <emphasis>'.'</emphasis>
-                            which leads to the behavior that all hidden files will be ignored. Setting this
-                            value to <code>'tmp'</code> would mean that directories and files like
-                            <code>'tmpImages'</code> and <code>'tmpFiles'</code>
-                            would be ignored as well as all subsequent directories
-                        </entry>
-                        <entry><emphasis>.</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>log</entry>
-                        <entry>all</entry>
-                        <entry>
-                            An instance of <classname>Zend_Log</classname> where untranslated messages and notices will be
-                            written to
-                        </entry>
-                        <entry><emphasis>null</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>logMessage</entry>
-                        <entry>all</entry>
-                        <entry>
-                            The message which will be written into the log
-                        </entry>
-                        <entry><emphasis>Untranslated message within '%locale%': %message%</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>logUntranslated</entry>
-                        <entry>all</entry>
-                        <entry>
-                            When this option is set to true, all message IDs which can not be
-                            translated will be written into the attached log
-                        </entry>
-                        <entry><emphasis>false</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>scan</entry>
-                        <entry>all</entry>
-                        <entry>
-                            If set to null, no scanning of the directory structure will be done.
-                            If set to <classname>Zend_Translate::LOCALE_DIRECTORY</classname> the locale will be detected within the
-                            directory. If set to <classname>Zend_Translate::LOCALE_FILENAME</classname> the locale will be detected
-                            within the filename. See <xref linkend="zend.translate.using.detection" />
-                            for details
-                        </entry>
-                        <entry><emphasis>null</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>delimiter</entry>
-                        <entry>Csv</entry>
-                        <entry>Defines which sign is used as delimiter for separating source and translation</entry>
-                        <entry><emphasis>;</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>enclosure</entry>
-                        <entry>Csv</entry>
-                        <entry>Defines the enclosure character to be used. Defaults to a doublequote</entry>
-                        <entry><emphasis>"</emphasis></entry>
-                    </row>
-                    <row>
-                        <entry>length</entry>
-                        <entry>Csv</entry>
-                        <entry>Defines the maximum length of a csv line. When set to 0 it will be detected automatically</entry>
-                        <entry><emphasis>0</emphasis></entry>
-                    </row>
-                </tbody>
-            </tgroup>
-        </table>
-
-        <para>
-            When you want to have self defined options, you are also able to use them within all adapters.
-            The <code>setOptions()</code> method can be used to define your option. <code>setOptions()</code>
-            needs an array with the options you want to set. If an given option exists it will be signed over.
-            You can define as much options as needed as they will not be checked by the adapter. Just make sure
-            not to overwrite any existing option which is used by an adapter.
-        </para>
-
-        <para>
-            To return the option you can use the <code>getOptions()</code> method. When <code>getOptions()</code>
-            is called without a parameter it will return all options set. When the optional parameter is given
-            you will only get the specified option.
-        </para>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.languages">
-
-        <title>Handling languages</title>
-
-        <para>
-            When working with different languages there are a few methods which will be useful.
-        </para>
-
-        <para>
-            The <code>getLocale()</code> method can be used to get the currently set language. It can either hold
-            an instance of <classname>Zend_Locale</classname> or the identifier of a locale.
-        </para>
-
-        <para>
-            The <code>setLocale()</code> method sets a new standard language for translation. This prevents the
-            need of setting the optional language parameter more than once to the <code>translate()</code> method.
-            If the given language does not exist, or no translation data is available for the language,
-            <code>setLocale()</code> tries to downgrade to the language without the region if any was given.
-            A language of <code>en_US</code> would be downgraded to <code>en</code>. When even the downgraded
-            language can not be found an exception will be thrown.
-        </para>
-
-        <para>
-            The <code>isAvailable()</code> method checks if a given language is already available. It returns
-            <constant>TRUE</constant> if data for the given language exist.
-        </para>
-
-        <para>
-            And finally the <code>getList()</code> method can be used to get all currently set languages for an adapter
-            returned as array.
-        </para>
-
-        <example id="zend.translate.using.languages.example">
-            <title>Handling languages with adapters</title>
-            <programlisting language="php"><![CDATA[
-// returns the currently set language
-$actual = $translate->getLocale();
-
-// you can use the optional parameter while translating
-echo $translate->_("my_text", "fr");
-// or set a new language
-$translate->setLocale("fr");
-echo $translate->_("my_text");
-// refer to the base language
-// fr_CH will be downgraded to fr
-$translate->setLocale("fr_CH");
-echo $translate->_("my_text");
-
-// check if this language exist
-if ($translate->isAvailable("fr")) {
-    // language exists
-}
-]]></programlisting>
-        </example>
-
-        <sect3 id="zend.translate.using.languages.automatic">
-
-            <title>Automatical handling of languages</title>
-
-            <para>
-                Note that as long as you only add new translation sources with the <code>addTranslation()</code>
-                method <classname>Zend_Translate</classname> will automatically set the best fitting language for your
-                environment when you use one of the automatic locales which are '<code>auto</code>' or '<code>browser</code>'. So
-                normally you will not need to call <code>setLocale()</code>. This should only be used in
-                conjunction with automatic source detection.
-            </para>
-
-            <para>
-                The algorithm will search for the best fitting locale depending on the user's browser and
-                your environment. See the following example for details:
-            </para>
-
-            <example id="zend.translate.using.languages.automatic.example">
-                <title>Automatically language detection</title>
-                <programlisting language="php"><![CDATA[
-// Let's expect the browser returns these language settings:
-// HTTP_ACCEPT_LANGUAGE = "de_AT=1;fr=1;en_US=0.8";
-
-// Example 1:
-// When no fitting language is found, the message ID is returned
-$translate = new Zend_Translate(
-    'gettext',
-    'my_it.mo',
-    'auto',
-    array('scan' => Zend_Translate::LOCALE_FILENAME));
-
-// Example 2:
-// Best found fitting language is 'fr'
-$translate = new Zend_Translate(
-    'gettext',
-    'my_fr.mo',
-    'auto',
-    array('scan' => Zend_Translate::LOCALE_FILENAME));
-
-// Example 3:
-// Best found fitting language is 'de' ('de_AT' will be degraded)
-$translate = new Zend_Translate(
-    'gettext',
-    'my_de.mo',
-    'auto',
-    array('scan' => Zend_Translate::LOCALE_FILENAME));
-
-// Example 4:
-// Returns 'it' as translation source and overrides the automatic settings
-$translate = new Zend_Translate(
-    'gettext',
-    'my_it.mo',
-    'auto',
-    array('scan' => Zend_Translate::LOCALE_FILENAME));
-
-$translate->addTranslation('my_ru.mo', 'ru');
-$translate->setLocale('it_IT');
-]]></programlisting>
-            </example>
-
-            <para>
-                After setting a language manually with the <code>setLocale()</code> method the automatic
-                detection will be switched off and overridden.
-            </para>
-
-            <para>
-                If you want to use it again, you can set the language
-                <emphasis>auto</emphasis> with <code>setLocale()</code> which will reactivate
-                the automatic detection for <classname>Zend_Translate</classname>.
-            </para>
-
-            <para>
-                Since Zend Framework 1.7.0 <classname>Zend_Translate</classname> also recognises an application
-                wide locale. You can simply set a <classname>Zend_Locale</classname> instance to the registry like shown
-                below. With this notation you can forget about setting the locale manually with each instance
-                when you want to use the same locale multiple times.
-            </para>
-
-            <programlisting language="php"><![CDATA[
-// in your bootstrap file
-$locale = new Zend_Locale();
-Zend_Registry::set('Zend_Locale', $locale);
-
-// default language when requested language is not available
-$defaultlanguage = 'en';
-
-// somewhere in your application
-$translate = new Zend_Translate('gettext', 'my_de.mo');
-
-if (!$translate->isAvailable($locale->getLanguage())) {
-    // not available languages are rerouted to another language
-    $translate->setLocale($defaultlanguage);
-}
-
-$translate->getLocale();
-]]></programlisting>
-
-        </sect3>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.detection">
-
-        <title>Automatic source detection</title>
-
-        <para>
-            <classname>Zend_Translate</classname> can detect translation sources automatically. So you don't have
-            to declare each source file manually. You can let <classname>Zend_Translate</classname> do this job and
-            scan the complete directory structure for source files.
-        </para>
-
-        <note>
-            <para>
-                Automatic source detection is available since Zend Framework version 1.5 .
-            </para>
-        </note>
-
-        <para>
-            The usage is quite the same as initiating a single translation source with one difference.
-            You must give a directory which has to be scanned instead a file.
-        </para>
-
-        <example id="zend.translate.using.languages.directory.example">
-            <title>Scanning a directory structure for sources</title>
-            <programlisting language="php"><![CDATA[
-// assuming we have the following structure
-//  /language/
-//  /language/login/login.tmx
-//  /language/logout/logout.tmx
-//  /language/error/loginerror.tmx
-//  /language/error/logouterror.tmx
-
-$translate = new Zend_Translate('tmx', '/language');
-]]></programlisting>
-        </example>
-
-        <para>
-            So <classname>Zend_Translate</classname> does not only search the given directory, but also all subdirectories for
-            translation source files. This makes the usage quite simple. But <classname>Zend_Translate</classname> will ignore all
-            files which are not sources or which produce failures while reading the translation data. So you
-            have to make sure that all of your translation sources are correct and readable because you will
-            not get any failure if a file is bogus or can not be read.
-        </para>
-
-        <note>
-            <para>
-                Depending on how deep your directory structure is and how much files are within this structure
-                it can take a long time for <classname>Zend_Translate</classname> to complete.
-            </para>
-        </note>
-
-        <para>
-            In our example we have used the TMX format which includes the language to be used within the
-            source. But many of the other source formats are not able to include the language within the
-            file. Even this sources can be used with automatic scanning if you do some pre-requisits as
-            described below:
-        </para>
-
-        <sect3 id="zend.translate.using.detection.directory">
-
-            <title>Language through naming directories</title>
-
-            <para>
-                One way to include automatic language detection is to name the directories related to the
-                language which is used for the sources within this directory. This is the easiest way and
-                is used for example within standard gettext implementations.
-            </para>
-
-            <para>
-                <classname>Zend_Translate</classname> needs the '<code>scan</code>' option to know that it should search the names of all
-                directories for languages. See the following example for details:
-            </para>
-
-            <example id="zend.translate.using.detection.directory.example">
-                <title>Directory scanning for languages</title>
-                <programlisting language="php"><![CDATA[
-// assuming we have the following structure
-//  /language/
-//  /language/de/login/login.mo
-//  /language/de/error/loginerror.mo
-//  /language/en/login/login.mo
-//  /language/en/error/loginerror.mo
-
-$translate = new Zend_Translate(
-    'gettext',
-    '/language',
-    null,
-    array('scan' => Zend_Translate::LOCALE_DIRECTORY));
-]]></programlisting>
-            </example>
-
-            <note>
-                <para>
-                    This works only for adapters which do not include the language within the source file.
-                    Using this option for example with TMX will be ignored. Also language definitions within
-                    the filename will be ignored when using this option.
-                </para>
-            </note>
-
-            <note>
-                <para>
-                    You should be aware if you have several subdirectories under the same
-                    structure. Assuming we have a structure like
-                    <code>/language/module/de/en/file.mo</code>. In this case the path contains
-                    multiple strings which would be detected as locale. It could be either
-                    <code>de</code> or <code>en</code>. In such a case the behaviour is
-                    undefined and it is recommended to use file detection in such situations.
-                </para>
-            </note>
-
-        </sect3>
-
-        <sect3 id="zend.translate.using.detection.filename">
-
-            <title>Language through filenames</title>
-
-            <para>
-                Another way to detect the language automatically is to use special filenames. You can either
-                name the complete file or parts of a file after the used language. To use this way of detection
-                you will have to set the '<code>scan</code>' option at initiation. There are several ways of naming the
-                sourcefiles which are described below:
-            </para>
-
-            <example id="zend.translate.using.detection.filename.example">
-                <title>Filename scanning for languages</title>
-                <programlisting language="php"><![CDATA[
-// assuming we have the following structure
-//  /language/
-//  /language/login/login_en.mo
-//  /language/login/login_de.mo
-//  /language/error/loginerror_en.mo
-//  /language/error/loginerror_de.mo
-
-$translate = new Zend_Translate(
-    'gettext',
-    '/language',
-    null,
-    array('scan' => Zend_Translate::LOCALE_FILENAME));
-]]></programlisting>
-            </example>
-
-            <sect4 id="zend.translate.using.detection.filename.complete">
-
-                <title>Complete filename</title>
-
-                <para>
-                    Having the whole file named after the language is the simplest way but only viable
-                    if you have only one file per language.
-                </para>
-
-                <programlisting language="txt"><![CDATA[
-/languages/
-/languages/en.mo
-/languages/de.mo
-/languages/es.mo
-]]></programlisting>
-
-            </sect4>
-
-            <sect4 id="zend.translate.using.detection.filename.extension">
-
-                <title>Extension of the file</title>
-
-                <para>
-                    Another simple way to use the extension of the file for language detection.
-                    But this may be confusing since you will no longer have an idea which extension the file
-                    originally had.
-                </para>
-
-                <programlisting language="txt"><![CDATA[
-/languages/
-/languages/view.en
-/languages/view.de
-/languages/view.es
-]]></programlisting>
-
-            </sect4>
-
-            <sect4 id="zend.translate.using.detection.filename.token">
-
-                <title>Filename tokens</title>
-
-                <para>
-                    <classname>Zend_Translate</classname> is also capable of detecting the language if it is included within the
-                    filename. But if you go this way you will have to separate the language with a token.
-                    There are three supported tokens which can be used: a dot '.', an underscore '_', or
-                    a hyphen '-'.
-                </para>
-
-                <programlisting language="txt"><![CDATA[
-/languages/
-/languages/view_en.mo -> detects english
-/languages/view_de.mo -> detects german
-/languages/view_it.mo -> detects italian
-]]></programlisting>
-
-                <para>
-                    The first found string delimited by a token which can be interpreted as a locale will be used. See the following
-                    example for details.
-                </para>
-
-                <programlisting language="txt"><![CDATA[
-/languages/
-/languages/view_en_de.mo -> detects english
-/languages/view_en_es.mo -> detects english and overwrites the first file
-/languages/view_it_it.mo -> detects italian
-]]></programlisting>
-
-                <para>
-                    All three tokens are used to detect the locale. When the filename contains multiple tokens,
-                    the first found token depends on the order of the tokens which are used. See the following
-                    example for details.
-                </para>
-
-                <programlisting language="txt"><![CDATA[
-/languages/
-/languages/view_en-it.mo -> detects english because '_' will be used before '-'
-/languages/view-en_it.mo -> detects italian because '_' will be used before '-'
-/languages/view_en.it.mo -> detects italian because '.' will be used before '_'
-]]></programlisting>
-
-            </sect4>
-
-        </sect3>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.istranslated">
-
-        <title>Checking for translations</title>
-
-        <para>
-            Normally text will be translated without any computation. But sometimes it is necessary to
-            know if a text is translated or not, therefor the <code>isTranslated()</code>
-            method can be used.
-        </para>
-
-        <para>
-            <code>isTranslated($messageId, $original = false, $locale = null)</code> takes
-            the text you want to check as its first parameter, and as optional third parameter the locale
-            for which you want to do the check. The optional second parameter declares whether translation
-            is fixed to the declared language or a lower set of translations can be used. If you have a text which
-            can be returned for 'en' but not for 'en_US' you will normally get the translation returned, but by
-            setting <code>$original</code> to true, <code>isTranslated()</code> will return false.
-        </para>
-
-        <example id="zend.translate.using.istranslated.example">
-            <title>Checking if a text is translatable</title>
-            <programlisting language="php"><![CDATA[
-$english = array(
-    'message1' => 'Nachricht 1',
-    'message2' => 'Nachricht 2',
-    'message3' => 'Nachricht 3');
-
-$translate = new Zend_Translate('array', $english, 'de_AT');
-
-if ($translate->isTranslated('message1')) {
-    print "'message1' can be translated";
-}
-
-if (!($translate->isTranslated('message1', true, 'de'))) {
-    print "'message1' can not be translated to 'de'"
-        . " as it's available only in 'de_AT'";
-}
-
-if ($translate->isTranslated('message1', false, 'de')) {
-    print "'message1' can be translated in 'de_AT' as it falls back to 'de'";
-}
-]]></programlisting>
-        </example>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.logging">
-
-        <title>How to log not found translations</title>
-
-        <para>
-            When you have a bigger site or you are creating the translation files manually, you often have
-            the problem that some messages are not translated. But there is an easy solution for you when you
-            are using <classname>Zend_Translate</classname>.
-        </para>
-
-        <para>
-            You have to follow two or three simple steps. First, you have to create an instance of
-            <classname>Zend_Log</classname>. Then you have to attach this instance to <classname>Zend_Translate</classname>.
-            See the following example:
-        </para>
-
-        <example id="zend.translate.using.logging.example">
-            <title>Log translations</title>
-            <programlisting language="php"><![CDATA[
-$translate = new Zend_Translate('gettext', $path, 'de');
-
-// Create a log instance
-$writer = new Zend_Log_Writer_Stream('/path/to/file.log');
-$log    = new Zend_Log($writer);
-
-// Attach it to the translation instance
-$translate->setOptions(array(
-    'log'             => $log,
-    'logUntranslated' => true));
-
-$translate->translate('unknown string');
-]]></programlisting>
-        </example>
-
-        <para>
-            Now you will have a new notice in the log: <code>Untranslated message within 'de': unknown string</code>.
-        </para>
-
-        <note>
-            <para>
-                You should note that any translation which can not be found will be logged. This means
-                all translations when a user requests a language which is not supported. Also every request
-                for a message which can not be translated will be logged. Be aware, that 100 people
-                requesting the same translation, will result 100 logged notices.
-            </para>
-        </note>
-
-        <para>
-            This feature can not only be used to log messages but also to attach this untranslated messages
-            into an empty translation file. To do so you will have to write your own log writer which
-            writes the format you want to have and strips the prepending "Untranslated message".
-        </para>
-
-        <para>
-            You can also set the '<code>logMessage</code>' option when you want to have your own log message.
-            Use the '<code>%message%</code>' token for placing the messageId within your log message, and the
-            '<code>%locale%</code>' token for the requested locale. See the following example for a self
-            defined log message:
-        </para>
-
-        <example id="zend.translate.using.logging.example2">
-            <title>Self defined log messages</title>
-            <programlisting language="php"><![CDATA[
-$translate = new Zend_Translate('gettext', $path, 'de');
-
-// Create a log instance
-$writer = new Zend_Log_Writer_Stream('/path/to/file.log');
-$log    = new Zend_Log($writer);
-
-// Attach it to the translation instance
-$translate->setOptions(array(
-    'log'             => $log,
-    'logMessage'      => "Missing '%message%' within locale '%locale%'",
-    'logUntranslated' => true));
-
-$translate->translate('unknown string');
-]]></programlisting>
-        </example>
-
-    </sect2>
-
-    <sect2 id="zend.translate.using.sourcedata">
-
-        <title>Accessing source data</title>
-
-        <para>
-            Sometimes it is useful to have access to the translation source data. Therefor
-            the following two functions are provided.
-        </para>
-
-        <para>
-            The <code>getMessageIds($locale = null)</code> method returns all known message IDs as array.
-        </para>
-
-        <para>
-            The <code>getMessages($locale = null)</code> method returns the complete translation source as
-            an array. The message ID is used as key and the translation data as value.
-        </para>
-
-        <para>
-            Both methods accept an optional parameter <code>$locale</code> which, if set, returns the
-            translation data for the specified language. If this parameter is not given, the actual set
-            language will be used. Keep in mind that normally all translations should be available in all
-            languages. Which means that in a normal situation you will not have to set this parameter.
-        </para>
-
-        <para>
-            Additionally the <code>getMessages()</code> method can be used to return the complete
-            translation dictionary using the pseudo-locale 'all'. This will return all available
-            translation data for each added locale.
-        </para>
-
-        <note>
-            <para>
-                Attention: the returned array can be <emphasis>very big</emphasis>,
-                depending on the number of added locales and the amount of translation data.
-            </para>
-        </note>
-
-        <example id="zend.translate.using.sourcedata.example">
-            <title>Handling languages with adapters</title>
-            <programlisting language="php"><![CDATA[
-// returns all known message IDs
-$messageIds = $translate->getMessageIds();
-print_r($messageIds);
-
-// or just for the specified language
-$messageIds = $translate->getMessageIds('en_US');
-print_r($messageIds);
-
-// returns all the complete translation data
-$source = $translate->getMessages();
-print_r($source);
-]]></programlisting>
-        </example>
-
-    </sect2>
-
 </sect1>
 </sect1>
 <!--
 <!--
 vim:se ts=4 sw=4 et:
 vim:se ts=4 sw=4 et:

+ 85 - 19
library/Zend/Translate/Adapter.php

@@ -26,6 +26,11 @@
 require_once 'Zend/Locale.php';
 require_once 'Zend/Locale.php';
 
 
 /**
 /**
+ * @see Zend_Translate_Plural
+ */
+require_once 'Zend/Translate/Plural.php';
+
+/**
  * Basic adapter class for each translation source adapter
  * Basic adapter class for each translation source adapter
  *
  *
  * @category   Zend
  * @category   Zend
@@ -244,7 +249,7 @@ abstract class Zend_Translate_Adapter {
         }
         }
 
 
         if ($locale !== null) {
         if ($locale !== null) {
-            $this->setLocale($option);
+            $this->setLocale($locale);
         }
         }
 
 
         if (isset(self::$_cache) and ($change == true)) {
         if (isset(self::$_cache) and ($change == true)) {
@@ -452,7 +457,7 @@ abstract class Zend_Translate_Adapter {
 
 
         $read = true;
         $read = true;
         if (isset(self::$_cache)) {
         if (isset(self::$_cache)) {
-            $id = 'Zend_Translate_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $data) . '_' . $this->toString();
+            $id = 'Zend_Translate_' . md5(serialize($data)) . '_' . $this->toString();
             $result = self::$_cache->load($id);
             $result = self::$_cache->load($id);
             if ($result) {
             if ($result) {
                 $temp = unserialize($result);
                 $temp = unserialize($result);
@@ -474,7 +479,7 @@ abstract class Zend_Translate_Adapter {
                 $this->_translate[$key] = array();
                 $this->_translate[$key] = array();
             }
             }
 
 
-            $this->_translate[$key] = $this->_translate[$key] + $temp[$key];
+            $this->_translate[$key] = array_merge($this->_translate[$key], $temp[$key]);
         }
         }
 
 
         if ($this->_automatic === true) {
         if ($this->_automatic === true) {
@@ -490,7 +495,7 @@ abstract class Zend_Translate_Adapter {
         }
         }
 
 
         if (($read) and (isset(self::$_cache))) {
         if (($read) and (isset(self::$_cache))) {
-            $id = 'Zend_Translate_' . preg_replace('/[^a-zA-Z0-9_]/', '_', $data) . '_' . $this->toString();
+            $id = 'Zend_Translate_' . md5(serialize($data)) . '_' . $this->toString();
             self::$_cache->save( serialize($temp), $id, array('Zend_Translate'));
             self::$_cache->save( serialize($temp), $id, array('Zend_Translate'));
         }
         }
 
 
@@ -502,7 +507,7 @@ abstract class Zend_Translate_Adapter {
      * returns the translation
      * returns the translation
      *
      *
      * @see Zend_Locale
      * @see Zend_Locale
-     * @param  string             $messageId Translation string
+     * @param  string|array       $messageId Translation string, or Array for plural translations
      * @param  string|Zend_Locale $locale    (optional) Locale/Language to use, identical with
      * @param  string|Zend_Locale $locale    (optional) Locale/Language to use, identical with
      *                                       locale identifier, @see Zend_Locale for more information
      *                                       locale identifier, @see Zend_Locale for more information
      * @return string
      * @return string
@@ -513,38 +518,99 @@ abstract class Zend_Translate_Adapter {
             $locale = $this->_options['locale'];
             $locale = $this->_options['locale'];
         }
         }
 
 
+        $plural = null;
+        if (is_array($messageId)) {
+            if (count($messageId) > 2) {
+                $number    = array_pop($messageId);
+                if (!is_numeric($number)) {
+                    $plocale = $number;
+                    $number       = array_pop($messageId);
+                } else {
+                    $plocale = 'en';
+                }
+
+                $plural    = $messageId;
+                $messageId = $messageId[0];
+            } else {
+                $messageId = $messageId[0];
+            }
+        }
+
         if (!Zend_Locale::isLocale($locale, true, false)) {
         if (!Zend_Locale::isLocale($locale, true, false)) {
             if (!Zend_Locale::isLocale($locale, false, false)) {
             if (!Zend_Locale::isLocale($locale, false, false)) {
                 // language does not exist, return original string
                 // language does not exist, return original string
                 $this->_log($messageId, $locale);
                 $this->_log($messageId, $locale);
-                return $messageId;
+                if ($plural === null) {
+                    return $messageId;
+                }
+
+                $rule = Zend_Translate_Plural::getPlural($number, $plocale);
+                if (!isset($plural[$rule])) {
+                    $rule = 0;
+                }
+
+                return $plural[$rule];
             }
             }
 
 
             $locale = new Zend_Locale($locale);
             $locale = new Zend_Locale($locale);
         }
         }
 
 
         $locale = (string) $locale;
         $locale = (string) $locale;
-        if ((is_string($messageId) || is_numeric($messageId))
-            && isset($this->_translate[$locale]) 
-            && is_array($this->_translate[$locale]) 
-            && isset($this->_translate[$locale][$messageId])
-        ) {
+        if (isset($this->_translate[$locale][$messageId])) {
             // return original translation
             // return original translation
-            return $this->_translate[$locale][$messageId];
+            if ($plural === null) {
+                return $this->_translate[$locale][$messageId];
+            }
+
+            $rule = Zend_Translate_Plural::getPlural($number, $locale);
+            if (isset($this->_translate[$locale][$plural[0]][$rule])) {
+                return $this->_translate[$locale][$plural[0]][$rule];
+            }
         } else if (strlen($locale) != 2) {
         } else if (strlen($locale) != 2) {
             // faster than creating a new locale and separate the leading part
             // faster than creating a new locale and separate the leading part
             $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
             $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
 
 
-            if ((is_string($messageId) || is_numeric($messageId))
-                && isset($this->_translate[$locale][$messageId])
-            ) {
+            if (isset($this->_translate[$locale][$messageId])) {
                 // return regionless translation (en_US -> en)
                 // return regionless translation (en_US -> en)
-                return $this->_translate[$locale][$messageId];
+                if ($plural === null) {
+                    return $this->_translate[$locale][$messageId];
+                }
+
+                $rule = Zend_Translate_Plural::getPlural($number, $locale);
+                if (isset($this->_translate[$locale][$plural[0]][$rule])) {
+                    return $this->_translate[$locale][$plural[0]][$rule];
+                }
             }
             }
         }
         }
 
 
         $this->_log($messageId, $locale);
         $this->_log($messageId, $locale);
-        return $messageId;
+        if ($plural === null) {
+            return $messageId;
+        }
+
+        $rule = Zend_Translate_Plural::getPlural($number, $plocale);
+        if (!isset($plural[$rule])) {
+            $rule = 0;
+        }
+
+        return $plural[$rule];
+    }
+
+    /**
+     * Translates the given string using plural notations
+     * Returns the translated string
+     *
+     * @see Zend_Locale
+     * @param  string             $singular Singular translation string
+     * @param  string             $plural   Plural translation string
+     * @param  integer            $number   Number for detecting the correct plural
+     * @param  string|Zend_Locale $locale   (Optional) Locale/Language to use, identical with
+     *                                      locale identifier, @see Zend_Locale for more information
+     * @return string
+     */
+    public function plural($singular, $plural, $number, $locale = null)
+    {
+        return $this->translate(array($singular, $plural, $number), $locale);
     }
     }
 
 
     /**
     /**
@@ -609,7 +675,7 @@ abstract class Zend_Translate_Adapter {
                 return false;
                 return false;
             }
             }
 
 
-            $locale = new Zend_Locale();
+            $locale = new Zend_Locale($locale);
         }
         }
 
 
         $locale = (string) $locale;
         $locale = (string) $locale;
@@ -692,4 +758,4 @@ abstract class Zend_Translate_Adapter {
      * @return string
      * @return string
      */
      */
     abstract public function toString();
     abstract public function toString();
-}
+}

+ 7 - 2
library/Zend/Translate/Adapter/Csv.php

@@ -77,11 +77,16 @@ class Zend_Translate_Adapter_Csv extends Zend_Translate_Adapter
                 continue;
                 continue;
             }
             }
 
 
-            if (isset($data[1]) !== true) {
+            if (!isset($data[1])) {
                 continue;
                 continue;
             }
             }
 
 
-            $this->_data[$locale][$data[0]] = $data[1];
+            if (count($data) == 2) {
+                $this->_data[$locale][$data[0]] = $data[1];
+            } else {
+                $singular = array_shift($data);
+                $this->_data[$locale][$singular] = $data;
+            }
         }
         }
 
 
         return $this->_data;
         return $this->_data;

+ 13 - 2
library/Zend/Translate/Adapter/Gettext.php

@@ -124,13 +124,24 @@ class Zend_Translate_Adapter_Gettext extends Zend_Translate_Adapter {
             if ($origtemp[$count * 2 + 1] != 0) {
             if ($origtemp[$count * 2 + 1] != 0) {
                 fseek($this->_file, $origtemp[$count * 2 + 2]);
                 fseek($this->_file, $origtemp[$count * 2 + 2]);
                 $original = @fread($this->_file, $origtemp[$count * 2 + 1]);
                 $original = @fread($this->_file, $origtemp[$count * 2 + 1]);
+                $original = explode(chr(00), $original);
             } else {
             } else {
-                $original = '';
+                $original[0] = '';
             }
             }
 
 
             if ($transtemp[$count * 2 + 1] != 0) {
             if ($transtemp[$count * 2 + 1] != 0) {
                 fseek($this->_file, $transtemp[$count * 2 + 2]);
                 fseek($this->_file, $transtemp[$count * 2 + 2]);
-                $this->_data[$locale][$original] = fread($this->_file, $transtemp[$count * 2 + 1]);
+                $translate = fread($this->_file, $transtemp[$count * 2 + 1]);
+                $translate = explode(chr(00), $translate);
+                if ((count($original) > 1) && (count($translate) > 1)) {
+                    $this->_data[$locale][$original[0]] = $translate;
+                    array_shift($original);
+                    foreach ($original as $orig) {
+                        $this->_data[$locale][$orig] = '';
+                    }
+                } else {
+                    $this->_data[$locale][$original[0]] = $translate[0];
+                }
             }
             }
         }
         }
 
 

+ 1 - 1
library/Zend/Translate/Adapter/Ini.php

@@ -66,7 +66,7 @@ class Zend_Translate_Adapter_Ini extends Zend_Translate_Adapter
             throw new Zend_Translate_Exception("Ini file '".$data."' not found");
             throw new Zend_Translate_Exception("Ini file '".$data."' not found");
         }
         }
 
 
-        $inidata                   = parse_ini_file($data, false);
+        $inidata = parse_ini_file($data, false);
         if (!isset($this->_data[$locale])) {
         if (!isset($this->_data[$locale])) {
             $this->_data[$locale] = array();
             $this->_data[$locale] = array();
         }
         }

+ 177 - 0
library/Zend/Translate/Plural.php

@@ -0,0 +1,177 @@
+<?php
+/**
+ * Zend Framework
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://framework.zend.com/license/new-bsd
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to license@zend.com so we can send you a copy immediately.
+ *
+ * @category   Zend
+ * @package    Zend_Locale
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ * @version    $Id: Math.php 14041 2009-02-10 21:49:38Z thomas $
+ */
+
+/**
+ * Utility class for returning the plural rules according to the given locale
+ *
+ * @category   Zend
+ * @package    Zend_Locale
+ * @copyright  Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license    http://framework.zend.com/license/new-bsd     New BSD License
+ */
+class Zend_Translate_Plural
+{
+    /**
+     * Returns the plural definition to use
+     */
+    public static function getPlural($number, $locale)
+    {
+        if ($locale == "pt_BR") {
+            // temporary set a locale for brasilian
+            $locale = "xbr";
+        }
+
+        if (strlen($locale) > 3) {
+            $locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
+        }
+
+        switch($locale) {
+            case 'bo':
+            case 'dz':
+            case 'id':
+            case 'ja':
+            case 'jv':
+            case 'ka':
+            case 'km':
+            case 'kn':
+            case 'ko':
+            case 'ms':
+            case 'th':
+            case 'tr':
+            case 'vi':
+                return 0;
+                break;
+
+            case 'af':
+            case 'az':
+            case 'bn':
+            case 'bg':
+            case 'ca':
+            case 'da':
+            case 'de':
+            case 'el':
+            case 'en':
+            case 'eo':
+            case 'es':
+            case 'et':
+            case 'eu':
+            case 'fa':
+            case 'fi':
+            case 'fo':
+            case 'fur':
+            case 'fy':
+            case 'gl':
+            case 'gu':
+            case 'ha':
+            case 'he':
+            case 'hu':
+            case 'is':
+            case 'it':
+            case 'ku':
+            case 'lb':
+            case 'ml':
+            case 'mn':
+            case 'mr':
+            case 'nah':
+            case 'nb':
+            case 'ne':
+            case 'nl':
+            case 'nn':
+            case 'no':
+            case 'om':
+            case 'or':
+            case 'pa':
+            case 'pap':
+            case 'ps':
+            case 'pt':
+            case 'so':
+            case 'sq':
+            case 'sv':
+            case 'sw':
+            case 'ta':
+            case 'te':
+            case 'tk':
+            case 'ur':
+            case 'zh':
+            case 'zu':
+                return ($number == 1) ? 0 : 1;
+
+            case 'am':
+            case 'bh':
+            case 'fil':
+            case 'fr':
+            case 'gun':
+            case 'hi':
+            case 'ln':
+            case 'mg':
+            case 'nso':
+            case 'xbr':
+            case 'ti':
+            case 'wa':
+                return (($number == 0) || ($number == 1)) ? 0 : 1;
+
+            case 'be':
+            case 'bs':
+            case 'hr':
+            case 'ru':
+            case 'sr':
+            case 'uk':
+                return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : (($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2;
+
+            case 'cs':
+            case 'sk':
+                return ($number == 1) ? 0 : (($number >= 2) && ($number <= 4)) ? 1 : 2;
+
+            case 'ga':
+                return ($number == 1) ? 0 : ($number == 2) ? 1 : 2;
+
+            case 'lt':
+                return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : (($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2;
+
+            case 'sl':
+                return ($number % 100 == 1) ? 0 : ($number % 100 == 2) ? 1 : (($number % 100 == 3) || ($number % 100 == 4)) ? 2 : 3;
+
+            case 'mk':
+                return ($number % 10 == 1) ? 0 : 1;
+
+            case 'mt':
+                return ($number == 1) ? 0 : (($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : (($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3;
+
+            case 'lv':
+                return ($number == 0) ? 0 : (($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2;
+
+            case 'pl':
+                return ($number == 1) ? 0 : (($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 > 29))) ? 1 : 2;
+
+            case 'cy':
+                return ($number == 1) ? 0 : ($number == 2) ? 1 : (($number == 8) || ($number == 11)) ? 2 : 3;
+
+            case 'ro':
+                return ($number == 1) ? 0 : (($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2;
+
+            case 'ar':
+                return ($number == 0) ? 0 : ($number == 1) ? 1 : ($number == 2) ? 2 : (($number >= 3) && ($number <= 10)) ? 3 : (($number >= 11) && ($number <= 99)) ? 4 : 5;
+
+            default:
+                return 0;
+        }
+    }
+}

+ 176 - 0
tests/Zend/TranslateTest.php

@@ -384,6 +384,182 @@ class Zend_TranslateTest extends PHPUnit_Framework_TestCase
     }
     }
 
 
     /**
     /**
+     * Tests if cached options are read from the cache for a new instance
+     */
+    public function testGetOptionsFromCache()
+    {
+        require_once 'Zend/Cache.php';
+        $cache = Zend_Cache::factory('Core', 'File',
+            array('lifetime' => 120, 'automatic_serialization' => true),
+            array('cache_dir' => dirname(__FILE__) . '/_files/'));
+        Zend_Translate::setCache($cache);
+
+        $lang = new Zend_Translate(Zend_Translate::AN_CSV, dirname(__FILE__) . '/Translate/Adapter/_files', 'en', array('delimiter' => ','));
+        $lang->setOptions(array('logMessage' => 'test'));
+        $this->assertEquals('test', $lang->getOptions('logMessage'));
+        unset($lang);
+
+        $lang2 = new Zend_Translate(Zend_Translate::AN_CSV, dirname(__FILE__) . '/Translate/Adapter/_files', 'en', array('delimiter' => ','));
+        $this->assertEquals('test', $lang2->getOptions('logMessage'));
+    }
+
+    /**
+     * Tests if setting locale as options sets locale
+     */
+    public function testSetLocaleAsOption()
+    {
+        $lang = new Zend_Translate(Zend_Translate::AN_ARRAY, array('msg1' => 'Message 1'), 'en');
+        $lang->addTranslation(array('msg1' => 'Message 1 (ru)'), 'ru');
+        $lang->setOptions(array('locale' => 'ru'));
+        $this->assertEquals('ru', $lang->getLocale());
+        $lang->setOptions(array('locale' => 'en'));
+        $this->assertEquals('en', $lang->getLocale());
+    }
+
+    /**
+     * Tests getting null returns all options
+     */
+    public function testGettingAllOptions()
+    {
+        $lang = new Zend_Translate(Zend_Translate::AN_ARRAY, array('msg1' => 'Message 1'), 'en');
+        $this->assertTrue(is_array($lang->getOptions()));
+    }
+
+    /**
+     * Tests if setting locale as options sets locale
+     */
+    public function testGettingUnknownOption()
+    {
+        $lang = new Zend_Translate(Zend_Translate::AN_ARRAY, array('msg1' => 'Message 1'), 'en');
+        $this->assertEquals(null, $lang->getOptions('unknown'));
+    }
+
+    /**
+     * Tests getting of all message ids works
+     */
+    public function testGettingAllMessageIds()
+    {
+        $lang = new Zend_Translate(Zend_Translate::AN_ARRAY, array('msg1' => 'Message 1', 'msg2' => 'Message 2'), 'en');
+        $lang->addTranslation(array('msg1' => 'Message 1 (ru)'), 'ru');
+        $this->assertEquals(array('msg1'), $lang->getMessageIds());
+        $this->assertEquals(array('msg1', 'msg2'), $lang->getMessageIds('en'));
+    }
+
+    /**
+     * Tests getting of all messages
+     */
+    public function testGettingAllMessages()
+    {
+        $lang = new Zend_Translate(Zend_Translate::AN_ARRAY, array('msg1' => 'Message 1', 'msg2' => 'Message 2'), 'en');
+        $lang->addTranslation(array('msg1' => 'Message 1 (ru)'), 'ru');
+        $this->assertEquals(array('msg1' => 'Message 1 (ru)'), $lang->getMessages());
+        $this->assertEquals(
+            array('msg1' => 'Message 1', 'msg2' => 'Message 2'),
+            $lang->getMessages('en'));
+        $this->assertEquals(
+            array(
+                'en' => array('msg1' => 'Message 1', 'msg2' => 'Message 2'),
+                'ru' => array('msg1' => 'Message 1 (ru)')),
+            $lang->getMessages('all'));
+    }
+
+    /**
+     * Tests getting default plurals
+     */
+    public function testGettingPlurals()
+    {
+        $lang = new Zend_Translate(
+            Zend_Translate::AN_ARRAY,
+            array('singular' =>
+                array('plural_0 (en)',
+                    'plural_1 (en)',
+                    'plural_2 (en)',
+                    'plural_3 (en)'),
+                'plural' => ''), 'en'
+        );
+
+        $this->assertEquals('plural_0 (en)', $lang->translate(array('singular', 'plural', 1)));
+        $this->assertEquals('plural_1 (en)', $lang->translate(array('singular', 'plural', 2)));
+
+        $this->assertEquals('plural_0 (en)', $lang->plural('singular', 'plural', 1));
+        $this->assertEquals('plural_1 (en)', $lang->plural('singular', 'plural', 2));
+    }
+
+    /**
+     * Tests getting plurals from lowered locale
+     */
+    public function testGettingPluralsFromLoweredLocale()
+    {
+        $lang = new Zend_Translate(
+            Zend_Translate::AN_ARRAY,
+            array('singular' =>
+                array('plural_0 (en)',
+                    'plural_1 (en)',
+                    'plural_2 (en)',
+                    'plural_3 (en)'),
+                'plural' => ''), 'en'
+        );
+        $lang->addTranslation(array('msg1' => 'Message 1 (ru)'), 'en_US');
+        $lang->setLocale('en_US');
+
+        $this->assertEquals('plural_0 (en)', $lang->translate(array('singular', 'plural', 1)));
+        $this->assertEquals('plural_0 (en)', $lang->plural('singular', 'plural', 1));
+    }
+
+    /**
+     * Tests getting plurals from lowered locale
+     */
+    public function testGettingPluralsFromUnknownLocale()
+    {
+        $lang = new Zend_Translate(
+            Zend_Translate::AN_ARRAY,
+            array('singular' =>
+                array('plural_0 (en)',
+                    'plural_1 (en)',
+                    'plural_2 (en)',
+                    'plural_3 (en)'),
+                'plural' => ''), 'en'
+        );
+
+        $this->assertEquals('singular', $lang->translate(array('singular', 'plural', 1), 'ru'));
+        $this->assertEquals('singular', $lang->plural('singular', 'plural', 1, 'ru'));
+        $this->assertEquals('plural', $lang->translate(array('singular', 'plural', 'plural2', 2, 'en'), 'ru'));
+        $this->assertEquals('plural', $lang->plural('singular', 'plural', 2, 'ru'));
+    }
+
+    public function testPluralsWithGettext()
+    {
+        $lang = new Zend_Translate(Zend_Translate::AN_GETTEXT , dirname(__FILE__) . '/Translate/Adapter/_files/translation_en.mo', 'en');
+
+        $this->assertEquals('Message 5 (en) Plural 0', $lang->translate(array('Message 5', 'Message 5 Plural', 1)));
+        $this->assertEquals('Message 5 (en) Plural 0', $lang->plural('Message 5', 'Message 5 Plural', 1));
+        $this->assertEquals('Message 5 (en) Plural 1', $lang->translate(array('Message 5', 'Message 5 Plural', 2)));
+        $this->assertEquals('Message 5 (en) Plural 1', $lang->plural('Message 5', 'Message 5 Plural', 2));
+    }
+
+    public function testPluralsWithCsv()
+    {
+        $lang = new Zend_Translate(Zend_Translate::AN_CSV , dirname(__FILE__) . '/Translate/Adapter/_files/translation_en.csv', 'en');
+
+        $this->assertEquals('Message 6 (en) Plural 0', $lang->translate(array('Message 6', 'Message 6 Plural1', 1)));
+        $this->assertEquals('Message 6 (en) Plural 0', $lang->plural('Message 6', 'Message 6 Plural1', 1));
+        $this->assertEquals('Message 6 (en) Plural 1', $lang->translate(array('Message 6', 'Message 6 Plural1', 2)));
+        $this->assertEquals('Message 6 (en) Plural 1', $lang->plural('Message 6', 'Message 6 Plural1', 2));
+    }
+
+    /**
+     * ZF-6671
+     */
+    public function testAddTranslationAfterwards()
+    {
+        $lang = new Zend_Translate(Zend_Translate::AN_ARRAY, array('msg1' => 'Message 1'), 'en');
+        $this->assertEquals('Message 1', $lang->_('msg1'));
+
+        $lang->addTranslation(array('msg1' => 'Message 1 (en)'), 'en');
+        $this->assertEquals('Message 1 (en)', $lang->_('msg1'));
+    }
+
+    /**
      * Ignores a raised PHP error when in effect, but throws a flag to indicate an error occurred
      * Ignores a raised PHP error when in effect, but throws a flag to indicate an error occurred
      *
      *
      * @param  integer $errno
      * @param  integer $errno