xref: /webtrees/app/Services/RelationshipService.php (revision b3679361640bf9d3bb5424fb15815c51cf0c1513)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2021 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Services;
21
22use Fisharebest\Webtrees\Auth;
23use Fisharebest\Webtrees\Fact;
24use Fisharebest\Webtrees\Family;
25use Fisharebest\Webtrees\I18N;
26use Fisharebest\Webtrees\Individual;
27use Fisharebest\Webtrees\Module\ModuleLanguageInterface;
28use Fisharebest\Webtrees\Relationship;
29
30use function abs;
31use function array_key_exists;
32use function array_merge;
33use function array_reduce;
34use function array_slice;
35use function count;
36use function implode;
37use function intdiv;
38use function min;
39use function preg_match;
40use function sprintf;
41use function strlen;
42use function substr;
43
44/**
45 * Names for relationships.
46 */
47class RelationshipService
48{
49    private const COMPONENTS = [
50        'CHIL' => [
51            'CHIL' => Relationship::SIBLINGS,
52            'HUSB' => Relationship::PARENTS,
53            'WIFE' => Relationship::PARENTS,
54        ],
55        'HUSB' => [
56            'CHIL' => Relationship::CHILDREN,
57            'HUSB' => Relationship::SPOUSES,
58            'WIFE' => Relationship::SPOUSES,
59        ],
60        'WIFE' => [
61            'CHIL' => Relationship::CHILDREN,
62            'HUSB' => Relationship::SPOUSES,
63            'WIFE' => Relationship::SPOUSES,
64        ],
65    ];
66
67    /**
68     * For close family relationships, such as the families tab, associates, and the family navigator.
69     *
70     * @param Individual $individual1
71     * @param Individual $individual2
72     *
73     * @return string
74     */
75    public function getCloseRelationshipName(Individual $individual1, Individual $individual2): string
76    {
77        $language = app(ModuleService::class)
78            ->findByInterface(ModuleLanguageInterface::class, true)
79            ->first(fn (ModuleLanguageInterface $language): bool => $language->locale()->languageTag() === I18N::languageTag());
80
81        $path = $this->getCloseRelationship($individual1, $individual2);
82
83        // No relationship found?
84        if ($path === []) {
85            return '';
86        }
87
88        return $this->nameFromPath($path, $language);
89    }
90
91    /**
92     * Get relationship between two individuals in the gedcom.  This function
93     * takes account of pending changes, so we can display names of newly added
94     * relations.
95     *
96     * @param Individual $individual1
97     * @param Individual $individual2
98     * @param int        $maxlength
99     *
100     * @return array<Individual|Family> An array of nodes on the relationship path
101     */
102    private function getCloseRelationship(Individual $individual1, Individual $individual2, int $maxlength = 4): array
103    {
104        if ($individual1 === $individual2) {
105            return [$individual1];
106        }
107
108        // Only examine each individual once
109        $visited = [
110            $individual1->xref() => true,
111        ];
112
113        // Build paths out from the first individual
114        $paths = [
115            [$individual1],
116        ];
117
118        // Loop over paths of length 1, 2, 3, ...
119        while ($maxlength >= 0) {
120            $maxlength--;
121
122            foreach ($paths as $i => $path) {
123                // Try each new relation from the end of the path
124                $indi = $path[count($path) - 1];
125
126                // Parents and siblings
127                foreach ($indi->childFamilies(Auth::PRIV_HIDE) as $family) {
128                    $visited[$family->xref()] = true;
129                    foreach ($family->spouses(Auth::PRIV_HIDE) as $spouse) {
130                        if (!isset($visited[$spouse->xref()])) {
131                            $new_path   = $path;
132                            $new_path[] = $family;
133                            $new_path[] = $spouse;
134                            if ($spouse === $individual2) {
135                                return $new_path;
136                            }
137
138                            $paths[]                  = $new_path;
139                            $visited[$spouse->xref()] = true;
140                        }
141                    }
142                    foreach ($family->children(Auth::PRIV_HIDE) as $child) {
143                        if (!isset($visited[$child->xref()])) {
144                            $new_path   = $path;
145                            $new_path[] = $family;
146                            $new_path[] = $child;
147                            if ($child === $individual2) {
148                                return $new_path;
149                            }
150
151                            $paths[]                 = $new_path;
152                            $visited[$child->xref()] = true;
153                        }
154                    }
155                }
156
157                // Spouses and children
158                foreach ($indi->spouseFamilies(Auth::PRIV_HIDE) as $family) {
159                    $visited[$family->xref()] = true;
160                    foreach ($family->spouses(Auth::PRIV_HIDE) as $spouse) {
161                        if (!isset($visited[$spouse->xref()])) {
162                            $new_path   = $path;
163                            $new_path[] = $family;
164                            $new_path[] = $spouse;
165                            if ($spouse === $individual2) {
166                                return $new_path;
167                            }
168
169                            $paths[]                  = $new_path;
170                            $visited[$spouse->xref()] = true;
171                        }
172                    }
173                    foreach ($family->children(Auth::PRIV_HIDE) as $child) {
174                        if (!isset($visited[$child->xref()])) {
175                            $new_path   = $path;
176                            $new_path[] = $family;
177                            $new_path[] = $child;
178                            if ($child === $individual2) {
179                                return $new_path;
180                            }
181
182                            $paths[]                 = $new_path;
183                            $visited[$child->xref()] = true;
184                        }
185                    }
186                }
187                unset($paths[$i]);
188            }
189        }
190
191        return [];
192    }
193
194    /**
195     * @param array<Individual|Family> $nodes
196     * @param ModuleLanguageInterface  $language
197     *
198     * @return string
199     */
200    public function nameFromPath(array $nodes, ModuleLanguageInterface $language): string
201    {
202        // The relationship matching algorithm could be used for this, but it is more efficient to check it here.
203        if (count($nodes) === 1) {
204            return $this->reflexivePronoun($nodes[0]);
205        }
206
207        // The relationship definitions for the language.
208        $relationships = $language->relationships();
209
210        // We don't strictly need this, as all the information is contained in the nodes.
211        // But it gives us simpler code and better performance.
212        // It is also needed for the legacy algorithm.
213        $pattern = $this->components($nodes);
214
215        // No definitions for this language?  Use the legacy algorithm.
216        if ($relationships === []) {
217            return $this->legacyNameAlgorithm(implode('', $pattern), $nodes[0], $nodes[count($nodes) - 1]);
218        }
219
220        // Match the relationship, using a longest-substring algorithm.
221        $relationships = $this->matchRelationships($nodes, $pattern, $relationships);
222
223        // Reduce the genitive-nominative chain to a single string.
224        return array_reduce($relationships, static function (array $carry, array $item): array {
225            return [sprintf($carry[1], $item[0]), sprintf($carry[1], $item[1])];
226        }, [0 => '', 1 => '%s'])[0];
227    }
228
229    /**
230     * Generate a reflexive pronoun for an individual
231     *
232     * @param Individual $individual
233     *
234     * @return string
235     */
236    protected function reflexivePronoun(Individual $individual): string
237    {
238        switch ($individual->sex()) {
239            case 'M':
240                /* I18N: reflexive pronoun */
241                return I18N::translate('himself');
242            case 'F':
243                /* I18N: reflexive pronoun */
244                return I18N::translate('herself');
245            default:
246                /* I18N: reflexive pronoun - gender neutral version of himself/herself */
247                return I18N::translate('themself');
248        }
249    }
250
251    /**
252     * Convert a relationship path into its component pieces; brother, wife, mother, daughter, etc.
253     *
254     * @param array<Individual|Family> $nodes - Alternating list of Individual and Family objects
255     *
256     * @return array<string>
257     */
258    private function components(array $nodes): array
259    {
260        $pattern = [];
261
262        $count = count($nodes);
263
264        for ($i = 1; $i < $count; $i += 2) {
265            $prev   = $nodes[$i - 1];
266            $family = $nodes[$i];
267            $next   = $nodes[$i + 1];
268
269            preg_match('/\n1 (HUSB|WIFE|CHIL) @' . $prev->xref() . '@/', $family->gedcom(), $match);
270            $rel1 = $match[1] ?? 'xxx';
271
272            preg_match('/\n1 (HUSB|WIFE|CHIL) @' . $next->xref() . '@/', $family->gedcom(), $match);
273            $rel2 = $match[1] ?? 'xxx';
274
275            $pattern[] = self::COMPONENTS[$rel1][$rel2][$next->sex()] ?? 'xxx';
276        }
277
278        return $pattern;
279    }
280
281    /**
282     * @param array<Individual|Family> $nodes
283     * @param array<string>            $pattern
284     * @param array<Relationship>      $relationships
285     *
286     * @return array<Relationship>
287     */
288    protected function matchRelationships(array $nodes, array $pattern, array $relationships): array
289    {
290        $count = count($pattern);
291
292        // Look for the longest matchable series of components
293        for ($length = $count; $length > 0; $length--) {
294            for ($start = $count - $length; $start >= 0; $start--) {
295                foreach ($relationships as $relationship) {
296                    $path_slice    = array_slice($nodes, $start * 2, $length * 2 + 1);
297                    $pattern_slice = array_slice($pattern, $start, $length);
298                    $result        = $relationship->match($path_slice, $pattern_slice);
299
300                    if ($result !== null) {
301                        $nodes_before   = array_slice($nodes, 0, $start * 2 + 1);
302                        $pattern_before = array_slice($pattern, 0, $start);
303                        $result_before  = $this->matchRelationships($nodes_before, $pattern_before, $relationships);
304
305                        $nodes_after   = array_slice($nodes, ($start + $length) * 2);
306                        $pattern_after = array_slice($pattern, $start + $length);
307                        $result_after  = $this->matchRelationships($nodes_after, $pattern_after, $relationships);
308
309                        return array_merge($result_before, [$result], $result_after);
310                    }
311                }
312            }
313        }
314
315        return [];
316    }
317
318    /**
319     * @param string          $path
320     * @param Individual|null $person1
321     * @param Individual|null $person2
322     *
323     * @return string
324     *
325     * @deprecated This code was originally Functions::getRelationshipNameFromPath
326     */
327    public function legacyNameAlgorithm(string $path, Individual $person1 = null, Individual $person2 = null): string
328    {
329        // The path does not include the starting person. In some languages, the
330        // translation for a man’s (relative) is different from a woman’s (relative),
331        // due to inflection.
332        $sex1 = $person1 ? $person1->sex() : 'U';
333
334        // The sex of the last person in the relationship determines the name in
335        // many cases. e.g. great-aunt / great-uncle
336        if (preg_match('/(fat|hus|son|bro)$/', $path)) {
337            $sex2 = 'M';
338        } elseif (preg_match('/(mot|wif|dau|sis)$/', $path)) {
339            $sex2 = 'F';
340        } else {
341            $sex2 = 'U';
342        }
343
344        switch ($path) {
345            case '':
346                return I18N::translate('self');
347            //  Level One relationships
348            case 'mot':
349                return I18N::translate('mother');
350            case 'fat':
351                return I18N::translate('father');
352            case 'par':
353                return I18N::translate('parent');
354            case 'hus':
355                if ($person1 instanceof Individual && $person2 instanceof Individual) {
356                    // We had the linking family earlier, but lost it.  Find it again.
357                    foreach ($person1->spouseFamilies(Auth::PRIV_HIDE) as $family) {
358                        if ($person2 === $family->spouse($person1)) {
359                            $event = $family->facts(['ANUL', 'DIV', 'ENGA', 'MARR'], true, Auth::PRIV_HIDE, true)->last();
360
361                            if ($event instanceof Fact) {
362                                switch ($event->tag()) {
363                                    case 'FAM:ANUL':
364                                    case 'FAM:DIV':
365                                        return I18N::translate('ex-husband');
366                                    case 'FAM:MARR':
367                                        return I18N::translate('husband');
368                                    case 'FAM:ENGA':
369                                        return I18N::translate('fiancé');
370                                }
371                            }
372                        }
373                    }
374                }
375
376                return I18N::translateContext('MALE', 'partner');
377
378            case 'wif':
379                if ($person1 instanceof Individual && $person2 instanceof Individual) {
380                    // We had the linking family earlier, but lost it.  Find it again.
381                    foreach ($person1->spouseFamilies(Auth::PRIV_HIDE) as $family) {
382                        if ($person2 === $family->spouse($person1)) {
383                            $event = $family->facts(['ANUL', 'DIV', 'ENGA', 'MARR'], true, Auth::PRIV_HIDE, true)->last();
384
385                            if ($event instanceof Fact) {
386                                switch ($event->tag()) {
387                                    case 'FAM:ANUL':
388                                    case 'FAM:DIV':
389                                        return I18N::translate('ex-wife');
390                                    case 'FAM:MARR':
391                                        return I18N::translate('wife');
392                                    case 'FAM:ENGA':
393                                        return I18N::translate('fiancée');
394                                }
395                            }
396                        }
397                    }
398                }
399
400                return I18N::translateContext('FEMALE', 'partner');
401            case 'spo':
402                if ($person1 instanceof Individual && $person2 instanceof Individual) {
403                    // We had the linking family earlier, but lost it.  Find it again.
404                    foreach ($person1->spouseFamilies(Auth::PRIV_HIDE) as $family) {
405                        if ($person2 === $family->spouse($person1)) {
406                            $event = $family->facts(['ANUL', 'DIV', 'ENGA', 'MARR'], true, Auth::PRIV_HIDE, true)->last();
407
408                            if ($event instanceof Fact) {
409                                switch ($event->tag()) {
410                                    case 'FAM:ANUL':
411                                    case 'FAM:DIV':
412                                        return I18N::translate('ex-spouse');
413                                    case 'FAM:MARR':
414                                        return I18N::translate('spouse');
415                                    case 'FAM:ENGA':
416                                        return I18N::translate('fiancé(e)');
417                                }
418                            }
419                        }
420                    }
421                }
422
423                return I18N::translate('partner');
424
425            case 'son':
426                return I18N::translate('son');
427            case 'dau':
428                return I18N::translate('daughter');
429            case 'chi':
430                return I18N::translate('child');
431            case 'bro':
432                if ($person1 && $person2) {
433                    $dob1 = $person1->getBirthDate();
434                    $dob2 = $person2->getBirthDate();
435                    if ($dob1->isOK() && $dob2->isOK()) {
436                        if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && $dob1->minimumDate()->day > 0 && $dob2->minimumDate()->day > 0) {
437                            // Exclude BEF, AFT, etc.
438                            return I18N::translate('twin brother');
439                        }
440
441                        if ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
442                            return I18N::translate('younger brother');
443                        }
444
445                        if ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
446                            return I18N::translate('elder brother');
447                        }
448                    }
449                }
450
451                return I18N::translate('brother');
452            case 'sis':
453                if ($person1 && $person2) {
454                    $dob1 = $person1->getBirthDate();
455                    $dob2 = $person2->getBirthDate();
456                    if ($dob1->isOK() && $dob2->isOK()) {
457                        if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && $dob1->minimumDate()->day > 0 && $dob2->minimumDate()->day > 0) {
458                            // Exclude BEF, AFT, etc.
459                            return I18N::translate('twin sister');
460                        }
461
462                        if ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
463                            return I18N::translate('younger sister');
464                        }
465
466                        if ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
467                            return I18N::translate('elder sister');
468                        }
469                    }
470                }
471
472                return I18N::translate('sister');
473            case 'sib':
474                if ($person1 && $person2) {
475                    $dob1 = $person1->getBirthDate();
476                    $dob2 = $person2->getBirthDate();
477                    if ($dob1->isOK() && $dob2->isOK()) {
478                        if (abs($dob1->julianDay() - $dob2->julianDay()) < 2 && $dob1->minimumDate()->day > 0 && $dob2->minimumDate()->day > 0) {
479                            // Exclude BEF, AFT, etc.
480                            return I18N::translate('twin sibling');
481                        }
482
483                        if ($dob1->maximumJulianDay() < $dob2->minimumJulianDay()) {
484                            return I18N::translate('younger sibling');
485                        }
486
487                        if ($dob1->minimumJulianDay() > $dob2->maximumJulianDay()) {
488                            return I18N::translate('elder sibling');
489                        }
490                    }
491                }
492
493                return I18N::translate('sibling');
494
495            // Level Two relationships
496            case 'brochi':
497                return I18N::translateContext('brother’s child', 'nephew/niece');
498            case 'brodau':
499                return I18N::translateContext('brother’s daughter', 'niece');
500            case 'broson':
501                return I18N::translateContext('brother’s son', 'nephew');
502            case 'browif':
503                return I18N::translateContext('brother’s wife', 'sister-in-law');
504            case 'chichi':
505                return I18N::translateContext('child’s child', 'grandchild');
506            case 'chidau':
507                return I18N::translateContext('child’s daughter', 'granddaughter');
508            case 'chihus':
509                return I18N::translateContext('child’s husband', 'son-in-law');
510            case 'chison':
511                return I18N::translateContext('child’s son', 'grandson');
512            case 'chispo':
513                return I18N::translateContext('child’s spouse', 'son/daughter-in-law');
514            case 'chiwif':
515                return I18N::translateContext('child’s wife', 'daughter-in-law');
516            case 'dauchi':
517                return I18N::translateContext('daughter’s child', 'grandchild');
518            case 'daudau':
519                return I18N::translateContext('daughter’s daughter', 'granddaughter');
520            case 'dauhus':
521                return I18N::translateContext('daughter’s husband', 'son-in-law');
522            case 'dauson':
523                return I18N::translateContext('daughter’s son', 'grandson');
524            case 'fatbro':
525                return I18N::translateContext('father’s brother', 'uncle');
526            case 'fatchi':
527                return I18N::translateContext('father’s child', 'half-sibling');
528            case 'fatdau':
529                return I18N::translateContext('father’s daughter', 'half-sister');
530            case 'fatfat':
531                return I18N::translateContext('father’s father', 'paternal grandfather');
532            case 'fatmot':
533                return I18N::translateContext('father’s mother', 'paternal grandmother');
534            case 'fatpar':
535                return I18N::translateContext('father’s parent', 'paternal grandparent');
536            case 'fatsib':
537                return I18N::translateContext('father’s sibling', 'aunt/uncle');
538            case 'fatsis':
539                return I18N::translateContext('father’s sister', 'aunt');
540            case 'fatson':
541                return I18N::translateContext('father’s son', 'half-brother');
542            case 'fatwif':
543                return I18N::translateContext('father’s wife', 'step-mother');
544            case 'husbro':
545                return I18N::translateContext('husband’s brother', 'brother-in-law');
546            case 'huschi':
547                return I18N::translateContext('husband’s child', 'step-child');
548            case 'husdau':
549                return I18N::translateContext('husband’s daughter', 'step-daughter');
550            case 'husfat':
551                return I18N::translateContext('husband’s father', 'father-in-law');
552            case 'husmot':
553                return I18N::translateContext('husband’s mother', 'mother-in-law');
554            case 'hussib':
555                return I18N::translateContext('husband’s sibling', 'brother/sister-in-law');
556            case 'hussis':
557                return I18N::translateContext('husband’s sister', 'sister-in-law');
558            case 'husson':
559                return I18N::translateContext('husband’s son', 'step-son');
560            case 'motbro':
561                return I18N::translateContext('mother’s brother', 'uncle');
562            case 'motchi':
563                return I18N::translateContext('mother’s child', 'half-sibling');
564            case 'motdau':
565                return I18N::translateContext('mother’s daughter', 'half-sister');
566            case 'motfat':
567                return I18N::translateContext('mother’s father', 'maternal grandfather');
568            case 'mothus':
569                return I18N::translateContext('mother’s husband', 'step-father');
570            case 'motmot':
571                return I18N::translateContext('mother’s mother', 'maternal grandmother');
572            case 'motpar':
573                return I18N::translateContext('mother’s parent', 'maternal grandparent');
574            case 'motsib':
575                return I18N::translateContext('mother’s sibling', 'aunt/uncle');
576            case 'motsis':
577                return I18N::translateContext('mother’s sister', 'aunt');
578            case 'motson':
579                return I18N::translateContext('mother’s son', 'half-brother');
580            case 'parbro':
581                return I18N::translateContext('parent’s brother', 'uncle');
582            case 'parchi':
583                return I18N::translateContext('parent’s child', 'half-sibling');
584            case 'pardau':
585                return I18N::translateContext('parent’s daughter', 'half-sister');
586            case 'parfat':
587                return I18N::translateContext('parent’s father', 'grandfather');
588            case 'parmot':
589                return I18N::translateContext('parent’s mother', 'grandmother');
590            case 'parpar':
591                return I18N::translateContext('parent’s parent', 'grandparent');
592            case 'parsib':
593                return I18N::translateContext('parent’s sibling', 'aunt/uncle');
594            case 'parsis':
595                return I18N::translateContext('parent’s sister', 'aunt');
596            case 'parson':
597                return I18N::translateContext('parent’s son', 'half-brother');
598            case 'parspo':
599                return I18N::translateContext('parent’s spouse', 'step-parent');
600            case 'sibchi':
601                return I18N::translateContext('sibling’s child', 'nephew/niece');
602            case 'sibdau':
603                return I18N::translateContext('sibling’s daughter', 'niece');
604            case 'sibson':
605                return I18N::translateContext('sibling’s son', 'nephew');
606            case 'sibspo':
607                return I18N::translateContext('sibling’s spouse', 'brother/sister-in-law');
608            case 'sischi':
609                return I18N::translateContext('sister’s child', 'nephew/niece');
610            case 'sisdau':
611                return I18N::translateContext('sister’s daughter', 'niece');
612            case 'sishus':
613                return I18N::translateContext('sister’s husband', 'brother-in-law');
614            case 'sisson':
615                return I18N::translateContext('sister’s son', 'nephew');
616            case 'sonchi':
617                return I18N::translateContext('son’s child', 'grandchild');
618            case 'sondau':
619                return I18N::translateContext('son’s daughter', 'granddaughter');
620            case 'sonson':
621                return I18N::translateContext('son’s son', 'grandson');
622            case 'sonwif':
623                return I18N::translateContext('son’s wife', 'daughter-in-law');
624            case 'spobro':
625                return I18N::translateContext('spouse’s brother', 'brother-in-law');
626            case 'spochi':
627                return I18N::translateContext('spouse’s child', 'step-child');
628            case 'spodau':
629                return I18N::translateContext('spouse’s daughter', 'step-daughter');
630            case 'spofat':
631                return I18N::translateContext('spouse’s father', 'father-in-law');
632            case 'spomot':
633                return I18N::translateContext('spouse’s mother', 'mother-in-law');
634            case 'sposis':
635                return I18N::translateContext('spouse’s sister', 'sister-in-law');
636            case 'sposon':
637                return I18N::translateContext('spouse’s son', 'step-son');
638            case 'spopar':
639                return I18N::translateContext('spouse’s parent', 'mother/father-in-law');
640            case 'sposib':
641                return I18N::translateContext('spouse’s sibling', 'brother/sister-in-law');
642            case 'wifbro':
643                return I18N::translateContext('wife’s brother', 'brother-in-law');
644            case 'wifchi':
645                return I18N::translateContext('wife’s child', 'step-child');
646            case 'wifdau':
647                return I18N::translateContext('wife’s daughter', 'step-daughter');
648            case 'wiffat':
649                return I18N::translateContext('wife’s father', 'father-in-law');
650            case 'wifmot':
651                return I18N::translateContext('wife’s mother', 'mother-in-law');
652            case 'wifsib':
653                return I18N::translateContext('wife’s sibling', 'brother/sister-in-law');
654            case 'wifsis':
655                return I18N::translateContext('wife’s sister', 'sister-in-law');
656            case 'wifson':
657                return I18N::translateContext('wife’s son', 'step-son');
658
659            // Level Three relationships
660            case 'brochichi':
661                if ($sex1 === 'M') {
662                    return I18N::translateContext('(a man’s) brother’s child’s child', 'great-nephew/niece');
663                }
664
665                return I18N::translateContext('(a woman’s) brother’s child’s child', 'great-nephew/niece');
666            case 'brochidau':
667                if ($sex1 === 'M') {
668                    return I18N::translateContext('(a man’s) brother’s child’s daughter', 'great-niece');
669                }
670
671                return I18N::translateContext('(a woman’s) brother’s child’s daughter', 'great-niece');
672            case 'brochison':
673                if ($sex1 === 'M') {
674                    return I18N::translateContext('(a man’s) brother’s child’s son', 'great-nephew');
675                }
676
677                return I18N::translateContext('(a woman’s) brother’s child’s son', 'great-nephew');
678            case 'brodauchi':
679                if ($sex1 === 'M') {
680                    return I18N::translateContext('(a man’s) brother’s daughter’s child', 'great-nephew/niece');
681                }
682
683                return I18N::translateContext('(a woman’s) brother’s daughter’s child', 'great-nephew/niece');
684            case 'brodaudau':
685                if ($sex1 === 'M') {
686                    return I18N::translateContext('(a man’s) brother’s daughter’s daughter', 'great-niece');
687                }
688
689                return I18N::translateContext('(a woman’s) brother’s daughter’s daughter', 'great-niece');
690            case 'brodauhus':
691                return I18N::translateContext('brother’s daughter’s husband', 'nephew-in-law');
692            case 'brodauson':
693                if ($sex1 === 'M') {
694                    return I18N::translateContext('(a man’s) brother’s daughter’s son', 'great-nephew');
695                }
696
697                return I18N::translateContext('(a woman’s) brother’s daughter’s son', 'great-nephew');
698            case 'brosonchi':
699                if ($sex1 === 'M') {
700                    return I18N::translateContext('(a man’s) brother’s son’s child', 'great-nephew/niece');
701                }
702
703                return I18N::translateContext('(a woman’s) brother’s son’s child', 'great-nephew/niece');
704            case 'brosondau':
705                if ($sex1 === 'M') {
706                    return I18N::translateContext('(a man’s) brother’s son’s daughter', 'great-niece');
707                }
708
709                return I18N::translateContext('(a woman’s) brother’s son’s daughter', 'great-niece');
710            case 'brosonson':
711                if ($sex1 === 'M') {
712                    return I18N::translateContext('(a man’s) brother’s son’s son', 'great-nephew');
713                }
714
715                return I18N::translateContext('(a woman’s) brother’s son’s son', 'great-nephew');
716            case 'brosonwif':
717                return I18N::translateContext('brother’s son’s wife', 'niece-in-law');
718            case 'browifbro':
719                return I18N::translateContext('brother’s wife’s brother', 'brother-in-law');
720            case 'browifsib':
721                return I18N::translateContext('brother’s wife’s sibling', 'brother/sister-in-law');
722            case 'browifsis':
723                return I18N::translateContext('brother’s wife’s sister', 'sister-in-law');
724            case 'chichichi':
725                return I18N::translateContext('child’s child’s child', 'great-grandchild');
726            case 'chichidau':
727                return I18N::translateContext('child’s child’s daughter', 'great-granddaughter');
728            case 'chichison':
729                return I18N::translateContext('child’s child’s son', 'great-grandson');
730            case 'chidauchi':
731                return I18N::translateContext('child’s daughter’s child', 'great-grandchild');
732            case 'chidaudau':
733                return I18N::translateContext('child’s daughter’s daughter', 'great-granddaughter');
734            case 'chidauhus':
735                return I18N::translateContext('child’s daughter’s husband', 'granddaughter’s husband');
736            case 'chidauson':
737                return I18N::translateContext('child’s daughter’s son', 'great-grandson');
738            case 'chisonchi':
739                return I18N::translateContext('child’s son’s child', 'great-grandchild');
740            case 'chisondau':
741                return I18N::translateContext('child’s son’s daughter', 'great-granddaughter');
742            case 'chisonson':
743                return I18N::translateContext('child’s son’s son', 'great-grandson');
744            case 'chisonwif':
745                return I18N::translateContext('child’s son’s wife', 'grandson’s wife');
746            case 'dauchichi':
747                return I18N::translateContext('daughter’s child’s child', 'great-grandchild');
748            case 'dauchidau':
749                return I18N::translateContext('daughter’s child’s daughter', 'great-granddaughter');
750            case 'dauchison':
751                return I18N::translateContext('daughter’s child’s son', 'great-grandson');
752            case 'daudauchi':
753                return I18N::translateContext('daughter’s daughter’s child', 'great-grandchild');
754            case 'daudaudau':
755                return I18N::translateContext('daughter’s daughter’s daughter', 'great-granddaughter');
756            case 'daudauhus':
757                return I18N::translateContext('daughter’s daughter’s husband', 'granddaughter’s husband');
758            case 'daudauson':
759                return I18N::translateContext('daughter’s daughter’s son', 'great-grandson');
760            case 'dauhusfat':
761                return I18N::translateContext('daughter’s husband’s father', 'son-in-law’s father');
762            case 'dauhusmot':
763                return I18N::translateContext('daughter’s husband’s mother', 'son-in-law’s mother');
764            case 'dauhuspar':
765                return I18N::translateContext('daughter’s husband’s parent', 'son-in-law’s parent');
766            case 'dausonchi':
767                return I18N::translateContext('daughter’s son’s child', 'great-grandchild');
768            case 'dausondau':
769                return I18N::translateContext('daughter’s son’s daughter', 'great-granddaughter');
770            case 'dausonson':
771                return I18N::translateContext('daughter’s son’s son', 'great-grandson');
772            case 'dausonwif':
773                return I18N::translateContext('daughter’s son’s wife', 'grandson’s wife');
774            case 'fatbrochi':
775                return I18N::translateContext('father’s brother’s child', 'first cousin');
776            case 'fatbrodau':
777                return I18N::translateContext('father’s brother’s daughter', 'first cousin');
778            case 'fatbroson':
779                return I18N::translateContext('father’s brother’s son', 'first cousin');
780            case 'fatbrowif':
781                return I18N::translateContext('father’s brother’s wife', 'aunt');
782            case 'fatfatbro':
783                return I18N::translateContext('father’s father’s brother', 'great-uncle');
784            case 'fatfatfat':
785                return I18N::translateContext('father’s father’s father', 'great-grandfather');
786            case 'fatfatmot':
787                return I18N::translateContext('father’s father’s mother', 'great-grandmother');
788            case 'fatfatpar':
789                return I18N::translateContext('father’s father’s parent', 'great-grandparent');
790            case 'fatfatsib':
791                return I18N::translateContext('father’s father’s sibling', 'great-aunt/uncle');
792            case 'fatfatsis':
793                return I18N::translateContext('father’s father’s sister', 'great-aunt');
794            case 'fatmotbro':
795                return I18N::translateContext('father’s mother’s brother', 'great-uncle');
796            case 'fatmotfat':
797                return I18N::translateContext('father’s mother’s father', 'great-grandfather');
798            case 'fatmotmot':
799                return I18N::translateContext('father’s mother’s mother', 'great-grandmother');
800            case 'fatmotpar':
801                return I18N::translateContext('father’s mother’s parent', 'great-grandparent');
802            case 'fatmotsib':
803                return I18N::translateContext('father’s mother’s sibling', 'great-aunt/uncle');
804            case 'fatmotsis':
805                return I18N::translateContext('father’s mother’s sister', 'great-aunt');
806            case 'fatparbro':
807                return I18N::translateContext('father’s parent’s brother', 'great-uncle');
808            case 'fatparfat':
809                return I18N::translateContext('father’s parent’s father', 'great-grandfather');
810            case 'fatparmot':
811                return I18N::translateContext('father’s parent’s mother', 'great-grandmother');
812            case 'fatparpar':
813                return I18N::translateContext('father’s parent’s parent', 'great-grandparent');
814            case 'fatparsib':
815                return I18N::translateContext('father’s parent’s sibling', 'great-aunt/uncle');
816            case 'fatparsis':
817                return I18N::translateContext('father’s parent’s sister', 'great-aunt');
818            case 'fatsischi':
819                return I18N::translateContext('father’s sister’s child', 'first cousin');
820            case 'fatsisdau':
821                return I18N::translateContext('father’s sister’s daughter', 'first cousin');
822            case 'fatsishus':
823                return I18N::translateContext('father’s sister’s husband', 'uncle');
824            case 'fatsisson':
825                return I18N::translateContext('father’s sister’s son', 'first cousin');
826            case 'fatwifchi':
827                return I18N::translateContext('father’s wife’s child', 'step-sibling');
828            case 'fatwifdau':
829                return I18N::translateContext('father’s wife’s daughter', 'step-sister');
830            case 'fatwifson':
831                return I18N::translateContext('father’s wife’s son', 'step-brother');
832            case 'husbrowif':
833                return I18N::translateContext('husband’s brother’s wife', 'sister-in-law');
834            case 'hussishus':
835                return I18N::translateContext('husband’s sister’s husband', 'brother-in-law');
836            case 'hussibchi':
837                return I18N::translateContext('husband’s sibling’s child', 'nephew/niece');
838            case 'hussischi':
839                return I18N::translateContext('husband’s sister’s child', 'nephew/niece');
840            case 'husbrochi':
841                return I18N::translateContext('husband’s brother’s child', 'nephew/niece');
842            case 'hussibdau':
843                return I18N::translateContext('husband’s sibling’s daughter', 'niece');
844            case 'hussisdau':
845                return I18N::translateContext('husband’s sister’s daughter', 'niece');
846            case 'husbrodau':
847                return I18N::translateContext('husband’s brother’s daughter', 'niece');
848            case 'hussibson':
849                return I18N::translateContext('husband’s sibling’s son', 'nephew');
850            case 'hussisson':
851                return I18N::translateContext('husband’s sister’s son', 'nephew');
852            case 'husbroson':
853                return I18N::translateContext('husband’s brother’s son', 'nephew');
854            case 'motbrochi':
855                return I18N::translateContext('mother’s brother’s child', 'first cousin');
856            case 'motbrodau':
857                return I18N::translateContext('mother’s brother’s daughter', 'first cousin');
858            case 'motbroson':
859                return I18N::translateContext('mother’s brother’s son', 'first cousin');
860            case 'motbrowif':
861                return I18N::translateContext('mother’s brother’s wife', 'aunt');
862            case 'motfatbro':
863                return I18N::translateContext('mother’s father’s brother', 'great-uncle');
864            case 'motfatfat':
865                return I18N::translateContext('mother’s father’s father', 'great-grandfather');
866            case 'motfatmot':
867                return I18N::translateContext('mother’s father’s mother', 'great-grandmother');
868            case 'motfatpar':
869                return I18N::translateContext('mother’s father’s parent', 'great-grandparent');
870            case 'motfatsib':
871                return I18N::translateContext('mother’s father’s sibling', 'great-aunt/uncle');
872            case 'motfatsis':
873                return I18N::translateContext('mother’s father’s sister', 'great-aunt');
874            case 'mothuschi':
875                return I18N::translateContext('mother’s husband’s child', 'step-sibling');
876            case 'mothusdau':
877                return I18N::translateContext('mother’s husband’s daughter', 'step-sister');
878            case 'mothusson':
879                return I18N::translateContext('mother’s husband’s son', 'step-brother');
880            case 'motmotbro':
881                return I18N::translateContext('mother’s mother’s brother', 'great-uncle');
882            case 'motmotfat':
883                return I18N::translateContext('mother’s mother’s father', 'great-grandfather');
884            case 'motmotmot':
885                return I18N::translateContext('mother’s mother’s mother', 'great-grandmother');
886            case 'motmotpar':
887                return I18N::translateContext('mother’s mother’s parent', 'great-grandparent');
888            case 'motmotsib':
889                return I18N::translateContext('mother’s mother’s sibling', 'great-aunt/uncle');
890            case 'motmotsis':
891                return I18N::translateContext('mother’s mother’s sister', 'great-aunt');
892            case 'motparbro':
893                return I18N::translateContext('mother’s parent’s brother', 'great-uncle');
894            case 'motparfat':
895                return I18N::translateContext('mother’s parent’s father', 'great-grandfather');
896            case 'motparmot':
897                return I18N::translateContext('mother’s parent’s mother', 'great-grandmother');
898            case 'motparpar':
899                return I18N::translateContext('mother’s parent’s parent', 'great-grandparent');
900            case 'motparsib':
901                return I18N::translateContext('mother’s parent’s sibling', 'great-aunt/uncle');
902            case 'motparsis':
903                return I18N::translateContext('mother’s parent’s sister', 'great-aunt');
904            case 'motsischi':
905                return I18N::translateContext('mother’s sister’s child', 'first cousin');
906            case 'motsisdau':
907                return I18N::translateContext('mother’s sister’s daughter', 'first cousin');
908            case 'motsishus':
909                return I18N::translateContext('mother’s sister’s husband', 'uncle');
910            case 'motsisson':
911                return I18N::translateContext('mother’s sister’s son', 'first cousin');
912            case 'parbrowif':
913                return I18N::translateContext('parent’s brother’s wife', 'aunt');
914            case 'parfatbro':
915                return I18N::translateContext('parent’s father’s brother', 'great-uncle');
916            case 'parfatfat':
917                return I18N::translateContext('parent’s father’s father', 'great-grandfather');
918            case 'parfatmot':
919                return I18N::translateContext('parent’s father’s mother', 'great-grandmother');
920            case 'parfatpar':
921                return I18N::translateContext('parent’s father’s parent', 'great-grandparent');
922            case 'parfatsib':
923                return I18N::translateContext('parent’s father’s sibling', 'great-aunt/uncle');
924            case 'parfatsis':
925                return I18N::translateContext('parent’s father’s sister', 'great-aunt');
926            case 'parmotbro':
927                return I18N::translateContext('parent’s mother’s brother', 'great-uncle');
928            case 'parmotfat':
929                return I18N::translateContext('parent’s mother’s father', 'great-grandfather');
930            case 'parmotmot':
931                return I18N::translateContext('parent’s mother’s mother', 'great-grandmother');
932            case 'parmotpar':
933                return I18N::translateContext('parent’s mother’s parent', 'great-grandparent');
934            case 'parmotsib':
935                return I18N::translateContext('parent’s mother’s sibling', 'great-aunt/uncle');
936            case 'parmotsis':
937                return I18N::translateContext('parent’s mother’s sister', 'great-aunt');
938            case 'parparbro':
939                return I18N::translateContext('parent’s parent’s brother', 'great-uncle');
940            case 'parparfat':
941                return I18N::translateContext('parent’s parent’s father', 'great-grandfather');
942            case 'parparmot':
943                return I18N::translateContext('parent’s parent’s mother', 'great-grandmother');
944            case 'parparpar':
945                return I18N::translateContext('parent’s parent’s parent', 'great-grandparent');
946            case 'parparsib':
947                return I18N::translateContext('parent’s parent’s sibling', 'great-aunt/uncle');
948            case 'parparsis':
949                return I18N::translateContext('parent’s parent’s sister', 'great-aunt');
950            case 'parsishus':
951                return I18N::translateContext('parent’s sister’s husband', 'uncle');
952            case 'parspochi':
953                return I18N::translateContext('parent’s spouse’s child', 'step-sibling');
954            case 'parspodau':
955                return I18N::translateContext('parent’s spouse’s daughter', 'step-sister');
956            case 'parsposon':
957                return I18N::translateContext('parent’s spouse’s son', 'step-brother');
958            case 'sibchichi':
959                return I18N::translateContext('sibling’s child’s child', 'great-nephew/niece');
960            case 'sibchidau':
961                return I18N::translateContext('sibling’s child’s daughter', 'great-niece');
962            case 'sibchison':
963                return I18N::translateContext('sibling’s child’s son', 'great-nephew');
964            case 'sibdauchi':
965                return I18N::translateContext('sibling’s daughter’s child', 'great-nephew/niece');
966            case 'sibdaudau':
967                return I18N::translateContext('sibling’s daughter’s daughter', 'great-niece');
968            case 'sibdauhus':
969                return I18N::translateContext('sibling’s daughter’s husband', 'nephew-in-law');
970            case 'sibdauson':
971                return I18N::translateContext('sibling’s daughter’s son', 'great-nephew');
972            case 'sibsonchi':
973                return I18N::translateContext('sibling’s son’s child', 'great-nephew/niece');
974            case 'sibsondau':
975                return I18N::translateContext('sibling’s son’s daughter', 'great-niece');
976            case 'sibsonson':
977                return I18N::translateContext('sibling’s son’s son', 'great-nephew');
978            case 'sibsonwif':
979                return I18N::translateContext('sibling’s son’s wife', 'niece-in-law');
980            case 'sischichi':
981                if ($sex1 === 'M') {
982                    return I18N::translateContext('(a man’s) sister’s child’s child', 'great-nephew/niece');
983                }
984
985                return I18N::translateContext('(a woman’s) sister’s child’s child', 'great-nephew/niece');
986            case 'sischidau':
987                if ($sex1 === 'M') {
988                    return I18N::translateContext('(a man’s) sister’s child’s daughter', 'great-niece');
989                }
990
991                return I18N::translateContext('(a woman’s) sister’s child’s daughter', 'great-niece');
992            case 'sischison':
993                if ($sex1 === 'M') {
994                    return I18N::translateContext('(a man’s) sister’s child’s son', 'great-nephew');
995                }
996
997                return I18N::translateContext('(a woman’s) sister’s child’s son', 'great-nephew');
998            case 'sisdauchi':
999                if ($sex1 === 'M') {
1000                    return I18N::translateContext('(a man’s) sister’s daughter’s child', 'great-nephew/niece');
1001                }
1002
1003                return I18N::translateContext('(a woman’s) sister’s daughter’s child', 'great-nephew/niece');
1004            case 'sisdaudau':
1005                if ($sex1 === 'M') {
1006                    return I18N::translateContext('(a man’s) sister’s daughter’s daughter', 'great-niece');
1007                }
1008
1009                return I18N::translateContext('(a woman’s) sister’s daughter’s daughter', 'great-niece');
1010            case 'sisdauhus':
1011                return I18N::translateContext('sisters’s daughter’s husband', 'nephew-in-law');
1012            case 'sisdauson':
1013                if ($sex1 === 'M') {
1014                    return I18N::translateContext('(a man’s) sister’s daughter’s son', 'great-nephew');
1015                }
1016
1017                return I18N::translateContext('(a woman’s) sister’s daughter’s son', 'great-nephew');
1018            case 'sishusbro':
1019                return I18N::translateContext('sister’s husband’s brother', 'brother-in-law');
1020            case 'sishussib':
1021                return I18N::translateContext('sister’s husband’s sibling', 'brother/sister-in-law');
1022            case 'sishussis':
1023                return I18N::translateContext('sister’s husband’s sister', 'sister-in-law');
1024            case 'sissonchi':
1025                if ($sex1 === 'M') {
1026                    return I18N::translateContext('(a man’s) sister’s son’s child', 'great-nephew/niece');
1027                }
1028
1029                return I18N::translateContext('(a woman’s) sister’s son’s child', 'great-nephew/niece');
1030            case 'sissondau':
1031                if ($sex1 === 'M') {
1032                    return I18N::translateContext('(a man’s) sister’s son’s daughter', 'great-niece');
1033                }
1034
1035                return I18N::translateContext('(a woman’s) sister’s son’s daughter', 'great-niece');
1036            case 'sissonson':
1037                if ($sex1 === 'M') {
1038                    return I18N::translateContext('(a man’s) sister’s son’s son', 'great-nephew');
1039                }
1040
1041                return I18N::translateContext('(a woman’s) sister’s son’s son', 'great-nephew');
1042            case 'sissonwif':
1043                return I18N::translateContext('sisters’s son’s wife', 'niece-in-law');
1044            case 'sonchichi':
1045                return I18N::translateContext('son’s child’s child', 'great-grandchild');
1046            case 'sonchidau':
1047                return I18N::translateContext('son’s child’s daughter', 'great-granddaughter');
1048            case 'sonchison':
1049                return I18N::translateContext('son’s child’s son', 'great-grandson');
1050            case 'sondauchi':
1051                return I18N::translateContext('son’s daughter’s child', 'great-grandchild');
1052            case 'sondaudau':
1053                return I18N::translateContext('son’s daughter’s daughter', 'great-granddaughter');
1054            case 'sondauhus':
1055                return I18N::translateContext('son’s daughter’s husband', 'granddaughter’s husband');
1056            case 'sondauson':
1057                return I18N::translateContext('son’s daughter’s son', 'great-grandson');
1058            case 'sonsonchi':
1059                return I18N::translateContext('son’s son’s child', 'great-grandchild');
1060            case 'sonsondau':
1061                return I18N::translateContext('son’s son’s daughter', 'great-granddaughter');
1062            case 'sonsonson':
1063                return I18N::translateContext('son’s son’s son', 'great-grandson');
1064            case 'sonsonwif':
1065                return I18N::translateContext('son’s son’s wife', 'grandson’s wife');
1066            case 'sonwiffat':
1067                return I18N::translateContext('son’s wife’s father', 'daughter-in-law’s father');
1068            case 'sonwifmot':
1069                return I18N::translateContext('son’s wife’s mother', 'daughter-in-law’s mother');
1070            case 'sonwifpar':
1071                return I18N::translateContext('son’s wife’s parent', 'daughter-in-law’s parent');
1072            case 'wifbrowif':
1073                return I18N::translateContext('wife’s brother’s wife', 'sister-in-law');
1074            case 'wifsishus':
1075                return I18N::translateContext('wife’s sister’s husband', 'brother-in-law');
1076            case 'wifsibchi':
1077                return I18N::translateContext('wife’s sibling’s child', 'nephew/niece');
1078            case 'wifsischi':
1079                return I18N::translateContext('wife’s sister’s child', 'nephew/niece');
1080            case 'wifbrochi':
1081                return I18N::translateContext('wife’s brother’s child', 'nephew/niece');
1082            case 'wifsibdau':
1083                return I18N::translateContext('wife’s sibling’s daughter', 'niece');
1084            case 'wifsisdau':
1085                return I18N::translateContext('wife’s sister’s daughter', 'niece');
1086            case 'wifbrodau':
1087                return I18N::translateContext('wife’s brother’s daughter', 'niece');
1088            case 'wifsibson':
1089                return I18N::translateContext('wife’s sibling’s son', 'nephew');
1090            case 'wifsisson':
1091                return I18N::translateContext('wife’s sister’s son', 'nephew');
1092            case 'wifbroson':
1093                return I18N::translateContext('wife’s brother’s son', 'nephew');
1094
1095            // Some “special case” level four relationships that have specific names in certain languages
1096            case 'fatfatbrowif':
1097                return I18N::translateContext('father’s father’s brother’s wife', 'great-aunt');
1098            case 'fatfatsibspo':
1099                return I18N::translateContext('father’s father’s sibling’s spouse', 'great-aunt/uncle');
1100            case 'fatfatsishus':
1101                return I18N::translateContext('father’s father’s sister’s husband', 'great-uncle');
1102            case 'fatmotbrowif':
1103                return I18N::translateContext('father’s mother’s brother’s wife', 'great-aunt');
1104            case 'fatmotsibspo':
1105                return I18N::translateContext('father’s mother’s sibling’s spouse', 'great-aunt/uncle');
1106            case 'fatmotsishus':
1107                return I18N::translateContext('father’s mother’s sister’s husband', 'great-uncle');
1108            case 'fatparbrowif':
1109                return I18N::translateContext('father’s parent’s brother’s wife', 'great-aunt');
1110            case 'fatparsibspo':
1111                return I18N::translateContext('father’s parent’s sibling’s spouse', 'great-aunt/uncle');
1112            case 'fatparsishus':
1113                return I18N::translateContext('father’s parent’s sister’s husband', 'great-uncle');
1114            case 'motfatbrowif':
1115                return I18N::translateContext('mother’s father’s brother’s wife', 'great-aunt');
1116            case 'motfatsibspo':
1117                return I18N::translateContext('mother’s father’s sibling’s spouse', 'great-aunt/uncle');
1118            case 'motfatsishus':
1119                return I18N::translateContext('mother’s father’s sister’s husband', 'great-uncle');
1120            case 'motmotbrowif':
1121                return I18N::translateContext('mother’s mother’s brother’s wife', 'great-aunt');
1122            case 'motmotsibspo':
1123                return I18N::translateContext('mother’s mother’s sibling’s spouse', 'great-aunt/uncle');
1124            case 'motmotsishus':
1125                return I18N::translateContext('mother’s mother’s sister’s husband', 'great-uncle');
1126            case 'motparbrowif':
1127                return I18N::translateContext('mother’s parent’s brother’s wife', 'great-aunt');
1128            case 'motparsibspo':
1129                return I18N::translateContext('mother’s parent’s sibling’s spouse', 'great-aunt/uncle');
1130            case 'motparsishus':
1131                return I18N::translateContext('mother’s parent’s sister’s husband', 'great-uncle');
1132            case 'parfatbrowif':
1133                return I18N::translateContext('parent’s father’s brother’s wife', 'great-aunt');
1134            case 'parfatsibspo':
1135                return I18N::translateContext('parent’s father’s sibling’s spouse', 'great-aunt/uncle');
1136            case 'parfatsishus':
1137                return I18N::translateContext('parent’s father’s sister’s husband', 'great-uncle');
1138            case 'parmotbrowif':
1139                return I18N::translateContext('parent’s mother’s brother’s wife', 'great-aunt');
1140            case 'parmotsibspo':
1141                return I18N::translateContext('parent’s mother’s sibling’s spouse', 'great-aunt/uncle');
1142            case 'parmotsishus':
1143                return I18N::translateContext('parent’s mother’s sister’s husband', 'great-uncle');
1144            case 'parparbrowif':
1145                return I18N::translateContext('parent’s parent’s brother’s wife', 'great-aunt');
1146            case 'parparsibspo':
1147                return I18N::translateContext('parent’s parent’s sibling’s spouse', 'great-aunt/uncle');
1148            case 'parparsishus':
1149                return I18N::translateContext('parent’s parent’s sister’s husband', 'great-uncle');
1150            case 'fatfatbrodau':
1151                return I18N::translateContext('father’s father’s brother’s daughter', 'first cousin once removed ascending');
1152            case 'fatfatbroson':
1153                return I18N::translateContext('father’s father’s brother’s son', 'first cousin once removed ascending');
1154            case 'fatfatbrochi':
1155                return I18N::translateContext('father’s father’s brother’s child', 'first cousin once removed ascending');
1156            case 'fatfatsisdau':
1157                return I18N::translateContext('father’s father’s sister’s daughter', 'first cousin once removed ascending');
1158            case 'fatfatsisson':
1159                return I18N::translateContext('father’s father’s sister’s son', 'first cousin once removed ascending');
1160            case 'fatfatsischi':
1161                return I18N::translateContext('father’s father’s sister’s child', 'first cousin once removed ascending');
1162            case 'fatmotbrodau':
1163                return I18N::translateContext('father’s mother’s brother’s daughter', 'first cousin once removed ascending');
1164            case 'fatmotbroson':
1165                return I18N::translateContext('father’s mother’s brother’s son', 'first cousin once removed ascending');
1166            case 'fatmotbrochi':
1167                return I18N::translateContext('father’s mother’s brother’s child', 'first cousin once removed ascending');
1168            case 'fatmotsisdau':
1169                return I18N::translateContext('father’s mother’s sister’s daughter', 'first cousin once removed ascending');
1170            case 'fatmotsisson':
1171                return I18N::translateContext('father’s mother’s sister’s son', 'first cousin once removed ascending');
1172            case 'fatmotsischi':
1173                return I18N::translateContext('father’s mother’s sister’s child', 'first cousin once removed ascending');
1174            case 'motfatbrodau':
1175                return I18N::translateContext('mother’s father’s brother’s daughter', 'first cousin once removed ascending');
1176            case 'motfatbroson':
1177                return I18N::translateContext('mother’s father’s brother’s son', 'first cousin once removed ascending');
1178            case 'motfatbrochi':
1179                return I18N::translateContext('mother’s father’s brother’s child', 'first cousin once removed ascending');
1180            case 'motfatsisdau':
1181                return I18N::translateContext('mother’s father’s sister’s daughter', 'first cousin once removed ascending');
1182            case 'motfatsisson':
1183                return I18N::translateContext('mother’s father’s sister’s son', 'first cousin once removed ascending');
1184            case 'motfatsischi':
1185                return I18N::translateContext('mother’s father’s sister’s child', 'first cousin once removed ascending');
1186            case 'motmotbrodau':
1187                return I18N::translateContext('mother’s mother’s brother’s daughter', 'first cousin once removed ascending');
1188            case 'motmotbroson':
1189                return I18N::translateContext('mother’s mother’s brother’s son', 'first cousin once removed ascending');
1190            case 'motmotbrochi':
1191                return I18N::translateContext('mother’s mother’s brother’s child', 'first cousin once removed ascending');
1192            case 'motmotsisdau':
1193                return I18N::translateContext('mother’s mother’s sister’s daughter', 'first cousin once removed ascending');
1194            case 'motmotsisson':
1195                return I18N::translateContext('mother’s mother’s sister’s son', 'first cousin once removed ascending');
1196            case 'motmotsischi':
1197                return I18N::translateContext('mother’s mother’s sister’s child', 'first cousin once removed ascending');
1198        }
1199
1200        // Some “special case” level five relationships that have specific names in certain languages
1201        if (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)dau$/', $path)) {
1202            return I18N::translateContext('grandfather’s brother’s granddaughter', 'second cousin');
1203        }
1204
1205        if (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)son$/', $path)) {
1206            return I18N::translateContext('grandfather’s brother’s grandson', 'second cousin');
1207        }
1208
1209        if (preg_match('/^(mot|fat|par)fatbro(son|dau|chi)chi$/', $path)) {
1210            return I18N::translateContext('grandfather’s brother’s grandchild', 'second cousin');
1211        }
1212
1213        if (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)dau$/', $path)) {
1214            return I18N::translateContext('grandfather’s sister’s granddaughter', 'second cousin');
1215        }
1216
1217        if (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)son$/', $path)) {
1218            return I18N::translateContext('grandfather’s sister’s grandson', 'second cousin');
1219        }
1220
1221        if (preg_match('/^(mot|fat|par)fatsis(son|dau|chi)chi$/', $path)) {
1222            return I18N::translateContext('grandfather’s sister’s grandchild', 'second cousin');
1223        }
1224
1225        if (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)dau$/', $path)) {
1226            return I18N::translateContext('grandfather’s sibling’s granddaughter', 'second cousin');
1227        }
1228
1229        if (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)son$/', $path)) {
1230            return I18N::translateContext('grandfather’s sibling’s grandson', 'second cousin');
1231        }
1232
1233        if (preg_match('/^(mot|fat|par)fatsib(son|dau|chi)chi$/', $path)) {
1234            return I18N::translateContext('grandfather’s sibling’s grandchild', 'second cousin');
1235        }
1236
1237        if (preg_match('/^(mot|fat|par)motbro(son|dau|chi)dau$/', $path)) {
1238            return I18N::translateContext('grandmother’s brother’s granddaughter', 'second cousin');
1239        }
1240
1241        if (preg_match('/^(mot|fat|par)motbro(son|dau|chi)son$/', $path)) {
1242            return I18N::translateContext('grandmother’s brother’s grandson', 'second cousin');
1243        }
1244
1245        if (preg_match('/^(mot|fat|par)motbro(son|dau|chi)chi$/', $path)) {
1246            return I18N::translateContext('grandmother’s brother’s grandchild', 'second cousin');
1247        }
1248
1249        if (preg_match('/^(mot|fat|par)motsis(son|dau|chi)dau$/', $path)) {
1250            return I18N::translateContext('grandmother’s sister’s granddaughter', 'second cousin');
1251        }
1252
1253        if (preg_match('/^(mot|fat|par)motsis(son|dau|chi)son$/', $path)) {
1254            return I18N::translateContext('grandmother’s sister’s grandson', 'second cousin');
1255        }
1256
1257        if (preg_match('/^(mot|fat|par)motsis(son|dau|chi)chi$/', $path)) {
1258            return I18N::translateContext('grandmother’s sister’s grandchild', 'second cousin');
1259        }
1260
1261        if (preg_match('/^(mot|fat|par)motsib(son|dau|chi)dau$/', $path)) {
1262            return I18N::translateContext('grandmother’s sibling’s granddaughter', 'second cousin');
1263        }
1264
1265        if (preg_match('/^(mot|fat|par)motsib(son|dau|chi)son$/', $path)) {
1266            return I18N::translateContext('grandmother’s sibling’s grandson', 'second cousin');
1267        }
1268
1269        if (preg_match('/^(mot|fat|par)motsib(son|dau|chi)chi$/', $path)) {
1270            return I18N::translateContext('grandmother’s sibling’s grandchild', 'second cousin');
1271        }
1272
1273        if (preg_match('/^(mot|fat|par)parbro(son|dau|chi)dau$/', $path)) {
1274            return I18N::translateContext('grandparent’s brother’s granddaughter', 'second cousin');
1275        }
1276
1277        if (preg_match('/^(mot|fat|par)parbro(son|dau|chi)son$/', $path)) {
1278            return I18N::translateContext('grandparent’s brother’s grandson', 'second cousin');
1279        }
1280
1281        if (preg_match('/^(mot|fat|par)parbro(son|dau|chi)chi$/', $path)) {
1282            return I18N::translateContext('grandparent’s brother’s grandchild', 'second cousin');
1283        }
1284
1285        if (preg_match('/^(mot|fat|par)parsis(son|dau|chi)dau$/', $path)) {
1286            return I18N::translateContext('grandparent’s sister’s granddaughter', 'second cousin');
1287        }
1288
1289        if (preg_match('/^(mot|fat|par)parsis(son|dau|chi)son$/', $path)) {
1290            return I18N::translateContext('grandparent’s sister’s grandson', 'second cousin');
1291        }
1292
1293        if (preg_match('/^(mot|fat|par)parsis(son|dau|chi)chi$/', $path)) {
1294            return I18N::translateContext('grandparent’s sister’s grandchild', 'second cousin');
1295        }
1296
1297        if (preg_match('/^(mot|fat|par)parsib(son|dau|chi)dau$/', $path)) {
1298            return I18N::translateContext('grandparent’s sibling’s granddaughter', 'second cousin');
1299        }
1300
1301        if (preg_match('/^(mot|fat|par)parsib(son|dau|chi)son$/', $path)) {
1302            return I18N::translateContext('grandparent’s sibling’s grandson', 'second cousin');
1303        }
1304
1305        if (preg_match('/^(mot|fat|par)parsib(son|dau|chi)chi$/', $path)) {
1306            return I18N::translateContext('grandparent’s sibling’s grandchild', 'second cousin');
1307        }
1308
1309        // Look for generic/pattern relationships.
1310        if (preg_match('/^((?:mot|fat|par)+)(bro|sis|sib)$/', $path, $match)) {
1311            // siblings of direct ancestors
1312            $up       = intdiv(strlen($match[1]), 3);
1313            $bef_last = substr($path, -6, 3);
1314            switch ($up) {
1315                case 3:
1316                    if ($sex2 === 'M') {
1317                        if ($bef_last === 'fat') {
1318                            return I18N::translateContext('great-grandfather’s brother', 'great-great-uncle');
1319                        }
1320
1321                        if ($bef_last === 'mot') {
1322                            return I18N::translateContext('great-grandmother’s brother', 'great-great-uncle');
1323                        }
1324
1325                        return I18N::translateContext('great-grandparent’s brother', 'great-great-uncle');
1326                    }
1327
1328                    if ($sex2 === 'F') {
1329                        return I18N::translate('great-great-aunt');
1330                    }
1331
1332                    return I18N::translate('great-great-aunt/uncle');
1333
1334                case 4:
1335                    if ($sex2 === 'M') {
1336                        if ($bef_last === 'fat') {
1337                            return I18N::translateContext('great-great-grandfather’s brother', 'great-great-great-uncle');
1338                        }
1339
1340                        if ($bef_last === 'mot') {
1341                            return I18N::translateContext('great-great-grandmother’s brother', 'great-great-great-uncle');
1342                        }
1343
1344                        return I18N::translateContext('great-great-grandparent’s brother', 'great-great-great-uncle');
1345                    }
1346
1347                    if ($sex2 === 'F') {
1348                        return I18N::translate('great-great-great-aunt');
1349                    }
1350
1351                    return I18N::translate('great-great-great-aunt/uncle');
1352
1353                case 5:
1354                    if ($sex2 === 'M') {
1355                        if ($bef_last === 'fat') {
1356                            return I18N::translateContext('great-great-great-grandfather’s brother', 'great ×4 uncle');
1357                        }
1358
1359                        if ($bef_last === 'mot') {
1360                            return I18N::translateContext('great-great-great-grandmother’s brother', 'great ×4 uncle');
1361                        }
1362
1363                        return I18N::translateContext('great-great-great-grandparent’s brother', 'great ×4 uncle');
1364                    }
1365
1366                    if ($sex2 === 'F') {
1367                        return I18N::translate('great ×4 aunt');
1368                    }
1369
1370                    return I18N::translate('great ×4 aunt/uncle');
1371
1372                case 6:
1373                    if ($sex2 === 'M') {
1374                        if ($bef_last === 'fat') {
1375                            return I18N::translateContext('great ×4 grandfather’s brother', 'great ×5 uncle');
1376                        }
1377
1378                        if ($bef_last === 'mot') {
1379                            return I18N::translateContext('great ×4 grandmother’s brother', 'great ×5 uncle');
1380                        }
1381
1382                        return I18N::translateContext('great ×4 grandparent’s brother', 'great ×5 uncle');
1383                    }
1384
1385                    if ($sex2 === 'F') {
1386                        return I18N::translate('great ×5 aunt');
1387                    }
1388
1389                    return I18N::translate('great ×5 aunt/uncle');
1390
1391                case 7:
1392                    if ($sex2 === 'M') {
1393                        if ($bef_last === 'fat') {
1394                            return I18N::translateContext('great ×5 grandfather’s brother', 'great ×6 uncle');
1395                        }
1396
1397                        if ($bef_last === 'mot') {
1398                            return I18N::translateContext('great ×5 grandmother’s brother', 'great ×6 uncle');
1399                        }
1400
1401                        return I18N::translateContext('great ×5 grandparent’s brother', 'great ×6 uncle');
1402                    }
1403
1404                    if ($sex2 === 'F') {
1405                        return I18N::translate('great ×6 aunt');
1406                    }
1407
1408                    return I18N::translate('great ×6 aunt/uncle');
1409
1410                case 8:
1411                    if ($sex2 === 'M') {
1412                        if ($bef_last === 'fat') {
1413                            return I18N::translateContext('great ×6 grandfather’s brother', 'great ×7 uncle');
1414                        }
1415
1416                        if ($bef_last === 'mot') {
1417                            return I18N::translateContext('great ×6 grandmother’s brother', 'great ×7 uncle');
1418                        }
1419
1420                        return I18N::translateContext('great ×6 grandparent’s brother', 'great ×7 uncle');
1421                    }
1422
1423                    if ($sex2 === 'F') {
1424                        return I18N::translate('great ×7 aunt');
1425                    }
1426
1427                    return I18N::translate('great ×7 aunt/uncle');
1428
1429                default:
1430                    // Different languages have different rules for naming generations.
1431                    // An English great ×12 uncle is a Danish great ×10 uncle.
1432                    //
1433                    // Need to find out which languages use which rules.
1434                    switch (I18N::languageTag()) {
1435                        case 'da':
1436                            if ($sex2 === 'M') {
1437                                return I18N::translate('great ×%s uncle', I18N::number($up - 4));
1438                            }
1439
1440                            if ($sex2 === 'F') {
1441                                return I18N::translate('great ×%s aunt', I18N::number($up - 4));
1442                            }
1443
1444                            return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 4));
1445
1446                        case 'pl':
1447                            if ($sex2 === 'M') {
1448                                if ($bef_last === 'fat') {
1449                                    return I18N::translateContext('great ×(%s-1) grandfather’s brother', 'great ×%s uncle', I18N::number($up - 2));
1450                                }
1451
1452                                if ($bef_last === 'mot') {
1453                                    return I18N::translateContext('great ×(%s-1) grandmother’s brother', 'great ×%s uncle', I18N::number($up - 2));
1454                                }
1455
1456                                return I18N::translateContext('great ×(%s-1) grandparent’s brother', 'great ×%s uncle', I18N::number($up - 2));
1457                            }
1458
1459                            if ($sex2 === 'F') {
1460                                return I18N::translate('great ×%s aunt', I18N::number($up - 2));
1461                            }
1462
1463                            return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 2));
1464
1465                        case 'hi': // Source: MrQD
1466                            if ($sex2 === 'M') {
1467                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1468                                return I18N::translate('great ×%s uncle', I18N::number($up - 2));
1469                            }
1470
1471                            if ($sex2 === 'F') {
1472                                return I18N::translate('great ×%s aunt', I18N::number($up - 2));
1473                            }
1474
1475                            return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 2));
1476
1477                        case 'zh-Hans': // Source: xmlf
1478                        case 'zh-Hant':
1479                            if ($sex2 === 'M') {
1480                                return I18N::translate('great ×%s uncle', I18N::number($up));
1481                            }
1482                            if ($sex2 === 'F') {
1483                                return I18N::translate('great ×%s aunt', I18N::number($up));
1484                            }
1485
1486                            return I18N::translate('great ×%s aunt/uncle', I18N::number($up));
1487
1488                        case 'it': // Source: Michele Locati
1489                        case 'en_AU':
1490                        case 'en_GB':
1491                        case 'en_US':
1492                        default:
1493                            if ($sex2 === 'M') {
1494                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1495                                return I18N::translate('great ×%s uncle', I18N::number($up - 1));
1496                            }
1497
1498                            if ($sex2 === 'F') {
1499                                return I18N::translate('great ×%s aunt', I18N::number($up - 1));
1500                            }
1501
1502                            return I18N::translate('great ×%s aunt/uncle', I18N::number($up - 1));
1503                    }
1504            }
1505        }
1506        if (preg_match('/^(?:bro|sis|sib)((?:son|dau|chi)+)$/', $path, $match)) {
1507            // direct descendants of siblings
1508            $down  = intdiv(strlen($match[1]), 3) + 1; // Add one, as we count generations from the common ancestor
1509            $first = substr($path, 0, 3);
1510            switch ($down) {
1511                case 4:
1512                    if ($sex2 === 'M') {
1513                        if ($first === 'bro' && $sex1 === 'M') {
1514                            return I18N::translateContext('(a man’s) brother’s great-grandson', 'great-great-nephew');
1515                        }
1516
1517                        if ($first === 'sis' && $sex1 === 'M') {
1518                            return I18N::translateContext('(a man’s) sister’s great-grandson', 'great-great-nephew');
1519                        }
1520
1521                        return I18N::translateContext('(a woman’s) great-great-nephew', 'great-great-nephew');
1522                    }
1523
1524                    if ($sex2 === 'F') {
1525                        if ($first === 'bro' && $sex1 === 'M') {
1526                            return I18N::translateContext('(a man’s) brother’s great-granddaughter', 'great-great-niece');
1527                        }
1528
1529                        if ($first === 'sis' && $sex1 === 'M') {
1530                            return I18N::translateContext('(a man’s) sister’s great-granddaughter', 'great-great-niece');
1531                        }
1532
1533                        return I18N::translateContext('(a woman’s) great-great-niece', 'great-great-niece');
1534                    }
1535
1536                    if ($first === 'bro' && $sex1 === 'M') {
1537                        return I18N::translateContext('(a man’s) brother’s great-grandchild', 'great-great-nephew/niece');
1538                    }
1539
1540                    if ($first === 'sis' && $sex1 === 'M') {
1541                        return I18N::translateContext('(a man’s) sister’s great-grandchild', 'great-great-nephew/niece');
1542                    }
1543
1544                    return I18N::translateContext('(a woman’s) great-great-nephew/niece', 'great-great-nephew/niece');
1545
1546                case 5:
1547                    if ($sex2 === 'M') {
1548                        if ($first === 'bro' && $sex1 === 'M') {
1549                            return I18N::translateContext('(a man’s) brother’s great-great-grandson', 'great-great-great-nephew');
1550                        }
1551
1552                        if ($first === 'sis' && $sex1 === 'M') {
1553                            return I18N::translateContext('(a man’s) sister’s great-great-grandson', 'great-great-great-nephew');
1554                        }
1555
1556                        return I18N::translateContext('(a woman’s) great-great-great-nephew', 'great-great-great-nephew');
1557                    }
1558
1559                    if ($sex2 === 'F') {
1560                        if ($first === 'bro' && $sex1 === 'M') {
1561                            return I18N::translateContext('(a man’s) brother’s great-great-granddaughter', 'great-great-great-niece');
1562                        }
1563
1564                        if ($first === 'sis' && $sex1 === 'M') {
1565                            return I18N::translateContext('(a man’s) sister’s great-great-granddaughter', 'great-great-great-niece');
1566                        }
1567
1568                        return I18N::translateContext('(a woman’s) great-great-great-niece', 'great-great-great-niece');
1569                    }
1570
1571                    if ($first === 'bro' && $sex1 === 'M') {
1572                        return I18N::translateContext('(a man’s) brother’s great-great-grandchild', 'great-great-great-nephew/niece');
1573                    }
1574
1575                    if ($first === 'sis' && $sex1 === 'M') {
1576                        return I18N::translateContext('(a man’s) sister’s great-great-grandchild', 'great-great-great-nephew/niece');
1577                    }
1578
1579                    return I18N::translateContext('(a woman’s) great-great-great-nephew/niece', 'great-great-great-nephew/niece');
1580
1581                case 6:
1582                    if ($sex2 === 'M') {
1583                        if ($first === 'bro' && $sex1 === 'M') {
1584                            return I18N::translateContext('(a man’s) brother’s great-great-great-grandson', 'great ×4 nephew');
1585                        }
1586
1587                        if ($first === 'sis' && $sex1 === 'M') {
1588                            return I18N::translateContext('(a man’s) sister’s great-great-great-grandson', 'great ×4 nephew');
1589                        }
1590
1591                        return I18N::translateContext('(a woman’s) great ×4 nephew', 'great ×4 nephew');
1592                    }
1593
1594                    if ($sex2 === 'F') {
1595                        if ($first === 'bro' && $sex1 === 'M') {
1596                            return I18N::translateContext('(a man’s) brother’s great-great-great-granddaughter', 'great ×4 niece');
1597                        }
1598
1599                        if ($first === 'sis' && $sex1 === 'M') {
1600                            return I18N::translateContext('(a man’s) sister’s great-great-great-granddaughter', 'great ×4 niece');
1601                        }
1602
1603                        return I18N::translateContext('(a woman’s) great ×4 niece', 'great ×4 niece');
1604                    }
1605
1606                    if ($first === 'bro' && $sex1 === 'M') {
1607                        return I18N::translateContext('(a man’s) brother’s great-great-great-grandchild', 'great ×4 nephew/niece');
1608                    }
1609
1610                    if ($first === 'sis' && $sex1 === 'M') {
1611                        return I18N::translateContext('(a man’s) sister’s great-great-great-grandchild', 'great ×4 nephew/niece');
1612                    }
1613
1614                    return I18N::translateContext('(a woman’s) great ×4 nephew/niece', 'great ×4 nephew/niece');
1615
1616                case 7:
1617                    if ($sex2 === 'M') {
1618                        if ($first === 'bro' && $sex1 === 'M') {
1619                            return I18N::translateContext('(a man’s) brother’s great ×4 grandson', 'great ×5 nephew');
1620                        }
1621
1622                        if ($first === 'sis' && $sex1 === 'M') {
1623                            return I18N::translateContext('(a man’s) sister’s great ×4 grandson', 'great ×5 nephew');
1624                        }
1625
1626                        return I18N::translateContext('(a woman’s) great ×5 nephew', 'great ×5 nephew');
1627                    }
1628
1629                    if ($sex2 === 'F') {
1630                        if ($first === 'bro' && $sex1 === 'M') {
1631                            return I18N::translateContext('(a man’s) brother’s great ×4 granddaughter', 'great ×5 niece');
1632                        }
1633
1634                        if ($first === 'sis' && $sex1 === 'M') {
1635                            return I18N::translateContext('(a man’s) sister’s great ×4 granddaughter', 'great ×5 niece');
1636                        }
1637
1638                        return I18N::translateContext('(a woman’s) great ×5 niece', 'great ×5 niece');
1639                    }
1640
1641                    if ($first === 'bro' && $sex1 === 'M') {
1642                        return I18N::translateContext('(a man’s) brother’s great ×4 grandchild', 'great ×5 nephew/niece');
1643                    }
1644
1645                    if ($first === 'sis' && $sex1 === 'M') {
1646                        return I18N::translateContext('(a man’s) sister’s great ×4 grandchild', 'great ×5 nephew/niece');
1647                    }
1648
1649                    return I18N::translateContext('(a woman’s) great ×5 nephew/niece', 'great ×5 nephew/niece');
1650
1651                default:
1652                    // Different languages have different rules for naming generations.
1653                    // An English great ×12 nephew is a Polish great ×11 nephew.
1654                    //
1655                    // Need to find out which languages use which rules.
1656                    switch (I18N::languageTag()) {
1657                        case 'pl': // Source: Lukasz Wilenski
1658                            if ($sex2 === 'M') {
1659                                if ($first === 'bro' && $sex1 === 'M') {
1660                                    return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 3));
1661                                }
1662
1663                                if ($first === 'sis' && $sex1 === 'M') {
1664                                    return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 3));
1665                                }
1666
1667                                return I18N::translateContext('(a woman’s) great ×%s nephew', 'great ×%s nephew', I18N::number($down - 3));
1668                            }
1669
1670                            if ($sex2 === 'F') {
1671                                if ($first === 'bro' && $sex1 === 'M') {
1672                                    return I18N::translateContext('(a man’s) brother’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 3));
1673                                }
1674
1675                                if ($first === 'sis' && $sex1 === 'M') {
1676                                    return I18N::translateContext('(a man’s) sister’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 3));
1677                                }
1678
1679                                return I18N::translateContext('(a woman’s) great ×%s niece', 'great ×%s niece', I18N::number($down - 3));
1680                            }
1681
1682                            if ($first === 'bro' && $sex1 === 'M') {
1683                                return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 3));
1684                            }
1685
1686                            if ($first === 'sis' && $sex1 === 'M') {
1687                                return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 3));
1688                            }
1689
1690                            return I18N::translateContext('(a woman’s) great ×%s nephew/niece', 'great ×%s nephew/niece', I18N::number($down - 3));
1691
1692                        case 'zh-Hans': // Source: xmlf
1693                        case 'zh-Hant':
1694                            if ($sex2 === 'M') {
1695                                if ($first === 'bro' && $sex1 === 'M') {
1696                                    return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 1));
1697                                }
1698                                if ($first === 'sis' && $sex1 === 'M') {
1699                                    return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandson', 'great ×%s nephew', I18N::number($down - 1));
1700                                }
1701
1702                                return I18N::translateContext('(a woman’s) great ×%s nephew', 'great ×%s nephew', I18N::number($down - 1));
1703                            }
1704                            if ($sex2 === 'F') {
1705                                if ($first === 'bro' && $sex1 === 'M') {
1706                                    return I18N::translateContext('(a man’s) brother’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 1));
1707                                }
1708                                if ($first === 'sis' && $sex1 === 'M') {
1709                                    return I18N::translateContext('(a man’s) sister’s great ×(%s-1) granddaughter', 'great ×%s niece', I18N::number($down - 1));
1710                                }
1711
1712                                return I18N::translateContext('(a woman’s) great ×%s niece', 'great ×%s niece', I18N::number($down - 1));
1713                            }
1714                            if ($first === 'bro' && $sex1 === 'M') {
1715                                return I18N::translateContext('(a man’s) brother’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 1));
1716                            }
1717                            if ($first === 'sis' && $sex1 === 'M') {
1718                                return I18N::translateContext('(a man’s) sister’s great ×(%s-1) grandchild', 'great ×%s nephew/niece', I18N::number($down - 1));
1719                            }
1720
1721                            return I18N::translateContext('(a woman’s) great ×%s nephew/niece', 'great ×%s nephew/niece', I18N::number($down - 1));
1722
1723                        case 'he': // Source: Meliza Amity
1724                            if ($sex2 === 'M') {
1725                                return I18N::translate('great ×%s nephew', I18N::number($down - 1));
1726                            }
1727
1728                            if ($sex2 === 'F') {
1729                                return I18N::translate('great ×%s niece', I18N::number($down - 1));
1730                            }
1731
1732                            return I18N::translate('great ×%s nephew/niece', I18N::number($down - 1));
1733
1734                        case 'hi': // Source: MrQD.
1735                            if ($sex2 === 'M') {
1736                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1737                                return I18N::translate('great ×%s nephew', I18N::number($down - 3));
1738                            }
1739
1740                            if ($sex2 === 'F') {
1741                                return I18N::translate('great ×%s niece', I18N::number($down - 3));
1742                            }
1743
1744                            return I18N::translate('great ×%s nephew/niece', I18N::number($down - 3));
1745
1746                        case 'it': // Source: Michele Locati.
1747                        case 'en_AU':
1748                        case 'en_GB':
1749                        case 'en_US':
1750                        default:
1751                            if ($sex2 === 'M') {
1752                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1753                                return I18N::translate('great ×%s nephew', I18N::number($down - 2));
1754                            }
1755
1756                            if ($sex2 === 'F') {
1757                                return I18N::translate('great ×%s niece', I18N::number($down - 2));
1758                            }
1759
1760                            return I18N::translate('great ×%s nephew/niece', I18N::number($down - 2));
1761                    }
1762            }
1763        }
1764        if (preg_match('/^((?:mot|fat|par)*)$/', $path, $match)) {
1765            // direct ancestors
1766            $up = intdiv(strlen($match[1]), 3);
1767            switch ($up) {
1768                case 4:
1769                    if ($sex2 === 'M') {
1770                        return I18N::translate('great-great-grandfather');
1771                    }
1772
1773                    if ($sex2 === 'F') {
1774                        return I18N::translate('great-great-grandmother');
1775                    }
1776
1777                    return I18N::translate('great-great-grandparent');
1778
1779                case 5:
1780                    if ($sex2 === 'M') {
1781                        return I18N::translate('great-great-great-grandfather');
1782                    }
1783
1784                    if ($sex2 === 'F') {
1785                        return I18N::translate('great-great-great-grandmother');
1786                    }
1787
1788                    return I18N::translate('great-great-great-grandparent');
1789
1790                case 6:
1791                    if ($sex2 === 'M') {
1792                        return I18N::translate('great ×4 grandfather');
1793                    }
1794
1795                    if ($sex2 === 'F') {
1796                        return I18N::translate('great ×4 grandmother');
1797                    }
1798
1799                    return I18N::translate('great ×4 grandparent');
1800
1801                case 7:
1802                    if ($sex2 === 'M') {
1803                        return I18N::translate('great ×5 grandfather');
1804                    }
1805
1806                    if ($sex2 === 'F') {
1807                        return I18N::translate('great ×5 grandmother');
1808                    }
1809
1810                    return I18N::translate('great ×5 grandparent');
1811
1812                case 8:
1813                    if ($sex2 === 'M') {
1814                        return I18N::translate('great ×6 grandfather');
1815                    }
1816
1817                    if ($sex2 === 'F') {
1818                        return I18N::translate('great ×6 grandmother');
1819                    }
1820
1821                    return I18N::translate('great ×6 grandparent');
1822
1823                case 9:
1824                    if ($sex2 === 'M') {
1825                        return I18N::translate('great ×7 grandfather');
1826                    }
1827
1828                    if ($sex2 === 'F') {
1829                        return I18N::translate('great ×7 grandmother');
1830                    }
1831
1832                    return I18N::translate('great ×7 grandparent');
1833
1834                default:
1835                    // Different languages have different rules for naming generations.
1836                    // An English great ×12 grandfather is a Danish great ×11 grandfather.
1837                    //
1838                    // Need to find out which languages use which rules.
1839                    switch (I18N::languageTag()) {
1840                        case 'da': // Source: Patrick Sorensen
1841                            if ($sex2 === 'M') {
1842                                return I18N::translate('great ×%s grandfather', I18N::number($up - 3));
1843                            }
1844
1845                            if ($sex2 === 'F') {
1846                                return I18N::translate('great ×%s grandmother', I18N::number($up - 3));
1847                            }
1848
1849                            return I18N::translate('great ×%s grandparent', I18N::number($up - 3));
1850
1851                        case 'it': // Source: Michele Locati
1852                        case 'zh-Hans': // Source: xmlf
1853                        case 'zh-Hant':
1854                        case 'es': // Source: Wes Groleau
1855                            if ($sex2 === 'M') {
1856                                return I18N::translate('great ×%s grandfather', I18N::number($up));
1857                            }
1858
1859                            if ($sex2 === 'F') {
1860                                return I18N::translate('great ×%s grandmother', I18N::number($up));
1861                            }
1862
1863                            return I18N::translate('great ×%s grandparent', I18N::number($up));
1864
1865                        case 'fr': // Source: Jacqueline Tetreault
1866                        case 'fr_CA':
1867                            if ($sex2 === 'M') {
1868                                return I18N::translate('great ×%s grandfather', I18N::number($up - 1));
1869                            }
1870
1871                            if ($sex2 === 'F') {
1872                                return I18N::translate('great ×%s grandmother', I18N::number($up - 1));
1873                            }
1874
1875                            return I18N::translate('great ×%s grandparent', I18N::number($up - 1));
1876
1877                        case 'nn': // Source: Hogne Røed Nilsen (https://bugs.launchpad.net/webtrees/+bug/1168553)
1878                        case 'nb':
1879                            if ($sex2 === 'M') {
1880                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1881                                return I18N::translate('great ×%s grandfather', I18N::number($up - 3));
1882                            }
1883
1884                            if ($sex2 === 'F') {
1885                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1886                                return I18N::translate('great ×%s grandmother', I18N::number($up - 3));
1887                            }
1888
1889                            // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1890                            return I18N::translate('great ×%s grandparent', I18N::number($up - 3));
1891                        case 'en_AU':
1892                        case 'en_GB':
1893                        case 'en_US':
1894                        default:
1895                            if ($sex2 === 'M') {
1896                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1897                                return I18N::translate('great ×%s grandfather', I18N::number($up - 2));
1898                            }
1899
1900                            if ($sex2 === 'F') {
1901                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1902                                return I18N::translate('great ×%s grandmother', I18N::number($up - 2));
1903                            }
1904
1905                            // I18N: if you need a different number for %s, contact the developers, as a code-change is required
1906                            return I18N::translate('great ×%s grandparent', I18N::number($up - 2));
1907                    }
1908            }
1909        }
1910        if (preg_match('/^((?:son|dau|chi)*)$/', $path, $match)) {
1911            // direct descendants
1912            $up = intdiv(strlen($match[1]), 3);
1913            switch ($up) {
1914                case 4:
1915                    if ($sex2 === 'M') {
1916                        return I18N::translate('great-great-grandson');
1917                    }
1918
1919                    if ($sex2 === 'F') {
1920                        return I18N::translate('great-great-granddaughter');
1921                    }
1922
1923                    return I18N::translate('great-great-grandchild');
1924
1925                case 5:
1926                    if ($sex2 === 'M') {
1927                        return I18N::translate('great-great-great-grandson');
1928                    }
1929
1930                    if ($sex2 === 'F') {
1931                        return I18N::translate('great-great-great-granddaughter');
1932                    }
1933
1934                    return I18N::translate('great-great-great-grandchild');
1935
1936                case 6:
1937                    if ($sex2 === 'M') {
1938                        return I18N::translate('great ×4 grandson');
1939                    }
1940
1941                    if ($sex2 === 'F') {
1942                        return I18N::translate('great ×4 granddaughter');
1943                    }
1944
1945                    return I18N::translate('great ×4 grandchild');
1946
1947                case 7:
1948                    if ($sex2 === 'M') {
1949                        return I18N::translate('great ×5 grandson');
1950                    }
1951
1952                    if ($sex2 === 'F') {
1953                        return I18N::translate('great ×5 granddaughter');
1954                    }
1955
1956                    return I18N::translate('great ×5 grandchild');
1957
1958                case 8:
1959                    if ($sex2 === 'M') {
1960                        return I18N::translate('great ×6 grandson');
1961                    }
1962
1963                    if ($sex2 === 'F') {
1964                        return I18N::translate('great ×6 granddaughter');
1965                    }
1966
1967                    return I18N::translate('great ×6 grandchild');
1968
1969                case 9:
1970                    if ($sex2 === 'M') {
1971                        return I18N::translate('great ×7 grandson');
1972                    }
1973
1974                    if ($sex2 === 'F') {
1975                        return I18N::translate('great ×7 granddaughter');
1976                    }
1977
1978                    return I18N::translate('great ×7 grandchild');
1979
1980                default:
1981                    // Different languages have different rules for naming generations.
1982                    // An English great ×12 grandson is a Danish great ×11 grandson.
1983                    //
1984                    // Need to find out which languages use which rules.
1985                    switch (I18N::languageTag()) {
1986                        case 'nn': // Source: Hogne Røed Nilsen
1987                        case 'nb':
1988                        case 'da': // Source: Patrick Sorensen
1989                            if ($sex2 === 'M') {
1990                                return I18N::translate('great ×%s grandson', I18N::number($up - 3));
1991                            }
1992
1993                            if ($sex2 === 'F') {
1994                                return I18N::translate('great ×%s granddaughter', I18N::number($up - 3));
1995                            }
1996
1997                            return I18N::translate('great ×%s grandchild', I18N::number($up - 3));
1998
1999                        case 'zh-Hans': // Source: xmlf
2000                        case 'zh-Hant':
2001                            if ($sex2 === 'M') {
2002                                return I18N::translate('great ×%s grandson', I18N::number($up));
2003                            }
2004                            if ($sex2 === 'F') {
2005                                return I18N::translate('great ×%s granddaughter', I18N::number($up));
2006                            }
2007
2008                            return I18N::translate('great ×%s grandchild', I18N::number($up));
2009
2010                        case 'it':
2011                            // Source: Michele Locati
2012                        case 'es':
2013                            // Source: Wes Groleau (adding doesn’t change behavior, but needs to be better researched)
2014                        case 'en_AU':
2015                        case 'en_GB':
2016                        case 'en_US':
2017                        default:
2018                            if ($sex2 === 'M') {
2019                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
2020                                return I18N::translate('great ×%s grandson', I18N::number($up - 2));
2021                            }
2022
2023                            if ($sex2 === 'F') {
2024                                // I18N: if you need a different number for %s, contact the developers, as a code-change is required
2025                                return I18N::translate('great ×%s granddaughter', I18N::number($up - 2));
2026                            }
2027
2028                            // I18N: if you need a different number for %s, contact the developers, as a code-change is required
2029                            return I18N::translate('great ×%s grandchild', I18N::number($up - 2));
2030                    }
2031            }
2032        }
2033        if (preg_match('/^((?:mot|fat|par)+)(?:bro|sis|sib)((?:son|dau|chi)+)$/', $path, $match)) {
2034            // cousins in English
2035            $ascent  = $match[1];
2036            $descent = $match[2];
2037            $up      = intdiv(strlen($ascent), 3);
2038            $down    = intdiv(strlen($descent), 3);
2039            $cousin  = min($up, $down); // Moved out of switch (en/default case) so that
2040            $removed = abs($down - $up); // Spanish (and other languages) can use it, too.
2041
2042            // Different languages have different rules for naming cousins. For example,
2043            // an English “second cousin once removed” is a Polish “cousin of 7th degree”.
2044            //
2045            // Need to find out which languages use which rules.
2046            switch (I18N::languageTag()) {
2047                case 'pl': // Source: Lukasz Wilenski
2048                    return self::legacyCousinName($up + $down + 2, $sex2);
2049                case 'it':
2050                    // Source: Michele Locati. See italian_cousins_names.zip
2051                    // https://webtrees.net/forums/8-translation/1200-great-xn-grandparent?limit=6&start=6
2052                    return self::legacyCousinName($up + $down - 3, $sex2);
2053                case 'es':
2054                    if ($down === $up) {
2055                        return self::legacyCousinName($cousin, $sex2);
2056                    }
2057
2058                    if ($down < $up) {
2059                        return self::legacyCousinName2($cousin + 1, $sex2, $this->legacyNameAlgorithm('sib' . $descent));
2060                    }
2061
2062                    if ($sex2 === 'M') {
2063                        return self::legacyCousinName2($cousin + 1, $sex2, $this->legacyNameAlgorithm('bro' . $descent));
2064                    }
2065
2066                    if ($sex2 === 'F') {
2067                        return self::legacyCousinName2($cousin + 1, $sex2, $this->legacyNameAlgorithm('sis' . $descent));
2068                    }
2069
2070                    return self::legacyCousinName2($cousin + 1, $sex2, $this->legacyNameAlgorithm('sib' . $descent));
2071
2072                case 'en_AU': // See: https://en.wikipedia.org/wiki/File:CousinTree.svg
2073                case 'en_GB':
2074                case 'en_US':
2075                default:
2076                    switch ($removed) {
2077                        case 0:
2078                            return self::legacyCousinName($cousin, $sex2);
2079                        case 1:
2080                            if ($up > $down) {
2081                                /* I18N: %s=“fifth cousin”, etc. */
2082                                return I18N::translate('%s once removed ascending', self::legacyCousinName($cousin, $sex2));
2083                            }
2084
2085                            /* I18N: %s=“fifth cousin”, etc. */
2086
2087                            return I18N::translate('%s once removed descending', self::legacyCousinName($cousin, $sex2));
2088                        case 2:
2089                            if ($up > $down) {
2090                                /* I18N: %s=“fifth cousin”, etc. */
2091                                return I18N::translate('%s twice removed ascending', self::legacyCousinName($cousin, $sex2));
2092                            }
2093
2094                            /* I18N: %s=“fifth cousin”, etc. */
2095
2096                            return I18N::translate('%s twice removed descending', self::legacyCousinName($cousin, $sex2));
2097                        case 3:
2098                            if ($up > $down) {
2099                                /* I18N: %s=“fifth cousin”, etc. */
2100                                return I18N::translate('%s three times removed ascending', self::legacyCousinName($cousin, $sex2));
2101                            }
2102
2103                            /* I18N: %s=“fifth cousin”, etc. */
2104
2105                            return I18N::translate('%s three times removed descending', self::legacyCousinName($cousin, $sex2));
2106                        default:
2107                            if ($up > $down) {
2108                                /* I18N: %1$s=“fifth cousin”, etc., %2$s>=4 */
2109                                return I18N::translate('%1$s %2$s times removed ascending', self::legacyCousinName($cousin, $sex2), I18N::number($removed));
2110                            }
2111
2112                            /* I18N: %1$s=“fifth cousin”, etc., %2$s>=4 */
2113
2114                            return I18N::translate('%1$s %2$s times removed descending', self::legacyCousinName($cousin, $sex2), I18N::number($removed));
2115                    }
2116            }
2117        }
2118
2119        // Split the relationship into sub-relationships, e.g., third-cousin’s great-uncle.
2120        // Try splitting at every point, and choose the path with the shorted translated name.
2121        // But before starting to recursively go through all combinations, do a cache look-up
2122
2123        static $relationshipsCache;
2124        $relationshipsCache ??= [];
2125        if (array_key_exists($path, $relationshipsCache)) {
2126            return $relationshipsCache[$path];
2127        }
2128
2129        $relationship = '';
2130        $path1        = substr($path, 0, 3);
2131        $path2        = substr($path, 3);
2132        while ($path2 !== '') {
2133            // I18N: A complex relationship, such as “third-cousin’s great-uncle”
2134            $tmp = I18N::translate(
2135                '%1$s’s %2$s',
2136                $this->legacyNameAlgorithm($path1),
2137                $this->legacyNameAlgorithm($path2)
2138            );
2139            if ($relationship === '' || strlen($tmp) < strlen($relationship)) {
2140                $relationship = $tmp;
2141            }
2142            $path1 .= substr($path2, 0, 3);
2143            $path2 = substr($path2, 3);
2144        }
2145        // and store the result in the cache
2146        $relationshipsCache[$path] = $relationship;
2147
2148        return $relationship;
2149    }
2150
2151    /**
2152     * Calculate the name of a cousin.
2153     *
2154     * @param int    $n
2155     * @param string $sex
2156     *
2157     * @return string
2158     *
2159     * @deprecated
2160     */
2161    private static function legacyCousinName(int $n, string $sex): string
2162    {
2163        if ($sex === 'M') {
2164            switch ($n) {
2165                case 1:
2166                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2167                    return I18N::translateContext('MALE', 'first cousin');
2168                case 2:
2169                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2170                    return I18N::translateContext('MALE', 'second cousin');
2171                case 3:
2172                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2173                    return I18N::translateContext('MALE', 'third cousin');
2174                case 4:
2175                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2176                    return I18N::translateContext('MALE', 'fourth cousin');
2177                case 5:
2178                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2179                    return I18N::translateContext('MALE', 'fifth cousin');
2180                case 6:
2181                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2182                    return I18N::translateContext('MALE', 'sixth cousin');
2183                case 7:
2184                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2185                    return I18N::translateContext('MALE', 'seventh cousin');
2186                case 8:
2187                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2188                    return I18N::translateContext('MALE', 'eighth cousin');
2189                case 9:
2190                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2191                    return I18N::translateContext('MALE', 'ninth cousin');
2192                case 10:
2193                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2194                    return I18N::translateContext('MALE', 'tenth cousin');
2195                case 11:
2196                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2197                    return I18N::translateContext('MALE', 'eleventh cousin');
2198                case 12:
2199                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2200                    return I18N::translateContext('MALE', 'twelfth cousin');
2201                case 13:
2202                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2203                    return I18N::translateContext('MALE', 'thirteenth cousin');
2204                case 14:
2205                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2206                    return I18N::translateContext('MALE', 'fourteenth cousin');
2207                case 15:
2208                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2209                    return I18N::translateContext('MALE', 'fifteenth cousin');
2210                default:
2211                    /* I18N: Note that for Italian and Polish, “N’th cousins” are different from English “N’th cousins”, and the software has already generated the correct “N” for your language. You only need to translate - you do not need to convert. For other languages, if your cousin rules are different from English, please contact the developers. */
2212                    return I18N::translateContext('MALE', '%s × cousin', I18N::number($n));
2213            }
2214        }
2215
2216        if ($sex === 'F') {
2217            switch ($n) {
2218                case 1:
2219                    return I18N::translateContext('FEMALE', 'first cousin');
2220                case 2:
2221                    return I18N::translateContext('FEMALE', 'second cousin');
2222                case 3:
2223                    return I18N::translateContext('FEMALE', 'third cousin');
2224                case 4:
2225                    return I18N::translateContext('FEMALE', 'fourth cousin');
2226                case 5:
2227                    return I18N::translateContext('FEMALE', 'fifth cousin');
2228                case 6:
2229                    return I18N::translateContext('FEMALE', 'sixth cousin');
2230                case 7:
2231                    return I18N::translateContext('FEMALE', 'seventh cousin');
2232                case 8:
2233                    return I18N::translateContext('FEMALE', 'eighth cousin');
2234                case 9:
2235                    return I18N::translateContext('FEMALE', 'ninth cousin');
2236                case 10:
2237                    return I18N::translateContext('FEMALE', 'tenth cousin');
2238                case 11:
2239                    return I18N::translateContext('FEMALE', 'eleventh cousin');
2240                case 12:
2241                    return I18N::translateContext('FEMALE', 'twelfth cousin');
2242                case 13:
2243                    return I18N::translateContext('FEMALE', 'thirteenth cousin');
2244                case 14:
2245                    return I18N::translateContext('FEMALE', 'fourteenth cousin');
2246                case 15:
2247                    return I18N::translateContext('FEMALE', 'fifteenth cousin');
2248                default:
2249                    return I18N::translateContext('FEMALE', '%s × cousin', I18N::number($n));
2250            }
2251        }
2252
2253        switch ($n) {
2254            case 1:
2255                return I18N::translate('first cousin');
2256            case 2:
2257                return I18N::translate('second cousin');
2258            case 3:
2259                return I18N::translate('third cousin');
2260            case 4:
2261                return I18N::translate('fourth cousin');
2262            case 5:
2263                return I18N::translate('fifth cousin');
2264            case 6:
2265                return I18N::translate('sixth cousin');
2266            case 7:
2267                return I18N::translate('seventh cousin');
2268            case 8:
2269                return I18N::translate('eighth cousin');
2270            case 9:
2271                return I18N::translate('ninth cousin');
2272            case 10:
2273                return I18N::translate('tenth cousin');
2274            case 11:
2275                return I18N::translate('eleventh cousin');
2276            case 12:
2277                return I18N::translate('twelfth cousin');
2278            case 13:
2279                return I18N::translate('thirteenth cousin');
2280            case 14:
2281                return I18N::translate('fourteenth cousin');
2282            case 15:
2283                return I18N::translate('fifteenth cousin');
2284            default:
2285                return I18N::translate('%s × cousin', I18N::number($n));
2286        }
2287    }
2288
2289    /**
2290     * A variation on cousin_name(), for constructs such as “sixth great-nephew”
2291     * Currently used only by Spanish relationship names.
2292     *
2293     * @param int    $n
2294     * @param string $sex
2295     * @param string $relation
2296     *
2297     * @return string
2298     *
2299     * @deprecated
2300     */
2301    private static function legacyCousinName2(int $n, string $sex, string $relation): string
2302    {
2303        if ($sex === 'M') {
2304            switch ($n) {
2305                case 1:
2306                    /* I18N: A Spanish relationship name, such as third great-nephew */
2307                    return I18N::translateContext('MALE', 'first %s', $relation);
2308                case 2:
2309                    /* I18N: A Spanish relationship name, such as third great-nephew */
2310                    return I18N::translateContext('MALE', 'second %s', $relation);
2311                case 3:
2312                    /* I18N: A Spanish relationship name, such as third great-nephew */
2313                    return I18N::translateContext('MALE', 'third %s', $relation);
2314                case 4:
2315                    /* I18N: A Spanish relationship name, such as third great-nephew */
2316                    return I18N::translateContext('MALE', 'fourth %s', $relation);
2317                case 5:
2318                    /* I18N: A Spanish relationship name, such as third great-nephew */
2319                    return I18N::translateContext('MALE', 'fifth %s', $relation);
2320                default:
2321                    /* I18N: A Spanish relationship name, such as third great-nephew */
2322                    return I18N::translateContext('MALE', '%1$s × %2$s', I18N::number($n), $relation);
2323            }
2324        }
2325
2326        if ($sex === 'F') {
2327            switch ($n) {
2328                case 1:
2329                    /* I18N: A Spanish relationship name, such as third great-nephew */
2330                    return I18N::translateContext('FEMALE', 'first %s', $relation);
2331                case 2:
2332                    /* I18N: A Spanish relationship name, such as third great-nephew */
2333                    return I18N::translateContext('FEMALE', 'second %s', $relation);
2334                case 3:
2335                    /* I18N: A Spanish relationship name, such as third great-nephew */
2336                    return I18N::translateContext('FEMALE', 'third %s', $relation);
2337                case 4:
2338                    /* I18N: A Spanish relationship name, such as third great-nephew */
2339                    return I18N::translateContext('FEMALE', 'fourth %s', $relation);
2340                case 5:
2341                    /* I18N: A Spanish relationship name, such as third great-nephew */
2342                    return I18N::translateContext('FEMALE', 'fifth %s', $relation);
2343                default:
2344                    /* I18N: A Spanish relationship name, such as third great-nephew */
2345                    return I18N::translateContext('FEMALE', '%1$s × %2$s', I18N::number($n), $relation);
2346            }
2347        }
2348
2349        switch ($n) {
2350            case 1:
2351                /* I18N: A Spanish relationship name, such as third great-nephew */
2352                return I18N::translate('first %s', $relation);
2353            case 2:
2354                /* I18N: A Spanish relationship name, such as third great-nephew */
2355                return I18N::translate('second %s', $relation);
2356            case 3:
2357                /* I18N: A Spanish relationship name, such as third great-nephew */
2358                return I18N::translate('third %s', $relation);
2359            case 4:
2360                /* I18N: A Spanish relationship name, such as third great-nephew */
2361                return I18N::translate('fourth %s', $relation);
2362            case 5:
2363                /* I18N: A Spanish relationship name, such as third great-nephew */
2364                return I18N::translate('fifth %s', $relation);
2365            default:
2366                /* I18N: A Spanish relationship name, such as third great-nephew */
2367                return I18N::translate('%1$s × %2$s', I18N::number($n), $relation);
2368        }
2369    }
2370}
2371