From e2468f6d3909b5ddd58762e79912c3abac97a652 Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Sun, 1 Nov 2026 10:40:10 +0100 Subject: [PATCH 1/5] kinship: use partner in english Signed-off-by: Arc676/Alessandro Vinciguerra --- frontend/src/utils/kinship/en.js | 4 ++-- frontend/src/utils/kinship/kinship.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/kinship/en.js b/frontend/src/utils/kinship/en.js index e684313..033d78d 100644 --- a/frontend/src/utils/kinship/en.js +++ b/frontend/src/utils/kinship/en.js @@ -6,8 +6,8 @@ const g = (sex, male, female, neutral) => (sex === 'male' ? male : sex === 'fema export default function nameEn(path) { const { ups, downs, hasSpouse, sex } = kinshipFacts(path) - // « Spouse » reste neutre : husband et wife supposeraient un mariage - if (hasSpouse && ups === 0 && downs === 0) return 'Spouse' + // « Partner » reste neutre au lieu de supposer un mariage + if (hasSpouse && ups === 0 && downs === 0) return 'Partner' if (hasSpouse) { // L'anglais distingue l'ordre : le parent du conjoint (in-law) n'est pas le conjoint du parent (step) diff --git a/frontend/src/utils/kinship/kinship.test.js b/frontend/src/utils/kinship/kinship.test.js index b73daa4..6cdaf85 100644 --- a/frontend/src/utils/kinship/kinship.test.js +++ b/frontend/src/utils/kinship/kinship.test.js @@ -65,7 +65,7 @@ describe('nommage anglais', () => { expect(bothEn(['spouse', 'down'])).toEqual(['Stepson', 'Stepdaughter', 'Stepchild']) expect(bothEn(['down', 'spouse'])).toEqual(['Son-in-law', 'Daughter-in-law', 'Child-in-law']) expect(bothEn(['spouse', 'sibling'])).toEqual(['Brother-in-law', 'Sister-in-law', 'Brother/sister-in-law']) - expect(bothEn(['spouse'])).toEqual(['Spouse', 'Spouse', 'Spouse']) + expect(bothEn(['spouse'])).toEqual(['Partner', 'Partner', 'Partner']) expect(bothEn(['up', 'sibling', 'down'])).toEqual(['First cousin', 'First cousin', 'First cousin']) }) From e0f7eb49139d15c4a53750c6f1b3348e00289da3 Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Sun, 1 Nov 2026 10:40:54 +0100 Subject: [PATCH 2/5] kinship: compute cousin type by degree and removal Signed-off-by: Arc676/Alessandro Vinciguerra --- frontend/src/utils/kinship/en.js | 32 +++++++++++++++++++--- frontend/src/utils/kinship/kinship.test.js | 5 ++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/frontend/src/utils/kinship/en.js b/frontend/src/utils/kinship/en.js index 033d78d..17d01db 100644 --- a/frontend/src/utils/kinship/en.js +++ b/frontend/src/utils/kinship/en.js @@ -3,6 +3,32 @@ import { kinshipFacts } from './facts.js' // Mot genré quand le sexe de la personne nommée est connu, forme neutre ou double sinon const g = (sex, male, female, neutral) => (sex === 'male' ? male : sex === 'female' ? female : neutral) +function ordinal(n) { + switch (n) { + case 1: + return "First" + case 2: + return "Second" + case 3: + return "Third" + default: + return `{n}th` + } +} + +function times(n) { + switch (n) { + case 1: + return "once" + case 2: + return "twice" + case 3: + return "thrice" + default: + return `${n} times` + } +} + export default function nameEn(path) { const { ups, downs, hasSpouse, sex } = kinshipFacts(path) @@ -36,10 +62,8 @@ export default function nameEn(path) { else if (ups >= 2 && downs >= 2) { const degree = Math.min(ups, downs) - 1 const removed = Math.abs(ups - downs) - if (degree === 1 && removed === 0) label = 'First cousin' - else if (degree === 1) label = 'First cousin once removed' - else if (removed === 0) label = `Cousin, degree ${degree}` - else label = 'Distant cousin' + if (removed === 0) label = `${ordinal(degree)} cousin` + else label = `${ordinal(degree)} cousin, ${times(removed)} removed` } else label = `${ups + downs} steps apart` return hasSpouse ? `${label} by marriage` : label diff --git a/frontend/src/utils/kinship/kinship.test.js b/frontend/src/utils/kinship/kinship.test.js index 6cdaf85..e093005 100644 --- a/frontend/src/utils/kinship/kinship.test.js +++ b/frontend/src/utils/kinship/kinship.test.js @@ -67,6 +67,11 @@ describe('nommage anglais', () => { expect(bothEn(['spouse', 'sibling'])).toEqual(['Brother-in-law', 'Sister-in-law', 'Brother/sister-in-law']) expect(bothEn(['spouse'])).toEqual(['Partner', 'Partner', 'Partner']) expect(bothEn(['up', 'sibling', 'down'])).toEqual(['First cousin', 'First cousin', 'First cousin']) + expect(bothEn(['up', 'up', 'sibling', 'down', 'down'])).toEqual(['Second cousin', 'Second cousin', 'Second cousin']) + expect(bothEn(['up', 'up', 'sibling', 'down'])).toEqual(['First cousin, once removed', 'First cousin, once removed', 'First cousin, once removed']) + expect(bothEn(['up', 'up', 'up', 'up', 'up', 'up', 'sibling', 'down', 'down', 'down'])).toEqual(['Third cousin, thrice removed', 'Third cousin, thrice removed', 'Third cousin, thrice removed']) + expect(bothEn(['up', 'up', 'up', 'sibling', 'down'])).toEqual(['First cousin, twice removed', 'First cousin, twice removed', 'First cousin, twice removed']) + expect(bothEn(['up', 'up', 'up', 'up', 'up', 'sibling', 'down'])).toEqual(['First cousin, 4 times removed', 'First cousin, 4 times removed', 'First cousin, 4 times removed']) }) it('une langue inconnue retombe sur l’anglais', () => { From 24e7ea1994454842ce4aec19fae2a3e9f40dc516 Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Sun, 1 Nov 2026 10:54:26 +0100 Subject: [PATCH 3/5] kinship: use nibling Signed-off-by: Arc676/Alessandro Vinciguerra --- frontend/src/utils/kinship/en.js | 4 ++-- frontend/src/utils/kinship/kinship.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/utils/kinship/en.js b/frontend/src/utils/kinship/en.js index 17d01db..148b27f 100644 --- a/frontend/src/utils/kinship/en.js +++ b/frontend/src/utils/kinship/en.js @@ -57,8 +57,8 @@ export default function nameEn(path) { } else if (ups === 1 && downs === 1) label = g(sex, 'Brother', 'Sister', 'Sibling') else if (ups === 2 && downs === 1) label = g(sex, 'Uncle', 'Aunt', 'Uncle/Aunt') else if (ups === 3 && downs === 1) label = g(sex, 'Great-uncle', 'Great-aunt', 'Great-uncle/aunt') - else if (ups === 1 && downs === 2) label = g(sex, 'Nephew', 'Niece', 'Nephew/Niece') - else if (ups === 1 && downs === 3) label = g(sex, 'Grandnephew', 'Grandniece', 'Grandnephew/niece') + else if (ups === 1 && downs === 2) label = g(sex, 'Nephew', 'Niece', 'Nibling') + else if (ups === 1 && downs === 3) label = g(sex, 'Grandnephew', 'Grandniece', 'Grandnibling') else if (ups >= 2 && downs >= 2) { const degree = Math.min(ups, downs) - 1 const removed = Math.abs(ups - downs) diff --git a/frontend/src/utils/kinship/kinship.test.js b/frontend/src/utils/kinship/kinship.test.js index e093005..f052bee 100644 --- a/frontend/src/utils/kinship/kinship.test.js +++ b/frontend/src/utils/kinship/kinship.test.js @@ -59,7 +59,7 @@ describe('nommage anglais', () => { expect(bothEn(['down', 'down'])).toEqual(['Grandson', 'Granddaughter', 'Grandchild']) expect(bothEn(['sibling'])).toEqual(['Brother', 'Sister', 'Sibling']) expect(bothEn(['up', 'sibling'])).toEqual(['Uncle', 'Aunt', 'Uncle/Aunt']) - expect(bothEn(['sibling', 'down'])).toEqual(['Nephew', 'Niece', 'Nephew/Niece']) + expect(bothEn(['sibling', 'down'])).toEqual(['Nephew', 'Niece', 'Nibling']) expect(bothEn(['spouse', 'up'])).toEqual(['Father-in-law', 'Mother-in-law', 'Parent-in-law']) expect(bothEn(['up', 'spouse'])).toEqual(['Stepfather', 'Stepmother', 'Stepparent']) expect(bothEn(['spouse', 'down'])).toEqual(['Stepson', 'Stepdaughter', 'Stepchild']) From 568db606da300f6820e4b3543084f19a98ff6db6 Mon Sep 17 00:00:00 2001 From: Arc676/Alessandro Vinciguerra Date: Sun, 1 Nov 2026 11:53:33 +0100 Subject: [PATCH 4/5] kinship: implement g-rule Allow arbitrary depth Signed-off-by: Arc676/Alessandro Vinciguerra --- frontend/src/utils/kinship/en.js | 61 +++++++++++++++++----- frontend/src/utils/kinship/kinship.test.js | 2 + 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/frontend/src/utils/kinship/en.js b/frontend/src/utils/kinship/en.js index 148b27f..a069651 100644 --- a/frontend/src/utils/kinship/en.js +++ b/frontend/src/utils/kinship/en.js @@ -29,6 +29,15 @@ function times(n) { } } +function grule(n) { + switch (n - 1) { + case 1: + return "Grand" + default: + return `Great-${"great-".repeat(n - 3)}grand` + } +} + export default function nameEn(path) { const { ups, downs, hasSpouse, sex } = kinshipFacts(path) @@ -45,21 +54,45 @@ export default function nameEn(path) { let label if (downs === 0 && ups > 0) { - if (ups === 1) label = g(sex, 'Father', 'Mother', 'Parent') - else if (ups === 2) label = g(sex, 'Grandfather', 'Grandmother', 'Grandparent') - else if (ups === 3) label = g(sex, 'Great-grandfather', 'Great-grandmother', 'Great-grandparent') - else label = `Ancestor, ${ups} generations up` + switch (ups) { + case 1: + label = g(sex, 'Father', 'Mother', 'Parent') + break; + default: + label = `${grule(ups)}${g(sex, 'father', 'mother', 'parent')}` + break; + } } else if (ups === 0 && downs > 0) { - if (downs === 1) label = g(sex, 'Son', 'Daughter', 'Child') - else if (downs === 2) label = g(sex, 'Grandson', 'Granddaughter', 'Grandchild') - else if (downs === 3) label = g(sex, 'Great-grandson', 'Great-granddaughter', 'Great-grandchild') - else label = `Descendant, ${downs} generations down` - } else if (ups === 1 && downs === 1) label = g(sex, 'Brother', 'Sister', 'Sibling') - else if (ups === 2 && downs === 1) label = g(sex, 'Uncle', 'Aunt', 'Uncle/Aunt') - else if (ups === 3 && downs === 1) label = g(sex, 'Great-uncle', 'Great-aunt', 'Great-uncle/aunt') - else if (ups === 1 && downs === 2) label = g(sex, 'Nephew', 'Niece', 'Nibling') - else if (ups === 1 && downs === 3) label = g(sex, 'Grandnephew', 'Grandniece', 'Grandnibling') - else if (ups >= 2 && downs >= 2) { + switch (downs) { + case 1: + label = g(sex, 'Son', 'Daughter', 'Child') + break; + default: + label = `${grule(downs)}${g(sex, 'son', 'daughter', 'child')}` + break; + } + } else if (downs == 1) { + switch (ups) { + case 1: + label = g(sex, 'Brother', 'Sister', 'Sibling') + break; + case 2: + label = g(sex, 'Uncle', 'Aunt', 'Uncle/Aunt') + break; + default: + label = `${grule(ups - 1)}${g(sex, 'uncle', 'aunt', 'uncle/aunt')}` + break; + } + } else if (ups === 1) { + switch (downs) { + case 2: + label = g(sex, 'Nephew', 'Niece', 'Nibling') + break; + default: + label = `${grule(downs - 1)}${g(sex, 'nephew', 'niece', 'nibling')}` + break; + } + } else if (ups >= 2 && downs >= 2) { const degree = Math.min(ups, downs) - 1 const removed = Math.abs(ups - downs) if (removed === 0) label = `${ordinal(degree)} cousin` diff --git a/frontend/src/utils/kinship/kinship.test.js b/frontend/src/utils/kinship/kinship.test.js index f052bee..69ad459 100644 --- a/frontend/src/utils/kinship/kinship.test.js +++ b/frontend/src/utils/kinship/kinship.test.js @@ -56,6 +56,8 @@ describe('nommage français', () => { describe('nommage anglais', () => { it('genre le mot quand le sexe est connu', () => { expect(bothEn(['up'])).toEqual(['Father', 'Mother', 'Parent']) + expect(bothEn(['up', 'up'])).toEqual(['Grandfather', 'Grandmother', 'Grandparent']) + expect(bothEn(['up', 'up', 'up', 'up'])).toEqual(['Great-great-grandfather', 'Great-great-grandmother', 'Great-great-grandparent']) expect(bothEn(['down', 'down'])).toEqual(['Grandson', 'Granddaughter', 'Grandchild']) expect(bothEn(['sibling'])).toEqual(['Brother', 'Sister', 'Sibling']) expect(bothEn(['up', 'sibling'])).toEqual(['Uncle', 'Aunt', 'Uncle/Aunt']) From 7fee10478d3c306d8aacd76e5f50039a1878843e Mon Sep 17 00:00:00 2001 From: IliesAllali Date: Sun, 1 Nov 2026 20:41:25 +0100 Subject: [PATCH 5/5] =?UTF-8?q?kinship=20en=20:=204th=20cousin,=20great-un?= =?UTF-8?q?cle,=20sans=20virgule=20avant=20=C2=AB=20removed=20=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ordinal() : il manquait le $, un cousin au 4e degré s'affichait « {n}th cousin » - Great-uncle et great-great-uncle, la forme courante, au lieu de granduncle - « First cousin once removed », sans virgule - Style du dépôt (guillemets simples, sans points-virgules) Co-Authored-By: Claude Opus 5.5 --- frontend/src/utils/kinship/en.js | 85 +++++----------------- frontend/src/utils/kinship/kinship.test.js | 12 ++- 2 files changed, 27 insertions(+), 70 deletions(-) diff --git a/frontend/src/utils/kinship/en.js b/frontend/src/utils/kinship/en.js index a069651..71e49d0 100644 --- a/frontend/src/utils/kinship/en.js +++ b/frontend/src/utils/kinship/en.js @@ -3,39 +3,21 @@ import { kinshipFacts } from './facts.js' // Mot genré quand le sexe de la personne nommée est connu, forme neutre ou double sinon const g = (sex, male, female, neutral) => (sex === 'male' ? male : sex === 'female' ? female : neutral) +// 1st, 2nd, 3rd, 4th… 11th, 12th, 13th, 21st function ordinal(n) { - switch (n) { - case 1: - return "First" - case 2: - return "Second" - case 3: - return "Third" - default: - return `{n}th` - } + const words = ['First', 'Second', 'Third'] + if (n <= 3) return words[n - 1] + const suffix = n % 100 >= 11 && n % 100 <= 13 ? 'th' : ({ 1: 'st', 2: 'nd', 3: 'rd' }[n % 10] || 'th') + return `${n}${suffix}` } function times(n) { - switch (n) { - case 1: - return "once" - case 2: - return "twice" - case 3: - return "thrice" - default: - return `${n} times` - } + return ['once', 'twice', 'thrice'][n - 1] || `${n} times` } +// Règle des G : autant de G que de générations d'écart (grandfather, great-grandfather, great-great-grandfather) function grule(n) { - switch (n - 1) { - case 1: - return "Grand" - default: - return `Great-${"great-".repeat(n - 3)}grand` - } + return n === 2 ? 'Grand' : `Great-${'great-'.repeat(n - 3)}grand` } export default function nameEn(path) { @@ -54,50 +36,21 @@ export default function nameEn(path) { let label if (downs === 0 && ups > 0) { - switch (ups) { - case 1: - label = g(sex, 'Father', 'Mother', 'Parent') - break; - default: - label = `${grule(ups)}${g(sex, 'father', 'mother', 'parent')}` - break; - } + label = ups === 1 ? g(sex, 'Father', 'Mother', 'Parent') : `${grule(ups)}${g(sex, 'father', 'mother', 'parent')}` } else if (ups === 0 && downs > 0) { - switch (downs) { - case 1: - label = g(sex, 'Son', 'Daughter', 'Child') - break; - default: - label = `${grule(downs)}${g(sex, 'son', 'daughter', 'child')}` - break; - } - } else if (downs == 1) { - switch (ups) { - case 1: - label = g(sex, 'Brother', 'Sister', 'Sibling') - break; - case 2: - label = g(sex, 'Uncle', 'Aunt', 'Uncle/Aunt') - break; - default: - label = `${grule(ups - 1)}${g(sex, 'uncle', 'aunt', 'uncle/aunt')}` - break; - } + label = downs === 1 ? g(sex, 'Son', 'Daughter', 'Child') : `${grule(downs)}${g(sex, 'son', 'daughter', 'child')}` + } else if (downs === 1) { + if (ups === 1) label = g(sex, 'Brother', 'Sister', 'Sibling') + else if (ups === 2) label = g(sex, 'Uncle', 'Aunt', 'Uncle/Aunt') + // Great-uncle plutôt que granduncle : c'est la forme courante + else label = `Great-${'great-'.repeat(ups - 3)}${g(sex, 'uncle', 'aunt', 'uncle/aunt')}` } else if (ups === 1) { - switch (downs) { - case 2: - label = g(sex, 'Nephew', 'Niece', 'Nibling') - break; - default: - label = `${grule(downs - 1)}${g(sex, 'nephew', 'niece', 'nibling')}` - break; - } - } else if (ups >= 2 && downs >= 2) { + label = downs === 2 ? g(sex, 'Nephew', 'Niece', 'Nibling') : `${grule(downs - 1)}${g(sex, 'nephew', 'niece', 'nibling')}` + } else { const degree = Math.min(ups, downs) - 1 const removed = Math.abs(ups - downs) - if (removed === 0) label = `${ordinal(degree)} cousin` - else label = `${ordinal(degree)} cousin, ${times(removed)} removed` - } else label = `${ups + downs} steps apart` + label = removed === 0 ? `${ordinal(degree)} cousin` : `${ordinal(degree)} cousin ${times(removed)} removed` + } return hasSpouse ? `${label} by marriage` : label } diff --git a/frontend/src/utils/kinship/kinship.test.js b/frontend/src/utils/kinship/kinship.test.js index 69ad459..f268b4d 100644 --- a/frontend/src/utils/kinship/kinship.test.js +++ b/frontend/src/utils/kinship/kinship.test.js @@ -70,10 +70,14 @@ describe('nommage anglais', () => { expect(bothEn(['spouse'])).toEqual(['Partner', 'Partner', 'Partner']) expect(bothEn(['up', 'sibling', 'down'])).toEqual(['First cousin', 'First cousin', 'First cousin']) expect(bothEn(['up', 'up', 'sibling', 'down', 'down'])).toEqual(['Second cousin', 'Second cousin', 'Second cousin']) - expect(bothEn(['up', 'up', 'sibling', 'down'])).toEqual(['First cousin, once removed', 'First cousin, once removed', 'First cousin, once removed']) - expect(bothEn(['up', 'up', 'up', 'up', 'up', 'up', 'sibling', 'down', 'down', 'down'])).toEqual(['Third cousin, thrice removed', 'Third cousin, thrice removed', 'Third cousin, thrice removed']) - expect(bothEn(['up', 'up', 'up', 'sibling', 'down'])).toEqual(['First cousin, twice removed', 'First cousin, twice removed', 'First cousin, twice removed']) - expect(bothEn(['up', 'up', 'up', 'up', 'up', 'sibling', 'down'])).toEqual(['First cousin, 4 times removed', 'First cousin, 4 times removed', 'First cousin, 4 times removed']) + expect(bothEn(['up', 'up', 'sibling', 'down'])).toEqual(['First cousin once removed', 'First cousin once removed', 'First cousin once removed']) + expect(bothEn(['up', 'up', 'up', 'up', 'up', 'up', 'sibling', 'down', 'down', 'down'])).toEqual(['Third cousin thrice removed', 'Third cousin thrice removed', 'Third cousin thrice removed']) + expect(bothEn(['up', 'up', 'up', 'sibling', 'down'])).toEqual(['First cousin twice removed', 'First cousin twice removed', 'First cousin twice removed']) + expect(bothEn(['up', 'up', 'up', 'up', 'up', 'sibling', 'down'])).toEqual(['First cousin 4 times removed', 'First cousin 4 times removed', 'First cousin 4 times removed']) + expect(bothEn(['up', 'up', 'up', 'up', 'sibling', 'down', 'down', 'down', 'down'])).toEqual(['4th cousin', '4th cousin', '4th cousin']) + expect(bothEn(['up', 'up', 'sibling'])).toEqual(['Great-uncle', 'Great-aunt', 'Great-uncle/aunt']) + expect(bothEn(['up', 'up', 'up', 'sibling'])).toEqual(['Great-great-uncle', 'Great-great-aunt', 'Great-great-uncle/aunt']) + expect(bothEn(['sibling', 'down', 'down'])).toEqual(['Grandnephew', 'Grandniece', 'Grandnibling']) }) it('une langue inconnue retombe sur l’anglais', () => {