From a5c1c25f539cc1025ea2b0841e46f28088fbac83 Mon Sep 17 00:00:00 2001 From: Sibin Grasic Date: Thu, 27 Mar 2025 00:14:12 +0100 Subject: [PATCH 1/2] fix: Cleaned up release --- .gitattributes | 27 ++++++++++++--------------- .phpcs.xml => phpcs.xml | 0 2 files changed, 12 insertions(+), 15 deletions(-) rename .phpcs.xml => phpcs.xml (100%) diff --git a/.gitattributes b/.gitattributes index b710ae0..cd1c9ec 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,15 +1,12 @@ -/.github export-ignore -/.codeclimate.yml export-ignore -/.editorconfig export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore -/.releaserc export-ignore -/CODE_OF_CONDUCT.md export-ignore -/composer.lock export-ignore -/CONTRIBUTING.md export-ignore -/docs export-ignore -/examples export-ignore -/SECURITY.md export-ignore -/phpcs.xml export-ignore -/phpstan.neon export-ignore -/phpunit.xml export-ignore +/.github export-ignore +/tests export-ignore +/vendor export-ignore +/.editorconfig export-ignore +/.gitattributes export-ignore +/.gitignore export-ignore +/.releaserc export-ignore +/composer.lock export-ignore +/phpbench.json export-ignore +/phpcs.xml export-ignore +/phpstan.neon export-ignore +/phpunit.xml export-ignore diff --git a/.phpcs.xml b/phpcs.xml similarity index 100% rename from .phpcs.xml rename to phpcs.xml From 16a18ff68f94389ec956cf8d1372a30ba5a3917e Mon Sep 17 00:00:00 2001 From: Sibin Grasic Date: Wed, 21 May 2025 15:50:34 +0200 Subject: [PATCH 2/2] feat: Finalized name inflection --- .gitignore | 1 + README.md | 73 ++++++++++++++++++ composer.lock | 141 ++++++++++++++++++++-------------- src/BaseDictionary.php | 13 +++- src/Interfaces/Dictionary.php | 10 +++ src/NullDictionary.php | 22 ++++++ src/Vocative.php | 88 ++++++++++++++++++++- src/dictionary.php | 47 ------------ 8 files changed, 285 insertions(+), 110 deletions(-) create mode 100644 src/NullDictionary.php delete mode 100644 src/dictionary.php diff --git a/.gitignore b/.gitignore index cc2238b..70e3759 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ vendor test.php +/*.txt diff --git a/README.md b/README.md index e69de29..d4390a1 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,73 @@ +
+ +

Vocative đŸ—Łïž

+

Vocative inflector for Serbian names

+ +[![Packagist Version](https://img.shields.io/packagist/v/oblak/vocative?label=Release&style=flat-square&logo=packagist&logoColor=white)](https://packagist.org/packages/oblak/vocative) +![Packagist PHP Version](https://img.shields.io/packagist/dependency-v/oblak/vocative/php?label=PHP&logo=php&logoColor=white&logoSize=auto&style=flat-square) + +
+ +This library allows you to effortlessly convert Serbian personal names to their correct vocative form. + +## Key Features + +1. **Peer-reviewed** - The library has been tested with almost all Serbian names, and reviewed by a team of linguists. +2. Easy to use - The library is designed to be simple and intuitive, making it easy for developers of all skill levels to integrate into their projects. +3. Lightweight - The library is small and efficient, ensuring that it won't slow down your application. + +## Installation + +You can install the library via Composer: + +```bash +composer require oblak/vocative +``` + +## Usage + +Below is a simple example of how to use the library: + + +### Basic Usage + +```php +make($firstName); // Outputs: Avrame + +``` + +### With custom dictionary + +`Vocative` class depends on an **exception dictionary** to handle special cases. By default it uses the built-in `BaseDictionary` class, but you can use your own by implementing the `Dictionary` interface. + +> [!TIP] +> We provide a `NullDictionary` class that does not use any exceptions. This is useful for testing purposes. + +```php +make($firstName); // Outputs: Aleksandre +echo $vocative->withDictionary(new NullDictionary())->make($firstName); // Outputs: Aleksandare + +``` + +## Special thanks + +As someone without deep expertise in Serbian linguistics, I am deeply grateful to the following individuals—without their guidance and support, this project could never have been completed: + +* **[Svetlana Slijepčević Bjelivuk, PhD](https://jezikofil.rs)** - For providing invaluable insights into intricacies of declination and conjugation of Serbian nouns and names and reviewing the data set. +* **[Nemanja Avramović](https://github.com/avramovic)** - For his initial implementation of the `Vokativ` Library +* **Milana BaĆĄić** - For taking the time to gather and provide a list of Serbian names, both male and female. diff --git a/composer.lock b/composer.lock index 01d59f9..63fb409 100644 --- a/composer.lock +++ b/composer.lock @@ -73,16 +73,16 @@ }, { "name": "symfony/polyfill-php84", - "version": "v1.31.0", + "version": "v1.32.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "e5493eb51311ab0b1cc2243416613f06ed8f18bd" + "reference": "000df7860439609837bbe28670b0be15783b7fbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/e5493eb51311ab0b1cc2243416613f06ed8f18bd", - "reference": "e5493eb51311ab0b1cc2243416613f06ed8f18bd", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/000df7860439609837bbe28670b0be15783b7fbf", + "reference": "000df7860439609837bbe28670b0be15783b7fbf", "shasum": "" }, "require": { @@ -129,7 +129,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php84/tree/v1.32.0" }, "funding": [ { @@ -145,7 +145,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T12:04:04+00:00" + "time": "2025-02-20T12:04:08+00:00" } ], "packages-dev": [ @@ -299,16 +299,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.0", + "version": "1.13.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414" + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/024473a478be9df5fdaca2c793f2232fe788e414", - "reference": "024473a478be9df5fdaca2c793f2232fe788e414", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", + "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", "shasum": "" }, "require": { @@ -347,7 +347,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" }, "funding": [ { @@ -355,7 +355,7 @@ "type": "tidelift" } ], - "time": "2025-02-12T12:17:51+00:00" + "time": "2025-04-29T12:36:36+00:00" }, { "name": "nikic/php-parser", @@ -417,16 +417,16 @@ }, { "name": "oblak/wordpress-coding-standard", - "version": "v1.2.6", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/oblakstudio/wordpress-coding-standards.git", - "reference": "5b93e77b6faa931b6eb160d73913a566236de3cb" + "reference": "b9c6fd5c58edccc464927c9b9f239b0ee0692daf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/oblakstudio/wordpress-coding-standards/zipball/5b93e77b6faa931b6eb160d73913a566236de3cb", - "reference": "5b93e77b6faa931b6eb160d73913a566236de3cb", + "url": "https://api.github.com/repos/oblakstudio/wordpress-coding-standards/zipball/b9c6fd5c58edccc464927c9b9f239b0ee0692daf", + "reference": "b9c6fd5c58edccc464927c9b9f239b0ee0692daf", "shasum": "" }, "require": { @@ -465,9 +465,15 @@ ], "support": { "issues": "https://github.com/oblakstudio/wordpress-coding-standards/issues", - "source": "https://github.com/oblakstudio/wordpress-coding-standards/tree/v1.2.6" + "source": "https://github.com/oblakstudio/wordpress-coding-standards/tree/v1.3.0" }, - "time": "2025-01-23T16:14:04+00:00" + "funding": [ + { + "url": "https://github.com/seebeen", + "type": "github" + } + ], + "time": "2025-04-25T15:40:25+00:00" }, { "name": "phar-io/manifest", @@ -723,21 +729,22 @@ }, { "name": "phpcompatibility/phpcompatibility-wp", - "version": "2.1.6", + "version": "2.1.7", "source": { "type": "git", "url": "https://github.com/PHPCompatibility/PHPCompatibilityWP.git", - "reference": "80ccb1a7640995edf1b87a4409fa584cd5869469" + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/80ccb1a7640995edf1b87a4409fa584cd5869469", - "reference": "80ccb1a7640995edf1b87a4409fa584cd5869469", + "url": "https://api.github.com/repos/PHPCompatibility/PHPCompatibilityWP/zipball/5bfbbfbabb3df2b9a83e601de9153e4a7111962c", + "reference": "5bfbbfbabb3df2b9a83e601de9153e4a7111962c", "shasum": "" }, "require": { "phpcompatibility/php-compatibility": "^9.0", - "phpcompatibility/phpcompatibility-paragonie": "^1.0" + "phpcompatibility/phpcompatibility-paragonie": "^1.0", + "squizlabs/php_codesniffer": "^3.3" }, "require-dev": { "dealerdirect/phpcodesniffer-composer-installer": "^1.0" @@ -787,28 +794,32 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcompatibility", + "type": "thanks_dev" } ], - "time": "2025-01-16T22:34:19+00:00" + "time": "2025-05-12T16:38:37+00:00" }, { "name": "phpcsstandards/phpcsextra", - "version": "1.2.1", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", - "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" + "reference": "46d08eb86eec622b96c466adec3063adfed280dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", - "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", + "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/46d08eb86eec622b96c466adec3063adfed280dd", + "reference": "46d08eb86eec622b96c466adec3063adfed280dd", "shasum": "" }, "require": { "php": ">=5.4", "phpcsstandards/phpcsutils": "^1.0.9", - "squizlabs/php_codesniffer": "^3.8.0" + "squizlabs/php_codesniffer": "^3.12.1" }, "require-dev": { "php-parallel-lint/php-console-highlighter": "^1.0", @@ -865,9 +876,13 @@ { "url": "https://opencollective.com/php_codesniffer", "type": "open_collective" + }, + { + "url": "https://thanks.dev/u/gh/phpcsstandards", + "type": "thanks_dev" } ], - "time": "2023-12-08T16:49:07+00:00" + "time": "2025-04-20T23:35:32+00:00" }, { "name": "phpcsstandards/phpcsutils", @@ -1054,16 +1069,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.12.23", + "version": "1.12.26", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "29201e7a743a6ab36f91394eab51889a82631428" + "reference": "84cbf8f018e01834b9b1ac3dacf3b9780e209e53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/29201e7a743a6ab36f91394eab51889a82631428", - "reference": "29201e7a743a6ab36f91394eab51889a82631428", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/84cbf8f018e01834b9b1ac3dacf3b9780e209e53", + "reference": "84cbf8f018e01834b9b1ac3dacf3b9780e209e53", "shasum": "" }, "require": { @@ -1108,7 +1123,7 @@ "type": "github" } ], - "time": "2025-03-23T14:57:32+00:00" + "time": "2025-05-14T11:08:32+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1431,16 +1446,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.22", + "version": "9.6.23", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c" + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c", - "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", + "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "shasum": "" }, "require": { @@ -1451,7 +1466,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=7.3", @@ -1514,7 +1529,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" }, "funding": [ { @@ -1525,12 +1540,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2024-12-05T13:48:26+00:00" + "time": "2025-05-02T06:40:34+00:00" }, { "name": "sebastian/cli-parser", @@ -2497,32 +2520,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.16.1", + "version": "8.18.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "490023f23813483b5f75381c4ee07d26d9edced1" + "reference": "f3b23cb9b26301b8c3c7bb03035a1bee23974593" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/490023f23813483b5f75381c4ee07d26d9edced1", - "reference": "490023f23813483b5f75381c4ee07d26d9edced1", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/f3b23cb9b26301b8c3c7bb03035a1bee23974593", + "reference": "f3b23cb9b26301b8c3c7bb03035a1bee23974593", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", "php": "^7.4 || ^8.0", "phpstan/phpdoc-parser": "^2.1.0", - "squizlabs/php_codesniffer": "^3.11.3" + "squizlabs/php_codesniffer": "^3.12.2" }, "require-dev": { "phing/phing": "3.0.1", "php-parallel-lint/php-parallel-lint": "1.4.0", - "phpstan/phpstan": "2.1.6", - "phpstan/phpstan-deprecation-rules": "2.0.1", - "phpstan/phpstan-phpunit": "2.0.4", - "phpstan/phpstan-strict-rules": "2.0.3", - "phpunit/phpunit": "9.6.8|10.5.45|11.4.4|11.5.9|12.0.4" + "phpstan/phpstan": "2.1.13", + "phpstan/phpstan-deprecation-rules": "2.0.2", + "phpstan/phpstan-phpunit": "2.0.6", + "phpstan/phpstan-strict-rules": "2.0.4", + "phpunit/phpunit": "9.6.8|10.5.45|11.4.4|11.5.17|12.1.3" }, "type": "phpcodesniffer-standard", "extra": { @@ -2546,7 +2569,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.16.1" + "source": "https://github.com/slevomat/coding-standard/tree/8.18.0" }, "funding": [ { @@ -2558,20 +2581,20 @@ "type": "tidelift" } ], - "time": "2025-03-23T16:33:42+00:00" + "time": "2025-05-01T09:40:50+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.12.0", + "version": "3.13.0", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "2d1b63db139c3c6ea0c927698e5160f8b3b8d630" + "reference": "65ff2489553b83b4597e89c3b8b721487011d186" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/2d1b63db139c3c6ea0c927698e5160f8b3b8d630", - "reference": "2d1b63db139c3c6ea0c927698e5160f8b3b8d630", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/65ff2489553b83b4597e89c3b8b721487011d186", + "reference": "65ff2489553b83b4597e89c3b8b721487011d186", "shasum": "" }, "require": { @@ -2642,7 +2665,7 @@ "type": "thanks_dev" } ], - "time": "2025-03-18T05:04:51+00:00" + "time": "2025-05-11T03:36:00+00:00" }, { "name": "theseer/tokenizer", diff --git a/src/BaseDictionary.php b/src/BaseDictionary.php index 9426e85..2bb90cc 100644 --- a/src/BaseDictionary.php +++ b/src/BaseDictionary.php @@ -12,21 +12,25 @@ class BaseDictionary implements Dictionary * * @var array */ - private array $names = [ + protected array $names = [ 'ALEKSA' => 'ALEKSA', 'ALEKSANDAR' => 'ALEKSANDRE', 'ANKA' => 'ANKA', + 'ANĐA' => 'ANĐO', 'BLAĆœA' => 'BLAĆœO', 'CANA' => 'CANO', 'CVETA' => 'CVETO', - 'GROZDA' => 'GROZDA', + 'DORIS' => 'DORIS', + 'FEMA' => 'FEMO', + 'GROZDA' => 'GROZDO', 'IVKA' => 'IVKA', 'JANA' => 'JANO', 'JELA' => 'JELO', 'JEVTA' => 'JEVTO', 'KRSTA' => 'KRSTO', - 'LARA' => 'LARA', + 'LARA' => 'LARO', 'LAZA' => 'LAZO', + 'LENA' => 'LENO', 'LOLA' => 'LOLO', 'LUKA' => 'LUKA', 'MATA' => 'MATO', @@ -39,14 +43,17 @@ class BaseDictionary implements Dictionary 'NATA' => 'NATO', 'OLJA' => 'OLJA', 'OSTOJA' => 'OSTOJA', + 'RANĐA' => 'RANĐO', 'RATAR' => 'RATARE', 'RELJA' => 'RELJA', + 'RUĆœA' => 'RUĆœO', 'SIMA' => 'SIMO', 'SLAĐAN' => 'SLAĐAN', 'STANA' => 'STANO', 'STAVRA' => 'STAVRO', 'TOMA' => 'TOMO', 'TOĆ A' => 'TOĆ O', + 'UNA' => 'UNO', 'VERA' => 'VERA', 'ZLATA' => 'ZLATO', 'Ć ANA' => 'Ć ANO', diff --git a/src/Interfaces/Dictionary.php b/src/Interfaces/Dictionary.php index 282cf24..99d9256 100644 --- a/src/Interfaces/Dictionary.php +++ b/src/Interfaces/Dictionary.php @@ -1,9 +1,19 @@ > + */ private const SUFFIX_RULES = [ 'ICA' => [ 'ICE' => 4 ], 'TRA' => [ 'TRA' => 0 ], @@ -74,8 +79,18 @@ class Vocative */ private ?string $source = null; + /** + * Original nominative form of the name + * + * @var string|null + */ private ?string $nominative = null; + /** + * Computed vocative form of the name + * + * @var string|null + */ private ?string $vocative = null; /** @@ -95,6 +110,12 @@ public function __construct(?Dictionary $dictionary = null) $this->dictionary = $dictionary ?? new BaseDictionary(); } + /** + * Set a custom dictionary for name exceptions + * + * @param Dictionary $dictionary Custom dictionary implementation + * @return static Current instance for method chaining + */ public function withDictionary(Dictionary $dictionary): static { $this->dictionary = $dictionary; @@ -102,6 +123,11 @@ public function withDictionary(Dictionary $dictionary): static return $this; } + /** + * Get the current dictionary instance + * + * @return Dictionary Current dictionary implementation + */ public function getDictionary(): Dictionary { return $this->dictionary; @@ -134,6 +160,12 @@ public function make(string $nominativ, bool $ignoreDict = false): string ->finalize(); } + /** + * Initialize object state for processing a new name + * + * @param string $nominative Name in nominative case + * @return static Current instance for method chaining + */ private function initialize(string $nominative): static { $this->nominative = $nominative; @@ -145,6 +177,12 @@ private function initialize(string $nominative): static return $this; } + /** + * Check if name exists in the dictionary of exceptions + * + * @param bool $ignoreDict Whether to ignore the dictionary lookup + * @return static Current instance for method chaining + */ private function checkException(bool $ignoreDict): static { if (!$ignoreDict && $this->dictionary->hasName($this->nominative)) { @@ -155,6 +193,11 @@ private function checkException(bool $ignoreDict): static return $this; } + /** + * Transform nominative to vocative form using appropriate rules + * + * @return static Current instance for method chaining + */ private function transform(): static { if ($this->done) { @@ -173,6 +216,11 @@ private function transform(): static }; } + /** + * Determine which suffix rule applies to the current name + * + * @return string The matching suffix rule or empty string if none matches + */ private function getRule(): string { foreach (\array_keys(self::SUFFIX_RULES) as $suffix) { @@ -184,6 +232,12 @@ private function getRule(): string return ''; } + /** + * Apply transformation rules for names ending with 'JA' + * Handles special exclusions that don't follow the standard pattern + * + * @return static Current instance for method chaining + */ private function transformJa(): static { foreach (self::JA_EXCLUSIONS as $exclusion) { @@ -195,6 +249,12 @@ private function transformJa(): static return $this->transformDefault('JA'); } + /** + * Apply transformation rules for names ending with 'KA' + * Length-dependent rules determine the proper replacement + * + * @return static Current instance for method chaining + */ private function transformKa(): static { $nominative = $this->nominative; @@ -209,6 +269,12 @@ private function transformKa(): static return $this->setVocative($nominative); } + /** + * Apply default transformation rules based on the identified suffix + * + * @param string $suffix The suffix rule to apply + * @return static Current instance for method chaining + */ private function transformDefault(string $suffix): static { $nominative = $this->nominative; @@ -228,6 +294,12 @@ private function transformDefault(string $suffix): static return $this->transformSpecial(); } + /** + * Apply special transformation rules for cases not handled by suffix rules + * Handles special consonant endings, vowel endings, and default case + * + * @return static Current instance for method chaining + */ private function transformSpecial(): static { $nominative = $this->nominative; @@ -248,6 +320,12 @@ private function transformSpecial(): static return $this->setVocative($nominative . 'E'); } + /** + * Set the vocative form and mark processing as complete + * + * @param string $vocative The final vocative form + * @return static Current instance for method chaining + */ private function setVocative(string $vocative): static { $this->vocative = $vocative; @@ -256,6 +334,13 @@ private function setVocative(string $vocative): static return $this; } + /** + * Check if a text ends with a specific suffix + * + * @param string $text The text to check + * @param string $suffix The suffix to match against + * @return bool True if the text ends with the suffix + */ private function matchSuffix(string $text, string $suffix): bool { return \mb_substr($text, -\mb_strlen($suffix)) === $suffix; @@ -278,7 +363,8 @@ private function finalize(): string /** * Normalize and clean input text * - * @param string $input Input text + * @param string|null $input Input text (uses nominative if null) + * @return static Current instance for method chaining */ private function normalizeInput(?string $input = null): static { diff --git a/src/dictionary.php b/src/dictionary.php deleted file mode 100644 index aa9d0c8..0000000 --- a/src/dictionary.php +++ /dev/null @@ -1,47 +0,0 @@ - 'ALEKSA', - 'ALEKSANDAR' => 'ALEKSANDRE', - 'ANKA' => 'ANKA', - 'BLAĆœA' => 'BLAĆœO', - 'CANA' => 'CANO', - 'CVETA' => 'CVETO', - 'GROZDA' => 'GROZDA', - 'IVKA' => 'IVKA', - 'JANA' => 'JANO', - 'JELA' => 'JELO', - 'JEVTA' => 'JEVTO', - 'KRSTA' => 'KRSTO', - 'LARA' => 'LARA', - 'LAZA' => 'LAZO', - 'LOLA' => 'LOLO', - 'LUKA' => 'LUKA', - 'MATA' => 'MATO', - 'MATEJ' => 'MATEJ', - 'MELISA' => 'MELISA', - 'MICA' => 'MICO', - 'MILESA' => 'MILESA', - 'MIĆA' => 'MIĆO', - 'MOĆ A' => 'MOĆ O', - 'NATA' => 'NATO', - 'OLJA' => 'OLJA', - 'OSTOJA' => 'OSTOJA', - 'RATAR' => 'RATARE', - 'RELJA' => 'RELJA', - 'SIMA' => 'SIMO', - 'SLAĐAN' => 'SLAĐAN', - 'STANA' => 'STANO', - 'STAVRA' => 'STAVRO', - 'TOMA' => 'TOMO', - 'TOĆ A' => 'TOĆ O', - 'VERA' => 'VERA', - 'ZLATA' => 'ZLATO', - 'Ć ANA' => 'Ć ANO', -);