forked from GithubBackups/tinyib
parent
6bc831ae19
commit
2bbbfc972c
102
imgboard.php
102
imgboard.php
@ -25,6 +25,8 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
use Gettext\Translator;
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
error_reporting(E_ALL);
|
error_reporting(E_ALL);
|
||||||
ini_set("display_errors", 1);
|
ini_set("display_errors", 1);
|
||||||
@ -60,24 +62,24 @@ if (!file_exists('settings.php')) {
|
|||||||
}
|
}
|
||||||
require 'settings.php';
|
require 'settings.php';
|
||||||
|
|
||||||
if (function_exists('_')) {
|
if (TINYIB_LOCALE == '') {
|
||||||
if (defined('TINYIB_LOCALE')) {
|
function __($string) {
|
||||||
setlocale(LC_ALL, TINYIB_LOCALE);
|
|
||||||
}
|
|
||||||
bindtextdomain('tinyib', 'locale');
|
|
||||||
textdomain('tinyib');
|
|
||||||
} else {
|
|
||||||
function _($string) {
|
|
||||||
return $string;
|
return $string;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
require 'inc/gettext/src/autoloader.php';
|
||||||
|
$translations = Translations::fromPoFile('locale/' . TINYIB_LOCALE . '/LC_MESSAGES/tinyib.po');
|
||||||
|
$translator = new Translator();
|
||||||
|
$translator->loadTranslations($translations);
|
||||||
|
$translator->register();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TINYIB_TRIPSEED == '' || TINYIB_ADMINPASS == '') {
|
if (TINYIB_TRIPSEED == '' || TINYIB_ADMINPASS == '') {
|
||||||
fancyDie(_('TINYIB_TRIPSEED and TINYIB_ADMINPASS must be configured.'));
|
fancyDie(__('TINYIB_TRIPSEED and TINYIB_ADMINPASS must be configured.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TINYIB_CAPTCHA === 'recaptcha' && (TINYIB_RECAPTCHA_SITE == '' || TINYIB_RECAPTCHA_SECRET == '')) {
|
if (TINYIB_CAPTCHA === 'recaptcha' && (TINYIB_RECAPTCHA_SITE == '' || TINYIB_RECAPTCHA_SECRET == '')) {
|
||||||
fancyDie(_('TINYIB_RECAPTCHA_SITE and TINYIB_RECAPTCHA_SECRET must be configured.'));
|
fancyDie(__('TINYIB_RECAPTCHA_SITE and TINYIB_RECAPTCHA_SECRET must be configured.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check directories are writable by the script
|
// Check directories are writable by the script
|
||||||
@ -87,7 +89,7 @@ if (TINYIB_DBMODE == 'flatfile') {
|
|||||||
}
|
}
|
||||||
foreach ($writedirs as $dir) {
|
foreach ($writedirs as $dir) {
|
||||||
if (!is_writable($dir)) {
|
if (!is_writable($dir)) {
|
||||||
fancyDie(sprintf(_("Directory '%s' can not be written to. Please modify its permissions."), $dir));
|
fancyDie(sprintf(__("Directory '%s' can not be written to. Please modify its permissions."), $dir));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +97,7 @@ $includes = array("inc/defines.php", "inc/functions.php", "inc/html.php");
|
|||||||
if (in_array(TINYIB_DBMODE, array('flatfile', 'mysql', 'mysqli', 'sqlite', 'sqlite3', 'pdo'))) {
|
if (in_array(TINYIB_DBMODE, array('flatfile', 'mysql', 'mysqli', 'sqlite', 'sqlite3', 'pdo'))) {
|
||||||
$includes[] = 'inc/database_' . TINYIB_DBMODE . '.php';
|
$includes[] = 'inc/database_' . TINYIB_DBMODE . '.php';
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_('Unknown database mode specified.'));
|
fancyDie(__('Unknown database mode specified.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($includes as $include) {
|
foreach ($includes as $include) {
|
||||||
@ -110,7 +112,7 @@ $redirect = true;
|
|||||||
// Check if the request is to make a post
|
// Check if the request is to make a post
|
||||||
if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name']) || isset($_POST['email']) || isset($_POST['subject']) || isset($_POST['message']) || isset($_POST['file']) || isset($_POST['embed']) || isset($_POST['password']))) {
|
if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name']) || isset($_POST['email']) || isset($_POST['subject']) || isset($_POST['message']) || isset($_POST['file']) || isset($_POST['embed']) || isset($_POST['password']))) {
|
||||||
if (TINYIB_DBMIGRATE) {
|
if (TINYIB_DBMIGRATE) {
|
||||||
fancyDie(_('Posting is currently disabled.<br>Please try again in a few moments.'));
|
fancyDie(__('Posting is currently disabled.<br>Please try again in a few moments.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
list($loggedin, $isadmin) = manageCheckLogIn();
|
list($loggedin, $isadmin) = manageCheckLogIn();
|
||||||
@ -129,9 +131,9 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
if ($post['parent'] != TINYIB_NEWTHREAD && !$loggedin) {
|
if ($post['parent'] != TINYIB_NEWTHREAD && !$loggedin) {
|
||||||
$parent = postByID($post['parent']);
|
$parent = postByID($post['parent']);
|
||||||
if (!isset($parent['locked'])) {
|
if (!isset($parent['locked'])) {
|
||||||
fancyDie(_('Invalid parent thread ID supplied, unable to create post.'));
|
fancyDie(__('Invalid parent thread ID supplied, unable to create post.'));
|
||||||
} else if ($parent['locked'] == 1) {
|
} else if ($parent['locked'] == 1) {
|
||||||
fancyDie(_('Replies are not allowed to locked threads.'));
|
fancyDie(__('Replies are not allowed to locked threads.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,12 +170,12 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
|
|
||||||
if (isset($_POST['embed']) && trim($_POST['embed']) != '' && ($rawpost || !in_array('embed', $hide_fields))) {
|
if (isset($_POST['embed']) && trim($_POST['embed']) != '' && ($rawpost || !in_array('embed', $hide_fields))) {
|
||||||
if (isset($_FILES['file']) && $_FILES['file']['name'] != "") {
|
if (isset($_FILES['file']) && $_FILES['file']['name'] != "") {
|
||||||
fancyDie(_('Embedding a URL and uploading a file at the same time is not supported.'));
|
fancyDie(__('Embedding a URL and uploading a file at the same time is not supported.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
list($service, $embed) = getEmbed(trim($_POST['embed']));
|
list($service, $embed) = getEmbed(trim($_POST['embed']));
|
||||||
if (empty($embed) || !isset($embed['html']) || !isset($embed['title']) || !isset($embed['thumbnail_url'])) {
|
if (empty($embed) || !isset($embed['html']) || !isset($embed['title']) || !isset($embed['thumbnail_url'])) {
|
||||||
fancyDie(sprintf(_('Invalid embed URL. Only %s URLs are supported.'), implode('/', array_keys($tinyib_embeds))));
|
fancyDie(sprintf(__('Invalid embed URL. Only %s URLs are supported.'), implode('/', array_keys($tinyib_embeds))));
|
||||||
}
|
}
|
||||||
|
|
||||||
$post['file_hex'] = $service;
|
$post['file_hex'] = $service;
|
||||||
@ -193,14 +195,14 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
} else if ($file_mime == "image/png") {
|
} else if ($file_mime == "image/png") {
|
||||||
$post['thumb'] = $temp_file . '.png';
|
$post['thumb'] = $temp_file . '.png';
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_('Error while processing audio/video.'));
|
fancyDie(__('Error while processing audio/video.'));
|
||||||
}
|
}
|
||||||
$thumb_location = "thumb/" . $post['thumb'];
|
$thumb_location = "thumb/" . $post['thumb'];
|
||||||
|
|
||||||
list($thumb_maxwidth, $thumb_maxheight) = thumbnailDimensions($post);
|
list($thumb_maxwidth, $thumb_maxheight) = thumbnailDimensions($post);
|
||||||
|
|
||||||
if (!createThumbnail($file_location, $thumb_location, $thumb_maxwidth, $thumb_maxheight)) {
|
if (!createThumbnail($file_location, $thumb_location, $thumb_maxwidth, $thumb_maxheight)) {
|
||||||
fancyDie(_('Could not create thumbnail.'));
|
fancyDie(__('Could not create thumbnail.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
addVideoOverlay($thumb_location);
|
addVideoOverlay($thumb_location);
|
||||||
@ -216,11 +218,11 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
validateFileUpload();
|
validateFileUpload();
|
||||||
|
|
||||||
if (!is_file($_FILES['file']['tmp_name']) || !is_readable($_FILES['file']['tmp_name'])) {
|
if (!is_file($_FILES['file']['tmp_name']) || !is_readable($_FILES['file']['tmp_name'])) {
|
||||||
fancyDie(_('File transfer failure. Please retry the submission.'));
|
fancyDie(__('File transfer failure. Please retry the submission.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((TINYIB_MAXKB > 0) && (filesize($_FILES['file']['tmp_name']) > (TINYIB_MAXKB * 1024))) {
|
if ((TINYIB_MAXKB > 0) && (filesize($_FILES['file']['tmp_name']) > (TINYIB_MAXKB * 1024))) {
|
||||||
fancyDie(sprintf(_('That file is larger than %s.'), TINYIB_MAXKBDESC));
|
fancyDie(sprintf(__('That file is larger than %s.'), TINYIB_MAXKBDESC));
|
||||||
}
|
}
|
||||||
|
|
||||||
$post['file_original'] = trim(htmlentities(substr($_FILES['file']['name'], 0, 50), ENT_QUOTES));
|
$post['file_original'] = trim(htmlentities(substr($_FILES['file']['name'], 0, 50), ENT_QUOTES));
|
||||||
@ -235,7 +237,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
$file_mime = strtolower(array_pop($file_mime_split));
|
$file_mime = strtolower(array_pop($file_mime_split));
|
||||||
} else {
|
} else {
|
||||||
if (!@getimagesize($_FILES['file']['tmp_name'])) {
|
if (!@getimagesize($_FILES['file']['tmp_name'])) {
|
||||||
fancyDie(_('Failed to read the MIME type and size of the uploaded file. Please retry the submission.'));
|
fancyDie(__('Failed to read the MIME type and size of the uploaded file. Please retry the submission.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$file_info = getimagesize($_FILES['file']['tmp_name']);
|
$file_info = getimagesize($_FILES['file']['tmp_name']);
|
||||||
@ -251,12 +253,12 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
|
|
||||||
$file_location = "src/" . $post['file'];
|
$file_location = "src/" . $post['file'];
|
||||||
if (!move_uploaded_file($_FILES['file']['tmp_name'], $file_location)) {
|
if (!move_uploaded_file($_FILES['file']['tmp_name'], $file_location)) {
|
||||||
fancyDie(_('Could not copy uploaded file.'));
|
fancyDie(__('Could not copy uploaded file.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($_FILES['file']['size'] != filesize($file_location)) {
|
if ($_FILES['file']['size'] != filesize($file_location)) {
|
||||||
@unlink($file_location);
|
@unlink($file_location);
|
||||||
fancyDie(_('File transfer failure. Please go back and try again.'));
|
fancyDie(__('File transfer failure. Please go back and try again.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($file_mime == "audio/webm" || $file_mime == "video/webm" || $file_mime == "audio/mp4" || $file_mime == "video/mp4") {
|
if ($file_mime == "audio/webm" || $file_mime == "video/webm" || $file_mime == "audio/mp4" || $file_mime == "video/mp4") {
|
||||||
@ -275,7 +277,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
if ($post['thumb_width'] <= 0 || $post['thumb_height'] <= 0) {
|
if ($post['thumb_width'] <= 0 || $post['thumb_height'] <= 0) {
|
||||||
@unlink($file_location);
|
@unlink($file_location);
|
||||||
@unlink("thumb/" . $post['thumb']);
|
@unlink("thumb/" . $post['thumb']);
|
||||||
fancyDie(_('Sorry, your video appears to be corrupt.'));
|
fancyDie(__('Sorry, your video appears to be corrupt.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
addVideoOverlay("thumb/" . $post['thumb']);
|
addVideoOverlay("thumb/" . $post['thumb']);
|
||||||
@ -300,7 +302,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
$post['thumb'] = $file_name . "s." . array_pop($thumbfile_split);
|
$post['thumb'] = $file_name . "s." . array_pop($thumbfile_split);
|
||||||
if (!copy($tinyib_uploads[$file_mime][1], "thumb/" . $post['thumb'])) {
|
if (!copy($tinyib_uploads[$file_mime][1], "thumb/" . $post['thumb'])) {
|
||||||
@unlink($file_location);
|
@unlink($file_location);
|
||||||
fancyDie(_('Could not create thumbnail.'));
|
fancyDie(__('Could not create thumbnail.'));
|
||||||
}
|
}
|
||||||
if ($file_mime == "application/x-shockwave-flash") {
|
if ($file_mime == "application/x-shockwave-flash") {
|
||||||
addVideoOverlay("thumb/" . $post['thumb']);
|
addVideoOverlay("thumb/" . $post['thumb']);
|
||||||
@ -311,7 +313,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
|
|
||||||
if (!createThumbnail($file_location, "thumb/" . $post['thumb'], $thumb_maxwidth, $thumb_maxheight)) {
|
if (!createThumbnail($file_location, "thumb/" . $post['thumb'], $thumb_maxwidth, $thumb_maxheight)) {
|
||||||
@unlink($file_location);
|
@unlink($file_location);
|
||||||
fancyDie(_('Could not create thumbnail.'));
|
fancyDie(__('Could not create thumbnail.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +337,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
$allowed .= "embed URL";
|
$allowed .= "embed URL";
|
||||||
}
|
}
|
||||||
if ($post['parent'] == TINYIB_NEWTHREAD && $allowed != "" && !TINYIB_NOFILEOK) {
|
if ($post['parent'] == TINYIB_NEWTHREAD && $allowed != "" && !TINYIB_NOFILEOK) {
|
||||||
fancyDie(sprintf(_('A %s is required to start a thread.'), $allowed));
|
fancyDie(sprintf(__('A %s is required to start a thread.'), $allowed));
|
||||||
}
|
}
|
||||||
if (!$rawpost && str_replace('<br>', '', $post['message']) == "") {
|
if (!$rawpost && str_replace('<br>', '', $post['message']) == "") {
|
||||||
$die_msg = "";
|
$die_msg = "";
|
||||||
@ -348,12 +350,12 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
fancyDie("Please $die_msg.");
|
fancyDie("Please $die_msg.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
echo sprintf(_('%s uploaded.'), $post['file_original']) . '<br>';
|
echo sprintf(__('%s uploaded.'), $post['file_original']) . '<br>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$loggedin && (($post['file'] != '' && TINYIB_REQMOD == 'files') || TINYIB_REQMOD == 'all')) {
|
if (!$loggedin && (($post['file'] != '' && TINYIB_REQMOD == 'files') || TINYIB_REQMOD == 'all')) {
|
||||||
$post['moderated'] = '0';
|
$post['moderated'] = '0';
|
||||||
echo sprintf(_('Your %s will be shown <b>once it has been approved</b>.'), $post['parent'] == TINYIB_NEWTHREAD ? 'thread' : 'post') . '<br>';
|
echo sprintf(__('Your %s will be shown <b>once it has been approved</b>.'), $post['parent'] == TINYIB_NEWTHREAD ? 'thread' : 'post') . '<br>';
|
||||||
$slow_redirect = true;
|
$slow_redirect = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,7 +368,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
|
|
||||||
trimThreads();
|
trimThreads();
|
||||||
|
|
||||||
echo _('Updating thread...') . '<br>';
|
echo __('Updating thread...') . '<br>';
|
||||||
if ($post['parent'] != TINYIB_NEWTHREAD) {
|
if ($post['parent'] != TINYIB_NEWTHREAD) {
|
||||||
rebuildThread($post['parent']);
|
rebuildThread($post['parent']);
|
||||||
|
|
||||||
@ -379,17 +381,17 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
rebuildThread($post['id']);
|
rebuildThread($post['id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
echo _('Updating index...') . '<br>';
|
echo __('Updating index...') . '<br>';
|
||||||
rebuildIndexes();
|
rebuildIndexes();
|
||||||
}
|
}
|
||||||
// Check if the request is to delete a post and/or its associated image
|
// Check if the request is to delete a post and/or its associated image
|
||||||
} elseif (isset($_GET['delete']) && !isset($_GET['manage'])) {
|
} elseif (isset($_GET['delete']) && !isset($_GET['manage'])) {
|
||||||
if (!isset($_POST['delete'])) {
|
if (!isset($_POST['delete'])) {
|
||||||
fancyDie(_('Tick the box next to a post and click "Delete" to delete it.'));
|
fancyDie(__('Tick the box next to a post and click "Delete" to delete it.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TINYIB_DBMIGRATE) {
|
if (TINYIB_DBMIGRATE) {
|
||||||
fancyDie(_('Post deletion is currently disabled.<br>Please try again in a few moments.'));
|
fancyDie(__('Post deletion is currently disabled.<br>Please try again in a few moments.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$post = postByID($_POST['delete']);
|
$post = postByID($_POST['delete']);
|
||||||
@ -406,12 +408,12 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
} else {
|
} else {
|
||||||
threadUpdated($post['parent']);
|
threadUpdated($post['parent']);
|
||||||
}
|
}
|
||||||
fancyDie(_('Post deleted.'));
|
fancyDie(__('Post deleted.'));
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_('Invalid password.'));
|
fancyDie(__('Invalid password.'));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_('Sorry, an invalid post identifier was sent. Please go back, refresh the page, and try again.'));
|
fancyDie(__('Sorry, an invalid post identifier was sent. Please go back, refresh the page, and try again.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$redirect = false;
|
$redirect = false;
|
||||||
@ -435,7 +437,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
rebuildThread($thread['id']);
|
rebuildThread($thread['id']);
|
||||||
}
|
}
|
||||||
rebuildIndexes();
|
rebuildIndexes();
|
||||||
$text .= manageInfo(_('Rebuilt board.'));
|
$text .= manageInfo(__('Rebuilt board.'));
|
||||||
} elseif (isset($_GET['bans'])) {
|
} elseif (isset($_GET['bans'])) {
|
||||||
clearExpiredBans();
|
clearExpiredBans();
|
||||||
|
|
||||||
@ -443,7 +445,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
if ($_POST['ip'] != '') {
|
if ($_POST['ip'] != '') {
|
||||||
$banexists = banByIP($_POST['ip']);
|
$banexists = banByIP($_POST['ip']);
|
||||||
if ($banexists) {
|
if ($banexists) {
|
||||||
fancyDie(_('Sorry, there is already a ban on record for that IP address.'));
|
fancyDie(__('Sorry, there is already a ban on record for that IP address.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$ban = array();
|
$ban = array();
|
||||||
@ -452,13 +454,13 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
$ban['reason'] = $_POST['reason'];
|
$ban['reason'] = $_POST['reason'];
|
||||||
|
|
||||||
insertBan($ban);
|
insertBan($ban);
|
||||||
$text .= manageInfo(sprintf(_('Ban record added for %s'), $ban['ip']));
|
$text .= manageInfo(sprintf(__('Ban record added for %s'), $ban['ip']));
|
||||||
}
|
}
|
||||||
} elseif (isset($_GET['lift'])) {
|
} elseif (isset($_GET['lift'])) {
|
||||||
$ban = banByID($_GET['lift']);
|
$ban = banByID($_GET['lift']);
|
||||||
if ($ban) {
|
if ($ban) {
|
||||||
deleteBanByID($_GET['lift']);
|
deleteBanByID($_GET['lift']);
|
||||||
$text .= manageInfo(sprintf(_('Ban record lifted for %s'), $ban['ip']));
|
$text .= manageInfo(sprintf(__('Ban record lifted for %s'), $ban['ip']));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,9 +552,9 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
if ($post['parent'] != TINYIB_NEWTHREAD) {
|
if ($post['parent'] != TINYIB_NEWTHREAD) {
|
||||||
rebuildThread($post['parent']);
|
rebuildThread($post['parent']);
|
||||||
}
|
}
|
||||||
$text .= manageInfo(sprintf(_('Post No.%d deleted.'), $post['id']));
|
$text .= manageInfo(sprintf(__('Post No.%d deleted.'), $post['id']));
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_("Sorry, there doesn't appear to be a post with that ID."));
|
fancyDie(__("Sorry, there doesn't appear to be a post with that ID."));
|
||||||
}
|
}
|
||||||
} elseif (isset($_GET['approve'])) {
|
} elseif (isset($_GET['approve'])) {
|
||||||
if ($_GET['approve'] > 0) {
|
if ($_GET['approve'] > 0) {
|
||||||
@ -566,9 +568,9 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
}
|
}
|
||||||
threadUpdated($thread_id);
|
threadUpdated($thread_id);
|
||||||
|
|
||||||
$text .= manageInfo(sprintf(_('Post No.%d approved.'), $post['id']));
|
$text .= manageInfo(sprintf(__('Post No.%d approved.'), $post['id']));
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_("Sorry, there doesn't appear to be a post with that ID."));
|
fancyDie(__("Sorry, there doesn't appear to be a post with that ID."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} elseif (isset($_GET['moderate'])) {
|
} elseif (isset($_GET['moderate'])) {
|
||||||
@ -577,7 +579,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
if ($post) {
|
if ($post) {
|
||||||
$text .= manageModeratePost($post);
|
$text .= manageModeratePost($post);
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_("Sorry, there doesn't appear to be a post with that ID."));
|
fancyDie(__("Sorry, there doesn't appear to be a post with that ID."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$onload = manageOnLoad('moderate');
|
$onload = manageOnLoad('moderate');
|
||||||
@ -592,10 +594,10 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
|
|
||||||
$text .= manageInfo('Thread No.' . $post['id'] . ' ' . (intval($_GET['setsticky']) == 1 ? 'stickied' : 'un-stickied') . '.');
|
$text .= manageInfo('Thread No.' . $post['id'] . ' ' . (intval($_GET['setsticky']) == 1 ? 'stickied' : 'un-stickied') . '.');
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_("Sorry, there doesn't appear to be a post with that ID."));
|
fancyDie(__("Sorry, there doesn't appear to be a post with that ID."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_('Form data was lost. Please go back and try again.'));
|
fancyDie(__('Form data was lost. Please go back and try again.'));
|
||||||
}
|
}
|
||||||
} elseif (isset($_GET['lock']) && isset($_GET['setlock'])) {
|
} elseif (isset($_GET['lock']) && isset($_GET['setlock'])) {
|
||||||
if ($_GET['lock'] > 0) {
|
if ($_GET['lock'] > 0) {
|
||||||
@ -606,10 +608,10 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
|
|||||||
|
|
||||||
$text .= manageInfo('Thread No.' . $post['id'] . ' ' . (intval($_GET['setlock']) == 1 ? 'locked' : 'unlocked') . '.');
|
$text .= manageInfo('Thread No.' . $post['id'] . ' ' . (intval($_GET['setlock']) == 1 ? 'locked' : 'unlocked') . '.');
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_("Sorry, there doesn't appear to be a post with that ID."));
|
fancyDie(__("Sorry, there doesn't appear to be a post with that ID."));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fancyDie(_('Form data was lost. Please go back and try again.'));
|
fancyDie(__('Form data was lost. Please go back and try again.'));
|
||||||
}
|
}
|
||||||
} elseif (isset($_GET["rawpost"])) {
|
} elseif (isset($_GET["rawpost"])) {
|
||||||
$onload = manageOnLoad("rawpost");
|
$onload = manageOnLoad("rawpost");
|
||||||
|
@ -211,7 +211,7 @@ function nameAndTripcode($name) {
|
|||||||
|
|
||||||
function nameBlock($name, $tripcode, $email, $timestamp, $rawposttext) {
|
function nameBlock($name, $tripcode, $email, $timestamp, $rawposttext) {
|
||||||
$output = '<span class="postername">';
|
$output = '<span class="postername">';
|
||||||
$output .= ($name == '' && $tripcode == '') ? _('Anonymous') : $name;
|
$output .= ($name == '' && $tripcode == '') ? __('Anonymous') : $name;
|
||||||
|
|
||||||
if ($tripcode != '') {
|
if ($tripcode != '') {
|
||||||
$output .= '</span><span class="postertrip">!' . $tripcode;
|
$output .= '</span><span class="postertrip">!' . $tripcode;
|
||||||
@ -320,9 +320,9 @@ function checkCAPTCHA() {
|
|||||||
$captcha_solution = isset($_SESSION['tinyibcaptcha']) ? strtolower(trim($_SESSION['tinyibcaptcha'])) : '';
|
$captcha_solution = isset($_SESSION['tinyibcaptcha']) ? strtolower(trim($_SESSION['tinyibcaptcha'])) : '';
|
||||||
|
|
||||||
if ($captcha == '') {
|
if ($captcha == '') {
|
||||||
fancyDie(_('Please enter the CAPTCHA text.'));
|
fancyDie(__('Please enter the CAPTCHA text.'));
|
||||||
} else if ($captcha != $captcha_solution) {
|
} else if ($captcha != $captcha_solution) {
|
||||||
fancyDie(_('Incorrect CAPTCHA text entered. Please try again.<br>Click the image to retrieve a new CAPTCHA.'));
|
fancyDie(__('Incorrect CAPTCHA text entered. Please try again.<br>Click the image to retrieve a new CAPTCHA.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,7 +353,7 @@ function checkFlood() {
|
|||||||
|
|
||||||
function checkMessageSize() {
|
function checkMessageSize() {
|
||||||
if (strlen($_POST["message"]) > 8000) {
|
if (strlen($_POST["message"]) > 8000) {
|
||||||
fancyDie(sprintf(_('Please shorten your message, or post it in multiple parts. Your message is %1$d characters long, and the maximum allowed is %2$d.'), strlen($_POST["message"]), 8000));
|
fancyDie(sprintf(__('Please shorten your message, or post it in multiple parts. Your message is %1$d characters long, and the maximum allowed is %2$d.'), strlen($_POST["message"]), 8000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -384,7 +384,7 @@ function setParent() {
|
|||||||
if (isset($_POST["parent"])) {
|
if (isset($_POST["parent"])) {
|
||||||
if ($_POST["parent"] != TINYIB_NEWTHREAD) {
|
if ($_POST["parent"] != TINYIB_NEWTHREAD) {
|
||||||
if (!threadExistsByID($_POST['parent'])) {
|
if (!threadExistsByID($_POST['parent'])) {
|
||||||
fancyDie(_('Invalid parent thread ID supplied, unable to create post.'));
|
fancyDie(__('Invalid parent thread ID supplied, unable to create post.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $_POST["parent"];
|
return $_POST["parent"];
|
||||||
@ -410,25 +410,25 @@ function validateFileUpload() {
|
|||||||
case UPLOAD_ERR_OK:
|
case UPLOAD_ERR_OK:
|
||||||
break;
|
break;
|
||||||
case UPLOAD_ERR_FORM_SIZE:
|
case UPLOAD_ERR_FORM_SIZE:
|
||||||
fancyDie(sprintf(_('That file is larger than %s.'), TINYIB_MAXKBDESC));
|
fancyDie(sprintf(__('That file is larger than %s.'), TINYIB_MAXKBDESC));
|
||||||
break;
|
break;
|
||||||
case UPLOAD_ERR_INI_SIZE:
|
case UPLOAD_ERR_INI_SIZE:
|
||||||
fancyDie(sprintf(_('The uploaded file exceeds the upload_max_filesize directive (%s) in php.ini.'), ini_get('upload_max_filesize')));
|
fancyDie(sprintf(__('The uploaded file exceeds the upload_max_filesize directive (%s) in php.ini.'), ini_get('upload_max_filesize')));
|
||||||
break;
|
break;
|
||||||
case UPLOAD_ERR_PARTIAL:
|
case UPLOAD_ERR_PARTIAL:
|
||||||
fancyDie(_('The uploaded file was only partially uploaded.'));
|
fancyDie(__('The uploaded file was only partially uploaded.'));
|
||||||
break;
|
break;
|
||||||
case UPLOAD_ERR_NO_FILE:
|
case UPLOAD_ERR_NO_FILE:
|
||||||
fancyDie(_('No file was uploaded.'));
|
fancyDie(__('No file was uploaded.'));
|
||||||
break;
|
break;
|
||||||
case UPLOAD_ERR_NO_TMP_DIR:
|
case UPLOAD_ERR_NO_TMP_DIR:
|
||||||
fancyDie(_('Missing a temporary folder.'));
|
fancyDie(__('Missing a temporary folder.'));
|
||||||
break;
|
break;
|
||||||
case UPLOAD_ERR_CANT_WRITE:
|
case UPLOAD_ERR_CANT_WRITE:
|
||||||
fancyDie(_('Failed to write file to disk'));
|
fancyDie(__('Failed to write file to disk'));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
fancyDie(_('Unable to save the uploaded file.'));
|
fancyDie(__('Unable to save the uploaded file.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -436,7 +436,7 @@ function checkDuplicateFile($hex) {
|
|||||||
$hexmatches = postsByHex($hex);
|
$hexmatches = postsByHex($hex);
|
||||||
if (count($hexmatches) > 0) {
|
if (count($hexmatches) > 0) {
|
||||||
foreach ($hexmatches as $hexmatch) {
|
foreach ($hexmatches as $hexmatch) {
|
||||||
fancyDie(sprintf(_('Duplicate file uploaded. That file has already been posted <a href="%s">here</a>.'), 'res/' . (($hexmatch['parent'] == TINYIB_NEWTHREAD) ? $hexmatch['id'] : $hexmatch['parent'])) . '.html#' . $hexmatch['id']);
|
fancyDie(sprintf(__('Duplicate file uploaded. That file has already been posted <a href="%s">here</a>.'), 'res/' . (($hexmatch['parent'] == TINYIB_NEWTHREAD) ? $hexmatch['id'] : $hexmatch['parent'])) . '.html#' . $hexmatch['id']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -467,7 +467,7 @@ function createThumbnail($file_location, $thumb_location, $new_w, $new_h) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!$src_img) {
|
if (!$src_img) {
|
||||||
fancyDie(_('Unable to read the uploaded file while creating its thumbnail. A common cause for this is an incorrect extension when the file is actually of a different type.'));
|
fancyDie(__('Unable to read the uploaded file while creating its thumbnail. A common cause for this is an incorrect extension when the file is actually of a different type.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$old_x = imageSX($src_img);
|
$old_x = imageSX($src_img);
|
||||||
|
172
inc/gettext/CHANGELOG.md
Normal file
172
inc/gettext/CHANGELOG.md
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
# Change Log
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
Previous releases are documented in [github releases](https://github.com/oscarotero/Gettext/releases)
|
||||||
|
|
||||||
|
## [4.8.2] - 2019-12-02
|
||||||
|
### Fixed
|
||||||
|
- UTF-8 handling for VueJs extractor [#242]
|
||||||
|
|
||||||
|
## [4.8.1] - 2019-11-15
|
||||||
|
### Fixed
|
||||||
|
- Php error when scanning for a single domain but other string found [#238]
|
||||||
|
|
||||||
|
## [4.8.0] - 2019-11-04
|
||||||
|
### Changed
|
||||||
|
- Many `private` properties and methods were changed to `protected` in order to improve the extensibility [#231]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- PHP 7.4 support [#230]
|
||||||
|
|
||||||
|
## [4.7.0] - 2019-10-07
|
||||||
|
### Added
|
||||||
|
- Support for UnitID in Xliff [#221] [#224] [#225]
|
||||||
|
- Support for scan multiple domains at the same time [#223]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- New lines in windows [#218] [#226]
|
||||||
|
|
||||||
|
## [4.6.3] - 2019-07-15
|
||||||
|
### Added
|
||||||
|
- Some VueJs extraction improvements and additions [#205], [#213]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Multiline extractions in jsCode [#200]
|
||||||
|
- Support for js template literals [#214]
|
||||||
|
- Fixed tabs in PHP comments [#215]
|
||||||
|
|
||||||
|
## [4.6.2] - 2019-01-12
|
||||||
|
### Added
|
||||||
|
- New option `facade` in blade extractor to use a facade instead create a blade compiler [#197], [#198]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Added php-7.3 to travis
|
||||||
|
- Added VueJS extractor method docblocks for IDEs [#191]
|
||||||
|
|
||||||
|
## [4.6.1] - 2018-08-27
|
||||||
|
### Fixed
|
||||||
|
- VueJS DOM parsing [#188]
|
||||||
|
- Javascript parser was unable to extract some functions [#187]
|
||||||
|
|
||||||
|
## [4.6.0] - 2018-06-26
|
||||||
|
### Added
|
||||||
|
- New extractor for VueJs [#178]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Do not include empty translations containing the headers in the translator [#182]
|
||||||
|
- Test enhancement [#177]
|
||||||
|
|
||||||
|
## [4.5.0] - 2018-04-23
|
||||||
|
### Added
|
||||||
|
- Support for disabled translations
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Added php-7.2 to travis
|
||||||
|
- Fixed po tests on bigendian [#159]
|
||||||
|
- Improved comment estraction [#166]
|
||||||
|
- Fixed incorrect docs to dn__ function [#170]
|
||||||
|
- Ignored phpcs.xml file on export [#168]
|
||||||
|
- Improved `@method` docs in `Translations` [#175]
|
||||||
|
|
||||||
|
## [4.4.4] - 2018-02-21
|
||||||
|
### Fixed
|
||||||
|
- Changed the comment extraction to be compatible with gettext behaviour: the comment must be placed in the line preceding the function [#161]
|
||||||
|
|
||||||
|
### Security
|
||||||
|
- Validate eval input from plural forms [#156]
|
||||||
|
|
||||||
|
## [4.4.3] - 2017-08-09
|
||||||
|
### Fixed
|
||||||
|
- Handle `NULL` arguments on extract entries in php. For example `dn__(null, 'singular', 'plural')`.
|
||||||
|
- Fixed the `PhpCode` and `JsCode` extractors that didn't extract `dn__` and `dngettext` entries [#155].
|
||||||
|
- Fixed the `PhpCode` and `JsCode` extractors that didn't extract `dnpgettext` correctly.
|
||||||
|
|
||||||
|
## [4.4.2] - 2017-07-27
|
||||||
|
### Fixed
|
||||||
|
- Clone the translations in `Translations::mergeWith` to prevent that the translation is referenced in both places. [#152]
|
||||||
|
- Fixed escaped quotes in the javascript extractor [#154]
|
||||||
|
|
||||||
|
## [4.4.1] - 2017-05-20
|
||||||
|
### Fixed
|
||||||
|
- Fixed a bug where the options was not passed correctly to the merging Translations object [#147]
|
||||||
|
- Unified the plural behaviours between PHP gettext and Translator when the plural translation is unknown [#148]
|
||||||
|
- Removed the deprecated function `create_function()` and use `eval()` instead
|
||||||
|
|
||||||
|
## [4.4.0] - 2017-05-10
|
||||||
|
### Added
|
||||||
|
- New option `noLocation` to po generator, to omit the references [#143]
|
||||||
|
- New options `delimiter`, `enclosure` and `escape_char` to Csv and CsvDictionary extractors and generators [#145]
|
||||||
|
- Added the missing `dn__()` function [#146]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Improved the code style including php_codesniffer in development
|
||||||
|
|
||||||
|
## 4.3.0 - 2017-03-04
|
||||||
|
### Added
|
||||||
|
- Added support for named placeholders (using `strtr`). For example:
|
||||||
|
```php
|
||||||
|
__('Hello :name', [':name' => 'World']);
|
||||||
|
```
|
||||||
|
- Added support for Twig v2
|
||||||
|
- New function `BaseTranslator::includeFunctions()` to include the functions file without register any translator
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed a bug related with the javascript source extraction with single quotes
|
||||||
|
|
||||||
|
[#143]: https://github.com/oscarotero/Gettext/issues/143
|
||||||
|
[#145]: https://github.com/oscarotero/Gettext/issues/145
|
||||||
|
[#146]: https://github.com/oscarotero/Gettext/issues/146
|
||||||
|
[#147]: https://github.com/oscarotero/Gettext/issues/147
|
||||||
|
[#148]: https://github.com/oscarotero/Gettext/issues/148
|
||||||
|
[#152]: https://github.com/oscarotero/Gettext/issues/152
|
||||||
|
[#154]: https://github.com/oscarotero/Gettext/issues/154
|
||||||
|
[#155]: https://github.com/oscarotero/Gettext/issues/155
|
||||||
|
[#156]: https://github.com/oscarotero/Gettext/issues/156
|
||||||
|
[#159]: https://github.com/oscarotero/Gettext/issues/159
|
||||||
|
[#161]: https://github.com/oscarotero/Gettext/issues/161
|
||||||
|
[#166]: https://github.com/oscarotero/Gettext/issues/166
|
||||||
|
[#168]: https://github.com/oscarotero/Gettext/issues/168
|
||||||
|
[#170]: https://github.com/oscarotero/Gettext/issues/170
|
||||||
|
[#175]: https://github.com/oscarotero/Gettext/issues/175
|
||||||
|
[#177]: https://github.com/oscarotero/Gettext/issues/177
|
||||||
|
[#178]: https://github.com/oscarotero/Gettext/issues/178
|
||||||
|
[#182]: https://github.com/oscarotero/Gettext/issues/182
|
||||||
|
[#187]: https://github.com/oscarotero/Gettext/issues/187
|
||||||
|
[#188]: https://github.com/oscarotero/Gettext/issues/188
|
||||||
|
[#191]: https://github.com/oscarotero/Gettext/issues/191
|
||||||
|
[#197]: https://github.com/oscarotero/Gettext/issues/197
|
||||||
|
[#198]: https://github.com/oscarotero/Gettext/issues/198
|
||||||
|
[#200]: https://github.com/oscarotero/Gettext/issues/200
|
||||||
|
[#205]: https://github.com/oscarotero/Gettext/issues/205
|
||||||
|
[#213]: https://github.com/oscarotero/Gettext/issues/213
|
||||||
|
[#214]: https://github.com/oscarotero/Gettext/issues/214
|
||||||
|
[#215]: https://github.com/oscarotero/Gettext/issues/215
|
||||||
|
[#218]: https://github.com/oscarotero/Gettext/issues/218
|
||||||
|
[#221]: https://github.com/oscarotero/Gettext/issues/221
|
||||||
|
[#223]: https://github.com/oscarotero/Gettext/issues/223
|
||||||
|
[#224]: https://github.com/oscarotero/Gettext/issues/224
|
||||||
|
[#225]: https://github.com/oscarotero/Gettext/issues/225
|
||||||
|
[#226]: https://github.com/oscarotero/Gettext/issues/226
|
||||||
|
[#230]: https://github.com/oscarotero/Gettext/issues/230
|
||||||
|
[#231]: https://github.com/oscarotero/Gettext/issues/231
|
||||||
|
[#238]: https://github.com/oscarotero/Gettext/issues/238
|
||||||
|
[#242]: https://github.com/oscarotero/Gettext/issues/242
|
||||||
|
|
||||||
|
[4.8.2]: https://github.com/oscarotero/Gettext/compare/v4.8.1...v4.8.2
|
||||||
|
[4.8.1]: https://github.com/oscarotero/Gettext/compare/v4.8.0...v4.8.1
|
||||||
|
[4.8.0]: https://github.com/oscarotero/Gettext/compare/v4.7.0...v4.8.0
|
||||||
|
[4.7.0]: https://github.com/oscarotero/Gettext/compare/v4.6.3...v4.7.0
|
||||||
|
[4.6.3]: https://github.com/oscarotero/Gettext/compare/v4.6.2...v4.6.3
|
||||||
|
[4.6.2]: https://github.com/oscarotero/Gettext/compare/v4.6.1...v4.6.2
|
||||||
|
[4.6.1]: https://github.com/oscarotero/Gettext/compare/v4.6.0...v4.6.1
|
||||||
|
[4.6.0]: https://github.com/oscarotero/Gettext/compare/v4.5.0...v4.6.0
|
||||||
|
[4.5.0]: https://github.com/oscarotero/Gettext/compare/v4.4.4...v4.5.0
|
||||||
|
[4.4.4]: https://github.com/oscarotero/Gettext/compare/v4.4.3...v4.4.4
|
||||||
|
[4.4.3]: https://github.com/oscarotero/Gettext/compare/v4.4.2...v4.4.3
|
||||||
|
[4.4.2]: https://github.com/oscarotero/Gettext/compare/v4.4.1...v4.4.2
|
||||||
|
[4.4.1]: https://github.com/oscarotero/Gettext/compare/v4.4.0...v4.4.1
|
||||||
|
[4.4.0]: https://github.com/oscarotero/Gettext/compare/v4.3.0...v4.4.0
|
17
inc/gettext/CONTRIBUTING.md
Normal file
17
inc/gettext/CONTRIBUTING.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
Contributing to Gettext
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Looking to contribute something to this library? Here's how you can help.
|
||||||
|
|
||||||
|
## Bugs
|
||||||
|
|
||||||
|
A bug is a demonstrable problem that is caused by the code in the repository. Good bug reports are extremely helpful – thank you!
|
||||||
|
|
||||||
|
Please try to be as detailed as possible in your report. Include specific information about the environment – version of PHP, version of gettext, etc, and steps required to reproduce the issue.
|
||||||
|
|
||||||
|
## Pull Requests
|
||||||
|
|
||||||
|
Good pull requests – patches, improvements, new features – are a fantastic help. New extractors or generator are welcome. Before create a pull request, please follow these instructions:
|
||||||
|
|
||||||
|
* The code must be PSR-2 compliant
|
||||||
|
* Write some tests
|
21
inc/gettext/LICENSE
Normal file
21
inc/gettext/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2017 Oscar Otero Marzoa
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
425
inc/gettext/README.md
Normal file
425
inc/gettext/README.md
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
Gettext
|
||||||
|
=======
|
||||||
|
|
||||||
|
[](https://travis-ci.org/oscarotero/Gettext)
|
||||||
|
[](https://scrutinizer-ci.com/g/oscarotero/Gettext/?branch=master)
|
||||||
|
[](https://packagist.org/packages/gettext/gettext)
|
||||||
|
[](https://packagist.org/packages/gettext/gettext)
|
||||||
|
[](https://packagist.org/packages/gettext/gettext)
|
||||||
|
[](https://packagist.org/packages/gettext/gettext)
|
||||||
|
|
||||||
|
[](https://insight.sensiolabs.com/projects/496dc2a6-43be-4046-a283-f8370239dd47)
|
||||||
|
|
||||||
|
Created by Oscar Otero <http://oscarotero.com> <oom@oscarotero.com> (MIT License)
|
||||||
|
|
||||||
|
Gettext is a PHP (>=5.4) library to import/export/edit gettext from PO, MO, PHP, JS files, etc.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
With composer (recomended):
|
||||||
|
|
||||||
|
```
|
||||||
|
composer require gettext/gettext
|
||||||
|
```
|
||||||
|
|
||||||
|
If you don't use composer in your project, you have to download and place this package in a directory of your project. You need to install also [gettext/languages](https://github.com/mlocati/cldr-to-gettext-plural-rules). Then, include the autoloaders of both projects in any place of your php code:
|
||||||
|
|
||||||
|
```php
|
||||||
|
include_once "libs/gettext/src/autoloader.php";
|
||||||
|
include_once "libs/cldr-to-gettext-plural-rules/src/autoloader.php";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Classes and functions
|
||||||
|
|
||||||
|
This package contains the following classes:
|
||||||
|
|
||||||
|
* `Gettext\Translation` - A translation definition
|
||||||
|
* `Gettext\Translations` - A collection of translations
|
||||||
|
* `Gettext\Extractors\*` - Import translations from various sources (po, mo, php, js, etc)
|
||||||
|
* `Gettext\Generators\*` - Export translations to various formats (po, mo, php, json, etc)
|
||||||
|
* `Gettext\Translator` - To use the translations in your php templates instead the [gettext extension](http://php.net/gettext)
|
||||||
|
* `Gettext\GettextTranslator` - To use the [gettext extension](http://php.net/gettext)
|
||||||
|
|
||||||
|
## Usage example
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
//import from a .po file:
|
||||||
|
$translations = Translations::fromPoFile('locales/gl.po');
|
||||||
|
|
||||||
|
//edit some translations:
|
||||||
|
$translation = $translations->find(null, 'apple');
|
||||||
|
|
||||||
|
if ($translation) {
|
||||||
|
$translation->setTranslation('Mazá');
|
||||||
|
}
|
||||||
|
|
||||||
|
//export to a php array:
|
||||||
|
$translations->toPhpArrayFile('locales/gl.php');
|
||||||
|
|
||||||
|
//and to a .mo file
|
||||||
|
$translations->toMoFile('Locale/gl/LC_MESSAGES/messages.mo');
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want use this translations in your php templates without using the gettext extension:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Gettext\Translator;
|
||||||
|
|
||||||
|
//Create the translator instance
|
||||||
|
$t = new Translator();
|
||||||
|
|
||||||
|
//Load your translations (exported as PhpArray):
|
||||||
|
$t->loadTranslations('locales/gl.php');
|
||||||
|
|
||||||
|
//Use it:
|
||||||
|
echo $t->gettext('apple'); // "Mazá"
|
||||||
|
|
||||||
|
//If you want use global functions:
|
||||||
|
$t->register();
|
||||||
|
|
||||||
|
echo __('apple'); // "Mazá"
|
||||||
|
```
|
||||||
|
|
||||||
|
To use this translations with the gettext extension:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Gettext\GettextTranslator;
|
||||||
|
|
||||||
|
//Create the translator instance
|
||||||
|
$t = new GettextTranslator();
|
||||||
|
|
||||||
|
//Set the language and load the domain
|
||||||
|
$t->setLanguage('gl');
|
||||||
|
$t->loadDomain('messages', 'Locale');
|
||||||
|
|
||||||
|
//Use it:
|
||||||
|
echo $t->gettext('apple'); // "Mazá"
|
||||||
|
|
||||||
|
//Or use the gettext functions
|
||||||
|
echo gettext('apple'); // "Mazá"
|
||||||
|
|
||||||
|
//If you want use the global functions
|
||||||
|
$t->register();
|
||||||
|
|
||||||
|
echo __('apple'); // "Mazá"
|
||||||
|
|
||||||
|
//And use sprintf/strtr placeholders
|
||||||
|
echo __('Hello %s', 'world'); //Hello world
|
||||||
|
echo __('Hello {name}', ['{name}' => 'world']); //Hello world
|
||||||
|
```
|
||||||
|
|
||||||
|
The benefits of using the functions provided by this library (`__()` instead `_()` or `gettext()`) are:
|
||||||
|
|
||||||
|
* You are using the same functions, no matter whether the translations are provided by gettext extension or any other method.
|
||||||
|
* You can use variables easier because `sprintf` functionality is included. For example: `__('Hello %s', 'world')` instead `sprintf(_('Hello %s'), 'world')`.
|
||||||
|
* You can also use named placeholders if the second argument is an array. For example: `__('Hello %name%', ['%name%' => 'world'])` instead of `strtr(_('Hello %name%'), ['%name%' => 'world'])`.
|
||||||
|
|
||||||
|
## Translation
|
||||||
|
|
||||||
|
The `Gettext\Translation` class stores all information about a translation: the original text, the translated text, source references, comments, etc.
|
||||||
|
|
||||||
|
```php
|
||||||
|
// __construct($context, $original, $plural)
|
||||||
|
$translation = new Gettext\Translation('comments', 'One comment', '%s comments');
|
||||||
|
|
||||||
|
$translation->setTranslation('Un comentario');
|
||||||
|
$translation->setPluralTranslation('%s comentarios');
|
||||||
|
|
||||||
|
$translation->addReference('templates/comments/comment.php', 34);
|
||||||
|
$translation->addComment('To display the amount of comments in a post');
|
||||||
|
|
||||||
|
echo $translation->getContext(); // comments
|
||||||
|
echo $translation->getOriginal(); // One comment
|
||||||
|
echo $translation->getTranslation(); // Un comentario
|
||||||
|
|
||||||
|
// etc...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
The `Gettext\Translations` class stores a collection of translations:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$translations = new Gettext\Translations();
|
||||||
|
|
||||||
|
//You can add new translations using the array syntax
|
||||||
|
$translations[] = new Gettext\Translation('comments', 'One comment', '%s comments');
|
||||||
|
|
||||||
|
//Or using the "insert" method
|
||||||
|
$insertedTranslation = $translations->insert('comments', 'One comment', '%s comments');
|
||||||
|
|
||||||
|
//Find a specific translation
|
||||||
|
$translation = $translations->find('comments', 'One comment');
|
||||||
|
|
||||||
|
//Edit headers, domain, etc
|
||||||
|
$translations->setHeader('Last-Translator', 'Oscar Otero');
|
||||||
|
$translations->setDomain('my-blog');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Extractors
|
||||||
|
|
||||||
|
The extrators allows to fetch gettext values from any source. For example, to scan a .po file:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$translations = new Gettext\Translations();
|
||||||
|
|
||||||
|
//From a file
|
||||||
|
Gettext\Extractors\Po::fromFile('locales/en.po', $translations);
|
||||||
|
|
||||||
|
//From a string
|
||||||
|
$string = file_get_contents('locales2/en.po');
|
||||||
|
Gettext\Extractors\Po::fromString($string, $translations);
|
||||||
|
```
|
||||||
|
|
||||||
|
The better way to use extractors is using the magic methods of `Gettext\Translations`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
//Create a Translations instance using a po file
|
||||||
|
$translations = Gettext\Translations::fromPoFile('locales/en.po');
|
||||||
|
|
||||||
|
//Add more messages from other files
|
||||||
|
$translations->addFromPoFile('locales2/en.po');
|
||||||
|
```
|
||||||
|
|
||||||
|
The available extractors are the following:
|
||||||
|
|
||||||
|
Name | Description | Example
|
||||||
|
---- | ----------- | --------
|
||||||
|
**Blade** | Scans a Blade template (For laravel users). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/blade/input.php)
|
||||||
|
**Csv** | Gets the messages from csv. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Csv.csv)
|
||||||
|
**CsvDictionary** | Gets the messages from csv (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/CsvDictionary.csv)
|
||||||
|
**Jed** | Gets the messages from a json compatible with [Jed](http://slexaxton.github.com/Jed/). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Jed.json)
|
||||||
|
**JsCode** | Scans javascript code looking for all gettext functions (the same than PhpCode but for javascript). You can use [the javascript gettext-translator library](https://github.com/oscarotero/gettext-translator) | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/jscode/input.js)
|
||||||
|
**Json** | Gets the messages from json compatible with [gettext-translator](https://github.com/oscarotero/gettext-translator). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Json.json)
|
||||||
|
**JsonDictionary** | Gets the messages from a json (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/JsonDictionary.json)
|
||||||
|
**Mo** | Gets the messages from MO. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Mo.mo)
|
||||||
|
**PhpArray** | Gets the messages from a php file that returns an array. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/PhpArray.php)
|
||||||
|
**PhpCode** | Scans php code looking for all gettext functions (see `translator_functions.php`). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/phpcode/input.php)
|
||||||
|
**Po** | Gets the messages from PO. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Po.po)
|
||||||
|
**Twig** | To scan a Twig template. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/twig/input.php)
|
||||||
|
**Xliff** | Gets the messages from [xliff (2.0)](http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Xliff.xlf)
|
||||||
|
**Yaml** | Gets the messages from yaml. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Yaml.yml)
|
||||||
|
**YamlDictionary** | Gets the messages from a yaml (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/YamlDictionary.yml)
|
||||||
|
**VueJs** | Gets the messages from a VueJs template. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/vuejs/input.vue)
|
||||||
|
|
||||||
|
## Generators
|
||||||
|
|
||||||
|
The generators export a `Gettext\Translations` instance to any format (po, mo, array, etc).
|
||||||
|
|
||||||
|
```php
|
||||||
|
//Save to a file
|
||||||
|
Gettext\Generators\Po::toFile($translations, 'locales/en.po');
|
||||||
|
|
||||||
|
//Return as a string
|
||||||
|
$content = Gettext\Generators\Po::toString($translations);
|
||||||
|
file_put_contents('locales/en.po', $content);
|
||||||
|
```
|
||||||
|
|
||||||
|
Like extractors, the better way to use generators is using the magic methods of `Gettext\Translations`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
//Extract messages from a php code file
|
||||||
|
$translations = Gettext\Translations::fromPhpCodeFile('templates/index.php');
|
||||||
|
|
||||||
|
//Export to a po file
|
||||||
|
$translations->toPoFile('locales/en.po');
|
||||||
|
|
||||||
|
//Export to a po string
|
||||||
|
$content = $translations->toPoString();
|
||||||
|
file_put_contents('locales/en.po', $content);
|
||||||
|
```
|
||||||
|
|
||||||
|
The available generators are the following:
|
||||||
|
|
||||||
|
Name | Description | Example
|
||||||
|
---- | ----------- | --------
|
||||||
|
**Csv** | Exports to csv. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Csv.csv)
|
||||||
|
**CsvDictionary** | Exports to csv (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/CsvDictionary.csv)
|
||||||
|
**Json** | Exports to json, compatible with [gettext-translator](https://github.com/oscarotero/gettext-translator). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Json.json)
|
||||||
|
**JsonDictionary** | Exports to json (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/JsonDictionary.json)
|
||||||
|
**Mo** | Exports to Mo. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Mo.mo)
|
||||||
|
**PhpArray** | Exports to php code that returns an array. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/PhpArray.php)
|
||||||
|
**Po** | Exports to Po. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Po.po)
|
||||||
|
**Jed** | Exports to json format compatible with [Jed](http://slexaxton.github.com/Jed/). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Jed.json)
|
||||||
|
**Xliff** | Exports to [xliff (2.0)](http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/xliff-core-v2.0-os.html). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Xliff.xlf)
|
||||||
|
**Yaml** | Exports to yaml. | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/Yaml.yml)
|
||||||
|
**YamlDictionary** | Exports to yaml (without plurals and context). | [example](https://github.com/oscarotero/Gettext/blob/master/tests/assets/po/YamlDictionary.yml)
|
||||||
|
|
||||||
|
## Translator
|
||||||
|
|
||||||
|
The class `Gettext\Translator` implements the gettext functions in php. Useful if you don't have the native gettext extension for php or want to avoid problems with it. You can load the translations from a php array file or using a `Gettext\Translations` instance:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Gettext\Translator;
|
||||||
|
|
||||||
|
//Create a new instance of the translator
|
||||||
|
$t = new Translator();
|
||||||
|
|
||||||
|
//Load the translations using any of the following ways:
|
||||||
|
|
||||||
|
// 1. from php files (generated by Gettext\Extractors\PhpArray)
|
||||||
|
$t->loadTranslations('locales/gl.php');
|
||||||
|
|
||||||
|
// 2. using the array directly
|
||||||
|
$array = include 'locales/gl.php';
|
||||||
|
$t->loadTranslations($array);
|
||||||
|
|
||||||
|
// 3. using a Gettext\Translations instance (slower)
|
||||||
|
$translations = Gettext\Translations::fromPoFile('locales/gl.po');
|
||||||
|
$t->loadTranslations($translations);
|
||||||
|
|
||||||
|
//Now you can use it in your templates
|
||||||
|
echo $t->gettext('apple');
|
||||||
|
```
|
||||||
|
|
||||||
|
## GettextTranslator
|
||||||
|
|
||||||
|
The class `Gettext\GettextTranslator` uses the gettext extension. It's useful because combines the performance of using real gettext functions but with the same API than `Translator` class, so you can switch to one or other translator deppending of the environment without change code of your app.
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Gettext\GettextTranslator;
|
||||||
|
|
||||||
|
//Create a new instance
|
||||||
|
$t = new GettextTranslator();
|
||||||
|
|
||||||
|
//It detects the environment variables to set the locale, but you can change it:
|
||||||
|
$t->setLanguage('gl');
|
||||||
|
|
||||||
|
//Load the domains:
|
||||||
|
$t->loadDomain('messages', 'project/Locale');
|
||||||
|
//this means you have the file "project/Locale/gl/LC_MESSAGES/messages.po"
|
||||||
|
|
||||||
|
//Now you can use it in your templates
|
||||||
|
echo $t->gettext('apple');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Global functions
|
||||||
|
|
||||||
|
To ease the use of translations in your php templates, you can use the provided functions:
|
||||||
|
|
||||||
|
```php
|
||||||
|
//Register the translator to use the global functions
|
||||||
|
$t->register();
|
||||||
|
|
||||||
|
echo __('apple'); // it's the same than $t->gettext('apple');
|
||||||
|
```
|
||||||
|
|
||||||
|
You can scan the php files containing these functions and extract the values with the PhpCode extractor:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- index.php -->
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<?= __('Hello world'); ?>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Merge translations
|
||||||
|
|
||||||
|
To work with different translations you may want merge them in an unique file. There are two ways to do this:
|
||||||
|
|
||||||
|
The simplest way is adding new translations:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
$translations = Translations::fromPoFile('my-file1.po');
|
||||||
|
$translations->addFromPoFile('my-file2.po');
|
||||||
|
```
|
||||||
|
|
||||||
|
A more advanced way is merge two `Translations` instances:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
//Create a new Translations instances with our translations.
|
||||||
|
|
||||||
|
$translations1 = Translations::fromPoFile('my-file1.po');
|
||||||
|
$translations2 = Translations::fromPoFile('my-file2.po');
|
||||||
|
|
||||||
|
//Merge one inside other:
|
||||||
|
$translations1->mergeWith($translations2);
|
||||||
|
|
||||||
|
//Now translations1 has all values
|
||||||
|
```
|
||||||
|
|
||||||
|
The second argument of `mergeWith` defines how the merge will be done. Use the `Gettext\Merge` constants to configure the merging:
|
||||||
|
|
||||||
|
Constant | Description
|
||||||
|
--------- | -----------
|
||||||
|
`Merge::ADD` | Adds the translations from `$translations2` that are missing
|
||||||
|
`Merge::REMOVE` | Removes the translations missing in `$translations2`
|
||||||
|
`Merge::HEADERS_ADD` | Adds the headers from `$translations2` that are missing
|
||||||
|
`Merge::HEADERS_REMOVE` | Removes the headers missing in `$translations2`
|
||||||
|
`Merge::HEADERS_OVERRIDE` | Overrides the headers with the values of `$translations2`
|
||||||
|
`Merge::LANGUAGE_OVERRIDE` | Set the language defined in `$translations2`
|
||||||
|
`Merge::DOMAIN_OVERRIDE` | Set the domain defined in `$translations2`
|
||||||
|
`Merge::TRANSLATION_OVERRIDE` | Override the translation and plural translations with the value of `$translation2`
|
||||||
|
`Merge::COMMENTS_OURS` | Use only the comments of `$translation1`
|
||||||
|
`Merge::COMMENTS_THEIRS` | Use only the comments of `$translation2`
|
||||||
|
`Merge::EXTRACTED_COMMENTS_OURS` | Use only the extracted comments of `$translation1`
|
||||||
|
`Merge::EXTRACTED_COMMENTS_THEIRS` | Use only the extracted comments of `$translation2`
|
||||||
|
`Merge::FLAGS_OURS` | Use only the flags of `$translation1`
|
||||||
|
`Merge::FLAGS_THEIRS` | Use only the flags of `$translation2`
|
||||||
|
`Merge::REFERENCES_OURS` | Use only the references of `$translation1`
|
||||||
|
`Merge::REFERENCES_THEIRS` | Use only the references of `$translation2`
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```php
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Merge;
|
||||||
|
|
||||||
|
//Scan the php code to find the latest gettext translations
|
||||||
|
$phpTranslations = Translations::fromPhpCodeFile('my-templates.php');
|
||||||
|
|
||||||
|
//Get the translations of the code that are stored in a po file
|
||||||
|
$poTranslations = Translations::fromPoFile('locale.po');
|
||||||
|
|
||||||
|
//Merge the translations from the po file using the references from `$phpTranslations`:
|
||||||
|
$translations->mergeWith($poTranslations, Merge::REFERENCES_OURS);
|
||||||
|
|
||||||
|
//Now save a po file with the result
|
||||||
|
$translations->toPoFile('locale.po');
|
||||||
|
```
|
||||||
|
|
||||||
|
Note, if the second argument is not defined, the default value is `Merge::DEFAULTS` that's equivalent to `Merge::ADD | Merge::HEADERS_ADD`.
|
||||||
|
|
||||||
|
## Use from CLI
|
||||||
|
|
||||||
|
There's a Robo task to use this library from the command line interface: https://github.com/oscarotero/GettextRobo
|
||||||
|
|
||||||
|
## Use in the browser
|
||||||
|
|
||||||
|
If you want to use your translations in the browser, there's a javascript translator: https://github.com/oscarotero/gettext-translator
|
||||||
|
|
||||||
|
## Third party packages
|
||||||
|
|
||||||
|
Twig integration:
|
||||||
|
|
||||||
|
* [jaimeperez/twig-configurable-i18n](https://packagist.org/packages/jaimeperez/twig-configurable-i18n)
|
||||||
|
* [cemerson/translator-twig-extension](https://packagist.org/packages/cemerson/translator-twig-extension)
|
||||||
|
|
||||||
|
Framework integration:
|
||||||
|
|
||||||
|
* [Laravel 5](https://packagist.org/packages/eusonlito/laravel-gettext)
|
||||||
|
* [CakePHP 3](https://packagist.org/packages/k1low/po)
|
||||||
|
* [Symfony 2](https://packagist.org/packages/mablae/gettext-bundle)
|
||||||
|
|
||||||
|
[add your package](https://github.com/oscarotero/Gettext/issues/new)
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
Thanks to all [contributors](https://github.com/oscarotero/Gettext/graphs/contributors) specially to [@mlocati](https://github.com/mlocati).
|
||||||
|
|
||||||
|
## Donations
|
||||||
|
|
||||||
|
If this library is useful for you, consider to donate to the author.
|
||||||
|
|
||||||
|
[Buy me a beer :beer:](https://www.paypal.me/oscarotero)
|
||||||
|
|
||||||
|
Thanks in advance!
|
54
inc/gettext/composer.json
Normal file
54
inc/gettext/composer.json
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"name": "gettext/gettext",
|
||||||
|
"type": "library",
|
||||||
|
"description": "PHP gettext manager",
|
||||||
|
"keywords": ["js", "gettext", "i18n", "translation", "po", "mo"],
|
||||||
|
"homepage": "https://github.com/oscarotero/Gettext",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Oscar Otero",
|
||||||
|
"email": "oom@oscarotero.com",
|
||||||
|
"homepage": "http://oscarotero.com",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"email": "oom@oscarotero.com",
|
||||||
|
"issues": "https://github.com/oscarotero/Gettext/issues"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.4.0",
|
||||||
|
"gettext/languages": "^2.3"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/view": "*",
|
||||||
|
"twig/twig": "^1.31|^2.0",
|
||||||
|
"twig/extensions": "*",
|
||||||
|
"symfony/yaml": "~2",
|
||||||
|
"phpunit/phpunit": "^4.8|^5.7|^6.5",
|
||||||
|
"squizlabs/php_codesniffer": "^3.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"illuminate/view": "Is necessary if you want to use the Blade extractor",
|
||||||
|
"twig/twig": "Is necessary if you want to use the Twig extractor",
|
||||||
|
"twig/extensions": "Is necessary if you want to use the Twig extractor",
|
||||||
|
"symfony/yaml": "Is necessary if you want to use the Yaml extractor/generator"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Gettext\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Gettext\\Tests\\": "tests"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": [
|
||||||
|
"phpunit",
|
||||||
|
"phpcs"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
39
inc/gettext/src/BaseTranslator.php
Normal file
39
inc/gettext/src/BaseTranslator.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext;
|
||||||
|
|
||||||
|
abstract class BaseTranslator implements TranslatorInterface
|
||||||
|
{
|
||||||
|
/** @var TranslatorInterface */
|
||||||
|
public static $current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*/
|
||||||
|
public function noop($original)
|
||||||
|
{
|
||||||
|
return $original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*/
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$previous = static::$current;
|
||||||
|
|
||||||
|
static::$current = $this;
|
||||||
|
|
||||||
|
static::includeFunctions();
|
||||||
|
|
||||||
|
return $previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Include the gettext functions
|
||||||
|
*/
|
||||||
|
public static function includeFunctions()
|
||||||
|
{
|
||||||
|
include_once __DIR__.'/translator_functions.php';
|
||||||
|
}
|
||||||
|
}
|
29
inc/gettext/src/Extractors/Blade.php
Normal file
29
inc/gettext/src/Extractors/Blade.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Illuminate\Filesystem\Filesystem;
|
||||||
|
use Illuminate\View\Compilers\BladeCompiler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from blade.php files returning arrays.
|
||||||
|
*/
|
||||||
|
class Blade extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
if (empty($options['facade'])) {
|
||||||
|
$cachePath = empty($options['cachePath']) ? sys_get_temp_dir() : $options['cachePath'];
|
||||||
|
$bladeCompiler = new BladeCompiler(new Filesystem(), $cachePath);
|
||||||
|
$string = $bladeCompiler->compileString($string);
|
||||||
|
} else {
|
||||||
|
$string = $options['facade']::compileString($string);
|
||||||
|
}
|
||||||
|
|
||||||
|
PhpCode::fromString($string, $translations, $options);
|
||||||
|
}
|
||||||
|
}
|
53
inc/gettext/src/Extractors/Csv.php
Normal file
53
inc/gettext/src/Extractors/Csv.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\HeadersExtractorTrait;
|
||||||
|
use Gettext\Utils\CsvTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from csv.
|
||||||
|
*/
|
||||||
|
class Csv extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
use HeadersExtractorTrait;
|
||||||
|
use CsvTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'delimiter' => ",",
|
||||||
|
'enclosure' => '"',
|
||||||
|
'escape_char' => "\\"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
$handle = fopen('php://memory', 'w');
|
||||||
|
|
||||||
|
fputs($handle, $string);
|
||||||
|
rewind($handle);
|
||||||
|
|
||||||
|
while ($row = static::fgetcsv($handle, $options)) {
|
||||||
|
$context = array_shift($row);
|
||||||
|
$original = array_shift($row);
|
||||||
|
|
||||||
|
if ($context === '' && $original === '') {
|
||||||
|
static::extractHeaders(array_shift($row), $translations);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation = $translations->insert($context, $original);
|
||||||
|
|
||||||
|
if (!empty($row)) {
|
||||||
|
$translation->setTranslation(array_shift($row));
|
||||||
|
$translation->setPluralTranslations($row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
}
|
47
inc/gettext/src/Extractors/CsvDictionary.php
Normal file
47
inc/gettext/src/Extractors/CsvDictionary.php
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\HeadersExtractorTrait;
|
||||||
|
use Gettext\Utils\CsvTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from csv.
|
||||||
|
*/
|
||||||
|
class CsvDictionary extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
use HeadersExtractorTrait;
|
||||||
|
use CsvTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'delimiter' => ",",
|
||||||
|
'enclosure' => '"',
|
||||||
|
'escape_char' => "\\"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
$handle = fopen('php://memory', 'w');
|
||||||
|
|
||||||
|
fputs($handle, $string);
|
||||||
|
rewind($handle);
|
||||||
|
|
||||||
|
while ($row = static::fgetcsv($handle, $options)) {
|
||||||
|
list($original, $translation) = $row + ['', ''];
|
||||||
|
|
||||||
|
if ($original === '') {
|
||||||
|
static::extractHeaders($translation, $translations);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations->insert(null, $original)->setTranslation($translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
}
|
80
inc/gettext/src/Extractors/Extractor.php
Normal file
80
inc/gettext/src/Extractors/Extractor.php
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
abstract class Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromFile($file, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
foreach (static::getFiles($file) as $file) {
|
||||||
|
$options['file'] = $file;
|
||||||
|
static::fromString(static::readFile($file), $translations, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks and returns all files.
|
||||||
|
*
|
||||||
|
* @param string|array $file The file/s
|
||||||
|
*
|
||||||
|
* @return array The file paths
|
||||||
|
*/
|
||||||
|
protected static function getFiles($file)
|
||||||
|
{
|
||||||
|
if (empty($file)) {
|
||||||
|
throw new InvalidArgumentException('There is not any file defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($file)) {
|
||||||
|
if (!is_file($file)) {
|
||||||
|
throw new InvalidArgumentException("'$file' is not a valid file");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_readable($file)) {
|
||||||
|
throw new InvalidArgumentException("'$file' is not a readable file");
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$file];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($file)) {
|
||||||
|
$files = [];
|
||||||
|
|
||||||
|
foreach ($file as $f) {
|
||||||
|
$files = array_merge($files, static::getFiles($f));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $files;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException('The first argument must be string or array');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads and returns the content of a file.
|
||||||
|
*
|
||||||
|
* @param string $file
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function readFile($file)
|
||||||
|
{
|
||||||
|
$length = filesize($file);
|
||||||
|
|
||||||
|
if (!($fd = fopen($file, 'rb'))) {
|
||||||
|
throw new Exception("Cannot read the file '$file', probably permissions");
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = $length ? fread($fd, $length) : '';
|
||||||
|
fclose($fd);
|
||||||
|
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
}
|
26
inc/gettext/src/Extractors/ExtractorInterface.php
Normal file
26
inc/gettext/src/Extractors/ExtractorInterface.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
interface ExtractorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Extract the translations from a file.
|
||||||
|
*
|
||||||
|
* @param array|string $file A path of a file or files
|
||||||
|
* @param Translations $translations The translations instance to append the new translations.
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public static function fromFile($file, Translations $translations, array $options = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a string and append the translations found in the Translations instance.
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @param Translations $translations
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = []);
|
||||||
|
}
|
28
inc/gettext/src/Extractors/ExtractorMultiInterface.php
Normal file
28
inc/gettext/src/Extractors/ExtractorMultiInterface.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
interface ExtractorMultiInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Parses a string and append the translations found in the Translations instance.
|
||||||
|
* Allows scanning for multiple domains at a time (each Translation has to have a different domain)
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
* @param Translations[] $translations
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public static function fromStringMultiple($string, array $translations, array $options = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a string and append the translations found in the Translations instance.
|
||||||
|
* Allows scanning for multiple domains at a time (each Translation has to have a different domain)
|
||||||
|
*
|
||||||
|
* @param $file
|
||||||
|
* @param Translations[] $translations
|
||||||
|
* @param array $options
|
||||||
|
*/
|
||||||
|
public static function fromFileMultiple($file, array $translations, array $options = []);
|
||||||
|
}
|
55
inc/gettext/src/Extractors/Jed.php
Normal file
55
inc/gettext/src/Extractors/Jed.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from json files.
|
||||||
|
*/
|
||||||
|
class Jed extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
static::extract(json_decode($string, true), $translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle an array of translations and append to the Translations instance.
|
||||||
|
*
|
||||||
|
* @param array $content
|
||||||
|
* @param Translations $translations
|
||||||
|
*/
|
||||||
|
public static function extract(array $content, Translations $translations)
|
||||||
|
{
|
||||||
|
$messages = current($content);
|
||||||
|
$headers = isset($messages['']) ? $messages[''] : null;
|
||||||
|
unset($messages['']);
|
||||||
|
|
||||||
|
if (!empty($headers['domain'])) {
|
||||||
|
$translations->setDomain($headers['domain']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($headers['lang'])) {
|
||||||
|
$translations->setLanguage($headers['lang']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($headers['plural-forms'])) {
|
||||||
|
$translations->setHeader(Translations::HEADER_PLURAL, $headers['plural-forms']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$context_glue = '\u0004';
|
||||||
|
|
||||||
|
foreach ($messages as $key => $translation) {
|
||||||
|
$key = explode($context_glue, $key);
|
||||||
|
$context = isset($key[1]) ? array_shift($key) : '';
|
||||||
|
|
||||||
|
$translations->insert($context, array_shift($key))
|
||||||
|
->setTranslation(array_shift($translation))
|
||||||
|
->setPluralTranslations($translation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
inc/gettext/src/Extractors/JsCode.php
Normal file
74
inc/gettext/src/Extractors/JsCode.php
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\FunctionsScanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from javascript files.
|
||||||
|
*/
|
||||||
|
class JsCode extends Extractor implements ExtractorInterface, ExtractorMultiInterface
|
||||||
|
{
|
||||||
|
public static $options = [
|
||||||
|
'constants' => [],
|
||||||
|
|
||||||
|
'functions' => [
|
||||||
|
'gettext' => 'gettext',
|
||||||
|
'__' => 'gettext',
|
||||||
|
'ngettext' => 'ngettext',
|
||||||
|
'n__' => 'ngettext',
|
||||||
|
'pgettext' => 'pgettext',
|
||||||
|
'p__' => 'pgettext',
|
||||||
|
'dgettext' => 'dgettext',
|
||||||
|
'd__' => 'dgettext',
|
||||||
|
'dngettext' => 'dngettext',
|
||||||
|
'dn__' => 'dngettext',
|
||||||
|
'dpgettext' => 'dpgettext',
|
||||||
|
'dp__' => 'dpgettext',
|
||||||
|
'npgettext' => 'npgettext',
|
||||||
|
'np__' => 'npgettext',
|
||||||
|
'dnpgettext' => 'dnpgettext',
|
||||||
|
'dnp__' => 'dnpgettext',
|
||||||
|
'noop' => 'noop',
|
||||||
|
'noop__' => 'noop',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static $functionsScannerClass = 'Gettext\Utils\JsFunctionsScanner';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
static::fromStringMultiple($string, [$translations], $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function fromStringMultiple($string, array $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
/** @var FunctionsScanner $functions */
|
||||||
|
$functions = new static::$functionsScannerClass($string);
|
||||||
|
$functions->saveGettextFunctions($translations, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function fromFileMultiple($file, array $translations, array $options = [])
|
||||||
|
{
|
||||||
|
foreach (static::getFiles($file) as $file) {
|
||||||
|
$options['file'] = $file;
|
||||||
|
static::fromStringMultiple(static::readFile($file), $translations, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
inc/gettext/src/Extractors/Json.php
Normal file
26
inc/gettext/src/Extractors/Json.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from json.
|
||||||
|
*/
|
||||||
|
class Json extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
use MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$messages = json_decode($string, true);
|
||||||
|
|
||||||
|
if (is_array($messages)) {
|
||||||
|
static::fromArray($messages, $translations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
inc/gettext/src/Extractors/JsonDictionary.php
Normal file
26
inc/gettext/src/Extractors/JsonDictionary.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\DictionaryTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from plain json.
|
||||||
|
*/
|
||||||
|
class JsonDictionary extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
use DictionaryTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$messages = json_decode($string, true);
|
||||||
|
|
||||||
|
if (is_array($messages)) {
|
||||||
|
static::fromArray($messages, $translations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
131
inc/gettext/src/Extractors/Mo.php
Normal file
131
inc/gettext/src/Extractors/Mo.php
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\StringReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from .mo files.
|
||||||
|
*/
|
||||||
|
class Mo extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
const MAGIC1 = -1794895138;
|
||||||
|
const MAGIC2 = -569244523;
|
||||||
|
const MAGIC3 = 2500072158;
|
||||||
|
|
||||||
|
protected static $stringReaderClass = 'Gettext\Utils\StringReader';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
/** @var StringReader $stream */
|
||||||
|
$stream = new static::$stringReaderClass($string);
|
||||||
|
$magic = static::readInt($stream, 'V');
|
||||||
|
|
||||||
|
if (($magic === static::MAGIC1) || ($magic === static::MAGIC3)) { //to make sure it works for 64-bit platforms
|
||||||
|
$byteOrder = 'V'; //low endian
|
||||||
|
} elseif ($magic === (static::MAGIC2 & 0xFFFFFFFF)) {
|
||||||
|
$byteOrder = 'N'; //big endian
|
||||||
|
} else {
|
||||||
|
throw new Exception('Not MO file');
|
||||||
|
}
|
||||||
|
|
||||||
|
static::readInt($stream, $byteOrder);
|
||||||
|
|
||||||
|
$total = static::readInt($stream, $byteOrder); //total string count
|
||||||
|
$originals = static::readInt($stream, $byteOrder); //offset of original table
|
||||||
|
$tran = static::readInt($stream, $byteOrder); //offset of translation table
|
||||||
|
|
||||||
|
$stream->seekto($originals);
|
||||||
|
$table_originals = static::readIntArray($stream, $byteOrder, $total * 2);
|
||||||
|
|
||||||
|
$stream->seekto($tran);
|
||||||
|
$table_translations = static::readIntArray($stream, $byteOrder, $total * 2);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $total; ++$i) {
|
||||||
|
$next = $i * 2;
|
||||||
|
|
||||||
|
$stream->seekto($table_originals[$next + 2]);
|
||||||
|
$original = $stream->read($table_originals[$next + 1]);
|
||||||
|
|
||||||
|
$stream->seekto($table_translations[$next + 2]);
|
||||||
|
$translated = $stream->read($table_translations[$next + 1]);
|
||||||
|
|
||||||
|
if ($original === '') {
|
||||||
|
// Headers
|
||||||
|
foreach (explode("\n", $translated) as $headerLine) {
|
||||||
|
if ($headerLine === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$headerChunks = preg_split('/:\s*/', $headerLine, 2);
|
||||||
|
$translations->setHeader($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$chunks = explode("\x04", $original, 2);
|
||||||
|
|
||||||
|
if (isset($chunks[1])) {
|
||||||
|
$context = $chunks[0];
|
||||||
|
$original = $chunks[1];
|
||||||
|
} else {
|
||||||
|
$context = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$chunks = explode("\x00", $original, 2);
|
||||||
|
|
||||||
|
if (isset($chunks[1])) {
|
||||||
|
$original = $chunks[0];
|
||||||
|
$plural = $chunks[1];
|
||||||
|
} else {
|
||||||
|
$plural = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation = $translations->insert($context, $original, $plural);
|
||||||
|
|
||||||
|
if ($translated === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($plural === '') {
|
||||||
|
$translation->setTranslation($translated);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$v = explode("\x00", $translated);
|
||||||
|
$translation->setTranslation(array_shift($v));
|
||||||
|
$translation->setPluralTranslations($v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param StringReader $stream
|
||||||
|
* @param string $byteOrder
|
||||||
|
*/
|
||||||
|
protected static function readInt(StringReader $stream, $byteOrder)
|
||||||
|
{
|
||||||
|
if (($read = $stream->read(4)) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$read = unpack($byteOrder, $read);
|
||||||
|
|
||||||
|
return array_shift($read);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param StringReader $stream
|
||||||
|
* @param string $byteOrder
|
||||||
|
* @param int $count
|
||||||
|
*/
|
||||||
|
protected static function readIntArray(StringReader $stream, $byteOrder, $count)
|
||||||
|
{
|
||||||
|
return unpack($byteOrder.$count, $stream->read(4 * $count));
|
||||||
|
}
|
||||||
|
}
|
33
inc/gettext/src/Extractors/PhpArray.php
Normal file
33
inc/gettext/src/Extractors/PhpArray.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use BadMethodCallException;
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from php files returning arrays.
|
||||||
|
*/
|
||||||
|
class PhpArray extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
use MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromFile($file, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
foreach (static::getFiles($file) as $file) {
|
||||||
|
static::fromArray(include($file), $translations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
throw new BadMethodCallException('PhpArray::fromString() cannot be called. Use PhpArray::fromFile()');
|
||||||
|
}
|
||||||
|
}
|
170
inc/gettext/src/Extractors/PhpCode.php
Normal file
170
inc/gettext/src/Extractors/PhpCode.php
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\FunctionsScanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from php files returning arrays.
|
||||||
|
*/
|
||||||
|
class PhpCode extends Extractor implements ExtractorInterface, ExtractorMultiInterface
|
||||||
|
{
|
||||||
|
public static $options = [
|
||||||
|
// - false: to not extract comments
|
||||||
|
// - empty string: to extract all comments
|
||||||
|
// - non-empty string: to extract comments that start with that string
|
||||||
|
// - array with strings to extract comments format.
|
||||||
|
'extractComments' => false,
|
||||||
|
|
||||||
|
'constants' => [],
|
||||||
|
|
||||||
|
'functions' => [
|
||||||
|
'gettext' => 'gettext',
|
||||||
|
'__' => 'gettext',
|
||||||
|
'ngettext' => 'ngettext',
|
||||||
|
'n__' => 'ngettext',
|
||||||
|
'pgettext' => 'pgettext',
|
||||||
|
'p__' => 'pgettext',
|
||||||
|
'dgettext' => 'dgettext',
|
||||||
|
'd__' => 'dgettext',
|
||||||
|
'dngettext' => 'dngettext',
|
||||||
|
'dn__' => 'dngettext',
|
||||||
|
'dpgettext' => 'dpgettext',
|
||||||
|
'dp__' => 'dpgettext',
|
||||||
|
'npgettext' => 'npgettext',
|
||||||
|
'np__' => 'npgettext',
|
||||||
|
'dnpgettext' => 'dnpgettext',
|
||||||
|
'dnp__' => 'dnpgettext',
|
||||||
|
'noop' => 'noop',
|
||||||
|
'noop__' => 'noop',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static $functionsScannerClass = 'Gettext\Utils\PhpFunctionsScanner';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
static::fromStringMultiple($string, [$translations], $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function fromStringMultiple($string, array $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
/** @var FunctionsScanner $functions */
|
||||||
|
$functions = new static::$functionsScannerClass($string);
|
||||||
|
|
||||||
|
if ($options['extractComments'] !== false) {
|
||||||
|
$functions->enableCommentsExtraction($options['extractComments']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$functions->saveGettextFunctions($translations, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
public static function fromFileMultiple($file, array $translations, array $options = [])
|
||||||
|
{
|
||||||
|
foreach (static::getFiles($file) as $file) {
|
||||||
|
$options['file'] = $file;
|
||||||
|
static::fromStringMultiple(static::readFile($file), $translations, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a T_CONSTANT_ENCAPSED_STRING string.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function convertString($value)
|
||||||
|
{
|
||||||
|
if (strpos($value, '\\') === false) {
|
||||||
|
return substr($value, 1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value[0] === "'") {
|
||||||
|
return strtr(substr($value, 1, -1), ['\\\\' => '\\', '\\\'' => '\'']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = substr($value, 1, -1);
|
||||||
|
|
||||||
|
return preg_replace_callback(
|
||||||
|
'/\\\(n|r|t|v|e|f|\$|"|\\\|x[0-9A-Fa-f]{1,2}|u{[0-9a-f]{1,6}}|[0-7]{1,3})/',
|
||||||
|
function ($match) {
|
||||||
|
switch ($match[1][0]) {
|
||||||
|
case 'n':
|
||||||
|
return "\n";
|
||||||
|
case 'r':
|
||||||
|
return "\r";
|
||||||
|
case 't':
|
||||||
|
return "\t";
|
||||||
|
case 'v':
|
||||||
|
return "\v";
|
||||||
|
case 'e':
|
||||||
|
return "\e";
|
||||||
|
case 'f':
|
||||||
|
return "\f";
|
||||||
|
case '$':
|
||||||
|
return '$';
|
||||||
|
case '"':
|
||||||
|
return '"';
|
||||||
|
case '\\':
|
||||||
|
return '\\';
|
||||||
|
case 'x':
|
||||||
|
return chr(hexdec(substr($match[1], 1)));
|
||||||
|
case 'u':
|
||||||
|
return static::unicodeChar(hexdec(substr($match[1], 1)));
|
||||||
|
default:
|
||||||
|
return chr(octdec($match[1]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $dec
|
||||||
|
* @return string|null
|
||||||
|
* @see http://php.net/manual/en/function.chr.php#118804
|
||||||
|
*/
|
||||||
|
protected static function unicodeChar($dec)
|
||||||
|
{
|
||||||
|
if ($dec < 0x80) {
|
||||||
|
return chr($dec);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dec < 0x0800) {
|
||||||
|
return chr(0xC0 + ($dec >> 6))
|
||||||
|
. chr(0x80 + ($dec & 0x3f));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dec < 0x010000) {
|
||||||
|
return chr(0xE0 + ($dec >> 12))
|
||||||
|
. chr(0x80 + (($dec >> 6) & 0x3f))
|
||||||
|
. chr(0x80 + ($dec & 0x3f));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dec < 0x200000) {
|
||||||
|
return chr(0xF0 + ($dec >> 18))
|
||||||
|
. chr(0x80 + (($dec >> 12) & 0x3f))
|
||||||
|
. chr(0x80 + (($dec >> 6) & 0x3f))
|
||||||
|
. chr(0x80 + ($dec & 0x3f));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
211
inc/gettext/src/Extractors/Po.php
Normal file
211
inc/gettext/src/Extractors/Po.php
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Translation;
|
||||||
|
use Gettext\Utils\HeadersExtractorTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from php files returning arrays.
|
||||||
|
*/
|
||||||
|
class Po extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
use HeadersExtractorTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a .po file and append the translations found in the Translations instance.
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$lines = explode("\n", $string);
|
||||||
|
$i = 0;
|
||||||
|
|
||||||
|
$translation = $translations->createNewTranslation('', '');
|
||||||
|
|
||||||
|
for ($n = count($lines); $i < $n; ++$i) {
|
||||||
|
$line = trim($lines[$i]);
|
||||||
|
$line = static::fixMultiLines($line, $lines, $i);
|
||||||
|
|
||||||
|
if ($line === '') {
|
||||||
|
if ($translation->is('', '')) {
|
||||||
|
static::extractHeaders($translation->getTranslation(), $translations);
|
||||||
|
} elseif ($translation->hasOriginal()) {
|
||||||
|
$translations[] = $translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation = $translations->createNewTranslation('', '');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$splitLine = preg_split('/\s+/', $line, 2);
|
||||||
|
$key = $splitLine[0];
|
||||||
|
$data = isset($splitLine[1]) ? $splitLine[1] : '';
|
||||||
|
|
||||||
|
if ($key === '#~') {
|
||||||
|
$translation->setDisabled(true);
|
||||||
|
|
||||||
|
$splitLine = preg_split('/\s+/', $data, 2);
|
||||||
|
$key = $splitLine[0];
|
||||||
|
$data = isset($splitLine[1]) ? $splitLine[1] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($key) {
|
||||||
|
case '#':
|
||||||
|
$translation->addComment($data);
|
||||||
|
$append = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '#.':
|
||||||
|
$translation->addExtractedComment($data);
|
||||||
|
$append = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '#,':
|
||||||
|
foreach (array_map('trim', explode(',', trim($data))) as $value) {
|
||||||
|
$translation->addFlag($value);
|
||||||
|
}
|
||||||
|
$append = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '#:':
|
||||||
|
foreach (preg_split('/\s+/', trim($data)) as $value) {
|
||||||
|
if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) {
|
||||||
|
$translation->addReference($matches[1], isset($matches[3]) ? $matches[3] : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$append = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'msgctxt':
|
||||||
|
$translation = $translation->getClone(static::convertString($data));
|
||||||
|
$append = 'Context';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'msgid':
|
||||||
|
$translation = $translation->getClone(null, static::convertString($data));
|
||||||
|
$append = 'Original';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'msgid_plural':
|
||||||
|
$translation->setPlural(static::convertString($data));
|
||||||
|
$append = 'Plural';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'msgstr':
|
||||||
|
case 'msgstr[0]':
|
||||||
|
$translation->setTranslation(static::convertString($data));
|
||||||
|
$append = 'Translation';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'msgstr[1]':
|
||||||
|
$translation->setPluralTranslations([static::convertString($data)]);
|
||||||
|
$append = 'PluralTranslation';
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (strpos($key, 'msgstr[') === 0) {
|
||||||
|
$p = $translation->getPluralTranslations();
|
||||||
|
$p[] = static::convertString($data);
|
||||||
|
|
||||||
|
$translation->setPluralTranslations($p);
|
||||||
|
$append = 'PluralTranslation';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($append)) {
|
||||||
|
if ($append === 'Context') {
|
||||||
|
$translation = $translation->getClone($translation->getContext()
|
||||||
|
."\n"
|
||||||
|
.static::convertString($data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($append === 'Original') {
|
||||||
|
$translation = $translation->getClone(null, $translation->getOriginal()
|
||||||
|
."\n"
|
||||||
|
.static::convertString($data));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($append === 'PluralTranslation') {
|
||||||
|
$p = $translation->getPluralTranslations();
|
||||||
|
$p[] = array_pop($p)."\n".static::convertString($data);
|
||||||
|
$translation->setPluralTranslations($p);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$getMethod = 'get'.$append;
|
||||||
|
$setMethod = 'set'.$append;
|
||||||
|
$translation->$setMethod($translation->$getMethod()."\n".static::convertString($data));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($translation->hasOriginal() && !in_array($translation, iterator_to_array($translations))) {
|
||||||
|
$translations[] = $translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets one string from multiline strings.
|
||||||
|
*
|
||||||
|
* @param string $line
|
||||||
|
* @param array $lines
|
||||||
|
* @param int &$i
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function fixMultiLines($line, array $lines, &$i)
|
||||||
|
{
|
||||||
|
for ($j = $i, $t = count($lines); $j < $t; ++$j) {
|
||||||
|
if (substr($line, -1, 1) == '"'
|
||||||
|
&& isset($lines[$j + 1])
|
||||||
|
&& substr(trim($lines[$j + 1]), 0, 1) == '"'
|
||||||
|
) {
|
||||||
|
$line = substr($line, 0, -1).substr(trim($lines[$j + 1]), 1);
|
||||||
|
} else {
|
||||||
|
$i = $j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string from its PO representation.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function convertString($value)
|
||||||
|
{
|
||||||
|
if (!$value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value[0] === '"') {
|
||||||
|
$value = substr($value, 1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtr(
|
||||||
|
$value,
|
||||||
|
[
|
||||||
|
'\\\\' => '\\',
|
||||||
|
'\\a' => "\x07",
|
||||||
|
'\\b' => "\x08",
|
||||||
|
'\\t' => "\t",
|
||||||
|
'\\n' => "\n",
|
||||||
|
'\\v' => "\x0b",
|
||||||
|
'\\f' => "\x0c",
|
||||||
|
'\\r' => "\r",
|
||||||
|
'\\"' => '"',
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
45
inc/gettext/src/Extractors/Twig.php
Normal file
45
inc/gettext/src/Extractors/Twig.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Twig_Loader_Array;
|
||||||
|
use Twig_Environment;
|
||||||
|
use Twig_Source;
|
||||||
|
use Twig_Extensions_Extension_I18n;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from twig files returning arrays.
|
||||||
|
*/
|
||||||
|
class Twig extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
public static $options = [
|
||||||
|
'extractComments' => 'notes:',
|
||||||
|
'twig' => null,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
$twig = $options['twig'] ?: static::createTwig();
|
||||||
|
|
||||||
|
PhpCode::fromString($twig->compileSource(new Twig_Source($string, '')), $translations, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a Twig instance.
|
||||||
|
*
|
||||||
|
* @return Twig_Environment
|
||||||
|
*/
|
||||||
|
protected static function createTwig()
|
||||||
|
{
|
||||||
|
$twig = new Twig_Environment(new Twig_Loader_Array(['' => '']));
|
||||||
|
$twig->addExtension(new Twig_Extensions_Extension_I18n());
|
||||||
|
|
||||||
|
return static::$options['twig'] = $twig;
|
||||||
|
}
|
||||||
|
}
|
423
inc/gettext/src/Extractors/VueJs.php
Normal file
423
inc/gettext/src/Extractors/VueJs.php
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
<?php
|
||||||
|
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use DOMAttr;
|
||||||
|
use DOMDocument;
|
||||||
|
use DOMElement;
|
||||||
|
use DOMNode;
|
||||||
|
use Exception;
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\FunctionsScanner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from VueJS template files.
|
||||||
|
*/
|
||||||
|
class VueJs extends Extractor implements ExtractorInterface, ExtractorMultiInterface
|
||||||
|
{
|
||||||
|
public static $options = [
|
||||||
|
'constants' => [],
|
||||||
|
|
||||||
|
'functions' => [
|
||||||
|
'gettext' => 'gettext',
|
||||||
|
'__' => 'gettext',
|
||||||
|
'ngettext' => 'ngettext',
|
||||||
|
'n__' => 'ngettext',
|
||||||
|
'pgettext' => 'pgettext',
|
||||||
|
'p__' => 'pgettext',
|
||||||
|
'dgettext' => 'dgettext',
|
||||||
|
'd__' => 'dgettext',
|
||||||
|
'dngettext' => 'dngettext',
|
||||||
|
'dn__' => 'dngettext',
|
||||||
|
'dpgettext' => 'dpgettext',
|
||||||
|
'dp__' => 'dpgettext',
|
||||||
|
'npgettext' => 'npgettext',
|
||||||
|
'np__' => 'npgettext',
|
||||||
|
'dnpgettext' => 'dnpgettext',
|
||||||
|
'dnp__' => 'dnpgettext',
|
||||||
|
'noop' => 'noop',
|
||||||
|
'noop__' => 'noop',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected static $functionsScannerClass = 'Gettext\Utils\JsFunctionsScanner';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function fromFileMultiple($file, array $translations, array $options = [])
|
||||||
|
{
|
||||||
|
foreach (static::getFiles($file) as $file) {
|
||||||
|
$options['file'] = $file;
|
||||||
|
static::fromStringMultiple(static::readFile($file), $translations, $options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
static::fromStringMultiple($string, [$translations], $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public static function fromStringMultiple($string, array $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
$options += [
|
||||||
|
// HTML attribute prefixes we parse as JS which could contain translations (are JS expressions)
|
||||||
|
'attributePrefixes' => [
|
||||||
|
':',
|
||||||
|
'v-bind:',
|
||||||
|
'v-on:',
|
||||||
|
'v-text',
|
||||||
|
],
|
||||||
|
// HTML Tags to parse
|
||||||
|
'tagNames' => [
|
||||||
|
'translate',
|
||||||
|
],
|
||||||
|
// HTML tags to parse when attribute exists
|
||||||
|
'tagAttributes' => [
|
||||||
|
'v-translate',
|
||||||
|
],
|
||||||
|
// Comments
|
||||||
|
'commentAttributes' => [
|
||||||
|
'translate-comment',
|
||||||
|
],
|
||||||
|
'contextAttributes' => [
|
||||||
|
'translate-context',
|
||||||
|
],
|
||||||
|
// Attribute with plural content
|
||||||
|
'pluralAttributes' => [
|
||||||
|
'translate-plural',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
// Ok, this is the weirdest hack, but let me explain:
|
||||||
|
// On Linux (Mac is fine), when converting HTML to DOM, new lines get trimmed after the first tag.
|
||||||
|
// So if there are new lines between <template> and next element, they are lost
|
||||||
|
// So we insert a "." which is a text node, and it will prevent that newlines are stripped between elements.
|
||||||
|
// Same thing happens between template and script tag.
|
||||||
|
$string = str_replace('<template>', '<template>.', $string);
|
||||||
|
$string = str_replace('</template>', '</template>.', $string);
|
||||||
|
|
||||||
|
// Normalize newlines
|
||||||
|
$string = str_replace(["\r\n", "\n\r", "\r"], "\n", $string);
|
||||||
|
|
||||||
|
// VueJS files are valid HTML files, we will operate with the DOM here
|
||||||
|
$dom = static::convertHtmlToDom($string);
|
||||||
|
|
||||||
|
$script = static::extractScriptTag($string);
|
||||||
|
|
||||||
|
// Parse the script part as a regular JS code
|
||||||
|
if ($script) {
|
||||||
|
$scriptLineNumber = $dom->getElementsByTagName('script')->item(0)->getLineNo();
|
||||||
|
static::getScriptTranslationsFromString(
|
||||||
|
$script,
|
||||||
|
$translations,
|
||||||
|
$options,
|
||||||
|
$scriptLineNumber - 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template part is parsed separately, all variables will be extracted
|
||||||
|
// and handled as a regular JS code
|
||||||
|
$template = $dom->getElementsByTagName('template')->item(0);
|
||||||
|
if ($template) {
|
||||||
|
static::getTemplateTranslations(
|
||||||
|
$template,
|
||||||
|
$translations,
|
||||||
|
$options,
|
||||||
|
$template->getLineNo() - 1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts script tag contents using regex instead of DOM operations.
|
||||||
|
* If we parse using DOM, some contents may change, for example, tags within strings will be stripped
|
||||||
|
*
|
||||||
|
* @param $string
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
protected static function extractScriptTag($string)
|
||||||
|
{
|
||||||
|
if (preg_match('#<\s*?script\b[^>]*>(.*?)</script\b[^>]*>#s', $string, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $html
|
||||||
|
* @return DOMDocument
|
||||||
|
*/
|
||||||
|
protected static function convertHtmlToDom($html)
|
||||||
|
{
|
||||||
|
$dom = new DOMDocument;
|
||||||
|
|
||||||
|
libxml_use_internal_errors(true);
|
||||||
|
|
||||||
|
// Prepend xml encoding so DOMDocument document handles UTF8 correctly.
|
||||||
|
// Assuming that vue template files will not have any xml encoding tags, because duplicate tags may be ignored.
|
||||||
|
$dom->loadHTML('<?xml encoding="utf-8"?>' . $html);
|
||||||
|
|
||||||
|
libxml_clear_errors();
|
||||||
|
|
||||||
|
return $dom;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract translations from script part
|
||||||
|
*
|
||||||
|
* @param string $scriptContents Only script tag contents, not the whole template
|
||||||
|
* @param Translations|Translations[] $translations One or multiple domain Translation objects
|
||||||
|
* @param array $options
|
||||||
|
* @param int $lineOffset Number of lines the script is offset in the vue template file
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected static function getScriptTranslationsFromString(
|
||||||
|
$scriptContents,
|
||||||
|
$translations,
|
||||||
|
array $options = [],
|
||||||
|
$lineOffset = 0
|
||||||
|
) {
|
||||||
|
/** @var FunctionsScanner $functions */
|
||||||
|
$functions = new static::$functionsScannerClass($scriptContents);
|
||||||
|
$options['lineOffset'] = $lineOffset;
|
||||||
|
$functions->saveGettextFunctions($translations, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse template to extract all translations (element content and dynamic element attributes)
|
||||||
|
*
|
||||||
|
* @param DOMNode $dom
|
||||||
|
* @param Translations|Translations[] $translations One or multiple domain Translation objects
|
||||||
|
* @param array $options
|
||||||
|
* @param int $lineOffset Line number where the template part starts in the vue file
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected static function getTemplateTranslations(
|
||||||
|
DOMNode $dom,
|
||||||
|
$translations,
|
||||||
|
array $options,
|
||||||
|
$lineOffset = 0
|
||||||
|
) {
|
||||||
|
// Build a JS string from all template attribute expressions
|
||||||
|
$fakeAttributeJs = static::getTemplateAttributeFakeJs($options, $dom);
|
||||||
|
|
||||||
|
// 1 line offset is necessary because parent template element was ignored when converting to DOM
|
||||||
|
static::getScriptTranslationsFromString($fakeAttributeJs, $translations, $options, $lineOffset);
|
||||||
|
|
||||||
|
// Build a JS string from template element content expressions
|
||||||
|
$fakeTemplateJs = static::getTemplateFakeJs($dom);
|
||||||
|
static::getScriptTranslationsFromString($fakeTemplateJs, $translations, $options, $lineOffset);
|
||||||
|
|
||||||
|
static::getTagTranslations($options, $dom, $translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $options
|
||||||
|
* @param DOMNode $dom
|
||||||
|
* @param Translations|Translations[] $translations
|
||||||
|
*/
|
||||||
|
protected static function getTagTranslations(array $options, DOMNode $dom, $translations)
|
||||||
|
{
|
||||||
|
// Since tag scanning does not support domains, we always use the first translation given
|
||||||
|
$translations = is_array($translations) ? reset($translations) : $translations;
|
||||||
|
|
||||||
|
$children = $dom->childNodes;
|
||||||
|
for ($i = 0; $i < $children->length; $i++) {
|
||||||
|
$node = $children->item($i);
|
||||||
|
|
||||||
|
if (!($node instanceof DOMElement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translatable = false;
|
||||||
|
|
||||||
|
if (in_array($node->tagName, $options['tagNames'], true)) {
|
||||||
|
$translatable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$attrList = $node->attributes;
|
||||||
|
$context = null;
|
||||||
|
$plural = "";
|
||||||
|
$comment = null;
|
||||||
|
|
||||||
|
for ($j = 0; $j < $attrList->length; $j++) {
|
||||||
|
/** @var DOMAttr $domAttr */
|
||||||
|
$domAttr = $attrList->item($j);
|
||||||
|
// Check if this is a dynamic vue attribute
|
||||||
|
if (in_array($domAttr->name, $options['tagAttributes'])) {
|
||||||
|
$translatable = true;
|
||||||
|
}
|
||||||
|
if (in_array($domAttr->name, $options['contextAttributes'])) {
|
||||||
|
$context = $domAttr->value;
|
||||||
|
}
|
||||||
|
if (in_array($domAttr->name, $options['pluralAttributes'])) {
|
||||||
|
$plural = $domAttr->value;
|
||||||
|
}
|
||||||
|
if (in_array($domAttr->name, $options['commentAttributes'])) {
|
||||||
|
$comment = $domAttr->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($translatable) {
|
||||||
|
$translation = $translations->insert($context, trim($node->textContent), $plural);
|
||||||
|
$translation->addReference($options['file'], $node->getLineNo());
|
||||||
|
if ($comment) {
|
||||||
|
$translation->addExtractedComment($comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->hasChildNodes()) {
|
||||||
|
static::getTagTranslations($options, $node, $translations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract JS expressions from element attribute bindings (excluding text within elements)
|
||||||
|
* For example: <span :title="__('extract this')"> skip element content </span>
|
||||||
|
*
|
||||||
|
* @param array $options
|
||||||
|
* @param DOMNode $dom
|
||||||
|
* @return string JS code
|
||||||
|
*/
|
||||||
|
protected static function getTemplateAttributeFakeJs(array $options, DOMNode $dom)
|
||||||
|
{
|
||||||
|
$expressionsByLine = static::getVueAttributeExpressions($options['attributePrefixes'], $dom);
|
||||||
|
|
||||||
|
if (empty($expressionsByLine)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$maxLines = max(array_keys($expressionsByLine));
|
||||||
|
$fakeJs = '';
|
||||||
|
|
||||||
|
for ($line = 1; $line <= $maxLines; $line++) {
|
||||||
|
if (isset($expressionsByLine[$line])) {
|
||||||
|
$fakeJs .= implode("; ", $expressionsByLine[$line]);
|
||||||
|
}
|
||||||
|
$fakeJs .= "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fakeJs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop DOM element recursively and parse out all dynamic vue attributes which are basically JS expressions
|
||||||
|
*
|
||||||
|
* @param array $attributePrefixes List of attribute prefixes we parse as JS (may contain translations)
|
||||||
|
* @param DOMNode $dom
|
||||||
|
* @param array $expressionByLine [lineNumber => [jsExpression, ..], ..]
|
||||||
|
* @return array [lineNumber => [jsExpression, ..], ..]
|
||||||
|
*/
|
||||||
|
protected static function getVueAttributeExpressions(
|
||||||
|
array $attributePrefixes,
|
||||||
|
DOMNode $dom,
|
||||||
|
array &$expressionByLine = []
|
||||||
|
) {
|
||||||
|
$children = $dom->childNodes;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $children->length; $i++) {
|
||||||
|
$node = $children->item($i);
|
||||||
|
|
||||||
|
if (!($node instanceof DOMElement)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$attrList = $node->attributes;
|
||||||
|
|
||||||
|
for ($j = 0; $j < $attrList->length; $j++) {
|
||||||
|
/** @var DOMAttr $domAttr */
|
||||||
|
$domAttr = $attrList->item($j);
|
||||||
|
|
||||||
|
// Check if this is a dynamic vue attribute
|
||||||
|
if (static::isAttributeMatching($domAttr->name, $attributePrefixes)) {
|
||||||
|
$line = $domAttr->getLineNo();
|
||||||
|
$expressionByLine += [$line => []];
|
||||||
|
$expressionByLine[$line][] = $domAttr->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($node->hasChildNodes()) {
|
||||||
|
$expressionByLine = static::getVueAttributeExpressions($attributePrefixes, $node, $expressionByLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $expressionByLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if this attribute name should be parsed for translations
|
||||||
|
*
|
||||||
|
* @param string $attributeName
|
||||||
|
* @param string[] $attributePrefixes
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected static function isAttributeMatching($attributeName, $attributePrefixes)
|
||||||
|
{
|
||||||
|
foreach ($attributePrefixes as $prefix) {
|
||||||
|
if (strpos($attributeName, $prefix) === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract JS expressions from within template elements (excluding attributes)
|
||||||
|
* For example: <span :title="skip attributes"> {{__("extract element content")}} </span>
|
||||||
|
*
|
||||||
|
* @param DOMNode $dom
|
||||||
|
* @return string JS code
|
||||||
|
*/
|
||||||
|
protected static function getTemplateFakeJs(DOMNode $dom)
|
||||||
|
{
|
||||||
|
$fakeJs = '';
|
||||||
|
$lines = explode("\n", $dom->textContent);
|
||||||
|
|
||||||
|
// Build a fake JS file from template by extracting JS expressions within each template line
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$expressionMatched = static::parseOneTemplateLine($line);
|
||||||
|
|
||||||
|
$fakeJs .= implode("; ", $expressionMatched) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $fakeJs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match JS expressions in a template line
|
||||||
|
*
|
||||||
|
* @param string $line
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
protected static function parseOneTemplateLine($line)
|
||||||
|
{
|
||||||
|
$line = trim($line);
|
||||||
|
|
||||||
|
if (!$line) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$regex = '#\{\{(.*?)\}\}#';
|
||||||
|
|
||||||
|
preg_match_all($regex, $line, $matches);
|
||||||
|
|
||||||
|
$matched = array_map(function ($v) {
|
||||||
|
return trim($v, '\'"{}');
|
||||||
|
}, $matches[1]);
|
||||||
|
|
||||||
|
return $matched;
|
||||||
|
}
|
||||||
|
}
|
86
inc/gettext/src/Extractors/Xliff.php
Normal file
86
inc/gettext/src/Extractors/Xliff.php
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Translation;
|
||||||
|
use SimpleXMLElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from xliff format.
|
||||||
|
*/
|
||||||
|
class Xliff extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'unitid_as_id' => false
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
$xml = new SimpleXMLElement($string, null, false);
|
||||||
|
|
||||||
|
foreach ($xml->file as $file) {
|
||||||
|
if (isset($file->notes)) {
|
||||||
|
foreach ($file->notes->note as $note) {
|
||||||
|
$translations->setHeader($note['id'], (string) $note);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($file->unit as $unit) {
|
||||||
|
foreach ($unit->segment as $segment) {
|
||||||
|
$targets = [];
|
||||||
|
|
||||||
|
foreach ($segment->target as $target) {
|
||||||
|
$targets[] = (string) $target;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation = $translations->createNewTranslation(null, (string) $segment->source);
|
||||||
|
if (isset($unit['id'])) {
|
||||||
|
$unitId = (string) $unit['id'];
|
||||||
|
$translation->addComment("XLIFF_UNIT_ID: $unitId");
|
||||||
|
if ($options['unitid_as_id']) {
|
||||||
|
$translation->setId($unitId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$translation->setTranslation(array_shift($targets));
|
||||||
|
$translation->setPluralTranslations($targets);
|
||||||
|
|
||||||
|
if (isset($unit->notes)) {
|
||||||
|
foreach ($unit->notes->note as $note) {
|
||||||
|
switch ($note['category']) {
|
||||||
|
case 'context':
|
||||||
|
$translation = $translation->getClone((string) $note);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'extracted-comment':
|
||||||
|
$translation->addExtractedComment((string) $note);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'flag':
|
||||||
|
$translation->addFlag((string) $note);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'reference':
|
||||||
|
$ref = explode(':', (string) $note, 2);
|
||||||
|
$translation->addReference($ref[0], isset($ref[1]) ? $ref[1] : null);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$translation->addComment((string) $note);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations[] = $translation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
inc/gettext/src/Extractors/Yaml.php
Normal file
27
inc/gettext/src/Extractors/Yaml.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||||
|
use Symfony\Component\Yaml\Yaml as YamlParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from yaml.
|
||||||
|
*/
|
||||||
|
class Yaml extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
use MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$messages = YamlParser::parse($string);
|
||||||
|
|
||||||
|
if (is_array($messages)) {
|
||||||
|
static::fromArray($messages, $translations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
27
inc/gettext/src/Extractors/YamlDictionary.php
Normal file
27
inc/gettext/src/Extractors/YamlDictionary.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Extractors;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\DictionaryTrait;
|
||||||
|
use Symfony\Component\Yaml\Yaml as YamlParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to get gettext strings from yaml.
|
||||||
|
*/
|
||||||
|
class YamlDictionary extends Extractor implements ExtractorInterface
|
||||||
|
{
|
||||||
|
use DictionaryTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function fromString($string, Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$messages = YamlParser::parse($string);
|
||||||
|
|
||||||
|
if (is_array($messages)) {
|
||||||
|
static::fromArray($messages, $translations);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
inc/gettext/src/Generators/Csv.php
Normal file
56
inc/gettext/src/Generators/Csv.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\HeadersGeneratorTrait;
|
||||||
|
use Gettext\Utils\CsvTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to export translations to csv.
|
||||||
|
*/
|
||||||
|
class Csv extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
use HeadersGeneratorTrait;
|
||||||
|
use CsvTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'includeHeaders' => false,
|
||||||
|
'delimiter' => ",",
|
||||||
|
'enclosure' => '"',
|
||||||
|
'escape_char' => "\\"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@parentDoc}.
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
$handle = fopen('php://memory', 'w');
|
||||||
|
|
||||||
|
if ($options['includeHeaders']) {
|
||||||
|
static::fputcsv($handle, ['', '', static::generateHeaders($translations)], $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($translations as $translation) {
|
||||||
|
if ($translation->isDisabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = [$translation->getContext(), $translation->getOriginal(), $translation->getTranslation()];
|
||||||
|
|
||||||
|
if ($translation->hasPluralTranslations(true)) {
|
||||||
|
$line = array_merge($line, $translation->getPluralTranslations());
|
||||||
|
}
|
||||||
|
|
||||||
|
static::fputcsv($handle, $line, $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
rewind($handle);
|
||||||
|
$csv = stream_get_contents($handle);
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
return $csv;
|
||||||
|
}
|
||||||
|
}
|
39
inc/gettext/src/Generators/CsvDictionary.php
Normal file
39
inc/gettext/src/Generators/CsvDictionary.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\DictionaryTrait;
|
||||||
|
use Gettext\Utils\CsvTrait;
|
||||||
|
|
||||||
|
class CsvDictionary extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
use DictionaryTrait;
|
||||||
|
use CsvTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'includeHeaders' => false,
|
||||||
|
'delimiter' => ",",
|
||||||
|
'enclosure' => '"',
|
||||||
|
'escape_char' => "\\"
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@parentDoc}.
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
$handle = fopen('php://memory', 'w');
|
||||||
|
|
||||||
|
foreach (static::toArray($translations, $options['includeHeaders']) as $original => $translation) {
|
||||||
|
static::fputcsv($handle, [$original, $translation], $options);
|
||||||
|
}
|
||||||
|
|
||||||
|
rewind($handle);
|
||||||
|
$csv = stream_get_contents($handle);
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
return $csv;
|
||||||
|
}
|
||||||
|
}
|
22
inc/gettext/src/Generators/Generator.php
Normal file
22
inc/gettext/src/Generators/Generator.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
abstract class Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function toFile(Translations $translations, $file, array $options = [])
|
||||||
|
{
|
||||||
|
$content = static::toString($translations, $options);
|
||||||
|
|
||||||
|
if (file_put_contents($file, $content) === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
29
inc/gettext/src/Generators/GeneratorInterface.php
Normal file
29
inc/gettext/src/Generators/GeneratorInterface.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
interface GeneratorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Saves the translations in a file.
|
||||||
|
*
|
||||||
|
* @param Translations $translations
|
||||||
|
* @param string $file
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function toFile(Translations $translations, $file, array $options = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a string with the translations ready to save in a file.
|
||||||
|
*
|
||||||
|
* @param Translations $translations
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = []);
|
||||||
|
}
|
66
inc/gettext/src/Generators/Jed.php
Normal file
66
inc/gettext/src/Generators/Jed.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
class Jed extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
public static $options = [
|
||||||
|
'json' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@parentDoc}.
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$domain = $translations->getDomain() ?: 'messages';
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
return json_encode([
|
||||||
|
$domain => [
|
||||||
|
'' => [
|
||||||
|
'domain' => $domain,
|
||||||
|
'lang' => $translations->getLanguage() ?: 'en',
|
||||||
|
'plural-forms' => $translations->getHeader('Plural-Forms') ?: 'nplurals=2; plural=(n != 1);',
|
||||||
|
],
|
||||||
|
] + static::buildMessages($translations),
|
||||||
|
], $options['json']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array with all translations.
|
||||||
|
*
|
||||||
|
* @param Translations $translations
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function buildMessages(Translations $translations)
|
||||||
|
{
|
||||||
|
$pluralForm = $translations->getPluralForms();
|
||||||
|
$pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
|
||||||
|
$messages = [];
|
||||||
|
$context_glue = '\u0004';
|
||||||
|
|
||||||
|
foreach ($translations as $translation) {
|
||||||
|
if ($translation->isDisabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$key = ($translation->hasContext() ? $translation->getContext().$context_glue : '')
|
||||||
|
.$translation->getOriginal();
|
||||||
|
|
||||||
|
if ($translation->hasPluralTranslations(true)) {
|
||||||
|
$message = $translation->getPluralTranslations($pluralSize);
|
||||||
|
array_unshift($message, $translation->getTranslation());
|
||||||
|
} else {
|
||||||
|
$message = [$translation->getTranslation()];
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages[$key] = $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
}
|
26
inc/gettext/src/Generators/Json.php
Normal file
26
inc/gettext/src/Generators/Json.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
class Json extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
use MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'json' => 0,
|
||||||
|
'includeHeaders' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
return json_encode(static::toArray($translations, $options['includeHeaders'], true), $options['json']);
|
||||||
|
}
|
||||||
|
}
|
26
inc/gettext/src/Generators/JsonDictionary.php
Normal file
26
inc/gettext/src/Generators/JsonDictionary.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\DictionaryTrait;
|
||||||
|
|
||||||
|
class JsonDictionary extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
use DictionaryTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'json' => 0,
|
||||||
|
'includeHeaders' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@parentDoc}.
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
return json_encode(static::toArray($translations, $options['includeHeaders']), $options['json']);
|
||||||
|
}
|
||||||
|
}
|
140
inc/gettext/src/Generators/Mo.php
Normal file
140
inc/gettext/src/Generators/Mo.php
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\HeadersGeneratorTrait;
|
||||||
|
|
||||||
|
class Mo extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
use HeadersGeneratorTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'includeHeaders' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@parentDoc}.
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
$messages = [];
|
||||||
|
|
||||||
|
if ($options['includeHeaders']) {
|
||||||
|
$messages[''] = static::generateHeaders($translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($translations as $translation) {
|
||||||
|
if (!$translation->hasTranslation() || $translation->isDisabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($translation->hasContext()) {
|
||||||
|
$originalString = $translation->getContext()."\x04".$translation->getOriginal();
|
||||||
|
} else {
|
||||||
|
$originalString = $translation->getOriginal();
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages[$originalString] = $translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($messages);
|
||||||
|
$numEntries = count($messages);
|
||||||
|
$originalsTable = '';
|
||||||
|
$translationsTable = '';
|
||||||
|
$originalsIndex = [];
|
||||||
|
$translationsIndex = [];
|
||||||
|
$pluralForm = $translations->getPluralForms();
|
||||||
|
$pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
|
||||||
|
|
||||||
|
foreach ($messages as $originalString => $translation) {
|
||||||
|
if (is_string($translation)) {
|
||||||
|
// Headers
|
||||||
|
$translationString = $translation;
|
||||||
|
} else {
|
||||||
|
/* @var $translation \Gettext\Translation */
|
||||||
|
if ($translation->hasPlural() && $translation->hasPluralTranslations(true)) {
|
||||||
|
$originalString .= "\x00".$translation->getPlural();
|
||||||
|
$translationString = $translation->getTranslation();
|
||||||
|
$translationString .= "\x00".implode("\x00", $translation->getPluralTranslations($pluralSize));
|
||||||
|
} else {
|
||||||
|
$translationString = $translation->getTranslation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$originalsIndex[] = [
|
||||||
|
'relativeOffset' => strlen($originalsTable),
|
||||||
|
'length' => strlen($originalString)
|
||||||
|
];
|
||||||
|
$originalsTable .= $originalString."\x00";
|
||||||
|
$translationsIndex[] = [
|
||||||
|
'relativeOffset' => strlen($translationsTable),
|
||||||
|
'length' => strlen($translationString)
|
||||||
|
];
|
||||||
|
$translationsTable .= $translationString."\x00";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset of table with the original strings index: right after the header (which is 7 words)
|
||||||
|
$originalsIndexOffset = 7 * 4;
|
||||||
|
|
||||||
|
// Size of table with the original strings index
|
||||||
|
$originalsIndexSize = $numEntries * (4 + 4);
|
||||||
|
|
||||||
|
// Offset of table with the translation strings index: right after the original strings index table
|
||||||
|
$translationsIndexOffset = $originalsIndexOffset + $originalsIndexSize;
|
||||||
|
|
||||||
|
// Size of table with the translation strings index
|
||||||
|
$translationsIndexSize = $numEntries * (4 + 4);
|
||||||
|
|
||||||
|
// Hashing table starts after the header and after the index table
|
||||||
|
$originalsStringsOffset = $translationsIndexOffset + $translationsIndexSize;
|
||||||
|
|
||||||
|
// Translations start after the keys
|
||||||
|
$translationsStringsOffset = $originalsStringsOffset + strlen($originalsTable);
|
||||||
|
|
||||||
|
// Let's generate the .mo file binary data
|
||||||
|
$mo = '';
|
||||||
|
|
||||||
|
// Magic number
|
||||||
|
$mo .= pack('L', 0x950412de);
|
||||||
|
|
||||||
|
// File format revision
|
||||||
|
$mo .= pack('L', 0);
|
||||||
|
|
||||||
|
// Number of strings
|
||||||
|
$mo .= pack('L', $numEntries);
|
||||||
|
|
||||||
|
// Offset of table with original strings
|
||||||
|
$mo .= pack('L', $originalsIndexOffset);
|
||||||
|
|
||||||
|
// Offset of table with translation strings
|
||||||
|
$mo .= pack('L', $translationsIndexOffset);
|
||||||
|
|
||||||
|
// Size of hashing table: we don't use it.
|
||||||
|
$mo .= pack('L', 0);
|
||||||
|
|
||||||
|
// Offset of hashing table: it would start right after the translations index table
|
||||||
|
$mo .= pack('L', $translationsIndexOffset + $translationsIndexSize);
|
||||||
|
|
||||||
|
// Write the lengths & offsets of the original strings
|
||||||
|
foreach ($originalsIndex as $info) {
|
||||||
|
$mo .= pack('L', $info['length']);
|
||||||
|
$mo .= pack('L', $originalsStringsOffset + $info['relativeOffset']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the lengths & offsets of the translated strings
|
||||||
|
foreach ($translationsIndex as $info) {
|
||||||
|
$mo .= pack('L', $info['length']);
|
||||||
|
$mo .= pack('L', $translationsStringsOffset + $info['relativeOffset']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write original strings
|
||||||
|
$mo .= $originalsTable;
|
||||||
|
|
||||||
|
// Write translation strings
|
||||||
|
$mo .= $translationsTable;
|
||||||
|
|
||||||
|
return $mo;
|
||||||
|
}
|
||||||
|
}
|
40
inc/gettext/src/Generators/PhpArray.php
Normal file
40
inc/gettext/src/Generators/PhpArray.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
class PhpArray extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
use MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'includeHeaders' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$array = static::generate($translations, $options);
|
||||||
|
|
||||||
|
return '<?php return '.var_export($array, true).';';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates an array with the translations.
|
||||||
|
*
|
||||||
|
* @param Translations $translations
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function generate(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
return static::toArray($translations, $options['includeHeaders'], true);
|
||||||
|
}
|
||||||
|
}
|
145
inc/gettext/src/Generators/Po.php
Normal file
145
inc/gettext/src/Generators/Po.php
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
class Po extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
public static $options = [
|
||||||
|
'noLocation' => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@parentDoc}.
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
$pluralForm = $translations->getPluralForms();
|
||||||
|
$pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
|
||||||
|
$lines = ['msgid ""', 'msgstr ""'];
|
||||||
|
|
||||||
|
foreach ($translations->getHeaders() as $name => $value) {
|
||||||
|
$lines[] = sprintf('"%s: %s\\n"', $name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines[] = '';
|
||||||
|
|
||||||
|
//Translations
|
||||||
|
foreach ($translations as $translation) {
|
||||||
|
if ($translation->hasComments()) {
|
||||||
|
foreach ($translation->getComments() as $comment) {
|
||||||
|
$lines[] = '# '.$comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($translation->hasExtractedComments()) {
|
||||||
|
foreach ($translation->getExtractedComments() as $comment) {
|
||||||
|
$lines[] = '#. '.$comment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$options['noLocation'] && $translation->hasReferences()) {
|
||||||
|
foreach ($translation->getReferences() as $reference) {
|
||||||
|
$lines[] = '#: '.$reference[0].(!is_null($reference[1]) ? ':'.$reference[1] : null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($translation->hasFlags()) {
|
||||||
|
$lines[] = '#, '.implode(',', $translation->getFlags());
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefix = $translation->isDisabled() ? '#~ ' : '';
|
||||||
|
|
||||||
|
if ($translation->hasContext()) {
|
||||||
|
$lines[] = $prefix.'msgctxt '.static::convertString($translation->getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
static::addLines($lines, $prefix.'msgid', $translation->getOriginal());
|
||||||
|
|
||||||
|
if ($translation->hasPlural()) {
|
||||||
|
static::addLines($lines, $prefix.'msgid_plural', $translation->getPlural());
|
||||||
|
static::addLines($lines, $prefix.'msgstr[0]', $translation->getTranslation());
|
||||||
|
|
||||||
|
foreach ($translation->getPluralTranslations($pluralSize) as $k => $v) {
|
||||||
|
static::addLines($lines, $prefix.'msgstr['.($k + 1).']', $v);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
static::addLines($lines, $prefix.'msgstr', $translation->getTranslation());
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines[] = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode("\n", $lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes and adds double quotes to a string.
|
||||||
|
*
|
||||||
|
* @param string $string
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function multilineQuote($string)
|
||||||
|
{
|
||||||
|
$lines = explode("\n", $string);
|
||||||
|
$last = count($lines) - 1;
|
||||||
|
|
||||||
|
foreach ($lines as $k => $line) {
|
||||||
|
if ($k === $last) {
|
||||||
|
$lines[$k] = static::convertString($line);
|
||||||
|
} else {
|
||||||
|
$lines[$k] = static::convertString($line."\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add one or more lines depending whether the string is multiline or not.
|
||||||
|
*
|
||||||
|
* @param array &$lines
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
protected static function addLines(array &$lines, $name, $value)
|
||||||
|
{
|
||||||
|
$newLines = static::multilineQuote($value);
|
||||||
|
|
||||||
|
if (count($newLines) === 1) {
|
||||||
|
$lines[] = $name.' '.$newLines[0];
|
||||||
|
} else {
|
||||||
|
$lines[] = $name.' ""';
|
||||||
|
|
||||||
|
foreach ($newLines as $line) {
|
||||||
|
$lines[] = $line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a string to its PO representation.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function convertString($value)
|
||||||
|
{
|
||||||
|
return '"'.strtr(
|
||||||
|
$value,
|
||||||
|
[
|
||||||
|
"\x00" => '',
|
||||||
|
'\\' => '\\\\',
|
||||||
|
"\t" => '\t',
|
||||||
|
"\r" => '\r',
|
||||||
|
"\n" => '\n',
|
||||||
|
'"' => '\\"',
|
||||||
|
]
|
||||||
|
).'"';
|
||||||
|
}
|
||||||
|
}
|
122
inc/gettext/src/Generators/Xliff.php
Normal file
122
inc/gettext/src/Generators/Xliff.php
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translation;
|
||||||
|
use Gettext\Translations;
|
||||||
|
use DOMDocument;
|
||||||
|
|
||||||
|
class Xliff extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
const UNIT_ID_REGEXP = '/^XLIFF_UNIT_ID: (.*)$/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$dom = new DOMDocument('1.0', 'utf-8');
|
||||||
|
$dom->formatOutput = true;
|
||||||
|
$xliff = $dom->appendChild($dom->createElement('xliff'));
|
||||||
|
$xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0');
|
||||||
|
$xliff->setAttribute('version', '2.0');
|
||||||
|
$xliff->setAttribute('srcLang', $translations->getLanguage());
|
||||||
|
$xliff->setAttribute('trgLang', $translations->getLanguage());
|
||||||
|
$file = $xliff->appendChild($dom->createElement('file'));
|
||||||
|
$file->setAttribute('id', $translations->getDomain().'.'.$translations->getLanguage());
|
||||||
|
|
||||||
|
//Save headers as notes
|
||||||
|
$notes = $dom->createElement('notes');
|
||||||
|
|
||||||
|
foreach ($translations->getHeaders() as $name => $value) {
|
||||||
|
$notes->appendChild(static::createTextNode($dom, 'note', $value))->setAttribute('id', $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($notes->hasChildNodes()) {
|
||||||
|
$file->appendChild($notes);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($translations as $translation) {
|
||||||
|
//Find an XLIFF unit ID, if one is available; otherwise generate
|
||||||
|
$unitId = static::getUnitID($translation)?:md5($translation->getContext().$translation->getOriginal());
|
||||||
|
|
||||||
|
$unit = $dom->createElement('unit');
|
||||||
|
$unit->setAttribute('id', $unitId);
|
||||||
|
|
||||||
|
//Save comments as notes
|
||||||
|
$notes = $dom->createElement('notes');
|
||||||
|
|
||||||
|
$notes->appendChild(static::createTextNode($dom, 'note', $translation->getContext()))
|
||||||
|
->setAttribute('category', 'context');
|
||||||
|
|
||||||
|
foreach ($translation->getComments() as $comment) {
|
||||||
|
//Skip XLIFF unit ID comments.
|
||||||
|
if (preg_match(static::UNIT_ID_REGEXP, $comment)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$notes->appendChild(static::createTextNode($dom, 'note', $comment))
|
||||||
|
->setAttribute('category', 'comment');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($translation->getExtractedComments() as $comment) {
|
||||||
|
$notes->appendChild(static::createTextNode($dom, 'note', $comment))
|
||||||
|
->setAttribute('category', 'extracted-comment');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($translation->getFlags() as $flag) {
|
||||||
|
$notes->appendChild(static::createTextNode($dom, 'note', $flag))
|
||||||
|
->setAttribute('category', 'flag');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($translation->getReferences() as $reference) {
|
||||||
|
$notes->appendChild(static::createTextNode($dom, 'note', $reference[0].':'.$reference[1]))
|
||||||
|
->setAttribute('category', 'reference');
|
||||||
|
}
|
||||||
|
|
||||||
|
$unit->appendChild($notes);
|
||||||
|
|
||||||
|
$segment = $unit->appendChild($dom->createElement('segment'));
|
||||||
|
$segment->appendChild(static::createTextNode($dom, 'source', $translation->getOriginal()));
|
||||||
|
$segment->appendChild(static::createTextNode($dom, 'target', $translation->getTranslation()));
|
||||||
|
|
||||||
|
foreach ($translation->getPluralTranslations() as $plural) {
|
||||||
|
if ($plural !== '') {
|
||||||
|
$segment->appendChild(static::createTextNode($dom, 'target', $plural));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$file->appendChild($unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $dom->saveXML();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function createTextNode(DOMDocument $dom, $name, $string)
|
||||||
|
{
|
||||||
|
$node = $dom->createElement($name);
|
||||||
|
$text = (preg_match('/[&<>]/', $string) === 1)
|
||||||
|
? $dom->createCDATASection($string)
|
||||||
|
: $dom->createTextNode($string);
|
||||||
|
$node->appendChild($text);
|
||||||
|
|
||||||
|
return $node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the translation's unit ID, if one is available.
|
||||||
|
*
|
||||||
|
* @param Translation $translation
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function getUnitID(Translation $translation)
|
||||||
|
{
|
||||||
|
foreach ($translation->getComments() as $comment) {
|
||||||
|
if (preg_match(static::UNIT_ID_REGEXP, $comment, $matches)) {
|
||||||
|
return $matches[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
32
inc/gettext/src/Generators/Yaml.php
Normal file
32
inc/gettext/src/Generators/Yaml.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\MultidimensionalArrayTrait;
|
||||||
|
use Symfony\Component\Yaml\Yaml as YamlDumper;
|
||||||
|
|
||||||
|
class Yaml extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
use MultidimensionalArrayTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'includeHeaders' => false,
|
||||||
|
'indent' => 2,
|
||||||
|
'inline' => 4,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
return YamlDumper::dump(
|
||||||
|
static::toArray($translations, $options['includeHeaders']),
|
||||||
|
$options['inline'],
|
||||||
|
$options['indent']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
32
inc/gettext/src/Generators/YamlDictionary.php
Normal file
32
inc/gettext/src/Generators/YamlDictionary.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Generators;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
use Gettext\Utils\DictionaryTrait;
|
||||||
|
use Symfony\Component\Yaml\Yaml as YamlDumper;
|
||||||
|
|
||||||
|
class YamlDictionary extends Generator implements GeneratorInterface
|
||||||
|
{
|
||||||
|
use DictionaryTrait;
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'includeHeaders' => false,
|
||||||
|
'indent' => 2,
|
||||||
|
'inline' => 3,
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function toString(Translations $translations, array $options = [])
|
||||||
|
{
|
||||||
|
$options += static::$options;
|
||||||
|
|
||||||
|
return YamlDumper::dump(
|
||||||
|
static::toArray($translations, $options['includeHeaders']),
|
||||||
|
$options['inline'],
|
||||||
|
$options['indent']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
161
inc/gettext/src/GettextTranslator.php
Normal file
161
inc/gettext/src/GettextTranslator.php
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext;
|
||||||
|
|
||||||
|
class GettextTranslator extends BaseTranslator implements TranslatorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Constructor. Detects the current language using the environment variables.
|
||||||
|
*
|
||||||
|
* @param string $language
|
||||||
|
*/
|
||||||
|
public function __construct($language = null)
|
||||||
|
{
|
||||||
|
if (!function_exists('gettext')) {
|
||||||
|
throw new \RuntimeException('This class require the gettext extension for PHP');
|
||||||
|
}
|
||||||
|
|
||||||
|
//detects the language environment respecting the priority order
|
||||||
|
//http://php.net/manual/en/function.gettext.php#114062
|
||||||
|
if (empty($language)) {
|
||||||
|
$language = getenv('LANGUAGE') ?: getenv('LC_ALL') ?: getenv('LC_MESSAGES') ?: getenv('LANG');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($language)) {
|
||||||
|
$this->setLanguage($language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the current locale.
|
||||||
|
*
|
||||||
|
* @param string $language
|
||||||
|
* @param int|null $category
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setLanguage($language, $category = null)
|
||||||
|
{
|
||||||
|
if ($category === null) {
|
||||||
|
$category = defined('LC_MESSAGES') ? LC_MESSAGES : LC_ALL;
|
||||||
|
}
|
||||||
|
|
||||||
|
setlocale($category, $language);
|
||||||
|
putenv('LANGUAGE='.$language);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a gettext domain.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $path
|
||||||
|
* @param bool $default
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function loadDomain($domain, $path = null, $default = true)
|
||||||
|
{
|
||||||
|
bindtextdomain($domain, $path);
|
||||||
|
bind_textdomain_codeset($domain, 'UTF-8');
|
||||||
|
|
||||||
|
if ($default) {
|
||||||
|
textdomain($domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function gettext($original)
|
||||||
|
{
|
||||||
|
return gettext($original);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function ngettext($original, $plural, $value)
|
||||||
|
{
|
||||||
|
return ngettext($original, $plural, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function dngettext($domain, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
return dngettext($domain, $original, $plural, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function npgettext($context, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
$message = $context."\x04".$original;
|
||||||
|
$translation = ngettext($message, $plural, $value);
|
||||||
|
|
||||||
|
return ($translation === $message) ? $original : $translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function pgettext($context, $original)
|
||||||
|
{
|
||||||
|
$message = $context."\x04".$original;
|
||||||
|
$translation = gettext($message);
|
||||||
|
|
||||||
|
return ($translation === $message) ? $original : $translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function dgettext($domain, $original)
|
||||||
|
{
|
||||||
|
return dgettext($domain, $original);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function dpgettext($domain, $context, $original)
|
||||||
|
{
|
||||||
|
$message = $context."\x04".$original;
|
||||||
|
$translation = dgettext($domain, $message);
|
||||||
|
|
||||||
|
return ($translation === $message) ? $original : $translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function dnpgettext($domain, $context, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
$message = $context."\x04".$original;
|
||||||
|
$translation = dngettext($domain, $message, $plural, $value);
|
||||||
|
|
||||||
|
return ($translation === $message) ? $original : $translation;
|
||||||
|
}
|
||||||
|
}
|
221
inc/gettext/src/Merge.php
Normal file
221
inc/gettext/src/Merge.php
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static class with merge contants.
|
||||||
|
*/
|
||||||
|
final class Merge
|
||||||
|
{
|
||||||
|
const ADD = 1;
|
||||||
|
const REMOVE = 2;
|
||||||
|
|
||||||
|
const HEADERS_ADD = 4;
|
||||||
|
const HEADERS_REMOVE = 8;
|
||||||
|
const HEADERS_OVERRIDE = 16;
|
||||||
|
|
||||||
|
const LANGUAGE_OVERRIDE = 32;
|
||||||
|
const DOMAIN_OVERRIDE = 64;
|
||||||
|
const TRANSLATION_OVERRIDE = 128;
|
||||||
|
|
||||||
|
const COMMENTS_OURS = 256;
|
||||||
|
const COMMENTS_THEIRS = 512;
|
||||||
|
|
||||||
|
const EXTRACTED_COMMENTS_OURS = 1024;
|
||||||
|
const EXTRACTED_COMMENTS_THEIRS = 2048;
|
||||||
|
|
||||||
|
const FLAGS_OURS = 4096;
|
||||||
|
const FLAGS_THEIRS = 8192;
|
||||||
|
|
||||||
|
const REFERENCES_OURS = 16384;
|
||||||
|
const REFERENCES_THEIRS = 32768;
|
||||||
|
|
||||||
|
const DEFAULTS = 5; //1 + 4
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the flags of two translations.
|
||||||
|
*
|
||||||
|
* @param Translation $from
|
||||||
|
* @param Translation $to
|
||||||
|
* @param int $options
|
||||||
|
*/
|
||||||
|
public static function mergeFlags(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||||
|
{
|
||||||
|
if ($options & self::FLAGS_THEIRS) {
|
||||||
|
$to->deleteFlags();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($options & self::FLAGS_OURS)) {
|
||||||
|
foreach ($from->getFlags() as $flag) {
|
||||||
|
$to->addFlag($flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the extracted comments of two translations.
|
||||||
|
*
|
||||||
|
* @param Translation $from
|
||||||
|
* @param Translation $to
|
||||||
|
* @param int $options
|
||||||
|
*/
|
||||||
|
public static function mergeExtractedComments(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||||
|
{
|
||||||
|
if ($options & self::EXTRACTED_COMMENTS_THEIRS) {
|
||||||
|
$to->deleteExtractedComments();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($options & self::EXTRACTED_COMMENTS_OURS)) {
|
||||||
|
foreach ($from->getExtractedComments() as $comment) {
|
||||||
|
$to->addExtractedComment($comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the comments of two translations.
|
||||||
|
*
|
||||||
|
* @param Translation $from
|
||||||
|
* @param Translation $to
|
||||||
|
* @param int $options
|
||||||
|
*/
|
||||||
|
public static function mergeComments(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||||
|
{
|
||||||
|
if ($options & self::COMMENTS_THEIRS) {
|
||||||
|
$to->deleteComments();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($options & self::COMMENTS_OURS)) {
|
||||||
|
foreach ($from->getComments() as $comment) {
|
||||||
|
$to->addComment($comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the references of two translations.
|
||||||
|
*
|
||||||
|
* @param Translation $from
|
||||||
|
* @param Translation $to
|
||||||
|
* @param int $options
|
||||||
|
*/
|
||||||
|
public static function mergeReferences(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||||
|
{
|
||||||
|
if ($options & self::REFERENCES_THEIRS) {
|
||||||
|
$to->deleteReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!($options & self::REFERENCES_OURS)) {
|
||||||
|
foreach ($from->getReferences() as $reference) {
|
||||||
|
$to->addReference($reference[0], $reference[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the translations of two translations.
|
||||||
|
*
|
||||||
|
* @param Translation $from
|
||||||
|
* @param Translation $to
|
||||||
|
* @param int $options
|
||||||
|
*/
|
||||||
|
public static function mergeTranslation(Translation $from, Translation $to, $options = self::DEFAULTS)
|
||||||
|
{
|
||||||
|
$override = (boolean) ($options & self::TRANSLATION_OVERRIDE);
|
||||||
|
|
||||||
|
if (!$to->hasTranslation() || ($from->hasTranslation() && $override)) {
|
||||||
|
$to->setTranslation($from->getTranslation());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$to->hasPlural() || ($from->hasPlural() && $override)) {
|
||||||
|
$to->setPlural($from->getPlural());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$to->hasPluralTranslations() || ($from->hasPluralTranslations() && $override)) {
|
||||||
|
$to->setPluralTranslations($from->getPluralTranslations());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the translations of two translations.
|
||||||
|
*
|
||||||
|
* @param Translations $from
|
||||||
|
* @param Translations $to
|
||||||
|
* @param int $options
|
||||||
|
*/
|
||||||
|
public static function mergeTranslations(Translations $from, Translations $to, $options = self::DEFAULTS)
|
||||||
|
{
|
||||||
|
if ($options & self::REMOVE) {
|
||||||
|
$filtered = [];
|
||||||
|
|
||||||
|
foreach ($to as $entry) {
|
||||||
|
if ($from->find($entry)) {
|
||||||
|
$filtered[$entry->getId()] = $entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$to->exchangeArray($filtered);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($from as $entry) {
|
||||||
|
if (($existing = $to->find($entry))) {
|
||||||
|
$existing->mergeWith($entry, $options);
|
||||||
|
} elseif ($options & self::ADD) {
|
||||||
|
$to[] = $entry->getClone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merge the headers of two translations.
|
||||||
|
*
|
||||||
|
* @param Translations $from
|
||||||
|
* @param Translations $to
|
||||||
|
* @param int $options
|
||||||
|
*/
|
||||||
|
public static function mergeHeaders(Translations $from, Translations $to, $options = self::DEFAULTS)
|
||||||
|
{
|
||||||
|
if ($options & self::HEADERS_REMOVE) {
|
||||||
|
foreach (array_keys($to->getHeaders()) as $name) {
|
||||||
|
if ($from->getHeader($name) === null) {
|
||||||
|
$to->deleteHeader($name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($from->getHeaders() as $name => $value) {
|
||||||
|
$current = $to->getHeader($name);
|
||||||
|
|
||||||
|
if (empty($current)) {
|
||||||
|
if ($options & self::HEADERS_ADD) {
|
||||||
|
$to->setHeader($name, $value);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($name) {
|
||||||
|
case Translations::HEADER_LANGUAGE:
|
||||||
|
case Translations::HEADER_PLURAL:
|
||||||
|
if ($options & self::LANGUAGE_OVERRIDE) {
|
||||||
|
$to->setHeader($name, $value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Translations::HEADER_DOMAIN:
|
||||||
|
if ($options & self::DOMAIN_OVERRIDE) {
|
||||||
|
$to->setHeader($name, $value);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if ($options & self::HEADERS_OVERRIDE) {
|
||||||
|
$to->setHeader($name, $value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
537
inc/gettext/src/Translation.php
Normal file
537
inc/gettext/src/Translation.php
Normal file
@ -0,0 +1,537 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to manage a translation string.
|
||||||
|
*/
|
||||||
|
class Translation
|
||||||
|
{
|
||||||
|
protected $id;
|
||||||
|
protected $context;
|
||||||
|
protected $original;
|
||||||
|
protected $translation = '';
|
||||||
|
protected $plural;
|
||||||
|
protected $pluralTranslation = [];
|
||||||
|
protected $references = [];
|
||||||
|
protected $comments = [];
|
||||||
|
protected $extractedComments = [];
|
||||||
|
protected $flags = [];
|
||||||
|
protected $disabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates the id of a translation (context + glue + original).
|
||||||
|
*
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function generateId($context, $original)
|
||||||
|
{
|
||||||
|
return "{$context}\004{$original}";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of a Translation object.
|
||||||
|
*
|
||||||
|
* This is a factory method that will work even when Translation is extended.
|
||||||
|
*
|
||||||
|
* @param string $context The context of the translation
|
||||||
|
* @param string $original The original string
|
||||||
|
* @param string $plural The original plural string
|
||||||
|
* @return static New Translation instance
|
||||||
|
*/
|
||||||
|
public static function create($context, $original, $plural = '')
|
||||||
|
{
|
||||||
|
return new static($context, $original, $plural);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct.
|
||||||
|
*
|
||||||
|
* @param string $context The context of the translation
|
||||||
|
* @param string $original The original string
|
||||||
|
* @param string $plural The original plural string
|
||||||
|
*/
|
||||||
|
public function __construct($context, $original, $plural = '')
|
||||||
|
{
|
||||||
|
$this->context = (string) $context;
|
||||||
|
$this->original = (string) $original;
|
||||||
|
|
||||||
|
$this->setPlural($plural);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clones this translation.
|
||||||
|
*
|
||||||
|
* @param null|string $context Optional new context
|
||||||
|
* @param null|string $original Optional new original
|
||||||
|
*
|
||||||
|
* @return Translation
|
||||||
|
*/
|
||||||
|
public function getClone($context = null, $original = null)
|
||||||
|
{
|
||||||
|
$new = clone $this;
|
||||||
|
|
||||||
|
if ($context !== null) {
|
||||||
|
$new->context = (string) $context;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($original !== null) {
|
||||||
|
$new->original = (string) $original;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $new;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the id of this translation.
|
||||||
|
* @warning The use of this function to set a custom ID will prevent
|
||||||
|
* Translations::find from matching this translation.
|
||||||
|
*
|
||||||
|
* @param string $id
|
||||||
|
*/
|
||||||
|
public function setId($id)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the id of this translation.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
if ($this->id === null) {
|
||||||
|
return static::generateId($this->context, $this->original);
|
||||||
|
}
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the translation matches with the arguments.
|
||||||
|
*
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function is($context, $original = '')
|
||||||
|
{
|
||||||
|
return (($this->context === $context) && ($this->original === $original)) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable or disable the translation
|
||||||
|
*
|
||||||
|
* @param bool $disabled
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setDisabled($disabled)
|
||||||
|
{
|
||||||
|
$this->disabled = (bool) $disabled;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the translation is disabled
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isDisabled()
|
||||||
|
{
|
||||||
|
return $this->disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the original string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getOriginal()
|
||||||
|
{
|
||||||
|
return $this->original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the original string is empty or not.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasOriginal()
|
||||||
|
{
|
||||||
|
return ($this->original !== '') ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the translation string.
|
||||||
|
*
|
||||||
|
* @param string $translation
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setTranslation($translation)
|
||||||
|
{
|
||||||
|
$this->translation = (string) $translation;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the translation string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getTranslation()
|
||||||
|
{
|
||||||
|
return $this->translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the translation string is empty or not.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasTranslation()
|
||||||
|
{
|
||||||
|
return ($this->translation !== '') ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the plural translation string.
|
||||||
|
*
|
||||||
|
* @param string $plural
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setPlural($plural)
|
||||||
|
{
|
||||||
|
$this->plural = (string) $plural;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the plural translation string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getPlural()
|
||||||
|
{
|
||||||
|
return $this->plural;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the plural translation string is empty or not.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasPlural()
|
||||||
|
{
|
||||||
|
return ($this->plural !== '') ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a new plural translation.
|
||||||
|
*
|
||||||
|
* @param array $plural
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setPluralTranslations(array $plural)
|
||||||
|
{
|
||||||
|
$this->pluralTranslation = $plural;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all plural translations.
|
||||||
|
*
|
||||||
|
* @param int $size
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPluralTranslations($size = null)
|
||||||
|
{
|
||||||
|
if ($size === null) {
|
||||||
|
return $this->pluralTranslation;
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = count($this->pluralTranslation);
|
||||||
|
|
||||||
|
if ($size > $current) {
|
||||||
|
return $this->pluralTranslation + array_fill(0, $size, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($size < $current) {
|
||||||
|
return array_slice($this->pluralTranslation, 0, $size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->pluralTranslation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are any plural translation.
|
||||||
|
*
|
||||||
|
* @param bool $checkContent
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasPluralTranslations($checkContent = false)
|
||||||
|
{
|
||||||
|
if ($checkContent) {
|
||||||
|
return implode('', $this->pluralTranslation) !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return !empty($this->pluralTranslation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all plural translations.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function deletePluralTranslation()
|
||||||
|
{
|
||||||
|
$this->pluralTranslation = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the context of this translation.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getContext()
|
||||||
|
{
|
||||||
|
return $this->context;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the context is empty or not.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasContext()
|
||||||
|
{
|
||||||
|
return (isset($this->context) && ($this->context !== '')) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new reference for this translation.
|
||||||
|
*
|
||||||
|
* @param string $filename The file path where the translation has been found
|
||||||
|
* @param null|int $line The line number where the translation has been found
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addReference($filename, $line = null)
|
||||||
|
{
|
||||||
|
$key = "{$filename}:{$line}";
|
||||||
|
$this->references[$key] = [$filename, $line];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the translation has any reference.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasReferences()
|
||||||
|
{
|
||||||
|
return !empty($this->references);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all references for this translation.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getReferences()
|
||||||
|
{
|
||||||
|
return array_values($this->references);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all references.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function deleteReferences()
|
||||||
|
{
|
||||||
|
$this->references = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new comment for this translation.
|
||||||
|
*
|
||||||
|
* @param string $comment
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addComment($comment)
|
||||||
|
{
|
||||||
|
if (!in_array($comment, $this->comments, true)) {
|
||||||
|
$this->comments[] = $comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the translation has any comment.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasComments()
|
||||||
|
{
|
||||||
|
return isset($this->comments[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all comments for this translation.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getComments()
|
||||||
|
{
|
||||||
|
return $this->comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all comments.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function deleteComments()
|
||||||
|
{
|
||||||
|
$this->comments = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new extracted comment for this translation.
|
||||||
|
*
|
||||||
|
* @param string $comment
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addExtractedComment($comment)
|
||||||
|
{
|
||||||
|
if (!in_array($comment, $this->extractedComments, true)) {
|
||||||
|
$this->extractedComments[] = $comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the translation has any extracted comment.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasExtractedComments()
|
||||||
|
{
|
||||||
|
return isset($this->extractedComments[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all extracted comments for this translation.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getExtractedComments()
|
||||||
|
{
|
||||||
|
return $this->extractedComments;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all extracted comments.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function deleteExtractedComments()
|
||||||
|
{
|
||||||
|
$this->extractedComments = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new flag for this translation.
|
||||||
|
*
|
||||||
|
* @param string $flag
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function addFlag($flag)
|
||||||
|
{
|
||||||
|
if (!in_array($flag, $this->flags, true)) {
|
||||||
|
$this->flags[] = $flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the translation has any flag.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasFlags()
|
||||||
|
{
|
||||||
|
return isset($this->flags[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all extracted flags for this translation.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getFlags()
|
||||||
|
{
|
||||||
|
return $this->flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all flags.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function deleteFlags()
|
||||||
|
{
|
||||||
|
$this->flags = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges this translation with other translation.
|
||||||
|
*
|
||||||
|
* @param Translation $translation The translation to merge with
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function mergeWith(Translation $translation, $options = Merge::DEFAULTS)
|
||||||
|
{
|
||||||
|
Merge::mergeTranslation($translation, $this, $options);
|
||||||
|
Merge::mergeReferences($translation, $this, $options);
|
||||||
|
Merge::mergeComments($translation, $this, $options);
|
||||||
|
Merge::mergeExtractedComments($translation, $this, $options);
|
||||||
|
Merge::mergeFlags($translation, $this, $options);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
498
inc/gettext/src/Translations.php
Normal file
498
inc/gettext/src/Translations.php
Normal file
@ -0,0 +1,498 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext;
|
||||||
|
|
||||||
|
use Gettext\Languages\Language;
|
||||||
|
use BadMethodCallException;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use ArrayObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to manage a collection of translations.
|
||||||
|
*
|
||||||
|
* @method static $this fromBladeFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromBladeString(string $string, array $options = [])
|
||||||
|
* @method $this addFromBladeFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromBladeString(string $string, array $options = [])
|
||||||
|
* @method static $this fromCsvFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromCsvString(string $string, array $options = [])
|
||||||
|
* @method $this addFromCsvFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromCsvString(string $string, array $options = [])
|
||||||
|
* @method bool toCsvFile(string $filename, array $options = [])
|
||||||
|
* @method string toCsvString(array $options = [])
|
||||||
|
* @method static $this fromCsvDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromCsvDictionaryString(string $string, array $options = [])
|
||||||
|
* @method $this addFromCsvDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromCsvDictionaryString(string $string, array $options = [])
|
||||||
|
* @method bool toCsvDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method string toCsvDictionaryString(array $options = [])
|
||||||
|
* @method static $this fromJedFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromJedString(string $string, array $options = [])
|
||||||
|
* @method $this addFromJedFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromJedString(string $string, array $options = [])
|
||||||
|
* @method bool toJedFile(string $filename, array $options = [])
|
||||||
|
* @method string toJedString(array $options = [])
|
||||||
|
* @method static $this fromJsCodeFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromJsCodeString(string $string, array $options = [])
|
||||||
|
* @method $this addFromJsCodeFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromJsCodeString(string $string, array $options = [])
|
||||||
|
* @method static $this fromJsonFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromJsonString(string $string, array $options = [])
|
||||||
|
* @method $this addFromJsonFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromJsonString(string $string, array $options = [])
|
||||||
|
* @method bool toJsonFile(string $filename, array $options = [])
|
||||||
|
* @method string toJsonString(array $options = [])
|
||||||
|
* @method static $this fromJsonDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromJsonDictionaryString(string $string, array $options = [])
|
||||||
|
* @method $this addFromJsonDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromJsonDictionaryString(string $string, array $options = [])
|
||||||
|
* @method bool toJsonDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method string toJsonDictionaryString(array $options = [])
|
||||||
|
* @method static $this fromMoFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromMoString(string $string, array $options = [])
|
||||||
|
* @method $this addFromMoFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromMoString(string $string, array $options = [])
|
||||||
|
* @method bool toMoFile(string $filename, array $options = [])
|
||||||
|
* @method string toMoString(array $options = [])
|
||||||
|
* @method static $this fromPhpArrayFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromPhpArrayString(string $string, array $options = [])
|
||||||
|
* @method $this addFromPhpArrayFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromPhpArrayString(string $string, array $options = [])
|
||||||
|
* @method bool toPhpArrayFile(string $filename, array $options = [])
|
||||||
|
* @method string toPhpArrayString(array $options = [])
|
||||||
|
* @method static $this fromPhpCodeFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromPhpCodeString(string $string, array $options = [])
|
||||||
|
* @method $this addFromPhpCodeFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromPhpCodeString(string $string, array $options = [])
|
||||||
|
* @method static $this fromPoFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromPoString(string $string, array $options = [])
|
||||||
|
* @method $this addFromPoFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromPoString(string $string, array $options = [])
|
||||||
|
* @method bool toPoFile(string $filename, array $options = [])
|
||||||
|
* @method string toPoString(array $options = [])
|
||||||
|
* @method static $this fromTwigFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromTwigString(string $string, array $options = [])
|
||||||
|
* @method $this addFromTwigFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromTwigString(string $string, array $options = [])
|
||||||
|
* @method static $this fromVueJsFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromVueJsString(string $filename, array $options = [])
|
||||||
|
* @method $this addFromVueJsFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromVueJsString(string $filename, array $options = [])
|
||||||
|
* @method static $this fromXliffFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromXliffString(string $string, array $options = [])
|
||||||
|
* @method $this addFromXliffFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromXliffString(string $string, array $options = [])
|
||||||
|
* @method bool toXliffFile(string $filename, array $options = [])
|
||||||
|
* @method string toXliffString(array $options = [])
|
||||||
|
* @method static $this fromYamlFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromYamlString(string $string, array $options = [])
|
||||||
|
* @method $this addFromYamlFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromYamlString(string $string, array $options = [])
|
||||||
|
* @method bool toYamlFile(string $filename, array $options = [])
|
||||||
|
* @method string toYamlString(array $options = [])
|
||||||
|
* @method static $this fromYamlDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method static $this fromYamlDictionaryString(string $string, array $options = [])
|
||||||
|
* @method $this addFromYamlDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method $this addFromYamlDictionaryString(string $string, array $options = [])
|
||||||
|
* @method bool toYamlDictionaryFile(string $filename, array $options = [])
|
||||||
|
* @method string toYamlDictionaryString(array $options = [])
|
||||||
|
*/
|
||||||
|
class Translations extends ArrayObject
|
||||||
|
{
|
||||||
|
const HEADER_LANGUAGE = 'Language';
|
||||||
|
const HEADER_PLURAL = 'Plural-Forms';
|
||||||
|
const HEADER_DOMAIN = 'X-Domain';
|
||||||
|
|
||||||
|
public static $options = [
|
||||||
|
'defaultHeaders' => [
|
||||||
|
'Project-Id-Version' => '',
|
||||||
|
'Report-Msgid-Bugs-To' => '',
|
||||||
|
'Last-Translator' => '',
|
||||||
|
'Language-Team' => '',
|
||||||
|
'MIME-Version' => '1.0',
|
||||||
|
'Content-Type' => 'text/plain; charset=UTF-8',
|
||||||
|
'Content-Transfer-Encoding' => '8bit',
|
||||||
|
],
|
||||||
|
'headersSorting' => false,
|
||||||
|
'defaultDateHeaders' => [
|
||||||
|
'POT-Creation-Date',
|
||||||
|
'PO-Revision-Date',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $headers;
|
||||||
|
|
||||||
|
protected $translationClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ArrayObject::__construct()
|
||||||
|
*/
|
||||||
|
public function __construct(
|
||||||
|
$input = [],
|
||||||
|
$flags = 0,
|
||||||
|
$iterator_class = 'ArrayIterator',
|
||||||
|
$translationClass = 'Gettext\Translation'
|
||||||
|
) {
|
||||||
|
$this->headers = static::$options['defaultHeaders'];
|
||||||
|
|
||||||
|
foreach (static::$options['defaultDateHeaders'] as $header) {
|
||||||
|
$this->headers[$header] = date('c');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->headers[self::HEADER_LANGUAGE] = '';
|
||||||
|
|
||||||
|
$this->translationClass = $translationClass;
|
||||||
|
|
||||||
|
parent::__construct($input, $flags, $iterator_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic method to create new instances using extractors
|
||||||
|
* For example: Translations::fromMoFile($filename, $options);.
|
||||||
|
*
|
||||||
|
* @return Translations
|
||||||
|
*/
|
||||||
|
public static function __callStatic($name, $arguments)
|
||||||
|
{
|
||||||
|
if (!preg_match('/^from(\w+)(File|String)$/i', $name, $matches)) {
|
||||||
|
throw new BadMethodCallException("The method $name does not exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
return call_user_func_array([new static(), 'add'.ucfirst($name)], $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic method to import/export the translations to a specific format
|
||||||
|
* For example: $translations->toMoFile($filename, $options);
|
||||||
|
* For example: $translations->addFromMoFile($filename, $options);.
|
||||||
|
*
|
||||||
|
* @return self|bool
|
||||||
|
*/
|
||||||
|
public function __call($name, $arguments)
|
||||||
|
{
|
||||||
|
if (!preg_match('/^(addFrom|to)(\w+)(File|String)$/i', $name, $matches)) {
|
||||||
|
throw new BadMethodCallException("The method $name does not exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($matches[1] === 'addFrom') {
|
||||||
|
$extractor = 'Gettext\\Extractors\\'.$matches[2].'::from'.$matches[3];
|
||||||
|
$source = array_shift($arguments);
|
||||||
|
$options = array_shift($arguments) ?: [];
|
||||||
|
|
||||||
|
call_user_func($extractor, $source, $this, $options);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
$generator = 'Gettext\\Generators\\'.$matches[2].'::to'.$matches[3];
|
||||||
|
|
||||||
|
array_unshift($arguments, $this);
|
||||||
|
|
||||||
|
return call_user_func_array($generator, $arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Magic method to clone each translation on clone the translations object.
|
||||||
|
*/
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
foreach ($this as $key => $translation) {
|
||||||
|
$array[$key] = clone $translation;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->exchangeArray($array);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control the new translations added.
|
||||||
|
*
|
||||||
|
* @param mixed $index
|
||||||
|
* @param Translation $value
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException If the value is not an instance of Gettext\Translation
|
||||||
|
*
|
||||||
|
* @return Translation
|
||||||
|
*/
|
||||||
|
public function offsetSet($index, $value)
|
||||||
|
{
|
||||||
|
if (!($value instanceof Translation)) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Only instances of Gettext\\Translation must be added to a Gettext\\Translations'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$id = $value->getId();
|
||||||
|
|
||||||
|
if ($this->offsetExists($id)) {
|
||||||
|
$this[$id]->mergeWith($value);
|
||||||
|
|
||||||
|
return $this[$id];
|
||||||
|
}
|
||||||
|
|
||||||
|
parent::offsetSet($id, $value);
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the plural definition.
|
||||||
|
*
|
||||||
|
* @param int $count
|
||||||
|
* @param string $rule
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setPluralForms($count, $rule)
|
||||||
|
{
|
||||||
|
if (preg_match('/[a-z]/i', str_replace('n', '', $rule))) {
|
||||||
|
throw new \InvalidArgumentException('Invalid Plural form: ' . $rule);
|
||||||
|
}
|
||||||
|
$this->setHeader(self::HEADER_PLURAL, "nplurals={$count}; plural={$rule};");
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the parsed plural definition.
|
||||||
|
*
|
||||||
|
* @param null|array [count, rule]
|
||||||
|
*/
|
||||||
|
public function getPluralForms()
|
||||||
|
{
|
||||||
|
$header = $this->getHeader(self::HEADER_PLURAL);
|
||||||
|
|
||||||
|
if (!empty($header)
|
||||||
|
&& preg_match('/^nplurals\s*=\s*(\d+)\s*;\s*plural\s*=\s*([^;]+)\s*;$/', $header, $matches)
|
||||||
|
) {
|
||||||
|
return [intval($matches[1]), $matches[2]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a new header.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setHeader($name, $value)
|
||||||
|
{
|
||||||
|
$name = trim($name);
|
||||||
|
$this->headers[$name] = trim($value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a header value.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return null|string
|
||||||
|
*/
|
||||||
|
public function getHeader($name)
|
||||||
|
{
|
||||||
|
return isset($this->headers[$name]) ? $this->headers[$name] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all header for this translations (in alphabetic order).
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getHeaders()
|
||||||
|
{
|
||||||
|
if (static::$options['headersSorting']) {
|
||||||
|
ksort($this->headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all headers.
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function deleteHeaders()
|
||||||
|
{
|
||||||
|
$this->headers = [];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes one header.
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function deleteHeader($name)
|
||||||
|
{
|
||||||
|
unset($this->headers[$name]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the language value.
|
||||||
|
*
|
||||||
|
* @return string $language
|
||||||
|
*/
|
||||||
|
public function getLanguage()
|
||||||
|
{
|
||||||
|
return $this->getHeader(self::HEADER_LANGUAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the language and the plural forms.
|
||||||
|
*
|
||||||
|
* @param string $language
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException if the language hasn't been recognized
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setLanguage($language)
|
||||||
|
{
|
||||||
|
$this->setHeader(self::HEADER_LANGUAGE, trim($language));
|
||||||
|
|
||||||
|
if (($info = Language::getById($language))) {
|
||||||
|
return $this->setPluralForms(count($info->categories), $info->formula);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(sprintf('The language "%s" is not valid', $language));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the language is empty or not.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasLanguage()
|
||||||
|
{
|
||||||
|
$language = $this->getLanguage();
|
||||||
|
|
||||||
|
return (is_string($language) && ($language !== '')) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a new domain for this translations.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function setDomain($domain)
|
||||||
|
{
|
||||||
|
$this->setHeader(self::HEADER_DOMAIN, trim($domain));
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the domain.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getDomain()
|
||||||
|
{
|
||||||
|
return $this->getHeader(self::HEADER_DOMAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the domain is empty or not.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function hasDomain()
|
||||||
|
{
|
||||||
|
$domain = $this->getDomain();
|
||||||
|
|
||||||
|
return (is_string($domain) && ($domain !== '')) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for a specific translation.
|
||||||
|
*
|
||||||
|
* @param string|Translation $context The context of the translation or a translation instance
|
||||||
|
* @param string $original The original string
|
||||||
|
* @warning Translations with custom identifiers (e.g. XLIFF unit IDs) cannot be found using this function.
|
||||||
|
*
|
||||||
|
* @return Translation|false
|
||||||
|
*/
|
||||||
|
public function find($context, $original = '')
|
||||||
|
{
|
||||||
|
if ($context instanceof Translation) {
|
||||||
|
$id = $context->getId();
|
||||||
|
} else {
|
||||||
|
$id = Translation::generateId($context, $original);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->offsetExists($id) ? $this[$id] : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count all elements translated
|
||||||
|
*
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
public function countTranslated()
|
||||||
|
{
|
||||||
|
$c = 0;
|
||||||
|
foreach ($this as $v) {
|
||||||
|
if ($v->hasTranslation()) {
|
||||||
|
$c++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and insert/merges a new translation.
|
||||||
|
*
|
||||||
|
* @param string $context The translation context
|
||||||
|
* @param string $original The translation original string
|
||||||
|
* @param string $plural The translation original plural string
|
||||||
|
*
|
||||||
|
* @return Translation The translation created
|
||||||
|
*/
|
||||||
|
public function insert($context, $original, $plural = '')
|
||||||
|
{
|
||||||
|
return $this->offsetSet(null, $this->createNewTranslation($context, $original, $plural));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges this translations with other translations.
|
||||||
|
*
|
||||||
|
* @param Translations $translations The translations instance to merge with
|
||||||
|
* @param int $options
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public function mergeWith(Translations $translations, $options = Merge::DEFAULTS)
|
||||||
|
{
|
||||||
|
Merge::mergeHeaders($translations, $this, $options);
|
||||||
|
Merge::mergeTranslations($translations, $this, $options);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of a Translation object.
|
||||||
|
*
|
||||||
|
* @param string $context The context of the translation
|
||||||
|
* @param string $original The original string
|
||||||
|
* @param string $plural The original plural string
|
||||||
|
* @return Translation New Translation instance
|
||||||
|
*/
|
||||||
|
public function createNewTranslation($context, $original, $plural = '')
|
||||||
|
{
|
||||||
|
$class = $this->translationClass;
|
||||||
|
return $class::create($context, $original, $plural);
|
||||||
|
}
|
||||||
|
}
|
270
inc/gettext/src/Translator.php
Normal file
270
inc/gettext/src/Translator.php
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext;
|
||||||
|
|
||||||
|
use Gettext\Generators\PhpArray;
|
||||||
|
|
||||||
|
class Translator extends BaseTranslator implements TranslatorInterface
|
||||||
|
{
|
||||||
|
protected $domain;
|
||||||
|
protected $dictionary = [];
|
||||||
|
protected $plurals = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads translation from a Translations instance, a file on an array.
|
||||||
|
*
|
||||||
|
* @param Translations|string|array $translations
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function loadTranslations($translations)
|
||||||
|
{
|
||||||
|
if (is_object($translations) && $translations instanceof Translations) {
|
||||||
|
$translations = PhpArray::generate($translations, ['includeHeaders' => false]);
|
||||||
|
} elseif (is_string($translations) && is_file($translations)) {
|
||||||
|
$translations = include $translations;
|
||||||
|
} elseif (!is_array($translations)) {
|
||||||
|
throw new \InvalidArgumentException(
|
||||||
|
'Invalid Translator: only arrays, files or instance of Translations are allowed'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addTranslations($translations);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default domain.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
*
|
||||||
|
* @return static
|
||||||
|
*/
|
||||||
|
public function defaultDomain($domain)
|
||||||
|
{
|
||||||
|
$this->domain = $domain;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function gettext($original)
|
||||||
|
{
|
||||||
|
return $this->dpgettext($this->domain, null, $original);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function ngettext($original, $plural, $value)
|
||||||
|
{
|
||||||
|
return $this->dnpgettext($this->domain, null, $original, $plural, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function dngettext($domain, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
return $this->dnpgettext($domain, null, $original, $plural, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function npgettext($context, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
return $this->dnpgettext($this->domain, $context, $original, $plural, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function pgettext($context, $original)
|
||||||
|
{
|
||||||
|
return $this->dpgettext($this->domain, $context, $original);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function dgettext($domain, $original)
|
||||||
|
{
|
||||||
|
return $this->dpgettext($domain, null, $original);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function dpgettext($domain, $context, $original)
|
||||||
|
{
|
||||||
|
$translation = $this->getTranslation($domain, $context, $original);
|
||||||
|
|
||||||
|
if (isset($translation[0]) && $translation[0] !== '') {
|
||||||
|
return $translation[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TranslatorInterface
|
||||||
|
*
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function dnpgettext($domain, $context, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
$translation = $this->getTranslation($domain, $context, $original);
|
||||||
|
$key = $this->getPluralIndex($domain, $value, $translation === false);
|
||||||
|
|
||||||
|
if (isset($translation[$key]) && $translation[$key] !== '') {
|
||||||
|
return $translation[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($key === 0) ? $original : $plural;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set new translations to the dictionary.
|
||||||
|
*
|
||||||
|
* @param array $translations
|
||||||
|
*/
|
||||||
|
protected function addTranslations(array $translations)
|
||||||
|
{
|
||||||
|
$domain = isset($translations['domain']) ? $translations['domain'] : '';
|
||||||
|
|
||||||
|
//Set the first domain loaded as default domain
|
||||||
|
if ($this->domain === null) {
|
||||||
|
$this->domain = $domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->dictionary[$domain])) {
|
||||||
|
$this->dictionary[$domain] = array_replace_recursive($this->dictionary[$domain], $translations['messages']);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($translations['plural-forms'])) {
|
||||||
|
list($count, $code) = array_map('trim', explode(';', $translations['plural-forms'], 2));
|
||||||
|
|
||||||
|
// extract just the expression turn 'n' into a php variable '$n'.
|
||||||
|
// Slap on a return keyword and semicolon at the end.
|
||||||
|
$this->plurals[$domain] = [
|
||||||
|
'count' => (int) str_replace('nplurals=', '', $count),
|
||||||
|
'code' => str_replace('plural=', 'return ', str_replace('n', '$n', $code)).';',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dictionary[$domain] = $translations['messages'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search and returns a translation.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string|false
|
||||||
|
*/
|
||||||
|
protected function getTranslation($domain, $context, $original)
|
||||||
|
{
|
||||||
|
return isset($this->dictionary[$domain][$context][$original])
|
||||||
|
? $this->dictionary[$domain][$context][$original]
|
||||||
|
: false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the plural decision code given the number to decide which
|
||||||
|
* plural version to take.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $n
|
||||||
|
* @param bool $fallback set to true to get fallback plural index
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
protected function getPluralIndex($domain, $n, $fallback)
|
||||||
|
{
|
||||||
|
//Not loaded domain or translation, use a fallback
|
||||||
|
if (!isset($this->plurals[$domain]) || $fallback === true) {
|
||||||
|
return $n == 1 ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->plurals[$domain]['function'])) {
|
||||||
|
$code = static::fixTerseIfs($this->plurals[$domain]['code']);
|
||||||
|
$this->plurals[$domain]['function'] = eval("return function (\$n) { $code };");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->plurals[$domain]['count'] <= 2) {
|
||||||
|
return call_user_func($this->plurals[$domain]['function'], $n) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return call_user_func($this->plurals[$domain]['function'], $n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function will recursively wrap failure states in brackets if they contain a nested terse if.
|
||||||
|
*
|
||||||
|
* This because PHP can not handle nested terse if's unless they are wrapped in brackets.
|
||||||
|
*
|
||||||
|
* This code probably only works for the gettext plural decision codes.
|
||||||
|
*
|
||||||
|
* return ($n==1 ? 0 : $n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2);
|
||||||
|
* becomes
|
||||||
|
* return ($n==1 ? 0 : ($n%10>=2 && $n%10<=4 && ($n%100<10 || $n%100>=20) ? 1 : 2));
|
||||||
|
*
|
||||||
|
* @param string $code the terse if string
|
||||||
|
* @param bool $inner If inner is true we wrap it in brackets
|
||||||
|
*
|
||||||
|
* @return string A formatted terse If that PHP can work with.
|
||||||
|
*/
|
||||||
|
private static function fixTerseIfs($code, $inner = false)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* (?P<expression>[^?]+) Capture everything up to ? as 'expression'
|
||||||
|
* \? ?
|
||||||
|
* (?P<success>[^:]+) Capture everything up to : as 'success'
|
||||||
|
* : :
|
||||||
|
* (?P<failure>[^;]+) Capture everything up to ; as 'failure'
|
||||||
|
*/
|
||||||
|
preg_match('/(?P<expression>[^?]+)\?(?P<success>[^:]+):(?P<failure>[^;]+)/', $code, $matches);
|
||||||
|
|
||||||
|
// If no match was found then no terse if was present
|
||||||
|
if (!isset($matches[0])) {
|
||||||
|
return $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expression = $matches['expression'];
|
||||||
|
$success = $matches['success'];
|
||||||
|
$failure = $matches['failure'];
|
||||||
|
|
||||||
|
// Go look for another terse if in the failure state.
|
||||||
|
$failure = static::fixTerseIfs($failure, true);
|
||||||
|
$code = $expression.' ? '.$success.' : '.$failure;
|
||||||
|
|
||||||
|
if ($inner) {
|
||||||
|
return "($code)";
|
||||||
|
}
|
||||||
|
|
||||||
|
// note the semicolon. We need that for executing the code.
|
||||||
|
return "$code;";
|
||||||
|
}
|
||||||
|
}
|
112
inc/gettext/src/TranslatorInterface.php
Normal file
112
inc/gettext/src/TranslatorInterface.php
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface used by all translators.
|
||||||
|
*/
|
||||||
|
interface TranslatorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register this translator as global, to use with the gettext functions __(), p__(), etc.
|
||||||
|
* Returns the previous translator if exists.
|
||||||
|
*
|
||||||
|
* @return TranslatorInterface|null
|
||||||
|
*/
|
||||||
|
public function register();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Noop, marks the string for translation but returns it unchanged.
|
||||||
|
*
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function noop($original);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a translation using the original string.
|
||||||
|
*
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function gettext($original);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a translation checking the plural form.
|
||||||
|
*
|
||||||
|
* @param string $original
|
||||||
|
* @param string $plural
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function ngettext($original, $plural, $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a translation checking the domain and the plural form.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $original
|
||||||
|
* @param string $plural
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function dngettext($domain, $original, $plural, $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a translation checking the context and the plural form.
|
||||||
|
*
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
* @param string $plural
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function npgettext($context, $original, $plural, $value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a translation checking the context.
|
||||||
|
*
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function pgettext($context, $original);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a translation checking the domain.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function dgettext($domain, $original);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a translation checking the domain and context.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function dpgettext($domain, $context, $original);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a translation checking the domain, the context and the plural form.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
* @param string $plural
|
||||||
|
* @param string $value
|
||||||
|
*/
|
||||||
|
public function dnpgettext($domain, $context, $original, $plural, $value);
|
||||||
|
}
|
56
inc/gettext/src/Utils/CsvTrait.php
Normal file
56
inc/gettext/src/Utils/CsvTrait.php
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Trait to provide the functionality of read/write csv.
|
||||||
|
*/
|
||||||
|
trait CsvTrait
|
||||||
|
{
|
||||||
|
protected static $csvEscapeChar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether support the escape_char argument to fgetcsv/fputcsv or not
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected static function supportsCsvEscapeChar()
|
||||||
|
{
|
||||||
|
if (static::$csvEscapeChar === null) {
|
||||||
|
static::$csvEscapeChar = version_compare(PHP_VERSION, '5.5.4') >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::$csvEscapeChar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $handle
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function fgetcsv($handle, $options)
|
||||||
|
{
|
||||||
|
if (static::supportsCsvEscapeChar()) {
|
||||||
|
return fgetcsv($handle, 0, $options['delimiter'], $options['enclosure'], $options['escape_char']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fgetcsv($handle, 0, $options['delimiter'], $options['enclosure']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $handle
|
||||||
|
* @param array $fields
|
||||||
|
* @param array $options
|
||||||
|
*
|
||||||
|
* @return bool|int
|
||||||
|
*/
|
||||||
|
protected static function fputcsv($handle, $fields, $options)
|
||||||
|
{
|
||||||
|
if (static::supportsCsvEscapeChar()) {
|
||||||
|
return fputcsv($handle, $fields, $options['delimiter'], $options['enclosure'], $options['escape_char']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fputcsv($handle, $fields, $options['delimiter'], $options['enclosure']);
|
||||||
|
}
|
||||||
|
}
|
59
inc/gettext/src/Utils/DictionaryTrait.php
Normal file
59
inc/gettext/src/Utils/DictionaryTrait.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait used by all generators that exports the translations to plain dictionary (original => singular-translation).
|
||||||
|
*/
|
||||||
|
trait DictionaryTrait
|
||||||
|
{
|
||||||
|
use HeadersGeneratorTrait;
|
||||||
|
use HeadersExtractorTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a plain dictionary with the format [original => translation].
|
||||||
|
*
|
||||||
|
* @param Translations $translations
|
||||||
|
* @param bool $includeHeaders
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function toArray(Translations $translations, $includeHeaders)
|
||||||
|
{
|
||||||
|
$messages = [];
|
||||||
|
|
||||||
|
if ($includeHeaders) {
|
||||||
|
$messages[''] = static::generateHeaders($translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($translations as $translation) {
|
||||||
|
if ($translation->isDisabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages[$translation->getOriginal()] = $translation->getTranslation();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the entries from a dictionary.
|
||||||
|
*
|
||||||
|
* @param array $messages
|
||||||
|
* @param Translations $translations
|
||||||
|
*/
|
||||||
|
protected static function fromArray(array $messages, Translations $translations)
|
||||||
|
{
|
||||||
|
foreach ($messages as $original => $translation) {
|
||||||
|
if ($original === '') {
|
||||||
|
static::extractHeaders($translation, $translations);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations->insert(null, $original)->setTranslation($translation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
167
inc/gettext/src/Utils/FunctionsScanner.php
Normal file
167
inc/gettext/src/Utils/FunctionsScanner.php
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
abstract class FunctionsScanner
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Scan and returns the functions and the arguments.
|
||||||
|
*
|
||||||
|
* @param array $constants Constants used in the code to replace
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
abstract public function getFunctions(array $constants = []);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search for specific functions and create translations.
|
||||||
|
*
|
||||||
|
* You can pass multiple translation with different domains and value found will be sorted respectively.
|
||||||
|
*
|
||||||
|
* @param Translations|Translations[] $translations Multiple domain translations instances where to save the values
|
||||||
|
* @param array $options The extractor options
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function saveGettextFunctions($translations, array $options)
|
||||||
|
{
|
||||||
|
$translations = is_array($translations) ? $translations : [$translations];
|
||||||
|
|
||||||
|
/** @var Translations[] $translationByDomain [domain => translations, ..] */
|
||||||
|
$translationByDomain = array_reduce($translations, function (&$carry, Translations $translations) {
|
||||||
|
$carry[$translations->getDomain()] = $translations;
|
||||||
|
return $carry;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
$functions = $options['functions'];
|
||||||
|
$file = $options['file'];
|
||||||
|
|
||||||
|
foreach ($this->getFunctions($options['constants']) as $function) {
|
||||||
|
list($name, $line, $args) = $function;
|
||||||
|
|
||||||
|
if (isset($options['lineOffset'])) {
|
||||||
|
$line += $options['lineOffset'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($functions[$name])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deconstructed = $this->deconstructArgs($functions[$name], $args);
|
||||||
|
|
||||||
|
if (!$deconstructed) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($domain, $context, $original, $plural) = $deconstructed;
|
||||||
|
|
||||||
|
if ((string)$original === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$isDefaultDomain = $domain === null;
|
||||||
|
|
||||||
|
$domainTranslations = isset($translationByDomain[$domain]) ? $translationByDomain[$domain] : false;
|
||||||
|
|
||||||
|
if (!empty($options['domainOnly']) && $isDefaultDomain) {
|
||||||
|
// If we want to find translations for a specific domain, skip default domain messages
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$domainTranslations) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation = $domainTranslations->insert($context, $original, $plural);
|
||||||
|
$translation->addReference($file, $line);
|
||||||
|
|
||||||
|
if (isset($function[3])) {
|
||||||
|
foreach ($function[3] as $extractedComment) {
|
||||||
|
$translation->addExtractedComment($extractedComment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deconstruct arguments to translation values
|
||||||
|
*
|
||||||
|
* @param $function
|
||||||
|
* @param $args
|
||||||
|
* @return array|null
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function deconstructArgs($function, $args)
|
||||||
|
{
|
||||||
|
$domain = null;
|
||||||
|
$context = null;
|
||||||
|
$original = null;
|
||||||
|
$plural = null;
|
||||||
|
|
||||||
|
switch ($function) {
|
||||||
|
case 'noop':
|
||||||
|
case 'gettext':
|
||||||
|
if (!isset($args[0])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$original = $args[0];
|
||||||
|
break;
|
||||||
|
case 'ngettext':
|
||||||
|
if (!isset($args[1])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($original, $plural) = $args;
|
||||||
|
break;
|
||||||
|
case 'pgettext':
|
||||||
|
if (!isset($args[1])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($context, $original) = $args;
|
||||||
|
break;
|
||||||
|
case 'dgettext':
|
||||||
|
if (!isset($args[1])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($domain, $original) = $args;
|
||||||
|
break;
|
||||||
|
case 'dpgettext':
|
||||||
|
if (!isset($args[2])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($domain, $context, $original) = $args;
|
||||||
|
break;
|
||||||
|
case 'npgettext':
|
||||||
|
if (!isset($args[2])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($context, $original, $plural) = $args;
|
||||||
|
break;
|
||||||
|
case 'dnpgettext':
|
||||||
|
if (!isset($args[3])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($domain, $context, $original, $plural) = $args;
|
||||||
|
break;
|
||||||
|
case 'dngettext':
|
||||||
|
if (!isset($args[2])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
list($domain, $original, $plural) = $args;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception(sprintf('Not valid function %s', $function));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$domain, $context, $original, $plural];
|
||||||
|
}
|
||||||
|
}
|
67
inc/gettext/src/Utils/HeadersExtractorTrait.php
Normal file
67
inc/gettext/src/Utils/HeadersExtractorTrait.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait to provide the functionality of extracting headers.
|
||||||
|
*/
|
||||||
|
trait HeadersExtractorTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Add the headers found to the translations instance.
|
||||||
|
*
|
||||||
|
* @param string $headers
|
||||||
|
* @param Translations $translations
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function extractHeaders($headers, Translations $translations)
|
||||||
|
{
|
||||||
|
$headers = explode("\n", $headers);
|
||||||
|
$currentHeader = null;
|
||||||
|
|
||||||
|
foreach ($headers as $line) {
|
||||||
|
$line = static::convertString($line);
|
||||||
|
|
||||||
|
if ($line === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (static::isHeaderDefinition($line)) {
|
||||||
|
$header = array_map('trim', explode(':', $line, 2));
|
||||||
|
$currentHeader = $header[0];
|
||||||
|
$translations->setHeader($currentHeader, $header[1]);
|
||||||
|
} else {
|
||||||
|
$entry = $translations->getHeader($currentHeader);
|
||||||
|
$translations->setHeader($currentHeader, $entry.$line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if it is a header definition line. Useful for distguishing between header definitions
|
||||||
|
* and possible continuations of a header entry.
|
||||||
|
*
|
||||||
|
* @param string $line Line to parse
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected static function isHeaderDefinition($line)
|
||||||
|
{
|
||||||
|
return (bool) preg_match('/^[\w-]+:/', $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize a string.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function convertString($value)
|
||||||
|
{
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
29
inc/gettext/src/Utils/HeadersGeneratorTrait.php
Normal file
29
inc/gettext/src/Utils/HeadersGeneratorTrait.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait to provide the functionality of extracting headers.
|
||||||
|
*/
|
||||||
|
trait HeadersGeneratorTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns the headers as a string.
|
||||||
|
*
|
||||||
|
* @param Translations $translations
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function generateHeaders(Translations $translations)
|
||||||
|
{
|
||||||
|
$headers = '';
|
||||||
|
|
||||||
|
foreach ($translations->getHeaders() as $name => $value) {
|
||||||
|
$headers .= sprintf("%s: %s\n", $name, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $headers;
|
||||||
|
}
|
||||||
|
}
|
320
inc/gettext/src/Utils/JsFunctionsScanner.php
Normal file
320
inc/gettext/src/Utils/JsFunctionsScanner.php
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
class JsFunctionsScanner extends FunctionsScanner
|
||||||
|
{
|
||||||
|
protected $code;
|
||||||
|
protected $status = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $code The php code to scan
|
||||||
|
*/
|
||||||
|
public function __construct($code)
|
||||||
|
{
|
||||||
|
// Normalize newline characters
|
||||||
|
$this->code = str_replace(["\r\n", "\n\r", "\r"], "\n", $code);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getFunctions(array $constants = [])
|
||||||
|
{
|
||||||
|
$length = strlen($this->code);
|
||||||
|
$line = 1;
|
||||||
|
$buffer = '';
|
||||||
|
$functions = [];
|
||||||
|
$bufferFunctions = [];
|
||||||
|
$char = null;
|
||||||
|
|
||||||
|
for ($pos = 0; $pos < $length; ++$pos) {
|
||||||
|
$prev = $char;
|
||||||
|
$char = $this->code[$pos];
|
||||||
|
$next = isset($this->code[$pos + 1]) ? $this->code[$pos + 1] : null;
|
||||||
|
|
||||||
|
switch ($char) {
|
||||||
|
case '\\':
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'simple-quote':
|
||||||
|
if ($next !== "'") {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'double-quote':
|
||||||
|
if ($next !== '"') {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'back-tick':
|
||||||
|
if ($next !== '`') {
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$prev = $char;
|
||||||
|
$char = $next;
|
||||||
|
$pos++;
|
||||||
|
$next = isset($this->code[$pos]) ? $this->code[$pos] : null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "\n":
|
||||||
|
++$line;
|
||||||
|
|
||||||
|
if ($this->status('line-comment')) {
|
||||||
|
$this->upStatus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'simple-quote':
|
||||||
|
case 'double-quote':
|
||||||
|
case 'back-tick':
|
||||||
|
case 'line-comment':
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'block-comment':
|
||||||
|
if ($prev === '*') {
|
||||||
|
$this->upStatus();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if ($next === '/') {
|
||||||
|
$this->downStatus('line-comment');
|
||||||
|
} elseif ($next === '*') {
|
||||||
|
$this->downStatus('block-comment');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "'":
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'simple-quote':
|
||||||
|
$this->upStatus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'line-comment':
|
||||||
|
case 'block-comment':
|
||||||
|
case 'double-quote':
|
||||||
|
case 'back-tick':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->downStatus('simple-quote');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'double-quote':
|
||||||
|
$this->upStatus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'line-comment':
|
||||||
|
case 'block-comment':
|
||||||
|
case 'simple-quote':
|
||||||
|
case 'back-tick':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->downStatus('double-quote');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '`':
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'back-tick':
|
||||||
|
$this->upStatus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'line-comment':
|
||||||
|
case 'block-comment':
|
||||||
|
case 'simple-quote':
|
||||||
|
case 'double-quote':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$this->downStatus('back-tick');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '(':
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'simple-quote':
|
||||||
|
case 'double-quote':
|
||||||
|
case 'back-tick':
|
||||||
|
case 'line-comment':
|
||||||
|
case 'block-comment':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if ($buffer && preg_match('/(\w+)$/', $buffer, $matches)) {
|
||||||
|
$this->downStatus('function');
|
||||||
|
array_unshift($bufferFunctions, [$matches[1], $line, []]);
|
||||||
|
$buffer = '';
|
||||||
|
continue 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ')':
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'function':
|
||||||
|
if (($argument = static::prepareArgument($buffer))) {
|
||||||
|
$bufferFunctions[0][2][] = $argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($bufferFunctions)) {
|
||||||
|
$functions[] = array_shift($bufferFunctions);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->upStatus();
|
||||||
|
$buffer = '';
|
||||||
|
continue 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'function':
|
||||||
|
if (($argument = static::prepareArgument($buffer))) {
|
||||||
|
$bufferFunctions[0][2][] = $argument;
|
||||||
|
}
|
||||||
|
|
||||||
|
$buffer = '';
|
||||||
|
continue 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ' ':
|
||||||
|
case '\t':
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'double-quote':
|
||||||
|
case 'simple-quote':
|
||||||
|
case 'back-tick':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$buffer = '';
|
||||||
|
continue 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($this->status()) {
|
||||||
|
case 'line-comment':
|
||||||
|
case 'block-comment':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
$buffer .= $char;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $functions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current context of the scan.
|
||||||
|
*
|
||||||
|
* @param null|string $match To check whether the current status is this value
|
||||||
|
*
|
||||||
|
* @return string|bool
|
||||||
|
*/
|
||||||
|
protected function status($match = null)
|
||||||
|
{
|
||||||
|
$status = isset($this->status[0]) ? $this->status[0] : null;
|
||||||
|
|
||||||
|
if ($match !== null) {
|
||||||
|
return $status === $match;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new status to the stack.
|
||||||
|
*
|
||||||
|
* @param string $status
|
||||||
|
*/
|
||||||
|
protected function downStatus($status)
|
||||||
|
{
|
||||||
|
array_unshift($this->status, $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes and return the current status.
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function upStatus()
|
||||||
|
{
|
||||||
|
return array_shift($this->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the arguments found in functions.
|
||||||
|
*
|
||||||
|
* @param string $argument
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function prepareArgument($argument)
|
||||||
|
{
|
||||||
|
if ($argument && in_array($argument[0], ['"', "'", '`'], true)) {
|
||||||
|
return static::convertString(substr($argument, 1, -1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decodes a string with an argument.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected static function convertString($value)
|
||||||
|
{
|
||||||
|
if (strpos($value, '\\') === false) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return preg_replace_callback(
|
||||||
|
'/\\\(n|r|t|v|e|f|"|\\\)/',
|
||||||
|
function ($match) {
|
||||||
|
switch ($match[1][0]) {
|
||||||
|
case 'n':
|
||||||
|
return "\n";
|
||||||
|
case 'r':
|
||||||
|
return "\r";
|
||||||
|
case 't':
|
||||||
|
return "\t";
|
||||||
|
case 'v':
|
||||||
|
return "\v";
|
||||||
|
case 'e':
|
||||||
|
return "\e";
|
||||||
|
case 'f':
|
||||||
|
return "\f";
|
||||||
|
case '"':
|
||||||
|
return '"';
|
||||||
|
case '\\':
|
||||||
|
return '\\';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
100
inc/gettext/src/Utils/MultidimensionalArrayTrait.php
Normal file
100
inc/gettext/src/Utils/MultidimensionalArrayTrait.php
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
use Gettext\Translations;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait used by all generators that exports the translations to multidimensional arrays
|
||||||
|
* (context => [original => [translation, plural1, pluraln...]]).
|
||||||
|
*/
|
||||||
|
trait MultidimensionalArrayTrait
|
||||||
|
{
|
||||||
|
use HeadersGeneratorTrait;
|
||||||
|
use HeadersExtractorTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a multidimensional array.
|
||||||
|
*
|
||||||
|
* @param Translations $translations
|
||||||
|
* @param bool $includeHeaders
|
||||||
|
* @param bool $forceArray
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected static function toArray(Translations $translations, $includeHeaders, $forceArray = false)
|
||||||
|
{
|
||||||
|
$pluralForm = $translations->getPluralForms();
|
||||||
|
$pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null;
|
||||||
|
$messages = [];
|
||||||
|
|
||||||
|
if ($includeHeaders) {
|
||||||
|
$messages[''] = [
|
||||||
|
'' => [static::generateHeaders($translations)],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($translations as $translation) {
|
||||||
|
if ($translation->isDisabled()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$context = $translation->getContext();
|
||||||
|
$original = $translation->getOriginal();
|
||||||
|
|
||||||
|
if (!isset($messages[$context])) {
|
||||||
|
$messages[$context] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($translation->hasPluralTranslations(true)) {
|
||||||
|
$messages[$context][$original] = $translation->getPluralTranslations($pluralSize);
|
||||||
|
array_unshift($messages[$context][$original], $translation->getTranslation());
|
||||||
|
} elseif ($forceArray) {
|
||||||
|
$messages[$context][$original] = [$translation->getTranslation()];
|
||||||
|
} else {
|
||||||
|
$messages[$context][$original] = $translation->getTranslation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'domain' => $translations->getDomain(),
|
||||||
|
'plural-forms' => $translations->getHeader('Plural-Forms'),
|
||||||
|
'messages' => $messages,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the entries from a multidimensional array.
|
||||||
|
*
|
||||||
|
* @param array $messages
|
||||||
|
* @param Translations $translations
|
||||||
|
*/
|
||||||
|
protected static function fromArray(array $messages, Translations $translations)
|
||||||
|
{
|
||||||
|
if (!empty($messages['domain'])) {
|
||||||
|
$translations->setDomain($messages['domain']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($messages['plural-forms'])) {
|
||||||
|
$translations->setHeader(Translations::HEADER_PLURAL, $messages['plural-forms']);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($messages['messages'] as $context => $contextTranslations) {
|
||||||
|
foreach ($contextTranslations as $original => $value) {
|
||||||
|
if ($context === '' && $original === '') {
|
||||||
|
static::extractHeaders(is_array($value) ? array_shift($value) : $value, $translations);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$translation = $translations->insert($context, $original);
|
||||||
|
|
||||||
|
if (is_array($value)) {
|
||||||
|
$translation->setTranslation(array_shift($value));
|
||||||
|
$translation->setPluralTranslations($value);
|
||||||
|
} else {
|
||||||
|
$translation->setTranslation($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
140
inc/gettext/src/Utils/ParsedComment.php
Normal file
140
inc/gettext/src/Utils/ParsedComment.php
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comment parsed by PhpFunctionsScanner.
|
||||||
|
*/
|
||||||
|
class ParsedComment
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The comment itself.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $comment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The line where the comment starts.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $firstLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The line where the comment ends.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $lastLine;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the instance.
|
||||||
|
*
|
||||||
|
* @param string $comment The comment itself.
|
||||||
|
* @param int $firstLine The line where the comment starts.
|
||||||
|
* @param int $lastLine The line where the comment ends.
|
||||||
|
*/
|
||||||
|
public function __construct($comment, $firstLine, $lastLine)
|
||||||
|
{
|
||||||
|
$this->comment = $comment;
|
||||||
|
$this->firstLine = $firstLine;
|
||||||
|
$this->lastLine = $lastLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new object from raw comment data.
|
||||||
|
*
|
||||||
|
* @param string $value The PHP comment string.
|
||||||
|
* @param int $line The line where the comment starts.
|
||||||
|
*
|
||||||
|
* @return static The parsed comment.
|
||||||
|
*/
|
||||||
|
public static function create($value, $line)
|
||||||
|
{
|
||||||
|
$lastLine = $line + substr_count($value, "\n");
|
||||||
|
|
||||||
|
$lines = array_map(function ($line) {
|
||||||
|
if ('' === trim($line)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$line = ltrim($line, "#*/ \t");
|
||||||
|
$line = rtrim($line, "#*/ \t");
|
||||||
|
|
||||||
|
return trim($line);
|
||||||
|
}, explode("\n", $value));
|
||||||
|
|
||||||
|
// Remove empty lines.
|
||||||
|
$lines = array_filter($lines);
|
||||||
|
$value = implode(' ', $lines);
|
||||||
|
|
||||||
|
return new static($value, $line, $lastLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the line where the comment starts.
|
||||||
|
*
|
||||||
|
* @return int Line number.
|
||||||
|
*/
|
||||||
|
public function getFirstLine()
|
||||||
|
{
|
||||||
|
return $this->firstLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the line where the comment ends.
|
||||||
|
*
|
||||||
|
* @return int Line number.
|
||||||
|
*/
|
||||||
|
public function getLastLine()
|
||||||
|
{
|
||||||
|
return $this->lastLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the actual comment string.
|
||||||
|
*
|
||||||
|
* @return string The comment.
|
||||||
|
*/
|
||||||
|
public function getComment()
|
||||||
|
{
|
||||||
|
return $this->comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this comment is related with a given function.
|
||||||
|
*
|
||||||
|
* @param ParsedFunction $function The function to check.
|
||||||
|
* @return bool Whether the comment is related or not.
|
||||||
|
*/
|
||||||
|
public function isRelatedWith(ParsedFunction $function)
|
||||||
|
{
|
||||||
|
return $this->getLastLine() === $function->getLine() || $this->getLastLine() === $function->getLine() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the comment matches the required prefixes.
|
||||||
|
*
|
||||||
|
* @param array $prefixes An array of prefixes to check.
|
||||||
|
* @return bool Whether the comment matches the prefixes or not.
|
||||||
|
*/
|
||||||
|
public function checkPrefixes(array $prefixes)
|
||||||
|
{
|
||||||
|
if ('' === $this->comment) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($prefixes)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($prefixes as $prefix) {
|
||||||
|
if (strpos($this->comment, $prefix) === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
159
inc/gettext/src/Utils/ParsedFunction.php
Normal file
159
inc/gettext/src/Utils/ParsedFunction.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function parsed by PhpFunctionsScanner.
|
||||||
|
*/
|
||||||
|
class ParsedFunction
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The function name.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The line where the function starts.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The strings extracted from the function arguments.
|
||||||
|
*
|
||||||
|
* @var string[]
|
||||||
|
*/
|
||||||
|
protected $arguments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current index of the function (-1 if no arguments).
|
||||||
|
*
|
||||||
|
* @var int|null
|
||||||
|
*/
|
||||||
|
protected $argumentIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shall we stop adding string chunks to the current argument?
|
||||||
|
*
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
protected $argumentStopped;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracted comments.
|
||||||
|
*
|
||||||
|
* @var string[]|null
|
||||||
|
*/
|
||||||
|
protected $comments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the instance.
|
||||||
|
*
|
||||||
|
* @param string $name The function name.
|
||||||
|
* @param int $line The line where the function starts.
|
||||||
|
*/
|
||||||
|
public function __construct($name, $line)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->line = $line;
|
||||||
|
$this->arguments = [];
|
||||||
|
$this->argumentIndex = -1;
|
||||||
|
$this->argumentStopped = false;
|
||||||
|
$this->comments = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop extracting strings from the current argument (because we found something that's not a string).
|
||||||
|
*/
|
||||||
|
public function stopArgument()
|
||||||
|
{
|
||||||
|
if ($this->argumentIndex === -1) {
|
||||||
|
$this->argumentIndex = 0;
|
||||||
|
}
|
||||||
|
$this->argumentStopped = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the next argument because we a comma was found.
|
||||||
|
*/
|
||||||
|
public function nextArgument()
|
||||||
|
{
|
||||||
|
if ($this->argumentIndex === -1) {
|
||||||
|
// This should neve occur, but let's stay safe - During test/development an Exception should be thrown.
|
||||||
|
$this->argumentIndex = 1;
|
||||||
|
} else {
|
||||||
|
++$this->argumentIndex;
|
||||||
|
}
|
||||||
|
$this->argumentStopped = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a string to the current argument.
|
||||||
|
*
|
||||||
|
* @param string|null $chunk
|
||||||
|
*/
|
||||||
|
public function addArgumentChunk($chunk)
|
||||||
|
{
|
||||||
|
if ($this->argumentStopped === false) {
|
||||||
|
if ($this->argumentIndex === -1) {
|
||||||
|
$this->argumentIndex = 0;
|
||||||
|
}
|
||||||
|
if (isset($this->arguments[$this->argumentIndex])) {
|
||||||
|
$this->arguments[$this->argumentIndex] .= $chunk;
|
||||||
|
} else {
|
||||||
|
$this->arguments[$this->argumentIndex] = $chunk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a comment associated to this function.
|
||||||
|
*
|
||||||
|
* @param string $comment
|
||||||
|
*/
|
||||||
|
public function addComment($comment)
|
||||||
|
{
|
||||||
|
if ($this->comments === null) {
|
||||||
|
$this->comments = [];
|
||||||
|
}
|
||||||
|
$this->comments[] = $comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the line the function starts.
|
||||||
|
*
|
||||||
|
* @return int Line number.
|
||||||
|
*/
|
||||||
|
public function getLine()
|
||||||
|
{
|
||||||
|
return $this->line;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A closing parenthesis was found: return the final data.
|
||||||
|
* The array returned has the following values:
|
||||||
|
* 0 => string The function name.
|
||||||
|
* 1 => int The line where the function starts.
|
||||||
|
* 2 => string[] the strings extracted from the function arguments.
|
||||||
|
* 3 => string[] the comments associated to the function.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function close()
|
||||||
|
{
|
||||||
|
$arguments = [];
|
||||||
|
for ($i = 0; $i <= $this->argumentIndex; ++$i) {
|
||||||
|
$arguments[$i] = isset($this->arguments[$i]) ? $this->arguments[$i] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
$this->name,
|
||||||
|
$this->line,
|
||||||
|
$arguments,
|
||||||
|
$this->comments,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
194
inc/gettext/src/Utils/PhpFunctionsScanner.php
Normal file
194
inc/gettext/src/Utils/PhpFunctionsScanner.php
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
use Gettext\Extractors\PhpCode;
|
||||||
|
|
||||||
|
class PhpFunctionsScanner extends FunctionsScanner
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* PHP tokens of the code to be parsed.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $tokens;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If not false, comments will be extracted.
|
||||||
|
*
|
||||||
|
* @var string|false|array
|
||||||
|
*/
|
||||||
|
protected $extractComments = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable extracting comments that start with a tag (if $tag is empty all the comments will be extracted).
|
||||||
|
*
|
||||||
|
* @param mixed $tag
|
||||||
|
*/
|
||||||
|
public function enableCommentsExtraction($tag = '')
|
||||||
|
{
|
||||||
|
$this->extractComments = $tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable comments extraction.
|
||||||
|
*/
|
||||||
|
public function disableCommentsExtraction()
|
||||||
|
{
|
||||||
|
$this->extractComments = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $code The php code to scan
|
||||||
|
*/
|
||||||
|
public function __construct($code)
|
||||||
|
{
|
||||||
|
$this->tokens = array_values(
|
||||||
|
array_filter(
|
||||||
|
token_get_all($code),
|
||||||
|
function ($token) {
|
||||||
|
return !is_array($token) || $token[0] !== T_WHITESPACE;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getFunctions(array $constants = [])
|
||||||
|
{
|
||||||
|
$count = count($this->tokens);
|
||||||
|
/* @var ParsedFunction[] $bufferFunctions */
|
||||||
|
$bufferFunctions = [];
|
||||||
|
/* @var ParsedComment[] $bufferComments */
|
||||||
|
$bufferComments = [];
|
||||||
|
/* @var array $functions */
|
||||||
|
$functions = [];
|
||||||
|
|
||||||
|
for ($k = 0; $k < $count; ++$k) {
|
||||||
|
$value = $this->tokens[$k];
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
if (isset($bufferFunctions[0])) {
|
||||||
|
switch ($value) {
|
||||||
|
case ',':
|
||||||
|
$bufferFunctions[0]->nextArgument();
|
||||||
|
break;
|
||||||
|
case ')':
|
||||||
|
$functions[] = array_shift($bufferFunctions)->close();
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$bufferFunctions[0]->stopArgument();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($value[0]) {
|
||||||
|
case T_CONSTANT_ENCAPSED_STRING:
|
||||||
|
//add an argument to the current function
|
||||||
|
if (isset($bufferFunctions[0])) {
|
||||||
|
$bufferFunctions[0]->addArgumentChunk(PhpCode::convertString($value[1]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_STRING:
|
||||||
|
if (isset($bufferFunctions[0])) {
|
||||||
|
if (isset($constants[$value[1]])) {
|
||||||
|
$bufferFunctions[0]->addArgumentChunk($constants[$value[1]]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strtolower($value[1]) === 'null') {
|
||||||
|
$bufferFunctions[0]->addArgumentChunk(null);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$bufferFunctions[0]->stopArgument();
|
||||||
|
}
|
||||||
|
|
||||||
|
//new function found
|
||||||
|
for ($j = $k + 1; $j < $count; ++$j) {
|
||||||
|
$nextToken = $this->tokens[$j];
|
||||||
|
|
||||||
|
if (is_array($nextToken) && $nextToken[0] === T_COMMENT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($nextToken === '(') {
|
||||||
|
$newFunction = new ParsedFunction($value[1], $value[2]);
|
||||||
|
|
||||||
|
// add comment that was on the line before.
|
||||||
|
if (isset($bufferComments[0])) {
|
||||||
|
$comment = $bufferComments[0];
|
||||||
|
|
||||||
|
if ($comment->isRelatedWith($newFunction)) {
|
||||||
|
$newFunction->addComment($comment->getComment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_unshift($bufferFunctions, $newFunction);
|
||||||
|
$k = $j;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case T_COMMENT:
|
||||||
|
$comment = $this->parsePhpComment($value[1], $value[2]);
|
||||||
|
|
||||||
|
if ($comment) {
|
||||||
|
array_unshift($bufferComments, $comment);
|
||||||
|
|
||||||
|
// The comment is inside the function call.
|
||||||
|
if (isset($bufferFunctions[0])) {
|
||||||
|
$bufferFunctions[0]->addComment($comment->getComment());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (isset($bufferFunctions[0])) {
|
||||||
|
$bufferFunctions[0]->stopArgument();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $functions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the actual text from a PHP comment.
|
||||||
|
*
|
||||||
|
* If set, only returns comments that match the prefix(es).
|
||||||
|
*
|
||||||
|
* @param string $value The PHP comment.
|
||||||
|
* @param int $line Line number.
|
||||||
|
*
|
||||||
|
* @return null|ParsedComment Comment or null if comment extraction is disabled or if there is a prefix mismatch.
|
||||||
|
*/
|
||||||
|
protected function parsePhpComment($value, $line)
|
||||||
|
{
|
||||||
|
if ($this->extractComments === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//this returns a comment or null
|
||||||
|
$comment = ParsedComment::create($value, $line);
|
||||||
|
|
||||||
|
$prefixes = array_filter((array) $this->extractComments);
|
||||||
|
|
||||||
|
if ($comment && $comment->checkPrefixes($prefixes)) {
|
||||||
|
return $comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
51
inc/gettext/src/Utils/StringReader.php
Normal file
51
inc/gettext/src/Utils/StringReader.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Gettext\Utils;
|
||||||
|
|
||||||
|
class StringReader
|
||||||
|
{
|
||||||
|
public $pos;
|
||||||
|
public $str;
|
||||||
|
public $strlen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $str The string to read
|
||||||
|
*/
|
||||||
|
public function __construct($str)
|
||||||
|
{
|
||||||
|
$this->str = $str;
|
||||||
|
$this->strlen = strlen($this->str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and returns a part of the string.
|
||||||
|
*
|
||||||
|
* @param int $bytes The number of bytes to read
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function read($bytes)
|
||||||
|
{
|
||||||
|
$data = substr($this->str, $this->pos, $bytes);
|
||||||
|
|
||||||
|
$this->seekto($this->pos + $bytes);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor to a specific position.
|
||||||
|
*
|
||||||
|
* @param int $pos The amount of bytes to move
|
||||||
|
*
|
||||||
|
* @return int The new position
|
||||||
|
*/
|
||||||
|
public function seekto($pos)
|
||||||
|
{
|
||||||
|
$this->pos = ($this->strlen < $pos) ? $this->strlen : $pos;
|
||||||
|
|
||||||
|
return $this->pos;
|
||||||
|
}
|
||||||
|
}
|
13
inc/gettext/src/autoloader.php
Normal file
13
inc/gettext/src/autoloader.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
spl_autoload_register(function ($class) {
|
||||||
|
if (strpos($class, 'Gettext\\') !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$file = __DIR__.str_replace('\\', DIRECTORY_SEPARATOR, substr($class, strlen('Gettext'))).'.php';
|
||||||
|
|
||||||
|
if (is_file($file)) {
|
||||||
|
require_once $file;
|
||||||
|
}
|
||||||
|
});
|
191
inc/gettext/src/translator_functions.php
Executable file
191
inc/gettext/src/translator_functions.php
Executable file
@ -0,0 +1,191 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Gettext\BaseTranslator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the translation of a string.
|
||||||
|
*
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function __($original)
|
||||||
|
{
|
||||||
|
$text = BaseTranslator::$current->gettext($original);
|
||||||
|
|
||||||
|
if (func_num_args() === 1) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_slice(func_get_args(), 1);
|
||||||
|
|
||||||
|
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Noop, marks the string for translation but returns it unchanged.
|
||||||
|
*
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function noop__($original)
|
||||||
|
{
|
||||||
|
return $original;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singular/plural translation of a string.
|
||||||
|
*
|
||||||
|
* @param string $original
|
||||||
|
* @param string $plural
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function n__($original, $plural, $value)
|
||||||
|
{
|
||||||
|
$text = BaseTranslator::$current->ngettext($original, $plural, $value);
|
||||||
|
|
||||||
|
if (func_num_args() === 3) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_slice(func_get_args(), 3);
|
||||||
|
|
||||||
|
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the translation of a string in a specific context.
|
||||||
|
*
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function p__($context, $original)
|
||||||
|
{
|
||||||
|
$text = BaseTranslator::$current->pgettext($context, $original);
|
||||||
|
|
||||||
|
if (func_num_args() === 2) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_slice(func_get_args(), 2);
|
||||||
|
|
||||||
|
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the translation of a string in a specific domain.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function d__($domain, $original)
|
||||||
|
{
|
||||||
|
$text = BaseTranslator::$current->dgettext($domain, $original);
|
||||||
|
|
||||||
|
if (func_num_args() === 2) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_slice(func_get_args(), 2);
|
||||||
|
|
||||||
|
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the translation of a string in a specific domain and context.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function dp__($domain, $context, $original)
|
||||||
|
{
|
||||||
|
$text = BaseTranslator::$current->dpgettext($domain, $context, $original);
|
||||||
|
|
||||||
|
if (func_num_args() === 3) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_slice(func_get_args(), 3);
|
||||||
|
|
||||||
|
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singular/plural translation of a string in a specific domain.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $original
|
||||||
|
* @param string $plural
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function dn__($domain, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
$text = BaseTranslator::$current->dngettext($domain, $original, $plural, $value);
|
||||||
|
|
||||||
|
if (func_num_args() === 4) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_slice(func_get_args(), 4);
|
||||||
|
|
||||||
|
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singular/plural translation of a string in a specific context.
|
||||||
|
*
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
* @param string $plural
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function np__($context, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
$text = BaseTranslator::$current->npgettext($context, $original, $plural, $value);
|
||||||
|
|
||||||
|
if (func_num_args() === 4) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_slice(func_get_args(), 4);
|
||||||
|
|
||||||
|
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the singular/plural translation of a string in a specific domain and context.
|
||||||
|
*
|
||||||
|
* @param string $domain
|
||||||
|
* @param string $context
|
||||||
|
* @param string $original
|
||||||
|
* @param string $plural
|
||||||
|
* @param string $value
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function dnp__($domain, $context, $original, $plural, $value)
|
||||||
|
{
|
||||||
|
$text = BaseTranslator::$current->dnpgettext($domain, $context, $original, $plural, $value);
|
||||||
|
|
||||||
|
if (func_num_args() === 5) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$args = array_slice(func_get_args(), 5);
|
||||||
|
|
||||||
|
return is_array($args[0]) ? strtr($text, $args[0]) : vsprintf($text, $args);
|
||||||
|
}
|
92
inc/html.php
92
inc/html.php
@ -59,9 +59,9 @@ function supportedFileTypes() {
|
|||||||
: $types_last;
|
: $types_last;
|
||||||
|
|
||||||
if (count($tinyib_uploads) == 1) {
|
if (count($tinyib_uploads) == 1) {
|
||||||
return sprintf(_('Supported file type is %s'), $types_formatted);
|
return sprintf(__('Supported file type is %s'), $types_formatted);
|
||||||
} else {
|
} else {
|
||||||
return sprintf(_('Supported file types are %s.'), $types_formatted);
|
return sprintf(__('Supported file types are %s.'), $types_formatted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,10 +97,10 @@ function buildPostForm($parent, $raw_post = false) {
|
|||||||
$input_extra = '';
|
$input_extra = '';
|
||||||
$rules_extra = '';
|
$rules_extra = '';
|
||||||
if ($raw_post) {
|
if ($raw_post) {
|
||||||
$txt_reply_to = _('Reply to');
|
$txt_reply_to = __('Reply to');
|
||||||
$txt_new_thread = _('0 to start a new thread');
|
$txt_new_thread = __('0 to start a new thread');
|
||||||
$txt_info_1 = _('Text entered in the Message field will be posted as is with no formatting applied.');
|
$txt_info_1 = __('Text entered in the Message field will be posted as is with no formatting applied.');
|
||||||
$txt_info_2 = _('Line-breaks must be specified with "<br>".');
|
$txt_info_2 = __('Line-breaks must be specified with "<br>".');
|
||||||
|
|
||||||
$form_action = '?';
|
$form_action = '?';
|
||||||
$form_extra = '<input type="hidden" name="rawpost" value="1">';
|
$form_extra = '<input type="hidden" name="rawpost" value="1">';
|
||||||
@ -151,11 +151,11 @@ EOF;
|
|||||||
</div>';
|
</div>';
|
||||||
} else { // Simple CAPTCHA
|
} else { // Simple CAPTCHA
|
||||||
$captcha_inner_html = '
|
$captcha_inner_html = '
|
||||||
<input type="text" name="captcha" id="captcha" size="6" accesskey="c" autocomplete="off"> ' . _('(enter the text below)') . '<br>
|
<input type="text" name="captcha" id="captcha" size="6" accesskey="c" autocomplete="off"> ' . __('(enter the text below)') . '<br>
|
||||||
<img id="captchaimage" src="inc/captcha.php" width="175" height="55" alt="CAPTCHA" onclick="javascript:reloadCAPTCHA()" style="margin-top: 5px;cursor: pointer;">';
|
<img id="captchaimage" src="inc/captcha.php" width="175" height="55" alt="CAPTCHA" onclick="javascript:reloadCAPTCHA()" style="margin-top: 5px;cursor: pointer;">';
|
||||||
}
|
}
|
||||||
|
|
||||||
$txt_captcha = _('CAPTCHA');
|
$txt_captcha = __('CAPTCHA');
|
||||||
$captcha_html = <<<EOF
|
$captcha_html = <<<EOF
|
||||||
<tr>
|
<tr>
|
||||||
<td class="postblock">
|
<td class="postblock">
|
||||||
@ -171,12 +171,12 @@ EOF;
|
|||||||
if (!empty($tinyib_uploads) && ($raw_post || !in_array('file', $hide_fields))) {
|
if (!empty($tinyib_uploads) && ($raw_post || !in_array('file', $hide_fields))) {
|
||||||
if (TINYIB_MAXKB > 0) {
|
if (TINYIB_MAXKB > 0) {
|
||||||
$max_file_size_input_html = '<input type="hidden" name="MAX_FILE_SIZE" value="' . strval(TINYIB_MAXKB * 1024) . '">';
|
$max_file_size_input_html = '<input type="hidden" name="MAX_FILE_SIZE" value="' . strval(TINYIB_MAXKB * 1024) . '">';
|
||||||
$max_file_size_rules_html = '<li>' . sprintf(_('Maximum file size allowed is %s.'), TINYIB_MAXKBDESC) . '</li>';
|
$max_file_size_rules_html = '<li>' . sprintf(__('Maximum file size allowed is %s.'), TINYIB_MAXKBDESC) . '</li>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$filetypes_html = '<li>' . supportedFileTypes() . '</li>';
|
$filetypes_html = '<li>' . supportedFileTypes() . '</li>';
|
||||||
|
|
||||||
$txt_file = _('File');
|
$txt_file = __('File');
|
||||||
$file_input_html = <<<EOF
|
$file_input_html = <<<EOF
|
||||||
<tr>
|
<tr>
|
||||||
<td class="postblock">
|
<td class="postblock">
|
||||||
@ -190,8 +190,8 @@ EOF;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($tinyib_embeds) && ($raw_post || !in_array('embed', $hide_fields))) {
|
if (!empty($tinyib_embeds) && ($raw_post || !in_array('embed', $hide_fields))) {
|
||||||
$txt_embed = _('Embed');
|
$txt_embed = __('Embed');
|
||||||
$txt_embed_help = _('(paste a YouTube URL)');
|
$txt_embed_help = __('(paste a YouTube URL)');
|
||||||
$embed_input_html = <<<EOF
|
$embed_input_html = <<<EOF
|
||||||
<tr>
|
<tr>
|
||||||
<td class="postblock">
|
<td class="postblock">
|
||||||
@ -215,12 +215,12 @@ EOF;
|
|||||||
$maxdimensions .= ' (new thread) or ' . TINYIB_MAXW . 'x' . TINYIB_MAXH . ' (reply)';
|
$maxdimensions .= ' (new thread) or ' . TINYIB_MAXW . 'x' . TINYIB_MAXH . ' (reply)';
|
||||||
}
|
}
|
||||||
|
|
||||||
$thumbnails_html = '<li>' . sprintf(_('Images greater than %s will be thumbnailed.'), $maxdimensions) . '</li>';
|
$thumbnails_html = '<li>' . sprintf(__('Images greater than %s will be thumbnailed.'), $maxdimensions) . '</li>';
|
||||||
}
|
}
|
||||||
|
|
||||||
$unique_posts = uniquePosts();
|
$unique_posts = uniquePosts();
|
||||||
if ($unique_posts > 0) {
|
if ($unique_posts > 0) {
|
||||||
$unique_posts_html = '<li>' . printf(_('Currently %s unique user posts.'), $unique_posts) . '</li>' . "\n";
|
$unique_posts_html = '<li>' . printf(__('Currently %s unique user posts.'), $unique_posts) . '</li>' . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = <<<EOF
|
$output = <<<EOF
|
||||||
@ -233,7 +233,7 @@ EOF;
|
|||||||
$input_extra
|
$input_extra
|
||||||
EOF;
|
EOF;
|
||||||
if ($raw_post || !in_array('name', $hide_fields)) {
|
if ($raw_post || !in_array('name', $hide_fields)) {
|
||||||
$txt_name = _('Name');
|
$txt_name = __('Name');
|
||||||
$output .= <<<EOF
|
$output .= <<<EOF
|
||||||
<tr>
|
<tr>
|
||||||
<td class="postblock">
|
<td class="postblock">
|
||||||
@ -247,7 +247,7 @@ EOF;
|
|||||||
EOF;
|
EOF;
|
||||||
}
|
}
|
||||||
if ($raw_post || !in_array('email', $hide_fields)) {
|
if ($raw_post || !in_array('email', $hide_fields)) {
|
||||||
$txt_email = _('E-mail');
|
$txt_email = __('E-mail');
|
||||||
$output .= <<<EOF
|
$output .= <<<EOF
|
||||||
<tr>
|
<tr>
|
||||||
<td class="postblock">
|
<td class="postblock">
|
||||||
@ -261,7 +261,7 @@ EOF;
|
|||||||
EOF;
|
EOF;
|
||||||
}
|
}
|
||||||
if ($raw_post || !in_array('subject', $hide_fields)) {
|
if ($raw_post || !in_array('subject', $hide_fields)) {
|
||||||
$txt_subject = _('Subject');
|
$txt_subject = __('Subject');
|
||||||
$output .= <<<EOF
|
$output .= <<<EOF
|
||||||
<tr>
|
<tr>
|
||||||
<td class="postblock">
|
<td class="postblock">
|
||||||
@ -275,7 +275,7 @@ EOF;
|
|||||||
EOF;
|
EOF;
|
||||||
}
|
}
|
||||||
if ($raw_post || !in_array('message', $hide_fields)) {
|
if ($raw_post || !in_array('message', $hide_fields)) {
|
||||||
$txt_message = _('Message');
|
$txt_message = __('Message');
|
||||||
$output .= <<<EOF
|
$output .= <<<EOF
|
||||||
<tr>
|
<tr>
|
||||||
<td class="postblock">
|
<td class="postblock">
|
||||||
@ -294,8 +294,8 @@ EOF;
|
|||||||
$embed_input_html
|
$embed_input_html
|
||||||
EOF;
|
EOF;
|
||||||
if ($raw_post || !in_array('password', $hide_fields)) {
|
if ($raw_post || !in_array('password', $hide_fields)) {
|
||||||
$txt_password = _('Password');
|
$txt_password = __('Password');
|
||||||
$txt_password_help = _('(for post and file deletion)');
|
$txt_password_help = __('(for post and file deletion)');
|
||||||
$output .= <<<EOF
|
$output .= <<<EOF
|
||||||
<tr>
|
<tr>
|
||||||
<td class="postblock">
|
<td class="postblock">
|
||||||
@ -369,7 +369,7 @@ function buildPost($post, $res) {
|
|||||||
$direct_link = isEmbed($post["file_hex"]) ? "#" : (($res == TINYIB_RESPAGE ? "../" : "") . "src/" . $post["file"]);
|
$direct_link = isEmbed($post["file_hex"]) ? "#" : (($res == TINYIB_RESPAGE ? "../" : "") . "src/" . $post["file"]);
|
||||||
|
|
||||||
if ($post['parent'] == TINYIB_NEWTHREAD && $post["file"] != '') {
|
if ($post['parent'] == TINYIB_NEWTHREAD && $post["file"] != '') {
|
||||||
$filesize .= (isEmbed($post['file_hex']) ? _('Embed:') : _('File:')) . ' ';
|
$filesize .= (isEmbed($post['file_hex']) ? __('Embed:') : __('File:')) . ' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEmbed($post["file_hex"])) {
|
if (isEmbed($post["file_hex"])) {
|
||||||
@ -466,13 +466,13 @@ EOF;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($post['parent'] == TINYIB_NEWTHREAD && $res == TINYIB_INDEXPAGE) {
|
if ($post['parent'] == TINYIB_NEWTHREAD && $res == TINYIB_INDEXPAGE) {
|
||||||
$return .= " [<a href=\"res/${post["id"]}.html\">" . _("Reply") . "</a>]";
|
$return .= " [<a href=\"res/${post["id"]}.html\">" . __("Reply") . "</a>]";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TINYIB_TRUNCATE > 0 && !$res && substr_count($post['message'], '<br>') > TINYIB_TRUNCATE) { // Truncate messages on board index pages for readability
|
if (TINYIB_TRUNCATE > 0 && !$res && substr_count($post['message'], '<br>') > TINYIB_TRUNCATE) { // Truncate messages on board index pages for readability
|
||||||
$br_offsets = strallpos($post['message'], '<br>');
|
$br_offsets = strallpos($post['message'], '<br>');
|
||||||
$post['message'] = substr($post['message'], 0, $br_offsets[TINYIB_TRUNCATE - 1]);
|
$post['message'] = substr($post['message'], 0, $br_offsets[TINYIB_TRUNCATE - 1]);
|
||||||
$post['message'] .= '<br><span class="omittedposts">' . _('Post truncated. Click Reply to view.') . '</span><br>';
|
$post['message'] .= '<br><span class="omittedposts">' . __('Post truncated. Click Reply to view.') . '</span><br>';
|
||||||
}
|
}
|
||||||
$return .= <<<EOF
|
$return .= <<<EOF
|
||||||
<div class="message">
|
<div class="message">
|
||||||
@ -483,9 +483,9 @@ EOF;
|
|||||||
if ($post['parent'] == TINYIB_NEWTHREAD) {
|
if ($post['parent'] == TINYIB_NEWTHREAD) {
|
||||||
if ($res == TINYIB_INDEXPAGE && $post['omitted'] > 0) {
|
if ($res == TINYIB_INDEXPAGE && $post['omitted'] > 0) {
|
||||||
if ($post['omitted'] == 1) {
|
if ($post['omitted'] == 1) {
|
||||||
$return .= '<span class="omittedposts">' . _('1 post omitted. Click Reply to view.') . '</span>';
|
$return .= '<span class="omittedposts">' . __('1 post omitted. Click Reply to view.') . '</span>';
|
||||||
} else {
|
} else {
|
||||||
$return .= '<span class="omittedposts">' . sprintf(_('%d posts omitted. Click Reply to view.'), $post['omitted']) . '</span>';
|
$return .= '<span class="omittedposts">' . sprintf(__('%d posts omitted. Click Reply to view.'), $post['omitted']) . '</span>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -501,7 +501,7 @@ EOF;
|
|||||||
}
|
}
|
||||||
|
|
||||||
function buildPage($htmlposts, $parent, $pages = 0, $thispage = 0) {
|
function buildPage($htmlposts, $parent, $pages = 0, $thispage = 0) {
|
||||||
$cataloglink = TINYIB_CATALOG ? ('[<a href="catalog.html" style="text-decoration: underline;">' . _('Catalog') . '</a>]') : '';
|
$cataloglink = TINYIB_CATALOG ? ('[<a href="catalog.html" style="text-decoration: underline;">' . __('Catalog') . '</a>]') : '';
|
||||||
$managelink = basename($_SERVER['PHP_SELF']) . "?manage";
|
$managelink = basename($_SERVER['PHP_SELF']) . "?manage";
|
||||||
|
|
||||||
$postingmode = "";
|
$postingmode = "";
|
||||||
@ -511,7 +511,7 @@ function buildPage($htmlposts, $parent, $pages = 0, $thispage = 0) {
|
|||||||
$previous = ($thispage == 1) ? "index" : $thispage - 1;
|
$previous = ($thispage == 1) ? "index" : $thispage - 1;
|
||||||
$next = $thispage + 1;
|
$next = $thispage + 1;
|
||||||
|
|
||||||
$pagelinks = ($thispage == 0) ? ('<td>' . _('Previous') . '</td>') : ('<td><form method="get" action="' . $previous . '.html"><input value="' . _('Previous') . '" type="submit"></form></td>');
|
$pagelinks = ($thispage == 0) ? ('<td>' . __('Previous') . '</td>') : ('<td><form method="get" action="' . $previous . '.html"><input value="' . __('Previous') . '" type="submit"></form></td>');
|
||||||
|
|
||||||
$pagelinks .= "<td>";
|
$pagelinks .= "<td>";
|
||||||
for ($i = 0; $i <= $pages; $i++) {
|
for ($i = 0; $i <= $pages; $i++) {
|
||||||
@ -524,7 +524,7 @@ function buildPage($htmlposts, $parent, $pages = 0, $thispage = 0) {
|
|||||||
}
|
}
|
||||||
$pagelinks .= "</td>";
|
$pagelinks .= "</td>";
|
||||||
|
|
||||||
$pagelinks .= ($pages <= $thispage) ? ('<td>' . _('Next') . '</td>') : ('<td><form method="get" action="' . $next . '.html"><input value="' . _('Next') . '" type="submit"></form></td>');
|
$pagelinks .= ($pages <= $thispage) ? ('<td>' . __('Next') . '</td>') : ('<td><form method="get" action="' . $next . '.html"><input value="' . __('Next') . '" type="submit"></form></td>');
|
||||||
|
|
||||||
$pagenavigator = <<<EOF
|
$pagenavigator = <<<EOF
|
||||||
<table border="1" style="display: inline-block;">
|
<table border="1" style="display: inline-block;">
|
||||||
@ -547,9 +547,9 @@ EOF;
|
|||||||
EOF;
|
EOF;
|
||||||
}
|
}
|
||||||
} else if ($parent == -1) {
|
} else if ($parent == -1) {
|
||||||
$postingmode = '[<a href="index.html">Return</a>]<div class="replymode">' . _('Catalog') . '</div> ';
|
$postingmode = '[<a href="index.html">Return</a>]<div class="replymode">' . __('Catalog') . '</div> ';
|
||||||
} else {
|
} else {
|
||||||
$postingmode = '[<a href="../">Return</a>]<div class="replymode">' . _('Posting mode: Reply') . '</div> ';
|
$postingmode = '[<a href="../">Return</a>]<div class="replymode">' . __('Posting mode: Reply') . '</div> ';
|
||||||
}
|
}
|
||||||
|
|
||||||
$postform = '';
|
$postform = '';
|
||||||
@ -557,9 +557,9 @@ EOF;
|
|||||||
$postform = buildPostForm($parent);
|
$postform = buildPostForm($parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
$txt_manage = _('Manage');
|
$txt_manage = __('Manage');
|
||||||
$txt_style = _('Style');
|
$txt_style = __('Style');
|
||||||
$txt_delete = _('Delete Post');
|
$txt_delete = __('Delete Post');
|
||||||
$body = <<<EOF
|
$body = <<<EOF
|
||||||
<body>
|
<body>
|
||||||
<div class="adminbar">
|
<div class="adminbar">
|
||||||
@ -697,7 +697,7 @@ function adminBar() {
|
|||||||
|
|
||||||
function managePage($text, $onload = '') {
|
function managePage($text, $onload = '') {
|
||||||
$adminbar = adminBar();
|
$adminbar = adminBar();
|
||||||
$txt_manage_mode = _('Manage mode');
|
$txt_manage_mode = __('Manage mode');
|
||||||
$body = <<<EOF
|
$body = <<<EOF
|
||||||
<body$onload>
|
<body$onload>
|
||||||
<div class="adminbar">
|
<div class="adminbar">
|
||||||
@ -729,8 +729,8 @@ function manageOnLoad($page) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function manageLogInForm() {
|
function manageLogInForm() {
|
||||||
$txt_login = _('Log In');
|
$txt_login = __('Log In');
|
||||||
$txt_login_prompt = _('Enter an administrator or moderator password');
|
$txt_login_prompt = __('Enter an administrator or moderator password');
|
||||||
return <<<EOF
|
return <<<EOF
|
||||||
<form id="tinyib" name="tinyib" method="post" action="?manage">
|
<form id="tinyib" name="tinyib" method="post" action="?manage">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@ -746,12 +746,12 @@ EOF;
|
|||||||
}
|
}
|
||||||
|
|
||||||
function manageBanForm() {
|
function manageBanForm() {
|
||||||
$txt_ban = _('Ban an IP address');
|
$txt_ban = __('Ban an IP address');
|
||||||
$txt_ban_ip = _('IP Address:');
|
$txt_ban_ip = __('IP Address:');
|
||||||
$txt_ban_expire = _('Expire(sec):');
|
$txt_ban_expire = __('Expire(sec):');
|
||||||
$txt_ban_reason = _('Reason:');
|
$txt_ban_reason = __('Reason:');
|
||||||
$txt_ban_never = _('never');
|
$txt_ban_never = __('never');
|
||||||
$txt_ban_optional = _('optional');
|
$txt_ban_optional = __('optional');
|
||||||
return <<<EOF
|
return <<<EOF
|
||||||
<form id="tinyib" name="tinyib" method="post" action="?manage&bans">
|
<form id="tinyib" name="tinyib" method="post" action="?manage&bans">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@ -769,11 +769,11 @@ function manageBansTable() {
|
|||||||
$text = '';
|
$text = '';
|
||||||
$allbans = allBans();
|
$allbans = allBans();
|
||||||
if (count($allbans) > 0) {
|
if (count($allbans) > 0) {
|
||||||
$text .= '<table border="1"><tr><th>' . _('IP Address') . '</th><th>' . _('Set At') . '</th><th>' . _('Expires') . '</th><th>' . _('Reason') . '</th><th> </th></tr>';
|
$text .= '<table border="1"><tr><th>' . __('IP Address') . '</th><th>' . __('Set At') . '</th><th>' . __('Expires') . '</th><th>' . __('Reason') . '</th><th> </th></tr>';
|
||||||
foreach ($allbans as $ban) {
|
foreach ($allbans as $ban) {
|
||||||
$expire = ($ban['expire'] > 0) ? date('y/m/d(D)H:i:s', $ban['expire']) : _('Does not expire');
|
$expire = ($ban['expire'] > 0) ? date('y/m/d(D)H:i:s', $ban['expire']) : __('Does not expire');
|
||||||
$reason = ($ban['reason'] == '') ? ' ' : htmlentities($ban['reason']);
|
$reason = ($ban['reason'] == '') ? ' ' : htmlentities($ban['reason']);
|
||||||
$text .= '<tr><td>' . $ban['ip'] . '</td><td>' . date('y/m/d(D)H:i:s', $ban['timestamp']) . '</td><td>' . $expire . '</td><td>' . $reason . '</td><td><a href="?manage&bans&lift=' . $ban['id'] . '">' . _('lift') . '</a></td></tr>';
|
$text .= '<tr><td>' . $ban['ip'] . '</td><td>' . date('y/m/d(D)H:i:s', $ban['timestamp']) . '</td><td>' . $expire . '</td><td>' . $reason . '</td><td><a href="?manage&bans&lift=' . $ban['id'] . '">' . __('lift') . '</a></td></tr>';
|
||||||
}
|
}
|
||||||
$text .= '</table>';
|
$text .= '</table>';
|
||||||
}
|
}
|
||||||
@ -992,7 +992,7 @@ function encodeJSON($array) {
|
|||||||
function buildSinglePostJSON($post) {
|
function buildSinglePostJSON($post) {
|
||||||
$name = $post['name'];
|
$name = $post['name'];
|
||||||
if ($name == '') {
|
if ($name == '') {
|
||||||
$name = _('Anonymous');
|
$name = __('Anonymous');
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = array('id' => $post['id'], 'parent' => $post['parent'], 'timestamp' => $post['timestamp'], 'bumped' => $post['bumped'], 'name' => $name, 'tripcode' => $post['tripcode'], 'subject' => $post['subject'], 'message' => $post['message'], 'file' => $post['file'], 'file_hex' => $post['file_hex'], 'file_original' => $post['file_original'], 'file_size' => $post['file_size'], 'file_size_formated' => $post['file_size_formatted'], 'image_width' => $post['image_width'], 'image_height' => $post['image_height'], 'thumb' => $post['thumb'], 'thumb_width' => $post['thumb_width'], 'thumb_height' => $post['thumb_height']);
|
$output = array('id' => $post['id'], 'parent' => $post['parent'], 'timestamp' => $post['timestamp'], 'bumped' => $post['bumped'], 'name' => $name, 'tripcode' => $post['tripcode'], 'subject' => $post['subject'], 'message' => $post['message'], 'file' => $post['file'], 'file_hex' => $post['file_hex'], 'file_original' => $post['file_original'], 'file_size' => $post['file_size'], 'file_size_formated' => $post['file_size_formatted'], 'image_width' => $post['image_width'], 'image_height' => $post['image_height'], 'thumb' => $post['thumb'], 'thumb_width' => $post['thumb_width'], 'thumb_height' => $post['thumb_height']);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user