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