Completed
Push — develop ( 2ca406...b33d79 )
by Greg
27:09 queued 11:25
created

RelationshipService::matchRelationships()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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