Move into nested docroot
This commit is contained in:
parent
83a0d3a149
commit
c8b70abde9
13405 changed files with 0 additions and 0 deletions
339
web/core/lib/Drupal/Component/Gettext/LICENSE.txt
Normal file
339
web/core/lib/Drupal/Component/Gettext/LICENSE.txt
Normal file
|
@ -0,0 +1,339 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
563
web/core/lib/Drupal/Component/Gettext/PoHeader.php
Normal file
563
web/core/lib/Drupal/Component/Gettext/PoHeader.php
Normal file
|
@ -0,0 +1,563 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
/**
|
||||
* Gettext PO header handler.
|
||||
*
|
||||
* Possible Gettext PO header elements are explained in
|
||||
* http://www.gnu.org/software/gettext/manual/gettext.html#Header-Entry,
|
||||
* but we only support a subset of these directly.
|
||||
*
|
||||
* Example header:
|
||||
*
|
||||
* "Project-Id-Version: Drupal core (7.11)\n"
|
||||
* "POT-Creation-Date: 2012-02-12 22:59+0000\n"
|
||||
* "PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
|
||||
* "Language-Team: Catalan\n"
|
||||
* "MIME-Version: 1.0\n"
|
||||
* "Content-Type: text/plain; charset=utf-8\n"
|
||||
* "Content-Transfer-Encoding: 8bit\n"
|
||||
* "Plural-Forms: nplurals=2; plural=(n>1);\n"
|
||||
*/
|
||||
class PoHeader {
|
||||
|
||||
/**
|
||||
* Language code.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_langcode;
|
||||
|
||||
/**
|
||||
* Formula for the plural form.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_pluralForms;
|
||||
|
||||
/**
|
||||
* Author(s) of the file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_authors;
|
||||
|
||||
/**
|
||||
* Date the po file got created.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_po_date;
|
||||
|
||||
/**
|
||||
* Human readable language name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_languageName;
|
||||
|
||||
/**
|
||||
* Name of the project the translation belongs to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_projectName;
|
||||
|
||||
/**
|
||||
* Constructor, creates a PoHeader with default values.
|
||||
*
|
||||
* @param string $langcode
|
||||
* Language code.
|
||||
*/
|
||||
public function __construct($langcode = NULL) {
|
||||
$this->_langcode = $langcode;
|
||||
// Ignore errors when run during site installation before
|
||||
// date_default_timezone_set() is called.
|
||||
$this->_po_date = @date("Y-m-d H:iO");
|
||||
$this->_pluralForms = 'nplurals=2; plural=(n > 1);';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plural form.
|
||||
*
|
||||
* @return string
|
||||
* Plural form component from the header, for example:
|
||||
* 'nplurals=2; plural=(n > 1);'.
|
||||
*/
|
||||
function getPluralForms() {
|
||||
return $this->_pluralForms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the human readable language name.
|
||||
*
|
||||
* @param string $languageName
|
||||
* Human readable language name.
|
||||
*/
|
||||
function setLanguageName($languageName) {
|
||||
$this->_languageName = $languageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the human readable language name.
|
||||
*
|
||||
* @return string
|
||||
* The human readable language name.
|
||||
*/
|
||||
function getLanguageName() {
|
||||
return $this->_languageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the project name.
|
||||
*
|
||||
* @param string $projectName
|
||||
* Human readable project name.
|
||||
*/
|
||||
function setProjectName($projectName) {
|
||||
$this->_projectName = $projectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the project name.
|
||||
*
|
||||
* @return string
|
||||
* The human readable project name.
|
||||
*/
|
||||
function getProjectName() {
|
||||
return $this->_projectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate internal values from a string.
|
||||
*
|
||||
* @param string $header
|
||||
* Full header string with key-value pairs.
|
||||
*/
|
||||
public function setFromString($header) {
|
||||
// Get an array of all header values for processing.
|
||||
$values = $this->parseHeader($header);
|
||||
|
||||
// There is only one value relevant for our header implementation when
|
||||
// reading, and that is the plural formula.
|
||||
if (!empty($values['Plural-Forms'])) {
|
||||
$this->_pluralForms = $values['Plural-Forms'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a Gettext PO formatted header string based on data set earlier.
|
||||
*/
|
||||
public function __toString() {
|
||||
$output = '';
|
||||
|
||||
$isTemplate = empty($this->_languageName);
|
||||
|
||||
$output .= '# ' . ($isTemplate ? 'LANGUAGE' : $this->_languageName) . ' translation of ' . ($isTemplate ? 'PROJECT' : $this->_projectName) . "\n";
|
||||
if (!empty($this->_authors)) {
|
||||
$output .= '# Generated by ' . implode("\n# ", $this->_authors) . "\n";
|
||||
}
|
||||
$output .= "#\n";
|
||||
|
||||
// Add the actual header information.
|
||||
$output .= "msgid \"\"\n";
|
||||
$output .= "msgstr \"\"\n";
|
||||
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
|
||||
$output .= "\"POT-Creation-Date: " . $this->_po_date . "\\n\"\n";
|
||||
$output .= "\"PO-Revision-Date: " . $this->_po_date . "\\n\"\n";
|
||||
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
|
||||
$output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
|
||||
$output .= "\"MIME-Version: 1.0\\n\"\n";
|
||||
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
|
||||
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
|
||||
$output .= "\"Plural-Forms: " . $this->_pluralForms . "\\n\"\n";
|
||||
$output .= "\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Plural-Forms entry from a Gettext Portable Object file header.
|
||||
*
|
||||
* @param string $pluralforms
|
||||
* The Plural-Forms entry value.
|
||||
*
|
||||
* @return
|
||||
* An indexed array of parsed plural formula data. Containing:
|
||||
* - 'nplurals': The number of plural forms defined by the plural formula.
|
||||
* - 'plurals': Array of plural positions keyed by plural value.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
function parsePluralForms($pluralforms) {
|
||||
$plurals = array();
|
||||
// First, delete all whitespace.
|
||||
$pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
|
||||
|
||||
// Select the parts that define nplurals and plural.
|
||||
$nplurals = strstr($pluralforms, "nplurals=");
|
||||
if (strpos($nplurals, ";")) {
|
||||
// We want the string from the 10th char, because "nplurals=" length is 9.
|
||||
$nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
$plural = strstr($pluralforms, "plural=");
|
||||
if (strpos($plural, ";")) {
|
||||
// We want the string from the 8th char, because "plural=" length is 7.
|
||||
$plural = substr($plural, 7, strpos($plural, ";") - 7);
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// If the number of plurals is zero, we return a default result.
|
||||
if ($nplurals == 0) {
|
||||
return array($nplurals, array('default' => 0));
|
||||
}
|
||||
|
||||
// Calculate possible plural positions of different plural values. All known
|
||||
// plural formula's are repetitive above 100.
|
||||
// For data compression we store the last position the array value
|
||||
// changes and store it as default.
|
||||
$element_stack = $this->parseArithmetic($plural);
|
||||
if ($element_stack !== FALSE) {
|
||||
for ($i = 0; $i <= 199; $i++) {
|
||||
$plurals[$i] = $this->evaluatePlural($element_stack, $i);
|
||||
}
|
||||
$default = $plurals[$i - 1];
|
||||
$plurals = array_filter($plurals, function ($value) use ($default) {
|
||||
return ($value != $default);
|
||||
});
|
||||
$plurals['default'] = $default;
|
||||
|
||||
return array($nplurals, $plurals);
|
||||
}
|
||||
else {
|
||||
throw new \Exception('The plural formula could not be parsed.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a Gettext Portable Object file header.
|
||||
*
|
||||
* @param string $header
|
||||
* A string containing the complete header.
|
||||
*
|
||||
* @return array
|
||||
* An associative array of key-value pairs.
|
||||
*/
|
||||
private function parseHeader($header) {
|
||||
$header_parsed = array();
|
||||
$lines = array_map('trim', explode("\n", $header));
|
||||
foreach ($lines as $line) {
|
||||
if ($line) {
|
||||
list($tag, $contents) = explode(":", $line, 2);
|
||||
$header_parsed[trim($tag)] = trim($contents);
|
||||
}
|
||||
}
|
||||
return $header_parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and sanitizes an arithmetic formula into a plural element stack.
|
||||
*
|
||||
* While parsing, we ensure, that the operators have the right
|
||||
* precedence and associativity.
|
||||
*
|
||||
* @param string $string
|
||||
* A string containing the arithmetic formula.
|
||||
*
|
||||
* @return
|
||||
* A stack of values and operations to be evaluated.
|
||||
*/
|
||||
private function parseArithmetic($string) {
|
||||
// Operator precedence table.
|
||||
$precedence = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8);
|
||||
// Right associativity.
|
||||
$right_associativity = array("?" => 1, ":" => 1);
|
||||
|
||||
$tokens = $this->tokenizeFormula($string);
|
||||
|
||||
// Parse by converting into infix notation then back into postfix
|
||||
// Operator stack - holds math operators and symbols.
|
||||
$operator_stack = array();
|
||||
// Element Stack - holds data to be operated on.
|
||||
$element_stack = array();
|
||||
|
||||
foreach ($tokens as $token) {
|
||||
$current_token = $token;
|
||||
|
||||
// Numbers and the $n variable are simply pushed into $element_stack.
|
||||
if (is_numeric($token)) {
|
||||
$element_stack[] = $current_token;
|
||||
}
|
||||
elseif ($current_token == "n") {
|
||||
$element_stack[] = '$n';
|
||||
}
|
||||
elseif ($current_token == "(") {
|
||||
$operator_stack[] = $current_token;
|
||||
}
|
||||
elseif ($current_token == ")") {
|
||||
$topop = array_pop($operator_stack);
|
||||
while (isset($topop) && ($topop != "(")) {
|
||||
$element_stack[] = $topop;
|
||||
$topop = array_pop($operator_stack);
|
||||
}
|
||||
}
|
||||
elseif (!empty($precedence[$current_token])) {
|
||||
// If it's an operator, then pop from $operator_stack into
|
||||
// $element_stack until the precedence in $operator_stack is less
|
||||
// than current, then push into $operator_stack.
|
||||
$topop = array_pop($operator_stack);
|
||||
while (isset($topop) && ($precedence[$topop] >= $precedence[$current_token]) && !(($precedence[$topop] == $precedence[$current_token]) && !empty($right_associativity[$topop]) && !empty($right_associativity[$current_token]))) {
|
||||
$element_stack[] = $topop;
|
||||
$topop = array_pop($operator_stack);
|
||||
}
|
||||
if ($topop) {
|
||||
// Return element to top.
|
||||
$operator_stack[] = $topop;
|
||||
}
|
||||
// Parentheses are not needed.
|
||||
$operator_stack[] = $current_token;
|
||||
}
|
||||
else {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// Flush operator stack.
|
||||
$topop = array_pop($operator_stack);
|
||||
while ($topop != NULL) {
|
||||
$element_stack[] = $topop;
|
||||
$topop = array_pop($operator_stack);
|
||||
}
|
||||
$return = $element_stack;
|
||||
|
||||
// Now validate stack.
|
||||
$previous_size = count($element_stack) + 1;
|
||||
while (count($element_stack) < $previous_size) {
|
||||
$previous_size = count($element_stack);
|
||||
for ($i = 2; $i < count($element_stack); $i++) {
|
||||
$op = $element_stack[$i];
|
||||
if (!empty($precedence[$op])) {
|
||||
if ($op == ":") {
|
||||
$f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")";
|
||||
}
|
||||
elseif ($op == "?") {
|
||||
$f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1];
|
||||
}
|
||||
else {
|
||||
$f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")";
|
||||
}
|
||||
array_splice($element_stack, $i - 2, 3, $f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If only one element is left, the number of operators is appropriate.
|
||||
return count($element_stack) == 1 ? $return : FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize the formula.
|
||||
*
|
||||
* @param string $formula
|
||||
* A string containing the arithmetic formula.
|
||||
*
|
||||
* @return array
|
||||
* List of arithmetic tokens identified in the formula.
|
||||
*/
|
||||
private function tokenizeFormula($formula) {
|
||||
$formula = str_replace(" ", "", $formula);
|
||||
$tokens = array();
|
||||
for ($i = 0; $i < strlen($formula); $i++) {
|
||||
if (is_numeric($formula[$i])) {
|
||||
$num = $formula[$i];
|
||||
$j = $i + 1;
|
||||
while ($j < strlen($formula) && is_numeric($formula[$j])) {
|
||||
$num .= $formula[$j];
|
||||
$j++;
|
||||
}
|
||||
$i = $j - 1;
|
||||
$tokens[] = $num;
|
||||
}
|
||||
elseif ($pos = strpos(" =<>!&|", $formula[$i])) {
|
||||
$next = $formula[$i + 1];
|
||||
switch ($pos) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
if ($next == '=') {
|
||||
$tokens[] = $formula[$i] . '=';
|
||||
$i++;
|
||||
}
|
||||
else {
|
||||
$tokens[] = $formula[$i];
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
if ($next == '&') {
|
||||
$tokens[] = '&&';
|
||||
$i++;
|
||||
}
|
||||
else {
|
||||
$tokens[] = $formula[$i];
|
||||
}
|
||||
break;
|
||||
case 6:
|
||||
if ($next == '|') {
|
||||
$tokens[] = '||';
|
||||
$i++;
|
||||
}
|
||||
else {
|
||||
$tokens[] = $formula[$i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$tokens[] = $formula[$i];
|
||||
}
|
||||
}
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the plural element stack using a plural value.
|
||||
*
|
||||
* Using an element stack, which represents a plural formula, we calculate
|
||||
* which plural string should be used for a given plural value.
|
||||
*
|
||||
* An example of plural formula parting and evaluation:
|
||||
* Plural formula: 'n!=1'
|
||||
* This formula is parsed by parseArithmetic() to a stack (array) of elements:
|
||||
* array(
|
||||
* 0 => '$n',
|
||||
* 1 => '1',
|
||||
* 2 => '!=',
|
||||
* );
|
||||
* The evaluatePlural() method evaluates the $element_stack using the plural
|
||||
* value $n. Before the actual evaluation, the '$n' in the array is replaced
|
||||
* by the value of $n.
|
||||
* For example: $n = 2 results in:
|
||||
* array(
|
||||
* 0 => '2',
|
||||
* 1 => '1',
|
||||
* 2 => '!=',
|
||||
* );
|
||||
* The stack is processed until only one element is (the result) is left. In
|
||||
* every iteration the top elements of the stack, up until the first operator,
|
||||
* are evaluated. After evaluation the arguments and the operator itself are
|
||||
* removed and replaced by the evaluation result. This is typically 2
|
||||
* arguments and 1 element for the operator.
|
||||
* Because the operator is '!=' the example stack is evaluated as:
|
||||
* $f = (int) 2 != 1;
|
||||
* The resulting stack is:
|
||||
* array(
|
||||
* 0 => 1,
|
||||
* );
|
||||
* With only one element left in the stack (the final result) the loop is
|
||||
* terminated and the result is returned.
|
||||
*
|
||||
* @param array $element_stack
|
||||
* Array of plural formula values and operators create by parseArithmetic().
|
||||
* @param int $n
|
||||
* The @count number for which we are determining the right plural position.
|
||||
*
|
||||
* @return int
|
||||
* Number of the plural string to be used for the given plural value.
|
||||
*
|
||||
* @see parseArithmetic()
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function evaluatePlural($element_stack, $n) {
|
||||
$count = count($element_stack);
|
||||
$limit = $count;
|
||||
// Replace the '$n' value in the formula by the plural value.
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
if ($element_stack[$i] === '$n') {
|
||||
$element_stack[$i] = $n;
|
||||
}
|
||||
}
|
||||
|
||||
// We process the stack until only one element is (the result) is left.
|
||||
// We limit the number of evaluation cycles to prevent an endless loop in
|
||||
// case the stack contains an error.
|
||||
while (isset($element_stack[1])) {
|
||||
for ($i = 2; $i < $count; $i++) {
|
||||
// There's no point in checking non-symbols. Also, switch(TRUE) would
|
||||
// match any case and so it would break.
|
||||
if (is_bool($element_stack[$i]) || is_numeric($element_stack[$i])) {
|
||||
continue;
|
||||
}
|
||||
$f = NULL;
|
||||
$length = 3;
|
||||
$delta = 2;
|
||||
switch ($element_stack[$i]) {
|
||||
case '==':
|
||||
$f = $element_stack[$i - 2] == $element_stack[$i - 1];
|
||||
break;
|
||||
case '!=':
|
||||
$f = $element_stack[$i - 2] != $element_stack[$i - 1];
|
||||
break;
|
||||
case '<=':
|
||||
$f = $element_stack[$i - 2] <= $element_stack[$i - 1];
|
||||
break;
|
||||
case '>=':
|
||||
$f = $element_stack[$i - 2] >= $element_stack[$i - 1];
|
||||
break;
|
||||
case '<':
|
||||
$f = $element_stack[$i - 2] < $element_stack[$i - 1];
|
||||
break;
|
||||
case '>':
|
||||
$f = $element_stack[$i - 2] > $element_stack[$i - 1];
|
||||
break;
|
||||
case '+':
|
||||
$f = $element_stack[$i - 2] + $element_stack[$i - 1];
|
||||
break;
|
||||
case '-':
|
||||
$f = $element_stack[$i - 2] - $element_stack[$i - 1];
|
||||
break;
|
||||
case '*':
|
||||
$f = $element_stack[$i - 2] * $element_stack[$i - 1];
|
||||
break;
|
||||
case '/':
|
||||
$f = $element_stack[$i - 2] / $element_stack[$i - 1];
|
||||
break;
|
||||
case '%':
|
||||
$f = $element_stack[$i - 2] % $element_stack[$i - 1];
|
||||
break;
|
||||
case '&&':
|
||||
$f = $element_stack[$i - 2] && $element_stack[$i - 1];
|
||||
break;
|
||||
case '||':
|
||||
$f = $element_stack[$i - 2] || $element_stack[$i - 1];
|
||||
break;
|
||||
case ':':
|
||||
$f = $element_stack[$i - 3] ? $element_stack[$i - 2] : $element_stack[$i - 1];
|
||||
// This operator has 3 preceding elements, instead of the default 2.
|
||||
$length = 5;
|
||||
$delta = 3;
|
||||
break;
|
||||
}
|
||||
|
||||
// If the element is an operator we remove the processed elements and
|
||||
// store the result.
|
||||
if (isset($f)) {
|
||||
array_splice($element_stack, $i - $delta, $length, $f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$limit) {
|
||||
throw new \Exception('The plural formula could not be evaluated.');
|
||||
}
|
||||
return (int) $element_stack[0];
|
||||
}
|
||||
|
||||
}
|
282
web/core/lib/Drupal/Component/Gettext/PoItem.php
Normal file
282
web/core/lib/Drupal/Component/Gettext/PoItem.php
Normal file
|
@ -0,0 +1,282 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
/**
|
||||
* PoItem handles one translation.
|
||||
*
|
||||
* @todo: This class contains some really old legacy code.
|
||||
* @see https://www.drupal.org/node/1637662
|
||||
*/
|
||||
class PoItem {
|
||||
|
||||
/**
|
||||
* The language code this translation is in.
|
||||
*
|
||||
* @car string
|
||||
*/
|
||||
private $_langcode;
|
||||
|
||||
/**
|
||||
* The context this translation belongs to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_context = '';
|
||||
|
||||
/**
|
||||
* The source string or array of strings if it has plurals.
|
||||
*
|
||||
* @var string or array
|
||||
* @see $_plural
|
||||
*/
|
||||
private $_source;
|
||||
|
||||
/**
|
||||
* Flag indicating if this translation has plurals.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_plural;
|
||||
|
||||
/**
|
||||
* The comment of this translation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_comment;
|
||||
|
||||
/**
|
||||
* The translation string or array of strings if it has plurals.
|
||||
*
|
||||
* @var string or array
|
||||
* @see $_plural
|
||||
*/
|
||||
private $_translation;
|
||||
|
||||
/**
|
||||
* Gets the language code of the currently used language.
|
||||
*
|
||||
* @return string with langcode
|
||||
*/
|
||||
function getLangcode() {
|
||||
return $this->_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the language code of the current language.
|
||||
*
|
||||
* @param string $langcode
|
||||
*/
|
||||
function setLangcode($langcode) {
|
||||
$this->_langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the context this translation belongs to.
|
||||
*
|
||||
* @return string $context
|
||||
*/
|
||||
function getContext() {
|
||||
return $this->_context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the context this translation belongs to.
|
||||
*
|
||||
* @param string $context
|
||||
*/
|
||||
function setContext($context) {
|
||||
$this->_context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source string or the array of strings if the translation has
|
||||
* plurals.
|
||||
*
|
||||
* @return string or array $translation
|
||||
*/
|
||||
function getSource() {
|
||||
return $this->_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the source string or the array of strings if the translation has
|
||||
* plurals.
|
||||
*
|
||||
* @param string or array $source
|
||||
*/
|
||||
function setSource($source) {
|
||||
$this->_source = $source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the translation string or the array of strings if the translation has
|
||||
* plurals.
|
||||
*
|
||||
* @return string or array $translation
|
||||
*/
|
||||
function getTranslation() {
|
||||
return $this->_translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the translation string or the array of strings if the translation has
|
||||
* plurals.
|
||||
*
|
||||
* @param string or array $translation
|
||||
*/
|
||||
function setTranslation($translation) {
|
||||
$this->_translation = $translation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set if the translation has plural values.
|
||||
*
|
||||
* @param bool $plural
|
||||
*/
|
||||
function setPlural($plural) {
|
||||
$this->_plural = $plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the translation has plural values.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function isPlural() {
|
||||
return $this->_plural;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the comment of this translation.
|
||||
*
|
||||
* @return String $comment
|
||||
*/
|
||||
function getComment() {
|
||||
return $this->_comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the comment of this translation.
|
||||
*
|
||||
* @param string $comment
|
||||
*/
|
||||
function setComment($comment) {
|
||||
$this->_comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the PoItem from a structured array.
|
||||
*
|
||||
* @param array $values
|
||||
*/
|
||||
public function setFromArray(array $values = array()) {
|
||||
if (isset($values['context'])) {
|
||||
$this->setContext($values['context']);
|
||||
}
|
||||
if (isset($values['source'])) {
|
||||
$this->setSource($values['source']);
|
||||
}
|
||||
if (isset($values['translation'])) {
|
||||
$this->setTranslation($values['translation']);
|
||||
}
|
||||
if (isset($values['comment'])) {
|
||||
$this->setComment($values['comment']);
|
||||
}
|
||||
if (isset($this->_source) &&
|
||||
strpos($this->_source, LOCALE_PLURAL_DELIMITER) !== FALSE) {
|
||||
$this->setSource(explode(LOCALE_PLURAL_DELIMITER, $this->_source));
|
||||
$this->setTranslation(explode(LOCALE_PLURAL_DELIMITER, $this->_translation));
|
||||
$this->setPlural(count($this->_source) > 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the PoItem as a string.
|
||||
*/
|
||||
public function __toString() {
|
||||
return $this->formatItem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the POItem as a string.
|
||||
*/
|
||||
private function formatItem() {
|
||||
$output = '';
|
||||
|
||||
// Format string context.
|
||||
if (!empty($this->_context)) {
|
||||
$output .= 'msgctxt ' . $this->formatString($this->_context);
|
||||
}
|
||||
|
||||
// Format translation.
|
||||
if ($this->_plural) {
|
||||
$output .= $this->formatPlural();
|
||||
}
|
||||
else {
|
||||
$output .= $this->formatSingular();
|
||||
}
|
||||
|
||||
// Add one empty line to separate the translations.
|
||||
$output .= "\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a plural translation.
|
||||
*/
|
||||
private function formatPlural() {
|
||||
$output = '';
|
||||
|
||||
// Format source strings.
|
||||
$output .= 'msgid ' . $this->formatString($this->_source[0]);
|
||||
$output .= 'msgid_plural ' . $this->formatString($this->_source[1]);
|
||||
|
||||
foreach ($this->_translation as $i => $trans) {
|
||||
if (isset($this->_translation[$i])) {
|
||||
$output .= 'msgstr[' . $i . '] ' . $this->formatString($trans);
|
||||
}
|
||||
else {
|
||||
$output .= 'msgstr[' . $i . '] ""' . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a singular translation.
|
||||
*/
|
||||
private function formatSingular() {
|
||||
$output = '';
|
||||
$output .= 'msgid ' . $this->formatString($this->_source);
|
||||
$output .= 'msgstr ' . (isset($this->_translation) ? $this->formatString($this->_translation) : '""');
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a string for output on multiple lines.
|
||||
*/
|
||||
private function formatString($string) {
|
||||
// Escape characters for processing.
|
||||
$string = addcslashes($string, "\0..\37\\\"");
|
||||
|
||||
// Always include a line break after the explicit \n line breaks from
|
||||
// the source string. Otherwise wrap at 70 chars to accommodate the extra
|
||||
// format overhead too.
|
||||
$parts = explode("\n", wordwrap(str_replace('\n', "\\n\n", $string), 70, " \n"));
|
||||
|
||||
// Multiline string should be exported starting with a "" and newline to
|
||||
// have all lines aligned on the same column.
|
||||
if (count($parts) > 1) {
|
||||
return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n";
|
||||
}
|
||||
// Single line strings are output on the same line.
|
||||
else {
|
||||
return "\"$parts[0]\"\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
87
web/core/lib/Drupal/Component/Gettext/PoMemoryWriter.php
Normal file
87
web/core/lib/Drupal/Component/Gettext/PoMemoryWriter.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
/**
|
||||
* Defines a Gettext PO memory writer, to be used by the installer.
|
||||
*/
|
||||
class PoMemoryWriter implements PoWriterInterface {
|
||||
|
||||
/**
|
||||
* Array to hold all PoItem elements.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_items;
|
||||
|
||||
/**
|
||||
* Constructor, initialize empty items.
|
||||
*/
|
||||
function __construct() {
|
||||
$this->_items = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeItem(PoItem $item) {
|
||||
if (is_array($item->getSource())) {
|
||||
$item->setSource(implode(LOCALE_PLURAL_DELIMITER, $item->getSource()));
|
||||
$item->setTranslation(implode(LOCALE_PLURAL_DELIMITER, $item->getTranslation()));
|
||||
}
|
||||
$context = $item->getContext();
|
||||
$this->_items[$context != NULL ? $context : ''][$item->getSource()] = $item->getTranslation();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeItems(PoReaderInterface $reader, $count = -1) {
|
||||
$forever = $count == -1;
|
||||
while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
|
||||
$this->writeItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all stored PoItem's.
|
||||
*
|
||||
* @return array PoItem
|
||||
*/
|
||||
public function getData() {
|
||||
return $this->_items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface:setLangcode().
|
||||
*
|
||||
* Not implemented. Not relevant for the MemoryWriter.
|
||||
*/
|
||||
function setLangcode($langcode) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface:getLangcode().
|
||||
*
|
||||
* Not implemented. Not relevant for the MemoryWriter.
|
||||
*/
|
||||
function getLangcode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface:getHeader().
|
||||
*
|
||||
* Not implemented. Not relevant for the MemoryWriter.
|
||||
*/
|
||||
function getHeader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface:setHeader().
|
||||
*
|
||||
* Not implemented. Not relevant for the MemoryWriter.
|
||||
*/
|
||||
function setHeader(PoHeader $header) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
/**
|
||||
* Methods required for both reader and writer implementations.
|
||||
*
|
||||
* @see \Drupal\Component\Gettext\PoReaderInterface
|
||||
* @see \Drupal\Component\Gettext\PoWriterInterface
|
||||
*/
|
||||
interface PoMetadataInterface {
|
||||
|
||||
/**
|
||||
* Set language code.
|
||||
*
|
||||
* @param string $langcode
|
||||
* Language code string.
|
||||
*/
|
||||
public function setLangcode($langcode);
|
||||
|
||||
/**
|
||||
* Get language code.
|
||||
*
|
||||
* @return string
|
||||
* Language code string.
|
||||
*/
|
||||
public function getLangcode();
|
||||
|
||||
/**
|
||||
* Set header metadata.
|
||||
*
|
||||
* @param \Drupal\Component\Gettext\PoHeader $header
|
||||
* Header object representing metadata in a PO header.
|
||||
*/
|
||||
public function setHeader(PoHeader $header);
|
||||
|
||||
/**
|
||||
* Get header metadata.
|
||||
*
|
||||
* @return \Drupal\Component\Gettext\PoHeader $header
|
||||
* Header instance representing metadata in a PO header.
|
||||
*/
|
||||
public function getHeader();
|
||||
|
||||
}
|
18
web/core/lib/Drupal/Component/Gettext/PoReaderInterface.php
Normal file
18
web/core/lib/Drupal/Component/Gettext/PoReaderInterface.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
/**
|
||||
* Shared interface definition for all Gettext PO Readers.
|
||||
*/
|
||||
interface PoReaderInterface extends PoMetadataInterface {
|
||||
|
||||
/**
|
||||
* Reads and returns a PoItem (source/translation pair).
|
||||
*
|
||||
* @return \Drupal\Component\Gettext\PoItem
|
||||
* Wrapper for item data instance.
|
||||
*/
|
||||
public function readItem();
|
||||
|
||||
}
|
39
web/core/lib/Drupal/Component/Gettext/PoStreamInterface.php
Normal file
39
web/core/lib/Drupal/Component/Gettext/PoStreamInterface.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
/**
|
||||
* Common functions for file/stream based PO readers/writers.
|
||||
*
|
||||
* @see PoReaderInterface
|
||||
* @see PoWriterInterface
|
||||
*/
|
||||
interface PoStreamInterface {
|
||||
|
||||
/**
|
||||
* Open the stream. Set the URI for the stream earlier with setURI().
|
||||
*/
|
||||
public function open();
|
||||
|
||||
/**
|
||||
* Close the stream.
|
||||
*/
|
||||
public function close();
|
||||
|
||||
/**
|
||||
* Gets the URI of the PO stream that is being read or written.
|
||||
*
|
||||
* @return
|
||||
* URI string for this stream.
|
||||
*/
|
||||
public function getURI();
|
||||
|
||||
/**
|
||||
* Set the URI of the PO stream that is going to be read or written.
|
||||
*
|
||||
* @param $uri
|
||||
* URI string to set for this stream.
|
||||
*/
|
||||
public function setURI($uri);
|
||||
|
||||
}
|
594
web/core/lib/Drupal/Component/Gettext/PoStreamReader.php
Normal file
594
web/core/lib/Drupal/Component/Gettext/PoStreamReader.php
Normal file
|
@ -0,0 +1,594 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
use Drupal\Component\Utility\SafeMarkup;
|
||||
|
||||
/**
|
||||
* Implements Gettext PO stream reader.
|
||||
*
|
||||
* The PO file format parsing is implemented according to the documentation at
|
||||
* http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
|
||||
*/
|
||||
class PoStreamReader implements PoStreamInterface, PoReaderInterface {
|
||||
|
||||
/**
|
||||
* Source line number of the stream being parsed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_line_number = 0;
|
||||
|
||||
/**
|
||||
* Parser context for the stream reader state machine.
|
||||
*
|
||||
* Possible contexts are:
|
||||
* - 'COMMENT' (#)
|
||||
* - 'MSGID' (msgid)
|
||||
* - 'MSGID_PLURAL' (msgid_plural)
|
||||
* - 'MSGCTXT' (msgctxt)
|
||||
* - 'MSGSTR' (msgstr or msgstr[])
|
||||
* - 'MSGSTR_ARR' (msgstr_arg)
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_context = 'COMMENT';
|
||||
|
||||
/**
|
||||
* Current entry being read. Incomplete.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_current_item = array();
|
||||
|
||||
/**
|
||||
* Current plural index for plural translations.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $_current_plural_index = 0;
|
||||
|
||||
/**
|
||||
* URI of the PO stream that is being read.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_uri = '';
|
||||
|
||||
/**
|
||||
* Language code for the PO stream being read.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_langcode = NULL;
|
||||
|
||||
/**
|
||||
* File handle of the current PO stream.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $_fd;
|
||||
|
||||
/**
|
||||
* The PO stream header.
|
||||
*
|
||||
* @var \Drupal\Component\Gettext\PoHeader
|
||||
*/
|
||||
private $_header;
|
||||
|
||||
/**
|
||||
* Object wrapper for the last read source/translation pair.
|
||||
*
|
||||
* @var \Drupal\Component\Gettext\PoItem
|
||||
*/
|
||||
private $_last_item;
|
||||
|
||||
/**
|
||||
* Indicator of whether the stream reading is finished.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $_finished;
|
||||
|
||||
/**
|
||||
* Array of translated error strings recorded on reading this stream so far.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $_errors;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLangcode() {
|
||||
return $this->_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLangcode($langcode) {
|
||||
$this->_langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHeader() {
|
||||
return $this->_header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoMetadataInterface::setHeader().
|
||||
*
|
||||
* Not applicable to stream reading and therefore not implemented.
|
||||
*/
|
||||
public function setHeader(PoHeader $header) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getURI() {
|
||||
return $this->_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setURI($uri) {
|
||||
$this->_uri = $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoStreamInterface::open().
|
||||
*
|
||||
* Opens the stream and reads the header. The stream is ready for reading
|
||||
* items after.
|
||||
*
|
||||
* @throws Exception
|
||||
* If the URI is not yet set.
|
||||
*/
|
||||
public function open() {
|
||||
if (!empty($this->_uri)) {
|
||||
$this->_fd = fopen($this->_uri, 'rb');
|
||||
$this->readHeader();
|
||||
}
|
||||
else {
|
||||
throw new \Exception('Cannot open stream without URI set.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoStreamInterface::close().
|
||||
*
|
||||
* @throws Exception
|
||||
* If the stream is not open.
|
||||
*/
|
||||
public function close() {
|
||||
if ($this->_fd) {
|
||||
fclose($this->_fd);
|
||||
}
|
||||
else {
|
||||
throw new \Exception('Cannot close stream that is not open.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function readItem() {
|
||||
// Clear out the last item.
|
||||
$this->_last_item = NULL;
|
||||
|
||||
// Read until finished with the stream or a complete item was identified.
|
||||
while (!$this->_finished && is_null($this->_last_item)) {
|
||||
$this->readLine();
|
||||
}
|
||||
|
||||
return $this->_last_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the seek position for the current PO stream.
|
||||
*
|
||||
* @param int $seek
|
||||
* The new seek position to set.
|
||||
*/
|
||||
public function setSeek($seek) {
|
||||
fseek($this->_fd, $seek);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pointer position of the current PO stream.
|
||||
*/
|
||||
public function getSeek() {
|
||||
return ftell($this->_fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the header from the PO stream.
|
||||
*
|
||||
* The header is a special case PoItem, using the empty string as source and
|
||||
* key-value pairs as translation. We just reuse the item reader logic to
|
||||
* read the header.
|
||||
*/
|
||||
private function readHeader() {
|
||||
$item = $this->readItem();
|
||||
// Handle the case properly when the .po file is empty (0 bytes).
|
||||
if (!$item) {
|
||||
return;
|
||||
}
|
||||
$header = new PoHeader();
|
||||
$header->setFromString(trim($item->getTranslation()));
|
||||
$this->_header = $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a line from the PO stream and stores data internally.
|
||||
*
|
||||
* Expands $this->_current_item based on new data for the current item. If
|
||||
* this line ends the current item, it is saved with setItemFromArray() with
|
||||
* data from $this->_current_item.
|
||||
*
|
||||
* An internal state machine is maintained in this reader using
|
||||
* $this->_context as the reading state. PO items are in between COMMENT
|
||||
* states (when items have at least one line or comment in between them) or
|
||||
* indicated by MSGSTR or MSGSTR_ARR followed immediately by an MSGID or
|
||||
* MSGCTXT (when items closely follow each other).
|
||||
*
|
||||
* @return
|
||||
* FALSE if an error was logged, NULL otherwise. The errors are considered
|
||||
* non-blocking, so reading can continue, while the errors are collected
|
||||
* for later presentation.
|
||||
*/
|
||||
private function readLine() {
|
||||
// Read a line and set the stream finished indicator if it was not
|
||||
// possible anymore.
|
||||
$line = fgets($this->_fd);
|
||||
$this->_finished = ($line === FALSE);
|
||||
|
||||
if (!$this->_finished) {
|
||||
|
||||
if ($this->_line_number == 0) {
|
||||
// The first line might come with a UTF-8 BOM, which should be removed.
|
||||
$line = str_replace("\xEF\xBB\xBF", '', $line);
|
||||
// Current plurality for 'msgstr[]'.
|
||||
$this->_current_plural_index = 0;
|
||||
}
|
||||
|
||||
// Track the line number for error reporting.
|
||||
$this->_line_number++;
|
||||
|
||||
// Initialize common values for error logging.
|
||||
$log_vars = array(
|
||||
'%uri' => $this->getURI(),
|
||||
'%line' => $this->_line_number,
|
||||
);
|
||||
|
||||
// Trim away the linefeed. \\n might appear at the end of the string if
|
||||
// another line continuing the same string follows. We can remove that.
|
||||
$line = trim(strtr($line, array("\\\n" => "")));
|
||||
|
||||
if (!strncmp('#', $line, 1)) {
|
||||
// Lines starting with '#' are comments.
|
||||
|
||||
if ($this->_context == 'COMMENT') {
|
||||
// Already in comment context, add to current comment.
|
||||
$this->_current_item['#'][] = substr($line, 1);
|
||||
}
|
||||
elseif (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
|
||||
// We are currently in string context, save current item.
|
||||
$this->setItemFromArray($this->_current_item);
|
||||
|
||||
// Start a new entry for the comment.
|
||||
$this->_current_item = array();
|
||||
$this->_current_item['#'][] = substr($line, 1);
|
||||
|
||||
$this->_context = 'COMMENT';
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// A comment following any other context is a syntax error.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp('msgid_plural', $line, 12)) {
|
||||
// A plural form for the current source string.
|
||||
|
||||
if ($this->_context != 'MSGID') {
|
||||
// A plural form can only be added to an msgid directly.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Remove 'msgid_plural' and trim away whitespace.
|
||||
$line = trim(substr($line, 12));
|
||||
|
||||
// Only the plural source string is left, parse it.
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The plural form must be wrapped in quotes.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains a syntax error on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Append the plural source to the current entry.
|
||||
if (is_string($this->_current_item['msgid'])) {
|
||||
// The first value was stored as string. Now we know the context is
|
||||
// plural, it is converted to array.
|
||||
$this->_current_item['msgid'] = array($this->_current_item['msgid']);
|
||||
}
|
||||
$this->_current_item['msgid'][] = $quoted;
|
||||
|
||||
$this->_context = 'MSGID_PLURAL';
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp('msgid', $line, 5)) {
|
||||
// Starting a new message.
|
||||
|
||||
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
|
||||
// We are currently in string context, save current item.
|
||||
$this->setItemFromArray($this->_current_item);
|
||||
|
||||
// Start a new context for the msgid.
|
||||
$this->_current_item = array();
|
||||
}
|
||||
elseif ($this->_context == 'MSGID') {
|
||||
// We are currently already in the context, meaning we passed an id with no data.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Remove 'msgid' and trim away whitespace.
|
||||
$line = trim(substr($line, 5));
|
||||
|
||||
// Only the message id string is left, parse it.
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The message id must be wrapped in quotes.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->_current_item['msgid'] = $quoted;
|
||||
$this->_context = 'MSGID';
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp('msgctxt', $line, 7)) {
|
||||
// Starting a new context.
|
||||
|
||||
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
|
||||
// We are currently in string context, save current item.
|
||||
$this->setItemFromArray($this->_current_item);
|
||||
$this->_current_item = array();
|
||||
}
|
||||
elseif (!empty($this->_current_item['msgctxt'])) {
|
||||
// A context cannot apply to another context.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Remove 'msgctxt' and trim away whitespaces.
|
||||
$line = trim(substr($line, 7));
|
||||
|
||||
// Only the msgctxt string is left, parse it.
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The context string must be quoted.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->_current_item['msgctxt'] = $quoted;
|
||||
|
||||
$this->_context = 'MSGCTXT';
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp('msgstr[', $line, 7)) {
|
||||
// A message string for a specific plurality.
|
||||
|
||||
if (($this->_context != 'MSGID') &&
|
||||
($this->_context != 'MSGCTXT') &&
|
||||
($this->_context != 'MSGID_PLURAL') &&
|
||||
($this->_context != 'MSGSTR_ARR')) {
|
||||
// Plural message strings must come after msgid, msgxtxt,
|
||||
// msgid_plural, or other msgstr[] entries.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Ensure the plurality is terminated.
|
||||
if (strpos($line, ']') === FALSE) {
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Extract the plurality.
|
||||
$frombracket = strstr($line, '[');
|
||||
$this->_current_plural_index = substr($frombracket, 1, strpos($frombracket, ']') - 1);
|
||||
|
||||
// Skip to the next whitespace and trim away any further whitespace,
|
||||
// bringing $line to the message text only.
|
||||
$line = trim(strstr($line, " "));
|
||||
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The string must be quoted.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
if (!isset($this->_current_item['msgstr']) || !is_array($this->_current_item['msgstr'])) {
|
||||
$this->_current_item['msgstr'] = array();
|
||||
}
|
||||
|
||||
$this->_current_item['msgstr'][$this->_current_plural_index] = $quoted;
|
||||
|
||||
$this->_context = 'MSGSTR_ARR';
|
||||
return;
|
||||
}
|
||||
elseif (!strncmp("msgstr", $line, 6)) {
|
||||
// A string pair for an msgid (with optional context).
|
||||
|
||||
if (($this->_context != 'MSGID') && ($this->_context != 'MSGCTXT')) {
|
||||
// Strings are only valid within an id or context scope.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Remove 'msgstr' and trim away away whitespaces.
|
||||
$line = trim(substr($line, 6));
|
||||
|
||||
// Only the msgstr string is left, parse it.
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// The string must be quoted.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->_current_item['msgstr'] = $quoted;
|
||||
|
||||
$this->_context = 'MSGSTR';
|
||||
return;
|
||||
}
|
||||
elseif ($line != '') {
|
||||
// Anything that is not a token may be a continuation of a previous token.
|
||||
|
||||
$quoted = $this->parseQuoted($line);
|
||||
if ($quoted === FALSE) {
|
||||
// This string must be quoted.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Append the string to the current item.
|
||||
if (($this->_context == 'MSGID') || ($this->_context == 'MSGID_PLURAL')) {
|
||||
if (is_array($this->_current_item['msgid'])) {
|
||||
// Add string to last array element for plural sources.
|
||||
$last_index = count($this->_current_item['msgid']) - 1;
|
||||
$this->_current_item['msgid'][$last_index] .= $quoted;
|
||||
}
|
||||
else {
|
||||
// Singular source, just append the string.
|
||||
$this->_current_item['msgid'] .= $quoted;
|
||||
}
|
||||
}
|
||||
elseif ($this->_context == 'MSGCTXT') {
|
||||
// Multiline context name.
|
||||
$this->_current_item['msgctxt'] .= $quoted;
|
||||
}
|
||||
elseif ($this->_context == 'MSGSTR') {
|
||||
// Multiline translation string.
|
||||
$this->_current_item['msgstr'] .= $quoted;
|
||||
}
|
||||
elseif ($this->_context == 'MSGSTR_ARR') {
|
||||
// Multiline plural translation string.
|
||||
$this->_current_item['msgstr'][$this->_current_plural_index] .= $quoted;
|
||||
}
|
||||
else {
|
||||
// No valid context to append to.
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Empty line read or EOF of PO stream, close out the last entry.
|
||||
if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
|
||||
$this->setItemFromArray($this->_current_item);
|
||||
$this->_current_item = array();
|
||||
}
|
||||
elseif ($this->_context != 'COMMENT') {
|
||||
$this->_errors[] = SafeMarkup::format('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the parsed values as a PoItem object.
|
||||
*/
|
||||
public function setItemFromArray($value) {
|
||||
$plural = FALSE;
|
||||
|
||||
$comments = '';
|
||||
if (isset($value['#'])) {
|
||||
$comments = $this->shortenComments($value['#']);
|
||||
}
|
||||
|
||||
if (is_array($value['msgstr'])) {
|
||||
// Sort plural variants by their form index.
|
||||
ksort($value['msgstr']);
|
||||
$plural = TRUE;
|
||||
}
|
||||
|
||||
$item = new PoItem();
|
||||
$item->setContext(isset($value['msgctxt']) ? $value['msgctxt'] : '');
|
||||
$item->setSource($value['msgid']);
|
||||
$item->setTranslation($value['msgstr']);
|
||||
$item->setPlural($plural);
|
||||
$item->setComment($comments);
|
||||
$item->setLangcode($this->_langcode);
|
||||
|
||||
$this->_last_item = $item;
|
||||
|
||||
$this->_context = 'COMMENT';
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string in quotes.
|
||||
*
|
||||
* @param $string
|
||||
* A string specified with enclosing quotes.
|
||||
*
|
||||
* @return
|
||||
* The string parsed from inside the quotes.
|
||||
*/
|
||||
function parseQuoted($string) {
|
||||
if (substr($string, 0, 1) != substr($string, -1, 1)) {
|
||||
// Start and end quotes must be the same.
|
||||
return FALSE;
|
||||
}
|
||||
$quote = substr($string, 0, 1);
|
||||
$string = substr($string, 1, -1);
|
||||
if ($quote == '"') {
|
||||
// Double quotes: strip slashes.
|
||||
return stripcslashes($string);
|
||||
}
|
||||
elseif ($quote == "'") {
|
||||
// Simple quote: return as-is.
|
||||
return $string;
|
||||
}
|
||||
else {
|
||||
// Unrecognized quote.
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a short, one-string version of the passed comment array.
|
||||
*
|
||||
* @param $comment
|
||||
* An array of strings containing a comment.
|
||||
*
|
||||
* @return
|
||||
* Short one-string version of the comment.
|
||||
*/
|
||||
private function shortenComments($comment) {
|
||||
$comm = '';
|
||||
while (count($comment)) {
|
||||
$test = $comm . substr(array_shift($comment), 1) . ', ';
|
||||
if (strlen($comm) < 130) {
|
||||
$comm = $test;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return trim(substr($comm, 0, -2));
|
||||
}
|
||||
|
||||
}
|
157
web/core/lib/Drupal/Component/Gettext/PoStreamWriter.php
Normal file
157
web/core/lib/Drupal/Component/Gettext/PoStreamWriter.php
Normal file
|
@ -0,0 +1,157 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
/**
|
||||
* Defines a Gettext PO stream writer.
|
||||
*/
|
||||
class PoStreamWriter implements PoWriterInterface, PoStreamInterface {
|
||||
|
||||
/**
|
||||
* URI of the PO stream that is being written.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $_uri;
|
||||
|
||||
/**
|
||||
* The Gettext PO header.
|
||||
*
|
||||
* @var \Drupal\Component\Gettext\PoHeader
|
||||
*/
|
||||
private $_header;
|
||||
|
||||
/**
|
||||
* File handle of the current PO stream.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
private $_fd;
|
||||
|
||||
/**
|
||||
* Gets the PO header of the current stream.
|
||||
*
|
||||
* @return \Drupal\Component\Gettext\PoHeader
|
||||
* The Gettext PO header.
|
||||
*/
|
||||
public function getHeader() {
|
||||
return $this->_header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the PO header for the current stream.
|
||||
*
|
||||
* @param \Drupal\Component\Gettext\PoHeader $header
|
||||
* The Gettext PO header to set.
|
||||
*/
|
||||
public function setHeader(PoHeader $header) {
|
||||
$this->_header = $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current language code used.
|
||||
*
|
||||
* @return string
|
||||
* The language code.
|
||||
*/
|
||||
public function getLangcode() {
|
||||
return $this->_langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the language code.
|
||||
*
|
||||
* @param string $langcode
|
||||
* The language code.
|
||||
*/
|
||||
public function setLangcode($langcode) {
|
||||
$this->_langcode = $langcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function open() {
|
||||
// Open in write mode. Will overwrite the stream if it already exists.
|
||||
$this->_fd = fopen($this->getURI(), 'w');
|
||||
// Write the header at the start.
|
||||
$this->writeHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoStreamInterface::close().
|
||||
*
|
||||
* @throws Exception
|
||||
* If the stream is not open.
|
||||
*/
|
||||
public function close() {
|
||||
if ($this->_fd) {
|
||||
fclose($this->_fd);
|
||||
}
|
||||
else {
|
||||
throw new Exception('Cannot close stream that is not open.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
* @param string $data
|
||||
* Piece of string to write to the stream. If the value is not directly a
|
||||
* string, casting will happen in writing.
|
||||
*
|
||||
* @throws Exception
|
||||
* If writing the data is not possible.
|
||||
*/
|
||||
private function write($data) {
|
||||
$result = fputs($this->_fd, $data);
|
||||
if ($result === FALSE) {
|
||||
throw new Exception('Unable to write data: ' . substr($data, 0, 20));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the PO header to the stream.
|
||||
*/
|
||||
private function writeHeader() {
|
||||
$this->write($this->_header);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeItem(PoItem $item) {
|
||||
$this->write($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function writeItems(PoReaderInterface $reader, $count = -1) {
|
||||
$forever = $count == -1;
|
||||
while (($count-- > 0 || $forever) && ($item = $reader->readItem())) {
|
||||
$this->writeItem($item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements Drupal\Component\Gettext\PoStreamInterface::getURI().
|
||||
*
|
||||
* @throws Exception
|
||||
* If the URI is not set.
|
||||
*/
|
||||
public function getURI() {
|
||||
if (empty($this->_uri)) {
|
||||
throw new Exception('No URI set.');
|
||||
}
|
||||
return $this->_uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setURI($uri) {
|
||||
$this->_uri = $uri;
|
||||
}
|
||||
|
||||
}
|
29
web/core/lib/Drupal/Component/Gettext/PoWriterInterface.php
Normal file
29
web/core/lib/Drupal/Component/Gettext/PoWriterInterface.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Component\Gettext;
|
||||
|
||||
/**
|
||||
* Shared interface definition for all Gettext PO Writers.
|
||||
*/
|
||||
interface PoWriterInterface extends PoMetadataInterface {
|
||||
|
||||
/**
|
||||
* Writes the given item.
|
||||
*
|
||||
* @param PoItem $item
|
||||
* One specific item to write.
|
||||
*/
|
||||
public function writeItem(PoItem $item);
|
||||
|
||||
/**
|
||||
* Writes all or the given amount of items.
|
||||
*
|
||||
* @param PoReaderInterface $reader
|
||||
* Reader to read PoItems from.
|
||||
* @param $count
|
||||
* Amount of items to read from $reader to write. If -1, all items are
|
||||
* read from $reader.
|
||||
*/
|
||||
public function writeItems(PoReaderInterface $reader, $count = -1);
|
||||
|
||||
}
|
12
web/core/lib/Drupal/Component/Gettext/README.txt
Normal file
12
web/core/lib/Drupal/Component/Gettext/README.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
The Drupal Gettext Component
|
||||
|
||||
Thanks for using this Drupal component.
|
||||
|
||||
You can participate in its development on Drupal.org, through our issue system:
|
||||
https://www.drupal.org/project/issues/drupal
|
||||
|
||||
You can get the full Drupal repo here:
|
||||
https://www.drupal.org/project/drupal/git-instructions
|
||||
|
||||
You can browse the full Drupal repo here:
|
||||
http://cgit.drupalcode.org/drupal
|
18
web/core/lib/Drupal/Component/Gettext/TESTING.txt
Normal file
18
web/core/lib/Drupal/Component/Gettext/TESTING.txt
Normal file
|
@ -0,0 +1,18 @@
|
|||
HOW-TO: Test this Drupal component
|
||||
|
||||
In order to test this component, you'll need to get the entire Drupal repo and
|
||||
run the tests there.
|
||||
|
||||
You'll find the tests under core/tests/Drupal/Tests/Component.
|
||||
|
||||
You can get the full Drupal repo here:
|
||||
https://www.drupal.org/project/drupal/git-instructions
|
||||
|
||||
You can find more information about running PHPUnit tests with Drupal here:
|
||||
https://www.drupal.org/node/2116263
|
||||
|
||||
Each component in the Drupal\Component namespace has its own annotated test
|
||||
group. You can use this group to run only the tests for this component. Like
|
||||
this:
|
||||
|
||||
$ ./vendor/bin/phpunit -c core --group Gettext
|
20
web/core/lib/Drupal/Component/Gettext/composer.json
Normal file
20
web/core/lib/Drupal/Component/Gettext/composer.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "drupal/core-gettext",
|
||||
"description": "PHP library for reading PO files.",
|
||||
"type": "library",
|
||||
"license": "GPL-2.0+",
|
||||
"support": {
|
||||
"issues": "https://www.drupal.org/project/issues/drupal",
|
||||
"irc": "irc://irc.freenode.net/drupal-contribute",
|
||||
"source": "https://www.drupal.org/project/drupal/git-instructions"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"drupal/core-utility": "~8.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Drupal\\Component\\Gettext\\": ""
|
||||
}
|
||||
}
|
||||
}
|
Reference in a new issue