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/.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
+
+[](https://packagist.org/packages/oblak/vocative)
+
+
+
+
+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/.phpcs.xml b/phpcs.xml
similarity index 100%
rename from .phpcs.xml
rename to phpcs.xml
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',
-);