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