From 1ff5c35de5dbd70a782875a91dd2232fd01b002b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:38:04 +0200 Subject: Adding upstream version 0.10.1. Signed-off-by: Daniel Baumann --- vendor/ipl/i18n/LICENSE | 21 ++ vendor/ipl/i18n/composer.json | 25 +++ vendor/ipl/i18n/src/GettextTranslator.php | 351 ++++++++++++++++++++++++++++++ vendor/ipl/i18n/src/Locale.php | 124 +++++++++++ vendor/ipl/i18n/src/NoopTranslator.php | 31 +++ vendor/ipl/i18n/src/StaticTranslator.php | 14 ++ vendor/ipl/i18n/src/Translation.php | 101 +++++++++ vendor/ipl/i18n/src/functions.php | 34 +++ vendor/ipl/i18n/src/functions_include.php | 6 + 9 files changed, 707 insertions(+) create mode 100644 vendor/ipl/i18n/LICENSE create mode 100644 vendor/ipl/i18n/composer.json create mode 100644 vendor/ipl/i18n/src/GettextTranslator.php create mode 100644 vendor/ipl/i18n/src/Locale.php create mode 100644 vendor/ipl/i18n/src/NoopTranslator.php create mode 100644 vendor/ipl/i18n/src/StaticTranslator.php create mode 100644 vendor/ipl/i18n/src/Translation.php create mode 100644 vendor/ipl/i18n/src/functions.php create mode 100644 vendor/ipl/i18n/src/functions_include.php (limited to 'vendor/ipl/i18n') diff --git a/vendor/ipl/i18n/LICENSE b/vendor/ipl/i18n/LICENSE new file mode 100644 index 0000000..e179593 --- /dev/null +++ b/vendor/ipl/i18n/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2017 Icinga GmbH https://www.icinga.com + +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/ipl/i18n/composer.json b/vendor/ipl/i18n/composer.json new file mode 100644 index 0000000..79b9b51 --- /dev/null +++ b/vendor/ipl/i18n/composer.json @@ -0,0 +1,25 @@ +{ + "name": "ipl/i18n", + "type": "library", + "description": "Icinga PHP Library - Internationalization", + "keywords": ["gettext", "i18n", "internationalization", "localization", "translation"], + "homepage": "https://github.com/Icinga/ipl-i18n", + "license": "MIT", + "require": { + "php": ">=7.2", + "ext-intl": "*", + "ext-gettext": "*", + "ipl/stdlib": ">=0.12.0" + }, + "autoload": { + "files": ["src/functions_include.php"], + "psr-4": { + "ipl\\I18n\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "ipl\\Tests\\I18n\\": "tests" + } + } +} diff --git a/vendor/ipl/i18n/src/GettextTranslator.php b/vendor/ipl/i18n/src/GettextTranslator.php new file mode 100644 index 0000000..a7a8144 --- /dev/null +++ b/vendor/ipl/i18n/src/GettextTranslator.php @@ -0,0 +1,351 @@ +addTranslationDirectory('/path/to/locales') + * ->addTranslationDirectory('/path/to/locales-of-domain', 'special') // Could also be the same directory as above + * ->setLocale('de_DE'); + * + * $translator->translate('user'); + * + * printf( + * $translator->translatePlural('%d user', '%d user', 42), + * 42 + * ); + * + * $translator->translateInDomain('special-domain', 'request'); + * + * printf( + * $translator->translatePluralInDomain('special-domain', '%d request', '%d requests', 42), + * 42 + * ); + * + * // All translation functions also accept a context as last parameter + * $translator->translate('group', 'a-context'); + * ``` + * + */ +class GettextTranslator implements Translator +{ + /** @var string Default gettext domain */ + protected $defaultDomain = 'default'; + + /** @var string Default locale code */ + protected $defaultLocale = 'en_US'; + + /** @var array Known translation directories as array[$domain] => $directory */ + protected $translationDirectories = []; + + /** @var array Loaded translations as array[$domain] => $directory */ + protected $loadedTranslations = []; + + /** @var string Primary locale code used for translations */ + protected $locale; + + /** + * Get the default domain + * + * @return string + */ + public function getDefaultDomain() + { + return $this->defaultDomain; + } + + /** + * Set the default domain + * + * @param string $defaultDomain + * + * @return $this + */ + public function setDefaultDomain($defaultDomain) + { + $this->defaultDomain = $defaultDomain; + + return $this; + } + + /** + * Get the default locale + * + * @return string + */ + public function getDefaultLocale() + { + return $this->defaultLocale; + } + + /** + * Set the default locale + * + * @param string $defaultLocale + * + * @return $this + */ + public function setDefaultLocale($defaultLocale) + { + $this->defaultLocale = $defaultLocale; + + return $this; + } + + /** + * Get available translations + * + * @return array Available translations as array[$domain] => $directory + */ + public function getTranslationDirectories() + { + return $this->translationDirectories; + } + + /** + * Add a translation directory + * + * @param string $directory Path to translation files + * @param string $domain Optional domain of the translation + * + * @return $this + */ + public function addTranslationDirectory($directory, $domain = null) + { + $this->translationDirectories[$domain ?: $this->defaultDomain] = $directory; + + return $this; + } + + /** + * Get loaded translations + * + * @return array Loaded translations as array[$domain] => $directory + */ + public function getLoadedTranslations() + { + return $this->loadedTranslations; + } + + /** + * Load a translation so that gettext is able to locate its message catalogs + * + * {@link bindtextdomain()} is called internally for every domain and path + * that has been added with {@link addTranslationDirectory()}. + * + * @return $this + * @throws \Exception If {@link bindtextdomain()} fails for a domain + */ + public function loadTranslations() + { + foreach ($this->translationDirectories as $domain => $directory) { + if ( + isset($this->loadedTranslations[$domain]) + && $this->loadedTranslations[$domain] === $directory + ) { + continue; + } + + if (bindtextdomain($domain, $directory) !== $directory) { + throw new \Exception(sprintf( + "Can't register domain '%s' with path '%s'", + $domain, + $directory + )); + } + + bind_textdomain_codeset($domain, 'UTF-8'); + + $this->loadedTranslations[$domain] = $directory; + } + + return $this; + } + + /** + * Get the primary locale code used for translations + * + * @return string + */ + public function getLocale() + { + return $this->locale; + } + + /** + * Setup the primary locale code to use for translations + * + * Calls {@link loadTranslations()} internally. + * + * @param string $locale Locale code + * + * @return $this + * @throws \Exception If {@link bindtextdomain()} fails for a domain + */ + public function setLocale($locale) + { + putenv("LANGUAGE=$locale.UTF-8"); + setlocale(LC_ALL, $locale . '.UTF-8'); + + $this->loadTranslations(); + + textdomain($this->getDefaultDomain()); + + $this->locale = $locale; + + return $this; + } + + /** + * Encode a message with context to the representation used in .mo files + * + * @param string $message + * @param string $context + * + * @return string The encoded message as context + "\x04" + message + */ + public function encodeMessageWithContext($message, $context) + { + // The encoding of a context and a message in a .mo file is + // context + "\x04" + message (gettext version >= 0.15) + return "{$context}\x04{$message}"; + } + + public function translate($message, $context = null) + { + if ($context !== null) { + $messageForGettext = $this->encodeMessageWithContext($message, $context); + } else { + $messageForGettext = $message; + } + + $translation = gettext($messageForGettext); + + if ($translation === $messageForGettext) { + return $message; + } + + return $translation; + } + + public function translateInDomain($domain, $message, $context = null) + { + if ($context !== null) { + $messageForGettext = $this->encodeMessageWithContext($message, $context); + } else { + $messageForGettext = $message; + } + + $translation = dgettext( + $domain, + $messageForGettext + ); + + if ($translation === $messageForGettext) { + $translation = dgettext( + $this->getDefaultDomain(), + $messageForGettext + ); + } + + if ($translation === $messageForGettext) { + return $message; + } + + return $translation; + } + + public function translatePlural($singular, $plural, $number, $context = null) + { + if ($context !== null) { + $singularForGettext = $this->encodeMessageWithContext($singular, $context); + } else { + $singularForGettext = $singular; + } + + + $translation = ngettext( + $singularForGettext, + $plural, + $number + ); + + if ($translation === $singularForGettext) { + return $number === 1 ? $singular : $plural; + } + + return $translation; + } + + public function translatePluralInDomain($domain, $singular, $plural, $number, $context = null) + { + if ($context !== null) { + $singularForGettext = $this->encodeMessageWithContext($singular, $context); + } else { + $singularForGettext = $singular; + } + + $translation = dngettext( + $domain, + $singularForGettext, + $plural, + $number + ); + + $isSingular = $number === 1; + + if ($translation === ($isSingular ? $singularForGettext : $plural)) { + $translation = dngettext( + $this->getDefaultDomain(), + $singularForGettext, + $plural, + $number + ); + } + + if ($translation === $singularForGettext) { + return $isSingular ? $singular : $plural; + } + + return $translation; + } + + /** + * List available locales by traversing the translation directories from {@link addTranslationDirectory()} + * + * @return string[] Array of available locale codes + */ + public function listLocales() + { + $locales = []; + + foreach (array_unique($this->getTranslationDirectories()) as $directory) { + $fs = new FilesystemIterator( + $directory, + FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS + ); + + foreach ($fs as $file) { + if (! $file->isDir()) { + continue; + } + + $locales[] = $file->getBasename(); + } + } + + $locales = array_filter(array_unique($locales)); + + sort($locales); + + return $locales; + } +} diff --git a/vendor/ipl/i18n/src/Locale.php b/vendor/ipl/i18n/src/Locale.php new file mode 100644 index 0000000..d9e6b05 --- /dev/null +++ b/vendor/ipl/i18n/src/Locale.php @@ -0,0 +1,124 @@ +defaultLocale; + } + + /** + * Set the default locale + * + * @param string $defaultLocale + * + * @return $this + */ + public function setDefaultLocale($defaultLocale) + { + $this->defaultLocale = $defaultLocale; + + return $this; + } + + /** + * Return the preferred locale based on the given HTTP header and the available translations + * + * @param string $header The HTTP "Accept-Language" header + * @param array $available Available translations + * + * @return string The browser's preferred locale code + */ + public function getPreferred($header, array $available) + { + $headerValues = explode(',', $header); + for ($i = 0; $i < count($headerValues); $i++) { + // In order to accomplish a stable sort we need to take the original + // index into account as well during element comparison + $headerValues[$i] = [$headerValues[$i], $i]; + } + usort( // Sort DESC but keep equal elements ASC + $headerValues, + function ($a, $b) { + $tagA = explode(';', $a[0], 2); + $tagB = explode(';', $b[0], 2); + $qValA = (float) (strpos($a[0], ';') > 0 ? substr(array_pop($tagA), 2) : 1); + $qValB = (float) (strpos($b[0], ';') > 0 ? substr(array_pop($tagB), 2) : 1); + + return $qValA < $qValB ? 1 : ($qValA > $qValB ? -1 : ($a[1] > $b[1] ? 1 : ($a[1] < $b[1] ? -1 : 0))); + } + ); + for ($i = 0; $i < count($headerValues); $i++) { + // We need to reset the array to its original structure once it's sorted + $headerValues[$i] = $headerValues[$i][0]; + } + $requestedLocales = []; + foreach ($headerValues as $headerValue) { + if (strpos($headerValue, ';') > 0) { + $parts = explode(';', $headerValue, 2); + $headerValue = $parts[0]; + } + $requestedLocales[] = str_replace('-', '_', $headerValue); + } + $requestedLocales = array_combine( + array_map('strtolower', array_values($requestedLocales)), + array_values($requestedLocales) + ); + + $available[] = $this->defaultLocale; + $availableLocales = array_combine( + array_map('strtolower', array_values($available)), + array_values($available) + ); + + $similarMatch = null; + + foreach ($requestedLocales as $requestedLocaleLowered => $requestedLocale) { + $localeObj = $this->parseLocale($requestedLocaleLowered); + + if ( + isset($availableLocales[$requestedLocaleLowered]) + && (! $similarMatch || $this->parseLocale($similarMatch)->language === $localeObj->language) + ) { + // Prefer perfect match only if no similar match has been found yet or the perfect match is more precise + // than the similar match + return $availableLocales[$requestedLocaleLowered]; + } + + if (! $similarMatch) { + foreach ($availableLocales as $availableLocaleLowered => $availableLocale) { + if ($this->parseLocale($availableLocaleLowered)->language === $localeObj->language) { + $similarMatch = $availableLocaleLowered; + break; + } + } + } + } + + return $similarMatch ? $availableLocales[$similarMatch] : $this->defaultLocale; + } + + /** + * Parse a locale into its subtags + * + * Converts to output of {@link \Locale::parseLocale()} to an object and returns it. + * + * @param string $locale + * + * @return object Output of {@link \Locale::parseLocale()} converted to an object + */ + public function parseLocale($locale) + { + return (object) \Locale::parseLocale($locale); + } +} diff --git a/vendor/ipl/i18n/src/NoopTranslator.php b/vendor/ipl/i18n/src/NoopTranslator.php new file mode 100644 index 0000000..1f9aab2 --- /dev/null +++ b/vendor/ipl/i18n/src/NoopTranslator.php @@ -0,0 +1,31 @@ +translationDomain === null + ? StaticTranslator::$instance->translate($message, $context) + : StaticTranslator::$instance->translateInDomain($this->translationDomain, $message, $context); + } + + /** + * Translate a message in the given domain + * + * If no translation is found in the specified domain, the translation is also searched for in the default domain. + * + * @param string $domain + * @param string $message + * @param string $context Message context + * + * @return string Translated message or original message if no translation is found + */ + public function translateInDomain($domain, $message, $context = null) + { + return StaticTranslator::$instance->translateInDomain($domain, $message, $context); + } + + /** + * Translate a plural message + * + * The returned message is based on the given number to decide between the singular and plural forms. + * That is also the case if no translation is found. + * + * @param string $singular Singular message + * @param string $plural Plural message + * @param int $number Number to decide between the returned singular and plural forms + * @param string $context Message context + * + * @return string Translated message or original message if no translation is found + */ + public function translatePlural($singular, $plural, $number, $context = null) + { + return $this->translationDomain === null + ? StaticTranslator::$instance->translatePlural($singular, $plural, $number, $context) + : StaticTranslator::$instance->translatePluralInDomain( + $this->translationDomain, + $singular, + $plural, + $number ?? 0, + $context + ); + } + + /** + * Translate a plural message in the given domain + * + * If no translation is found in the specified domain, the translation is also searched for in the default domain. + * + * The returned message is based on the given number to decide between the singular and plural forms. + * That is also the case if no translation is found. + * + * @param string $domain + * @param string $singular Singular message + * @param string $plural Plural message + * @param int $number Number to decide between the returned singular and plural forms + * @param string $context Message context + * + * @return string Translated message or original message if no translation is found + */ + public function translatePluralInDomain($domain, $singular, $plural, $number, $context = null) + { + return StaticTranslator::$instance->translatePluralInDomain( + $domain, + $singular, + $plural, + $number ?? 0, + $context + ); + } +} diff --git a/vendor/ipl/i18n/src/functions.php b/vendor/ipl/i18n/src/functions.php new file mode 100644 index 0000000..74d58df --- /dev/null +++ b/vendor/ipl/i18n/src/functions.php @@ -0,0 +1,34 @@ +translate($message, $context); +} + +/** + * Translate a plural message + * + * The returned message is based on the given number to decide between the singular and plural forms. + * That is also the case if no translation is found. + * + * @param string $singular Singular message + * @param string $plural Plural message + * @param int $number Number to decide between the returned singular and plural forms + * @param string $context Message context + * + * @return string Translated message or original message if no translation is found + */ +function tp($singular, $plural, $number, $context = null) +{ + return StaticTranslator::$instance->translatePlural($singular, $plural, $number, $context); +} diff --git a/vendor/ipl/i18n/src/functions_include.php b/vendor/ipl/i18n/src/functions_include.php new file mode 100644 index 0000000..68f3806 --- /dev/null +++ b/vendor/ipl/i18n/src/functions_include.php @@ -0,0 +1,6 @@ +