From fda7425f6b3ed3ada11cbea79a50b76cbb6ff5a2 Mon Sep 17 00:00:00 2001 From: Jay Trees Date: Tue, 22 Mar 2022 08:41:26 +0100 Subject: [PATCH] require gettext/gettext --- composer.json | 3 +- composer.lock | 150 ++- vendor/composer/autoload_psr4.php | 2 + vendor/composer/autoload_static.php | 10 + vendor/composer/installed.json | 154 +++ vendor/composer/installed.php | 22 +- vendor/gettext/gettext/CHANGELOG.md | 129 +++ vendor/gettext/gettext/CONTRIBUTING.md | 17 + vendor/gettext/gettext/LICENSE | 21 + vendor/gettext/gettext/README.md | 260 +++++ vendor/gettext/gettext/composer.json | 48 + vendor/gettext/gettext/src/Comments.php | 89 ++ vendor/gettext/gettext/src/Flags.php | 96 ++ .../gettext/src/Generator/Generator.php | 18 + .../src/Generator/GeneratorInterface.php | 13 + .../gettext/src/Generator/MoGenerator.php | 151 +++ .../gettext/src/Generator/PoGenerator.php | 134 +++ vendor/gettext/gettext/src/Headers.php | 144 +++ vendor/gettext/gettext/src/Loader/Loader.php | 56 ++ .../gettext/src/Loader/LoaderInterface.php | 13 + .../gettext/gettext/src/Loader/MoLoader.php | 142 +++ .../gettext/gettext/src/Loader/PoLoader.php | 226 +++++ vendor/gettext/gettext/src/Merge.php | 40 + vendor/gettext/gettext/src/References.php | 89 ++ .../gettext/src/Scanner/CodeScanner.php | 171 ++++ .../src/Scanner/FunctionsHandlersTrait.php | 145 +++ .../src/Scanner/FunctionsScannerInterface.php | 12 + .../gettext/src/Scanner/ParsedFunction.php | 105 ++ .../gettext/gettext/src/Scanner/Scanner.php | 88 ++ .../gettext/src/Scanner/ScannerInterface.php | 22 + vendor/gettext/gettext/src/Translation.php | 236 +++++ vendor/gettext/gettext/src/Translations.php | 216 +++++ vendor/gettext/languages/LICENSE | 22 + vendor/gettext/languages/UNICODE-LICENSE.txt | 46 + .../gettext/languages/bin/export-plural-rules | 290 ++++++ .../languages/bin/export-plural-rules.bat | 1 + vendor/gettext/languages/composer.json | 51 + vendor/gettext/languages/src/Category.php | 124 +++ vendor/gettext/languages/src/CldrData.php | 342 +++++++ .../gettext/languages/src/Exporter/Docs.php | 64 ++ .../languages/src/Exporter/Exporter.php | 153 +++ .../gettext/languages/src/Exporter/Html.php | 69 ++ .../gettext/languages/src/Exporter/Json.php | 71 ++ vendor/gettext/languages/src/Exporter/Php.php | 61 ++ vendor/gettext/languages/src/Exporter/Po.php | 37 + .../languages/src/Exporter/Prettyjson.php | 32 + .../gettext/languages/src/Exporter/Ruby.php | 60 ++ vendor/gettext/languages/src/Exporter/Xml.php | 60 ++ .../languages/src/FormulaConverter.php | 172 ++++ vendor/gettext/languages/src/Language.php | 411 ++++++++ vendor/gettext/languages/src/autoloader.php | 13 + .../src/cldr-data/main/en-US/languages.json | 657 +++++++++++++ .../src/cldr-data/main/en-US/scripts.json | 223 +++++ .../src/cldr-data/main/en-US/territories.json | 327 +++++++ .../src/cldr-data/supplemental/plurals.json | 912 ++++++++++++++++++ 55 files changed, 7216 insertions(+), 4 deletions(-) create mode 100644 vendor/gettext/gettext/CHANGELOG.md create mode 100644 vendor/gettext/gettext/CONTRIBUTING.md create mode 100644 vendor/gettext/gettext/LICENSE create mode 100644 vendor/gettext/gettext/README.md create mode 100644 vendor/gettext/gettext/composer.json create mode 100644 vendor/gettext/gettext/src/Comments.php create mode 100644 vendor/gettext/gettext/src/Flags.php create mode 100644 vendor/gettext/gettext/src/Generator/Generator.php create mode 100644 vendor/gettext/gettext/src/Generator/GeneratorInterface.php create mode 100644 vendor/gettext/gettext/src/Generator/MoGenerator.php create mode 100644 vendor/gettext/gettext/src/Generator/PoGenerator.php create mode 100644 vendor/gettext/gettext/src/Headers.php create mode 100644 vendor/gettext/gettext/src/Loader/Loader.php create mode 100644 vendor/gettext/gettext/src/Loader/LoaderInterface.php create mode 100644 vendor/gettext/gettext/src/Loader/MoLoader.php create mode 100644 vendor/gettext/gettext/src/Loader/PoLoader.php create mode 100644 vendor/gettext/gettext/src/Merge.php create mode 100644 vendor/gettext/gettext/src/References.php create mode 100644 vendor/gettext/gettext/src/Scanner/CodeScanner.php create mode 100644 vendor/gettext/gettext/src/Scanner/FunctionsHandlersTrait.php create mode 100644 vendor/gettext/gettext/src/Scanner/FunctionsScannerInterface.php create mode 100644 vendor/gettext/gettext/src/Scanner/ParsedFunction.php create mode 100644 vendor/gettext/gettext/src/Scanner/Scanner.php create mode 100644 vendor/gettext/gettext/src/Scanner/ScannerInterface.php create mode 100644 vendor/gettext/gettext/src/Translation.php create mode 100644 vendor/gettext/gettext/src/Translations.php create mode 100644 vendor/gettext/languages/LICENSE create mode 100644 vendor/gettext/languages/UNICODE-LICENSE.txt create mode 100644 vendor/gettext/languages/bin/export-plural-rules create mode 100644 vendor/gettext/languages/bin/export-plural-rules.bat create mode 100644 vendor/gettext/languages/composer.json create mode 100644 vendor/gettext/languages/src/Category.php create mode 100644 vendor/gettext/languages/src/CldrData.php create mode 100644 vendor/gettext/languages/src/Exporter/Docs.php create mode 100644 vendor/gettext/languages/src/Exporter/Exporter.php create mode 100644 vendor/gettext/languages/src/Exporter/Html.php create mode 100644 vendor/gettext/languages/src/Exporter/Json.php create mode 100644 vendor/gettext/languages/src/Exporter/Php.php create mode 100644 vendor/gettext/languages/src/Exporter/Po.php create mode 100644 vendor/gettext/languages/src/Exporter/Prettyjson.php create mode 100644 vendor/gettext/languages/src/Exporter/Ruby.php create mode 100644 vendor/gettext/languages/src/Exporter/Xml.php create mode 100644 vendor/gettext/languages/src/FormulaConverter.php create mode 100644 vendor/gettext/languages/src/Language.php create mode 100644 vendor/gettext/languages/src/autoloader.php create mode 100644 vendor/gettext/languages/src/cldr-data/main/en-US/languages.json create mode 100644 vendor/gettext/languages/src/cldr-data/main/en-US/scripts.json create mode 100644 vendor/gettext/languages/src/cldr-data/main/en-US/territories.json create mode 100644 vendor/gettext/languages/src/cldr-data/supplemental/plurals.json diff --git a/composer.json b/composer.json index adc21ee8..121668a2 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "knplabs/github-api": "^3.0", "guzzlehttp/guzzle": "^7.0.1", "http-interop/http-factory-guzzle": "^1.0", - "qferr/mjml-php": "^1.1" + "qferr/mjml-php": "^1.1", + "gettext/gettext": "^5.6" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index 1eef5e7b..c4404f6c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "25846fd23f7252ccce4ba4832e5697ad", + "content-hash": "a9023528de34be7d5f1d8155ae3a2d47", "packages": [ { "name": "clue/stream-filter", @@ -237,6 +237,154 @@ ], "time": "2022-03-13T01:27:51+00:00" }, + { + "name": "gettext/gettext", + "version": "v5.6.1", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Gettext.git", + "reference": "017e249601d32b9a88c2eb4c10eac89bf582a7d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/017e249601d32b9a88c2eb4c10eac89bf582a7d3", + "reference": "017e249601d32b9a88c2eb4c10eac89bf582a7d3", + "shasum": "" + }, + "require": { + "gettext/languages": "^2.3", + "php": "^7.2|^8.0" + }, + "require-dev": { + "brick/varexporter": "^0.3.5", + "friendsofphp/php-cs-fixer": "^3.2", + "oscarotero/php-cs-fixer-config": "^2.0", + "phpunit/phpunit": "^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "description": "PHP gettext manager", + "homepage": "https://github.com/php-gettext/Gettext", + "keywords": [ + "JS", + "gettext", + "i18n", + "mo", + "po", + "translation" + ], + "support": { + "email": "oom@oscarotero.com", + "issues": "https://github.com/php-gettext/Gettext/issues", + "source": "https://github.com/php-gettext/Gettext/tree/v5.6.1" + }, + "funding": [ + { + "url": "https://paypal.me/oscarotero", + "type": "custom" + }, + { + "url": "https://github.com/oscarotero", + "type": "github" + }, + { + "url": "https://www.patreon.com/misteroom", + "type": "patreon" + } + ], + "time": "2021-12-04T11:33:21+00:00" + }, + { + "name": "gettext/languages", + "version": "2.9.0", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Languages.git", + "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Languages/zipball/ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", + "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" + }, + "bin": [ + "bin/export-plural-rules" + ], + "type": "library", + "autoload": { + "psr-4": { + "Gettext\\Languages\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michele Locati", + "email": "mlocati@gmail.com", + "role": "Developer" + } + ], + "description": "gettext languages with plural rules", + "homepage": "https://github.com/php-gettext/Languages", + "keywords": [ + "cldr", + "i18n", + "internationalization", + "l10n", + "language", + "languages", + "localization", + "php", + "plural", + "plural rules", + "plurals", + "translate", + "translations", + "unicode" + ], + "support": { + "issues": "https://github.com/php-gettext/Languages/issues", + "source": "https://github.com/php-gettext/Languages/tree/2.9.0" + }, + "funding": [ + { + "url": "https://paypal.me/mlocati", + "type": "custom" + }, + { + "url": "https://github.com/mlocati", + "type": "github" + } + ], + "time": "2021-11-11T17:30:39+00:00" + }, { "name": "grandel/include-directory", "version": "v0.2.2", diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index 796e0e2b..56db7e7e 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -29,6 +29,8 @@ return array( 'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'), 'Grandel\\' => array($vendorDir . '/grandel/include-directory/src'), 'Github\\' => array($vendorDir . '/knplabs/github-api/lib/Github'), + 'Gettext\\Languages\\' => array($vendorDir . '/gettext/languages/src'), + 'Gettext\\' => array($vendorDir . '/gettext/gettext/src'), 'Embed\\' => array($vendorDir . '/embed/embed/src'), 'Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => array($vendorDir . '/dealerdirect/phpcodesniffer-composer-installer/src'), 'Composer\\CaBundle\\' => array($vendorDir . '/composer/ca-bundle/src'), diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 75609b9b..3a33c8f3 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -77,6 +77,8 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1 'GuzzleHttp\\' => 11, 'Grandel\\' => 8, 'Github\\' => 7, + 'Gettext\\Languages\\' => 18, + 'Gettext\\' => 8, ), 'E' => array ( @@ -188,6 +190,14 @@ class ComposerStaticInit5f3db9fc1d0cf1dd6a77a1d84501b4b1 array ( 0 => __DIR__ . '/..' . '/knplabs/github-api/lib/Github', ), + 'Gettext\\Languages\\' => + array ( + 0 => __DIR__ . '/..' . '/gettext/languages/src', + ), + 'Gettext\\' => + array ( + 0 => __DIR__ . '/..' . '/gettext/gettext/src', + ), 'Embed\\' => array ( 0 => __DIR__ . '/..' . '/embed/embed/src', diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index f81c2589..d6ef0c5e 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -318,6 +318,160 @@ ], "install-path": "../embed/embed" }, + { + "name": "gettext/gettext", + "version": "v5.6.1", + "version_normalized": "5.6.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Gettext.git", + "reference": "017e249601d32b9a88c2eb4c10eac89bf582a7d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Gettext/zipball/017e249601d32b9a88c2eb4c10eac89bf582a7d3", + "reference": "017e249601d32b9a88c2eb4c10eac89bf582a7d3", + "shasum": "" + }, + "require": { + "gettext/languages": "^2.3", + "php": "^7.2|^8.0" + }, + "require-dev": { + "brick/varexporter": "^0.3.5", + "friendsofphp/php-cs-fixer": "^3.2", + "oscarotero/php-cs-fixer-config": "^2.0", + "phpunit/phpunit": "^8.0|^9.0", + "squizlabs/php_codesniffer": "^3.0" + }, + "time": "2021-12-04T11:33:21+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Gettext\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oscar Otero", + "email": "oom@oscarotero.com", + "homepage": "http://oscarotero.com", + "role": "Developer" + } + ], + "description": "PHP gettext manager", + "homepage": "https://github.com/php-gettext/Gettext", + "keywords": [ + "JS", + "gettext", + "i18n", + "mo", + "po", + "translation" + ], + "support": { + "email": "oom@oscarotero.com", + "issues": "https://github.com/php-gettext/Gettext/issues", + "source": "https://github.com/php-gettext/Gettext/tree/v5.6.1" + }, + "funding": [ + { + "url": "https://paypal.me/oscarotero", + "type": "custom" + }, + { + "url": "https://github.com/oscarotero", + "type": "github" + }, + { + "url": "https://www.patreon.com/misteroom", + "type": "patreon" + } + ], + "install-path": "../gettext/gettext" + }, + { + "name": "gettext/languages", + "version": "2.9.0", + "version_normalized": "2.9.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-gettext/Languages.git", + "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-gettext/Languages/zipball/ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", + "reference": "ed56dd2c7f4024cc953ed180d25f02f2640e3ffa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" + }, + "time": "2021-11-11T17:30:39+00:00", + "bin": [ + "bin/export-plural-rules" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Gettext\\Languages\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michele Locati", + "email": "mlocati@gmail.com", + "role": "Developer" + } + ], + "description": "gettext languages with plural rules", + "homepage": "https://github.com/php-gettext/Languages", + "keywords": [ + "cldr", + "i18n", + "internationalization", + "l10n", + "language", + "languages", + "localization", + "php", + "plural", + "plural rules", + "plurals", + "translate", + "translations", + "unicode" + ], + "support": { + "issues": "https://github.com/php-gettext/Languages/issues", + "source": "https://github.com/php-gettext/Languages/tree/2.9.0" + }, + "funding": [ + { + "url": "https://paypal.me/mlocati", + "type": "custom" + }, + { + "url": "https://github.com/mlocati", + "type": "github" + } + ], + "install-path": "../gettext/languages" + }, { "name": "grandel/include-directory", "version": "v0.2.2", diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index 18510fde..d82bf928 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -5,7 +5,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '8308faa51dda410511c214f509c47eef2cee61c1', + 'reference' => 'a739fb797078662b318e799a9545755c6a03ce82', 'name' => '__root__', 'dev' => true, ), @@ -16,7 +16,7 @@ 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), - 'reference' => '8308faa51dda410511c214f509c47eef2cee61c1', + 'reference' => 'a739fb797078662b318e799a9545755c6a03ce82', 'dev_requirement' => false, ), 'clue/stream-filter' => array( @@ -55,6 +55,24 @@ 'reference' => '2ac32581a8617c3bbe593e3d7799ca9db6974471', 'dev_requirement' => false, ), + 'gettext/gettext' => array( + 'pretty_version' => 'v5.6.1', + 'version' => '5.6.1.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../gettext/gettext', + 'aliases' => array(), + 'reference' => '017e249601d32b9a88c2eb4c10eac89bf582a7d3', + 'dev_requirement' => false, + ), + 'gettext/languages' => array( + 'pretty_version' => '2.9.0', + 'version' => '2.9.0.0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../gettext/languages', + 'aliases' => array(), + 'reference' => 'ed56dd2c7f4024cc953ed180d25f02f2640e3ffa', + 'dev_requirement' => false, + ), 'grandel/include-directory' => array( 'pretty_version' => 'v0.2.2', 'version' => '0.2.2.0', diff --git a/vendor/gettext/gettext/CHANGELOG.md b/vendor/gettext/gettext/CHANGELOG.md new file mode 100644 index 00000000..10b3aa25 --- /dev/null +++ b/vendor/gettext/gettext/CHANGELOG.md @@ -0,0 +1,129 @@ +# 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) + +## [5.6.1] - 2021-12-04 +### 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. + +### Fixed +- Subsequent load file fails [#257] [#276] +- Upgraded some dependencies in `dev`. + +## [5.5.4] - 2020-12-20 +### Fixed +- TypeError in which numeric entries were converted to integers [#265] + +## [5.5.3] - 2020-12-01 +### Fixed +- Add PHP 8 to composer.json + +## [5.5.2] - 2020-11-17 +### Fixed +- Parse of multiline disabled translations [#262] [#263] + +## [5.5.1] - 2020-06-08 +### Fixed +- Type error in which numeric filenames were converted to integers [#260] + +## [5.5.0] - 2020-05-23 +### Added +- New option `addReferences()` to configure the code scanners whether add or not references [#258] + +### 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 + +### 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. + +[#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 + +[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 diff --git a/vendor/gettext/gettext/CONTRIBUTING.md b/vendor/gettext/gettext/CONTRIBUTING.md new file mode 100644 index 00000000..0c1b5433 --- /dev/null +++ b/vendor/gettext/gettext/CONTRIBUTING.md @@ -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 diff --git a/vendor/gettext/gettext/LICENSE b/vendor/gettext/gettext/LICENSE new file mode 100644 index 00000000..106de2aa --- /dev/null +++ b/vendor/gettext/gettext/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 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. diff --git a/vendor/gettext/gettext/README.md b/vendor/gettext/gettext/README.md new file mode 100644 index 00000000..0d181c9a --- /dev/null +++ b/vendor/gettext/gettext/README.md @@ -0,0 +1,260 @@ +# Gettext + +[![Latest Version on Packagist][ico-version]][link-packagist] +[![Software License][ico-license]](LICENSE) +![ico-ga] +[![Total Downloads][ico-downloads]][link-downloads] + +> 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 + +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. + +## Installation + +``` +composer require gettext/gettext +``` + +## 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, ...) + +## Usage example + +```php +use Gettext\Loader\PoLoader; +use Gettext\Generator\MoGenerator; + +//import from a .po file: +$loader = new PoLoader(); +$translations = $loader->loadFile('locales/gl.po'); + +//edit some translations: +$translation = $translations->find(null, 'apple'); + +if ($translation) { + $translation->translate('Mazá'); +} + +//export to a .mo file: +$generator = new MoGenerator(); +$generator->generateFile($translations, 'Locale/gl/LC_MESSAGES/messages.mo'); +``` + +## 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; + +$translation = Translation::create('comments', 'One comment', '%s comments'); + +$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'); + +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 +use Gettext\Translations; + +$translations = Translations::create('my-domain'); + +//You can add new translations: +$translation = Translation::create('comments', 'One comment', '%s comments'); +$translations->add($translation); + +//Find a specific translation +$translation = $translations->find('comments', 'One comment'); + +//Edit headers, domain, etc +$translations->getHeaders()->set('Last-Translator', 'Oscar Otero'); +$translations->setDomain('my-blog'); +``` + +## Loaders + +The loaders allows to get gettext values from any format. For example, to load a .po file: + +```php +use Gettext\Loader\PoLoader; + +$loader = new PoLoader(); + +//From a file +$translations = $loader->loadFile('locales/en.po'); + +//From a string +$string = file_get_contents('locales2/en.po'); +$translations = $loader->loadString($string); +``` + +This package includes the following loaders: + +- `MoLoader` +- `PoLoader` + +And you can install other formats with loaders and generators: + +- [Json](https://github.com/php-gettext/Json) + +## Generators + +The generators export a `Gettext\Translations` instance to any format (po, mo, etc). + +```php +use Gettext\Loader\PoLoader; +use Gettext\Generator\MoGenerator; + +//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); +``` + +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: + +```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(); +``` + +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: + +```php +$translations3 = $translations1->mergeWith($translations2); +``` + +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: + +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::HEADERS_OVERRIDE` | Overrides the headers with the values of `$translations2` +`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` + +Use the second argument to configure the merging strategy: + +```php +$strategy = Merge::TRANSLATIONS_OURS | Merge::HEADERS_OURS; + +$translations3 = $translations1->mergeWith($translations2, $strategy); +``` + +There are some typical scenarios, one of the most common: + +- 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 + +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). + +```php +$newEntries = $scanner->scanFile('template.php'); +$previousEntries = $loader->loadFile('translations.po'); + +$updatedEntries = $newEntries->mergeWith($previousEntries); +``` + +More common scenarios may be added in a future. + +## Contributors + +Thanks to all [contributors](https://github.com/oscarotero/Gettext/graphs/contributors) specially to [@mlocati](https://github.com/mlocati). + +--- + +Please see [CHANGELOG](CHANGELOG.md) for more information about recent changes and [CONTRIBUTING](CONTRIBUTING.md) for contributing details. + +The MIT License (MIT). Please see [LICENSE](LICENSE) for more information. + +[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 diff --git a/vendor/gettext/gettext/composer.json b/vendor/gettext/gettext/composer.json new file mode 100644 index 00000000..16bdde90 --- /dev/null +++ b/vendor/gettext/gettext/composer.json @@ -0,0 +1,48 @@ +{ + "name": "gettext/gettext", + "type": "library", + "description": "PHP gettext manager", + "keywords": ["js", "gettext", "i18n", "translation", "po", "mo"], + "homepage": "https://github.com/php-gettext/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/php-gettext/Gettext/issues" + }, + "require": { + "php": "^7.2|^8.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" + }, + "autoload": { + "psr-4": { + "Gettext\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Gettext\\Tests\\": "tests" + } + }, + "scripts": { + "test": [ + "phpunit", + "phpcs" + ], + "cs-fix": "php-cs-fixer fix" + } +} diff --git a/vendor/gettext/gettext/src/Comments.php b/vendor/gettext/gettext/src/Comments.php new file mode 100644 index 00000000..cd706b93 --- /dev/null +++ b/vendor/gettext/gettext/src/Comments.php @@ -0,0 +1,89 @@ +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/vendor/gettext/gettext/src/Flags.php b/vendor/gettext/gettext/src/Flags.php new file mode 100644 index 00000000..49add0be --- /dev/null +++ b/vendor/gettext/gettext/src/Flags.php @@ -0,0 +1,96 @@ +add(...$flags); + } + } + + public function __debugInfo() + { + return $this->toArray(); + } + + public function add(string ...$flags): self + { + foreach ($flags as $flag) { + if (!$this->has($flag)) { + $this->flags[] = $flag; + } + } + + sort($this->flags); + + return $this; + } + + public function delete(string ...$flags): self + { + foreach ($flags as $flag) { + $key = array_search($flag, $this->flags); + + if (is_int($key)) { + array_splice($this->flags, $key, 1); + } + } + + return $this; + } + + public function has(string $flag): bool + { + return in_array($flag, $this->flags, true); + } + + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->flags); + } + + public function count(): int + { + return count($this->flags); + } + + public function toArray(): array + { + return $this->flags; + } + + public function mergeWith(Flags $flags): Flags + { + $merged = clone $this; + $merged->add(...$flags->flags); + + return $merged; + } +} diff --git a/vendor/gettext/gettext/src/Generator/Generator.php b/vendor/gettext/gettext/src/Generator/Generator.php new file mode 100644 index 00000000..6caa8002 --- /dev/null +++ b/vendor/gettext/gettext/src/Generator/Generator.php @@ -0,0 +1,18 @@ +generateString($translations); + + return file_put_contents($filename, $content) !== false; + } + + abstract public function generateString(Translations $translations): string; +} diff --git a/vendor/gettext/gettext/src/Generator/GeneratorInterface.php b/vendor/gettext/gettext/src/Generator/GeneratorInterface.php new file mode 100644 index 00000000..1d2821da --- /dev/null +++ b/vendor/gettext/gettext/src/Generator/GeneratorInterface.php @@ -0,0 +1,13 @@ +includeHeaders = $includeHeaders; + + return $this; + } + + public function generateString(Translations $translations): string + { + $messages = []; + + if ($this->includeHeaders) { + $lines = []; + + foreach ($translations->getHeaders() as $name => $value) { + $lines[] = sprintf('%s: %s', $name, $value); + } + + $messages[''] = implode("\n", $lines); + } + + foreach ($translations as $translation) { + if (!$translation->getTranslation() || $translation->isDisabled()) { + continue; + } + + if ($context = $translation->getContext()) { + $originalString = "{$context}\x04{$translation->getOriginal()}"; + } else { + $originalString = $translation->getOriginal(); + } + + $messages[$originalString] = $translation; + } + + ksort($messages); + $numEntries = count($messages); + $originalsTable = ''; + $translationsTable = ''; + $originalsIndex = []; + $translationsIndex = []; + $pluralForm = $translations->getHeaders()->getPluralForm(); + $pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null; + + foreach ($messages as $originalString => $translation) { + if (is_string($translation)) { + $translationString = $translation; + } elseif (self::hasPluralTranslations($translation)) { + $originalString .= "\x00{$translation->getPlural()}"; + $translationString = "{$translation->getTranslation()}\x00" + .implode("\x00", $translation->getPluralTranslations($pluralSize)); + } else { + $translationString = $translation->getTranslation(); + } + + $originalsIndex[] = [ + 'relativeOffset' => strlen($originalsTable), + 'length' => strlen((string) $originalString), + ]; + $originalsTable .= $originalString."\x00"; + $translationsIndex[] = [ + 'relativeOffset' => strlen($translationsTable), + 'length' => strlen($translationString), + ]; + $translationsTable .= $translationString."\x00"; + } + + // Offset of table with the original strings index: right after the header (which is 7 words) + $originalsIndexOffset = 7 * 4; + + // Size of table with the original strings index + $originalsIndexSize = $numEntries * (4 + 4); + + // Offset of table with the translation strings index: right after the original strings index table + $translationsIndexOffset = $originalsIndexOffset + $originalsIndexSize; + + // Size of table with the translation strings index + $translationsIndexSize = $numEntries * (4 + 4); + + // Hashing table starts after the header and after the index table + $originalsStringsOffset = $translationsIndexOffset + $translationsIndexSize; + + // Translations start after the keys + $translationsStringsOffset = $originalsStringsOffset + strlen($originalsTable); + + // Let's generate the .mo file binary data + $mo = ''; + + // Magic number + $mo .= pack('L', 0x950412de); + + // File format revision + $mo .= pack('L', 0); + + // Number of strings + $mo .= pack('L', $numEntries); + + // Offset of table with original strings + $mo .= pack('L', $originalsIndexOffset); + + // Offset of table with translation strings + $mo .= pack('L', $translationsIndexOffset); + + // Size of hashing table: we don't use it. + $mo .= pack('L', 0); + + // Offset of hashing table: it would start right after the translations index table + $mo .= pack('L', $translationsIndexOffset + $translationsIndexSize); + + // Write the lengths & offsets of the original strings + foreach ($originalsIndex as $info) { + $mo .= pack('L', $info['length']); + $mo .= pack('L', $originalsStringsOffset + $info['relativeOffset']); + } + + // Write the lengths & offsets of the translated strings + foreach ($translationsIndex as $info) { + $mo .= pack('L', $info['length']); + $mo .= pack('L', $translationsStringsOffset + $info['relativeOffset']); + } + + // Write original strings + $mo .= $originalsTable; + + // Write translation strings + $mo .= $translationsTable; + + return $mo; + } + + private static function hasPluralTranslations(Translation $translation): bool + { + if (!$translation->getPlural()) { + return false; + } + + return implode('', $translation->getPluralTranslations()) !== ''; + } +} diff --git a/vendor/gettext/gettext/src/Generator/PoGenerator.php b/vendor/gettext/gettext/src/Generator/PoGenerator.php new file mode 100644 index 00000000..0dc8de8f --- /dev/null +++ b/vendor/gettext/gettext/src/Generator/PoGenerator.php @@ -0,0 +1,134 @@ +getHeaders()->getPluralForm(); + $pluralSize = is_array($pluralForm) ? ($pluralForm[0] - 1) : null; + $lines = []; + + //Description and flags + if ($translations->getDescription()) { + $description = explode("\n", $translations->getDescription()); + + foreach ($description as $line) { + $lines[] = sprintf('# %s', $line); + } + + $lines[] = '#'; + } + + if (count($translations->getFlags())) { + $lines[] = sprintf('#, %s', implode(',', $translations->getFlags()->toArray())); + } + + //Headers + $lines[] = 'msgid ""'; + $lines[] = 'msgstr ""'; + + foreach ($translations->getHeaders() as $name => $value) { + $lines[] = sprintf('"%s: %s\\n"', $name, $value); + } + + $lines[] = ''; + + //Translations + foreach ($translations as $translation) { + foreach ($translation->getComments() as $comment) { + $lines[] = sprintf('# %s', $comment); + } + + foreach ($translation->getExtractedComments() as $comment) { + $lines[] = sprintf('#. %s', $comment); + } + + foreach ($translation->getReferences() as $filename => $lineNumbers) { + if (empty($lineNumbers)) { + $lines[] = sprintf('#: %s', $filename); + continue; + } + + foreach ($lineNumbers as $number) { + $lines[] = sprintf('#: %s:%d', $filename, $number); + } + } + + if (count($translation->getFlags())) { + $lines[] = sprintf('#, %s', implode(',', $translation->getFlags()->toArray())); + } + + $prefix = $translation->isDisabled() ? '#~ ' : ''; + + if ($context = $translation->getContext()) { + $lines[] = sprintf('%smsgctxt %s', $prefix, self::encode($context)); + } + + self::appendLines($lines, $prefix, 'msgid', $translation->getOriginal()); + + if ($plural = $translation->getPlural()) { + self::appendLines($lines, $prefix, 'msgid_plural', $plural); + self::appendLines($lines, $prefix, 'msgstr[0]', $translation->getTranslation() ?: ''); + + foreach ($translation->getPluralTranslations($pluralSize) as $k => $v) { + self::appendLines($lines, $prefix, sprintf('msgstr[%d]', $k + 1), $v); + } + } else { + self::appendLines($lines, $prefix, 'msgstr', $translation->getTranslation() ?: ''); + } + + $lines[] = ''; + } + + return implode("\n", $lines); + } + + /** + * Add one or more lines depending whether the string is multiline or not. + */ + private static function appendLines(array &$lines, string $prefix, string $name, string $value): void + { + $newLines = explode("\n", $value); + $total = count($newLines); + + if ($total === 1) { + $lines[] = sprintf('%s%s %s', $prefix, $name, self::encode($newLines[0])); + + return; + } + + $lines[] = sprintf('%s%s ""', $prefix, $name); + + $last = $total - 1; + foreach ($newLines as $k => $line) { + if ($k < $last) { + $line .= "\n"; + } + + $lines[] = self::encode($line); + } + } + + /** + * Convert a string to its PO representation. + */ + public static function encode(string $value): string + { + return '"'.strtr( + $value, + [ + "\x00" => '', + '\\' => '\\\\', + "\t" => '\t', + "\r" => '\r', + "\n" => '\n', + '"' => '\\"', + ] + ).'"'; + } +} diff --git a/vendor/gettext/gettext/src/Headers.php b/vendor/gettext/gettext/src/Headers.php new file mode 100644 index 00000000..87c782e3 --- /dev/null +++ b/vendor/gettext/gettext/src/Headers.php @@ -0,0 +1,144 @@ +headers = $headers; + ksort($this->headers); + } + + public function __debugInfo() + { + return $this->toArray(); + } + + public function set(string $name, string $value): self + { + $this->headers[$name] = trim($value); + ksort($this->headers); + + return $this; + } + + public function get(string $name): ?string + { + return $this->headers[$name] ?? null; + } + + public function delete(string $name): self + { + unset($this->headers[$name]); + + return $this; + } + + public function clear(): self + { + $this->headers = []; + + return $this; + } + + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->toArray()); + } + + public function count(): int + { + return count($this->headers); + } + + public function setLanguage(string $language): self + { + return $this->set(self::HEADER_LANGUAGE, $language); + } + + public function getLanguage(): ?string + { + return $this->get(self::HEADER_LANGUAGE); + } + + public function setDomain(string $domain): self + { + return $this->set(self::HEADER_DOMAIN, $domain); + } + + public function getDomain(): ?string + { + return $this->get(self::HEADER_DOMAIN); + } + + public function setPluralForm(int $count, string $rule): self + { + if (preg_match('/[a-z]/i', str_replace('n', '', $rule))) { + throw new InvalidArgumentException(sprintf('Invalid Plural form: "%s"', $rule)); + } + + return $this->set(self::HEADER_PLURAL, sprintf('nplurals=%d; plural=%s;', $count, $rule)); + } + + /** + * Returns the parsed plural definition. + * + * @return array|null [count, rule] + */ + public function getPluralForm(): ?array + { + $header = $this->get(self::HEADER_PLURAL); + + if (!empty($header) && + preg_match('/^nplurals\s*=\s*(\d+)\s*;\s*plural\s*=\s*([^;]+)\s*;$/', $header, $matches) + ) { + return [intval($matches[1]), $matches[2]]; + } + + return null; + } + + public function toArray(): array + { + return $this->headers; + } + + public function mergeWith(Headers $headers): Headers + { + $merged = clone $this; + $merged->headers = $headers->headers + $merged->headers; + ksort($merged->headers); + + return $merged; + } +} diff --git a/vendor/gettext/gettext/src/Loader/Loader.php b/vendor/gettext/gettext/src/Loader/Loader.php new file mode 100644 index 00000000..4ff0a59b --- /dev/null +++ b/vendor/gettext/gettext/src/Loader/Loader.php @@ -0,0 +1,56 @@ +loadString($string, $translations); + } + + public function loadString(string $string, Translations $translations = null): Translations + { + return $translations ?: $this->createTranslations(); + } + + protected function createTranslations(): Translations + { + return Translations::create(); + } + + protected function createTranslation(?string $context, string $original, string $plural = null): ?Translation + { + $translation = Translation::create($context, $original); + + if (isset($plural)) { + $translation->setPlural($plural); + } + + return $translation; + } + + /** + * Reads and returns the content of a file. + */ + protected static function readFile(string $file): string + { + $content = @file_get_contents($file); + + if (false === $content) { + throw new Exception("Cannot read the file '$file', probably permissions"); + } + + return $content; + } +} diff --git a/vendor/gettext/gettext/src/Loader/LoaderInterface.php b/vendor/gettext/gettext/src/Loader/LoaderInterface.php new file mode 100644 index 00000000..4f330d3b --- /dev/null +++ b/vendor/gettext/gettext/src/Loader/LoaderInterface.php @@ -0,0 +1,13 @@ +init($string); + + $magic = $this->readInt('V'); + + if (($magic === self::MAGIC1) || ($magic === self::MAGIC3)) { //to make sure it works for 64-bit platforms + $byteOrder = 'V'; //low endian + } elseif ($magic === (self::MAGIC2 & 0xFFFFFFFF)) { + $byteOrder = 'N'; //big endian + } else { + throw new Exception('Not MO file'); + } + + $this->readInt($byteOrder); + + $total = $this->readInt($byteOrder); //total string count + $originals = $this->readInt($byteOrder); //offset of original table + $tran = $this->readInt($byteOrder); //offset of translation table + + $this->seekto($originals); + $table_originals = $this->readIntArray($byteOrder, $total * 2); + + $this->seekto($tran); + $table_translations = $this->readIntArray($byteOrder, $total * 2); + + for ($i = 0; $i < $total; ++$i) { + $next = $i * 2; + + $this->seekto($table_originals[$next + 2]); + $original = $this->read($table_originals[$next + 1]); + + $this->seekto($table_translations[$next + 2]); + $translated = $this->read($table_translations[$next + 1]); + + // Headers + if ($original === '') { + foreach (explode("\n", $translated) as $headerLine) { + if ($headerLine === '') { + continue; + } + + $headerChunks = preg_split('/:\s*/', $headerLine, 2); + $translations->getHeaders()->set($headerChunks[0], isset($headerChunks[1]) ? $headerChunks[1] : ''); + } + + continue; + } + + $context = $plural = null; + $chunks = explode("\x04", $original, 2); + + if (isset($chunks[1])) { + list($context, $original) = $chunks; + } + + $chunks = explode("\x00", $original, 2); + + if (isset($chunks[1])) { + list($original, $plural) = $chunks; + } + + $translation = $this->createTranslation($context, $original, $plural); + $translations->add($translation); + + if ($translated === '') { + continue; + } + + if ($plural === null) { + $translation->translate($translated); + continue; + } + + $v = explode("\x00", $translated); + $translation->translate(array_shift($v)); + $translation->translatePlural(...array_filter($v)); + } + + return $translations; + } + + private function init(string $string): void + { + $this->string = $string; + $this->position = 0; + $this->length = strlen($string); + } + + private function read(int $bytes): string + { + $data = substr($this->string, $this->position, $bytes); + + $this->seekTo($this->position + $bytes); + + return $data; + } + + private function seekTo(int $position): void + { + $this->position = ($this->length < $position) ? $this->length : $position; + } + + private function readInt(string $byteOrder): int + { + if (($read = $this->read(4)) === false) { + return 0; + } + + $read = (array) unpack($byteOrder, $read); + + return (int) array_shift($read); + } + + private function readIntArray(string $byteOrder, int $count): array + { + return unpack($byteOrder.$count, $this->read(4 * $count)) ?: []; + } +} diff --git a/vendor/gettext/gettext/src/Loader/PoLoader.php b/vendor/gettext/gettext/src/Loader/PoLoader.php new file mode 100644 index 00000000..1d98c56e --- /dev/null +++ b/vendor/gettext/gettext/src/Loader/PoLoader.php @@ -0,0 +1,226 @@ +createTranslation(null, ''); + + while ($line !== false) { + $line = trim($line); + $nextLine = next($lines); + + //Multiline + while (substr($line, -1, 1) === '"' + && $nextLine !== false + && (substr(trim($nextLine), 0, 1) === '"' || substr(trim($nextLine), 0, 4) === '#~ "') + ) { + if (substr(trim($nextLine), 0, 1) === '"') { // Normal multiline + $line = substr($line, 0, -1).substr(trim($nextLine), 1); + } elseif (substr(trim($nextLine), 0, 4) === '#~ "') { // Disabled multiline + $line = substr($line, 0, -1).substr(trim($nextLine), 4); + } + $nextLine = next($lines); + } + + //End of translation + if ($line === '') { + if (!self::isEmpty($translation)) { + $translations->add($translation); + } + + $translation = $this->createTranslation(null, ''); + $line = $nextLine; + continue; + } + + $splitLine = preg_split('/\s+/', $line, 2); + $key = $splitLine[0]; + $data = $splitLine[1] ?? ''; + + if ($key === '#~') { + $translation->disable(); + + $splitLine = preg_split('/\s+/', $data, 2); + $key = $splitLine[0]; + $data = $splitLine[1] ?? ''; + } + + if ($data === '') { + $line = $nextLine; + continue; + } + + switch ($key) { + case '#': + $translation->getComments()->add($data); + break; + case '#.': + $translation->getExtractedComments()->add($data); + break; + case '#,': + foreach (array_map('trim', explode(',', trim($data))) as $value) { + $translation->getFlags()->add($value); + } + break; + case '#:': + foreach (preg_split('/\s+/', trim($data)) as $value) { + if (preg_match('/^(.+)(:(\d*))?$/U', $value, $matches)) { + $line = isset($matches[3]) ? intval($matches[3]) : null; + $translation->getReferences()->add($matches[1], $line); + } + } + break; + case 'msgctxt': + $translation = $translation->withContext(self::decode($data)); + break; + case 'msgid': + $translation = $translation->withOriginal(self::decode($data)); + break; + case 'msgid_plural': + $translation->setPlural(self::decode($data)); + break; + case 'msgstr': + case 'msgstr[0]': + $translation->translate(self::decode($data)); + break; + case 'msgstr[1]': + $translation->translatePlural(self::decode($data)); + break; + default: + if (strpos($key, 'msgstr[') === 0) { + $p = $translation->getPluralTranslations(); + $p[] = self::decode($data); + + $translation->translatePlural(...$p); + break; + } + break; + } + + $line = $nextLine; + } + + if (!self::isEmpty($translation)) { + $translations->add($translation); + } + + //Headers + $translation = $translations->find(null, ''); + + if (!$translation) { + return $translations; + } + + $translations->remove($translation); + + $description = $translation->getComments()->toArray(); + + if (!empty($description)) { + $translations->setDescription(implode("\n", $description)); + } + + $flags = $translation->getFlags()->toArray(); + + if (!empty($flags)) { + $translations->getFlags()->add(...$flags); + } + + $headers = $translations->getHeaders(); + + foreach (self::parseHeaders($translation->getTranslation()) as $name => $value) { + $headers->set($name, $value); + } + + return $translations; + } + + private static function parseHeaders(?string $string): array + { + if (empty($string)) { + return []; + } + + $headers = []; + $lines = explode("\n", $string); + $name = null; + + foreach ($lines as $line) { + $line = self::decode($line); + + if ($line === '') { + continue; + } + + // Checks if it is a header definition line. + // Useful for distinguishing between header definitions and possible continuations of a header entry. + if (preg_match('/^[\w-]+:/', $line)) { + $pieces = array_map('trim', explode(':', $line, 2)); + list($name, $value) = $pieces; + + $headers[$name] = $value; + continue; + } + + $value = $headers[$name] ?? ''; + $headers[$name] = $value.$line; + } + + return $headers; + } + + /** + * Convert a string from its PO representation. + */ + public static function decode(string $value): string + { + 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", + '\\"' => '"', + ] + ); + } + + private static function isEmpty(Translation $translation): bool + { + if (!empty($translation->getOriginal())) { + return false; + } + + if (!empty($translation->getTranslation())) { + return false; + } + + return true; + } +} diff --git a/vendor/gettext/gettext/src/Merge.php b/vendor/gettext/gettext/src/Merge.php new file mode 100644 index 00000000..29d4035e --- /dev/null +++ b/vendor/gettext/gettext/src/Merge.php @@ -0,0 +1,40 @@ +references = $state['references']; + + return $references; + } + + public function __debugInfo() + { + return $this->toArray(); + } + + public function add(string $filename, int $line = null): self + { + $fileReferences = $this->references[$filename] ?? []; + + if (isset($line) && !in_array($line, $fileReferences)) { + $fileReferences[] = $line; + } + + $this->references[$filename] = $fileReferences; + + return $this; + } + + #[ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->references); + } + + public function count(): int + { + return array_reduce($this->references, function ($carry, $item) { + return $carry + (count($item) ?: 1); + }, 0); + } + + public function toArray(): array + { + return $this->references; + } + + public function mergeWith(References $references): References + { + $merged = clone $this; + + foreach ($references as $filename => $lines) { + //Set filename always to string + $filename = (string) $filename; + + if (empty($lines)) { + $merged->add($filename); + continue; + } + + foreach ($lines as $line) { + $merged->add($filename, $line); + } + } + + return $merged; + } +} diff --git a/vendor/gettext/gettext/src/Scanner/CodeScanner.php b/vendor/gettext/gettext/src/Scanner/CodeScanner.php new file mode 100644 index 00000000..e854dd7e --- /dev/null +++ b/vendor/gettext/gettext/src/Scanner/CodeScanner.php @@ -0,0 +1,171 @@ + handler] + */ + public function setFunctions(array $functions): self + { + $this->functions = $functions; + + return $this; + } + + /** + * @return array [fnName => handler] + */ + public function getFunctions(): array + { + return $this->functions; + } + + public function ignoreInvalidFunctions($ignore = true): self + { + $this->ignoreInvalidFunctions = $ignore; + + return $this; + } + + public function addReferences($enabled = true): self + { + $this->addReferences = $enabled; + + return $this; + } + + public function extractCommentsStartingWith(string ...$prefixes): self + { + $this->commentsPrefixes = $prefixes; + + return $this; + } + + public function scanString(string $string, string $filename): void + { + $functionsScanner = $this->getFunctionsScanner(); + $functions = $functionsScanner->scan($string, $filename); + + foreach ($functions as $function) { + $this->handleFunction($function); + } + } + + abstract public function getFunctionsScanner(): FunctionsScannerInterface; + + protected function handleFunction(ParsedFunction $function) + { + $handler = $this->getFunctionHandler($function); + + if (is_null($handler)) { + return; + } + + $translation = call_user_func($handler, $function); + + if ($translation && $this->addReferences) { + $translation->getReferences()->add($function->getFilename(), $function->getLine()); + } + } + + protected function getFunctionHandler(ParsedFunction $function): ?callable + { + $name = $function->getName(); + $handler = $this->functions[$name] ?? null; + + return is_null($handler) ? null : [$this, $handler]; + } + + protected function addComments(ParsedFunction $function, ?Translation $translation): ?Translation + { + if (empty($this->commentsPrefixes) || empty($translation)) { + return $translation; + } + + foreach ($function->getComments() as $comment) { + if ($this->checkComment($comment)) { + $translation->getExtractedComments()->add($comment); + } + } + + return $translation; + } + + protected function addFlags(ParsedFunction $function, ?Translation $translation): ?Translation + { + if (empty($translation)) { + return $translation; + } + + foreach ($function->getFlags() as $flag) { + $translation->getFlags()->add($flag); + } + + return $translation; + } + + protected function checkFunction(ParsedFunction $function, int $minLength): bool + { + if ($function->countArguments() < $minLength) { + if ($this->ignoreInvalidFunctions) { + return false; + } + + throw new Exception( + sprintf( + 'Invalid gettext function in %s:%d. At least %d arguments are required', + $function->getFilename(), + $function->getLine(), + $minLength + ) + ); + } + + $arguments = array_slice($function->getArguments(), 0, $minLength); + + if (in_array(null, $arguments, true)) { + if ($this->ignoreInvalidFunctions) { + return false; + } + + throw new Exception( + sprintf( + 'Invalid gettext function in %s:%d. Some required arguments are not valid', + $function->getFilename(), + $function->getLine() + ) + ); + } + + return true; + } + + protected function checkComment(string $comment): bool + { + foreach ($this->commentsPrefixes as $prefix) { + if ($prefix === '' || strpos($comment, $prefix) === 0) { + return true; + } + } + + return false; + } +} diff --git a/vendor/gettext/gettext/src/Scanner/FunctionsHandlersTrait.php b/vendor/gettext/gettext/src/Scanner/FunctionsHandlersTrait.php new file mode 100644 index 00000000..a31da441 --- /dev/null +++ b/vendor/gettext/gettext/src/Scanner/FunctionsHandlersTrait.php @@ -0,0 +1,145 @@ +checkFunction($function, 1)) { + return null; + } + list($original) = $function->getArguments(); + + $translation = $this->addComments( + $function, + $this->saveTranslation(null, null, $original) + ); + + return $this->addFlags($function, $translation); + } + + protected function ngettext(ParsedFunction $function): ?Translation + { + if (!$this->checkFunction($function, 2)) { + return null; + } + list($original, $plural) = $function->getArguments(); + + $translation = $this->addComments( + $function, + $this->saveTranslation(null, null, $original, $plural) + ); + + return $this->addFlags($function, $translation); + } + + protected function pgettext(ParsedFunction $function): ?Translation + { + if (!$this->checkFunction($function, 2)) { + return null; + } + list($context, $original) = $function->getArguments(); + + $translation = $this->addComments( + $function, + $this->saveTranslation(null, $context, $original) + ); + + return $this->addFlags($function, $translation); + } + + protected function dgettext(ParsedFunction $function): ?Translation + { + if (!$this->checkFunction($function, 2)) { + return null; + } + list($domain, $original) = $function->getArguments(); + + $translation = $this->addComments( + $function, + $this->saveTranslation($domain, null, $original) + ); + + return $this->addFlags($function, $translation); + } + + protected function dpgettext(ParsedFunction $function): ?Translation + { + if (!$this->checkFunction($function, 3)) { + return null; + } + list($domain, $context, $original) = $function->getArguments(); + + $translation = $this->addComments( + $function, + $this->saveTranslation($domain, $context, $original) + ); + + return $this->addFlags($function, $translation); + } + + protected function npgettext(ParsedFunction $function): ?Translation + { + if (!$this->checkFunction($function, 3)) { + return null; + } + list($context, $original, $plural) = $function->getArguments(); + + $translation = $this->addComments( + $function, + $this->saveTranslation(null, $context, $original, $plural) + ); + + return $this->addFlags($function, $translation); + } + + protected function dngettext(ParsedFunction $function): ?Translation + { + if (!$this->checkFunction($function, 3)) { + return null; + } + list($domain, $original, $plural) = $function->getArguments(); + + $translation = $this->addComments( + $function, + $this->saveTranslation($domain, null, $original, $plural) + ); + + return $this->addFlags($function, $translation); + } + + protected function dnpgettext(ParsedFunction $function): ?Translation + { + if (!$this->checkFunction($function, 4)) { + return null; + } + list($domain, $context, $original, $plural) = $function->getArguments(); + + $translation = $this->addComments( + $function, + $this->saveTranslation($domain, $context, $original, $plural) + ); + + return $this->addFlags($function, $translation); + } + + abstract protected function addComments(ParsedFunction $function, ?Translation $translation): ?Translation; + + abstract protected function addFlags(ParsedFunction $function, ?Translation $translation): ?Translation; + + abstract protected function checkFunction(ParsedFunction $function, int $minLength): bool; + + abstract protected function saveTranslation( + ?string $domain, + ?string $context, + string $original, + string $plural = null + ): ?Translation; +} diff --git a/vendor/gettext/gettext/src/Scanner/FunctionsScannerInterface.php b/vendor/gettext/gettext/src/Scanner/FunctionsScannerInterface.php new file mode 100644 index 00000000..0386b722 --- /dev/null +++ b/vendor/gettext/gettext/src/Scanner/FunctionsScannerInterface.php @@ -0,0 +1,12 @@ +name = $name; + $this->filename = $filename; + $this->line = $line; + $this->lastLine = isset($lastLine) ? $lastLine : $line; + } + + public function __debugInfo() + { + return $this->toArray(); + } + + public function toArray(): array + { + return [ + 'name' => $this->name, + 'filename' => $this->filename, + 'line' => $this->line, + 'lastLine' => $this->lastLine, + 'arguments' => $this->arguments, + 'comments' => $this->comments, + 'flags' => $this->flags, + ]; + } + + public function getName(): string + { + return $this->name; + } + + public function getLine(): int + { + return $this->line; + } + + public function getLastLine(): int + { + return $this->lastLine; + } + + public function getFilename(): string + { + return $this->filename; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function countArguments(): int + { + return count($this->arguments); + } + + public function getComments(): array + { + return $this->comments; + } + + public function getFlags(): array + { + return $this->flags; + } + + public function addArgument($argument = null): self + { + $this->arguments[] = $argument; + + return $this; + } + + public function addComment(string $comment): self + { + $this->comments[] = $comment; + + return $this; + } + + public function addFlag(string $flag): self + { + $this->flags[] = $flag; + + return $this; + } +} diff --git a/vendor/gettext/gettext/src/Scanner/Scanner.php b/vendor/gettext/gettext/src/Scanner/Scanner.php new file mode 100644 index 00000000..b1440e5f --- /dev/null +++ b/vendor/gettext/gettext/src/Scanner/Scanner.php @@ -0,0 +1,88 @@ +getDomain(); + $this->translations[$domain] = $translations; + } + } + + public function setDefaultDomain(string $defaultDomain): void + { + $this->defaultDomain = $defaultDomain; + } + + public function getDefaultDomain(): string + { + return $this->defaultDomain; + } + + public function getTranslations(): array + { + return $this->translations; + } + + public function scanFile(string $filename): void + { + $string = static::readFile($filename); + + $this->scanString($string, $filename); + } + + abstract public function scanString(string $string, string $filename): void; + + protected function saveTranslation( + ?string $domain, + ?string $context, + string $original, + string $plural = null + ): ?Translation { + if (is_null($domain)) { + $domain = $this->defaultDomain; + } + + if (!isset($this->translations[$domain])) { + return null; + } + + $translation = $this->translations[$domain]->addOrMerge( + Translation::create($context, $original) + ); + + if (isset($plural)) { + $translation->setPlural($plural); + } + + return $translation; + } + + /** + * Reads and returns the content of a file. + */ + protected static function readFile(string $file): string + { + $content = @file_get_contents($file); + + if (false === $content) { + throw new Exception("Cannot read the file '$file', probably permissions"); + } + + return $content; + } +} diff --git a/vendor/gettext/gettext/src/Scanner/ScannerInterface.php b/vendor/gettext/gettext/src/Scanner/ScannerInterface.php new file mode 100644 index 00000000..b93db2a8 --- /dev/null +++ b/vendor/gettext/gettext/src/Scanner/ScannerInterface.php @@ -0,0 +1,22 @@ +context = $context; + $translation->original = $original; + + return $translation; + } + + protected static function generateId(?string $context, string $original): string + { + return "{$context}\004{$original}"; + } + + protected function __construct(string $id) + { + $this->id = $id; + + $this->references = new References(); + $this->flags = new Flags(); + $this->comments = new Comments(); + $this->extractedComments = new Comments(); + } + + public function __clone() + { + $this->references = clone $this->references; + $this->flags = clone $this->flags; + $this->comments = clone $this->comments; + $this->extractedComments = clone $this->extractedComments; + } + + public function toArray(): array + { + return [ + 'id' => $this->id, + 'context' => $this->context, + 'original' => $this->original, + 'translation' => $this->translation, + 'plural' => $this->plural, + 'pluralTranslations' => $this->pluralTranslations, + 'disabled' => $this->disabled, + 'references' => $this->getReferences()->toArray(), + 'flags' => $this->getFlags()->toArray(), + 'comments' => $this->getComments()->toArray(), + 'extractedComments' => $this->getExtractedComments()->toArray(), + ]; + } + + public function getId(): string + { + return $this->id; + } + + public function getContext(): ?string + { + return $this->context; + } + + public function withContext(?string $context): Translation + { + $clone = clone $this; + $clone->context = $context; + $clone->id = static::generateId($clone->getContext(), $clone->getOriginal()); + + return $clone; + } + + public function getOriginal(): string + { + return $this->original; + } + + public function withOriginal(string $original): Translation + { + $clone = clone $this; + $clone->original = $original; + $clone->id = static::generateId($clone->getContext(), $clone->getOriginal()); + + return $clone; + } + + public function setPlural(string $plural): self + { + $this->plural = $plural; + + return $this; + } + + public function getPlural(): ?string + { + return $this->plural; + } + + public function disable(bool $disabled = true): self + { + $this->disabled = $disabled; + + return $this; + } + + public function isDisabled(): bool + { + return $this->disabled; + } + + public function translate(string $translation): self + { + $this->translation = $translation; + + return $this; + } + + public function getTranslation(): ?string + { + return $this->translation; + } + + public function isTranslated(): bool + { + return isset($this->translation) && $this->translation !== ''; + } + + public function translatePlural(string ...$translations): self + { + $this->pluralTranslations = $translations; + + return $this; + } + + public function getPluralTranslations(int $size = null): array + { + if ($size === null) { + return $this->pluralTranslations; + } + + $length = count($this->pluralTranslations); + + if ($size > $length) { + return $this->pluralTranslations + array_fill(0, $size, ''); + } + + return array_slice($this->pluralTranslations, 0, $size); + } + + public function getReferences(): References + { + return $this->references; + } + + public function getFlags(): Flags + { + return $this->flags; + } + + public function getComments(): Comments + { + return $this->comments; + } + + public function getExtractedComments(): Comments + { + return $this->extractedComments; + } + + public function mergeWith(Translation $translation, int $strategy = 0): Translation + { + $merged = clone $this; + + if ($strategy & Merge::COMMENTS_THEIRS) { + $merged->comments = clone $translation->comments; + } elseif (!($strategy & Merge::COMMENTS_OURS)) { + $merged->comments = $merged->comments->mergeWith($translation->comments); + } + + if ($strategy & Merge::EXTRACTED_COMMENTS_THEIRS) { + $merged->extractedComments = clone $translation->extractedComments; + } elseif (!($strategy & Merge::EXTRACTED_COMMENTS_OURS)) { + $merged->extractedComments = $merged->extractedComments->mergeWith($translation->extractedComments); + } + + if ($strategy & Merge::REFERENCES_THEIRS) { + $merged->references = clone $translation->references; + } elseif (!($strategy & Merge::REFERENCES_OURS)) { + $merged->references = $merged->references->mergeWith($translation->references); + } + + if ($strategy & Merge::FLAGS_THEIRS) { + $merged->flags = clone $translation->flags; + } elseif (!($strategy & Merge::FLAGS_OURS)) { + $merged->flags = $merged->flags->mergeWith($translation->flags); + } + + $override = (bool) ($strategy & Merge::TRANSLATIONS_OVERRIDE); + + if (!$merged->translation || ($translation->translation && $override)) { + $merged->translation = $translation->translation; + } + + if (!$merged->plural || ($translation->plural && $override)) { + $merged->plural = $translation->plural; + } + + if (empty($merged->pluralTranslations) || (!empty($translation->pluralTranslations) && $override)) { + $merged->pluralTranslations = $translation->pluralTranslations; + } + + $merged->disable($translation->isDisabled()); + + return $merged; + } +} diff --git a/vendor/gettext/gettext/src/Translations.php b/vendor/gettext/gettext/src/Translations.php new file mode 100644 index 00000000..eed33979 --- /dev/null +++ b/vendor/gettext/gettext/src/Translations.php @@ -0,0 +1,216 @@ +setDomain($domain); + } + + if (isset($language)) { + $translations->setLanguage($language); + } + + return $translations; + } + + protected function __construct() + { + $this->headers = new Headers(); + $this->flags = new Flags(); + } + + public function __clone() + { + foreach ($this->translations as $id => $translation) { + $this->translations[$id] = clone $translation; + } + + $this->headers = clone $this->headers; + } + + public function setDescription(?string $description): self + { + $this->description = $description; + + return $this; + } + + public function getDescription(): ?string + { + return $this->description; + } + + public function getFlags(): Flags + { + return $this->flags; + } + + public function toArray(): array + { + return [ + 'description' => $this->description, + 'headers' => $this->headers->toArray(), + 'flags' => $this->flags->toArray(), + 'translations' => array_map( + function ($translation) { + return $translation->toArray(); + }, + array_values($this->translations) + ), + ]; + } + + #[ReturnTypeWillChange] + public function getIterator() + { + return new ArrayIterator($this->translations); + } + + public function getTranslations(): array + { + return $this->translations; + } + + public function count(): int + { + return count($this->translations); + } + + public function getHeaders(): Headers + { + return $this->headers; + } + + public function add(Translation $translation): self + { + $id = $translation->getId(); + + $this->translations[$id] = $translation; + + return $this; + } + + public function addOrMerge(Translation $translation, int $mergeStrategy = 0): Translation + { + $id = $translation->getId(); + + if (isset($this->translations[$id])) { + return $this->translations[$id] = $this->translations[$id]->mergeWith($translation, $mergeStrategy); + } + + return $this->translations[$id] = $translation; + } + + public function remove(Translation $translation): self + { + $key = array_search($translation, $this->translations); + + if ($key !== false) { + unset($this->translations[$key]); + } + + return $this; + } + + public function setDomain(string $domain): self + { + $this->getHeaders()->setDomain($domain); + + return $this; + } + + public function getDomain(): ?string + { + return $this->getHeaders()->getDomain(); + } + + public function setLanguage(string $language): self + { + $info = Language::getById($language); + + if (empty($info)) { + throw new InvalidArgumentException(sprintf('The language "%s" is not valid', $language)); + } + + $this->getHeaders() + ->setLanguage($language) + ->setPluralForm(count($info->categories), $info->formula); + + return $this; + } + + public function getLanguage(): ?string + { + return $this->getHeaders()->getLanguage(); + } + + public function find(?string $context, string $original): ?Translation + { + foreach ($this->translations as $translation) { + if ($translation->getContext() === $context && $translation->getOriginal() === $original) { + return $translation; + } + } + + return null; + } + + public function mergeWith(Translations $translations, int $strategy = 0): Translations + { + $merged = clone $this; + + if ($strategy & Merge::HEADERS_THEIRS) { + $merged->headers = clone $translations->headers; + } elseif (!($strategy & Merge::HEADERS_OURS)) { + $merged->headers = $merged->headers->mergeWith($translations->headers); + } + + if ($strategy & Merge::FLAGS_THEIRS) { + $merged->flags = clone $translations->flags; + } elseif (!($strategy & Merge::FLAGS_OURS)) { + $merged->flags = $merged->flags->mergeWith($translations->flags); + } + + if (!$merged->description) { + $merged->description = $translations->description; + } + + foreach ($translations as $id => $translation) { + if (isset($merged->translations[$id])) { + $translation = $merged->translations[$id]->mergeWith($translation, $strategy); + } + + $merged->add($translation); + } + + if ($strategy & Merge::TRANSLATIONS_THEIRS) { + $merged->translations = array_intersect_key($merged->translations, $translations->translations); + } elseif ($strategy & Merge::TRANSLATIONS_OURS) { + $merged->translations = array_intersect_key($merged->translations, $this->translations); + } + + return $merged; + } +} diff --git a/vendor/gettext/languages/LICENSE b/vendor/gettext/languages/LICENSE new file mode 100644 index 00000000..26ddf05e --- /dev/null +++ b/vendor/gettext/languages/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Michele Locati + +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. + diff --git a/vendor/gettext/languages/UNICODE-LICENSE.txt b/vendor/gettext/languages/UNICODE-LICENSE.txt new file mode 100644 index 00000000..567fda24 --- /dev/null +++ b/vendor/gettext/languages/UNICODE-LICENSE.txt @@ -0,0 +1,46 @@ +UNICODE, INC. LICENSE AGREEMENT - DATA FILES AND SOFTWARE + +See Terms of Use for definitions of Unicode Inc.'s +Data Files and Software. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright © 1991-2019 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in https://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE 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 OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. diff --git a/vendor/gettext/languages/bin/export-plural-rules b/vendor/gettext/languages/bin/export-plural-rules new file mode 100644 index 00000000..0389e2e6 --- /dev/null +++ b/vendor/gettext/languages/bin/export-plural-rules @@ -0,0 +1,290 @@ +#!/usr/bin/env php + $arg) { + if ($argi === 0) { + continue; + } + if (is_string($arg)) { + $argLC = trim(strtolower($arg)); + switch ($argLC) { + case '-h': + case '--help': + self::showSyntax(); + exit(0); + case '--us-ascii': + self::$outputUSAscii = true; + break; + case '--reduce=yes': + self::$reduce = true; + break; + case '--reduce=no': + self::$reduce = false; + break; + case '--parenthesis=yes': + self::$noExtraParenthesis = false; + break; + case '--parenthesis=no': + self::$noExtraParenthesis = true; + break; + default: + if (preg_match('/^--output=.+$/', $argLC)) { + if (isset(self::$outputFilename)) { + fwrite(STDERR, "The output file name has been specified more than once!\n"); + self::showSyntax(); + exit(3); + } + list(, self::$outputFilename) = explode('=', $arg, 2); + self::$outputFilename = trim(self::$outputFilename); + } elseif (preg_match('/^--languages?=.+$/', $argLC)) { + list(, $s) = explode('=', $arg, 2); + $list = explode(',', $s); + if (is_array(self::$languages)) { + self::$languages = array_merge(self::$languages, $list); + } else { + self::$languages = $list; + } + } elseif (isset($exporters[$argLC])) { + if (isset(self::$outputFormat)) { + fwrite(STDERR, "The output format has been specified more than once!\n"); + self::showSyntax(); + exit(3); + } + self::$outputFormat = $argLC; + } else { + fwrite(STDERR, "Unknown option: {$arg}\n"); + self::showSyntax(); + exit(2); + } + break; + } + } + } + } + if (!isset(self::$outputFormat)) { + self::showSyntax(); + exit(1); + } + if (isset(self::$languages)) { + self::$languages = array_values(array_unique(self::$languages)); + } + if (!isset(self::$reduce)) { + self::$reduce = isset(self::$languages) ? false : true; + } + } + + /** + * Write out the syntax. + */ + public static function showSyntax() + { + $basename = basename(__FILE__); + $exporters = array_keys(Exporter::getExporters(true)); + $exporterList = implode('|', $exporters); + fwrite( + STDERR, + <<[,,...]] [--reduce=yes|no] [--parenthesis=yes|no] [--output=] <{$exporterList}> + +Where: + --help + show this help message. + + --us-ascii + if specified, the output will contain only US-ASCII characters. + + --languages(or --language) + export only the specified language codes. + Separate languages with commas; you can also use this argument + more than once; it's case insensitive and accepts both '_' and + '-' as locale chunks separator (eg we accept 'it_IT' as well as + 'it-it'). + --reduce + if set to yes the output won't contain languages with the same + base language and rules. + For instance nl_BE ('Flemish') will be omitted because it's the + same as nl ('Dutch'). + Defaults to 'no' if --languages is specified, to 'yes' otherwise. + --parenthesis + if set to no, extra parenthesis will be omitted in generated + plural rules formulas. + Those extra parenthesis are needed to create a PHP-compatible + formula. + Defaults to 'yes' + --output + if specified, the output will be saved to . If not + specified we'll output to standard output. + +Output formats + +EOT + ); + $len = max(array_map('strlen', $exporters)); + foreach ($exporters as $exporter) { + fwrite(STDERR, ' ' . str_pad($exporter, $len) . ': ' . Exporter::getExporterDescription($exporter) . "\n"); + } + fwrite(STDERR, "\n"); + } + + /** + * Reduce a language list to the minimum common denominator. + * + * @param Language[] $languages + * + * @return Language[] + */ + public static function reduce($languages) + { + for ($numChunks = 3; $numChunks >= 2; $numChunks--) { + $filtered = array(); + foreach ($languages as $language) { + $chunks = explode('_', $language->id); + $compatibleFound = false; + if ($numChunks === count($chunks)) { + $categoriesHash = serialize($language->categories); + $otherIds = array(); + $otherIds[] = $chunks[0]; + for ($k = 2; $k < $numChunks; $k++) { + $otherIds[] = $chunks[0] . '_' . $chunks[$numChunks - 1]; + } + + foreach ($languages as $check) { + foreach ($otherIds as $otherId) { + if ($check->id === $otherId && $check->formula === $language->formula && $categoriesHash === serialize($check->categories)) { + $compatibleFound = true; + break; + } + } + if ($compatibleFound === true) { + break; + } + } + } + if (!$compatibleFound) { + $filtered[] = $language; + } + } + $languages = $filtered; + } + + return $languages; + } +} + +// Parse the command line options +Enviro::initialize(); + +try { + if (isset(Enviro::$languages)) { + $languages = array(); + foreach (Enviro::$languages as $languageId) { + $language = Language::getById($languageId); + if (!isset($language)) { + throw new Exception("Unable to find the language with id '{$languageId}'"); + } + $languages[] = $language; + } + } else { + $languages = Language::getAll(); + } + if (Enviro::$reduce) { + $languages = Enviro::reduce($languages); + } + if (Enviro::$noExtraParenthesis) { + $languages = array_map( + function (Language $language) { + $language->formula = $language->buildFormula(true); + + return $language; + }, + $languages + ); + } + if (isset(Enviro::$outputFilename)) { + echo call_user_func(array(Exporter::getExporterClassName(Enviro::$outputFormat), 'toFile'), $languages, Enviro::$outputFilename, array('us-ascii' => Enviro::$outputUSAscii)); + } else { + echo call_user_func(array(Exporter::getExporterClassName(Enviro::$outputFormat), 'toString'), $languages, array('us-ascii' => Enviro::$outputUSAscii)); + } +} catch (Exception $x) { + fwrite(STDERR, $x->getMessage() . "\n"); + fwrite(STDERR, "Trace:\n"); + fwrite(STDERR, $x->getTraceAsString() . "\n"); + exit(4); +} + +exit(0); diff --git a/vendor/gettext/languages/bin/export-plural-rules.bat b/vendor/gettext/languages/bin/export-plural-rules.bat new file mode 100644 index 00000000..b99ef8d4 --- /dev/null +++ b/vendor/gettext/languages/bin/export-plural-rules.bat @@ -0,0 +1 @@ +@php "%~dpn0" %* \ No newline at end of file diff --git a/vendor/gettext/languages/composer.json b/vendor/gettext/languages/composer.json new file mode 100644 index 00000000..2f914a4d --- /dev/null +++ b/vendor/gettext/languages/composer.json @@ -0,0 +1,51 @@ +{ + "name": "gettext/languages", + "description": "gettext languages with plural rules", + "keywords": [ + "localization", + "l10n", + "internationalization", + "i18n", + "translations", + "translate", + "php", + "unicode", + "cldr", + "language", + "languages", + "plural", + "plurals", + "plural rules" + ], + "homepage": "https://github.com/php-gettext/Languages", + "license": "MIT", + "authors": [ + { + "name": "Michele Locati", + "email": "mlocati@gmail.com", + "role": "Developer" + } + ], + "autoload": { + "psr-4": { + "Gettext\\Languages\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Gettext\\Languages\\Test\\": "tests/test/" + } + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5 || ^8.4" + }, + "scripts": { + "test": "phpunit" + }, + "bin": [ + "bin/export-plural-rules" + ] +} \ No newline at end of file diff --git a/vendor/gettext/languages/src/Category.php b/vendor/gettext/languages/src/Category.php new file mode 100644 index 00000000..a3e5d3b8 --- /dev/null +++ b/vendor/gettext/languages/src/Category.php @@ -0,0 +1,124 @@ +id = $matches[1]; + $cldrFormulaAndExamplesNormalized = trim(preg_replace('/\s+/', ' ', $cldrFormulaAndExamples)); + if (!preg_match('/^([^@]*)(?:@integer([^@]+))?(?:@decimal(?:[^@]+))?$/', $cldrFormulaAndExamplesNormalized, $matches)) { + throw new Exception("Invalid CLDR category rule: {$cldrFormulaAndExamples}"); + } + $cldrFormula = trim($matches[1]); + $s = isset($matches[2]) ? trim($matches[2]) : ''; + $this->examples = ($s === '') ? null : $s; + switch ($this->id) { + case CldrData::OTHER_CATEGORY: + if ($cldrFormula !== '') { + throw new Exception("The '" . CldrData::OTHER_CATEGORY . "' category should not have any formula, but it has '{$cldrFormula}'"); + } + $this->formula = null; + break; + default: + if ($cldrFormula === '') { + throw new Exception("The '{$this->id}' category does not have a formula"); + } + $this->formula = FormulaConverter::convertFormula($cldrFormula); + break; + } + } + + /** + * Return a list of numbers corresponding to the $examples value. + * + * @throws \Exception throws an Exception if we weren't able to expand the examples + * + * @return int[] + */ + public function getExampleIntegers() + { + return self::expandExamples($this->examples); + } + + /** + * Expand a list of examples as defined by CLDR. + * + * @param string $examples A string like '1, 2, 5...7, …'. + * + * @throws \Exception throws an Exception if we weren't able to expand $examples + * + * @return int[] + */ + public static function expandExamples($examples) + { + $result = array(); + $m = null; + if (substr($examples, -strlen(', …')) === ', …') { + $examples = substr($examples, 0, strlen($examples) - strlen(', …')); + } + foreach (explode(',', str_replace(' ', '', $examples)) as $range) { + if (preg_match('/^(?\d+)((c|e)(?\d+))?$/', $range, $m)) { + $result[] = (int) (isset($m['exp']) ? ($m['num'] . str_repeat('0', (int) $m['exp'])) : $range); + } elseif (preg_match('/^(\d+)~(\d+)$/', $range, $m)) { + $from = (int) $m[1]; + $to = (int) $m[2]; + $delta = $to - $from; + $step = (int) max(1, $delta / 100); + for ($i = $from; $i < $to; $i += $step) { + $result[] = $i; + } + $result[] = $to; + } else { + throw new Exception("Unhandled test range '{$range}' in '{$examples}'"); + } + } + if (empty($result)) { + throw new Exception("No test numbers from '{$examples}'"); + } + + return $result; + } +} diff --git a/vendor/gettext/languages/src/CldrData.php b/vendor/gettext/languages/src/CldrData.php new file mode 100644 index 00000000..9169a1da --- /dev/null +++ b/vendor/gettext/languages/src/CldrData.php @@ -0,0 +1,342 @@ +
+     * "en": {
+     *     "pluralRule-count-one": "i = 1 and v = 0 @integer 1",
+     *     "pluralRule-count-other": " @integer 0, 2~16, 100, 1000, 10000, 100000, 1000000, … @decimal 0.0~1.5, 10.0, 100.0, 1000.0, 10000.0, 100000.0, 1000000.0, …"
+     * }
+     * 
+ * + * @return array + */ + public static function getPlurals() + { + return self::getData('plurals'); + } + + /** + * Return a list of superseded language codes. + * + * @return array keys are the former language codes, values are the new language/locale codes + */ + public static function getSupersededLanguages() + { + return self::getData('supersededLanguages'); + } + + /** + * Retrieve the name of a language, as well as if a language code is deprecated in favor of another language code. + * + * @param string $id the language identifier + * + * @return array|null Returns an array with the keys 'id' (normalized), 'name', 'supersededBy' (optional), 'territory' (optional), 'script' (optional), 'baseLanguage' (optional), 'categories'. If $id is not valid returns null. + */ + public static function getLanguageInfo($id) + { + $result = null; + $matches = array(); + if (preg_match('/^([a-z]{2,3})(?:[_\-]([a-z]{4}))?(?:[_\-]([a-z]{2}|[0-9]{3}))?(?:$|-)/i', $id, $matches)) { + $languageId = strtolower($matches[1]); + $scriptId = (isset($matches[2]) && ($matches[2] !== '')) ? ucfirst(strtolower($matches[2])) : null; + $territoryId = (isset($matches[3]) && ($matches[3] !== '')) ? strtoupper($matches[3]) : null; + $normalizedId = $languageId; + if (isset($scriptId)) { + $normalizedId .= '_' . $scriptId; + } + if (isset($territoryId)) { + $normalizedId .= '_' . $territoryId; + } + // Structure precedence: see Likely Subtags - http://www.unicode.org/reports/tr35/tr35-31/tr35.html#Likely_Subtags + $variants = array(); + $variantsWithScript = array(); + $variantsWithTerritory = array(); + if (isset($scriptId) && isset($territoryId)) { + $variantsWithTerritory[] = $variantsWithScript[] = $variants[] = "{$languageId}_{$scriptId}_{$territoryId}"; + } + if (isset($scriptId)) { + $variantsWithScript[] = $variants[] = "{$languageId}_{$scriptId}"; + } + if (isset($territoryId)) { + $variantsWithTerritory[] = $variants[] = "{$languageId}_{$territoryId}"; + } + $variants[] = $languageId; + $allGood = true; + $scriptName = null; + $scriptStandAloneName = null; + if (isset($scriptId)) { + $scriptNames = self::getScriptNames(false); + if (isset($scriptNames[$scriptId])) { + $scriptName = $scriptNames[$scriptId]; + $scriptStandAloneNames = self::getScriptNames(true); + $scriptStandAloneName = $scriptStandAloneNames[$scriptId]; + } else { + $allGood = false; + } + } + $territoryName = null; + if (isset($territoryId)) { + $territoryNames = self::getTerritoryNames(); + if (isset($territoryNames[$territoryId])) { + if ($territoryId !== '001') { + $territoryName = $territoryNames[$territoryId]; + } + } else { + $allGood = false; + } + } + $languageName = null; + $languageNames = self::getLanguageNames(); + foreach ($variants as $variant) { + if (isset($languageNames[$variant])) { + $languageName = $languageNames[$variant]; + if (isset($scriptName) && (!in_array($variant, $variantsWithScript))) { + $languageName = $scriptName . ' ' . $languageName; + } + if (isset($territoryName) && (!in_array($variant, $variantsWithTerritory))) { + $languageName .= ' (' . $territoryNames[$territoryId] . ')'; + } + break; + } + } + if (!isset($languageName)) { + $allGood = false; + } + $baseLanguage = null; + if (isset($scriptId) || isset($territoryId)) { + if (isset($languageNames[$languageId]) && ($languageNames[$languageId] !== $languageName)) { + $baseLanguage = $languageNames[$languageId]; + } + } + $plural = null; + $plurals = self::getPlurals(); + foreach ($variants as $variant) { + if (isset($plurals[$variant])) { + $plural = $plurals[$variant]; + break; + } + } + if (!isset($plural)) { + $allGood = false; + } + $supersededBy = null; + $supersededBys = self::getSupersededLanguages(); + foreach ($variants as $variant) { + if (isset($supersededBys[$variant])) { + $supersededBy = $supersededBys[$variant]; + break; + } + } + if ($allGood) { + $result = array(); + $result['id'] = $normalizedId; + $result['name'] = $languageName; + if (isset($supersededBy)) { + $result['supersededBy'] = $supersededBy; + } + if (isset($scriptStandAloneName)) { + $result['script'] = $scriptStandAloneName; + } + if (isset($territoryName)) { + $result['territory'] = $territoryName; + } + if (isset($baseLanguage)) { + $result['baseLanguage'] = $baseLanguage; + } + $result['categories'] = $plural; + } + } + + return $result; + } + + /** + * Returns the loaded CLDR data. + * + * @param string $key Can be 'languages', 'territories', 'plurals', 'supersededLanguages', 'scripts', 'standAloneScripts' + * + * @return array + */ + private static function getData($key) + { + if (!isset(self::$data)) { + $fixKeys = function ($list, &$standAlone = null) { + $result = array(); + $standAlone = array(); + $match = null; + foreach ($list as $key => $value) { + $variant = ''; + if (preg_match('/^(.+)-alt-(short|variant|stand-alone|long|menu)$/', $key, $match)) { + $key = $match[1]; + $variant = $match[2]; + } + $key = str_replace('-', '_', $key); + switch ($key) { + case 'root': // Language: Root + case 'und': // Language: Unknown Language + case 'zxx': // Language: No linguistic content + case 'ZZ': // Territory: Unknown Region + case 'Zinh': // Script: Inherited + case 'Zmth': // Script: Mathematical Notation + case 'Zsym': // Script: Symbols + case 'Zxxx': // Script: Unwritten + case 'Zyyy': // Script: Common + case 'Zzzz': // Script: Unknown Script + break; + default: + switch ($variant) { + case 'stand-alone': + $standAlone[$key] = $value; + break; + case '': + $result[$key] = $value; + break; + } + break; + } + } + + return $result; + }; + $data = array(); + $json = json_decode(file_get_contents(__DIR__ . '/cldr-data/main/en-US/languages.json'), true); + $data['languages'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['languages']); + $json = json_decode(file_get_contents(__DIR__ . '/cldr-data/main/en-US/territories.json'), true); + $data['territories'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['territories']); + $json = json_decode(file_get_contents(__DIR__ . '/cldr-data/supplemental/plurals.json'), true); + $data['plurals'] = $fixKeys($json['supplemental']['plurals-type-cardinal']); + $json = json_decode(file_get_contents(__DIR__ . '/cldr-data/main/en-US/scripts.json'), true); + $data['scripts'] = $fixKeys($json['main']['en-US']['localeDisplayNames']['scripts'], $data['standAloneScripts']); + $data['standAloneScripts'] = array_merge($data['scripts'], $data['standAloneScripts']); + $data['scripts'] = array_merge($data['standAloneScripts'], $data['scripts']); + $data['supersededLanguages'] = array(); + // Remove the languages for which we don't have plurals + $m = null; + foreach (array_keys(array_diff_key($data['languages'], $data['plurals'])) as $missingPlural) { + if (preg_match('/^([a-z]{2,3})_/', $missingPlural, $m)) { + if (!isset($data['plurals'][$m[1]])) { + unset($data['languages'][$missingPlural]); + } + } else { + unset($data['languages'][$missingPlural]); + } + } + // Fix the languages for which we have plurals + $formerCodes = array( + 'in' => 'id', // former Indonesian + 'iw' => 'he', // former Hebrew + 'ji' => 'yi', // former Yiddish + 'jw' => 'jv', // former Javanese + 'mo' => 'ro_MD', // former Moldavian + ); + $knownMissingLanguages = array( + 'guw' => 'Gun', + 'hnj' => 'Mong Njua', + 'nah' => 'Nahuatl', + 'smi' => 'Sami', + ); + foreach (array_keys(array_diff_key($data['plurals'], $data['languages'])) as $missingLanguage) { + if (isset($formerCodes[$missingLanguage]) && isset($data['languages'][$formerCodes[$missingLanguage]])) { + $data['languages'][$missingLanguage] = $data['languages'][$formerCodes[$missingLanguage]]; + $data['supersededLanguages'][$missingLanguage] = $formerCodes[$missingLanguage]; + } else { + if (isset($knownMissingLanguages[$missingLanguage])) { + $data['languages'][$missingLanguage] = $knownMissingLanguages[$missingLanguage]; + } else { + throw new Exception("We have the plural rule for the language '{$missingLanguage}' but we don't have its language name"); + } + } + } + ksort($data['languages'], SORT_STRING); + ksort($data['territories'], SORT_STRING); + ksort($data['plurals'], SORT_STRING); + ksort($data['scripts'], SORT_STRING); + ksort($data['standAloneScripts'], SORT_STRING); + ksort($data['supersededLanguages'], SORT_STRING); + self::$data = $data; + } + if (!isset(self::$data[$key])) { + throw new Exception("Invalid CLDR data key: '{$key}'"); + } + + return self::$data[$key]; + } +} diff --git a/vendor/gettext/languages/src/Exporter/Docs.php b/vendor/gettext/languages/src/Exporter/Docs.php new file mode 100644 index 00000000..c4c5d4c0 --- /dev/null +++ b/vendor/gettext/languages/src/Exporter/Docs.php @@ -0,0 +1,64 @@ + + + + + + + + gettext plural rules - built from CLDR + + + + + + +
+ +EOT; + $result .= static::buildTable($languages, true); + $result .= <<<'EOT' + +
+ + + + +EOT; + + return $result; + } +} diff --git a/vendor/gettext/languages/src/Exporter/Exporter.php b/vendor/gettext/languages/src/Exporter/Exporter.php new file mode 100644 index 00000000..95a7112c --- /dev/null +++ b/vendor/gettext/languages/src/Exporter/Exporter.php @@ -0,0 +1,153 @@ + $class) { + if (call_user_func(self::getExporterClassName($handle) . '::isForPublicUse') === true) { + $result[$handle] = $class; + } + } + } else { + $result = self::$exporters; + } + + return $result; + } + + /** + * Return the description of a specific exporter. + * + * @param string $exporterHandle the handle of the exporter + * + * @throws \Exception throws an Exception if $exporterHandle is not valid + * + * @return string + */ + final public static function getExporterDescription($exporterHandle) + { + $exporters = self::getExporters(); + if (!isset($exporters[$exporterHandle])) { + throw new Exception("Invalid exporter handle: '{$exporterHandle}'"); + } + + return call_user_func(self::getExporterClassName($exporterHandle) . '::getDescription'); + } + + /** + * Returns the fully qualified class name of a exporter given its handle. + * + * @param string $exporterHandle the exporter class handle + * + * @return string + */ + final public static function getExporterClassName($exporterHandle) + { + return __NAMESPACE__ . '\\' . ucfirst(strtolower($exporterHandle)); + } + + /** + * Convert a list of Language instances to string. + * + * @param \Gettext\Languages\Language[] $languages the Language instances to convert + * @param array|null $options + * + * @return string + */ + final public static function toString($languages, $options = null) + { + if (isset($options) && is_array($options)) { + if (isset($options['us-ascii']) && $options['us-ascii']) { + $asciiList = array(); + foreach ($languages as $language) { + $asciiList[] = $language->getUSAsciiClone(); + } + $languages = $asciiList; + } + } + + return static::toStringDo($languages); + } + + /** + * Save the Language instances to a file. + * + * @param \Gettext\Languages\Language[] $languages the Language instances to convert + * @param array|null $options + * + * @throws \Exception + */ + final public static function toFile($languages, $filename, $options = null) + { + $data = self::toString($languages, $options); + if (@file_put_contents($filename, $data) === false) { + throw new Exception("Error writing data to '{$filename}'"); + } + } + + /** + * Is this exporter for public use? + * + * @return bool + */ + public static function isForPublicUse() + { + return true; + } + + /** + * Return a short description of the exporter. + * + * @return string + */ + public static function getDescription() + { + throw new Exception(get_called_class() . ' does not implement the method ' . __FUNCTION__); + } + + /** + * Convert a list of Language instances to string. + * + * @param \Gettext\Languages\Language[] $languages the Language instances to convert + * + * @return string + */ + protected static function toStringDo($languages) + { + throw new Exception(get_called_class() . ' does not implement the method ' . __FUNCTION__); + } +} diff --git a/vendor/gettext/languages/src/Exporter/Html.php b/vendor/gettext/languages/src/Exporter/Html.php new file mode 100644 index 00000000..d16e7820 --- /dev/null +++ b/vendor/gettext/languages/src/Exporter/Html.php @@ -0,0 +1,69 @@ +'; + $lines[] = $prefix . ' '; + $lines[] = $prefix . ' '; + $lines[] = $prefix . ' Language code'; + $lines[] = $prefix . ' Language name'; + $lines[] = $prefix . ' # plurals'; + $lines[] = $prefix . ' Formula'; + $lines[] = $prefix . ' Plurals'; + $lines[] = $prefix . ' '; + $lines[] = $prefix . ' '; + $lines[] = $prefix . ' '; + foreach ($languages as $lc) { + $lines[] = $prefix . ' '; + $lines[] = $prefix . ' ' . $lc->id . ''; + $name = self::h($lc->name); + if (isset($lc->supersededBy)) { + $name .= '
Superseded by ' . $lc->supersededBy . ''; + } + $lines[] = $prefix . ' ' . $name . ''; + $lines[] = $prefix . ' ' . count($lc->categories) . ''; + $lines[] = $prefix . ' ' . self::h($lc->formula) . ''; + $cases = array(); + foreach ($lc->categories as $c) { + $cases[] = '
  • ' . $c->id . '' . self::h($c->examples) . '
  • '; + } + $lines[] = $prefix . ' ' . implode('', $cases) . ''; + $lines[] = $prefix . ' '; + } + $lines[] = $prefix . ' '; + $lines[] = $prefix . ''; + + return implode("\n", $lines); + } +} diff --git a/vendor/gettext/languages/src/Exporter/Json.php b/vendor/gettext/languages/src/Exporter/Json.php new file mode 100644 index 00000000..8ea57b88 --- /dev/null +++ b/vendor/gettext/languages/src/Exporter/Json.php @@ -0,0 +1,71 @@ +name; + if (isset($language->supersededBy)) { + $item['supersededBy'] = $language->supersededBy; + } + if (isset($language->script)) { + $item['script'] = $language->script; + } + if (isset($language->territory)) { + $item['territory'] = $language->territory; + } + if (isset($language->baseLanguage)) { + $item['baseLanguage'] = $language->baseLanguage; + } + $item['formula'] = $language->formula; + $item['plurals'] = count($language->categories); + $item['cases'] = array(); + $item['examples'] = array(); + foreach ($language->categories as $category) { + $item['cases'][] = $category->id; + $item['examples'][$category->id] = $category->examples; + } + $list[$language->id] = $item; + } + + return json_encode($list, static::getEncodeOptions()); + } +} diff --git a/vendor/gettext/languages/src/Exporter/Php.php b/vendor/gettext/languages/src/Exporter/Php.php new file mode 100644 index 00000000..d6b8d1b3 --- /dev/null +++ b/vendor/gettext/languages/src/Exporter/Php.php @@ -0,0 +1,61 @@ +id . '\' => array('; + $lines[] = ' \'name\' => \'' . addslashes($lc->name) . '\','; + if (isset($lc->supersededBy)) { + $lines[] = ' \'supersededBy\' => \'' . $lc->supersededBy . '\','; + } + if (isset($lc->script)) { + $lines[] = ' \'script\' => \'' . addslashes($lc->script) . '\','; + } + if (isset($lc->territory)) { + $lines[] = ' \'territory\' => \'' . addslashes($lc->territory) . '\','; + } + if (isset($lc->baseLanguage)) { + $lines[] = ' \'baseLanguage\' => \'' . addslashes($lc->baseLanguage) . '\','; + } + $lines[] = ' \'formula\' => \'' . $lc->formula . '\','; + $lines[] = ' \'plurals\' => ' . count($lc->categories) . ','; + $catNames = array(); + foreach ($lc->categories as $c) { + $catNames[] = "'{$c->id}'"; + } + $lines[] = ' \'cases\' => array(' . implode(', ', $catNames) . '),'; + $lines[] = ' \'examples\' => array('; + foreach ($lc->categories as $c) { + $lines[] = ' \'' . $c->id . '\' => \'' . $c->examples . '\','; + } + $lines[] = ' ),'; + $lines[] = ' ),'; + } + $lines[] = ');'; + $lines[] = ''; + + return implode("\n", $lines); + } +} diff --git a/vendor/gettext/languages/src/Exporter/Po.php b/vendor/gettext/languages/src/Exporter/Po.php new file mode 100644 index 00000000..797f4b92 --- /dev/null +++ b/vendor/gettext/languages/src/Exporter/Po.php @@ -0,0 +1,37 @@ +id . '\n"'; + $lines[] = '"Plural-Forms: nplurals=' . count($language->categories) . '; plural=' . $language->formula . '\n"'; + $lines[] = ''; + + return implode("\n", $lines); + } +} diff --git a/vendor/gettext/languages/src/Exporter/Prettyjson.php b/vendor/gettext/languages/src/Exporter/Prettyjson.php new file mode 100644 index 00000000..927f80a6 --- /dev/null +++ b/vendor/gettext/languages/src/Exporter/Prettyjson.php @@ -0,0 +1,32 @@ +id . '\' => {'; + $lines[] = ' \'name\' => \'' . addslashes($lc->name) . '\','; + if (isset($lc->supersededBy)) { + $lines[] = ' \'supersededBy\' => \'' . $lc->supersededBy . '\','; + } + if (isset($lc->script)) { + $lines[] = ' \'script\' => \'' . addslashes($lc->script) . '\','; + } + if (isset($lc->territory)) { + $lines[] = ' \'territory\' => \'' . addslashes($lc->territory) . '\','; + } + if (isset($lc->baseLanguage)) { + $lines[] = ' \'baseLanguage\' => \'' . addslashes($lc->baseLanguage) . '\','; + } + $lines[] = ' \'formula\' => \'' . $lc->formula . '\','; + $lines[] = ' \'plurals\' => ' . count($lc->categories) . ','; + $catNames = array(); + foreach ($lc->categories as $c) { + $catNames[] = "'{$c->id}'"; + } + $lines[] = ' \'cases\' => [' . implode(', ', $catNames) . '],'; + $lines[] = ' \'examples\' => {'; + foreach ($lc->categories as $c) { + $lines[] = ' \'' . $c->id . '\' => \'' . $c->examples . '\','; + } + $lines[] = ' },'; + $lines[] = ' },'; + } + $lines[] = '}'; + $lines[] = ''; + + return implode("\n", $lines); + } +} diff --git a/vendor/gettext/languages/src/Exporter/Xml.php b/vendor/gettext/languages/src/Exporter/Xml.php new file mode 100644 index 00000000..32358b8f --- /dev/null +++ b/vendor/gettext/languages/src/Exporter/Xml.php @@ -0,0 +1,60 @@ +loadXML(''); + $xLanguages = $xml->firstChild; + foreach ($languages as $language) { + $xLanguage = $xml->createElement('language'); + $xLanguage->setAttribute('id', $language->id); + $xLanguage->setAttribute('name', $language->name); + if (isset($language->supersededBy)) { + $xLanguage->setAttribute('supersededBy', $language->supersededBy); + } + if (isset($language->script)) { + $xLanguage->setAttribute('script', $language->script); + } + if (isset($language->territory)) { + $xLanguage->setAttribute('territory', $language->territory); + } + if (isset($language->baseLanguage)) { + $xLanguage->setAttribute('baseLanguage', $language->baseLanguage); + } + $xLanguage->setAttribute('formula', $language->formula); + foreach ($language->categories as $category) { + $xCategory = $xml->createElement('category'); + $xCategory->setAttribute('id', $category->id); + $xCategory->setAttribute('examples', $category->examples); + $xLanguage->appendChild($xCategory); + } + $xLanguages->appendChild($xLanguage); + } + $xml->formatOutput = true; + + return $xml->saveXML(); + } +} diff --git a/vendor/gettext/languages/src/FormulaConverter.php b/vendor/gettext/languages/src/FormulaConverter.php new file mode 100644 index 00000000..72061ba5 --- /dev/null +++ b/vendor/gettext/languages/src/FormulaConverter.php @@ -0,0 +1,172 @@ + the whole 'and' group is always false + $gettextFormulaChunk = false; + break; + } + if ($gettextAtom !== true) { + $andSeparatedChunks[] = $gettextAtom; + } + } + if (!isset($gettextFormulaChunk)) { + if (empty($andSeparatedChunks)) { + // All the atoms joined by 'and' always evaluate to true => the whole 'and' group is always true + $gettextFormulaChunk = true; + } else { + $gettextFormulaChunk = implode(' && ', $andSeparatedChunks); + // Special cases simplification + switch ($gettextFormulaChunk) { + case 'n >= 0 && n <= 2 && n != 2': + $gettextFormulaChunk = 'n == 0 || n == 1'; + break; + } + } + } + if ($gettextFormulaChunk === true) { + // One part of the formula joined with the others by 'or' always evaluates to true => the whole formula always evaluates to true + return true; + } + if ($gettextFormulaChunk !== false) { + $orSeparatedChunks[] = $gettextFormulaChunk; + } + } + if (empty($orSeparatedChunks)) { + // All the parts joined by 'or' always evaluate to false => the whole formula always evaluates to false + return false; + } + + return implode(' || ', $orSeparatedChunks); + } + + /** + * Converts an atomic part of the CLDR formula to its gettext representation. + * + * @param string $cldrAtom the CLDR formula atom to convert + * + * @throws \Exception + * + * @return bool|string returns true if the gettext will always evaluate to true, false if gettext will always evaluate to false, return the gettext formula otherwise + */ + private static function convertAtom($cldrAtom) + { + $m = null; + $gettextAtom = $cldrAtom; + $gettextAtom = str_replace(' = ', ' == ', $gettextAtom); + $gettextAtom = str_replace('i', 'n', $gettextAtom); + if (preg_match('/^n( % \d+)? (!=|==) \d+$/', $gettextAtom)) { + return $gettextAtom; + } + if (preg_match('/^n( % \d+)? (!=|==) \d+(,\d+|\.\.\d+)+$/', $gettextAtom)) { + return self::expandAtom($gettextAtom); + } + if (preg_match('/^(?:v|w)(?: % 10+)? == (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // For gettext: v == 0, w == 0 + return (int) $m[1] === 0 ? true : false; + } + if (preg_match('/^(?:v|w)(?: % 10+)? != (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // For gettext: v == 0, w == 0 + return (int) $m[1] === 0 ? false : true; + } + if (preg_match('/^(?:f|t|c|e)(?: % 10+)? == (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // f == empty, t == empty, c == empty, e == empty + return (int) $m[1] === 0 ? true : false; + } + if (preg_match('/^(?:f|t|c|e)(?: % 10+)? != (\d+)(?:\.\.\d+)?$/', $gettextAtom, $m)) { // f == empty, t == empty, c == empty, e == empty + return (int) $m[1] === 0 ? false : true; + } + throw new Exception("Unable to convert the formula chunk '{$cldrAtom}' from CLDR to gettext"); + } + + /** + * Expands an atom containing a range (for instance: 'n == 1,3..5'). + * + * @param string $atom + * + * @throws \Exception + * + * @return string + */ + private static function expandAtom($atom) + { + $m = null; + if (preg_match('/^(n(?: % \d+)?) (==|!=) (\d+(?:\.\.\d+|,\d+)+)$/', $atom, $m)) { + $what = $m[1]; + $op = $m[2]; + $chunks = array(); + foreach (explode(',', $m[3]) as $range) { + $chunk = null; + if ((!isset($chunk)) && preg_match('/^\d+$/', $range)) { + $chunk = "{$what} {$op} {$range}"; + } + if ((!isset($chunk)) && preg_match('/^(\d+)\.\.(\d+)$/', $range, $m)) { + $from = (int) $m[1]; + $to = (int) $m[2]; + if (($to - $from) === 1) { + switch ($op) { + case '==': + $chunk = "({$what} == {$from} || {$what} == {$to})"; + break; + case '!=': + $chunk = "{$what} != {$from} && {$what} == {$to}"; + break; + } + } else { + switch ($op) { + case '==': + $chunk = "{$what} >= {$from} && {$what} <= {$to}"; + break; + case '!=': + if ($what === 'n' && $from <= 0) { + $chunk = "{$what} > {$to}"; + } else { + $chunk = "({$what} < {$from} || {$what} > {$to})"; + } + break; + } + } + } + if (!isset($chunk)) { + throw new Exception("Unhandled range '{$range}' in '{$atom}'"); + } + $chunks[] = $chunk; + } + if (count($chunks) === 1) { + return $chunks[0]; + } + switch ($op) { + case '==': + return '(' . implode(' || ', $chunks) . ')'; break; + case '!=': + return implode(' && ', $chunks); + } + } + throw new Exception("Unable to expand '{$atom}'"); + } +} diff --git a/vendor/gettext/languages/src/Language.php b/vendor/gettext/languages/src/Language.php new file mode 100644 index 00000000..4f28c3bd --- /dev/null +++ b/vendor/gettext/languages/src/Language.php @@ -0,0 +1,411 @@ +id = $info['id']; + $this->name = $info['name']; + $this->supersededBy = isset($info['supersededBy']) ? $info['supersededBy'] : null; + $this->script = isset($info['script']) ? $info['script'] : null; + $this->territory = isset($info['territory']) ? $info['territory'] : null; + $this->baseLanguage = isset($info['baseLanguage']) ? $info['baseLanguage'] : null; + // Let's build the category list + $this->categories = array(); + foreach ($info['categories'] as $cldrCategoryId => $cldrFormulaAndExamples) { + $category = new Category($cldrCategoryId, $cldrFormulaAndExamples); + foreach ($this->categories as $c) { + if ($category->id === $c->id) { + throw new Exception("The category '{$category->id}' is specified more than once"); + } + } + $this->categories[] = $category; + } + if (empty($this->categories)) { + throw new Exception("The language '{$info['id']}' does not have any plural category"); + } + // Let's sort the categories from 'zero' to 'other' + usort($this->categories, function (Category $category1, Category $category2) { + return array_search($category1->id, CldrData::$categories) - array_search($category2->id, CldrData::$categories); + }); + // The 'other' category should always be there + if ($this->categories[count($this->categories) - 1]->id !== CldrData::OTHER_CATEGORY) { + throw new Exception("The language '{$info['id']}' does not have the '" . CldrData::OTHER_CATEGORY . "' plural category"); + } + $this->checkAlwaysTrueCategories(); + $this->checkAlwaysFalseCategories(); + $this->checkAllCategoriesWithExamples(); + $this->formula = $this->buildFormula(); + } + + /** + * Return a list of all languages available. + * + * @throws \Exception + * + * @return \Gettext\Languages\Language[] + */ + public static function getAll() + { + $result = array(); + foreach (array_keys(CldrData::getLanguageNames()) as $cldrLanguageId) { + $result[] = new self(CldrData::getLanguageInfo($cldrLanguageId)); + } + + return $result; + } + + /** + * Return a Language instance given the language id. + * + * @param string $id + * + * @return \Gettext\Languages\Language|null + */ + public static function getById($id) + { + $result = null; + $info = CldrData::getLanguageInfo($id); + if (isset($info)) { + $result = new self($info); + } + + return $result; + } + + /** + * Returns a clone of this instance with all the strings to US-ASCII. + * + * @return \Gettext\Languages\Language + */ + public function getUSAsciiClone() + { + $clone = clone $this; + self::asciifier($clone->name); + self::asciifier($clone->formula); + $clone->categories = array(); + foreach ($this->categories as $category) { + $categoryClone = clone $category; + self::asciifier($categoryClone->examples); + $clone->categories[] = $categoryClone; + } + + return $clone; + } + + /** + * Build the formula starting from the currently defined categories. + * + * @param bool $withoutParenthesis TRUE to build a formula in standard gettext format, FALSE (default) to build a PHP-compatible formula + * + * @return string + */ + public function buildFormula($withoutParenthesis = false) + { + $numCategories = count($this->categories); + switch ($numCategories) { + case 1: + // Just one category + return '0'; + case 2: + return self::reduceFormula(self::reverseFormula($this->categories[0]->formula)); + default: + $formula = (string) ($numCategories - 1); + for ($i = $numCategories - 2; $i >= 0; $i--) { + $f = self::reduceFormula($this->categories[$i]->formula); + if (!$withoutParenthesis && !preg_match('/^\([^()]+\)$/', $f)) { + $f = "({$f})"; + } + $formula = "{$f} ? {$i} : {$formula}"; + if (!$withoutParenthesis && $i > 0) { + $formula = "({$formula})"; + } + } + + return $formula; + } + } + + /** + * Let's look for categories that will always occur. + * This because with decimals (CLDR) we may have more cases, with integers (gettext) we have just one case. + * If we found that (single) category we reduce the categories to that one only. + * + * @throws \Exception + */ + private function checkAlwaysTrueCategories() + { + $alwaysTrueCategory = null; + foreach ($this->categories as $category) { + if ($category->formula === true) { + if (!isset($category->examples)) { + throw new Exception("The category '{$category->id}' should always occur, but it does not have examples (so for CLDR it will never occur for integers!)"); + } + $alwaysTrueCategory = $category; + break; + } + } + if (isset($alwaysTrueCategory)) { + foreach ($this->categories as $category) { + if (($category !== $alwaysTrueCategory) && isset($category->examples)) { + throw new Exception("The category '{$category->id}' should never occur, but it has some examples (so for CLDR it will occur!)"); + } + } + $alwaysTrueCategory->id = CldrData::OTHER_CATEGORY; + $alwaysTrueCategory->formula = null; + $this->categories = array($alwaysTrueCategory); + } + } + + /** + * Let's look for categories that will never occur. + * This because with decimals (CLDR) we may have more cases, with integers (gettext) we have some less cases. + * If we found those categories we strip them out. + * + * @throws \Exception + */ + private function checkAlwaysFalseCategories() + { + $filtered = array(); + foreach ($this->categories as $category) { + if ($category->formula === false) { + if (isset($category->examples)) { + throw new Exception("The category '{$category->id}' should never occur, but it has examples (so for CLDR it may occur!)"); + } + } else { + $filtered[] = $category; + } + } + $this->categories = $filtered; + } + + /** + * Let's look for categories that don't have examples. + * This because with decimals (CLDR) we may have more cases, with integers (gettext) we have some less cases. + * If we found those categories, we check that they never occur and we strip them out. + * + * @throws \Exception + */ + private function checkAllCategoriesWithExamples() + { + $allCategoriesIds = array(); + $goodCategories = array(); + $badCategories = array(); + $badCategoriesIds = array(); + foreach ($this->categories as $category) { + $allCategoriesIds[] = $category->id; + if (isset($category->examples)) { + $goodCategories[] = $category; + } else { + $badCategories[] = $category; + $badCategoriesIds[] = $category->id; + } + } + if (empty($badCategories)) { + return; + } + $removeCategoriesWithoutExamples = false; + switch (implode(',', $badCategoriesIds) . '@' . implode(',', $allCategoriesIds)) { + case CldrData::OTHER_CATEGORY . '@one,few,many,' . CldrData::OTHER_CATEGORY: + switch ($this->buildFormula()) { + case '(n % 10 == 1 && n % 100 != 11) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : ((n % 10 == 0 || n % 10 >= 5 && n % 10 <= 9 || n % 100 >= 11 && n % 100 <= 14) ? 2 : 3))': + // Numbers ending with 0 => case 2 ('many') + // Numbers ending with 1 but not with 11 => case 0 ('one') + // Numbers ending with 11 => case 2 ('many') + // Numbers ending with 2 but not with 12 => case 1 ('few') + // Numbers ending with 12 => case 2 ('many') + // Numbers ending with 3 but not with 13 => case 1 ('few') + // Numbers ending with 13 => case 2 ('many') + // Numbers ending with 4 but not with 14 => case 1 ('few') + // Numbers ending with 14 => case 2 ('many') + // Numbers ending with 5 => case 2 ('many') + // Numbers ending with 6 => case 2 ('many') + // Numbers ending with 7 => case 2 ('many') + // Numbers ending with 8 => case 2 ('many') + // Numbers ending with 9 => case 2 ('many') + // => the 'other' case never occurs: use 'other' for 'many' + $removeCategoriesWithoutExamples = true; + break; + case '(n == 1) ? 0 : ((n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14)) ? 1 : ((n != 1 && (n % 10 == 0 || n % 10 == 1) || n % 10 >= 5 && n % 10 <= 9 || n % 100 >= 12 && n % 100 <= 14) ? 2 : 3))': + // Numbers ending with 0 => case 2 ('many') + // Numbers ending with 1 but not number 1 => case 2 ('many') + // Number 1 => case 0 ('one') + // Numbers ending with 2 but not with 12 => case 1 ('few') + // Numbers ending with 12 => case 2 ('many') + // Numbers ending with 3 but not with 13 => case 1 ('few') + // Numbers ending with 13 => case 2 ('many') + // Numbers ending with 4 but not with 14 => case 1 ('few') + // Numbers ending with 14 => case 2 ('many') + // Numbers ending with 5 => case 2 ('many') + // Numbers ending with 6 => case 2 ('many') + // Numbers ending with 7 => case 2 ('many') + // Numbers ending with 8 => case 2 ('many') + // Numbers ending with 9 => case 2 ('many') + // => the 'other' case never occurs: use 'other' for 'many' + $removeCategoriesWithoutExamples = true; + break; + } + } + if (!$removeCategoriesWithoutExamples) { + throw new Exception("Unhandled case of plural categories without examples '" . implode(', ', $badCategoriesIds) . "' out of '" . implode(', ', $allCategoriesIds) . "'"); + } + if ($badCategories[count($badCategories) - 1]->id === CldrData::OTHER_CATEGORY) { + // We're removing the 'other' cagory: let's change the last good category to 'other' + $lastGood = $goodCategories[count($goodCategories) - 1]; + $lastGood->id = CldrData::OTHER_CATEGORY; + $lastGood->formula = null; + } + $this->categories = $goodCategories; + } + + /** + * Reverse a formula. + * + * @param string $formula + * + * @throws \Exception + * + * @return string + */ + private static function reverseFormula($formula) + { + if (preg_match('/^n( % \d+)? == \d+(\.\.\d+|,\d+)*?$/', $formula)) { + return str_replace(' == ', ' != ', $formula); + } + if (preg_match('/^n( % \d+)? != \d+(\.\.\d+|,\d+)*?$/', $formula)) { + return str_replace(' != ', ' == ', $formula); + } + if (preg_match('/^\(?n == \d+ \|\| n == \d+\)?$/', $formula)) { + return trim(str_replace(array(' == ', ' || '), array(' != ', ' && '), $formula), '()'); + } + $m = null; + if (preg_match('/^(n(?: % \d+)?) == (\d+) && (n(?: % \d+)?) != (\d+)$/', $formula, $m)) { + return "{$m[1]} != {$m[2]} || {$m[3]} == {$m[4]}"; + } + switch ($formula) { + case '(n == 1 || n == 2 || n == 3) || n % 10 != 4 && n % 10 != 6 && n % 10 != 9': + return 'n != 1 && n != 2 && n != 3 && (n % 10 == 4 || n % 10 == 6 || n % 10 == 9)'; + case '(n == 0 || n == 1) || n >= 11 && n <= 99': + return 'n >= 2 && (n < 11 || n > 99)'; + } + throw new Exception("Unable to reverse the formula '{$formula}'"); + } + + /** + * Reduce some excessively complex formulas. + * + * @param string $formula + * + * @return string + */ + private static function reduceFormula($formula) + { + $map = array( + 'n != 0 && n != 1' => 'n > 1', + '(n == 0 || n == 1) && n != 0' => 'n == 1', + ); + + return isset($map[$formula]) ? $map[$formula] : $formula; + } + + /** + * Take one variable and, if it's a string, we transliterate it to US-ASCII. + * + * @param mixed $value the variable to work on + * + * @throws \Exception + */ + private static function asciifier(&$value) + { + if (is_string($value) && $value !== '') { + // Avoid converting from 'Ÿ' to '"Y', let's prefer 'Y' + $value = strtr($value, array( + 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', + 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', + 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', + 'Ñ' => 'N', + 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', + 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', + 'Ÿ' => 'Y', 'Ý' => 'Y', + 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', + 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', + 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', + 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', + 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', + 'ý' => 'y', 'ÿ' => 'y', + '…' => '...', + 'ʼ' => "'", '’' => "'", + )); + } + } +} diff --git a/vendor/gettext/languages/src/autoloader.php b/vendor/gettext/languages/src/autoloader.php new file mode 100644 index 00000000..03077d46 --- /dev/null +++ b/vendor/gettext/languages/src/autoloader.php @@ -0,0 +1,13 @@ +