[
  {
    "path": ".doctrine-project.json",
    "content": "{\n    \"active\": true,\n    \"name\": \"Inflector\",\n    \"slug\": \"inflector\",\n    \"docsSlug\": \"doctrine-inflector\",\n    \"versions\": [\n        {\n            \"name\": \"2.2\",\n            \"branchName\": \"2.2.x\",\n            \"slug\": \"2.2\",\n            \"upcoming\": true\n        },\n        {\n            \"name\": \"2.1\",\n            \"branchName\": \"2.1.x\",\n            \"slug\": \"2.1\",\n            \"current\": true\n        },\n        {\n            \"name\": \"2.0\",\n            \"slug\": \"2.0\",\n            \"maintained\": false\n        },\n        {\n            \"name\": \"1.4\",\n            \"slug\": \"1.4\",\n            \"maintained\": false\n        },\n        {\n            \"name\": \"1.3\",\n            \"branchName\": \"1.3.x\",\n            \"slug\": \"1.3\",\n            \"maintained\": false\n        },\n        {\n            \"name\": \"1.2\",\n            \"branchName\": \"1.2.x\",\n            \"slug\": \"1.2\",\n            \"maintained\": false\n        },\n        {\n            \"name\": \"1.1\",\n            \"branchName\": \"1.1.x\",\n            \"slug\": \"1.1\",\n            \"maintained\": false\n        },\n        {\n            \"name\": \"1.0\",\n            \"branchName\": \"1.0.x\",\n            \"slug\": \"1.0\",\n            \"maintained\": false\n        }\n    ]\n}\n"
  },
  {
    "path": ".gitattributes",
    "content": "/tests export-ignore\n.* export-ignore\nphpunit.xml.dist export-ignore\nphpcs.xml.dist export-ignore\ncomposer.lock export-ignore\nphpstan.neon.dist export-ignore\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "patreon: phpdoctrine\ntidelift: packagist/doctrine%2Finflector\ncustom: https://www.doctrine-project.org/sponsorship.html\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    labels:\n      - \"CI\"\n"
  },
  {
    "path": ".github/workflows/coding-standards.yml",
    "content": "\nname: \"Coding Standards\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n  push:\n    branches:\n      - \"*.x\"\n\njobs:\n  coding-standards:\n    name: \"Coding Standards\"\n    uses: \"doctrine/.github/.github/workflows/coding-standards.yml@13.1.0\"\n"
  },
  {
    "path": ".github/workflows/composer-lint.yml",
    "content": "name: \"Composer Lint\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n    paths:\n      - \"composer.json\"\n  push:\n    branches:\n      - \"*.x\"\n    paths:\n      - \"composer.json\"\n\njobs:\n  composer-lint:\n    name: \"Composer Lint\"\n    uses: \"doctrine/.github/.github/workflows/composer-lint.yml@13.1.0\"\n"
  },
  {
    "path": ".github/workflows/continuous-integration.yml",
    "content": "\nname: \"Continuous Integration\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n  push:\n    branches:\n      - \"*.x\"\n\njobs:\n  roave_bc_check:\n    name: \"Roave BC Check\"\n    runs-on: ubuntu-latest\n    steps:\n      - uses: \"actions/checkout@v6\"\n        with:\n          fetch-depth: 0\n\n      - name: Install PHP with extensions.\n        uses: shivammathur/setup-php@v2\n        with:\n          php-version: \"8.4\"\n\n      - name: Install roave/backward-compatibility-check.\n        run: composer require --dev roave/backward-compatibility-check\n\n      - name: Run roave/backward-compatibility-check.\n        run: vendor/bin/roave-backward-compatibility-check\n\n  phpunit:\n    name: \"PHPUnit\"\n    uses: \"doctrine/.github/.github/workflows/continuous-integration.yml@13.1.0\"\n    with:\n      php-versions: '[\"7.2\", \"7.3\", \"7.4\", \"8.0\", \"8.1\", \"8.2\", \"8.3\", \"8.4\"]'\n    secrets:\n      CODECOV_TOKEN: \"${{ secrets.CODECOV_TOKEN }}\"\n"
  },
  {
    "path": ".github/workflows/release-on-milestone-closed.yml",
    "content": "name: \"Automatic Releases\"\n\non:\n  milestone:\n    types:\n      - \"closed\"\n\njobs:\n  release:\n    name: \"Git tag, release & create merge-up PR\"\n    uses: \"doctrine/.github/.github/workflows/release-on-milestone-closed.yml@13.1.0\"\n    secrets:\n      GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}\n      GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}\n      ORGANIZATION_ADMIN_TOKEN: ${{ secrets.ORGANIZATION_ADMIN_TOKEN }}\n      SIGNING_SECRET_KEY: ${{ secrets.SIGNING_SECRET_KEY }}\n"
  },
  {
    "path": ".github/workflows/static-analysis.yml",
    "content": "\nname: \"Static Analysis\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n  push:\n    branches:\n      - \"*.x\"\n\njobs:\n  static-analysis-phpstan:\n    name: \"Static Analysis\"\n    uses: \"doctrine/.github/.github/workflows/phpstan.yml@13.1.0\"\n"
  },
  {
    "path": ".github/workflows/website-schema.yml",
    "content": "\nname: \"Website config validation\"\n\non:\n  pull_request:\n    branches:\n      - \"*.x\"\n    paths:\n      - \".doctrine-project.json\"\n      - \".github/workflows/website-schema.yml\"\n  push:\n    branches:\n      - \"*.x\"\n    paths:\n      - \".doctrine-project.json\"\n      - \".github/workflows/website-schema.yml\"\n\njobs:\n  json-validate:\n    name: \"Validate JSON schema\"\n    uses: \"doctrine/.github/.github/workflows/website-schema.yml@13.1.0\"\n"
  },
  {
    "path": ".gitignore",
    "content": "/vendor/\n/composer.lock\n/composer.phar\n/phpunit.xml\n/.phpunit.result.cache\n/phpcs.xml\n/.phpcs-cache\n/phpstan.neon\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2006-2015 Doctrine Project\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\nof the Software, and to permit persons to whom the Software is furnished to do\nso, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Doctrine Inflector\n\nDoctrine Inflector is a small library that can perform string manipulations\nwith regard to uppercase/lowercase and singular/plural forms of words.\n\n[![Build Status](https://github.com/doctrine/inflector/workflows/Continuous%20Integration/badge.svg)](https://github.com/doctrine/inflector/actions?query=workflow%3A%22Continuous+Integration%22+branch%3A4.0.x)\n[![Code Coverage](https://codecov.io/gh/doctrine/inflector/branch/2.0.x/graph/badge.svg)](https://codecov.io/gh/doctrine/inflector/branch/2.0.x)\n"
  },
  {
    "path": "composer.json",
    "content": "{\n    \"name\": \"doctrine/inflector\",\n    \"description\": \"PHP Doctrine Inflector is a small library that can perform string manipulations with regard to upper/lowercase and singular/plural forms of words.\",\n    \"license\": \"MIT\",\n    \"type\": \"library\",\n    \"keywords\": [\n        \"php\",\n        \"strings\",\n        \"words\",\n        \"manipulation\",\n        \"inflector\",\n        \"inflection\",\n        \"uppercase\",\n        \"lowercase\",\n        \"singular\",\n        \"plural\"\n    ],\n    \"authors\": [\n        {\n            \"name\": \"Guilherme Blanco\",\n            \"email\": \"guilhermeblanco@gmail.com\"\n        },\n        {\n            \"name\": \"Roman Borschel\",\n            \"email\": \"roman@code-factory.org\"\n        },\n        {\n            \"name\": \"Benjamin Eberlei\",\n            \"email\": \"kontakt@beberlei.de\"\n        },\n        {\n            \"name\": \"Jonathan Wage\",\n            \"email\": \"jonwage@gmail.com\"\n        },\n        {\n            \"name\": \"Johannes Schmitt\",\n            \"email\": \"schmittjoh@gmail.com\"\n        }\n    ],\n    \"homepage\": \"https://www.doctrine-project.org/projects/inflector.html\",\n    \"require\": {\n        \"php\": \"^7.2 || ^8.0\"\n    },\n    \"require-dev\": {\n        \"doctrine/coding-standard\": \"^12.0 || ^14.0\",\n        \"phpstan/phpstan\": \"^1.12 || ^2.0\",\n        \"phpstan/phpstan-phpunit\": \"^1.4 || ^2.0\",\n        \"phpstan/phpstan-strict-rules\": \"^1.6 || ^2.0\",\n        \"phpunit/phpunit\": \"^8.5 || ^12.2\"\n    },\n    \"autoload\": {\n        \"psr-4\": {\n            \"Doctrine\\\\Inflector\\\\\": \"src\"\n        }\n    },\n    \"autoload-dev\": {\n        \"psr-4\": {\n            \"Doctrine\\\\Tests\\\\Inflector\\\\\": \"tests\"\n        }\n    },\n    \"config\": {\n        \"allow-plugins\": {\n            \"dealerdirect/phpcodesniffer-composer-installer\": true\n        },\n        \"sort-packages\": true\n    }\n}\n"
  },
  {
    "path": "docs/en/index.rst",
    "content": "Introduction\n============\n\nThe Doctrine Inflector has methods for inflecting text. The features include pluralization,\nsingularization, converting between camelCase and under_score and capitalizing\nwords.\n\nInstallation\n============\n\nYou can install the Inflector with composer:\n\n.. code-block:: console\n\n    $ composer require doctrine/inflector\n\nUsage\n=====\n\nUsing the inflector is easy, you can create a new ``Doctrine\\Inflector\\Inflector`` instance by using\nthe ``Doctrine\\Inflector\\InflectorFactory`` class:\n\n.. code-block:: php\n\n    use Doctrine\\Inflector\\InflectorFactory;\n\n    $inflector = InflectorFactory::create()->build();\n\nBy default it will create an English inflector. If you want to use another language, just pass the language\nyou want to create an inflector for to the ``createForLanguage()`` method:\n\n.. code-block:: php\n\n    use Doctrine\\Inflector\\InflectorFactory;\n    use Doctrine\\Inflector\\Language;\n\n    $inflector = InflectorFactory::createForLanguage(Language::SPANISH)->build();\n\nThe supported languages are as follows:\n\n- ``Language::ENGLISH``\n- ``Language::ESPERANTO``\n- ``Language::FRENCH``\n- ``Language::ITALIAN``\n- ``Language::NORWEGIAN_BOKMAL``\n- ``Language::PORTUGUESE``\n- ``Language::SPANISH``\n- ``Language::TURKISH``\n\nIf you want to manually construct the inflector instead of using a factory, you can do so like this:\n\n.. code-block:: php\n\n    use Doctrine\\Inflector\\CachedWordInflector;\n    use Doctrine\\Inflector\\RulesetInflector;\n    use Doctrine\\Inflector\\Rules\\English;\n\n    $inflector = new Inflector(\n        new CachedWordInflector(new RulesetInflector(\n            English\\Rules::getSingularRuleset()\n        )),\n        new CachedWordInflector(new RulesetInflector(\n            English\\Rules::getPluralRuleset()\n        ))\n    );\n\nAdding Languages\n----------------\n\nIf you are interested in adding support for your language, take a look at the other languages defined in the\n``Doctrine\\Inflector\\Rules`` namespace and the tests located in ``Doctrine\\Tests\\Inflector\\Rules``. You can copy\none of the languages and update the rules for your language.\n\nOnce you have done this, send a pull request to the ``doctrine/inflector`` repository with the additions.\n\nCustom Setup\n============\n\nIf you want to setup custom singular and plural rules, you can configure these in the factory:\n\n.. code-block:: php\n\n    use Doctrine\\Inflector\\InflectorFactory;\n    use Doctrine\\Inflector\\Rules\\Pattern;\n    use Doctrine\\Inflector\\Rules\\Patterns;\n    use Doctrine\\Inflector\\Rules\\Ruleset;\n    use Doctrine\\Inflector\\Rules\\Substitution;\n    use Doctrine\\Inflector\\Rules\\Substitutions;\n    use Doctrine\\Inflector\\Rules\\Transformation;\n    use Doctrine\\Inflector\\Rules\\Transformations;\n    use Doctrine\\Inflector\\Rules\\Word;\n\n    $inflector = InflectorFactory::create()\n        ->withSingularRules(\n            new Ruleset(\n                new Transformations(\n                    new Transformation(new Pattern('/^(bil)er$/i'), '\\1'),\n                    new Transformation(new Pattern('/^(inflec|contribu)tors$/i'), '\\1ta')\n                ),\n                new Patterns(new Pattern('singulars')),\n                new Substitutions(new Substitution(new Word('spins'), new Word('spinor')))\n            )\n        )\n        ->withPluralRules(\n            new Ruleset(\n                new Transformations(\n                    new Transformation(new Pattern('^(bil)er$'), '\\1'),\n                    new Transformation(new Pattern('^(inflec|contribu)tors$'), '\\1ta')\n                ),\n                new Patterns(new Pattern('noflect'), new Pattern('abtuse')),\n                new Substitutions(\n                    new Substitution(new Word('amaze'), new Word('amazable')),\n                    new Substitution(new Word('phone'), new Word('phonezes'))\n                )\n            )\n        )\n        ->build();\n\nNo operation inflector\n----------------------\n\nThe ``Doctrine\\Inflector\\NoopWordInflector`` may be used to configure an inflector that doesn't perform any operation for\npluralization and/or singularization. If will simply return the input as output.\n\nThis is an implementation of the `Null Object design pattern <https://sourcemaking.com/design_patterns/null_object>`_.\n\n.. code-block:: php\n\n    use Doctrine\\Inflector\\Inflector;\n    use Doctrine\\Inflector\\NoopWordInflector;\n\n    $inflector = new Inflector(new NoopWordInflector(), new NoopWordInflector());\n\nTableize\n========\n\nConverts ``ModelName`` to ``model_name``:\n\n.. code-block:: php\n\n    echo $inflector->tableize('ModelName'); // model_name\n\nClassify\n========\n\nConverts ``model_name`` to ``ModelName``:\n\n.. code-block:: php\n\n    echo $inflector->classify('model_name'); // ModelName\n\nCamelize\n========\n\nThis method uses `Classify`_ and then converts the first character to lowercase:\n\n.. code-block:: php\n\n    echo $inflector->camelize('model_name'); // modelName\n\nCapitalize\n==========\n\nTakes a string and capitalizes all of the words, like PHP's built-in\n``ucwords`` function. This extends that behavior, however, by allowing the\nword delimiters to be configured, rather than only separating on\nwhitespace.\n\nHere is an example:\n\n.. code-block:: php\n\n    $string = 'top-o-the-morning to all_of_you!';\n\n    echo $inflector->capitalize($string); // Top-O-The-Morning To All_of_you!\n\n    echo $inflector->capitalize($string, '-_ '); // Top-O-The-Morning To All_Of_You!\n\nPluralize\n=========\n\nReturns a word in plural form.\n\n.. code-block:: php\n\n    echo $inflector->pluralize('browser'); // browsers\n\nSingularize\n===========\n\nReturns a word in singular form.\n\n.. code-block:: php\n\n    echo $inflector->singularize('browsers'); // browser\n\nUrlize\n======\n\nGenerate a URL friendly string from a string of text:\n\n.. code-block:: php\n\n    echo $inflector->urlize('My first blog post'); // my-first-blog-post\n\nUnaccent\n========\n\nYou can unaccent a string of text using the ``unaccent()`` method:\n\n.. code-block:: php\n\n    echo $inflector->unaccent('año'); // ano\n\nLegacy API\n==========\n\nThe API present in Inflector 1.x is still available, but will be deprecated in a future release and dropped for 3.0.\nSupport for languages other than English is available in the 2.0 API only.\n\nAcknowledgements\n================\n\nThe language rules in this library have been adapted from several different sources, including but not limited to:\n\n- `Ruby On Rails Inflector <http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html>`_\n- `ICanBoogie Inflector <https://github.com/ICanBoogie/Inflector>`_\n- `CakePHP Inflector <https://book.cakephp.org/3.0/en/core-libraries/inflector.html>`_\n"
  },
  {
    "path": "phpcs.xml.dist",
    "content": "<?xml version=\"1.0\"?>\n<ruleset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         name=\"PHP_CodeSniffer\"\n         xsi:noNamespaceSchemaLocation=\"vendor/squizlabs/php_codesniffer/phpcs.xsd\">\n    <arg name=\"basepath\" value=\".\"/>\n    <arg name=\"extensions\" value=\"php\"/>\n    <arg name=\"parallel\" value=\"80\"/>\n    <arg name=\"cache\" value=\".phpcs-cache\"/>\n    <arg name=\"colors\"/>\n\n    <!-- Ignore warnings, show progress of the run and show sniff names -->\n    <arg value=\"nps\"/>\n\n    <config name=\"php_version\" value=\"70200\"/>\n\n    <file>src</file>\n    <file>tests</file>\n\n    <rule ref=\"Doctrine\" />\n\n    <rule ref=\"SlevomatCodingStandard.TypeHints.PropertyTypeHint\">\n        <properties>\n            <property name=\"enableNativeTypeHint\" value=\"false\" />\n            <property name=\"traversableTypeHints\" type=\"array\">\n                <element value=\"Traversable\"/>\n                <element value=\"Iterator\"/>\n                <element value=\"IteratorAggregate\"/>\n                <element value=\"Doctrine\\Common\\Collections\\Collection\"/>\n            </property>\n        </properties>\n    </rule>\n</ruleset>\n"
  },
  {
    "path": "phpstan.neon.dist",
    "content": "includes:\n    - vendor/phpstan/phpstan-phpunit/extension.neon\n    - vendor/phpstan/phpstan-phpunit/rules.neon\n    - vendor/phpstan/phpstan-strict-rules/rules.neon\n\nparameters:\n    level: 10\n    paths:\n      - src\n      - tests\n"
  },
  {
    "path": "phpunit.xml.dist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<phpunit xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:noNamespaceSchemaLocation=\"vendor/phpunit/phpunit/phpunit.xsd\"\n         colors=\"true\"\n         beStrictAboutOutputDuringTests=\"true\"\n         failOnRisky=\"true\"\n         failOnWarning=\"true\"\n>\n    <testsuites>\n        <testsuite name=\"Doctrine Inflector Test Suite\">\n            <directory>tests</directory>\n        </testsuite>\n    </testsuites>\n\n    <source>\n        <include>\n            <directory>src</directory>\n        </include>\n    </source>\n\n    <groups>\n        <exclude>\n            <group>performance</group>\n        </exclude>\n    </groups>\n</phpunit>\n"
  },
  {
    "path": "src/CachedWordInflector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nclass CachedWordInflector implements WordInflector\n{\n    /** @var WordInflector */\n    private $wordInflector;\n\n    /** @var string[] */\n    private $cache = [];\n\n    public function __construct(WordInflector $wordInflector)\n    {\n        $this->wordInflector = $wordInflector;\n    }\n\n    public function inflect(string $word): string\n    {\n        return $this->cache[$word] ?? $this->cache[$word] = $this->wordInflector->inflect($word);\n    }\n}\n"
  },
  {
    "path": "src/GenericLanguageInflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nuse function array_unshift;\n\nabstract class GenericLanguageInflectorFactory implements LanguageInflectorFactory\n{\n    /** @var Ruleset[] */\n    private $singularRulesets = [];\n\n    /** @var Ruleset[] */\n    private $pluralRulesets = [];\n\n    final public function __construct()\n    {\n        $this->singularRulesets[] = $this->getSingularRuleset();\n        $this->pluralRulesets[]   = $this->getPluralRuleset();\n    }\n\n    final public function build(): Inflector\n    {\n        return new Inflector(\n            new CachedWordInflector(new RulesetInflector(\n                ...$this->singularRulesets\n            )),\n            new CachedWordInflector(new RulesetInflector(\n                ...$this->pluralRulesets\n            ))\n        );\n    }\n\n    final public function withSingularRules(?Ruleset $singularRules, bool $reset = false): LanguageInflectorFactory\n    {\n        if ($reset) {\n            $this->singularRulesets = [];\n        }\n\n        if ($singularRules instanceof Ruleset) {\n            array_unshift($this->singularRulesets, $singularRules);\n        }\n\n        return $this;\n    }\n\n    final public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): LanguageInflectorFactory\n    {\n        if ($reset) {\n            $this->pluralRulesets = [];\n        }\n\n        if ($pluralRules instanceof Ruleset) {\n            array_unshift($this->pluralRulesets, $pluralRules);\n        }\n\n        return $this;\n    }\n\n    abstract protected function getSingularRuleset(): Ruleset;\n\n    abstract protected function getPluralRuleset(): Ruleset;\n}\n"
  },
  {
    "path": "src/Inflector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse RuntimeException;\n\nuse function chr;\nuse function function_exists;\nuse function lcfirst;\nuse function mb_strtolower;\nuse function ord;\nuse function preg_match;\nuse function preg_replace;\nuse function sprintf;\nuse function str_replace;\nuse function strlen;\nuse function strtolower;\nuse function strtr;\nuse function trim;\nuse function ucwords;\n\nclass Inflector\n{\n    private const ACCENTED_CHARACTERS = [\n        'À' => 'A',\n        'Á' => 'A',\n        'Â' => 'A',\n        'Ã' => 'A',\n        'Ä' => 'Ae',\n        'Æ' => 'Ae',\n        'Å' => 'Aa',\n        'æ' => 'a',\n        'Ç' => 'C',\n        'È' => 'E',\n        'É' => 'E',\n        'Ê' => 'E',\n        'Ë' => 'E',\n        'Ì' => 'I',\n        'Í' => 'I',\n        'Î' => 'I',\n        'Ï' => 'I',\n        'Ñ' => 'N',\n        'Ò' => 'O',\n        'Ó' => 'O',\n        'Ô' => 'O',\n        'Õ' => 'O',\n        'Ö' => 'Oe',\n        'Ù' => 'U',\n        'Ú' => 'U',\n        'Û' => 'U',\n        'Ü' => 'Ue',\n        'Ý' => 'Y',\n        'ß' => 'ss',\n        'à' => 'a',\n        'á' => 'a',\n        'â' => 'a',\n        'ã' => 'a',\n        'ä' => 'ae',\n        'å' => 'aa',\n        'ç' => 'c',\n        'è' => 'e',\n        'é' => 'e',\n        'ê' => 'e',\n        'ë' => 'e',\n        'ì' => 'i',\n        'í' => 'i',\n        'î' => 'i',\n        'ï' => 'i',\n        'ñ' => 'n',\n        'ò' => 'o',\n        'ó' => 'o',\n        'ô' => 'o',\n        'õ' => 'o',\n        'ö' => 'oe',\n        'ù' => 'u',\n        'ú' => 'u',\n        'û' => 'u',\n        'ü' => 'ue',\n        'ý' => 'y',\n        'ÿ' => 'y',\n        'Ā' => 'A',\n        'ā' => 'a',\n        'Ă' => 'A',\n        'ă' => 'a',\n        'Ą' => 'A',\n        'ą' => 'a',\n        'Ć' => 'C',\n        'ć' => 'c',\n        'Ĉ' => 'C',\n        'ĉ' => 'c',\n        'Ċ' => 'C',\n        'ċ' => 'c',\n        'Č' => 'C',\n        'č' => 'c',\n        'Ď' => 'D',\n        'ď' => 'd',\n        'Đ' => 'D',\n        'đ' => 'd',\n        'Ē' => 'E',\n        'ē' => 'e',\n        'Ĕ' => 'E',\n        'ĕ' => 'e',\n        'Ė' => 'E',\n        'ė' => 'e',\n        'Ę' => 'E',\n        'ę' => 'e',\n        'Ě' => 'E',\n        'ě' => 'e',\n        'Ĝ' => 'G',\n        'ĝ' => 'g',\n        'Ğ' => 'G',\n        'ğ' => 'g',\n        'Ġ' => 'G',\n        'ġ' => 'g',\n        'Ģ' => 'G',\n        'ģ' => 'g',\n        'Ĥ' => 'H',\n        'ĥ' => 'h',\n        'Ħ' => 'H',\n        'ħ' => 'h',\n        'Ĩ' => 'I',\n        'ĩ' => 'i',\n        'Ī' => 'I',\n        'ī' => 'i',\n        'Ĭ' => 'I',\n        'ĭ' => 'i',\n        'Į' => 'I',\n        'į' => 'i',\n        'İ' => 'I',\n        'ı' => 'i',\n        'Ĳ' => 'IJ',\n        'ĳ' => 'ij',\n        'Ĵ' => 'J',\n        'ĵ' => 'j',\n        'Ķ' => 'K',\n        'ķ' => 'k',\n        'ĸ' => 'k',\n        'Ĺ' => 'L',\n        'ĺ' => 'l',\n        'Ļ' => 'L',\n        'ļ' => 'l',\n        'Ľ' => 'L',\n        'ľ' => 'l',\n        'Ŀ' => 'L',\n        'ŀ' => 'l',\n        'Ł' => 'L',\n        'ł' => 'l',\n        'Ń' => 'N',\n        'ń' => 'n',\n        'Ņ' => 'N',\n        'ņ' => 'n',\n        'Ň' => 'N',\n        'ň' => 'n',\n        'ŉ' => 'N',\n        'Ŋ' => 'n',\n        'ŋ' => 'N',\n        'Ō' => 'O',\n        'ō' => 'o',\n        'Ŏ' => 'O',\n        'ŏ' => 'o',\n        'Ő' => 'O',\n        'ő' => 'o',\n        'Œ' => 'OE',\n        'œ' => 'oe',\n        'Ø' => 'O',\n        'ø' => 'o',\n        'Ŕ' => 'R',\n        'ŕ' => 'r',\n        'Ŗ' => 'R',\n        'ŗ' => 'r',\n        'Ř' => 'R',\n        'ř' => 'r',\n        'Ś' => 'S',\n        'ś' => 's',\n        'Ŝ' => 'S',\n        'ŝ' => 's',\n        'Ş' => 'S',\n        'ş' => 's',\n        'Š' => 'S',\n        'š' => 's',\n        'Ţ' => 'T',\n        'ţ' => 't',\n        'Ť' => 'T',\n        'ť' => 't',\n        'Ŧ' => 'T',\n        'ŧ' => 't',\n        'Ũ' => 'U',\n        'ũ' => 'u',\n        'Ū' => 'U',\n        'ū' => 'u',\n        'Ŭ' => 'U',\n        'ŭ' => 'u',\n        'Ů' => 'U',\n        'ů' => 'u',\n        'Ű' => 'U',\n        'ű' => 'u',\n        'Ų' => 'U',\n        'ų' => 'u',\n        'Ŵ' => 'W',\n        'ŵ' => 'w',\n        'Ŷ' => 'Y',\n        'ŷ' => 'y',\n        'Ÿ' => 'Y',\n        'Ź' => 'Z',\n        'ź' => 'z',\n        'Ż' => 'Z',\n        'ż' => 'z',\n        'Ž' => 'Z',\n        'ž' => 'z',\n        'ſ' => 's',\n        '€' => 'E',\n        '£' => '',\n    ];\n\n    /** @var WordInflector */\n    private $singularizer;\n\n    /** @var WordInflector */\n    private $pluralizer;\n\n    public function __construct(WordInflector $singularizer, WordInflector $pluralizer)\n    {\n        $this->singularizer = $singularizer;\n        $this->pluralizer   = $pluralizer;\n    }\n\n    /**\n     * Converts a word into the format for a Doctrine table name. Converts 'ModelName' to 'model_name'.\n     */\n    public function tableize(string $word): string\n    {\n        $tableized = preg_replace('~(?<=\\\\w)([A-Z])~u', '_$1', $word);\n\n        if ($tableized === null) {\n            throw new RuntimeException(sprintf(\n                'preg_replace returned null for value \"%s\"',\n                $word\n            ));\n        }\n\n        return mb_strtolower($tableized);\n    }\n\n    /**\n     * Converts a word into the format for a Doctrine class name. Converts 'table_name' to 'TableName'.\n     */\n    public function classify(string $word): string\n    {\n        return str_replace([' ', '_', '-'], '', ucwords($word, ' _-'));\n    }\n\n    /**\n     * Camelizes a word. This uses the classify() method and turns the first character to lowercase.\n     */\n    public function camelize(string $word): string\n    {\n        return lcfirst($this->classify($word));\n    }\n\n    /**\n     * Uppercases words with configurable delimiters between words.\n     *\n     * Takes a string and capitalizes all of the words, like PHP's built-in\n     * ucwords function. This extends that behavior, however, by allowing the\n     * word delimiters to be configured, rather than only separating on\n     * whitespace.\n     *\n     * Here is an example:\n     * <code>\n     * <?php\n     * $string = 'top-o-the-morning to all_of_you!';\n     * echo $inflector->capitalize($string);\n     * // Top-O-The-Morning To All_of_you!\n     *\n     * echo $inflector->capitalize($string, '-_ ');\n     * // Top-O-The-Morning To All_Of_You!\n     * ?>\n     * </code>\n     *\n     * @param string $string     The string to operate on.\n     * @param string $delimiters A list of word separators.\n     *\n     * @return string The string with all delimiter-separated words capitalized.\n     */\n    public function capitalize(string $string, string $delimiters = \" \\n\\t\\r\\0\\x0B-\"): string\n    {\n        return ucwords($string, $delimiters);\n    }\n\n    /**\n     * Checks if the given string seems like it has utf8 characters in it.\n     *\n     * @param string $string The string to check for utf8 characters in.\n     */\n    public function seemsUtf8(string $string): bool\n    {\n        for ($i = 0; $i < strlen($string); $i++) {\n            if (ord($string[$i]) < 0x80) {\n                continue; // 0bbbbbbb\n            }\n\n            if ((ord($string[$i]) & 0xE0) === 0xC0) {\n                $n = 1; // 110bbbbb\n            } elseif ((ord($string[$i]) & 0xF0) === 0xE0) {\n                $n = 2; // 1110bbbb\n            } elseif ((ord($string[$i]) & 0xF8) === 0xF0) {\n                $n = 3; // 11110bbb\n            } elseif ((ord($string[$i]) & 0xFC) === 0xF8) {\n                $n = 4; // 111110bb\n            } elseif ((ord($string[$i]) & 0xFE) === 0xFC) {\n                $n = 5; // 1111110b\n            } else {\n                return false; // Does not match any model\n            }\n\n            for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?\n                if (++$i === strlen($string) || ((ord($string[$i]) & 0xC0) !== 0x80)) {\n                    return false;\n                }\n            }\n        }\n\n        return true;\n    }\n\n    /**\n     * Remove any illegal characters, accents, etc.\n     *\n     * @param  string $string String to unaccent\n     *\n     * @return string Unaccented string\n     */\n    public function unaccent(string $string): string\n    {\n        if (preg_match('/[\\x80-\\xff]/', $string) === false) {\n            return $string;\n        }\n\n        if ($this->seemsUtf8($string)) {\n            $string = strtr($string, self::ACCENTED_CHARACTERS);\n        } else {\n            $characters = [];\n\n            // Assume ISO-8859-1 if not UTF-8\n            $characters['in'] =\n                  chr(128)\n                . chr(131)\n                . chr(138)\n                . chr(142)\n                . chr(154)\n                . chr(158)\n                . chr(159)\n                . chr(162)\n                . chr(165)\n                . chr(181)\n                . chr(192)\n                . chr(193)\n                . chr(194)\n                . chr(195)\n                . chr(196)\n                . chr(197)\n                . chr(199)\n                . chr(200)\n                . chr(201)\n                . chr(202)\n                . chr(203)\n                . chr(204)\n                . chr(205)\n                . chr(206)\n                . chr(207)\n                . chr(209)\n                . chr(210)\n                . chr(211)\n                . chr(212)\n                . chr(213)\n                . chr(214)\n                . chr(216)\n                . chr(217)\n                . chr(218)\n                . chr(219)\n                . chr(220)\n                . chr(221)\n                . chr(224)\n                . chr(225)\n                . chr(226)\n                . chr(227)\n                . chr(228)\n                . chr(229)\n                . chr(231)\n                . chr(232)\n                . chr(233)\n                . chr(234)\n                . chr(235)\n                . chr(236)\n                . chr(237)\n                . chr(238)\n                . chr(239)\n                . chr(241)\n                . chr(242)\n                . chr(243)\n                . chr(244)\n                . chr(245)\n                . chr(246)\n                . chr(248)\n                . chr(249)\n                . chr(250)\n                . chr(251)\n                . chr(252)\n                . chr(253)\n                . chr(255);\n\n            $characters['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy';\n\n            $string = strtr($string, $characters['in'], $characters['out']);\n\n            $doubleChars = [];\n\n            $doubleChars['in'] = [\n                chr(140),\n                chr(156),\n                chr(198),\n                chr(208),\n                chr(222),\n                chr(223),\n                chr(230),\n                chr(240),\n                chr(254),\n            ];\n\n            $doubleChars['out'] = ['OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th'];\n\n            $string = str_replace($doubleChars['in'], $doubleChars['out'], $string);\n        }\n\n        return $string;\n    }\n\n    /**\n     * Convert any passed string to a url friendly string.\n     * Converts 'My first blog post' to 'my-first-blog-post'\n     *\n     * @param  string $string String to urlize.\n     *\n     * @return string Urlized string.\n     */\n    public function urlize(string $string): string\n    {\n        // Remove all non url friendly characters with the unaccent function\n        $unaccented = $this->unaccent($string);\n\n        if (function_exists('mb_strtolower')) {\n            $lowered = mb_strtolower($unaccented);\n        } else {\n            $lowered = strtolower($unaccented);\n        }\n\n        $replacements = [\n            '/\\W/' => ' ',\n            '/([A-Z]+)([A-Z][a-z])/' => '\\1_\\2',\n            '/([a-z\\d])([A-Z])/' => '\\1_\\2',\n            '/[^A-Z^a-z^0-9^\\/]+/' => '-',\n        ];\n\n        $urlized = $lowered;\n\n        foreach ($replacements as $pattern => $replacement) {\n            $replaced = preg_replace($pattern, $replacement, $urlized);\n\n            if ($replaced === null) {\n                throw new RuntimeException(sprintf(\n                    'preg_replace returned null for value \"%s\"',\n                    $urlized\n                ));\n            }\n\n            $urlized = $replaced;\n        }\n\n        return trim($urlized, '-');\n    }\n\n    /**\n     * Returns a word in singular form.\n     *\n     * @param string $word The word in plural form.\n     *\n     * @return string The word in singular form.\n     */\n    public function singularize(string $word): string\n    {\n        return $this->singularizer->inflect($word);\n    }\n\n    /**\n     * Returns a word in plural form.\n     *\n     * @param string $word The word in singular form.\n     *\n     * @return string The word in plural form.\n     */\n    public function pluralize(string $word): string\n    {\n        return $this->pluralizer->inflect($word);\n    }\n}\n"
  },
  {
    "path": "src/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\English;\nuse Doctrine\\Inflector\\Rules\\Esperanto;\nuse Doctrine\\Inflector\\Rules\\French;\nuse Doctrine\\Inflector\\Rules\\Italian;\nuse Doctrine\\Inflector\\Rules\\NorwegianBokmal;\nuse Doctrine\\Inflector\\Rules\\Portuguese;\nuse Doctrine\\Inflector\\Rules\\Spanish;\nuse Doctrine\\Inflector\\Rules\\Turkish;\nuse InvalidArgumentException;\n\nuse function sprintf;\n\nfinal class InflectorFactory\n{\n    public static function create(): LanguageInflectorFactory\n    {\n        return self::createForLanguage(Language::ENGLISH);\n    }\n\n    public static function createForLanguage(string $language): LanguageInflectorFactory\n    {\n        switch ($language) {\n            case Language::ENGLISH:\n                return new English\\InflectorFactory();\n\n            case Language::ESPERANTO:\n                return new Esperanto\\InflectorFactory();\n\n            case Language::FRENCH:\n                return new French\\InflectorFactory();\n\n            case Language::ITALIAN:\n                return new Italian\\InflectorFactory();\n\n            case Language::NORWEGIAN_BOKMAL:\n                return new NorwegianBokmal\\InflectorFactory();\n\n            case Language::PORTUGUESE:\n                return new Portuguese\\InflectorFactory();\n\n            case Language::SPANISH:\n                return new Spanish\\InflectorFactory();\n\n            case Language::TURKISH:\n                return new Turkish\\InflectorFactory();\n\n            default:\n                throw new InvalidArgumentException(sprintf(\n                    'Language \"%s\" is not supported.',\n                    $language\n                ));\n        }\n    }\n}\n"
  },
  {
    "path": "src/Language.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nfinal class Language\n{\n    public const ENGLISH          = 'english';\n    public const ESPERANTO        = 'esperanto';\n    public const FRENCH           = 'french';\n    public const ITALIAN          = 'italian';\n    public const NORWEGIAN_BOKMAL = 'norwegian-bokmal';\n    public const PORTUGUESE       = 'portuguese';\n    public const SPANISH          = 'spanish';\n    public const TURKISH          = 'turkish';\n\n    private function __construct()\n    {\n    }\n}\n"
  },
  {
    "path": "src/LanguageInflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\ninterface LanguageInflectorFactory\n{\n    /**\n     * Applies custom rules for singularisation\n     *\n     * @param bool $reset If true, will unset default inflections for all new rules\n     *\n     * @return $this\n     */\n    public function withSingularRules(?Ruleset $singularRules, bool $reset = false): self;\n\n    /**\n     * Applies custom rules for pluralisation\n     *\n     * @param bool $reset If true, will unset default inflections for all new rules\n     *\n     * @return $this\n     */\n    public function withPluralRules(?Ruleset $pluralRules, bool $reset = false): self;\n\n    /**\n     * Builds the inflector instance with all applicable rules\n     */\n    public function build(): Inflector;\n}\n"
  },
  {
    "path": "src/NoopWordInflector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nclass NoopWordInflector implements WordInflector\n{\n    public function inflect(string $word): string\n    {\n        return $word;\n    }\n}\n"
  },
  {
    "path": "src/Rules/English/Inflectible.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Word;\n\nclass Inflectible\n{\n    /** @return Transformation[] */\n    public static function getSingular(): iterable\n    {\n        yield new Transformation(new Pattern('(s)tatuses$'), '\\1\\2tatus');\n        yield new Transformation(new Pattern('(s)tatus$'), '\\1\\2tatus');\n        yield new Transformation(new Pattern('(c)ampus$'), '\\1\\2ampus');\n        yield new Transformation(new Pattern('^(.*)(menu)s$'), '\\1\\2');\n        yield new Transformation(new Pattern('(quiz)zes$'), '\\\\1');\n        yield new Transformation(new Pattern('(matr)ices$'), '\\1ix');\n        yield new Transformation(new Pattern('(vert|ind)ices$'), '\\1ex');\n        yield new Transformation(new Pattern('^(ox)en'), '\\1');\n        yield new Transformation(new Pattern('(alias)(es)*$'), '\\1');\n        yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)oes$'), '\\1o');\n        yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$'), '\\1us');\n        yield new Transformation(new Pattern('([ftw]ax)es'), '\\1');\n        yield new Transformation(new Pattern('(analys|ax|cris|test|thes)es$'), '\\1is');\n        yield new Transformation(new Pattern('(shoe|slave)s$'), '\\1');\n        yield new Transformation(new Pattern('(o)es$'), '\\1');\n        yield new Transformation(new Pattern('ouses$'), 'ouse');\n        yield new Transformation(new Pattern('([^a])uses$'), '\\1us');\n        yield new Transformation(new Pattern('([m|l])ice$'), '\\1ouse');\n        yield new Transformation(new Pattern('(x|ch|ss|sh)es$'), '\\1');\n        yield new Transformation(new Pattern('(m)ovies$'), '\\1\\2ovie');\n        yield new Transformation(new Pattern('(s)eries$'), '\\1\\2eries');\n        yield new Transformation(new Pattern('([^aeiouy]|qu)ies$'), '\\1y');\n        yield new Transformation(new Pattern('([lr])ves$'), '\\1f');\n        yield new Transformation(new Pattern('(tive)s$'), '\\1');\n        yield new Transformation(new Pattern('(hive)s$'), '\\1');\n        yield new Transformation(new Pattern('(drive)s$'), '\\1');\n        yield new Transformation(new Pattern('(dive)s$'), '\\1');\n        yield new Transformation(new Pattern('(olive)s$'), '\\1');\n        yield new Transformation(new Pattern('([^fo])ves$'), '\\1fe');\n        yield new Transformation(new Pattern('(^analy)ses$'), '\\1sis');\n        yield new Transformation(new Pattern('(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$'), '\\1\\2sis');\n        yield new Transformation(new Pattern('(tax)a$'), '\\1on');\n        yield new Transformation(new Pattern('(c)riteria$'), '\\1riterion');\n        yield new Transformation(new Pattern('([ti])a(?<!regatta)$'), '\\1um');\n        yield new Transformation(new Pattern('(p)eople$'), '\\1\\2erson');\n        yield new Transformation(new Pattern('(m)en$'), '\\1an');\n        yield new Transformation(new Pattern('(c)hildren$'), '\\1\\2hild');\n        yield new Transformation(new Pattern('(f)eet$'), '\\1oot');\n        yield new Transformation(new Pattern('(n)ews$'), '\\1\\2ews');\n        yield new Transformation(new Pattern('eaus$'), 'eau');\n        yield new Transformation(new Pattern('^tights$'), 'tights');\n        yield new Transformation(new Pattern('^shorts$'), 'shorts');\n        yield new Transformation(new Pattern('s$'), '');\n    }\n\n    /** @return Transformation[] */\n    public static function getPlural(): iterable\n    {\n        yield new Transformation(new Pattern('(s)tatus$'), '\\1\\2tatuses');\n        yield new Transformation(new Pattern('(quiz)$'), '\\1zes');\n        yield new Transformation(new Pattern('^(ox)$'), '\\1\\2en');\n        yield new Transformation(new Pattern('([m|l])ouse$'), '\\1ice');\n        yield new Transformation(new Pattern('(matr|vert|ind)(ix|ex)$'), '\\1ices');\n        yield new Transformation(new Pattern('(x|ch|ss|sh)$'), '\\1es');\n        yield new Transformation(new Pattern('([^aeiouy]|qu)y$'), '\\1ies');\n        yield new Transformation(new Pattern('(hive|gulf)$'), '\\1s');\n        yield new Transformation(new Pattern('(?:([^f])fe|([lr])f)$'), '\\1\\2ves');\n        yield new Transformation(new Pattern('sis$'), 'ses');\n        yield new Transformation(new Pattern('([ti])um$'), '\\1a');\n        yield new Transformation(new Pattern('(tax)on$'), '\\1a');\n        yield new Transformation(new Pattern('(c)riterion$'), '\\1riteria');\n        yield new Transformation(new Pattern('(p)erson$'), '\\1eople');\n        yield new Transformation(new Pattern('(m)an$'), '\\1en');\n        yield new Transformation(new Pattern('(c)hild$'), '\\1hildren');\n        yield new Transformation(new Pattern('(f)oot$'), '\\1eet');\n        yield new Transformation(new Pattern('(buffal|her|potat|tomat|volcan)o$'), '\\1\\2oes');\n        yield new Transformation(new Pattern('(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$'), '\\1i');\n        yield new Transformation(new Pattern('us$'), 'uses');\n        yield new Transformation(new Pattern('(alias)$'), '\\1es');\n        yield new Transformation(new Pattern('(analys|ax|cris|test|thes)is$'), '\\1es');\n        yield new Transformation(new Pattern('s$'), 's');\n        yield new Transformation(new Pattern('^$'), '');\n        yield new Transformation(new Pattern('$'), 's');\n    }\n\n    /** @return Substitution[] */\n    public static function getIrregular(): iterable\n    {\n        yield new Substitution(new Word('abuse'), new Word('abuses'));\n        yield new Substitution(new Word('alga'), new Word('algae'));\n        yield new Substitution(new Word('atlas'), new Word('atlases'));\n        yield new Substitution(new Word('avalanche'), new Word('avalanches'));\n        yield new Substitution(new Word('axis'), new Word('axes'));\n        yield new Substitution(new Word('axe'), new Word('axes'));\n        yield new Substitution(new Word('beef'), new Word('beefs'));\n        yield new Substitution(new Word('blouse'), new Word('blouses'));\n        yield new Substitution(new Word('brother'), new Word('brothers'));\n        yield new Substitution(new Word('brownie'), new Word('brownies'));\n        yield new Substitution(new Word('cache'), new Word('caches'));\n        yield new Substitution(new Word('cafe'), new Word('cafes'));\n        yield new Substitution(new Word('canvas'), new Word('canvases'));\n        yield new Substitution(new Word('cave'), new Word('caves'));\n        yield new Substitution(new Word('chateau'), new Word('chateaux'));\n        yield new Substitution(new Word('child'), new Word('children'));\n        yield new Substitution(new Word('cookie'), new Word('cookies'));\n        yield new Substitution(new Word('corpus'), new Word('corpuses'));\n        yield new Substitution(new Word('cow'), new Word('cows'));\n        yield new Substitution(new Word('criterion'), new Word('criteria'));\n        yield new Substitution(new Word('curriculum'), new Word('curricula'));\n        yield new Substitution(new Word('curve'), new Word('curves'));\n        yield new Substitution(new Word('demo'), new Word('demos'));\n        yield new Substitution(new Word('die'), new Word('dice'));\n        yield new Substitution(new Word('domino'), new Word('dominoes'));\n        yield new Substitution(new Word('echo'), new Word('echoes'));\n        yield new Substitution(new Word('emphasis'), new Word('emphases'));\n        yield new Substitution(new Word('epoch'), new Word('epochs'));\n        yield new Substitution(new Word('foe'), new Word('foes'));\n        yield new Substitution(new Word('foot'), new Word('feet'));\n        yield new Substitution(new Word('fungus'), new Word('fungi'));\n        yield new Substitution(new Word('ganglion'), new Word('ganglions'));\n        yield new Substitution(new Word('gas'), new Word('gases'));\n        yield new Substitution(new Word('genie'), new Word('genies'));\n        yield new Substitution(new Word('genus'), new Word('genera'));\n        yield new Substitution(new Word('goose'), new Word('geese'));\n        yield new Substitution(new Word('graffito'), new Word('graffiti'));\n        yield new Substitution(new Word('grave'), new Word('graves'));\n        yield new Substitution(new Word('hippopotamus'), new Word('hippopotami'));\n        yield new Substitution(new Word('hoax'), new Word('hoaxes'));\n        yield new Substitution(new Word('hoof'), new Word('hoofs'));\n        yield new Substitution(new Word('human'), new Word('humans'));\n        yield new Substitution(new Word('iris'), new Word('irises'));\n        yield new Substitution(new Word('larva'), new Word('larvae'));\n        yield new Substitution(new Word('leaf'), new Word('leaves'));\n        yield new Substitution(new Word('lens'), new Word('lenses'));\n        yield new Substitution(new Word('loaf'), new Word('loaves'));\n        yield new Substitution(new Word('man'), new Word('men'));\n        yield new Substitution(new Word('medium'), new Word('media'));\n        yield new Substitution(new Word('memorandum'), new Word('memoranda'));\n        yield new Substitution(new Word('money'), new Word('monies'));\n        yield new Substitution(new Word('mongoose'), new Word('mongooses'));\n        yield new Substitution(new Word('motto'), new Word('mottoes'));\n        yield new Substitution(new Word('move'), new Word('moves'));\n        yield new Substitution(new Word('mythos'), new Word('mythoi'));\n        yield new Substitution(new Word('neurosis'), new Word('neuroses'));\n        yield new Substitution(new Word('niche'), new Word('niches'));\n        yield new Substitution(new Word('niveau'), new Word('niveaux'));\n        yield new Substitution(new Word('nucleus'), new Word('nuclei'));\n        yield new Substitution(new Word('numen'), new Word('numina'));\n        yield new Substitution(new Word('nursery'), new Word('nurseries'));\n        yield new Substitution(new Word('oasis'), new Word('oases'));\n        yield new Substitution(new Word('occiput'), new Word('occiputs'));\n        yield new Substitution(new Word('octopus'), new Word('octopuses'));\n        yield new Substitution(new Word('opus'), new Word('opuses'));\n        yield new Substitution(new Word('ox'), new Word('oxen'));\n        yield new Substitution(new Word('passerby'), new Word('passersby'));\n        yield new Substitution(new Word('penis'), new Word('penises'));\n        yield new Substitution(new Word('person'), new Word('people'));\n        yield new Substitution(new Word('plateau'), new Word('plateaux'));\n        yield new Substitution(new Word('runner-up'), new Word('runners-up'));\n        yield new Substitution(new Word('safe'), new Word('safes'));\n        yield new Substitution(new Word('save'), new Word('saves'));\n        yield new Substitution(new Word('sex'), new Word('sexes'));\n        yield new Substitution(new Word('sieve'), new Word('sieves'));\n        yield new Substitution(new Word('soliloquy'), new Word('soliloquies'));\n        yield new Substitution(new Word('son-in-law'), new Word('sons-in-law'));\n        yield new Substitution(new Word('stadium'), new Word('stadiums'));\n        yield new Substitution(new Word('syllabus'), new Word('syllabi'));\n        yield new Substitution(new Word('testis'), new Word('testes'));\n        yield new Substitution(new Word('thief'), new Word('thieves'));\n        yield new Substitution(new Word('tooth'), new Word('teeth'));\n        yield new Substitution(new Word('tornado'), new Word('tornadoes'));\n        yield new Substitution(new Word('trilby'), new Word('trilbys'));\n        yield new Substitution(new Word('turf'), new Word('turfs'));\n        yield new Substitution(new Word('valve'), new Word('valves'));\n        yield new Substitution(new Word('volcano'), new Word('volcanoes'));\n        yield new Substitution(new Word('wave'), new Word('waves'));\n        yield new Substitution(new Word('zombie'), new Word('zombies'));\n    }\n}\n"
  },
  {
    "path": "src/Rules/English/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\GenericLanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nfinal class InflectorFactory extends GenericLanguageInflectorFactory\n{\n    protected function getSingularRuleset(): Ruleset\n    {\n        return Rules::getSingularRuleset();\n    }\n\n    protected function getPluralRuleset(): Ruleset\n    {\n        return Rules::getPluralRuleset();\n    }\n}\n"
  },
  {
    "path": "src/Rules/English/Rules.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformations;\n\nfinal class Rules\n{\n    public static function getSingularRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getSingular()),\n            new Patterns(...Uninflected::getSingular()),\n            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()\n        );\n    }\n\n    public static function getPluralRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getPlural()),\n            new Patterns(...Uninflected::getPlural()),\n            new Substitutions(...Inflectible::getIrregular())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Rules/English/Uninflected.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfinal class Uninflected\n{\n    /** @return Pattern[] */\n    public static function getSingular(): iterable\n    {\n        yield from self::getDefault();\n\n        yield new Pattern('.*ss');\n        yield new Pattern('clothes');\n        yield new Pattern('data');\n        yield new Pattern('fascia');\n        yield new Pattern('fuchsia');\n        yield new Pattern('galleria');\n        yield new Pattern('mafia');\n        yield new Pattern('militia');\n        yield new Pattern('pants');\n        yield new Pattern('petunia');\n        yield new Pattern('sepia');\n        yield new Pattern('trivia');\n        yield new Pattern('utopia');\n    }\n\n    /** @return Pattern[] */\n    public static function getPlural(): iterable\n    {\n        yield from self::getDefault();\n\n        yield new Pattern('people');\n        yield new Pattern('trivia');\n        yield new Pattern('\\w+ware$');\n        yield new Pattern('media');\n    }\n\n    /** @return Pattern[] */\n    private static function getDefault(): iterable\n    {\n        yield new Pattern('\\w+media');\n        yield new Pattern('advice');\n        yield new Pattern('aircraft');\n        yield new Pattern('amoyese');\n        yield new Pattern('art');\n        yield new Pattern('audio');\n        yield new Pattern('baggage');\n        yield new Pattern('bison');\n        yield new Pattern('borghese');\n        yield new Pattern('bream');\n        yield new Pattern('breeches');\n        yield new Pattern('britches');\n        yield new Pattern('buffalo');\n        yield new Pattern('butter');\n        yield new Pattern('cantus');\n        yield new Pattern('carp');\n        yield new Pattern('cattle');\n        yield new Pattern('chassis');\n        yield new Pattern('clippers');\n        yield new Pattern('clothing');\n        yield new Pattern('coal');\n        yield new Pattern('cod');\n        yield new Pattern('coitus');\n        yield new Pattern('compensation');\n        yield new Pattern('congoese');\n        yield new Pattern('contretemps');\n        yield new Pattern('coreopsis');\n        yield new Pattern('corps');\n        yield new Pattern('cotton');\n        yield new Pattern('data');\n        yield new Pattern('debris');\n        yield new Pattern('deer');\n        yield new Pattern('diabetes');\n        yield new Pattern('djinn');\n        yield new Pattern('education');\n        yield new Pattern('eland');\n        yield new Pattern('elk');\n        yield new Pattern('emoji');\n        yield new Pattern('equipment');\n        yield new Pattern('evidence');\n        yield new Pattern('faroese');\n        yield new Pattern('feedback');\n        yield new Pattern('fish');\n        yield new Pattern('flounder');\n        yield new Pattern('flour');\n        yield new Pattern('foochowese');\n        yield new Pattern('food');\n        yield new Pattern('furniture');\n        yield new Pattern('gallows');\n        yield new Pattern('genevese');\n        yield new Pattern('genoese');\n        yield new Pattern('gilbertese');\n        yield new Pattern('gold');\n        yield new Pattern('headquarters');\n        yield new Pattern('herpes');\n        yield new Pattern('hijinks');\n        yield new Pattern('homework');\n        yield new Pattern('hottentotese');\n        yield new Pattern('impatience');\n        yield new Pattern('information');\n        yield new Pattern('innings');\n        yield new Pattern('jackanapes');\n        yield new Pattern('jeans');\n        yield new Pattern('jedi');\n        yield new Pattern('kin');\n        yield new Pattern('kiplingese');\n        yield new Pattern('knowledge');\n        yield new Pattern('kongoese');\n        yield new Pattern('leather');\n        yield new Pattern('love');\n        yield new Pattern('lucchese');\n        yield new Pattern('luggage');\n        yield new Pattern('mackerel');\n        yield new Pattern('Maltese');\n        yield new Pattern('management');\n        yield new Pattern('metadata');\n        yield new Pattern('mews');\n        yield new Pattern('money');\n        yield new Pattern('moose');\n        yield new Pattern('mumps');\n        yield new Pattern('music');\n        yield new Pattern('nankingese');\n        yield new Pattern('news');\n        yield new Pattern('nexus');\n        yield new Pattern('niasese');\n        yield new Pattern('nutrition');\n        yield new Pattern('offspring');\n        yield new Pattern('oil');\n        yield new Pattern('patience');\n        yield new Pattern('pekingese');\n        yield new Pattern('piedmontese');\n        yield new Pattern('pincers');\n        yield new Pattern('pistoiese');\n        yield new Pattern('plankton');\n        yield new Pattern('pliers');\n        yield new Pattern('pokemon');\n        yield new Pattern('police');\n        yield new Pattern('polish');\n        yield new Pattern('portuguese');\n        yield new Pattern('proceedings');\n        yield new Pattern('progress');\n        yield new Pattern('rabies');\n        yield new Pattern('rain');\n        yield new Pattern('research');\n        yield new Pattern('rhinoceros');\n        yield new Pattern('rice');\n        yield new Pattern('salmon');\n        yield new Pattern('sand');\n        yield new Pattern('sarawakese');\n        yield new Pattern('scissors');\n        yield new Pattern('sea[- ]bass');\n        yield new Pattern('series');\n        yield new Pattern('shavese');\n        yield new Pattern('shears');\n        yield new Pattern('sheep');\n        yield new Pattern('siemens');\n        yield new Pattern('silk');\n        yield new Pattern('sms');\n        yield new Pattern('soap');\n        yield new Pattern('social media');\n        yield new Pattern('spam');\n        yield new Pattern('species');\n        yield new Pattern('staff');\n        yield new Pattern('sugar');\n        yield new Pattern('swine');\n        yield new Pattern('talent');\n        yield new Pattern('toothpaste');\n        yield new Pattern('traffic');\n        yield new Pattern('travel');\n        yield new Pattern('trousers');\n        yield new Pattern('trout');\n        yield new Pattern('tuna');\n        yield new Pattern('us');\n        yield new Pattern('vermontese');\n        yield new Pattern('vinegar');\n        yield new Pattern('weather');\n        yield new Pattern('wenchowese');\n        yield new Pattern('wheat');\n        yield new Pattern('whiting');\n        yield new Pattern('wildebeest');\n        yield new Pattern('wood');\n        yield new Pattern('wool');\n        yield new Pattern('yengeese');\n    }\n}\n"
  },
  {
    "path": "src/Rules/Esperanto/Inflectible.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Word;\n\nclass Inflectible\n{\n    /** @return Transformation[] */\n    public static function getSingular(): iterable\n    {\n        yield new Transformation(new Pattern('oj$'), 'o');\n    }\n\n    /** @return Transformation[] */\n    public static function getPlural(): iterable\n    {\n        yield new Transformation(new Pattern('o$'), 'oj');\n    }\n\n    /** @return Substitution[] */\n    public static function getIrregular(): iterable\n    {\n        yield new Substitution(new Word(''), new Word(''));\n    }\n}\n"
  },
  {
    "path": "src/Rules/Esperanto/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\GenericLanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nfinal class InflectorFactory extends GenericLanguageInflectorFactory\n{\n    protected function getSingularRuleset(): Ruleset\n    {\n        return Rules::getSingularRuleset();\n    }\n\n    protected function getPluralRuleset(): Ruleset\n    {\n        return Rules::getPluralRuleset();\n    }\n}\n"
  },
  {
    "path": "src/Rules/Esperanto/Rules.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformations;\n\nfinal class Rules\n{\n    public static function getSingularRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getSingular()),\n            new Patterns(...Uninflected::getSingular()),\n            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()\n        );\n    }\n\n    public static function getPluralRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getPlural()),\n            new Patterns(...Uninflected::getPlural()),\n            new Substitutions(...Inflectible::getIrregular())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Rules/Esperanto/Uninflected.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfinal class Uninflected\n{\n    /** @return Pattern[] */\n    public static function getSingular(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    public static function getPlural(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    private static function getDefault(): iterable\n    {\n        yield new Pattern('');\n    }\n}\n"
  },
  {
    "path": "src/Rules/French/Inflectible.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Word;\n\nclass Inflectible\n{\n    /** @return Transformation[] */\n    public static function getSingular(): iterable\n    {\n        yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)aux$/'), '\\1ail');\n        yield new Transformation(new Pattern('/ails$/'), 'ail');\n        yield new Transformation(new Pattern('/(journ|chev|loc)aux$/'), '\\1al');\n        yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|pou|au|eu|eau)x$/'), '\\1');\n        yield new Transformation(new Pattern('/s$/'), '');\n    }\n\n    /** @return Transformation[] */\n    public static function getPlural(): iterable\n    {\n        yield new Transformation(new Pattern('/(s|x|z)$/'), '\\1');\n        yield new Transformation(new Pattern('/(b|cor|ém|gemm|soupir|trav|vant|vitr)ail$/'), '\\1aux');\n        yield new Transformation(new Pattern('/ail$/'), 'ails');\n        yield new Transformation(new Pattern('/(chacal|carnaval|festival|récital)$/'), '\\1s');\n        yield new Transformation(new Pattern('/al$/'), 'aux');\n        yield new Transformation(new Pattern('/(bleu|émeu|landau|pneu|sarrau)$/'), '\\1s');\n        yield new Transformation(new Pattern('/(bijou|caillou|chou|genou|hibou|joujou|lieu|pou|au|eu|eau)$/'), '\\1x');\n        yield new Transformation(new Pattern('/$/'), 's');\n    }\n\n    /** @return Substitution[] */\n    public static function getIrregular(): iterable\n    {\n        yield new Substitution(new Word('monsieur'), new Word('messieurs'));\n        yield new Substitution(new Word('madame'), new Word('mesdames'));\n        yield new Substitution(new Word('mademoiselle'), new Word('mesdemoiselles'));\n    }\n}\n"
  },
  {
    "path": "src/Rules/French/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\GenericLanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nfinal class InflectorFactory extends GenericLanguageInflectorFactory\n{\n    protected function getSingularRuleset(): Ruleset\n    {\n        return Rules::getSingularRuleset();\n    }\n\n    protected function getPluralRuleset(): Ruleset\n    {\n        return Rules::getPluralRuleset();\n    }\n}\n"
  },
  {
    "path": "src/Rules/French/Rules.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformations;\n\nfinal class Rules\n{\n    public static function getSingularRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getSingular()),\n            new Patterns(...Uninflected::getSingular()),\n            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()\n        );\n    }\n\n    public static function getPluralRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getPlural()),\n            new Patterns(...Uninflected::getPlural()),\n            new Substitutions(...Inflectible::getIrregular())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Rules/French/Uninflected.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfinal class Uninflected\n{\n    /** @return Pattern[] */\n    public static function getSingular(): iterable\n    {\n        yield from self::getDefault();\n\n        yield new Pattern('bois');\n        yield new Pattern('mas');\n    }\n\n    /** @return Pattern[] */\n    public static function getPlural(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    private static function getDefault(): iterable\n    {\n        yield new Pattern('');\n    }\n}\n"
  },
  {
    "path": "src/Rules/Italian/Inflectible.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Word;\n\nclass Inflectible\n{\n    /** @return iterable<Transformation> */\n    public static function getSingular(): iterable\n    {\n        // Reverse of -sce → -scia (fasce → fascia)\n        yield new Transformation(new Pattern('([aeiou])sce$'), '\\\\1scia');\n\n        // Reverse of -cie → -cia (farmacia → farmacie)\n        yield new Transformation(new Pattern('cie$'), 'cia');\n\n        // Reverse of -gie → -gia (bugia → bugie)\n        yield new Transformation(new Pattern('gie$'), 'gia');\n\n        // Reverse of -ce → -cia (arance → arancia)\n        yield new Transformation(new Pattern('([^aeiou])ce$'), '\\1cia');\n\n        // Reverse of -ge → -gia (valige → valigia)\n        yield new Transformation(new Pattern('([^aeiou])ge$'), '\\1gia');\n\n        // Reverse of -chi → -co (bachi → baco)\n        yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])chi$'), '\\1co');\n\n        // Reverse of -ghi → -go (laghi → lago)\n        yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])ghi$'), '\\1go');\n\n        // Reverse of -ci → -co (medici → medico)\n        yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])ci$'), '\\1co');\n\n        // Reverse of -gi → -go (psicologi → psicologo)\n        yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])gi$'), '\\1go');\n\n        // Reverse of -i → -io (zii → zio, negozi → negozio)\n        // This is more complex due to Italian's stress patterns, but we'll handle the basic case\n        yield new Transformation(new Pattern('([^aeiou])i$'), '\\1io');\n\n        // Handle words that end with -i but should go to -co/-go (amici → amico, not amice)\n        yield new Transformation(new Pattern('([^aeiou])ci$'), '\\1co');\n        yield new Transformation(new Pattern('([^aeiou])gi$'), '\\1go');\n\n        // Reverse of -a → -e\n        yield new Transformation(new Pattern('e$'), 'a');\n\n        // Reverse of -e → -i\n        yield new Transformation(new Pattern('i$'), 'e');\n\n        // Reverse of -o → -i\n        yield new Transformation(new Pattern('i$'), 'o');\n    }\n\n    /** @return iterable<Transformation> */\n    public static function getPlural(): iterable\n    {\n        // Words ending in -scia without stress on 'i' become -sce (e.g. fascia → fasce)\n        yield new Transformation(new Pattern('([aeiou])scia$'), '\\\\1sce');\n\n        // Words ending in -cia/gia with stress on 'i' keep the 'i' in plural\n        yield new Transformation(new Pattern('cia$'), 'cie'); // e.g. farmacia → farmacie\n        yield new Transformation(new Pattern('gia$'), 'gie'); // e.g. bugia → bugie\n\n        // Words ending in -cia/gia without stress on 'i' lose the 'i' in plural\n        yield new Transformation(new Pattern('([^aeiou])cia$'), '\\\\1ce'); // e.g. arancia → arance\n        yield new Transformation(new Pattern('([^aeiou])gia$'), '\\\\1ge'); // e.g. valigia → valige\n\n        // Words ending in -co/-go with stress on 'o' become -chi/-ghi\n        yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])co$'), '\\\\1chi'); // e.g. baco → bachi\n        yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])go$'), '\\\\1ghi'); // e.g. lago → laghi\n\n        // Words ending in -co/-go with stress on the penultimate syllable become -ci/-gi\n        yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])co$'), '\\\\1ci'); // e.g. medico → medici\n        yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])go$'), '\\\\1gi'); // e.g. psicologo → psicologi\n\n        // Words ending in -io with stress on 'i' keep the 'i' in plural\n        yield new Transformation(new Pattern('([^aeiou])io$'), '\\\\1i'); // e.g. zio → zii\n\n        // Words ending in -io with stress on 'o' lose the 'i' in plural\n        yield new Transformation(new Pattern('([aeiou])io$'), '\\\\1i'); // e.g. negozio → negozi\n\n        // Standard ending rules\n        yield new Transformation(new Pattern('a$'), 'e');  // -a → -e\n        yield new Transformation(new Pattern('e$'), 'i');  // -e → -i\n        yield new Transformation(new Pattern('o$'), 'i');  // -o → -i\n    }\n\n    /** @return iterable<Substitution> */\n    public static function getIrregular(): iterable\n    {\n        // Irregular substitutions (singular => plural)\n        $irregulars = [\n            'ala' => 'ali',\n            'albergo' => 'alberghi',\n            'amica' => 'amiche',\n            'amico' => 'amici',\n            'ampio' => 'ampi',\n            'arancia' => 'arance',\n            'arma' => 'armi',\n            'asparago' => 'asparagi',\n            'banca' => 'banche',\n            'belga' => 'belgi',\n            'braccio' => 'braccia',\n            'budello' => 'budella',\n            'bue' => 'buoi',\n            'caccia' => 'cacce',\n            'calcagno' => 'calcagna',\n            'camicia' => 'camicie',\n            'cane' => 'cani',\n            'capitale' => 'capitali',\n            'carcere' => 'carceri',\n            'casa' => 'case',\n            'cavaliere' => 'cavalieri',\n            'centinaio' => 'centinaia',\n            'cerchio' => 'cerchia',\n            'cervello' => 'cervella',\n            'chiave' => 'chiavi',\n            'chirurgo' => 'chirurgi',\n            'ciglio' => 'ciglia',\n            'città' => 'città',\n            'corno' => 'corna',\n            'corpo' => 'corpi',\n            'crisi' => 'crisi',\n            'dente' => 'denti',\n            'dio' => 'dei',\n            'dito' => 'dita',\n            'dottore' => 'dottori',\n            'fiore' => 'fiori',\n            'fratello' => 'fratelli',\n            'fuoco' => 'fuochi',\n            'gamba' => 'gambe',\n            'ginocchio' => 'ginocchia',\n            'gioco' => 'giochi',\n            'giornale' => 'giornali',\n            'giraffa' => 'giraffe',\n            'labbro' => 'labbra',\n            'lenzuolo' => 'lenzuola',\n            'libro' => 'libri',\n            'madre' => 'madri',\n            'maestro' => 'maestri',\n            'magico' => 'magici',\n            'mago' => 'maghi',\n            'maniaco' => 'maniaci',\n            'manico' => 'manici',\n            'mano' => 'mani',\n            'medico' => 'medici',\n            'membro' => 'membri',\n            'metropoli' => 'metropoli',\n            'migliaio' => 'migliaia',\n            'miglio' => 'miglia',\n            'mille' => 'mila',\n            'mio' => 'miei',\n            'moglie' => 'mogli',\n            'mosaico' => 'mosaici',\n            'muro' => 'muri',\n            'nemico' => 'nemici',\n            'nome' => 'nomi',\n            'occhio' => 'occhi',\n            'orecchio' => 'orecchi',\n            'osso' => 'ossa',\n            'paio' => 'paia',\n            'pane' => 'pani',\n            'papa' => 'papi',\n            'pasta' => 'paste',\n            'penna' => 'penne',\n            'pesce' => 'pesci',\n            'piede' => 'piedi',\n            'pittore' => 'pittori',\n            'poeta' => 'poeti',\n            'porco' => 'porci',\n            'porto' => 'porti',\n            'problema' => 'problemi',\n            'ragazzo' => 'ragazzi',\n            're' => 're',\n            'rene' => 'reni',\n            'riso' => 'risa',\n            'rosa' => 'rosa',\n            'sale' => 'sali',\n            'sarto' => 'sarti',\n            'scuola' => 'scuole',\n            'serie' => 'serie',\n            'serramento' => 'serramenta',\n            'sorella' => 'sorelle',\n            'specie' => 'specie',\n            'staio' => 'staia',\n            'stazione' => 'stazioni',\n            'strido' => 'strida',\n            'strillo' => 'strilla',\n            'studio' => 'studi',\n            'suo' => 'suoi',\n            'superficie' => 'superfici',\n            'tavolo' => 'tavoli',\n            'tempio' => 'templi',\n            'treno' => 'treni',\n            'tuo' => 'tuoi',\n            'uomo' => 'uomini',\n            'uovo' => 'uova',\n            'urlo' => 'urla',\n            'valigia' => 'valigie',\n            'vestigio' => 'vestigia',\n            'vino' => 'vini',\n            'viola' => 'viola',\n            'zio' => 'zii',\n        ];\n\n        foreach ($irregulars as $singular => $plural) {\n            yield new Substitution(new Word($singular), new Word($plural));\n        }\n    }\n}\n"
  },
  {
    "path": "src/Rules/Italian/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\GenericLanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nfinal class InflectorFactory extends GenericLanguageInflectorFactory\n{\n    protected function getSingularRuleset(): Ruleset\n    {\n        return Rules::getSingularRuleset();\n    }\n\n    protected function getPluralRuleset(): Ruleset\n    {\n        return Rules::getPluralRuleset();\n    }\n}\n"
  },
  {
    "path": "src/Rules/Italian/Rules.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformations;\n\nfinal class Rules\n{\n    public static function getSingularRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getSingular()),\n            new Patterns(...Uninflected::getSingular()),\n            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()\n        );\n    }\n\n    public static function getPluralRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getPlural()),\n            new Patterns(...Uninflected::getPlural()),\n            new Substitutions(...Inflectible::getIrregular())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Rules/Italian/Uninflected.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfinal class Uninflected\n{\n    /** @return iterable<Pattern> */\n    public static function getSingular(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return iterable<Pattern> */\n    public static function getPlural(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return iterable<Pattern> */\n    private static function getDefault(): iterable\n    {\n        // Invariable words (same form in singular and plural)\n        $invariables = [\n            'alpaca',\n            'auto',\n            'bar',\n            'blu',\n            'boia',\n            'boomerang',\n            'brindisi',\n            'campus',\n            'computer',\n            'crisi',\n            'crocevia',\n            'dopocena',\n            'film',\n            'foto',\n            'fuchsia',\n            'gnu',\n            'gorilla',\n            'gru',\n            'iguana',\n            'kamikaze',\n            'karaoke',\n            'koala',\n            'lama',\n            'menu',\n            'metropoli',\n            'moto',\n            'opossum',\n            'panda',\n            'quiz',\n            'radio',\n            're',\n            'scacciapensieri',\n            'serie',\n            'smartphone',\n            'sosia',\n            'sottoscala',\n            'specie',\n            'sport',\n            'tablet',\n            'taxi',\n            'vaglia',\n            'virtù',\n            'virus',\n            'yogurt',\n            'foto',\n            'fuchsia',\n        ];\n\n        foreach ($invariables as $word) {\n            yield new Pattern($word);\n        }\n    }\n}\n"
  },
  {
    "path": "src/Rules/NorwegianBokmal/Inflectible.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Word;\n\nclass Inflectible\n{\n    /** @return Transformation[] */\n    public static function getSingular(): iterable\n    {\n        yield new Transformation(new Pattern('/re$/i'), 'r');\n        yield new Transformation(new Pattern('/er$/i'), '');\n    }\n\n    /** @return Transformation[] */\n    public static function getPlural(): iterable\n    {\n        yield new Transformation(new Pattern('/e$/i'), 'er');\n        yield new Transformation(new Pattern('/r$/i'), 're');\n        yield new Transformation(new Pattern('/$/'), 'er');\n    }\n\n    /** @return Substitution[] */\n    public static function getIrregular(): iterable\n    {\n        yield new Substitution(new Word('konto'), new Word('konti'));\n    }\n}\n"
  },
  {
    "path": "src/Rules/NorwegianBokmal/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\GenericLanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nfinal class InflectorFactory extends GenericLanguageInflectorFactory\n{\n    protected function getSingularRuleset(): Ruleset\n    {\n        return Rules::getSingularRuleset();\n    }\n\n    protected function getPluralRuleset(): Ruleset\n    {\n        return Rules::getPluralRuleset();\n    }\n}\n"
  },
  {
    "path": "src/Rules/NorwegianBokmal/Rules.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformations;\n\nfinal class Rules\n{\n    public static function getSingularRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getSingular()),\n            new Patterns(...Uninflected::getSingular()),\n            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()\n        );\n    }\n\n    public static function getPluralRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getPlural()),\n            new Patterns(...Uninflected::getPlural()),\n            new Substitutions(...Inflectible::getIrregular())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Rules/NorwegianBokmal/Uninflected.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfinal class Uninflected\n{\n    /** @return Pattern[] */\n    public static function getSingular(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    public static function getPlural(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    private static function getDefault(): iterable\n    {\n        yield new Pattern('barn');\n        yield new Pattern('fjell');\n        yield new Pattern('hus');\n    }\n}\n"
  },
  {
    "path": "src/Rules/Pattern.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse function preg_match;\n\nfinal class Pattern\n{\n    /** @var string */\n    private $pattern;\n\n    /** @var string */\n    private $regex;\n\n    public function __construct(string $pattern)\n    {\n        $this->pattern = $pattern;\n\n        if (isset($this->pattern[0]) && $this->pattern[0] === '/') {\n            $this->regex = $this->pattern;\n        } else {\n            $this->regex = '/' . $this->pattern . '/i';\n        }\n    }\n\n    public function getPattern(): string\n    {\n        return $this->pattern;\n    }\n\n    public function getRegex(): string\n    {\n        return $this->regex;\n    }\n\n    public function matches(string $word): bool\n    {\n        return preg_match($this->getRegex(), $word) === 1;\n    }\n}\n"
  },
  {
    "path": "src/Rules/Patterns.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse function array_map;\nuse function implode;\nuse function preg_match;\n\nclass Patterns\n{\n    /** @var string */\n    private $regex;\n\n    public function __construct(Pattern ...$patterns)\n    {\n        $patterns = array_map(static function (Pattern $pattern): string {\n            return $pattern->getPattern();\n        }, $patterns);\n\n        $this->regex = '/^(?:' . implode('|', $patterns) . ')$/i';\n    }\n\n    public function matches(string $word): bool\n    {\n        return preg_match($this->regex, $word, $regs) === 1;\n    }\n}\n"
  },
  {
    "path": "src/Rules/Portuguese/Inflectible.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Word;\n\nclass Inflectible\n{\n    /** @return Transformation[] */\n    public static function getSingular(): iterable\n    {\n        yield new Transformation(new Pattern('/^(g|)ases$/i'), '\\1ás');\n        yield new Transformation(new Pattern('/(japon|escoc|ingl|dinamarqu|fregu|portugu)eses$/i'), '\\1ês');\n        yield new Transformation(new Pattern('/(ae|ao|oe)s$/'), 'ao');\n        yield new Transformation(new Pattern('/(ãe|ão|õe)s$/'), 'ão');\n        yield new Transformation(new Pattern('/^(.*[^s]s)es$/i'), '\\1');\n        yield new Transformation(new Pattern('/sses$/i'), 'sse');\n        yield new Transformation(new Pattern('/ns$/i'), 'm');\n        yield new Transformation(new Pattern('/(r|t|f|v)is$/i'), '\\1il');\n        yield new Transformation(new Pattern('/uis$/i'), 'ul');\n        yield new Transformation(new Pattern('/ois$/i'), 'ol');\n        yield new Transformation(new Pattern('/eis$/i'), 'ei');\n        yield new Transformation(new Pattern('/éis$/i'), 'el');\n        yield new Transformation(new Pattern('/([^p])ais$/i'), '\\1al');\n        yield new Transformation(new Pattern('/(r|z)es$/i'), '\\1');\n        yield new Transformation(new Pattern('/^(á|gá)s$/i'), '\\1s');\n        yield new Transformation(new Pattern('/([^ê])s$/i'), '\\1');\n    }\n\n    /** @return Transformation[] */\n    public static function getPlural(): iterable\n    {\n        yield new Transformation(new Pattern('/^(alem|c|p)ao$/i'), '\\1aes');\n        yield new Transformation(new Pattern('/^(irm|m)ao$/i'), '\\1aos');\n        yield new Transformation(new Pattern('/ao$/i'), 'oes');\n        yield new Transformation(new Pattern('/^(alem|c|p)ão$/i'), '\\1ães');\n        yield new Transformation(new Pattern('/^(irm|m)ão$/i'), '\\1ãos');\n        yield new Transformation(new Pattern('/ão$/i'), 'ões');\n        yield new Transformation(new Pattern('/^(|g)ás$/i'), '\\1ases');\n        yield new Transformation(new Pattern('/^(japon|escoc|ingl|dinamarqu|fregu|portugu)ês$/i'), '\\1eses');\n        yield new Transformation(new Pattern('/m$/i'), 'ns');\n        yield new Transformation(new Pattern('/([^aeou])il$/i'), '\\1is');\n        yield new Transformation(new Pattern('/ul$/i'), 'uis');\n        yield new Transformation(new Pattern('/ol$/i'), 'ois');\n        yield new Transformation(new Pattern('/el$/i'), 'eis');\n        yield new Transformation(new Pattern('/al$/i'), 'ais');\n        yield new Transformation(new Pattern('/(z|r)$/i'), '\\1es');\n        yield new Transformation(new Pattern('/(s)$/i'), '\\1');\n        yield new Transformation(new Pattern('/$/'), 's');\n    }\n\n    /** @return Substitution[] */\n    public static function getIrregular(): iterable\n    {\n        yield new Substitution(new Word('abdomen'), new Word('abdomens'));\n        yield new Substitution(new Word('alemão'), new Word('alemães'));\n        yield new Substitution(new Word('artesã'), new Word('artesãos'));\n        yield new Substitution(new Word('álcool'), new Word('álcoois'));\n        yield new Substitution(new Word('árvore'), new Word('árvores'));\n        yield new Substitution(new Word('bencão'), new Word('bencãos'));\n        yield new Substitution(new Word('cão'), new Word('cães'));\n        yield new Substitution(new Word('campus'), new Word('campi'));\n        yield new Substitution(new Word('cadáver'), new Word('cadáveres'));\n        yield new Substitution(new Word('capelão'), new Word('capelães'));\n        yield new Substitution(new Word('capitão'), new Word('capitães'));\n        yield new Substitution(new Word('chão'), new Word('chãos'));\n        yield new Substitution(new Word('charlatão'), new Word('charlatães'));\n        yield new Substitution(new Word('cidadão'), new Word('cidadãos'));\n        yield new Substitution(new Word('cônsul'), new Word('cônsules'));\n        yield new Substitution(new Word('cristão'), new Word('cristãos'));\n        yield new Substitution(new Word('difícil'), new Word('difíceis'));\n        yield new Substitution(new Word('email'), new Word('emails'));\n        yield new Substitution(new Word('escrivão'), new Word('escrivães'));\n        yield new Substitution(new Word('fóssil'), new Word('fósseis'));\n        yield new Substitution(new Word('gás'), new Word('gases'));\n        yield new Substitution(new Word('germens'), new Word('germen'));\n        yield new Substitution(new Word('grão'), new Word('grãos'));\n        yield new Substitution(new Word('hífen'), new Word('hífens'));\n        yield new Substitution(new Word('irmão'), new Word('irmãos'));\n        yield new Substitution(new Word('liquens'), new Word('liquen'));\n        yield new Substitution(new Word('mal'), new Word('males'));\n        yield new Substitution(new Word('mão'), new Word('mãos'));\n        yield new Substitution(new Word('mês'), new Word('meses'));\n        yield new Substitution(new Word('orfão'), new Word('orfãos'));\n        yield new Substitution(new Word('país'), new Word('países'));\n        yield new Substitution(new Word('pai'), new Word('pais'));\n        yield new Substitution(new Word('pão'), new Word('pães'));\n        yield new Substitution(new Word('projétil'), new Word('projéteis'));\n        yield new Substitution(new Word('réptil'), new Word('répteis'));\n        yield new Substitution(new Word('sacristão'), new Word('sacristães'));\n        yield new Substitution(new Word('sotão'), new Word('sotãos'));\n        yield new Substitution(new Word('tabelião'), new Word('tabeliães'));\n        yield new Substitution(new Word('útil'), new Word('úteis'));\n    }\n}\n"
  },
  {
    "path": "src/Rules/Portuguese/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\GenericLanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nfinal class InflectorFactory extends GenericLanguageInflectorFactory\n{\n    protected function getSingularRuleset(): Ruleset\n    {\n        return Rules::getSingularRuleset();\n    }\n\n    protected function getPluralRuleset(): Ruleset\n    {\n        return Rules::getPluralRuleset();\n    }\n}\n"
  },
  {
    "path": "src/Rules/Portuguese/Rules.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformations;\n\nfinal class Rules\n{\n    public static function getSingularRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getSingular()),\n            new Patterns(...Uninflected::getSingular()),\n            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()\n        );\n    }\n\n    public static function getPluralRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getPlural()),\n            new Patterns(...Uninflected::getPlural()),\n            new Substitutions(...Inflectible::getIrregular())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Rules/Portuguese/Uninflected.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfinal class Uninflected\n{\n    /** @return Pattern[] */\n    public static function getSingular(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    public static function getPlural(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    private static function getDefault(): iterable\n    {\n        yield new Pattern('atlas');\n        yield new Pattern('bate-papo');\n        yield new Pattern('cais');\n        yield new Pattern('fênix');\n        yield new Pattern('guarda-chuva');\n        yield new Pattern('guarda-roupa');\n        yield new Pattern('lápis');\n        yield new Pattern('oásis');\n        yield new Pattern('ônibus');\n        yield new Pattern('ônus');\n        yield new Pattern('pára-brisa');\n        yield new Pattern('pára-choque');\n        yield new Pattern('pires');\n        yield new Pattern('porta-malas');\n        yield new Pattern('porta-voz');\n        yield new Pattern('sem-terra');\n        yield new Pattern('tênis');\n        yield new Pattern('tórax');\n        yield new Pattern('vírus');\n    }\n}\n"
  },
  {
    "path": "src/Rules/Ruleset.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nclass Ruleset\n{\n    /** @var Transformations */\n    private $regular;\n\n    /** @var Patterns */\n    private $uninflected;\n\n    /** @var Substitutions */\n    private $irregular;\n\n    public function __construct(Transformations $regular, Patterns $uninflected, Substitutions $irregular)\n    {\n        $this->regular     = $regular;\n        $this->uninflected = $uninflected;\n        $this->irregular   = $irregular;\n    }\n\n    public function getRegular(): Transformations\n    {\n        return $this->regular;\n    }\n\n    public function getUninflected(): Patterns\n    {\n        return $this->uninflected;\n    }\n\n    public function getIrregular(): Substitutions\n    {\n        return $this->irregular;\n    }\n}\n"
  },
  {
    "path": "src/Rules/Spanish/Inflectible.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Word;\n\nclass Inflectible\n{\n    /** @return Transformation[] */\n    public static function getSingular(): iterable\n    {\n        yield new Transformation(new Pattern('/ereses$/'), 'erés');\n        yield new Transformation(new Pattern('/iones$/'), 'ión');\n        yield new Transformation(new Pattern('/ces$/'), 'z');\n        yield new Transformation(new Pattern('/es$/'), '');\n        yield new Transformation(new Pattern('/s$/'), '');\n    }\n\n    /** @return Transformation[] */\n    public static function getPlural(): iterable\n    {\n        yield new Transformation(new Pattern('/ú([sn])$/i'), 'u\\1es');\n        yield new Transformation(new Pattern('/ó([sn])$/i'), 'o\\1es');\n        yield new Transformation(new Pattern('/í([sn])$/i'), 'i\\1es');\n        yield new Transformation(new Pattern('/é([sn])$/i'), 'e\\1es');\n        yield new Transformation(new Pattern('/á([sn])$/i'), 'a\\1es');\n        yield new Transformation(new Pattern('/z$/i'), 'ces');\n        yield new Transformation(new Pattern('/([aeiou]s)$/i'), '\\1');\n        yield new Transformation(new Pattern('/([^aeéiou])$/i'), '\\1es');\n        yield new Transformation(new Pattern('/$/'), 's');\n    }\n\n    /** @return Substitution[] */\n    public static function getIrregular(): iterable\n    {\n        yield new Substitution(new Word('el'), new Word('los'));\n        yield new Substitution(new Word('papá'), new Word('papás'));\n        yield new Substitution(new Word('mamá'), new Word('mamás'));\n        yield new Substitution(new Word('sofá'), new Word('sofás'));\n        yield new Substitution(new Word('mes'), new Word('meses'));\n    }\n}\n"
  },
  {
    "path": "src/Rules/Spanish/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\GenericLanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nfinal class InflectorFactory extends GenericLanguageInflectorFactory\n{\n    protected function getSingularRuleset(): Ruleset\n    {\n        return Rules::getSingularRuleset();\n    }\n\n    protected function getPluralRuleset(): Ruleset\n    {\n        return Rules::getPluralRuleset();\n    }\n}\n"
  },
  {
    "path": "src/Rules/Spanish/Rules.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformations;\n\nfinal class Rules\n{\n    public static function getSingularRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getSingular()),\n            new Patterns(...Uninflected::getSingular()),\n            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()\n        );\n    }\n\n    public static function getPluralRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getPlural()),\n            new Patterns(...Uninflected::getPlural()),\n            new Substitutions(...Inflectible::getIrregular())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Rules/Spanish/Uninflected.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfinal class Uninflected\n{\n    /** @return Pattern[] */\n    public static function getSingular(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    public static function getPlural(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    private static function getDefault(): iterable\n    {\n        yield new Pattern('lunes');\n        yield new Pattern('rompecabezas');\n        yield new Pattern('crisis');\n    }\n}\n"
  },
  {
    "path": "src/Rules/Substitution.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nfinal class Substitution\n{\n    /** @var Word */\n    private $from;\n\n    /** @var Word */\n    private $to;\n\n    public function __construct(Word $from, Word $to)\n    {\n        $this->from = $from;\n        $this->to   = $to;\n    }\n\n    public function getFrom(): Word\n    {\n        return $this->from;\n    }\n\n    public function getTo(): Word\n    {\n        return $this->to;\n    }\n}\n"
  },
  {
    "path": "src/Rules/Substitutions.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\WordInflector;\n\nuse function strtolower;\nuse function strtoupper;\nuse function substr;\n\nclass Substitutions implements WordInflector\n{\n    /** @var Substitution[] */\n    private $substitutions;\n\n    public function __construct(Substitution ...$substitutions)\n    {\n        foreach ($substitutions as $substitution) {\n            $this->substitutions[$substitution->getFrom()->getWord()] = $substitution;\n        }\n    }\n\n    public function getFlippedSubstitutions(): Substitutions\n    {\n        $substitutions = [];\n\n        foreach ($this->substitutions as $substitution) {\n            $substitutions[] = new Substitution(\n                $substitution->getTo(),\n                $substitution->getFrom()\n            );\n        }\n\n        return new Substitutions(...$substitutions);\n    }\n\n    public function inflect(string $word): string\n    {\n        $lowerWord = strtolower($word);\n\n        if (isset($this->substitutions[$lowerWord])) {\n            $firstLetterUppercase = $lowerWord[0] !== $word[0];\n\n            $toWord = $this->substitutions[$lowerWord]->getTo()->getWord();\n\n            if ($firstLetterUppercase) {\n                return strtoupper($toWord[0]) . substr($toWord, 1);\n            }\n\n            return $toWord;\n        }\n\n        return $word;\n    }\n}\n"
  },
  {
    "path": "src/Rules/Transformation.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\WordInflector;\n\nuse function preg_replace;\n\nfinal class Transformation implements WordInflector\n{\n    /** @var Pattern */\n    private $pattern;\n\n    /** @var string */\n    private $replacement;\n\n    public function __construct(Pattern $pattern, string $replacement)\n    {\n        $this->pattern     = $pattern;\n        $this->replacement = $replacement;\n    }\n\n    public function getPattern(): Pattern\n    {\n        return $this->pattern;\n    }\n\n    public function getReplacement(): string\n    {\n        return $this->replacement;\n    }\n\n    public function inflect(string $word): string\n    {\n        return (string) preg_replace($this->pattern->getRegex(), $this->replacement, $word);\n    }\n}\n"
  },
  {
    "path": "src/Rules/Transformations.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\WordInflector;\n\nclass Transformations implements WordInflector\n{\n    /** @var Transformation[] */\n    private $transformations;\n\n    public function __construct(Transformation ...$transformations)\n    {\n        $this->transformations = $transformations;\n    }\n\n    public function inflect(string $word): string\n    {\n        foreach ($this->transformations as $transformation) {\n            if ($transformation->getPattern()->matches($word)) {\n                return $transformation->inflect($word);\n            }\n        }\n\n        return $word;\n    }\n}\n"
  },
  {
    "path": "src/Rules/Turkish/Inflectible.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Word;\n\nclass Inflectible\n{\n    /** @return Transformation[] */\n    public static function getSingular(): iterable\n    {\n        yield new Transformation(new Pattern('/l[ae]r$/i'), '');\n    }\n\n    /** @return Transformation[] */\n    public static function getPlural(): iterable\n    {\n        yield new Transformation(new Pattern('/([eöiü][^aoıueöiü]{0,6})$/u'), '\\1ler');\n        yield new Transformation(new Pattern('/([aoıu][^aoıueöiü]{0,6})$/u'), '\\1lar');\n    }\n\n    /** @return Substitution[] */\n    public static function getIrregular(): iterable\n    {\n        yield new Substitution(new Word('ben'), new Word('biz'));\n        yield new Substitution(new Word('sen'), new Word('siz'));\n        yield new Substitution(new Word('o'), new Word('onlar'));\n    }\n}\n"
  },
  {
    "path": "src/Rules/Turkish/InflectorFactory.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\GenericLanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nfinal class InflectorFactory extends GenericLanguageInflectorFactory\n{\n    protected function getSingularRuleset(): Ruleset\n    {\n        return Rules::getSingularRuleset();\n    }\n\n    protected function getPluralRuleset(): Ruleset\n    {\n        return Rules::getPluralRuleset();\n    }\n}\n"
  },
  {
    "path": "src/Rules/Turkish/Rules.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformations;\n\nfinal class Rules\n{\n    public static function getSingularRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getSingular()),\n            new Patterns(...Uninflected::getSingular()),\n            (new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()\n        );\n    }\n\n    public static function getPluralRuleset(): Ruleset\n    {\n        return new Ruleset(\n            new Transformations(...Inflectible::getPlural()),\n            new Patterns(...Uninflected::getPlural()),\n            new Substitutions(...Inflectible::getIrregular())\n        );\n    }\n}\n"
  },
  {
    "path": "src/Rules/Turkish/Uninflected.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\n\nfinal class Uninflected\n{\n    /** @return Pattern[] */\n    public static function getSingular(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    public static function getPlural(): iterable\n    {\n        yield from self::getDefault();\n    }\n\n    /** @return Pattern[] */\n    private static function getDefault(): iterable\n    {\n        yield new Pattern('lunes');\n        yield new Pattern('rompecabezas');\n        yield new Pattern('crisis');\n    }\n}\n"
  },
  {
    "path": "src/Rules/Word.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector\\Rules;\n\nclass Word\n{\n    /** @var string */\n    private $word;\n\n    public function __construct(string $word)\n    {\n        $this->word = $word;\n    }\n\n    public function getWord(): string\n    {\n        return $this->word;\n    }\n}\n"
  },
  {
    "path": "src/RulesetInflector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\Ruleset;\n\nuse function array_merge;\n\n/**\n * Inflects based on multiple rulesets.\n *\n * Rules:\n * - If the word matches any uninflected word pattern, it is not inflected\n * - The first ruleset that returns a different value for an irregular word wins\n * - The first ruleset that returns a different value for a regular word wins\n * - If none of the above match, the word is left as-is\n */\nclass RulesetInflector implements WordInflector\n{\n    /** @var Ruleset[] */\n    private $rulesets;\n\n    public function __construct(Ruleset $ruleset, Ruleset ...$rulesets)\n    {\n        $this->rulesets = array_merge([$ruleset], $rulesets);\n    }\n\n    public function inflect(string $word): string\n    {\n        if ($word === '') {\n            return '';\n        }\n\n        foreach ($this->rulesets as $ruleset) {\n            if ($ruleset->getUninflected()->matches($word)) {\n                return $word;\n            }\n\n            $inflected = $ruleset->getIrregular()->inflect($word);\n\n            if ($inflected !== $word) {\n                return $inflected;\n            }\n\n            $inflected = $ruleset->getRegular()->inflect($word);\n\n            if ($inflected !== $word) {\n                return $inflected;\n            }\n        }\n\n        return $word;\n    }\n}\n"
  },
  {
    "path": "src/WordInflector.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Inflector;\n\ninterface WordInflector\n{\n    public function inflect(string $word): string;\n}\n"
  },
  {
    "path": "tests/CachedWordInflectorTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\CachedWordInflector;\nuse Doctrine\\Inflector\\WordInflector;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse PHPUnit\\Framework\\TestCase;\n\nclass CachedWordInflectorTest extends TestCase\n{\n    /** @var WordInflector&MockObject */\n    private $wordInflector;\n\n    /** @var CachedWordInflector */\n    private $cachedWordInflector;\n\n    public function testInflect(): void\n    {\n        $this->wordInflector->expects(self::once())\n            ->method('inflect')\n            ->with('in')\n            ->willReturn('out');\n\n        self::assertSame('out', $this->cachedWordInflector->inflect('in'));\n        self::assertSame('out', $this->cachedWordInflector->inflect('in'));\n    }\n\n    protected function setUp(): void\n    {\n        $this->wordInflector = $this->createMock(WordInflector::class);\n\n        $this->cachedWordInflector = new CachedWordInflector(\n            $this->wordInflector\n        );\n    }\n}\n"
  },
  {
    "path": "tests/InflectorFactoryTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Inflector\\LanguageInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\English\\InflectorFactory as EnglishInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Esperanto\\InflectorFactory as EsperantoInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\French\\InflectorFactory as FrenchInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\NorwegianBokmal\\InflectorFactory as NorwegianBokmalInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Portuguese\\InflectorFactory as PortugueseInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Spanish\\InflectorFactory as SpanishInflectorFactory;\nuse Doctrine\\Inflector\\Rules\\Turkish\\InflectorFactory as TurkishInflectorFactory;\nuse Generator;\nuse InvalidArgumentException;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nclass InflectorFactoryTest extends TestCase\n{\n    public function testCreateUsesEnglishByDefault(): void\n    {\n        self::assertInstanceOf(EnglishInflectorFactory::class, InflectorFactory::create());\n    }\n\n    /**\n     * @phpstan-param class-string<LanguageInflectorFactory> $expectedClass\n     *\n     * @dataProvider provideLanguages\n     */\n    #[DataProvider('provideLanguages')]\n    public function testCreateForLanguageWithCustomLanguage(string $expectedClass, string $language): void\n    {\n        self::assertInstanceOf($expectedClass, InflectorFactory::createForLanguage($language));\n    }\n\n    /** @phpstan-return Generator<string, array{class-string<LanguageInflectorFactory>, Language::*}> */\n    public static function provideLanguages(): Generator\n    {\n        yield 'English' => [EnglishInflectorFactory::class, Language::ENGLISH];\n        yield 'Esperanto' => [EsperantoInflectorFactory::class, Language::ESPERANTO];\n        yield 'French' => [FrenchInflectorFactory::class, Language::FRENCH];\n        yield 'Norwegian Bokmal' => [NorwegianBokmalInflectorFactory::class, Language::NORWEGIAN_BOKMAL];\n        yield 'Portuguese' => [PortugueseInflectorFactory::class, Language::PORTUGUESE];\n        yield 'Spanish' => [SpanishInflectorFactory::class, Language::SPANISH];\n        yield 'Turkish' => [TurkishInflectorFactory::class, Language::TURKISH];\n    }\n\n    public function testCreateForLanguageThrowsInvalidArgumentExceptionForUnsupportedLanguage(): void\n    {\n        $this->expectException(InvalidArgumentException::class);\n        $this->expectExceptionMessage('Language \"invalid\" is not supported.');\n\n        InflectorFactory::createForLanguage('invalid')->build();\n    }\n}\n"
  },
  {
    "path": "tests/InflectorFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nclass InflectorFunctionalTest extends TestCase\n{\n    public function testCapitalize(): void\n    {\n        self::assertSame(\n            'Top-O-The-Morning To All_of_you!',\n            $this->createInflector()->capitalize('top-o-the-morning to all_of_you!')\n        );\n    }\n\n    public function testCapitalizeWithCustomDelimiters(): void\n    {\n        self::assertSame(\n            'Top-O-The-Morning To All_Of_You!',\n            $this->createInflector()->capitalize('top-o-the-morning to all_of_you!', '-_ ')\n        );\n    }\n\n    /** @dataProvider dataStringsTableize */\n    #[DataProvider('dataStringsTableize')]\n    public function testTableize(string $expected, string $word): void\n    {\n        self::assertSame($expected, $this->createInflector()->tableize($word));\n    }\n\n    /**\n     * Strings which are used for testTableize.\n     *\n     * @return string[][]\n     */\n    public static function dataStringsTableize(): array\n    {\n        // In the format array('expected', 'word')\n        return [\n            ['', ''],\n            ['foo_bar', 'FooBar'],\n            ['f0o_bar', 'F0oBar'],\n        ];\n    }\n\n    /** @dataProvider dataStringsClassify */\n    #[DataProvider('dataStringsClassify')]\n    public function testClassify(string $expected, string $word): void\n    {\n        self::assertSame($expected, $this->createInflector()->classify($word));\n    }\n\n    /**\n     * Strings which are used for testClassify.\n     *\n     * @return string[][]\n     */\n    public static function dataStringsClassify(): array\n    {\n        // In the format array('expected', 'word')\n        return [\n            ['', ''],\n            ['FooBar', 'foo_bar'],\n            ['FooBar', 'foo bar'],\n            ['F0oBar', 'f0o bar'],\n            ['F0oBar', 'f0o  bar'],\n            ['FooBar', 'foo_bar_'],\n        ];\n    }\n\n    /** @dataProvider dataStringsCamelize */\n    #[DataProvider('dataStringsCamelize')]\n    public function testCamelize(string $expected, string $word): void\n    {\n        self::assertSame($expected, $this->createInflector()->camelize($word));\n    }\n\n    /**\n     * Strings which are used for testCamelize.\n     *\n     * @return string[][]\n     */\n    public static function dataStringsCamelize(): array\n    {\n        // In the format array('expected', 'word')\n        return [\n            ['', ''],\n            ['fooBar', 'foo_bar'],\n            ['fooBar', 'foo bar'],\n            ['f0oBar', 'f0o bar'],\n            ['f0oBar', 'f0o  bar'],\n        ];\n    }\n\n    private function createInflector(): Inflector\n    {\n        return InflectorFactory::create()->build();\n    }\n}\n"
  },
  {
    "path": "tests/InflectorTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\WordInflector;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\MockObject\\Stub;\nuse PHPUnit\\Framework\\TestCase;\n\nclass InflectorTest extends TestCase\n{\n    /** @var WordInflector&Stub */\n    private $singularInflector;\n\n    /** @var WordInflector&Stub */\n    private $pluralInflector;\n\n    /** @var Inflector */\n    private $inflector;\n\n    public function testTableize(): void\n    {\n        self::assertSame('model_name', $this->inflector->tableize('ModelName'));\n        self::assertSame('model_name', $this->inflector->tableize('modelName'));\n        self::assertSame('model_name', $this->inflector->tableize('model_name'));\n    }\n\n    public function testClassify(): void\n    {\n        self::assertSame('ModelName', $this->inflector->classify('model_name'));\n        self::assertSame('ModelName', $this->inflector->classify('modelName'));\n        self::assertSame('ModelName', $this->inflector->classify('ModelName'));\n    }\n\n    public function testCamelize(): void\n    {\n        self::assertSame('modelName', $this->inflector->camelize('ModelName'));\n        self::assertSame('modelName', $this->inflector->camelize('model_name'));\n        self::assertSame('modelName', $this->inflector->camelize('modelName'));\n    }\n\n    public function testCapitalize(): void\n    {\n        self::assertSame(\n            'Top-O-The-Morning To All_of_you!',\n            $this->inflector->capitalize('top-o-the-morning to all_of_you!')\n        );\n    }\n\n    public function testSeemsUtf8(): void\n    {\n        self::assertTrue($this->inflector->seemsUtf8('teléfono'));\n        self::assertTrue($this->inflector->seemsUtf8('král'));\n        self::assertTrue($this->inflector->seemsUtf8('telephone'));\n    }\n\n    public function testUnaccent(): void\n    {\n        self::assertSame('telefono', $this->inflector->unaccent('teléfono'));\n        self::assertSame('telephone', $this->inflector->unaccent('telephone'));\n    }\n\n    /** @dataProvider dataStringsUrlize */\n    #[DataProvider('dataStringsUrlize')]\n    public function testUrlize(string $expected, string $string): void\n    {\n        self::assertSame(\n            $expected,\n            $this->inflector->urlize($string)\n        );\n    }\n\n    /**\n     * Strings which are used for testUrlize.\n     *\n     * @return string[][]\n     */\n    public static function dataStringsUrlize(): array\n    {\n        return [\n            [\n                'testing-creating-a-slug-from-a-random-string',\n                'Testing_Creating a -Slug from a random-string!@#',\n            ],\n            [\n                'contesta-el-telefono',\n                'Contesta el teléfono',\n            ],\n            [\n                'den-hund-fuettern',\n                'den hund füttern',\n            ],\n            [\n                'jsem-kral-na-severu',\n                'Jsem král na severu',\n            ],\n            [\n                'test1-test2',\n                'test1::test2',\n            ],\n            [\n                'test1-test2',\n                'test1$test2',\n            ],\n            [\n                'testab-test2',\n                'TESTAb-test2',\n            ],\n            [\n                'ano',\n                'año',\n            ],\n        ];\n    }\n\n    public function testPluralize(): void\n    {\n        $pluralInflector = $this->createMock(WordInflector::class);\n\n        $pluralInflector->expects(self::once())\n            ->method('inflect')\n            ->with('in')\n            ->willReturn('out');\n\n        $inflector = new Inflector($this->singularInflector, $pluralInflector);\n\n        self::assertSame('out', $inflector->pluralize('in'));\n    }\n\n    public function testSingularize(): void\n    {\n        $singularInflector = $this->createMock(WordInflector::class);\n\n        $inflector = new Inflector($singularInflector, $this->pluralInflector);\n        $singularInflector->expects(self::once())\n            ->method('inflect')\n            ->with('in')\n            ->willReturn('out');\n\n        self::assertSame('out', $inflector->singularize('in'));\n    }\n\n    protected function setUp(): void\n    {\n        $this->singularInflector = self::createStub(WordInflector::class);\n        $this->pluralInflector   = self::createStub(WordInflector::class);\n\n        $this->inflector = new Inflector($this->singularInflector, $this->pluralInflector);\n    }\n}\n"
  },
  {
    "path": "tests/NoopWordInflectorTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\NoopWordInflector;\nuse PHPUnit\\Framework\\TestCase;\n\nclass NoopWordInflectorTest extends TestCase\n{\n    /** @var NoopWordInflector */\n    private $inflector;\n\n    public function testInflect(): void\n    {\n        self::assertSame('foo', $this->inflector->inflect('foo'));\n        self::assertSame('bar', $this->inflector->inflect('bar'));\n    }\n\n    protected function setUp(): void\n    {\n        $this->inflector = new NoopWordInflector();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/English/EnglishFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\English;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Tests\\Inflector\\Rules\\LanguageFunctionalTestCase;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\n\nuse function sprintf;\n\nclass EnglishFunctionalTest extends LanguageFunctionalTestCase\n{\n    /** @return string[][] */\n    public static function dataSampleWords(): array\n    {\n        return [\n            ['', ''],\n            ['ability', 'abilities'],\n            ['abuse', 'abuses'],\n            ['acceptancecriterion', 'acceptancecriteria'],\n            ['address', 'addresses'],\n            ['advice', 'advice'],\n            ['agency', 'agencies'],\n            ['aircraft', 'aircraft'],\n            ['alga', 'algae'],\n            ['alias', 'aliases'],\n            ['alumnus', 'alumni'],\n            ['amoyese', 'amoyese'],\n            ['analysis', 'analyses'],\n            ['aquarium', 'aquaria'],\n            ['arch', 'arches'],\n            ['archive', 'archives'],\n            ['art', 'art'],\n            ['atlas', 'atlases'],\n            ['audio', 'audio'],\n            ['avalanche', 'avalanches'],\n            ['axe', 'axes'],\n            ['baby', 'babies'],\n            ['bacillus', 'bacilli'],\n            ['bacterium', 'bacteria'],\n            ['baggage', 'baggage'],\n            ['basis', 'bases'],\n            ['bison', 'bison'],\n            ['blouse', 'blouses'],\n            ['borghese', 'borghese'],\n            ['box', 'boxes'],\n            ['bream', 'bream'],\n            ['breeches', 'breeches'],\n            ['britches', 'britches'],\n            ['buffalo', 'buffalo'],\n            ['bureau', 'bureaus'],\n            ['bus', 'buses'],\n            ['butter', 'butter'],\n            ['cache', 'caches'],\n            ['cactus', 'cacti'],\n            ['cafe', 'cafes'],\n            ['calf', 'calves'],\n            ['cantus', 'cantus'],\n            ['canvas', 'canvases'],\n            ['carp', 'carp'],\n            ['case', 'cases'],\n            ['cave', 'caves'],\n            ['categoria', 'categorias'],\n            ['category', 'categories'],\n            ['cattle', 'cattle'],\n            ['chassis', 'chassis'],\n            ['chateau', 'chateaux'],\n            ['cherry', 'cherries'],\n            ['child', 'children'],\n            ['church', 'churches'],\n            ['circus', 'circuses'],\n            ['city', 'cities'],\n            ['clippers', 'clippers'],\n            ['clothes', 'clothes'],\n            ['clothing', 'clothing'],\n            ['coal', 'coal'],\n            ['cod', 'cod'],\n            ['coitus', 'coitus'],\n            ['comment', 'comments'],\n            ['compensation', 'compensation'],\n            ['congoese', 'congoese'],\n            ['contretemps', 'contretemps'],\n            ['cookie', 'cookies'],\n            ['brownie', 'brownies'],\n            ['copy', 'copies'],\n            ['coreopsis', 'coreopsis'],\n            ['corps', 'corps'],\n            ['cotton', 'cotton'],\n            ['cow', 'cows'],\n            ['crisis', 'crises'],\n            ['criterion', 'criteria'],\n            ['currency', 'currencies'],\n            ['curriculum', 'curricula'],\n            ['curve', 'curves'],\n            ['data', 'data'],\n            ['database', 'databases'],\n            ['day', 'days'],\n            ['debris', 'debris'],\n            ['deer', 'deer'],\n            ['demo', 'demos'],\n            ['die', 'dice'],\n            ['diabetes', 'diabetes'],\n            ['diagnosis', 'diagnoses'],\n            ['diagnosis_a', 'diagnosis_as'],\n            ['dictionary', 'dictionaries'],\n            ['dive', 'dives'],\n            ['djinn', 'djinn'],\n            ['domino', 'dominoes'],\n            ['dwarf', 'dwarves'],\n            ['echo', 'echoes'],\n            ['edge', 'edges'],\n            ['education', 'education'],\n            ['eland', 'eland'],\n            ['elf', 'elves'],\n            ['elk', 'elk'],\n            ['emoji', 'emoji'],\n            ['emphasis', 'emphases'],\n            ['employee-child', 'employee-children'],\n            ['energy', 'energies'],\n            ['epoch', 'epochs'],\n            ['equipment', 'equipment'],\n            ['evidence', 'evidence'],\n            ['experience', 'experiences'],\n            ['family', 'families'],\n            ['faroese', 'faroese'],\n            ['fax', 'faxes'],\n            ['feedback', 'feedback'],\n            ['fish', 'fish'],\n            ['fix', 'fixes'],\n            ['flounder', 'flounder'],\n            ['flour', 'flour'],\n            ['flush', 'flushes'],\n            ['fly', 'flies'],\n            ['focus', 'foci'],\n            ['foe', 'foes'],\n            ['foobar', 'foobars'],\n            ['foochowese', 'foochowese'],\n            ['food', 'food'],\n            ['food_menu', 'food_menus'],\n            ['foodmenu', 'foodmenus'],\n            ['foot', 'feet'],\n            ['fungus', 'fungi'],\n            ['furniture', 'furniture'],\n            ['gallows', 'gallows'],\n            ['gas', 'gases'],\n            ['genevese', 'genevese'],\n            ['genoese', 'genoese'],\n            ['genus', 'genera'],\n            ['gilbertese', 'gilbertese'],\n            ['glove', 'gloves'],\n            ['gold', 'gold'],\n            ['goose', 'geese'],\n            ['grave', 'graves'],\n            ['gulf', 'gulfs'],\n            ['half', 'halves'],\n            ['hardware', 'hardware'],\n            ['headquarters', 'headquarters'],\n            ['hero', 'heroes'],\n            ['herpes', 'herpes'],\n            ['hijinks', 'hijinks'],\n            ['hippopotamus', 'hippopotami'],\n            ['hoax', 'hoaxes'],\n            ['homework', 'homework'],\n            ['horse', 'horses'],\n            ['hottentotese', 'hottentotese'],\n            ['house', 'houses'],\n            ['human', 'humans'],\n            ['identity', 'identities'],\n            ['impatience', 'impatience'],\n            ['index', 'indices'],\n            ['information', 'information'],\n            ['innings', 'innings'],\n            ['iris', 'irises'],\n            ['jackanapes', 'jackanapes'],\n            ['jeans', 'jeans'],\n            ['jedi', 'jedi'],\n            ['kin', 'kin'],\n            ['kiplingese', 'kiplingese'],\n            ['kiss', 'kisses'],\n            ['kitchenware', 'kitchenware'],\n            ['knife', 'knives'],\n            ['knowledge', 'knowledge'],\n            ['kongoese', 'kongoese'],\n            ['larva', 'larvae'],\n            ['leaf', 'leaves'],\n            ['leather', 'leather'],\n            ['lens', 'lenses'],\n            ['life', 'lives'],\n            ['loaf', 'loaves'],\n            ['louse', 'lice'],\n            ['love', 'love'],\n            ['lucchese', 'lucchese'],\n            ['luggage', 'luggage'],\n            ['mackerel', 'mackerel'],\n            ['maltese', 'maltese'],\n            ['man', 'men'],\n            ['management', 'management'],\n            ['matrix', 'matrices'],\n            ['matrix_fu', 'matrix_fus'],\n            ['matrix_row', 'matrix_rows'],\n            ['medium', 'media'],\n            ['memorandum', 'memoranda'],\n            ['menu', 'menus'],\n            ['mess', 'messes'],\n            ['metadata', 'metadata'],\n            ['mews', 'mews'],\n            ['middleware', 'middleware'],\n            ['money', 'money'],\n            ['moose', 'moose'],\n            ['motto', 'mottoes'],\n            ['mouse', 'mice'],\n            ['move', 'moves'],\n            ['movie', 'movies'],\n            ['mumps', 'mumps'],\n            ['music', 'music'],\n            ['my_analysis', 'my_analyses'],\n            ['nankingese', 'nankingese'],\n            ['neurosis', 'neuroses'],\n            ['news', 'news'],\n            ['newsletter', 'newsletters'],\n            ['nexus', 'nexus'],\n            ['niasese', 'niasese'],\n            ['niveau', 'niveaux'],\n            ['node_child', 'node_children'],\n            ['nodemedia', 'nodemedia'],\n            ['nucleus', 'nuclei'],\n            ['nursery', 'nurseries'],\n            ['nutrition', 'nutrition'],\n            ['oasis', 'oases'],\n            ['octopus', 'octopuses'],\n            ['offspring', 'offspring'],\n            ['oil', 'oil'],\n            ['old_news', 'old_news'],\n            ['olive', 'olives'],\n            ['ox', 'oxen'],\n            ['pactum', 'pacta'],\n            ['pants', 'pants'],\n            ['pass', 'passes'],\n            ['passerby', 'passersby'],\n            ['pasta', 'pastas'],\n            ['patience', 'patience'],\n            ['pekingese', 'pekingese'],\n            ['person', 'people'],\n            ['perspective', 'perspectives'],\n            ['photo', 'photos'],\n            ['piedmontese', 'piedmontese'],\n            ['pincers', 'pincers'],\n            ['pistoiese', 'pistoiese'],\n            ['plankton', 'plankton'],\n            ['plateau', 'plateaux'],\n            ['pliers', 'pliers'],\n            ['pokemon', 'pokemon'],\n            ['police', 'police'],\n            ['polish', 'polish'],\n            ['portfolio', 'portfolios'],\n            ['portuguese', 'portuguese'],\n            ['potato', 'potatoes'],\n            ['powerhouse', 'powerhouses'],\n            ['prize', 'prizes'],\n            ['proceedings', 'proceedings'],\n            ['process', 'processes'],\n            ['progress', 'progress'],\n            ['query', 'queries'],\n            ['quiz', 'quizzes'],\n            ['rabies', 'rabies'],\n            ['radius', 'radii'],\n            ['rain', 'rain'],\n            ['reflex', 'reflexes'],\n            ['regatta', 'regattas'],\n            ['research', 'research'],\n            ['rhinoceros', 'rhinoceros'],\n            ['rice', 'rice'],\n            ['roof', 'roofs'],\n            ['runner-up', 'runners-up'],\n            ['safe', 'safes'],\n            ['salesperson', 'salespeople'],\n            ['salmon', 'salmon'],\n            ['sand', 'sand'],\n            ['sarawakese', 'sarawakese'],\n            ['save', 'saves'],\n            ['scarf', 'scarves'],\n            ['scissors', 'scissors'],\n            ['scratch', 'scratches'],\n            ['sea-bass', 'sea-bass'],\n            ['sea bass', 'sea bass'],\n            ['search', 'searches'],\n            ['series', 'series'],\n            ['sex', 'sexes'],\n            ['shavese', 'shavese'],\n            ['shears', 'shears'],\n            ['sheep', 'sheep'],\n            ['shelf', 'shelves'],\n            ['shoe', 'shoes'],\n            ['shorts', 'shorts'],\n            ['siemens', 'siemens'],\n            ['sieve', 'sieves'],\n            ['silk', 'silk'],\n            ['sku', 'skus'],\n            ['slice', 'slices'],\n            ['sms', 'sms'],\n            ['soap', 'soap'],\n            ['social media', 'social media'],\n            ['socialmedia', 'socialmedia'],\n            ['software', 'software'],\n            ['son-in-law', 'sons-in-law'],\n            ['spam', 'spam'],\n            ['species', 'species'],\n            ['splash', 'splashes'],\n            ['spokesman', 'spokesmen'],\n            ['spouse', 'spouses'],\n            ['spy', 'spies'],\n            ['stack', 'stacks'],\n            ['stadium', 'stadiums'],\n            ['staff', 'staff'],\n            ['status', 'statuses'],\n            ['status_code', 'status_codes'],\n            ['stimulus', 'stimuli'],\n            ['stitch', 'stitches'],\n            ['story', 'stories'],\n            ['stratum', 'strata'],\n            ['sugar', 'sugar'],\n            ['swine', 'swine'],\n            ['switch', 'switches'],\n            ['syllabus', 'syllabi'],\n            ['talent', 'talent'],\n            ['tax', 'taxes'],\n            ['taxi', 'taxis'],\n            ['taxon', 'taxa'],\n            ['terminus', 'termini'],\n            ['testis', 'testes'],\n            ['thesis', 'theses'],\n            ['Thief', 'Thieves'],\n            ['tights', 'tights'],\n            ['tomato', 'tomatoes'],\n            ['tooth', 'teeth'],\n            ['toothpaste', 'toothpaste'],\n            ['tornado', 'tornadoes'],\n            ['traffic', 'traffic'],\n            ['travel', 'travel'],\n            ['trivia', 'trivia'],\n            ['trousers', 'trousers'],\n            ['trout', 'trout'],\n            ['try', 'tries'],\n            ['tuna', 'tuna'],\n            ['us', 'us'],\n            ['valve', 'valves'],\n            ['vermontese', 'vermontese'],\n            ['vertex', 'vertices'],\n            ['vinegar', 'vinegar'],\n            ['virus', 'viri'],\n            ['volcano', 'volcanoes'],\n            ['ware', 'wares'],\n            ['wash', 'washes'],\n            ['watch', 'watches'],\n            ['wave', 'waves'],\n            ['weather', 'weather'],\n            ['wenchowese', 'wenchowese'],\n            ['wharf', 'wharves'],\n            ['wheat', 'wheat'],\n            ['whiting', 'whiting'],\n            ['wife', 'wives'],\n            ['wildebeest', 'wildebeest'],\n            ['wish', 'wishes'],\n            ['woman', 'women'],\n            ['wood', 'wood'],\n            ['wool', 'wool'],\n            ['work', 'works'],\n            ['yengeese', 'yengeese'],\n            ['zombie', 'zombies'],\n            ['|ice', '|ices'],\n        ];\n    }\n\n    /**\n     * Singulars as Plural test data.\n     *\n     * A list of singulars that should not yield the given result if passed through `singularize`.\n     * Returns an array of sample words.\n     *\n     * @return string[][]\n     */\n    public static function dataSingularsUninflectedWhenSingularized(): array\n    {\n        // In the format array('singular', 'notEquals')\n        return [\n            ['fuchsia', 'fuchsium'],\n            ['militia', 'militium'],\n            ['galleria', 'gallerium'],\n            ['petunia', 'petunium'],\n            ['trivia', 'trivium'],\n            ['utopia', 'utopium'],\n            ['sepia', 'sepium'],\n            ['mafia', 'mafium'],\n            ['regatta', 'regattum'],\n            ['regattas', 'regattum'],\n            ['pactum', 'pactums'],\n            ['fascia', 'fascium'],\n            ['status', 'statu'],\n            ['stratum', 'strati'],\n            ['stratum', 'stratums'],\n            ['campus', 'campu'],\n            ['axis', 'axes'],\n        ];\n    }\n\n    /** @dataProvider dataSingularsUninflectedWhenSingularized */\n    #[DataProvider('dataSingularsUninflectedWhenSingularized')]\n    public function testSingularsWhenSingularizedShouldBeUninflected(string $singular, string $notEquals): void\n    {\n        self::assertNotSame(\n            $notEquals,\n            $this->createInflector()->singularize($singular),\n            sprintf(\"'%s' should not be singularized to '%s'\", $singular, $notEquals)\n        );\n    }\n\n    /**\n     * Words without plural test data.\n     *\n     * List of words that don't have a plural form.\n     *\n     * @return string[][]\n     */\n    public static function dataPluralUninflectedWhenPluralized(): array\n    {\n        return [\n            ['media'],\n        ];\n    }\n\n    /** @dataProvider dataPluralUninflectedWhenPluralized */\n    #[DataProvider('dataPluralUninflectedWhenPluralized')]\n    public function testPluralsWhenPluralizedShouldBeUninflected(string $plural): void\n    {\n        $pluralized = $this->createInflector()->pluralize($plural);\n\n        self::assertSame(\n            $plural,\n            $pluralized,\n            sprintf(\"'%s' should not be pluralized to '%s'\", $plural, $pluralized)\n        );\n    }\n\n    protected function createInflector(): Inflector\n    {\n        return InflectorFactory::createForLanguage(Language::ENGLISH)->build();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/Esperanto/EsperantoFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Esperanto;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Tests\\Inflector\\Rules\\LanguageFunctionalTestCase;\n\nclass EsperantoFunctionalTest extends LanguageFunctionalTestCase\n{\n    /** @return string[][] */\n    public static function dataSampleWords(): array\n    {\n        return [\n            ['abelo', 'abeloj'],\n            ['ĉapelo', 'ĉapeloj'],\n            ['domo', 'domoj'],\n            ['eĥoŝanĝo', 'eĥoŝanĝoj'],\n            ['fervojo', 'fervojoj'],\n            ['lingvo', 'lingvoj'],\n            ['manĝaĵo', 'manĝaĵoj'],\n            ['muzikalo', 'muzikaloj'],\n            ['terpomo', 'terpomoj'],\n            ['vortaro', 'vortaroj'],\n        ];\n    }\n\n    protected function createInflector(): Inflector\n    {\n        return InflectorFactory::createForLanguage(Language::ESPERANTO)->build();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/French/FrenchFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\French;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Tests\\Inflector\\Rules\\LanguageFunctionalTestCase;\n\nclass FrenchFunctionalTest extends LanguageFunctionalTestCase\n{\n    /** @return string[][] */\n    public static function dataSampleWords(): array\n    {\n        return [\n            ['ami', 'amis'],\n            ['chien', 'chiens'],\n            ['fidèle', 'fidèles'],\n            ['rapport', 'rapports'],\n            ['sain', 'sains'],\n            ['jouet', 'jouets'],\n            ['bijou', 'bijoux'],\n            ['caillou', 'cailloux'],\n            ['chou', 'choux'],\n            ['genou', 'genoux'],\n            ['hibou', 'hiboux'],\n            ['joujou', 'joujoux'],\n            ['pou', 'poux'],\n            ['gaz', 'gaz'],\n            ['tuyau', 'tuyaux'],\n            ['nouveau', 'nouveaux'],\n            ['aveu', 'aveux'],\n            ['bleu', 'bleus'],\n            ['émeu', 'émeus'],\n            ['landau', 'landaus'],\n            ['lieu', 'lieux'],\n            ['pneu', 'pneus'],\n            ['sarrau', 'sarraus'],\n            ['journal', 'journaux'],\n            ['local', 'locaux'],\n            ['détail', 'détails'],\n            ['bail', 'baux'],\n            ['corail', 'coraux'],\n            ['émail', 'émaux'],\n            ['gemmail', 'gemmaux'],\n            ['soupirail', 'soupiraux'],\n            ['travail', 'travaux'],\n            ['vantail', 'vantaux'],\n            ['vitrail', 'vitraux'],\n            ['monsieur', 'messieurs'],\n            ['madame', 'mesdames'],\n            ['mademoiselle', 'mesdemoiselles'],\n            ['chacal', 'chacals'],\n            ['carnaval', 'carnavals'],\n            ['festival', 'festivals'],\n            ['récital', 'récitals'],\n            ['bois', 'bois'],\n            ['mas', 'mas'],\n        ];\n    }\n\n    protected function createInflector(): Inflector\n    {\n        return InflectorFactory::createForLanguage(Language::FRENCH)->build();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/Italian/ItalianFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Italian;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Tests\\Inflector\\Rules\\LanguageFunctionalTestCase;\n\nclass ItalianFunctionalTest extends LanguageFunctionalTestCase\n{\n    /** @return string[][] */\n    public static function dataSampleWords(): array\n    {\n        return [\n            // Empty string and edge cases\n            ['', ''],\n            [' ', ' '],\n            ['123', '123'],\n            ['@#!', '@#!'],\n\n            // Invariable nouns (same in singular and plural)\n            ['re', 're'],\n            ['città', 'città'],\n            ['virtù', 'virtù'],\n            ['specie', 'specie'],\n            ['serie', 'serie'],\n            ['crisi', 'crisi'],\n            ['superficie', 'superfici'],\n            ['metropoli', 'metropoli'],\n\n            // Foreign words and loanwords\n            ['film', 'film'],\n            ['sport', 'sport'],\n            ['bar', 'bar'],\n            ['computer', 'computer'],\n            ['menu', 'menu'],\n            ['taxi', 'taxi'],\n            ['quiz', 'quiz'],\n            ['smartphone', 'smartphone'],\n            ['tablet', 'tablet'],\n            ['virus', 'virus'],\n            ['campus', 'campus'],\n\n            // Abbreviations and shortened forms\n            ['foto', 'foto'],  // from fotografia\n            ['moto', 'moto'],  // from motocicletta\n            ['auto', 'auto'],  // from automobile\n\n            // Words with accented vowels\n            ['caffè', 'caffè'],\n            ['tè', 'tè'],\n            ['menù', 'menù'],\n\n            // Compound words\n            ['dopocena', 'dopocena'],\n            ['sottoscala', 'sottoscala'],\n\n            // Nouns with irregular patterns\n            ['tempio', 'templi'],\n            ['ala', 'ali'],\n            ['mano', 'mani'],\n\n            // Words with multiple plural forms\n            ['braccio', 'braccia'],  // arm -> arms\n            ['ginocchio', 'ginocchia'],  // body part\n            ['dito', 'dita'],  // more common\n            ['baco', 'bachi'],  // more common\n\n            // Words that change meaning in plural\n            ['membro', 'membri'],    // members of an organization\n            ['membrana', 'membrane'],  // membranes\n\n            // Words with identical forms but different genders/meanings\n            ['capitale', 'capitali'],  // capital (money)\n            ['capitale', 'capitali'], // capital city (context determines meaning)\n\n            // Irregular plurals and exceptions\n            ['uomo', 'uomini'],\n            ['dio', 'dei'],\n            ['bue', 'buoi'],\n\n            // Nouns ending in -o (masculine)\n            ['libro', 'libri'],\n            ['tavolo', 'tavoli'],\n            ['ragazzo', 'ragazzi'],\n\n            // Nouns ending in -a (feminine)\n            ['casa', 'case'],\n            ['penna', 'penne'],\n            ['amica', 'amiche'],\n\n            // Nouns ending in -e\n            ['fiore', 'fiori'],\n            ['cane', 'cani'],\n            ['chiave', 'chiavi'],\n\n            // Nouns ending in -ca/ga\n            ['banca', 'banche'],\n\n            // Nouns ending in -cia/gia\n            ['arancia', 'arance'],\n            ['valigia', 'valigie'],\n            ['camicia', 'camicie'],\n            ['fascia', 'fasce'],\n            ['farmacia', 'farmacie'],\n\n            // Nouns ending in -co/go\n            ['gioco', 'giochi'],\n            ['fuoco', 'fuochi'],\n            ['albergo', 'alberghi'],\n\n            // Words that are the same in both singular and plural\n            ['sosia', 'sosia'],\n            ['vaglia', 'vaglia'],\n            ['gorilla', 'gorilla'],\n            ['yogurt', 'yogurt'],\n            ['boomerang', 'boomerang'],\n            ['kamikaze', 'kamikaze'],\n            ['karaoke', 'karaoke'],\n            ['brindisi', 'brindisi'],\n            ['boia', 'boia'],\n        ];\n    }\n\n    protected function createInflector(): Inflector\n    {\n        return InflectorFactory::createForLanguage(Language::ITALIAN)->build();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/LanguageFunctionalTestCase.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Inflector;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nuse function sprintf;\n\nabstract class LanguageFunctionalTestCase extends TestCase\n{\n    /** @return string[][] */\n    abstract public static function dataSampleWords(): array;\n\n    /** @dataProvider dataSampleWords */\n    #[DataProvider('dataSampleWords')]\n    public function testInflectingSingulars(string $singular, string $plural): void\n    {\n        self::assertSame(\n            $singular,\n            $this->createInflector()->singularize($plural),\n            sprintf(\"'%s' should be singularized to '%s'\", $plural, $singular)\n        );\n    }\n\n    /** @dataProvider dataSampleWords */\n    #[DataProvider('dataSampleWords')]\n    public function testInflectingPlurals(string $singular, string $plural): void\n    {\n        self::assertSame(\n            $plural,\n            $this->createInflector()->pluralize($singular),\n            sprintf(\"'%s' should be pluralized to '%s'\", $singular, $plural)\n        );\n    }\n\n    abstract protected function createInflector(): Inflector;\n}\n"
  },
  {
    "path": "tests/Rules/NorwegianBokmal/NorwegianBokmalFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\NorwegianBokmal;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Tests\\Inflector\\Rules\\LanguageFunctionalTestCase;\n\nclass NorwegianBokmalFunctionalTest extends LanguageFunctionalTestCase\n{\n    /** @return string[][] */\n    public static function dataSampleWords(): array\n    {\n        return [\n            ['dag', 'dager'],\n            ['fjord', 'fjorder'],\n            ['hund', 'hunder'],\n            ['kalender', 'kalendere'],\n            ['katt' , 'katter'],\n            ['lærer', 'lærere'],\n            ['test', 'tester'],\n            ['konto', 'konti'],\n            ['barn', 'barn'],\n            ['fjell', 'fjell'],\n            ['hus', 'hus'],\n        ];\n    }\n\n    protected function createInflector(): Inflector\n    {\n        return InflectorFactory::createForLanguage(Language::NORWEGIAN_BOKMAL)->build();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/PatternTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse PHPUnit\\Framework\\TestCase;\n\nclass PatternTest extends TestCase\n{\n    /** @var Pattern */\n    private $pattern;\n\n    public function testGetPattern(): void\n    {\n        self::assertSame('test', $this->pattern->getPattern());\n    }\n\n    public function testGetRegex(): void\n    {\n        self::assertSame('/test/i', $this->pattern->getRegex());\n    }\n\n    public function testPatternWithExplicitRegex(): void\n    {\n        $pattern = new Pattern('/test/');\n\n        self::assertSame('/test/', $pattern->getRegex());\n    }\n\n    public function testMatches(): void\n    {\n        self::assertTrue($this->pattern->matches('test'));\n    }\n\n    protected function setUp(): void\n    {\n        $this->pattern = new Pattern('test');\n    }\n}\n"
  },
  {
    "path": "tests/Rules/PatternsTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse PHPUnit\\Framework\\TestCase;\n\nclass PatternsTest extends TestCase\n{\n    /** @var Patterns */\n    private $patterns;\n\n    public function testMatches(): void\n    {\n        self::assertTrue($this->patterns->matches('test1'));\n        self::assertFalse($this->patterns->matches('test2'));\n    }\n\n    protected function setUp(): void\n    {\n        $this->patterns = new Patterns(new Pattern('test1'));\n    }\n}\n"
  },
  {
    "path": "tests/Rules/Portuguese/PortugueseFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Portuguese;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Tests\\Inflector\\Rules\\LanguageFunctionalTestCase;\n\nclass PortugueseFunctionalTest extends LanguageFunctionalTestCase\n{\n    /** @return string[][] */\n    public static function dataSampleWords(): array\n    {\n        return [\n            ['livro', 'livros'],\n            ['radio', 'radios'],\n            ['senhor', 'senhores'],\n            ['lei', 'leis'],\n            ['rei', 'reis'],\n            ['luz', 'luzes'],\n            ['juiz', 'juizes'],\n            ['avião', 'aviões'],\n            ['cão', 'cães'],\n            ['interesse', 'interesses'],\n            ['ás', 'ases'],\n            ['mão', 'mãos'],\n            ['peão', 'peões'],\n            ['casa', 'casas'],\n            ['árvore', 'árvores'],\n            ['cor', 'cores'],\n            ['álbum', 'álbuns'],\n            ['mulher', 'mulheres'],\n            ['nação', 'nações'],\n            ['país', 'países'],\n            ['chão', 'chãos'],\n            ['charlatão', 'charlatães'],\n            ['cidadão', 'cidadãos'],\n            ['cônsul', 'cônsules'],\n            ['cristão', 'cristãos'],\n            ['difícil', 'difíceis'],\n            ['email', 'emails'],\n            ['mês', 'meses'],\n            ['útil', 'úteis'],\n        ];\n    }\n\n    protected function createInflector(): Inflector\n    {\n        return InflectorFactory::createForLanguage(Language::PORTUGUESE)->build();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/RulesetTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Transformations;\nuse Doctrine\\Inflector\\Rules\\Word;\nuse PHPUnit\\Framework\\TestCase;\n\nclass RulesetTest extends TestCase\n{\n    /** @var Transformations */\n    private $regular;\n\n    /** @var Patterns */\n    private $uninflected;\n\n    /** @var Substitutions */\n    private $irregular;\n\n    /** @var Ruleset */\n    private $ruleset;\n\n    public function testGetRegular(): void\n    {\n        self::assertSame($this->regular, $this->ruleset->getRegular());\n    }\n\n    public function testGetUninflected(): void\n    {\n        self::assertSame($this->uninflected, $this->ruleset->getUninflected());\n    }\n\n    public function testGetIrregular(): void\n    {\n        self::assertSame($this->irregular, $this->ruleset->getIrregular());\n    }\n\n    protected function setUp(): void\n    {\n        $this->regular     = new Transformations(\n            new Transformation(new Pattern('test'), 'tests')\n        );\n        $this->uninflected = new Patterns(new Pattern('uninflected'));\n        $this->irregular   = new Substitutions(\n            new Substitution(new Word('test'), new Word('tests'))\n        );\n\n        $this->ruleset = new Ruleset(\n            $this->regular,\n            $this->uninflected,\n            $this->irregular\n        );\n    }\n}\n"
  },
  {
    "path": "tests/Rules/Spanish/SpanishFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Spanish;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Tests\\Inflector\\Rules\\LanguageFunctionalTestCase;\n\nclass SpanishFunctionalTest extends LanguageFunctionalTestCase\n{\n    /** @return string[][] */\n    public static function dataSampleWords(): array\n    {\n        return [\n            ['libro', 'libros'],\n            ['pluma', 'plumas'],\n            ['chico', 'chicos'],\n            ['señora', 'señoras'],\n            ['radio', 'radios'],\n            ['borrador', 'borradores'],\n            ['universidad', 'universidades'],\n            ['profesor', 'profesores'],\n            ['ciudad', 'ciudades'],\n            ['señor', 'señores'],\n            ['escultor', 'escultores'],\n            ['sociedad', 'sociedades'],\n            ['azul', 'azules'],\n            ['mes', 'meses'],\n            ['avión', 'aviones'],\n            ['conversación', 'conversaciones'],\n            ['sección', 'secciones'],\n            ['televisión', 'televisiones'],\n            ['interés', 'intereses'],\n            ['lápiz', 'lápices'],\n            ['voz', 'voces'],\n            ['tapiz', 'tapices'],\n            ['actriz', 'actrices'],\n            ['luz', 'luces'],\n            ['mez', 'meces'],\n            ['tisú', 'tisúes'],\n            ['hindú', 'hindúes'],\n            ['ley', 'leyes'],\n            ['café', 'cafés'],\n            ['el', 'los'],\n            ['lunes', 'lunes'],\n            ['rompecabezas', 'rompecabezas'],\n            ['crisis', 'crisis'],\n            ['papá', 'papás'],\n            ['mamá', 'mamás'],\n            ['sofá', 'sofás'],\n        ];\n    }\n\n    protected function createInflector(): Inflector\n    {\n        return InflectorFactory::createForLanguage(Language::SPANISH)->build();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/SubstitutionTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Word;\nuse PHPUnit\\Framework\\TestCase;\n\nclass SubstitutionTest extends TestCase\n{\n    /** @var Substitution */\n    private $substitution;\n\n    public function testGetFrom(): void\n    {\n        self::assertSame('from', $this->substitution->getFrom()->getWord());\n    }\n\n    public function testGetTo(): void\n    {\n        self::assertSame('to', $this->substitution->getTo()->getWord());\n    }\n\n    protected function setUp(): void\n    {\n        $this->substitution = new Substitution(new Word('from'), new Word('to'));\n    }\n}\n"
  },
  {
    "path": "tests/Rules/SubstitutionsTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Word;\nuse PHPUnit\\Framework\\TestCase;\n\nclass SubstitutionsTest extends TestCase\n{\n    /** @var Substitution[] */\n    private $substitutions;\n\n    /** @var Substitutions */\n    private $irregular;\n\n    public function testGetFlippedSubstitutions(): void\n    {\n        $substitutions = $this->irregular->getFlippedSubstitutions();\n\n        self::assertSame('spins', $substitutions->inflect('spinor'));\n    }\n\n    public function testInflect(): void\n    {\n        self::assertSame('spinor', $this->irregular->inflect('spins'));\n    }\n\n    protected function setUp(): void\n    {\n        $this->substitutions = [\n            new Substitution(new Word('spins'), new Word('spinor')),\n        ];\n\n        $this->irregular = new Substitutions(...$this->substitutions);\n    }\n}\n"
  },
  {
    "path": "tests/Rules/TransformationTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse PHPUnit\\Framework\\TestCase;\n\nclass TransformationTest extends TestCase\n{\n    /** @var Transformation */\n    private $transformation;\n\n    public function testGetPattern(): void\n    {\n        self::assertSame('s$', $this->transformation->getPattern()->getPattern());\n    }\n\n    public function testGetReplacement(): void\n    {\n        self::assertSame('', $this->transformation->getReplacement());\n    }\n\n    public function testInflect(): void\n    {\n        self::assertSame('test', $this->transformation->inflect('tests'));\n    }\n\n    protected function setUp(): void\n    {\n        $this->transformation = new Transformation(new Pattern('s$'), '');\n    }\n}\n"
  },
  {
    "path": "tests/Rules/TransformationsTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Transformations;\nuse PHPUnit\\Framework\\TestCase;\n\nclass TransformationsTest extends TestCase\n{\n    /** @var Transformations */\n    private $transformations;\n\n    public function testInflect(): void\n    {\n        self::assertSame('customizables', $this->transformations->inflect('custom'));\n    }\n\n    protected function setUp(): void\n    {\n        $this->transformations = new Transformations(new Transformation(new Pattern('/^(custom)$/i'), '\\1izables'));\n    }\n}\n"
  },
  {
    "path": "tests/Rules/Turkish/TurkishFunctionalTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules\\Turkish;\n\nuse Doctrine\\Inflector\\Inflector;\nuse Doctrine\\Inflector\\InflectorFactory;\nuse Doctrine\\Inflector\\Language;\nuse Doctrine\\Tests\\Inflector\\Rules\\LanguageFunctionalTestCase;\n\nclass TurkishFunctionalTest extends LanguageFunctionalTestCase\n{\n    /** @return string[][] */\n    public static function dataSampleWords(): array\n    {\n        return [\n            ['gün', 'günler'],\n            ['kiraz', 'kirazlar'],\n            ['kitap', 'kitaplar'],\n            ['köpek', 'köpekler'],\n            ['test', 'testler'],\n            ['üçgen', 'üçgenler'],\n            ['ben', 'biz'],\n            ['sen', 'siz'],\n            ['o', 'onlar'],\n        ];\n    }\n\n    protected function createInflector(): Inflector\n    {\n        return InflectorFactory::createForLanguage(Language::TURKISH)->build();\n    }\n}\n"
  },
  {
    "path": "tests/Rules/WordTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector\\Rules;\n\nuse Doctrine\\Inflector\\Rules\\Word;\nuse PHPUnit\\Framework\\TestCase;\n\nclass WordTest extends TestCase\n{\n    /** @var Word */\n    private $word;\n\n    public function testGetWord(): void\n    {\n        self::assertSame('test', $this->word->getWord());\n    }\n\n    protected function setUp(): void\n    {\n        $this->word = new Word('test');\n    }\n}\n"
  },
  {
    "path": "tests/RulesetInflectorTest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n\nnamespace Doctrine\\Tests\\Inflector;\n\nuse Doctrine\\Inflector\\Rules\\Pattern;\nuse Doctrine\\Inflector\\Rules\\Patterns;\nuse Doctrine\\Inflector\\Rules\\Ruleset;\nuse Doctrine\\Inflector\\Rules\\Substitution;\nuse Doctrine\\Inflector\\Rules\\Substitutions;\nuse Doctrine\\Inflector\\Rules\\Transformation;\nuse Doctrine\\Inflector\\Rules\\Transformations;\nuse Doctrine\\Inflector\\Rules\\Word;\nuse Doctrine\\Inflector\\RulesetInflector;\nuse PHPUnit\\Framework\\Attributes\\DataProvider;\nuse PHPUnit\\Framework\\TestCase;\n\nclass RulesetInflectorTest extends TestCase\n{\n    /** @dataProvider rulesetProvider */\n    #[DataProvider('rulesetProvider')]\n    public function testInflect(\n        Ruleset $firstRuleset,\n        Ruleset $secondRuleset,\n        string $input,\n        string $expected\n    ): void {\n        $inflector = new RulesetInflector($firstRuleset, $secondRuleset);\n\n        self::assertSame($expected, $inflector->inflect($input));\n    }\n\n    /** @return iterable<string, array{Ruleset, Ruleset, string, string}> */\n    public static function rulesetProvider(): iterable\n    {\n        yield 'irregular uses first match' => [\n            new Ruleset(\n                new Transformations(),\n                new Patterns(),\n                new Substitutions(new Substitution(new Word('in'), new Word('first')))\n            ),\n            new Ruleset(\n                new Transformations(),\n                new Patterns(),\n                new Substitutions(new Substitution(new Word('in'), new Word('second')))\n            ),\n            'in',\n            'first',\n        ];\n\n        yield 'irregular continues if first ruleset returns original value' => [\n            new Ruleset(\n                new Transformations(),\n                new Patterns(),\n                new Substitutions()\n            ),\n            new Ruleset(\n                new Transformations(),\n                new Patterns(),\n                new Substitutions(new Substitution(new Word('in'), new Word('second')))\n            ),\n            'in',\n            'second',\n        ];\n\n        yield 'uninflected skips on first match' => [\n            new Ruleset(\n                new Transformations(),\n                new Patterns(new Pattern('in')),\n                new Substitutions()\n            ),\n            new Ruleset(\n                new Transformations(new Transformation(new Pattern('in'), 'should-not-reach')),\n                new Patterns(),\n                new Substitutions()\n            ),\n            'in',\n            'in',\n        ];\n\n        yield 'irregular is inflected even if later ruleset ignores' => [\n            new Ruleset(\n                new Transformations(),\n                new Patterns(),\n                new Substitutions(new Substitution(new Word('travel'), new Word('travels')))\n            ),\n            new Ruleset(\n                new Transformations(),\n                new Patterns(new Pattern('travel')),\n                new Substitutions()\n            ),\n            'travel',\n            'travels',\n        ];\n\n        yield 'regular uses first match' => [\n            new Ruleset(\n                new Transformations(new Transformation(new Pattern('in'), 'first')),\n                new Patterns(),\n                new Substitutions()\n            ),\n            new Ruleset(\n                new Transformations(new Transformation(new Pattern('in'), 'second')),\n                new Patterns(),\n                new Substitutions()\n            ),\n            'in',\n            'first',\n        ];\n\n        yield 'regular continues if first ruleset returns original value' => [\n            new Ruleset(\n                new Transformations(new Transformation(new Pattern('nomatch'), 'first')),\n                new Patterns(),\n                new Substitutions()\n            ),\n            new Ruleset(\n                new Transformations(new Transformation(new Pattern('in'), 'second')),\n                new Patterns(),\n                new Substitutions()\n            ),\n            'in',\n            'second',\n        ];\n\n        yield 'returns original value on no matches' => [\n            new Ruleset(\n                new Transformations(new Transformation(new Pattern('nomatch'), 'replaced')),\n                new Patterns(),\n                new Substitutions()\n            ),\n            new Ruleset(\n                new Transformations(new Transformation(new Pattern('nomatch'), 'replaced')),\n                new Patterns(),\n                new Substitutions()\n            ),\n            'in',\n            'in',\n        ];\n    }\n}\n"
  }
]