Use third-party translation library

Resolves #101.
This commit is contained in:
Trevor Slocum 2020-08-31 20:51:14 -07:00
parent 6bc831ae19
commit 2bbbfc972c
60 changed files with 6558 additions and 111 deletions

View file

@ -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
SOFTWARE.
*/
use Gettext\Translator;
use Gettext\Translations;
error_reporting(E_ALL);
ini_set("display_errors", 1);
@ -60,24 +62,24 @@ if (!file_exists('settings.php')) {
}
require 'settings.php';
if (function_exists('_')) {
if (defined('TINYIB_LOCALE')) {
setlocale(LC_ALL, TINYIB_LOCALE);
}
bindtextdomain('tinyib', 'locale');
textdomain('tinyib');
} else {
function _($string) {
if (TINYIB_LOCALE == '') {
function __($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 == '') {
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 == '')) {
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
@ -87,7 +89,7 @@ if (TINYIB_DBMODE == 'flatfile') {
}
foreach ($writedirs as $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'))) {
$includes[] = 'inc/database_' . TINYIB_DBMODE . '.php';
} else {
fancyDie(_('Unknown database mode specified.'));
fancyDie(__('Unknown database mode specified.'));
}
foreach ($includes as $include) {
@ -110,7 +112,7 @@ $redirect = true;
// 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 (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();
@ -129,9 +131,9 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
if ($post['parent'] != TINYIB_NEWTHREAD && !$loggedin) {
$parent = postByID($post['parent']);
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) {
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($_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']));
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;
@ -193,14 +195,14 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
} else if ($file_mime == "image/png") {
$post['thumb'] = $temp_file . '.png';
} else {
fancyDie(_('Error while processing audio/video.'));
fancyDie(__('Error while processing audio/video.'));
}
$thumb_location = "thumb/" . $post['thumb'];
list($thumb_maxwidth, $thumb_maxheight) = thumbnailDimensions($post);
if (!createThumbnail($file_location, $thumb_location, $thumb_maxwidth, $thumb_maxheight)) {
fancyDie(_('Could not create thumbnail.'));
fancyDie(__('Could not create thumbnail.'));
}
addVideoOverlay($thumb_location);
@ -216,11 +218,11 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
validateFileUpload();
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))) {
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));
@ -235,7 +237,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
$file_mime = strtolower(array_pop($file_mime_split));
} else {
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']);
@ -251,12 +253,12 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
$file_location = "src/" . $post['file'];
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)) {
@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") {
@ -275,7 +277,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
if ($post['thumb_width'] <= 0 || $post['thumb_height'] <= 0) {
@unlink($file_location);
@unlink("thumb/" . $post['thumb']);
fancyDie(_('Sorry, your video appears to be corrupt.'));
fancyDie(__('Sorry, your video appears to be corrupt.'));
}
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);
if (!copy($tinyib_uploads[$file_mime][1], "thumb/" . $post['thumb'])) {
@unlink($file_location);
fancyDie(_('Could not create thumbnail.'));
fancyDie(__('Could not create thumbnail.'));
}
if ($file_mime == "application/x-shockwave-flash") {
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)) {
@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";
}
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']) == "") {
$die_msg = "";
@ -348,12 +350,12 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
fancyDie("Please $die_msg.");
}
} 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')) {
$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;
}
@ -366,7 +368,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
trimThreads();
echo _('Updating thread...') . '<br>';
echo __('Updating thread...') . '<br>';
if ($post['parent'] != TINYIB_NEWTHREAD) {
rebuildThread($post['parent']);
@ -379,17 +381,17 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
rebuildThread($post['id']);
}
echo _('Updating index...') . '<br>';
echo __('Updating index...') . '<br>';
rebuildIndexes();
}
// Check if the request is to delete a post and/or its associated image
} elseif (isset($_GET['delete']) && !isset($_GET['manage'])) {
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) {
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']);
@ -406,12 +408,12 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
} else {
threadUpdated($post['parent']);
}
fancyDie(_('Post deleted.'));
fancyDie(__('Post deleted.'));
} else {
fancyDie(_('Invalid password.'));
fancyDie(__('Invalid password.'));
}
} 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;
@ -435,7 +437,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
rebuildThread($thread['id']);
}
rebuildIndexes();
$text .= manageInfo(_('Rebuilt board.'));
$text .= manageInfo(__('Rebuilt board.'));
} elseif (isset($_GET['bans'])) {
clearExpiredBans();
@ -443,7 +445,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
if ($_POST['ip'] != '') {
$banexists = banByIP($_POST['ip']);
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();
@ -452,13 +454,13 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
$ban['reason'] = $_POST['reason'];
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'])) {
$ban = banByID($_GET['lift']);
if ($ban) {
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) {
rebuildThread($post['parent']);
}
$text .= manageInfo(sprintf(_('Post No.%d deleted.'), $post['id']));
$text .= manageInfo(sprintf(__('Post No.%d deleted.'), $post['id']));
} 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'])) {
if ($_GET['approve'] > 0) {
@ -566,9 +568,9 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
}
threadUpdated($thread_id);
$text .= manageInfo(sprintf(_('Post No.%d approved.'), $post['id']));
$text .= manageInfo(sprintf(__('Post No.%d approved.'), $post['id']));
} 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'])) {
@ -577,7 +579,7 @@ if (!isset($_GET['delete']) && !isset($_GET['manage']) && (isset($_POST['name'])
if ($post) {
$text .= manageModeratePost($post);
} 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 {
$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') . '.');
} 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 {
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'])) {
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') . '.');
} 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 {
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"])) {
$onload = manageOnLoad("rawpost");

View file

@ -211,7 +211,7 @@ function nameAndTripcode($name) {
function nameBlock($name, $tripcode, $email, $timestamp, $rawposttext) {
$output = '<span class="postername">';
$output .= ($name == '' && $tripcode == '') ? _('Anonymous') : $name;
$output .= ($name == '' && $tripcode == '') ? __('Anonymous') : $name;
if ($tripcode != '') {
$output .= '</span><span class="postertrip">!' . $tripcode;
@ -320,9 +320,9 @@ function checkCAPTCHA() {
$captcha_solution = isset($_SESSION['tinyibcaptcha']) ? strtolower(trim($_SESSION['tinyibcaptcha'])) : '';
if ($captcha == '') {
fancyDie(_('Please enter the CAPTCHA text.'));
fancyDie(__('Please enter the CAPTCHA text.'));
} 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() {
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 ($_POST["parent"] != TINYIB_NEWTHREAD) {
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"];
@ -410,25 +410,25 @@ function validateFileUpload() {
case UPLOAD_ERR_OK:
break;
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;
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;
case UPLOAD_ERR_PARTIAL:
fancyDie(_('The uploaded file was only partially uploaded.'));
fancyDie(__('The uploaded file was only partially uploaded.'));
break;
case UPLOAD_ERR_NO_FILE:
fancyDie(_('No file was uploaded.'));
fancyDie(__('No file was uploaded.'));
break;
case UPLOAD_ERR_NO_TMP_DIR:
fancyDie(_('Missing a temporary folder.'));
fancyDie(__('Missing a temporary folder.'));
break;
case UPLOAD_ERR_CANT_WRITE:
fancyDie(_('Failed to write file to disk'));
fancyDie(__('Failed to write file to disk'));
break;
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);
if (count($hexmatches) > 0) {
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) {
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);

172
inc/gettext/CHANGELOG.md Normal file
View 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

View 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
View 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
View file

@ -0,0 +1,425 @@
Gettext
=======
[![Build Status](https://travis-ci.org/oscarotero/Gettext.png?branch=master)](https://travis-ci.org/oscarotero/Gettext)
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/oscarotero/Gettext/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/oscarotero/Gettext/?branch=master)
[![Latest Stable Version](https://poser.pugx.org/gettext/gettext/v/stable.svg)](https://packagist.org/packages/gettext/gettext)
[![Total Downloads](https://poser.pugx.org/gettext/gettext/downloads.svg)](https://packagist.org/packages/gettext/gettext)
[![Monthly Downloads](https://poser.pugx.org/gettext/gettext/d/monthly.png)](https://packagist.org/packages/gettext/gettext)
[![License](https://poser.pugx.org/gettext/gettext/license.svg)](https://packagist.org/packages/gettext/gettext)
[![SensioLabsInsight](https://insight.sensiolabs.com/projects/496dc2a6-43be-4046-a283-f8370239dd47/big.png)](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
View 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"
]
}
}

View 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';
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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 = []);
}

View 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 = []);
}

View 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