Skip to content

Commit d3a8df8

Browse files
author
Federico Liva
committed
Add italian rules
1 parent 57c8f08 commit d3a8df8

File tree

7 files changed

+487
-0
lines changed

7 files changed

+487
-0
lines changed

src/InflectorFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Doctrine\Inflector\Rules\English;
88
use Doctrine\Inflector\Rules\French;
9+
use Doctrine\Inflector\Rules\Italian;
910
use Doctrine\Inflector\Rules\NorwegianBokmal;
1011
use Doctrine\Inflector\Rules\Portuguese;
1112
use Doctrine\Inflector\Rules\Spanish;
@@ -30,6 +31,9 @@ public static function createForLanguage(string $language): LanguageInflectorFac
3031
case Language::FRENCH:
3132
return new French\InflectorFactory();
3233

34+
case Language::ITALIAN:
35+
return new Italian\InflectorFactory();
36+
3337
case Language::NORWEGIAN_BOKMAL:
3438
return new NorwegianBokmal\InflectorFactory();
3539

src/Language.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ final class Language
88
{
99
public const ENGLISH = 'english';
1010
public const FRENCH = 'french';
11+
public const ITALIAN = 'italian';
1112
public const NORWEGIAN_BOKMAL = 'norwegian-bokmal';
1213
public const PORTUGUESE = 'portuguese';
1314
public const SPANISH = 'spanish';

src/Rules/Italian/Inflectible.php

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Inflector\Rules\Italian;
6+
7+
use Doctrine\Inflector\Rules\Pattern;
8+
use Doctrine\Inflector\Rules\Substitution;
9+
use Doctrine\Inflector\Rules\Transformation;
10+
use Doctrine\Inflector\Rules\Word;
11+
12+
class Inflectible
13+
{
14+
/** @return iterable<Transformation> */
15+
public static function getSingular(): iterable
16+
{
17+
// Reverse of -sce → -scia (fasce → fascia)
18+
yield new Transformation(new Pattern('([aeiou])sce$'), '\\1scia');
19+
20+
// Reverse of -cie → -cia (farmacia → farmacie)
21+
yield new Transformation(new Pattern('cie$'), 'cia');
22+
23+
// Reverse of -gie → -gia (bugia → bugie)
24+
yield new Transformation(new Pattern('gie$'), 'gia');
25+
26+
// Reverse of -ce → -cia (arance → arancia)
27+
yield new Transformation(new Pattern('([^aeiou])ce$'), '\1cia');
28+
29+
// Reverse of -ge → -gia (valige → valigia)
30+
yield new Transformation(new Pattern('([^aeiou])ge$'), '\1gia');
31+
32+
// Reverse of -chi → -co (bachi → baco)
33+
yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])chi$'), '\1co');
34+
35+
// Reverse of -ghi → -go (laghi → lago)
36+
yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])ghi$'), '\1go');
37+
38+
// Reverse of -ci → -co (medici → medico)
39+
yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])ci$'), '\1co');
40+
41+
// Reverse of -gi → -go (psicologi → psicologo)
42+
yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])gi$'), '\1go');
43+
44+
// Reverse of -i → -io (zii → zio, negozi → negozio)
45+
// This is more complex due to Italian's stress patterns, but we'll handle the basic case
46+
yield new Transformation(new Pattern('([^aeiou])i$'), '\1io');
47+
48+
// Handle words that end with -i but should go to -co/-go (amici → amico, not amice)
49+
yield new Transformation(new Pattern('([^aeiou])ci$'), '\1co');
50+
yield new Transformation(new Pattern('([^aeiou])gi$'), '\1go');
51+
52+
// Reverse of -a → -e
53+
yield new Transformation(new Pattern('e$'), 'a');
54+
55+
// Reverse of -e → -i
56+
yield new Transformation(new Pattern('i$'), 'e');
57+
58+
// Reverse of -o → -i
59+
yield new Transformation(new Pattern('i$'), 'o');
60+
}
61+
62+
/** @return iterable<Transformation> */
63+
public static function getPlural(): iterable
64+
{
65+
// Words ending in -scia without stress on 'i' become -sce (e.g. fascia → fasce)
66+
yield new Transformation(new Pattern('([aeiou])scia$'), '\\1sce');
67+
68+
// Words ending in -cia/gia with stress on 'i' keep the 'i' in plural
69+
yield new Transformation(new Pattern('cia$'), 'cie'); // e.g. farmacia → farmacie
70+
yield new Transformation(new Pattern('gia$'), 'gie'); // e.g. bugia → bugie
71+
72+
// Words ending in -cia/gia without stress on 'i' lose the 'i' in plural
73+
yield new Transformation(new Pattern('([^aeiou])cia$'), '\\1ce'); // e.g. arancia → arance
74+
yield new Transformation(new Pattern('([^aeiou])gia$'), '\\1ge'); // e.g. valigia → valige
75+
76+
// Words ending in -co/-go with stress on 'o' become -chi/-ghi
77+
yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])co$'), '\\1chi'); // e.g. baco → bachi
78+
yield new Transformation(new Pattern('([bcdfghjklmnpqrstvwxyz][aeiou])go$'), '\\1ghi'); // e.g. lago → laghi
79+
80+
// Words ending in -co/-go with stress on the penultimate syllable become -ci/-gi
81+
yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])co$'), '\\1ci'); // e.g. medico → medici
82+
yield new Transformation(new Pattern('([aeiou][bcdfghjklmnpqrstvwxyz])go$'), '\\1gi'); // e.g. psicologo → psicologi
83+
84+
// Words ending in -io with stress on 'i' keep the 'i' in plural
85+
yield new Transformation(new Pattern('([^aeiou])io$'), '\\1i'); // e.g. zio → zii
86+
87+
// Words ending in -io with stress on 'o' lose the 'i' in plural
88+
yield new Transformation(new Pattern('([aeiou])io$'), '\\1i'); // e.g. negozio → negozi
89+
90+
// Standard ending rules
91+
yield new Transformation(new Pattern('a$'), 'e'); // -a → -e
92+
yield new Transformation(new Pattern('e$'), 'i'); // -e → -i
93+
yield new Transformation(new Pattern('o$'), 'i'); // -o → -i
94+
}
95+
96+
/** @return iterable<Substitution> */
97+
public static function getIrregular(): iterable
98+
{
99+
// Irregular substitutions (singular => plural)
100+
$irregulars = [
101+
'ala' => 'ali',
102+
'albergo' => 'alberghi',
103+
'amica' => 'amiche',
104+
'amico' => 'amici',
105+
'ampio' => 'ampi',
106+
'arancia' => 'arance',
107+
'arma' => 'armi',
108+
'asparago' => 'asparagi',
109+
'banca' => 'banche',
110+
'belga' => 'belgi',
111+
'braccio' => 'braccia',
112+
'budello' => 'budella',
113+
'bue' => 'buoi',
114+
'caccia' => 'cacce',
115+
'calcagno' => 'calcagna',
116+
'camicia' => 'camicie',
117+
'cane' => 'cani',
118+
'capitale' => 'capitali',
119+
'carcere' => 'carceri',
120+
'casa' => 'case',
121+
'cavaliere' => 'cavalieri',
122+
'centinaio' => 'centinaia',
123+
'cerchio' => 'cerchia',
124+
'cervello' => 'cervella',
125+
'chiave' => 'chiavi',
126+
'chirurgo' => 'chirurgi',
127+
'ciglio' => 'ciglia',
128+
'città' => 'città',
129+
'corno' => 'corna',
130+
'corpo' => 'corpi',
131+
'crisi' => 'crisi',
132+
'dente' => 'denti',
133+
'dio' => 'dei',
134+
'dito' => 'dita',
135+
'dottore' => 'dottori',
136+
'fiore' => 'fiori',
137+
'fratello' => 'fratelli',
138+
'fuoco' => 'fuochi',
139+
'gamba' => 'gambe',
140+
'ginocchio' => 'ginocchia',
141+
'gioco' => 'giochi',
142+
'giornale' => 'giornali',
143+
'giraffa' => 'giraffe',
144+
'labbro' => 'labbra',
145+
'lenzuolo' => 'lenzuola',
146+
'libro' => 'libri',
147+
'madre' => 'madri',
148+
'maestro' => 'maestri',
149+
'magico' => 'magici',
150+
'mago' => 'maghi',
151+
'maniaco' => 'maniaci',
152+
'manico' => 'manici',
153+
'mano' => 'mani',
154+
'medico' => 'medici',
155+
'membro' => 'membri',
156+
'metropoli' => 'metropoli',
157+
'migliaio' => 'migliaia',
158+
'miglio' => 'miglia',
159+
'mille' => 'mila',
160+
'mio' => 'miei',
161+
'moglie' => 'mogli',
162+
'mosaico' => 'mosaici',
163+
'muro' => 'muri',
164+
'nemico' => 'nemici',
165+
'nome' => 'nomi',
166+
'occhio' => 'occhi',
167+
'orecchio' => 'orecchi',
168+
'osso' => 'ossa',
169+
'paio' => 'paia',
170+
'pane' => 'pani',
171+
'papa' => 'papi',
172+
'pasta' => 'paste',
173+
'penna' => 'penne',
174+
'pesce' => 'pesci',
175+
'piede' => 'piedi',
176+
'pittore' => 'pittori',
177+
'poeta' => 'poeti',
178+
'porco' => 'porci',
179+
'porto' => 'porti',
180+
'problema' => 'problemi',
181+
'ragazzo' => 'ragazzi',
182+
're' => 're',
183+
'rene' => 'reni',
184+
'riso' => 'risa',
185+
'rosa' => 'rosa',
186+
'sale' => 'sali',
187+
'sarto' => 'sarti',
188+
'scuola' => 'scuole',
189+
'serie' => 'serie',
190+
'serramento' => 'serramenta',
191+
'sorella' => 'sorelle',
192+
'specie' => 'specie',
193+
'staio' => 'staia',
194+
'stazione' => 'stazioni',
195+
'strido' => 'strida',
196+
'strillo' => 'strilla',
197+
'studio' => 'studi',
198+
'suo' => 'suoi',
199+
'superficie' => 'superfici',
200+
'tavolo' => 'tavoli',
201+
'tempio' => 'templi',
202+
'treno' => 'treni',
203+
'tuo' => 'tuoi',
204+
'uomo' => 'uomini',
205+
'uovo' => 'uova',
206+
'urlo' => 'urla',
207+
'valigia' => 'valigie',
208+
'vestigio' => 'vestigia',
209+
'vino' => 'vini',
210+
'viola' => 'viola',
211+
'zio' => 'zii',
212+
];
213+
214+
foreach ($irregulars as $singular => $plural) {
215+
yield new Substitution(new Word($singular), new Word($plural));
216+
}
217+
}
218+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Inflector\Rules\Italian;
6+
7+
use Doctrine\Inflector\GenericLanguageInflectorFactory;
8+
use Doctrine\Inflector\Rules\Ruleset;
9+
10+
final class InflectorFactory extends GenericLanguageInflectorFactory
11+
{
12+
protected function getSingularRuleset(): Ruleset
13+
{
14+
return Rules::getSingularRuleset();
15+
}
16+
17+
protected function getPluralRuleset(): Ruleset
18+
{
19+
return Rules::getPluralRuleset();
20+
}
21+
}

