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