pax_global_header00006660000000000000000000000064143116312250014510gustar00rootroot0000000000000052 comment=00222b5910999da60a52f1c8f97d7d7826f44e75 php-gettext-languages-2.9.0/000077500000000000000000000000001431163122500157355ustar00rootroot00000000000000php-gettext-languages-2.9.0/.github/000077500000000000000000000000001431163122500172755ustar00rootroot00000000000000php-gettext-languages-2.9.0/.github/FUNDING.yml000066400000000000000000000000641431163122500211120ustar00rootroot00000000000000github: mlocati custom: "https://paypal.me/mlocati" php-gettext-languages-2.9.0/.github/workflows/000077500000000000000000000000001431163122500213325ustar00rootroot00000000000000php-gettext-languages-2.9.0/.github/workflows/tests.yml000066400000000000000000000032371431163122500232240ustar00rootroot00000000000000name: Tests on: push: branches: - master pull_request: branches: - master jobs: php-coding-style: name: Check PHP coding style runs-on: ubuntu-latest steps: - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: "7.2" extensions: mbstring, zip tools: composer:2, php-cs-fixer:3 coverage: none - name: Checkout uses: actions/checkout@v2 - name: Check PHP coding style run: | php-cs-fixer fix --path-mode=intersection --config=./.php-cs-fixer.dist.php --dry-run --using-cache=no --diff --show-progress=dots --verbose --no-interaction --ansi . phpunit: name: Run PHPUnit tests needs: php-coding-style strategy: matrix: os: - ubuntu-latest php-version: - "5.3" - "5.4" - "5.5" - "5.6" - "7.0" - "7.1" - "7.2" - "7.3" - "7.4" - "8.0" - "8.1" include: - os: windows-latest php-version: "5.5" - os: windows-latest php-version: "7.4" runs-on: ${{ matrix.os }} steps: - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-version }} tools: composer:v2 coverage: none - name: Checkout uses: actions/checkout@v2 - name: Install Composer dependencies run: composer update --no-progress --no-suggest --optimize-autoloader --ansi --no-interaction - name: Run PHPUnit run: composer --no-interaction run-script test php-gettext-languages-2.9.0/.gitignore000066400000000000000000000001451431163122500177250ustar00rootroot00000000000000/.php-cs-fixer.cache /.php-cs-fixer.php /.phpunit.result.cache /composer.lock /tests/data.* /vendor/ php-gettext-languages-2.9.0/.php-cs-fixer.dist.php000066400000000000000000000523651431163122500220060ustar00rootroot00000000000000setCacheFile(__DIR__ . '/.php-cs-fixer.cache') ->setRiskyAllowed(true) ->setFinder((new PhpCsFixer\Finder()) ->exclude([ 'src/data', 'vendor', ]) ->append([ __DIR__ . '/bin/export-plural-rules', ]) ->in(__DIR__) ) ->setRules([ // Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one. 'align_multiline_comment' => true, // Each element of an array must be indented exactly once. 'array_indentation' => true, // Converts simple usages of `array_push($x, $y);` to `$x[] = $y;`. 'array_push' => true, // PHP arrays should be declared using the configured syntax. 'array_syntax' => ['syntax'=>'long'], // Converts backtick operators to `shell_exec` calls. 'backtick_to_shell_exec' => true, // Binary operators should be surrounded by space as configured. 'binary_operator_spaces' => true, // There MUST be one blank line after the namespace declaration. 'blank_line_after_namespace' => true, // Ensure there is no code on the same line as the PHP open tag and it is followed by a blank line. 'blank_line_after_opening_tag' => true, // The body of each structure MUST be enclosed by braces. Braces should be properly placed. Body of braces should be properly indented. 'braces' => ['allow_single_line_anonymous_class_with_empty_body'=>true,'allow_single_line_closure'=>true], // A single space or none should be between cast and variable. 'cast_spaces' => true, // Class, trait and interface elements must be separated with one or none blank line. 'class_attributes_separation' => true, // Whitespace around the keywords of a class, trait or interfaces definition should be one space. 'class_definition' => true, // Converts `::class` keywords to FQCN strings. 'class_keyword_remove' => true, // Namespace must not contain spacing, comments or PHPDoc. 'clean_namespace' => true, // Comments with annotation should be docblock when used on structural elements. 'comment_to_phpdoc' => true, // Remove extra spaces in a nullable typehint. 'compact_nullable_typehint' => true, // Concatenation should be spaced according configuration. 'concat_space' => ['spacing'=>'one'], // The PHP constants `true`, `false`, and `null` MUST be written using the correct casing. 'constant_case' => true, // Equal sign in declare statement should be surrounded by spaces or not following configuration. 'declare_equal_normalize' => true, // Replaces `dirname(__FILE__)` expression with equivalent `__DIR__` constant. 'dir_constant' => true, // Replaces short-echo ` true, // The keyword `elseif` should be used instead of `else if` so that all control keywords look like single words. 'elseif' => true, // PHP code MUST use only UTF-8 without BOM (remove BOM). 'encoding' => true, // Replace deprecated `ereg` regular expression functions with `preg`. 'ereg_to_preg' => true, // Converts implicit variables into explicit ones in double-quoted strings or heredoc syntax. 'explicit_string_variable' => true, // PHP code must use the long ` true, // Spaces should be properly placed in a function declaration. 'function_declaration' => true, // Replace core functions calls returning constants with the constants. 'function_to_constant' => ['functions'=>['get_class','get_class_this','php_sapi_name','phpversion','pi']], // Ensure single space between function's argument and its typehint. 'function_typehint_space' => true, // Imports or fully qualifies global classes/functions/constants. // WE WANT TO KEEP fully-qualified class names in phpdocs, but this fixer replaces in phpdocs too // 'global_namespace_import' => true, // Convert `heredoc` to `nowdoc` where possible. 'heredoc_to_nowdoc' => true, // Function `implode` must be called with 2 arguments in the documented order. 'implode_call' => true, // Include/Require and file path should be divided with a single space. File path should not be placed under brackets. 'include' => true, // Pre- or post-increment and decrement operators should be used if possible. 'increment_style' => ['style'=>'post'], // Code MUST use configured indentation type. 'indentation_type' => true, // Replaces `is_null($var)` expression with `null === $var`. 'is_null' => true, // Lambda must not import variables it doesn't use. 'lambda_not_used_import' => true, // All PHP files must use same line ending. 'line_ending' => true, // Ensure there is no code on the same line as the PHP open tag. 'linebreak_after_opening_tag' => true, // Use `&&` and `||` logical operators instead of `and` and `or`. 'logical_operators' => true, // Cast should be written in lower case. 'lowercase_cast' => true, // PHP keywords MUST be in lower case. 'lowercase_keywords' => true, // Class static references `self`, `static` and `parent` MUST be in lower case. 'lowercase_static_reference' => true, // Magic constants should be referred to using the correct casing. 'magic_constant_casing' => true, // Magic method definitions and calls must be using the correct casing. 'magic_method_casing' => true, // In method arguments and method call, there MUST NOT be a space before each comma and there MUST be one space after each comma. Argument lists MAY be split across multiple lines, where each subsequent line is indented once. When doing so, the first item in the list MUST be on the next line, and there MUST be only one argument per line. 'method_argument_space' => ['on_multiline'=>'ensure_fully_multiline'], // Replaces `intval`, `floatval`, `doubleval`, `strval` and `boolval` function calls with according type casting operator. 'modernize_types_casting' => true, // DocBlocks must start with two asterisks, multiline comments must start with a single asterisk, after the opening slash. Both must end with a single asterisk before the closing slash. 'multiline_comment_opening_closing' => true, // Forbid multi-line whitespace before the closing semicolon or move the semicolon to the new line for chained calls. 'multiline_whitespace_before_semicolons' => ['strategy'=>'new_line_for_chained_calls'], // Function defined by PHP should be called using the correct casing. 'native_function_casing' => true, // Native type hints for functions should use the correct case. 'native_function_type_declaration_casing' => true, // All instances created with new keyword must be followed by braces. 'new_with_braces' => true, // Master functions shall be used instead of aliases. 'no_alias_functions' => true, // Master language constructs shall be used instead of aliases. 'no_alias_language_construct_call' => true, // Replace control structure alternative syntax to use braces. 'no_alternative_syntax' => true, // There should not be a binary flag before strings. 'no_binary_string' => true, // There should be no empty lines after class opening brace. 'no_blank_lines_after_class_opening' => true, // There should not be blank lines between docblock and the documented element. 'no_blank_lines_after_phpdoc' => true, // There must be a comment when fall-through is intentional in a non-empty case body. 'no_break_comment' => true, // The closing `? >` tag MUST be omitted from files containing only PHP. 'no_closing_tag' => true, // There should not be any empty comments. 'no_empty_comment' => true, // There should not be empty PHPDoc blocks. 'no_empty_phpdoc' => true, // Remove useless (semicolon) statements. 'no_empty_statement' => true, // Removes extra blank lines and/or blank lines following configuration. 'no_extra_blank_lines' => true, // Replace accidental usage of homoglyphs (non ascii characters) in names. 'no_homoglyph_names' => true, // Remove leading slashes in `use` clauses. 'no_leading_import_slash' => true, // The namespace declaration line shouldn't contain leading whitespace. 'no_leading_namespace_whitespace' => true, // Either language construct `print` or `echo` should be used. 'no_mixed_echo_print' => ['use'=>'echo'], // Operator `=>` should not be surrounded by multi-line whitespaces. 'no_multiline_whitespace_around_double_arrow' => true, // Properties MUST not be explicitly initialized with `null` except when they have a type declaration (PHP 7.4). 'no_null_property_initialization' => true, // Convert PHP4-style constructors to `__construct`. 'no_php4_constructor' => true, // Short cast `bool` using double exclamation mark should not be used. 'no_short_bool_cast' => true, // Single-line whitespace before closing semicolon are prohibited. 'no_singleline_whitespace_before_semicolons' => true, // When making a method or function call, there MUST NOT be a space between the method or function name and the opening parenthesis. 'no_spaces_after_function_name' => true, // There MUST NOT be spaces around offset braces. 'no_spaces_around_offset' => true, // There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis. 'no_spaces_inside_parenthesis' => true, // Replaces superfluous `elseif` with `if`. 'no_superfluous_elseif' => true, // Removes `@param`, `@return` and `@var` tags that don't provide any useful information. 'no_superfluous_phpdoc_tags' => true, // Remove trailing commas in list function calls. 'no_trailing_comma_in_list_call' => true, // PHP single-line arrays should not have trailing comma. 'no_trailing_comma_in_singleline_array' => true, // Remove trailing whitespace at the end of non-blank lines. 'no_trailing_whitespace' => true, // There MUST be no trailing spaces inside comment or PHPDoc. 'no_trailing_whitespace_in_comment' => true, // Removes unneeded parentheses around control statements. 'no_unneeded_control_parentheses' => true, // Removes unneeded curly braces that are superfluous and aren't part of a control structure's body. 'no_unneeded_curly_braces' => true, // In function arguments there must not be arguments with default values before non-default ones. 'no_unreachable_default_argument_value' => true, // Unused `use` statements must be removed. 'no_unused_imports' => true, // There should not be useless `else` cases. 'no_useless_else' => true, // There should not be an empty `return` statement at the end of a function. 'no_useless_return' => true, // There must be no `sprintf` calls with only the first argument. 'no_useless_sprintf' => true, // In array declaration, there MUST NOT be a whitespace before each comma. 'no_whitespace_before_comma_in_array' => true, // Remove trailing whitespace at the end of blank lines. 'no_whitespace_in_blank_line' => true, // Remove Zero-width space (ZWSP), Non-breaking space (NBSP) and other invisible unicode symbols. 'non_printable_character' => true, // Array index should always be written by using square braces. 'normalize_index_brace' => true, // Adds or removes `?` before type declarations for parameters with a default `null` value. 'nullable_type_declaration_for_default_null_value' => ['use_nullable_type_declaration'=>false], // There should not be space before or after object operators `->` and `?->`. 'object_operator_without_whitespace' => true, // Operators - when multiline - must always be at the beginning or at the end of the line. 'operator_linebreak' => true, // Orders the elements of classes/interfaces/traits. 'ordered_class_elements' => true, // Ordering `use` statements. 'ordered_imports' => true, // Orders the interfaces in an `implements` or `interface extends` clause. 'ordered_interfaces' => true, // Trait `use` statements must be sorted alphabetically. 'ordered_traits' => true, // PHPDoc should contain `@param` for all params. 'phpdoc_add_missing_param_annotation' => true, // All items of the given phpdoc tags must be either left-aligned or (by default) aligned vertically. 'phpdoc_align' => ['align'=>'left'], // PHPDoc annotation descriptions should not be a sentence. 'phpdoc_annotation_without_dot' => true, // Docblocks should have the same indentation as the documented subject. 'phpdoc_indent' => true, // Changes doc blocks from single to multi line, or reversed. Works for class constants, properties and methods only. 'phpdoc_line_span' => true, // `@access` annotations should be omitted from PHPDoc. 'phpdoc_no_access' => true, // No alias PHPDoc tags should be used. 'phpdoc_no_alias_tag' => true, // `@return void` and `@return null` annotations should be omitted from PHPDoc. 'phpdoc_no_empty_return' => true, // `@package` and `@subpackage` annotations should be omitted from PHPDoc. 'phpdoc_no_package' => true, // Classy that does not inherit must not have `@inheritdoc` tags. 'phpdoc_no_useless_inheritdoc' => true, // Annotations in PHPDoc should be ordered so that `@param` annotations come first, then `@throws` annotations, then `@return` annotations. 'phpdoc_order' => true, // The type of `@return` annotations of methods returning a reference to itself must the configured one. 'phpdoc_return_self_reference' => true, // Scalar types should always be written in the same form. `int` not `integer`, `bool` not `boolean`, `float` not `real` or `double`. 'phpdoc_scalar' => true, // Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other, and annotations of a different type are separated by a single blank line. 'phpdoc_separation' => true, // PHPDoc summary should end in either a full stop, exclamation mark, or question mark. 'phpdoc_summary' => true, // Docblocks should only be used on structural elements. 'phpdoc_to_comment' => true, // PHPDoc should start and end with content, excluding the very first and last line of the docblocks. 'phpdoc_trim' => true, // Removes extra blank lines after summary and after description in PHPDoc. 'phpdoc_trim_consecutive_blank_line_separation' => true, // The correct case must be used for standard PHP types in PHPDoc. 'phpdoc_types' => true, // Sorts PHPDoc types. 'phpdoc_types_order' => ['null_adjustment'=>'always_last','sort_algorithm'=>'none'], // `@var` and `@type` annotations must have type and name in the correct order. 'phpdoc_var_annotation_correct_order' => true, // `@var` and `@type` annotations of classy properties should not contain the name. 'phpdoc_var_without_name' => true, // Classes must be in a path that matches their namespace, be at least one namespace deep and the class name should match the file name. 'psr_autoloading' => true, // Replaces `rand`, `srand`, `getrandmax` functions calls with their `mt_*` analogs. 'random_api_migration' => true, // Local, dynamic and directly referenced variables should not be assigned and directly returned by a function or method. 'return_assignment' => true, // There should be one or no space before colon, and one space after it in return type declarations, according to configuration. 'return_type_declaration' => true, // Inside class or interface element `self` should be preferred to the class name itself. // DOES NOT WORK WITH PHP 5.3 WHEN USED INSIDE CLOSURES // 'self_accessor' => true, // Cast shall be used, not `settype`. 'set_type_to_cast' => true, // Cast `(boolean)` and `(integer)` should be written as `(bool)` and `(int)`, `(double)` and `(real)` as `(float)`, `(binary)` as `(string)`. 'short_scalar_cast' => true, // Converts explicit variables in double-quoted strings and heredoc syntax from simple to complex format (`${` to `{$`). 'simple_to_complex_string_variable' => true, // Simplify `if` control structures that return the boolean result of their condition. 'simplified_if_return' => true, // A PHP file without end tag must always end with a single empty line feed. 'single_blank_line_at_eof' => true, // There should be exactly one blank line before a namespace declaration. 'single_blank_line_before_namespace' => true, // There MUST NOT be more than one property or constant declared per statement. 'single_class_element_per_statement' => true, // There MUST be one use keyword per declaration. 'single_import_per_statement' => true, // Each namespace use MUST go on its own line and there MUST be one blank line after the use statements block. 'single_line_after_imports' => true, // Single-line comments and multi-line comments with only one line of actual content should use the `//` syntax. 'single_line_comment_style' => true, // Convert double quotes to single quotes for simple strings. 'single_quote' => true, // Ensures a single space after language constructs. 'single_space_after_construct' => true, // Each trait `use` must be done as single statement. 'single_trait_insert_per_statement' => true, // Fix whitespace after a semicolon. 'space_after_semicolon' => ['remove_in_empty_for_expressions'=>false], // Increment and decrement operators should be used if possible. 'standardize_increment' => true, // Replace all `<>` with `!=`. 'standardize_not_equals' => true, // Lambdas not (indirect) referencing `$this` must be declared `static`. // DOES NOT WORK WITH PHP 5.3 // 'static_lambda' => true, // All multi-line strings must use correct line ending. 'string_line_ending' => true, // A case should be followed by a colon and not a semicolon. 'switch_case_semicolon_to_colon' => true, // Removes extra spaces between colon and case value. 'switch_case_space' => true, // Switch case must not be ended with `continue` but with `break`. 'switch_continue_to_break' => true, // Standardize spaces around ternary operator. 'ternary_operator_spaces' => true, // Multi-line arrays, arguments list and parameters list must have a trailing comma. 'trailing_comma_in_multiline' => ['elements'=>['arrays']], // Arrays should be formatted like function/method arguments, without leading or trailing single line space. 'trim_array_spaces' => true, // Unary operators should be placed adjacent to their operands. 'unary_operator_spaces' => true, // Visibility MUST be declared on all properties and methods; `abstract` and `final` MUST be declared before the visibility; `static` MUST be declared after the visibility. 'visibility_required' => ['elements'=>['method','property']], // In array declaration, there MUST be a whitespace after each comma. 'whitespace_after_comma_in_array' => true, // Write conditions in Yoda style (`true`), non-Yoda style (`['equal' => false, 'identical' => false, 'less_and_greater' => false]`) or ignore those conditions (`null`) based on configuration. 'yoda_style' => ['always_move_variable'=>true,'equal'=>false,'identical'=>false,'less_and_greater'=>false], ]) ; php-gettext-languages-2.9.0/LICENSE000066400000000000000000000020721431163122500167430ustar00rootroot00000000000000The 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. php-gettext-languages-2.9.0/README.md000066400000000000000000000246331431163122500172240ustar00rootroot00000000000000[![Tests](https://github.com/php-gettext/Languages/actions/workflows/tests.yml/badge.svg)](https://github.com/php-gettext/Languages/actions/workflows/tests.yml) # gettext language list automatically generated from CLDR data ## Static usage To use the languages data generated from this tool you can use the `bin/export-plural-rules` command. #### Export command line options `export-plural-rules` supports the following options: - `--us-ascii` If specified, the output will contain only US-ASCII characters. If not specified, the output charset is UTF-8. - `--languages=[,,...]]` `--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`). If this option is not specified, the result will contain all the available languages. - `--reduce=yes|no` 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=yes|no` 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. #### Export formats `export-plural-rules` can generate data in the following formats: - `json`: compressed JSON data ```bash export-plural-rules json ``` - `prettyjson`: uncompressed JSON data ```bash export-plural-rules prettyjson ``` - `html`: html table ([see the result](https://php-gettext.github.io/Languages/)) ```bash export-plural-rules html ``` - `php`: build a php file that can be included ```bash export-plural-rules --output=yourfile.php php ``` Then you can use that generated file in your php scripts: ```php $languages = include 'yourfile.php'; ``` - `ruby`: build a ruby file that can be included ```bash export-plural-rules --parenthesis=no --output=yourfile.rb ruby ``` Then you can use that generated file in your ruby scripts: ```ruby require './yourfile.rb' PLURAL_RULES['en'] ``` - `xml`: generate an XML document ([here you can find the xsd XML schema](https://php-gettext.github.io/Languages/GettextLanguages.xsd)) ```bash export-plural-rules xml ``` - `po`: generate the gettext .po headers for a single language ```bash export-plural-rules po --language=YourLanguageCode ``` ## Dynamic usage #### With Composer You can use [Composer](https://getcomposer.org/) to include this tool in your project. Simply launch `composer require gettext/languages` or add `"gettext/languages": "*"` to the `"require"` section of your `composer.json` file. #### Without Composer If you don't use composer in your project, you can download this package in a directory of your project and include the autoloader file: ```php require_once 'path/to/src/autoloader.php'; ``` #### Main methods The most useful functions of this tools are the following ```php $allLanguages = Gettext\Languages\Language::getAll(); ... $oneLanguage = Gettext\Languages\Language::getById('en_US'); ... ``` `getAll` returns a list of `Gettext\Languages\Language` instances, `getById` returns a single `Gettext\Languages\Language` instance (or `null` if the specified language identifier is not valid). The main properties of the `Gettext\Languages\Language` instances are: - `id`: the normalized language ID (for instance `en_US`) - `name`: the language name (for instance `American English` for `en_US`) - `supersededBy`: the code of a language that supersedes this language code (for instance, `jw` is superseded by `jv` to represent the Javanese language) - `script`: the script name (for instance, for `zh_Hans` - `Simplified Chinese` - the script is `Simplified Han`) - `territory`: the name of the territory (for instance `United States` for `en_US`) - `baseLanguage`: the name of the base language (for instance `English` for `en_US`) - `formula`: the [gettext formula](https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html) to distinguish between different plural rules. For instance `n != 1` - `categories`: the plural cases applicable for this language. It's an array of `Gettext\Languages\Category` instances. Each instance has these properties: - `id`: can be (in this order) one of `zero`, `one`, `two`, `few`, `many` or `other`. The `other` case is always present. - `examples`: a representation of some values for which this plural case is valid (examples are simple numbers like `1` or complex ranges like `0, 2~16, 100, 1000, 10000, 100000, 1000000, …`) ## Is this data correct? Yes - as far as you trust the [Unicode CLDR](http://cldr.unicode.org) project. The conversion from CLDR to gettext includes also [a lot of tests](https://travis-ci.org/php-gettext/Languages) to check the results. And all passes :wink:. ## Reference #### CLDR The [CLDR specifications](https://unicode.org/reports/tr35/tr35-numbers.html#Language_Plural_Rules) define the following variables to be used in the CLDR plural formulas: - `n`: absolute value of the source number (integer and decimals) (eg: `9.870` => `9.87`) - `i`: integer digits of n (eg: `9.870` => `9`) - `v`: number of visible fraction digits in n, with trailing zeros (eg: `9.870` => `3`) - `w`: number of visible fraction digits in n, without trailing zeros (eg: `9.870` => `2`) - `f`: visible fractional digits in n, with trailing zeros (eg: `9.870` => `870`) - `t`: visible fractional digits in n, without trailing zeros (eg: `9.870` => `87`) - `c`: exponent of the power of 10 used in compact decimal formatting (eg: `98c7` => `7`) - `e`: synonym for `c` #### gettext The [gettext specifications](https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/Plural-forms.html) define the following variables to be used in the gettext plural formulas: - `n`: unsigned long int ### Conversion CLDR > gettext | CLDR variable | gettext equivalent | |---------------|--------------------| | `n` | `n` | | `i` | `n` | | `v` | `0` | | `w` | `0` | | `f` | *empty* | | `t` | *empty* | | `c` | *empty* | | `e` | *empty* | ## Parenthesis in ternary operators The generated gettext formulas contain some extra parenthesis, in order to avoid problems in some programming language. For instance, let's assume we have this formula: `(0 == 0) ? 0 : (0 == 1) ? 1 : 2` - [in C it evaluates to `0`](http://codepad.org/Epw5WkmJ) since is the same as `(0 == 0) ? 0 : ((0 == 1) ? 1 : 2)` - [in Java it evaluates to `0`](https://ideone.com/vbRHjW) since is the same as `(0 == 0) ? 0 : ((0 == 1) ? 1 : 2)` - [in JavaScript it evaluates to `0`](https://jsfiddle.net/7fnxa599/) since is the same as `(0 == 0) ? 0 : ((0 == 1) ? 1 : 2)` - [in PHP it evaluates to `2`](https://3v4l.org/QAAnA) since is the same as `((0 == 0) ? 0 : (0 == 1)) ? 1 : 2` So, in order to avoid problems, instead of a simple `a ? 0 : b ? 1 : 2` the resulting formulas will be in this format: `a ? 0 : (b ? 1 : 2)` ## Contributing ### Generating the CLDR data This repository uses the CLDR data, including American English (`en_US`) json files. In order to generate this data, you can use Docker. Start a new Docker container by running ```sh docker run --rm -it -v path/to/src/cldr-data:/output alpine:3.13 sh ``` Then run the following script, setting the values of the variables accordingly to your needs: ```sh # The value of the CLDR version (eg 39, 38.1, ...) CLDR_VERSION=39 # Your GitHub username (required since CLDR 38) - see http://cldr.unicode.org/development/maven#TOC-Introduction GITHUB_USERNAME= # Your GitHub personal access token (required since CLDR 38) - see http://cldr.unicode.org/development/maven#TOC-Introduction GITHUB_TOKEN= if ! test -d /output; then echo 'Missing output directory' >&2 return 1 fi apk -U upgrade apk add --no-cache git git-lfs openjdk8 apache-ant maven CLDR_MAJORVERSION="$(printf '%s' "$CLDR_VERSION" | sed -E 's/^([0-9]+).*/\1/')" SOURCE_DIR="$(mktemp -d)" DESTINATION_DIR="$(mktemp -d)" git clone --single-branch --depth=1 "--branch=release-$(printf '%s' "$CLDR_VERSION" | tr '.' '-')" https://github.com/unicode-org/cldr.git "$SOURCE_DIR" if test $CLDR_MAJORVERSION -lt 38; then git -C "$SOURCE_DIR" lfs pull --include tools/java || true ant -f "$SOURCE_DIR/tools/java/build.xml" jar JARFILE="$SOURCE_DIR/tools/java/cldr.jar" DESTINATION_DIR_LOCALE="$DESTINATION_DIR/en_US" DESTINATION_FILE_PLURALS="$DESTINATION_DIR/supplemental/plurals.json" else if test -z "${GITHUB_USERNAME:-}"; then echo 'GITHUB_USERNAME is missing' >&2 return 1 fi if test -z "${GITHUB_TOKEN:-}"; then echo 'GITHUB_TOKEN is missing' >&2 return 1 fi printf 'githubicu%s%s' "$GITHUB_USERNAME" "$GITHUB_TOKEN" > "$SOURCE_DIR/mvn-settings.xml" mvn --settings "$SOURCE_DIR/mvn-settings.xml" package -DskipTests=true --file "$SOURCE_DIR/tools/cldr-code/pom.xml" JARFILE="$SOURCE_DIR//tools/cldr-code/target/cldr-code.jar" DESTINATION_DIR_LOCALE="$DESTINATION_DIR" DESTINATION_FILE_PLURALS="$DESTINATION_DIR/supplemental/plurals/plurals.json" fi java -Duser.language=en -Duser.country=US "-DCLDR_DIR=$SOURCE_DIR" "-DCLDR_GEN_DIR=$DESTINATION_DIR_LOCALE" -jar "$JARFILE" ldml2json -t main -r true -s contributed -m en_US java -Duser.language=en -Duser.country=US "-DCLDR_DIR=$SOURCE_DIR" "-DCLDR_GEN_DIR=$DESTINATION_DIR/supplemental" -jar "$JARFILE" ldml2json -s contributed -o true -t supplemental mkdir -p /output/main/en-US cp $DESTINATION_DIR/en_US/languages.json /output/main/en-US/ cp $DESTINATION_DIR/en_US/scripts.json /output/main/en-US/ cp $DESTINATION_DIR/en_US/territories.json /output/main/en-US/ mkdir -p /output/supplemental cp "$DESTINATION_FILE_PLURALS" /output/supplemental/ ``` ## Support this project You can offer me a [monthy coffee](https://github.com/sponsors/mlocati) or a [one-time coffee](https://paypal.me/mlocati) :wink: php-gettext-languages-2.9.0/UNICODE-LICENSE.txt000066400000000000000000000043431431163122500206500ustar00rootroot00000000000000UNICODE, 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. php-gettext-languages-2.9.0/bin/000077500000000000000000000000001431163122500165055ustar00rootroot00000000000000php-gettext-languages-2.9.0/bin/export-plural-rules000077500000000000000000000234011431163122500224010ustar00rootroot00000000000000#!/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); php-gettext-languages-2.9.0/bin/export-plural-rules.bat000066400000000000000000000000201431163122500231330ustar00rootroot00000000000000@php "%~dpn0" %*php-gettext-languages-2.9.0/composer.json000066400000000000000000000021101431163122500204510ustar00rootroot00000000000000{ "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" ] }php-gettext-languages-2.9.0/phpunit.xml000066400000000000000000000005211431163122500201440ustar00rootroot00000000000000 ./tests/test/ php-gettext-languages-2.9.0/src/000077500000000000000000000000001431163122500165245ustar00rootroot00000000000000php-gettext-languages-2.9.0/src/Category.php000066400000000000000000000103211431163122500210070ustar00rootroot00000000000000id = $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; } } php-gettext-languages-2.9.0/src/CldrData.php000066400000000000000000000331151431163122500207160ustar00rootroot00000000000000
     * "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]; } } php-gettext-languages-2.9.0/src/Exporter/000077500000000000000000000000001431163122500203345ustar00rootroot00000000000000php-gettext-languages-2.9.0/src/Exporter/Docs.php000066400000000000000000000035751431163122500217470ustar00rootroot00000000000000 gettext plural rules - built from CLDR
EOT; $result .= static::buildTable($languages, true); $result .= <<<'EOT'
EOT; return $result; } } php-gettext-languages-2.9.0/src/Exporter/Exporter.php000066400000000000000000000104161431163122500226570ustar00rootroot00000000000000 $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__); } } php-gettext-languages-2.9.0/src/Exporter/Html.php000066400000000000000000000050201431163122500217460ustar00rootroot00000000000000'; $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); } } php-gettext-languages-2.9.0/src/Exporter/Json.php000066400000000000000000000037401431163122500217620ustar00rootroot00000000000000name; 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()); } } php-gettext-languages-2.9.0/src/Exporter/Php.php000066400000000000000000000040321431163122500215730ustar00rootroot00000000000000id . '\' => 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); } } php-gettext-languages-2.9.0/src/Exporter/Po.php000066400000000000000000000016721431163122500214310ustar00rootroot00000000000000id . '\n"'; $lines[] = '"Plural-Forms: nplurals=' . count($language->categories) . '; plural=' . $language->formula . '\n"'; $lines[] = ''; return implode("\n", $lines); } } php-gettext-languages-2.9.0/src/Exporter/Prettyjson.php000066400000000000000000000015041431163122500232260ustar00rootroot00000000000000id . '\' => {'; $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); } } php-gettext-languages-2.9.0/src/Exporter/Xml.php000066400000000000000000000043751431163122500216160ustar00rootroot00000000000000loadXML(''); $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(); } } php-gettext-languages-2.9.0/src/FormulaConverter.php000066400000000000000000000156621431163122500225440ustar00rootroot00000000000000 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}'"); } } php-gettext-languages-2.9.0/src/Language.php000066400000000000000000000373651431163122500207760ustar00rootroot00000000000000id = $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', '…' => '...', 'ʼ' => "'", '’' => "'", )); } } } php-gettext-languages-2.9.0/src/autoloader.php000066400000000000000000000005321431163122500213740ustar00rootroot00000000000000isGoingToThrowException('\Exception'); new Category('invalid-cldr-category', 'i = 1 and v = 0 @integer 1'); } public function testConstructorOnCldrIdIsNotInList() { $this->isGoingToThrowException('\Exception'); new Category('pluralRule-count-10000000', 'i = 1 and v = 0 @integer 1'); } public function testConstructorWithInvalidCldrRule() { $this->isGoingToThrowException('\Exception'); new Category('pluralRule-count-one', 'invalid category rule'); } public function testGetExampleIntegers() { $category = new Category('pluralRule-count-one', 'i = 1 and v = 0 @integer 1'); $this->assertSame(array(1), $category->getExampleIntegers()); } } php-gettext-languages-2.9.0/tests/test/ExecutableFilesTest.php000066400000000000000000000024561431163122500245020ustar00rootroot00000000000000markTestSkipped('Testing executable files requires a Posix environment'); } $expected = array( 'bin/export-plural-rules', ); $actual = $this->listExecutableFiles(); $this->assertSame($expected, $actual); } /** * @return string[] */ private function listExecutableFiles() { $rc = -1; $output = array(); exec('find ' . escapeshellarg(GETTEXT_LANGUAGES_TESTROOTDIR) . ' -type f -executable 2>&1', $output, $rc); if ($rc !== 0) { $this->markTestSkipped('Failed to retrieve the list of executable files (' . trim(implode("\n", $output)) . ')'); } $result = array_map( function ($file) { return substr($file, strlen(GETTEXT_LANGUAGES_TESTROOTDIR) + 1); }, $output ); $result = array_filter( $result, function ($file) { return $file !== '' && strpos($file, '.git/') !== 0 && strpos($file, 'vendor/') !== 0; } ); sort($result); return $result; } } php-gettext-languages-2.9.0/tests/test/FormulaConverterTest.php000066400000000000000000000007521431163122500247300ustar00rootroot00000000000000isGoingToThrowException('\Exception'); FormulaConverter::convertFormula('()'); } public function testConvertAtomWithInvalidFormulaChunk() { $this->isGoingToThrowException('\Exception'); FormulaConverter::convertFormula('f ==== empty'); } } php-gettext-languages-2.9.0/tests/test/GetTest.php000066400000000000000000000060701431163122500221510ustar00rootroot00000000000000assertGreaterThan(100, $count, 'The number of all languages is too small'); $this->assertLessThan(10000, $count, 'The number of all languages is too big'); } public function testGetById() { $this->assertNull(Language::getById('root'), 'The root language is found!'); $language = Language::getById('it'); $this->assertNotNull($language, "The language 'it' has not been found"); $this->assertInstanceOf('Gettext\Languages\Language', $language); $this->assertSame('Italian', $language->name); $this->assertNull($language->territory); $language = Language::getById('it-IT'); $this->assertNotNull($language, "The language 'it-IT' has not been found"); $this->assertSame('it_IT', $language->id); $this->assertSame('Italian (Italy)', $language->name); $this->assertSame('Italy', $language->territory); $language = Language::getById('it_IT'); $this->assertNotNull($language, "The language 'it_IT' has not been found"); $this->assertSame('it_IT', $language->id); $this->assertSame('Italian (Italy)', $language->name); $language1 = Language::getById('nl_BE'); $this->assertNotNull($language1, "The language 'nl_BE' has not been found"); $language2 = Language::getById('nl'); $this->assertNotNull($language2, "The language 'nl' has not been found"); $this->assertSame($language1->baseLanguage, $language2->name); $language = Language::getById('it'); $this->assertNull($language->script); $language = Language::getById('it_Xxxxx'); $this->assertNull($language); $language = Language::getById('it_Latn'); $this->assertNotNull($language); $this->assertNotNull($language->script); } public function testPortuguese() { $pt = Language::getById('pt'); $this->assertSame('Portuguese', $pt->name); $this->assertCount(3, $pt->categories); $this->assertSame('one', $pt->categories[0]->id); $ptPT = Language::getById('pt-PT'); $this->assertSame('European Portuguese', $ptPT->name); $this->assertCount(3, $ptPT->categories); $this->assertSame('one', $ptPT->categories[0]->id); $ptBR = Language::getById('pt-BR'); $this->assertSame('Brazilian Portuguese', $ptBR->name); $this->assertCount(3, $ptBR->categories); $this->assertSame('one', $ptBR->categories[0]->id); $ptCV = Language::getById('pt-CV'); $this->assertSame('Portuguese (Cape Verde)', $ptCV->name); $this->assertCount(3, $ptCV->categories); $this->assertSame('one', $ptCV->categories[0]->id); $this->assertSame($pt->formula, $ptBR->formula); $this->assertNotSame($pt->formula, $ptPT->formula); $this->assertSame($ptBR->formula, $ptCV->formula); } } php-gettext-languages-2.9.0/tests/test/RulesTest.php000066400000000000000000000070311431163122500225220ustar00rootroot00000000000000readData($format) as $locale => $info) { foreach ($info['examples'] as $rule => $numbers) { $testData[] = array( $format, $locale, $info['formula'], $info['cases'], $numbers, $rule, ); } } } return $testData; } /** * @dataProvider providerTestRules */ public function testRules($format, $locale, $formula, $allCases, $numbers, $expectedCase) { $expectedCaseIndex = in_array($expectedCase, $allCases); foreach (Category::expandExamples($numbers) as $number) { $numericFormula = preg_replace('/\bn\b/', (string) $number, $formula); $extraneousChars = preg_replace('/^[\d %!=<>&\|()?:]+$/', '', $numericFormula); $this->assertSame('', $extraneousChars, "The formula '{$numericFormula}' contains extraneous characters: '{$extraneousChars}' (format: {$format})"); $caseIndex = @eval("return (({$numericFormula}) === true) ? 1 : ((({$numericFormula}) === false) ? 0 : ({$numericFormula}));"); $caseIndexType = gettype($caseIndex); $this->assertSame('integer', $caseIndexType, "Error evaluating the numeric formula '{$numericFormula}' (format: {$format})"); $this->assertArrayHasKey($caseIndex, $allCases, "The formula '{$formula}' evaluated for {$number} gave an out-of-range case index ({$caseIndex}) (format: {$format})"); $case = $allCases[$caseIndex]; $this->assertSame($expectedCase, $case, "The formula '{$formula}' evaluated for {$number} resulted in '{$case}' ({$caseIndex}) instead of '{$expectedCase}' ({$expectedCaseIndex}) (format: {$format})"); } } public function providerTestExamplesExist() { $testData = array(); foreach (array('php', 'json') as $format) { foreach ($this->readData($format) as $locale => $info) { foreach ($info['cases'] as $case) { $testData[] = array( $format, $locale, $case, $info['examples'], ); } } } return $testData; } /** * @dataProvider providerTestExamplesExist */ public function testExamplesExist($format, $locale, $case, $examples) { $this->assertArrayHasKey($case, $examples, "The language '{$locale}' does not have tests for the case '{$case}' (format: {$format})"); } private function readData($format) { static $data = array(); if (!isset($data[$format])) { $filename = GETTEXT_LANGUAGES_TESTDIR . '/data.' . $format; switch ($format) { case 'php': $data[$format] = require $filename; break; case 'json': $data[$format] = json_decode(file_get_contents($filename), true); break; default: throw new Exception("Unhandled format: {$format}"); } } return $data[$format]; } } php-gettext-languages-2.9.0/tests/test/TestCase.php000066400000000000000000000011321431163122500222770ustar00rootroot00000000000000expectException($class); if ($message !== null) { $this->expectExceptionMessage($message); } } else { $this->setExpectedException($class, $message); } } } php-gettext-languages-2.9.0/tests/test/USAsciiTest.php000066400000000000000000000022261431163122500227310ustar00rootroot00000000000000getExportedPhpArray(); foreach ($array as $localeID => $localeData) { $this->assertUSAscii($localeID, $localeData); } } /** * @param string $key */ private function assertUSAscii($key, $value) { switch (gettype($value)) { case 'string': $this->assertSame(1, preg_match('/^[\x20-\x7F\n]*$/s', $value), "The string at {$key} does not contain only US-ASCII characters: {$value}"); break; case 'array': foreach ($value as $valueKey => $valueValue) { $this->assertUSAscii("{$key}.{$valueKey}", $valueValue); } break; } } /** * @return array */ private function getExportedPhpArray() { $phpCode = Php::toString(Language::getAll(), array('us-ascii' => true)); return eval(preg_replace('/^<\?php\n/', '', $phpCode)); } }