src/Rules/Italian/Rules.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Inflector\Rules\Italian;
6+
7+
use Doctrine\Inflector\Rules\Patterns;
8+
use Doctrine\Inflector\Rules\Ruleset;
9+
use Doctrine\Inflector\Rules\Substitutions;
10+
use Doctrine\Inflector\Rules\Transformations;
11+
12+
final class Rules
13+
{
14+
public static function getSingularRuleset(): Ruleset
15+
{
16+
return new Ruleset(
17+
new Transformations(...Inflectible::getSingular()),
18+
new Patterns(...Uninflected::getSingular()),
19+
(new Substitutions(...Inflectible::getIrregular()))->getFlippedSubstitutions()
20+
);
21+
}
22+
23+
public static function getPluralRuleset(): Ruleset
24+
{
25+
return new Ruleset(
26+
new Transformations(...Inflectible::getPlural()),
27+
new Patterns(...Uninflected::getPlural()),
28+
new Substitutions(...Inflectible::getIrregular())
29+
);
30+
}
31+
}

src/Rules/Italian/Uninflected.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\Inflector\Rules\Italian;
6+
7+
use Doctrine\Inflector\Rules\Pattern;
8+
9+
final class Uninflected
10+
{
11+
/** @return iterable<Pattern> */
12+
public static function getSingular(): iterable
13+
{
14+
yield from self::getDefault();
15+
}
16+
17+
/** @return iterable<Pattern> */
18+
public static function getPlural(): iterable
19+
{
20+
yield from self::getDefault();
21+
}
22+
23+
/** @return iterable<Pattern> */
24+
private static function getDefault(): iterable
25+
{
26+
// Invariable words (same form in singular and plural)
27+
$invariables = [
28+
'alpaca',
29+
'auto',
30+
'bar',
31+
'blu',
32+
'boia',
33+
'boomerang',
34+
'brindisi',
35+
'campus',
36+
'computer',
37+
'crisi',
38+
'crocevia',
39+
'dopocena',
40+
'film',
41+
'foto',
42+
'fuchsia',
43+
'gnu',
44+
'gorilla',
45+
'gru',
46+
'iguana',
47+
'kamikaze',
48+
'karaoke',
49+
'koala',
50+
'lama',
51+
'menu',
52+
'metropoli',
53+
'moto',
54+
'opossum',
55+
'panda',
56+
'quiz',
57+
'radio',
58+
're',
59+
'scacciapensieri',
60+
'serie',
61+
'smartphone',
62+
'sosia',
63+
'sottoscala',
64+
'specie',
65+
'sport',
66+
'tablet',
67+
'taxi',
68+
'vaglia',
69+
'virtù',
70+
'virus',
71+
'yogurt',
72+
'foto',
73+
'fuchsia',
74+
];
75+
76+
foreach ($invariables as $word) {
77+
yield new Pattern($word);
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)