From b22ff54f127c1c8b78333e96cda61465d31f1aa6 Mon Sep 17 00:00:00 2001 From: Trevor Slocum Date: Sat, 9 Sep 2023 09:12:53 -0700 Subject: [PATCH] Switch gettext library from v5.6.1 to v4.8.11 This restores compatibility with PHP 5+. Relates to #273. --- inc/gettext.php | 21 +- inc/gettext/CHANGELOG.md | 302 ++++++--- inc/gettext/LICENSE | 2 +- inc/gettext/README.md | 437 +++++++++---- inc/gettext/composer.json | 31 +- inc/gettext/src/BaseTranslator.php | 39 ++ inc/gettext/src/Comments.php | 89 --- inc/gettext/src/Extractors/Blade.php | 34 + inc/gettext/src/Extractors/Csv.php | 53 ++ inc/gettext/src/Extractors/CsvDictionary.php | 47 ++ inc/gettext/src/Extractors/Extractor.php | 80 +++ .../src/Extractors/ExtractorInterface.php | 26 + .../Extractors/ExtractorMultiInterface.php | 28 + inc/gettext/src/Extractors/Jed.php | 55 ++ inc/gettext/src/Extractors/JsCode.php | 74 +++ inc/gettext/src/Extractors/Json.php | 26 + inc/gettext/src/Extractors/JsonDictionary.php | 26 + inc/gettext/src/Extractors/Mo.php | 131 ++++ inc/gettext/src/Extractors/PhpArray.php | 33 + inc/gettext/src/Extractors/PhpCode.php | 170 +++++ inc/gettext/src/Extractors/Po.php | 215 +++++++ inc/gettext/src/Extractors/Twig.php | 45 ++ inc/gettext/src/Extractors/VueJs.php | 423 +++++++++++++ inc/gettext/src/Extractors/Xliff.php | 86 +++ inc/gettext/src/Extractors/Yaml.php | 27 + inc/gettext/src/Extractors/YamlDictionary.php | 27 + inc/gettext/src/Flags.php | 96 --- inc/gettext/src/Generator/Generator.php | 18 - .../src/Generator/GeneratorInterface.php | 13 - inc/gettext/src/Generator/PoGenerator.php | 134 ---- inc/gettext/src/Generators/Csv.php | 56 ++ inc/gettext/src/Generators/CsvDictionary.php | 39 ++ inc/gettext/src/Generators/Generator.php | 22 + .../src/Generators/GeneratorInterface.php | 29 + inc/gettext/src/Generators/Jed.php | 66 ++ inc/gettext/src/Generators/Json.php | 26 + inc/gettext/src/Generators/JsonDictionary.php | 26 + .../MoGenerator.php => Generators/Mo.php} | 71 +-- inc/gettext/src/Generators/PhpArray.php | 40 ++ inc/gettext/src/Generators/Po.php | 145 +++++ inc/gettext/src/Generators/Xliff.php | 122 ++++ inc/gettext/src/Generators/Yaml.php | 32 + inc/gettext/src/Generators/YamlDictionary.php | 32 + inc/gettext/src/GettextTranslator.php | 161 +++++ inc/gettext/src/Headers.php | 144 ----- inc/gettext/src/Loader/Loader.php | 56 -- inc/gettext/src/Loader/LoaderInterface.php | 13 - inc/gettext/src/Loader/MoLoader.php | 142 ----- inc/gettext/src/Loader/PoLoader.php | 226 ------- inc/gettext/src/Merge.php | 231 ++++++- inc/gettext/src/References.php | 89 --- inc/gettext/src/Scanner/CodeScanner.php | 171 ----- .../src/Scanner/FunctionsHandlersTrait.php | 145 ----- .../src/Scanner/FunctionsScannerInterface.php | 12 - inc/gettext/src/Scanner/ParsedFunction.php | 105 ---- inc/gettext/src/Scanner/Scanner.php | 88 --- inc/gettext/src/Scanner/ScannerInterface.php | 22 - inc/gettext/src/Translation.php | 591 +++++++++++++----- inc/gettext/src/Translations.php | 565 ++++++++++++----- inc/gettext/src/Translator.php | 270 ++++++++ inc/gettext/src/TranslatorInterface.php | 112 ++++ inc/gettext/src/Utils/CsvTrait.php | 56 ++ inc/gettext/src/Utils/DictionaryTrait.php | 59 ++ inc/gettext/src/Utils/FunctionsScanner.php | 181 ++++++ .../src/Utils/HeadersExtractorTrait.php | 67 ++ .../src/Utils/HeadersGeneratorTrait.php | 29 + inc/gettext/src/Utils/JsFunctionsScanner.php | 320 ++++++++++ .../src/Utils/MultidimensionalArrayTrait.php | 100 +++ inc/gettext/src/Utils/ParsedComment.php | 140 +++++ inc/gettext/src/Utils/ParsedFunction.php | 159 +++++ inc/gettext/src/Utils/PhpFunctionsScanner.php | 199 ++++++ inc/gettext/src/Utils/StringReader.php | 52 ++ inc/gettext/src/autoloader.php | 14 +- inc/gettext/src/translator_functions.php | 191 ++++++ 74 files changed, 6019 insertions(+), 2185 deletions(-) create mode 100644 inc/gettext/src/BaseTranslator.php delete mode 100644 inc/gettext/src/Comments.php create mode 100644 inc/gettext/src/Extractors/Blade.php create mode 100644 inc/gettext/src/Extractors/Csv.php create mode 100644 inc/gettext/src/Extractors/CsvDictionary.php create mode 100644 inc/gettext/src/Extractors/Extractor.php create mode 100644 inc/gettext/src/Extractors/ExtractorInterface.php create mode 100644 inc/gettext/src/Extractors/ExtractorMultiInterface.php create mode 100644 inc/gettext/src/Extractors/Jed.php create mode 100644 inc/gettext/src/Extractors/JsCode.php create mode 100644 inc/gettext/src/Extractors/Json.php create mode 100644 inc/gettext/src/Extractors/JsonDictionary.php create mode 100644 inc/gettext/src/Extractors/Mo.php create mode 100644 inc/gettext/src/Extractors/PhpArray.php create mode 100644 inc/gettext/src/Extractors/PhpCode.php create mode 100644 inc/gettext/src/Extractors/Po.php create mode 100644 inc/gettext/src/Extractors/Twig.php create mode 100644 inc/gettext/src/Extractors/VueJs.php create mode 100644 inc/gettext/src/Extractors/Xliff.php create mode 100644 inc/gettext/src/Extractors/Yaml.php create mode 100644 inc/gettext/src/Extractors/YamlDictionary.php delete mode 100644 inc/gettext/src/Flags.php delete mode 100644 inc/gettext/src/Generator/Generator.php delete mode 100644 inc/gettext/src/Generator/GeneratorInterface.php delete mode 100644 inc/gettext/src/Generator/PoGenerator.php create mode 100644 inc/gettext/src/Generators/Csv.php create mode 100644 inc/gettext/src/Generators/CsvDictionary.php create mode 100644 inc/gettext/src/Generators/Generator.php create mode 100644 inc/gettext/src/Generators/GeneratorInterface.php create mode 100644 inc/gettext/src/Generators/Jed.php create mode 100644 inc/gettext/src/Generators/Json.php create mode 100644 inc/gettext/src/Generators/JsonDictionary.php rename inc/gettext/src/{Generator/MoGenerator.php => Generators/Mo.php} (67%) create mode 100644 inc/gettext/src/Generators/PhpArray.php create mode 100644 inc/gettext/src/Generators/Po.php create mode 100644 inc/gettext/src/Generators/Xliff.php create mode 100644 inc/gettext/src/Generators/Yaml.php create mode 100644 inc/gettext/src/Generators/YamlDictionary.php create mode 100644 inc/gettext/src/GettextTranslator.php delete mode 100644 inc/gettext/src/Headers.php delete mode 100644 inc/gettext/src/Loader/Loader.php delete mode 100644 inc/gettext/src/Loader/LoaderInterface.php delete mode 100644 inc/gettext/src/Loader/MoLoader.php delete mode 100644 inc/gettext/src/Loader/PoLoader.php delete mode 100644 inc/gettext/src/References.php delete mode 100644 inc/gettext/src/Scanner/CodeScanner.php delete mode 100644 inc/gettext/src/Scanner/FunctionsHandlersTrait.php delete mode 100644 inc/gettext/src/Scanner/FunctionsScannerInterface.php delete mode 100644 inc/gettext/src/Scanner/ParsedFunction.php delete mode 100644 inc/gettext/src/Scanner/Scanner.php delete mode 100644 inc/gettext/src/Scanner/ScannerInterface.php create mode 100644 inc/gettext/src/Translator.php create mode 100644 inc/gettext/src/TranslatorInterface.php create mode 100644 inc/gettext/src/Utils/CsvTrait.php create mode 100644 inc/gettext/src/Utils/DictionaryTrait.php create mode 100644 inc/gettext/src/Utils/FunctionsScanner.php create mode 100644 inc/gettext/src/Utils/HeadersExtractorTrait.php create mode 100644 inc/gettext/src/Utils/HeadersGeneratorTrait.php create mode 100644 inc/gettext/src/Utils/JsFunctionsScanner.php create mode 100644 inc/gettext/src/Utils/MultidimensionalArrayTrait.php create mode 100644 inc/gettext/src/Utils/ParsedComment.php create mode 100644 inc/gettext/src/Utils/ParsedFunction.php create mode 100644 inc/gettext/src/Utils/PhpFunctionsScanner.php create mode 100644 inc/gettext/src/Utils/StringReader.php create mode 100755 inc/gettext/src/translator_functions.php diff --git a/inc/gettext.php b/inc/gettext.php index 14f8014..bff489d 100644 --- a/inc/gettext.php +++ b/inc/gettext.php @@ -1,18 +1,13 @@ loadFile('locale/' . TINYIB_LOCALE . '/tinyib.po'); - -function __($string) { - global $translations; - $translation = $translations->find(null, $string)->getTranslation(); - if ($translation == '') { - return $string; - } - return $translation; -} +$translations = Translations::fromPoFile('locale/' . TINYIB_LOCALE . '/tinyib.po'); +$translator = new Translator(); +$translator->loadTranslations($translations); +$translator->register(); diff --git a/inc/gettext/CHANGELOG.md b/inc/gettext/CHANGELOG.md index 10b3aa2..f04bccc 100644 --- a/inc/gettext/CHANGELOG.md +++ b/inc/gettext/CHANGELOG.md @@ -1,5 +1,4 @@ # 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/) @@ -7,123 +6,220 @@ and this project adheres to [Semantic Versioning](http://semver.org/). Previous releases are documented in [github releases](https://github.com/oscarotero/Gettext/releases) -## [5.6.1] - 2021-12-04 +## [4.8.11] - 2023-08-14 ### Fixed -- PHP 8.1 support [#278]. - -## [5.6.0] - 2021-11-05 -### Added -- New method `addFlag` to `ParsedFunction`, that allows to assign flags by scanners. -- The `FunctionsHandlersTrait` has an abstract `addFlags` method. +- PHP 5.4 support [#289] +## [4.8.10] - 2023-08-10 ### Fixed -- Subsequent load file fails [#257] [#276] -- Upgraded some dependencies in `dev`. +- Previous version was tagged with the incorrect branch. -## [5.5.4] - 2020-12-20 +## [4.8.9] - 2023-08-10 ### Fixed -- TypeError in which numeric entries were converted to integers [#265] +- PHP 8.1 deprecation warning [#289] -## [5.5.3] - 2020-12-01 +## [4.8.8] - 2022-12-08 ### Fixed -- Add PHP 8 to composer.json +- PHP functions prefixed with a slash are being ignored [#284], [#288] -## [5.5.2] - 2020-11-17 +## [4.8.7] - 2022-08-02 ### Fixed -- Parse of multiline disabled translations [#262] [#263] +- Suppress deprecation error on PHP 8.1 [#280] -## [5.5.1] - 2020-06-08 +## [4.8.6] - 2021-10-19 ### Fixed -- Type error in which numeric filenames were converted to integers [#260] +- Parse PO files with multiline disabled entries [#274] -## [5.5.0] - 2020-05-23 -### Added -- New option `addReferences()` to configure the code scanners whether add or not references [#258] +## [4.8.5] - 2021-07-13 +### Fixed +- Prevent adding the same translator comment to multiple functions [#271] +## [4.8.4] - 2021-03-10 +### Fixed +- PHP 8 compatibilty [#266] + +## [4.8.3] - 2020-11-18 +### Fixed +- Blade extractor for Laravel8/Jetstream [#261] + +## [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 -- BREAKING: Moved some code from `CodeScanner` to the new `FunctionsHandlersTrait` in order to better reuse. - -## [5.4.1] - 2020-03-15 -### Fixed -- PoGenerator includes the description and flags of the translations [#253] - -## [5.4.0] - 2020-03-07 -### Added -- Added `_` function to the list of functions scanned by default -- Added `Translations::setDescription()` and `Translations::getDescription()` methods [#251] -- Added `Translations::getFlags()` that returns a `Flags` object to assign flags to the entire po file [#251] - -## [5.3.0] - 2020-02-18 -### Added -- `Comments::delete()` and `Flags::delete()` methods [#247] - -## [5.2.2] - 2020-02-09 -### Fixed -- MoLoader with plurals [#246] - -## [5.2.1] - 2019-12-08 -### Fixed -- Multiline string in PoGenerator [#244] - -## [5.2.0] - 2019-11-25 -### Added -- New function `CodeScanner::extractCommentsStartingWith()` to extract comments from the code. - -## [5.1.0] - 2019-11-11 -### Added -- New function `CodeScanner::ignoreInvalidFunctions()` to ignore invalid functions instead throw an exception - -## 5.0.0 - 2019-11-04 -### Added -- New interfaces: `ScannerInterface` and `FunctionsScannerInterface`. - -### Changed -- Moved the package and dependencies to [php-gettext](https://github.com/php-gettext) organization -- Minimum PHP version supported is 7.2 -- Added php7 strict typing -- Extractors have been split into two different types of classes to import translations: - - Scanners: To scan code files (like php, javascript, twig, etc) in order to collect gettext entries from many domains at the same time. - - Loaders: To load a translation format such po, mo, json, xliff, etc -- Split the `Translation` and `Translations` classes in different sub-classes to handle comments, flags, references, etc. For example, instead `$translation->addComment('foo')` now it's `$translation->getComments()->add('foo')`. -- Simplified the options to merge translations with pre-configured options like `Merged::SCAN_AND_LOAD`. -- The headers of translations are always sorted alphabetically. -- Changed the signature of all classes and interfaces. - -### Removed -- Extractors (now scanners and loaders), generators and translators were removed from this package and published as external packages, allowing to install only those that you need. Only Po and Mo formats are included by default. -- Removed magic classes like `Translations::fromPoFile` or `$translation->toMoFile()`. Now, the scanners, loaders and generators are independent classes that have to be instantiated. -- Removed `Merge::LANGUAGE_OVERRIDE` and `Merge::DOMAIN_OVERRIDE` contants +- Many `private` properties and methods were changed to `protected` in order to improve the extensibility [#231] ### Fixed -- Improved code quality -- The library is easier to extend -- Translation id can be independent of the context + original values, in order to be more compatible with Xliff format. +- PHP 7.4 support [#230] -[#244]: https://github.com/php-gettext/Gettext/issues/244 -[#246]: https://github.com/php-gettext/Gettext/issues/246 -[#247]: https://github.com/php-gettext/Gettext/issues/247 -[#251]: https://github.com/php-gettext/Gettext/issues/251 -[#253]: https://github.com/php-gettext/Gettext/issues/253 -[#257]: https://github.com/php-gettext/Gettext/issues/257 -[#258]: https://github.com/php-gettext/Gettext/issues/258 -[#260]: https://github.com/php-gettext/Gettext/issues/260 -[#262]: https://github.com/php-gettext/Gettext/issues/262 -[#263]: https://github.com/php-gettext/Gettext/issues/263 -[#265]: https://github.com/php-gettext/Gettext/issues/265 -[#276]: https://github.com/php-gettext/Gettext/issues/276 -[#278]: https://github.com/php-gettext/Gettext/issues/278 +## [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] -[5.6.1]: https://github.com/php-gettext/Gettext/compare/v5.6.0...v5.6.1 -[5.6.0]: https://github.com/php-gettext/Gettext/compare/v5.5.4...v5.6.0 -[5.5.4]: https://github.com/php-gettext/Gettext/compare/v5.5.3...v5.5.4 -[5.5.3]: https://github.com/php-gettext/Gettext/compare/v5.5.2...v5.5.3 -[5.5.2]: https://github.com/php-gettext/Gettext/compare/v5.5.1...v5.5.2 -[5.5.1]: https://github.com/php-gettext/Gettext/compare/v5.5.0...v5.5.1 -[5.5.0]: https://github.com/php-gettext/Gettext/compare/v5.4.1...v5.5.0 -[5.4.1]: https://github.com/php-gettext/Gettext/compare/v5.4.0...v5.4.1 -[5.4.0]: https://github.com/php-gettext/Gettext/compare/v5.3.0...v5.4.0 -[5.3.0]: https://github.com/php-gettext/Gettext/compare/v5.2.2...v5.3.0 -[5.2.2]: https://github.com/php-gettext/Gettext/compare/v5.2.1...v5.2.2 -[5.2.1]: https://github.com/php-gettext/Gettext/compare/v5.2.0...v5.2.1 -[5.2.0]: https://github.com/php-gettext/Gettext/compare/v5.1.0...v5.2.0 -[5.1.0]: https://github.com/php-gettext/Gettext/compare/v5.0.0...v5.1.0 +### 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 +[#261]: https://github.com/oscarotero/Gettext/issues/261 +[#266]: https://github.com/oscarotero/Gettext/issues/266 +[#271]: https://github.com/oscarotero/Gettext/issues/271 +[#274]: https://github.com/oscarotero/Gettext/issues/274 +[#280]: https://github.com/oscarotero/Gettext/issues/280 +[#284]: https://github.com/oscarotero/Gettext/issues/284 +[#288]: https://github.com/oscarotero/Gettext/issues/288 +[#289]: https://github.com/oscarotero/Gettext/issues/289 + +[4.8.11]: https://github.com/oscarotero/Gettext/compare/v4.8.10...v4.8.11 +[4.8.10]: https://github.com/oscarotero/Gettext/compare/v4.8.9...v4.8.10 +[4.8.9]: https://github.com/oscarotero/Gettext/compare/v4.8.8...v4.8.9 +[4.8.8]: https://github.com/oscarotero/Gettext/compare/v4.8.7...v4.8.8 +[4.8.7]: https://github.com/oscarotero/Gettext/compare/v4.8.6...v4.8.7 +[4.8.6]: https://github.com/oscarotero/Gettext/compare/v4.8.5...v4.8.6 +[4.8.5]: https://github.com/oscarotero/Gettext/compare/v4.8.4...v4.8.5 +[4.8.4]: https://github.com/oscarotero/Gettext/compare/v4.8.3...v4.8.4 +[4.8.3]: https://github.com/oscarotero/Gettext/compare/v4.8.2...v4.8.3 +[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 +[4.3.0]: https://github.com/oscarotero/Gettext/releases/tag/v4.3.0 diff --git a/inc/gettext/LICENSE b/inc/gettext/LICENSE index 106de2a..2385321 100644 --- a/inc/gettext/LICENSE +++ b/inc/gettext/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2019 Oscar Otero Marzoa +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 diff --git a/inc/gettext/README.md b/inc/gettext/README.md index 0d181c9..4911afb 100644 --- a/inc/gettext/README.md +++ b/inc/gettext/README.md @@ -1,68 +1,134 @@ -# Gettext +Gettext +======= -[![Latest Version on Packagist][ico-version]][link-packagist] -[![Software License][ico-license]](LICENSE) -![ico-ga] -[![Total Downloads][ico-downloads]][link-downloads] +[![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) -> Note: this is the documentation of the new 5.x version. Go to [4.x branch](https://github.com/php-gettext/Gettext/tree/4.x) if you're looking for the old 4.x version +[![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 (MIT License) -Gettext is a PHP (^7.2) library to import/export/edit gettext from PO, MO, PHP, JS files, etc. +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 (under the same domain) -* `Gettext\Scanner\*` - Scan files to extract translations (php, js, twig templates, ...) -* `Gettext\Loader\*` - Load translations from different formats (po, mo, json, ...) -* `Gettext\Generator\*` - Export translations to various formats (po, mo, json, ...) +* `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\Loader\PoLoader; -use Gettext\Generator\MoGenerator; +use Gettext\Translations; //import from a .po file: -$loader = new PoLoader(); -$translations = $loader->loadFile('locales/gl.po'); +$translations = Translations::fromPoFile('locales/gl.po'); //edit some translations: $translation = $translations->find(null, 'apple'); if ($translation) { - $translation->translate('Mazá'); + $translation->setTranslation('Mazá'); } -//export to a .mo file: -$generator = new MoGenerator(); -$generator->generateFile($translations, 'Locale/gl/LC_MESSAGES/messages.mo'); +//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 -use Gettext\Translation; +// __construct($context, $original, $plural) +$translation = new Gettext\Translation('comments', 'One comment', '%s comments'); -$translation = Translation::create('comments', 'One comment', '%s comments'); +$translation->setTranslation('Un comentario'); +$translation->setPluralTranslation('%s comentarios'); -$translation->translate('Un comentario'); -$translation->translatePlural('%s comentarios'); - -$translation->getReferences()->add('templates/comments/comment.php', 34); -$translation->getComments()->add('To display the amount of comments in a post'); +$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 @@ -76,134 +142,223 @@ echo $translation->getTranslation(); // Un comentario The `Gettext\Translations` class stores a collection of translations: ```php -use Gettext\Translations; +$translations = new Gettext\Translations(); -$translations = Translations::create('my-domain'); +//You can add new translations using the array syntax +$translations[] = new Gettext\Translation('comments', 'One comment', '%s comments'); -//You can add new translations: -$translation = Translation::create('comments', 'One comment', '%s comments'); -$translations->add($translation); +//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->getHeaders()->set('Last-Translator', 'Oscar Otero'); +$translations->setHeader('Last-Translator', 'Oscar Otero'); $translations->setDomain('my-blog'); ``` -## Loaders +## Extractors -The loaders allows to get gettext values from any format. For example, to load a .po file: +The extrators allows to fetch gettext values from any source. For example, to scan a .po file: ```php -use Gettext\Loader\PoLoader; - -$loader = new PoLoader(); +$translations = new Gettext\Translations(); //From a file -$translations = $loader->loadFile('locales/en.po'); +Gettext\Extractors\Po::fromFile('locales/en.po', $translations); //From a string $string = file_get_contents('locales2/en.po'); -$translations = $loader->loadString($string); +Gettext\Extractors\Po::fromString($string, $translations); ``` -This package includes the following loaders: +The better way to use extractors is using the magic methods of `Gettext\Translations`: -- `MoLoader` -- `PoLoader` +```php +//Create a Translations instance using a po file +$translations = Gettext\Translations::fromPoFile('locales/en.po'); -And you can install other formats with loaders and generators: +//Add more messages from other files +$translations->addFromPoFile('locales2/en.po'); +``` -- [Json](https://github.com/php-gettext/Json) +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, etc). +The generators export a `Gettext\Translations` instance to any format (po, mo, array, etc). ```php -use Gettext\Loader\PoLoader; -use Gettext\Generator\MoGenerator; +//Save to a file +Gettext\Generators\Po::toFile($translations, 'locales/en.po'); -//Load a PO file -$poLoader = new PoLoader(); - -$translations = $poLoader->loadFile('locales/en.po'); - -//Save to MO file -$moGenerator = new MoGenerator(); - -$moGenerator->generateFile($translations, 'locales/en.mo'); - -//Or return as a string -$content = $moGenerator->generateString($translations); -file_put_contents('locales/en.mo', $content); +//Return as a string +$content = Gettext\Generators\Po::toString($translations); +file_put_contents('locales/en.po', $content); ``` -This package includes the following generators: - -- `MoGenerator` -- `PoGenerator` - -And you can install other formats with loaders and generators: - -- [Json](https://github.com/php-gettext/Json) - - -## Scanners - -Scanners allow to search and extract new gettext entries from different sources like php files, twig templates, blade templates, etc. Unlike loaders, scanners allows to extract gettext entries with different domains at the same time: +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 + + + + + + +``` + + +## 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\Scanner\PhpScanner; use Gettext\Translations; -//Create a new scanner, adding a translation for each domain we want to get: -$phpScanner = new PhpScanner( - Translations::create('domain1'), - Translations::create('domain2'), - Translations::create('domain3') -); - -//Set a default domain, so any translations with no domain specified, will be added to that domain -$phpScanner->setDefaultDomain('domain1'); - -//Extract all comments starting with 'i18n:' and 'Translators:' -$phpScanner->extractCommentsStartingWith('i18n:', 'Translators:'); - -//Scan files -foreach (glob('*.php') as $file) { - $phpScanner->scanFile($file); -} - -//Get the translations -list('domain1' => $domain1, 'domain2' => $domain2, 'domain3' => $domain3) = $phpScanner->getTranslations(); +$translations = Translations::fromPoFile('my-file1.po'); +$translations->addFromPoFile('my-file2.po'); ``` -This package does not include any scanner by default. But there are some that you can install: - -- [PHP Scanner](https://github.com/php-gettext/PHP-Scanner) -- [JS Scanner](https://github.com/php-gettext/JS-Scanner) - -## Merging translations - -You will want to update or merge translations. The function `mergeWith` create a new `Translations` instance with other translations merged: +A more advanced way is merge two `Translations` instances: ```php -$translations3 = $translations1->mergeWith($translations2); +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 ``` -But sometimes this is not enough, and this is why we have merging options, allowing to configure how two translations will be merged. These options are defined as constants in the `Gettext\Merge` class, and are the following: +The second argument of `mergeWith` defines how the merge will be done. Use the `Gettext\Merge` constants to configure the merging: Constant | Description --------- | ----------- -`Merge::TRANSLATIONS_OURS` | Use only the translations present in `$translations1` -`Merge::TRANSLATIONS_THEIRS` | Use only the translations present in `$translations2` -`Merge::TRANSLATION_OVERRIDE` | Override the translation and plural translations with the value of `$translation2` -`Merge::HEADERS_OURS` | Use only the headers of `$translations1` -`Merge::HEADERS_REMOVE` | Use only the headers of `$translations2` +`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` @@ -213,48 +368,58 @@ Constant | Description `Merge::REFERENCES_OURS` | Use only the references of `$translation1` `Merge::REFERENCES_THEIRS` | Use only the references of `$translation2` -Use the second argument to configure the merging strategy: +Example: ```php -$strategy = Merge::TRANSLATIONS_OURS | Merge::HEADERS_OURS; +use Gettext\Translations; +use Gettext\Merge; -$translations3 = $translations1->mergeWith($translations2, $strategy); +//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'); ``` -There are some typical scenarios, one of the most common: +Note, if the second argument is not defined, the default value is `Merge::DEFAULTS` that's equivalent to `Merge::ADD | Merge::HEADERS_ADD`. -- Scan php templates searching for entries to translate -- Complete these entries with the translations stored in a .po file -- You may want to add new entries to the .po file -- And also remove those entries present in the .po file but not in the templates (because they were removed) -- But you want to update some translations with new references and extracted comments -- And keep the translations, comments and flags defined in .po file +## Use from CLI -For this scenario, you can use the option `Merge::SCAN_AND_LOAD` with the combination of options to fit this needs (SCAN new entries and LOAD a .po file). +There's a Robo task to use this library from the command line interface: https://github.com/oscarotero/GettextRobo -```php -$newEntries = $scanner->scanFile('template.php'); -$previousEntries = $loader->loadFile('translations.po'); +## Use in the browser -$updatedEntries = $newEntries->mergeWith($previousEntries); -``` +If you want to use your translations in the browser, there's a javascript translator: https://github.com/oscarotero/gettext-translator -More common scenarios may be added in a future. +## 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 -Please see [CHANGELOG](CHANGELOG.md) for more information about recent changes and [CONTRIBUTING](CONTRIBUTING.md) for contributing details. +If this library is useful for you, consider to donate to the author. -The MIT License (MIT). Please see [LICENSE](LICENSE) for more information. +[Buy me a beer :beer:](https://www.paypal.me/oscarotero) -[ico-version]: https://img.shields.io/packagist/v/gettext/gettext.svg?style=flat-square -[ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square -[ico-ga]: https://github.com/php-gettext/Gettext/workflows/testing/badge.svg -[ico-downloads]: https://img.shields.io/packagist/dt/gettext/gettext.svg?style=flat-square - -[link-packagist]: https://packagist.org/packages/gettext/gettext -[link-downloads]: https://packagist.org/packages/gettext/gettext +Thanks in advance! diff --git a/inc/gettext/composer.json b/inc/gettext/composer.json index 16bdde9..289e14f 100644 --- a/inc/gettext/composer.json +++ b/inc/gettext/composer.json @@ -3,7 +3,7 @@ "type": "library", "description": "PHP gettext manager", "keywords": ["js", "gettext", "i18n", "translation", "po", "mo"], - "homepage": "https://github.com/php-gettext/Gettext", + "homepage": "https://github.com/oscarotero/Gettext", "license": "MIT", "authors": [ { @@ -15,18 +15,25 @@ ], "support": { "email": "oom@oscarotero.com", - "issues": "https://github.com/php-gettext/Gettext/issues" + "issues": "https://github.com/oscarotero/Gettext/issues" }, "require": { - "php": "^7.2|^8.0", + "php": ">=5.4.0", "gettext/languages": "^2.3" }, "require-dev": { - "phpunit/phpunit": "^8.0|^9.0", - "squizlabs/php_codesniffer": "^3.0", - "brick/varexporter": "^0.3.5", - "friendsofphp/php-cs-fixer": "^3.2", - "oscarotero/php-cs-fixer-config": "^2.0" + "illuminate/view": "^5.0.x-dev", + "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": { @@ -42,7 +49,11 @@ "test": [ "phpunit", "phpcs" - ], - "cs-fix": "php-cs-fixer fix" + ] + }, + "config": { + "allow-plugins": { + "kylekatarnls/update-helper": false + } } } diff --git a/inc/gettext/src/BaseTranslator.php b/inc/gettext/src/BaseTranslator.php new file mode 100644 index 0000000..c52038f --- /dev/null +++ b/inc/gettext/src/BaseTranslator.php @@ -0,0 +1,39 @@ +add(...$comments); - } - } - - public function __debugInfo() - { - return $this->toArray(); - } - - public function add(string ...$comments): self - { - foreach ($comments as $comment) { - if (!in_array($comment, $this->comments)) { - $this->comments[] = $comment; - } - } - - return $this; - } - - public function delete(string ...$comments): self - { - foreach ($comments as $comment) { - $key = array_search($comment, $this->comments); - - if (is_int($key)) { - array_splice($this->comments, $key, 1); - } - } - - return $this; - } - - #[ReturnTypeWillChange] - public function jsonSerialize() - { - return $this->toArray(); - } - - #[ReturnTypeWillChange] - public function getIterator() - { - return new ArrayIterator($this->comments); - } - - public function count(): int - { - return count($this->comments); - } - - public function toArray(): array - { - return $this->comments; - } - - public function mergeWith(Comments $comments): Comments - { - $merged = clone $this; - $merged->add(...$comments->comments); - - return $merged; - } -} diff --git a/inc/gettext/src/Extractors/Blade.php b/inc/gettext/src/Extractors/Blade.php new file mode 100644 index 0000000..9122bb0 --- /dev/null +++ b/inc/gettext/src/Extractors/Blade.php @@ -0,0 +1,34 @@ +withoutComponentTags(); + } + + $string = $bladeCompiler->compileString($string); + } else { + $string = $options['facade']::compileString($string); + } + + PhpCode::fromString($string, $translations, $options); + } +} diff --git a/inc/gettext/src/Extractors/Csv.php b/inc/gettext/src/Extractors/Csv.php new file mode 100644 index 0000000..fba3a98 --- /dev/null +++ b/inc/gettext/src/Extractors/Csv.php @@ -0,0 +1,53 @@ + ",", + '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); + } +} diff --git a/inc/gettext/src/Extractors/CsvDictionary.php b/inc/gettext/src/Extractors/CsvDictionary.php new file mode 100644 index 0000000..807357c --- /dev/null +++ b/inc/gettext/src/Extractors/CsvDictionary.php @@ -0,0 +1,47 @@ + ",", + '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); + } +} diff --git a/inc/gettext/src/Extractors/Extractor.php b/inc/gettext/src/Extractors/Extractor.php new file mode 100644 index 0000000..59974aa --- /dev/null +++ b/inc/gettext/src/Extractors/Extractor.php @@ -0,0 +1,80 @@ +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); + } + } +} diff --git a/inc/gettext/src/Extractors/JsCode.php b/inc/gettext/src/Extractors/JsCode.php new file mode 100644 index 0000000..0a88d4f --- /dev/null +++ b/inc/gettext/src/Extractors/JsCode.php @@ -0,0 +1,74 @@ + [], + + '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); + } + } +} diff --git a/inc/gettext/src/Extractors/Json.php b/inc/gettext/src/Extractors/Json.php new file mode 100644 index 0000000..3aaea29 --- /dev/null +++ b/inc/gettext/src/Extractors/Json.php @@ -0,0 +1,26 @@ +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)); + } +} diff --git a/inc/gettext/src/Extractors/PhpArray.php b/inc/gettext/src/Extractors/PhpArray.php new file mode 100644 index 0000000..3e4b262 --- /dev/null +++ b/inc/gettext/src/Extractors/PhpArray.php @@ -0,0 +1,33 @@ + 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; + } +} diff --git a/inc/gettext/src/Extractors/Po.php b/inc/gettext/src/Extractors/Po.php new file mode 100644 index 0000000..4343a79 --- /dev/null +++ b/inc/gettext/src/Extractors/Po.php @@ -0,0 +1,215 @@ +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])) { + $nextLine = trim($lines[$j + 1]); + if (substr($nextLine, 0, 1) == '"') { + $line = substr($line, 0, -1).substr($nextLine, 1); + continue; + } + if (substr($nextLine, 0, 4) == '#~ "') { + $line = substr($line, 0, -1).substr($nextLine, 4); + continue; + } + } + $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", + '\\"' => '"', + ] + ); + } +} diff --git a/inc/gettext/src/Extractors/Twig.php b/inc/gettext/src/Extractors/Twig.php new file mode 100644 index 0000000..2060d08 --- /dev/null +++ b/inc/gettext/src/Extractors/Twig.php @@ -0,0 +1,45 @@ + '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; + } +} diff --git a/inc/gettext/src/Extractors/VueJs.php b/inc/gettext/src/Extractors/VueJs.php new file mode 100644 index 0000000..0d29f45 --- /dev/null +++ b/inc/gettext/src/Extractors/VueJs.php @@ -0,0 +1,423 @@ + [], + + '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