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